Skip to content

Commit ce48497

Browse files
cschanhniemOpenCode
andauthored
feat: Phase W W1 novelty challenge harness schema (NCH-) -- batch-level novelty gate blocking novel_batch claim when near-neighbor fraction exceeds 20%; VALID_NCH_VERDICTS (4), VALID_REFERENCE_DATABASES (6), identity threshold 0.80, NCHCandidateResult helper; 63 tests; adds Phase W to NEXT_100_PR_MAP (#1003)
Co-authored-by: OpenCode <opencode@example.com>
1 parent 33dfeed commit ce48497

3 files changed

Lines changed: 602 additions & 1 deletion

File tree

‎docs/research/NEXT_100_PR_MAP.md‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Make learning controlled rather than self-serving.
134134
| G6 | Add calibration-overfit warning when cohort is too small (complete). — calibration/overfit_warning.py: detects cohort-too-small condition and emits OverfitWarning; make calibration-overfit-check target; tests/calibration/test_overfit_warning.py. | Prevents false learning. | C |
135135
| G7 | Add result-quality flag propagation into calibration engine (complete). — calibration/result_quality.py: flags low-quality outcomes before they enter calibration; make result-quality-filter target; tests/calibration/test_result_quality.py (27 tests). | Low-quality outcomes cannot drive updates. | C |
136136
| G8 | Add policy that synthetic results cannot raise proof-ladder level (complete). | SBR- schema: 14 fields, 16 validation rules, synthetic-only evidence cannot propose level ≥4 without violations recorded, policy_enforced=True enforced, violation rate consistency check (tol 0.01); anti-overclaim boundary is now auditable artifact. | B/C |
137-
| G9 | Add calibration decision review checklist. | Human review stronger. | C/D |
137+
| G9 | Add calibration decision review checklist (complete). — calibration/decision_checklist.py: CHECKLIST_ITEMS (12 items, 11 required), CalibrationDecisionChecklist (8 fields), build_checklist(), write_checklist_json(), write_checklist_markdown(); 14 tests in tests/calibration/test_decision_checklist.py. | Human review stronger. | C/D |
138138
| G10 | Add recalibration rollback plan (complete). — calibration/rollback_plan.py: structured plan for rolling back a calibration update if quality degrades; make calibration-rollback-plan target; tests/calibration/test_rollback_plan.py. | Safer updates. | C |
139139

140140
## Phase H — Virtual assay discipline
@@ -269,3 +269,15 @@ Gate external sharing on all evidence schemas passing; seal the evidence trail f
269269
| V3 | Add scientific reproducibility seal schema (SRS-) (complete). — src/openamp_foundry/evidence/scientific_reproducibility_seal.py: sealed/provisional/invalidated statuses; human_reviewed cross-check; PENDING hash placeholder; 50 tests. | Immutable record asserting that a batch's evidence trail is complete and auditable; includes pipeline version, schema hash placeholder, and human-reviewed flag; enables preprint data availability statements. | C |
270270
| V4 | Add external review packet schema (ERP-) (complete). — src/openamp_foundry/evidence/external_review_packet.py: 5 components (BRC/ECI/FET/PTR/SRS); ready/incomplete/draft status; 45 tests. | Assembles BRC + ECI + FET + PTR + SRS into a single record listing everything a scientist needs to review the batch's computational evidence; dry-lab-only constraint explicit; closes the "what do I send to a reviewer?" question. | C |
271271
| V5 | Add Phase V completeness gate schema (V5G-) (complete). — src/openamp_foundry/evidence/phase_v_completeness_gate.py: 4 components (PRG/EBM/SRS/ERP); prefix-validated artifact IDs; ready/blocked verdict; 63 tests. | Top-level gate asserting PRG + EBM + SRS + ERP all present; closes Phase V and signals the batch is ready for external scientific review. | C |
272+
273+
## Phase W — Batch-level benchmark hardness and novelty challenges
274+
275+
Make it machine-verifiable that the pipeline produces novel candidates that beat cheap baselines. Directly addresses the strategic bottleneck: "Can the system help choose real experiments better than cheap baselines?" Every item produces a named artifact that gates further claims.
276+
277+
| ID | Task | Why it matters | Priority |
278+
|----|------|----------------|----------|
279+
| 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 |
280+
| W2 | Add charge-matched challenge schema (CMC-). | 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 |
282+
| 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 |
283+
| 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: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
"""NCH- novelty challenge harness schema.
2+
3+
Batch-level novelty challenge record: documents what fraction of top
4+
candidates have high sequence identity (≥ threshold) to known AMPs in a
5+
reference database. Blocks 'novel family' claims at the batch level when
6+
the near-neighbor fraction is above the allowed ceiling.
7+
8+
This schema operates at batch level; per-family novelty is captured in
9+
the FNR- (family novelty report) schema. NCH- provides the aggregate
10+
machine-verifiable gate.
11+
"""
12+
13+
from __future__ import annotations
14+
15+
from dataclasses import dataclass
16+
17+
VALID_NCH_VERDICTS: frozenset[str] = frozenset({
18+
"novel_batch",
19+
"mixed_novelty",
20+
"near_neighbor_dominated",
21+
"challenge_not_run",
22+
})
23+
24+
VALID_REFERENCE_DATABASES: frozenset[str] = frozenset({
25+
"APD3",
26+
"DRAMP",
27+
"DBAASP",
28+
"CAMP",
29+
"LAMP",
30+
"custom",
31+
})
32+
33+
NEAR_NEIGHBOR_IDENTITY_THRESHOLD: float = 0.80
34+
NOVEL_BATCH_CEILING: float = 0.20
35+
NEAR_NEIGHBOR_DOMINATED_FLOOR: float = 0.60
36+
37+
38+
@dataclass
39+
class NCHCandidateResult:
40+
candidate_id: str
41+
max_identity_to_known: float
42+
is_near_neighbor: bool
43+
closest_known_amp_id: str
44+
45+
46+
@dataclass
47+
class NoveltyChallengeHarness:
48+
nch_id: str
49+
batch_id: str
50+
pipeline_version: str
51+
reference_database: str
52+
identity_threshold: float
53+
n_candidates_checked: int
54+
n_near_neighbors: int
55+
near_neighbor_fraction: float
56+
candidate_results: list[NCHCandidateResult]
57+
nch_verdict: str
58+
dry_lab_only: bool
59+
limitations: list[str]
60+
created_at: str
61+
62+
63+
def validate_novelty_challenge_harness(nch: NoveltyChallengeHarness) -> None:
64+
if not nch.nch_id.startswith("NCH-"):
65+
raise ValueError(f"nch_id must start with 'NCH-': {nch.nch_id!r}")
66+
if not nch.batch_id:
67+
raise ValueError("batch_id must be non-empty")
68+
if not nch.pipeline_version:
69+
raise ValueError("pipeline_version must be non-empty")
70+
if nch.reference_database not in VALID_REFERENCE_DATABASES:
71+
raise ValueError(
72+
f"reference_database {nch.reference_database!r} not in VALID_REFERENCE_DATABASES"
73+
)
74+
if not (0.0 < nch.identity_threshold <= 1.0):
75+
raise ValueError(
76+
f"identity_threshold must be in (0, 1]: {nch.identity_threshold}"
77+
)
78+
if nch.n_candidates_checked < 0:
79+
raise ValueError("n_candidates_checked must be non-negative")
80+
if nch.n_near_neighbors < 0:
81+
raise ValueError("n_near_neighbors must be non-negative")
82+
if nch.n_near_neighbors > nch.n_candidates_checked:
83+
raise ValueError("n_near_neighbors cannot exceed n_candidates_checked")
84+
for cr in nch.candidate_results:
85+
if not (0.0 <= cr.max_identity_to_known <= 1.0):
86+
raise ValueError(
87+
f"max_identity_to_known must be in [0, 1]: {cr.max_identity_to_known}"
88+
)
89+
expected_nn = cr.max_identity_to_known >= nch.identity_threshold
90+
if cr.is_near_neighbor != expected_nn:
91+
raise ValueError(
92+
f"is_near_neighbor mismatch for {cr.candidate_id!r}: "
93+
f"identity={cr.max_identity_to_known}, threshold={nch.identity_threshold}"
94+
)
95+
if nch.n_candidates_checked != len(nch.candidate_results):
96+
raise ValueError("n_candidates_checked must equal len(candidate_results)")
97+
expected_nn_count = sum(1 for cr in nch.candidate_results if cr.is_near_neighbor)
98+
if nch.n_near_neighbors != expected_nn_count:
99+
raise ValueError("n_near_neighbors mismatch")
100+
if nch.n_candidates_checked == 0:
101+
expected_fraction = 0.0
102+
else:
103+
expected_fraction = round(
104+
nch.n_near_neighbors / nch.n_candidates_checked, 6
105+
)
106+
if abs(nch.near_neighbor_fraction - expected_fraction) > 1e-4:
107+
raise ValueError(
108+
f"near_neighbor_fraction {nch.near_neighbor_fraction} does not match "
109+
f"computed {expected_fraction}"
110+
)
111+
if nch.nch_verdict not in VALID_NCH_VERDICTS:
112+
raise ValueError(
113+
f"nch_verdict {nch.nch_verdict!r} not in VALID_NCH_VERDICTS"
114+
)
115+
if not nch.dry_lab_only:
116+
raise ValueError("dry_lab_only must be True")
117+
if not nch.limitations:
118+
raise ValueError("limitations must be non-empty")
119+
if not nch.created_at:
120+
raise ValueError("created_at must be non-empty")
121+
122+
123+
def _compute_verdict(
124+
n_candidates: int,
125+
near_neighbor_fraction: float,
126+
) -> str:
127+
if n_candidates == 0:
128+
return "challenge_not_run"
129+
if near_neighbor_fraction <= NOVEL_BATCH_CEILING:
130+
return "novel_batch"
131+
if near_neighbor_fraction >= NEAR_NEIGHBOR_DOMINATED_FLOOR:
132+
return "near_neighbor_dominated"
133+
return "mixed_novelty"
134+
135+
136+
def build_novelty_challenge_harness(
137+
*,
138+
nch_id: str,
139+
batch_id: str,
140+
pipeline_version: str,
141+
reference_database: str,
142+
identity_threshold: float = NEAR_NEIGHBOR_IDENTITY_THRESHOLD,
143+
candidate_result_dicts: list[dict],
144+
limitations: list[str],
145+
created_at: str,
146+
) -> NoveltyChallengeHarness:
147+
"""Build a NoveltyChallengeHarness.
148+
149+
candidate_result_dicts: list of dicts with keys:
150+
candidate_id (str), max_identity_to_known (float),
151+
closest_known_amp_id (str, optional, default "")
152+
"""
153+
candidate_results = []
154+
for d in candidate_result_dicts:
155+
identity = float(d["max_identity_to_known"])
156+
is_nn = identity >= identity_threshold
157+
candidate_results.append(
158+
NCHCandidateResult(
159+
candidate_id=d["candidate_id"],
160+
max_identity_to_known=identity,
161+
is_near_neighbor=is_nn,
162+
closest_known_amp_id=d.get("closest_known_amp_id", ""),
163+
)
164+
)
165+
n = len(candidate_results)
166+
n_nn = sum(1 for cr in candidate_results if cr.is_near_neighbor)
167+
fraction = round(n_nn / n, 6) if n > 0 else 0.0
168+
verdict = _compute_verdict(n, fraction)
169+
nch = NoveltyChallengeHarness(
170+
nch_id=nch_id,
171+
batch_id=batch_id,
172+
pipeline_version=pipeline_version,
173+
reference_database=reference_database,
174+
identity_threshold=identity_threshold,
175+
n_candidates_checked=n,
176+
n_near_neighbors=n_nn,
177+
near_neighbor_fraction=fraction,
178+
candidate_results=candidate_results,
179+
nch_verdict=verdict,
180+
dry_lab_only=True,
181+
limitations=limitations,
182+
created_at=created_at,
183+
)
184+
validate_novelty_challenge_harness(nch)
185+
return nch
186+
187+
188+
def format_novelty_challenge_harness(nch: NoveltyChallengeHarness) -> str:
189+
lines = [
190+
f"Novelty Challenge Harness — {nch.nch_id}",
191+
f"Batch: {nch.batch_id} | Pipeline: {nch.pipeline_version}",
192+
f"Reference DB: {nch.reference_database} | Identity threshold: {nch.identity_threshold:.0%}",
193+
f"Verdict: {nch.nch_verdict}",
194+
f"Near-neighbors: {nch.n_near_neighbors}/{nch.n_candidates_checked} "
195+
f"({nch.near_neighbor_fraction:.1%})",
196+
]
197+
if nch.candidate_results:
198+
lines.append("Top candidates:")
199+
for cr in nch.candidate_results[:5]:
200+
nn_label = "NEAR-NEIGHBOR" if cr.is_near_neighbor else "novel"
201+
lines.append(
202+
f" {cr.candidate_id}: {cr.max_identity_to_known:.1%} identity [{nn_label}]"
203+
)
204+
if len(nch.candidate_results) > 5:
205+
lines.append(f" ... ({len(nch.candidate_results) - 5} more)")
206+
lines.append(f"Created: {nch.created_at}")
207+
lines.append(f"Limitations: {'; '.join(nch.limitations)}")
208+
lines.append(f"dry_lab_only: {nch.dry_lab_only}")
209+
return "\n".join(lines)

0 commit comments

Comments
 (0)