Skip to content

Commit dcabc17

Browse files
cschanhniemOpenCode
andauthored
feat: Phase W W3 similarity challenge harness schema (SCH-) -- flags proximity-driven selection vs random draw; VALID_SCH_VERDICTS (4), SELECTION_VALUE_GAP_THRESHOLD=0.10, SimilarityGroupStats helper; gap auto-computed; dry_lab_only=True; 60 tests (#1005)
Co-authored-by: OpenCode <opencode@example.com>
1 parent 431eec1 commit dcabc17

3 files changed

Lines changed: 493 additions & 1 deletion

File tree

‎docs/research/NEXT_100_PR_MAP.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,6 @@ Make it machine-verifiable that the pipeline produces novel candidates that beat
278278
|----|------|----------------|----------|
279279
| W1 | Add novelty challenge harness schema (NCH-) (complete). — src/openamp_foundry/evidence/novelty_challenge_harness.py: VALID_NCH_VERDICTS (4: novel_batch/mixed_novelty/near_neighbor_dominated/challenge_not_run), VALID_REFERENCE_DATABASES (6), NEAR_NEIGHBOR_IDENTITY_THRESHOLD=0.80, NOVEL_BATCH_CEILING=0.20, NEAR_NEIGHBOR_DOMINATED_FLOOR=0.60; NCHCandidateResult helper; build() auto-computes is_near_neighbor from identity vs threshold, fraction, verdict; dry_lab_only=True enforced; 63 tests in tests/evidence/test_novelty_challenge_harness.py. | Batch-level novelty challenge: documents the fraction of top candidates with ≥80% sequence identity to a known AMP in a reference database (APD3/DRAMP/etc.); blocks novel_batch claim when near-neighbor fraction exceeds 20%; prevents pipeline from advancing near-copies of known AMPs under a novelty label. | C |
280280
| W2 | Add charge-matched challenge schema (CMC-) (complete). — src/openamp_foundry/evidence/charge_matched_challenge.py: VALID_CMC_VERDICTS (4: gap_meaningful/gap_marginal/gap_absent/challenge_not_run), VALID_CHARGE_BASELINE_METHODS (4), MEANINGFUL_GAP_THRESHOLD=0.05, MARGINAL_GAP_LOWER=0.02; auroc_gap auto-computed; verdict auto-derived; dry_lab_only=True enforced; 60 tests in tests/evidence/test_charge_matched_challenge.py. | Formally documents the charge-matched challenge: compares pipeline AUROC vs a charge-only baseline on the same candidate set; verdict controlled vocabulary (gap_meaningful/gap_marginal/gap_absent/not_run); blocks performance claims when the charge-only baseline explains the gap. | C |
281-
| W3 | Add similarity challenge harness schema (SCH-). | Documents whether pipeline-selected candidates are systematically more similar to known AMPs than random selection from the sequence space; flags selection bias from similarity clustering; prevents "novel panel" claim when selection is proximity-driven. | C |
281+
| W3 | Add similarity challenge harness schema (SCH-) (complete). — src/openamp_foundry/evidence/similarity_challenge_harness.py: VALID_SCH_VERDICTS (4: selection_adds_value/marginal_improvement/proximity_driven/challenge_not_run), VALID_SIMILARITY_METRICS (4), SELECTION_VALUE_GAP_THRESHOLD=0.10, MARGINAL_IMPROVEMENT_LOWER=0.03; SimilarityGroupStats helper; similarity_gap auto-computed; dry_lab_only=True enforced; 60 tests in tests/evidence/test_similarity_challenge_harness.py. | Documents whether pipeline-selected candidates are systematically more similar to known AMPs than random selection from the sequence space; flags selection bias from similarity clustering; prevents "novel panel" claim when selection is proximity-driven. | C |
282282
| W4 | Add benchmark challenge registry schema (BCR-). | Machine-readable registry of which benchmark challenges (NCH/CMC/SCH) have been run and passed for a given pipeline version; aggregates challenge verdicts; overall hardness grade (A: all passed, B: most passed, C: some passed, D: none passed). | C |
283283
| W5 | Add Phase W benchmark gate (WBG-). | Top-level gate asserting NCH + CMC + SCH + BCR all present; overall verdict: hardened/partially_hardened/not_hardened; closes Phase W; no batch-level performance claim is credible without passing this gate. | C |
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
"""SCH- similarity challenge harness schema.
2+
3+
Documents whether pipeline-selected candidates are systematically more similar
4+
to known AMPs than a random draw from the same sequence space. Flags selection
5+
bias from proximity clustering: if the pipeline is essentially selecting
6+
near-neighbors of known AMPs rather than exploring new space, novelty claims
7+
are not credible.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
from dataclasses import dataclass
13+
14+
VALID_SCH_VERDICTS: frozenset[str] = frozenset({
15+
"selection_adds_value",
16+
"marginal_improvement",
17+
"proximity_driven",
18+
"challenge_not_run",
19+
})
20+
21+
VALID_SIMILARITY_METRICS: frozenset[str] = frozenset({
22+
"sequence_identity",
23+
"blosum62_score",
24+
"edit_distance",
25+
"physicochemical_distance",
26+
})
27+
28+
SELECTION_VALUE_GAP_THRESHOLD: float = 0.10
29+
MARGINAL_IMPROVEMENT_LOWER: float = 0.03
30+
31+
32+
@dataclass
33+
class SimilarityGroupStats:
34+
group_label: str
35+
mean_similarity_to_known: float
36+
n_sequences: int
37+
38+
39+
@dataclass
40+
class SimilarityChallengeHarness:
41+
sch_id: str
42+
batch_id: str
43+
pipeline_version: str
44+
similarity_metric: str
45+
pipeline_group: SimilarityGroupStats
46+
random_group: SimilarityGroupStats
47+
similarity_gap: float
48+
sch_verdict: str
49+
dry_lab_only: bool
50+
limitations: list[str]
51+
created_at: str
52+
53+
54+
def validate_similarity_challenge_harness(sch: SimilarityChallengeHarness) -> None:
55+
if not sch.sch_id.startswith("SCH-"):
56+
raise ValueError(f"sch_id must start with 'SCH-': {sch.sch_id!r}")
57+
if not sch.batch_id:
58+
raise ValueError("batch_id must be non-empty")
59+
if not sch.pipeline_version:
60+
raise ValueError("pipeline_version must be non-empty")
61+
if sch.similarity_metric not in VALID_SIMILARITY_METRICS:
62+
raise ValueError(
63+
f"similarity_metric {sch.similarity_metric!r} not in VALID_SIMILARITY_METRICS"
64+
)
65+
for group in (sch.pipeline_group, sch.random_group):
66+
if not (0.0 <= group.mean_similarity_to_known <= 1.0):
67+
raise ValueError(
68+
f"mean_similarity_to_known must be in [0, 1]: {group.mean_similarity_to_known}"
69+
)
70+
if group.n_sequences < 0:
71+
raise ValueError(
72+
f"n_sequences must be non-negative: {group.n_sequences}"
73+
)
74+
expected_gap = round(
75+
sch.pipeline_group.mean_similarity_to_known
76+
- sch.random_group.mean_similarity_to_known,
77+
6,
78+
)
79+
if abs(sch.similarity_gap - expected_gap) > 1e-4:
80+
raise ValueError(
81+
f"similarity_gap {sch.similarity_gap} does not match computed "
82+
f"{expected_gap}"
83+
)
84+
if sch.sch_verdict not in VALID_SCH_VERDICTS:
85+
raise ValueError(
86+
f"sch_verdict {sch.sch_verdict!r} not in VALID_SCH_VERDICTS"
87+
)
88+
if not sch.dry_lab_only:
89+
raise ValueError("dry_lab_only must be True")
90+
if not sch.limitations:
91+
raise ValueError("limitations must be non-empty")
92+
if not sch.created_at:
93+
raise ValueError("created_at must be non-empty")
94+
95+
96+
def _compute_verdict(
97+
n_pipeline: int,
98+
n_random: int,
99+
similarity_gap: float,
100+
) -> str:
101+
if n_pipeline == 0 or n_random == 0:
102+
return "challenge_not_run"
103+
if similarity_gap >= SELECTION_VALUE_GAP_THRESHOLD:
104+
return "selection_adds_value"
105+
if similarity_gap >= MARGINAL_IMPROVEMENT_LOWER:
106+
return "marginal_improvement"
107+
return "proximity_driven"
108+
109+
110+
def build_similarity_challenge_harness(
111+
*,
112+
sch_id: str,
113+
batch_id: str,
114+
pipeline_version: str,
115+
similarity_metric: str,
116+
pipeline_mean_similarity: float,
117+
pipeline_n_sequences: int,
118+
random_mean_similarity: float,
119+
random_n_sequences: int,
120+
limitations: list[str],
121+
created_at: str,
122+
) -> SimilarityChallengeHarness:
123+
pipeline_group = SimilarityGroupStats(
124+
group_label="pipeline_selected",
125+
mean_similarity_to_known=pipeline_mean_similarity,
126+
n_sequences=pipeline_n_sequences,
127+
)
128+
random_group = SimilarityGroupStats(
129+
group_label="random_draw",
130+
mean_similarity_to_known=random_mean_similarity,
131+
n_sequences=random_n_sequences,
132+
)
133+
gap = round(pipeline_mean_similarity - random_mean_similarity, 6)
134+
verdict = _compute_verdict(pipeline_n_sequences, random_n_sequences, gap)
135+
sch = SimilarityChallengeHarness(
136+
sch_id=sch_id,
137+
batch_id=batch_id,
138+
pipeline_version=pipeline_version,
139+
similarity_metric=similarity_metric,
140+
pipeline_group=pipeline_group,
141+
random_group=random_group,
142+
similarity_gap=gap,
143+
sch_verdict=verdict,
144+
dry_lab_only=True,
145+
limitations=limitations,
146+
created_at=created_at,
147+
)
148+
validate_similarity_challenge_harness(sch)
149+
return sch
150+
151+
152+
def format_similarity_challenge_harness(sch: SimilarityChallengeHarness) -> str:
153+
lines = [
154+
f"Similarity Challenge Harness — {sch.sch_id}",
155+
f"Batch: {sch.batch_id} | Pipeline: {sch.pipeline_version}",
156+
f"Similarity metric: {sch.similarity_metric}",
157+
f"Verdict: {sch.sch_verdict}",
158+
f"Pipeline selected: mean={sch.pipeline_group.mean_similarity_to_known:.4f} "
159+
f"(n={sch.pipeline_group.n_sequences})",
160+
f"Random draw: mean={sch.random_group.mean_similarity_to_known:.4f} "
161+
f"(n={sch.random_group.n_sequences})",
162+
f"Gap (pipeline - random): {sch.similarity_gap:+.4f}",
163+
f"Created: {sch.created_at}",
164+
f"Limitations: {'; '.join(sch.limitations)}",
165+
f"dry_lab_only: {sch.dry_lab_only}",
166+
]
167+
return "\n".join(lines)

0 commit comments

Comments
 (0)