Skip to content

Commit 2a1e6c9

Browse files
cschanhniemOpenCode
andauthored
feat: Phase X X3 learning progress report schema (LPR-) -- human-readable per-pipeline-version summary of feature predictivity and learning trajectory across batches (#851) (#1010)
VALID_LPR_VERDICTS (learning_confirmed/learning_inconclusive/no_learning_signal/insufficient_data), VALID_FEATURE_PREDICTIVITY (predictive/not_predictive/uncertain), VALID_FEATURE_CATEGORIES (8: charge/hydrophobicity/length/amphipathicity/helicity/sequence_motif/secondary_structure/physicochemical_composite). FeatureLearningEntry helper; verdict auto-computed from predictive vs not-predictive count; insufficient_data when n_batches=0 or no features with definitive predictivity. dry_lab_only=True enforced; cit_id validated (CIT- prefix); 58 tests. BASELINE updated 9830→10367. Co-authored-by: OpenCode <opencode@example.com>
1 parent 7740d25 commit 2a1e6c9

4 files changed

Lines changed: 514 additions & 2 deletions

File tree

‎docs/research/NEXT_100_PR_MAP.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,6 @@ Track whether the pipeline actually improves across batches by capturing per-bat
290290
|----|------|----------------|----------|
291291
| X1 | Add multi-batch learning record schema (MBL-) (complete). — src/openamp_foundry/evidence/multi_batch_learning_record.py: VALID_MBL_QUALITY_GRADES (5: A-D/N/A), VALID_BATCH_LEARNING_STATUSES (3), GRADE_A_HIT_RATE=0.40; hit_rate auto-computed from n_confirmed_hits/n_candidates_tested; grade auto-derived; no_wet_lab_data forces N/A grade; whr_ids list; dry_lab_only=True; 61 tests in tests/evidence/test_multi_batch_learning_record.py. | Per-batch snapshot of prediction quality (hit rate, AUROC, n_confirmed_hits) after wet-lab feedback; enables cross-batch comparison of whether the pipeline is learning; feeds into calibration improvement tracker. | C |
292292
| X2 | Add calibration improvement tracker schema (CIT-) (complete). — src/openamp_foundry/evidence/calibration_improvement_tracker.py: VALID_CIT_TREND_DIRECTIONS (4: improving/stable/degrading/insufficient_data), VALID_CIT_SUMMARY_GRADES (5: A-D/N/A), MIN_BATCHES_FOR_TREND=2, IMPROVEMENT_THRESHOLD=0.05; BatchHitRateEntry helper; trend auto-computed from first→latest hit_rate delta; insufficient_data forces N/A grade; dry_lab_only=True; 49 tests. | Aggregates MBL records across batches; computes hit-rate trend direction (improving/stable/degrading/insufficient_data); minimum 2 batches required; flags when calibration is not producing measurable improvement. | C |
293-
| X3 | Add learning progress report schema (LPR-). | Human-readable summary of what the pipeline has learned from all batches to date; references CIT- for trend data; includes which candidate features proved predictive vs not; links to calibration decision logs. | C |
293+
| X3 | Add learning progress report schema (LPR-) (complete). — src/openamp_foundry/evidence/learning_progress_report.py: VALID_LPR_VERDICTS (4: learning_confirmed/learning_inconclusive/no_learning_signal/insufficient_data), VALID_FEATURE_PREDICTIVITY (3: predictive/not_predictive/uncertain), VALID_FEATURE_CATEGORIES (8); FeatureLearningEntry helper; verdict auto-computed: learning_confirmed (n_pred>n_non), learning_inconclusive (equal+>0), no_learning_signal (n_pred<n_non), insufficient_data (no batches or no definitive features); dry_lab_only=True; 58 tests in tests/evidence/test_learning_progress_report.py. | Human-readable summary of what the pipeline has learned from all batches to date; references CIT- for trend data; includes which candidate features proved predictive vs not; links to calibration decision logs. | C |
294294
| X4 | Add recalibration confidence certificate schema (RCC-). | Asserts with what confidence the current calibration weights are reliable based on cohort size, quality, and consistency across batches; A/B/C/D grade; prevents overconfident calibration claims. | C |
295295
| X5 | Add Phase X learning gate (XLG-). | Top-level gate asserting MBL + CIT + LPR + RCC all present; overall verdict: learning_verified/learning_in_progress/learning_not_started; closes Phase X; no calibration improvement claim is credible without passing this gate. | C |
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""LPR- learning progress report schema.
2+
3+
Human-readable summary of what the pipeline has learned from all batches to
4+
date. References a CIT- tracker for trend data, lists which candidate feature
5+
categories proved predictive vs not, and links to calibration decision logs.
6+
Gives a scientist a single document to understand the pipeline's learning
7+
trajectory.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
from dataclasses import dataclass
13+
14+
VALID_LPR_VERDICTS: frozenset[str] = frozenset({
15+
"learning_confirmed",
16+
"learning_inconclusive",
17+
"no_learning_signal",
18+
"insufficient_data",
19+
})
20+
21+
VALID_FEATURE_PREDICTIVITY: frozenset[str] = frozenset({
22+
"predictive",
23+
"not_predictive",
24+
"uncertain",
25+
})
26+
27+
VALID_FEATURE_CATEGORIES: frozenset[str] = frozenset({
28+
"charge",
29+
"hydrophobicity",
30+
"length",
31+
"amphipathicity",
32+
"helicity",
33+
"sequence_motif",
34+
"secondary_structure",
35+
"physicochemical_composite",
36+
})
37+
38+
39+
@dataclass
40+
class FeatureLearningEntry:
41+
feature_category: str
42+
predictivity: str
43+
evidence_summary: str
44+
45+
46+
@dataclass
47+
class LearningProgressReport:
48+
lpr_id: str
49+
pipeline_version: str
50+
cit_id: str
51+
n_batches_summarized: int
52+
feature_entries: list[FeatureLearningEntry]
53+
n_predictive_features: int
54+
n_non_predictive_features: int
55+
lpr_verdict: str
56+
key_findings: list[str]
57+
dry_lab_only: bool
58+
limitations: list[str]
59+
created_at: str
60+
61+
62+
def validate_learning_progress_report(lpr: LearningProgressReport) -> None:
63+
if not lpr.lpr_id.startswith("LPR-"):
64+
raise ValueError(f"lpr_id must start with 'LPR-': {lpr.lpr_id!r}")
65+
if not lpr.pipeline_version:
66+
raise ValueError("pipeline_version must be non-empty")
67+
if not lpr.cit_id.startswith("CIT-"):
68+
raise ValueError(f"cit_id must start with 'CIT-': {lpr.cit_id!r}")
69+
if lpr.n_batches_summarized < 0:
70+
raise ValueError("n_batches_summarized must be non-negative")
71+
for entry in lpr.feature_entries:
72+
if entry.feature_category not in VALID_FEATURE_CATEGORIES:
73+
raise ValueError(
74+
f"feature_category {entry.feature_category!r} not in VALID_FEATURE_CATEGORIES"
75+
)
76+
if entry.predictivity not in VALID_FEATURE_PREDICTIVITY:
77+
raise ValueError(
78+
f"predictivity {entry.predictivity!r} not in VALID_FEATURE_PREDICTIVITY"
79+
)
80+
n_pred = sum(1 for e in lpr.feature_entries if e.predictivity == "predictive")
81+
n_non = sum(1 for e in lpr.feature_entries if e.predictivity == "not_predictive")
82+
if lpr.n_predictive_features != n_pred:
83+
raise ValueError("n_predictive_features mismatch")
84+
if lpr.n_non_predictive_features != n_non:
85+
raise ValueError("n_non_predictive_features mismatch")
86+
if lpr.lpr_verdict not in VALID_LPR_VERDICTS:
87+
raise ValueError(
88+
f"lpr_verdict {lpr.lpr_verdict!r} not in VALID_LPR_VERDICTS"
89+
)
90+
if not lpr.key_findings:
91+
raise ValueError("key_findings must be non-empty")
92+
if not lpr.dry_lab_only:
93+
raise ValueError("dry_lab_only must be True")
94+
if not lpr.limitations:
95+
raise ValueError("limitations must be non-empty")
96+
if not lpr.created_at:
97+
raise ValueError("created_at must be non-empty")
98+
99+
100+
def _compute_verdict(
101+
n_batches: int,
102+
n_predictive: int,
103+
n_non_predictive: int,
104+
) -> str:
105+
if n_batches == 0:
106+
return "insufficient_data"
107+
if n_predictive == 0 and n_non_predictive == 0:
108+
return "insufficient_data"
109+
if n_predictive > n_non_predictive:
110+
return "learning_confirmed"
111+
if n_predictive == n_non_predictive and n_predictive > 0:
112+
return "learning_inconclusive"
113+
return "no_learning_signal"
114+
115+
116+
def build_learning_progress_report(
117+
*,
118+
lpr_id: str,
119+
pipeline_version: str,
120+
cit_id: str,
121+
n_batches_summarized: int,
122+
feature_entry_dicts: list[dict],
123+
key_findings: list[str],
124+
limitations: list[str],
125+
created_at: str,
126+
) -> LearningProgressReport:
127+
"""Build a LearningProgressReport.
128+
129+
feature_entry_dicts: list of dicts with keys:
130+
feature_category, predictivity, evidence_summary (optional, default "")
131+
"""
132+
entries = [
133+
FeatureLearningEntry(
134+
feature_category=d["feature_category"],
135+
predictivity=d["predictivity"],
136+
evidence_summary=d.get("evidence_summary", ""),
137+
)
138+
for d in feature_entry_dicts
139+
]
140+
n_pred = sum(1 for e in entries if e.predictivity == "predictive")
141+
n_non = sum(1 for e in entries if e.predictivity == "not_predictive")
142+
verdict = _compute_verdict(n_batches_summarized, n_pred, n_non)
143+
lpr = LearningProgressReport(
144+
lpr_id=lpr_id,
145+
pipeline_version=pipeline_version,
146+
cit_id=cit_id,
147+
n_batches_summarized=n_batches_summarized,
148+
feature_entries=entries,
149+
n_predictive_features=n_pred,
150+
n_non_predictive_features=n_non,
151+
lpr_verdict=verdict,
152+
key_findings=list(key_findings),
153+
dry_lab_only=True,
154+
limitations=limitations,
155+
created_at=created_at,
156+
)
157+
validate_learning_progress_report(lpr)
158+
return lpr
159+
160+
161+
def format_learning_progress_report(lpr: LearningProgressReport) -> str:
162+
lines = [
163+
f"Learning Progress Report — {lpr.lpr_id}",
164+
f"Pipeline: {lpr.pipeline_version} | CIT: {lpr.cit_id}",
165+
f"Verdict: {lpr.lpr_verdict}",
166+
f"Batches summarized: {lpr.n_batches_summarized}",
167+
f"Features: {lpr.n_predictive_features} predictive, "
168+
f"{lpr.n_non_predictive_features} not predictive",
169+
]
170+
if lpr.feature_entries:
171+
lines.append("Feature categories:")
172+
for entry in lpr.feature_entries:
173+
lines.append(f" {entry.feature_category}: {entry.predictivity}")
174+
lines.append("Key findings:")
175+
for finding in lpr.key_findings:
176+
lines.append(f" - {finding}")
177+
lines.append(f"Created: {lpr.created_at}")
178+
lines.append(f"Limitations: {'; '.join(lpr.limitations)}")
179+
lines.append(f"dry_lab_only: {lpr.dry_lab_only}")
180+
return "\n".join(lines)

0 commit comments

Comments
 (0)