Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,8 @@ Prepare:

After qualified assay data exists, build:

**v0.5.19 progress:** `openamp-foundry calibration-intake` joins pilot panel predictions with validated lab result actuals and produces a per-candidate report. Cohort metrics are gated by `MIN_COHORT_SIZE=5` to prevent small-sample theater. The module is descriptive only — recalibration requires a separate human-reviewed decision log (`docs/DECISION_RULES.md`). Synthetic-but-clearly-labeled example data is in `examples/lab_results/`. 29 new tests, total 1614 passing.

* lab-result ingestion;
* hit/failure calibration;
* retrospective analysis of why the model was right or wrong;
Expand Down
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,52 @@ improvements made before committing the ~$10k wet-lab synthesis budget.
- Ensemble scoring with configurable weights
- JSONL output with per-candidate `selected` field
- `pipeline.yaml` and `phase3.yaml` configs

---

## [Unreleased — Calibration Intake Module] — 2026-07-04

### v0.5.19 — Calibration Intake Module

- **`openamp_foundry/calibration/intake.py`**: new module
- `build_calibration_intake_report(panel_csv, results_dir)` joins a pilot
panel CSV (computational predictions) with a directory of validated lab
result JSON files (experimental actuals) on `candidate_id`.
- `write_calibration_intake_json` and `write_calibration_intake_markdown`
produce machine-readable and human-readable review artifacts.
- **Honest minimum-cohort-size gate (`MIN_COHORT_SIZE=5`)**: aggregate
cohort metrics are NOT reported when `n < 5`. Below the gate the metric
is marked `insufficient_data: True` and no point estimate is produced.
This prevents small-sample theater.
- Two pilot cohort metrics: `activity_vs_active_mic` (MIC ≤ 32 µg/mL active)
and `rich_selectivity_vs_high_hemolysis` (hemolysis ≥ 10% high-risk).
- Per-candidate join rows expose every prediction column plus aggregated
actual outcomes. Control failures and orphan lab results surfaced.
- **`cli/commands/reports.py`**: new `_run_calibration_intake` command.
- **`cli/main.py`**: new `calibration-intake` subcommand registered.
- **`examples/lab_results/`**: 5 SYNTHETIC JSON files demonstrating the
workflow (active hit, inactive candidate, low hemolysis, high hemolysis,
control failure). All files include a `disclaimer` field that explicitly
states they are synthetic examples. README in the directory repeats the
warning prominently.
- **`examples/lab_results_panel.csv`**: 8-candidate synthetic pilot panel.
- **`tests/test_calibration_intake.py`**: 29 tests covering empty panels,
missing columns, orphan detection, control failure surfacing, cohort
metric gating (below/equal/above `MIN_COHORT_SIZE`), hemolysis direction
logic, synthetic-example schema validation, threshold constants.
- **`Makefile`**: `lab-result-intake` and `lab-result-intake-example`
targets added. Test-count help text updated.
- **Total tests:** 1614 passing (was 1585).

#### Honest limitations

- This module does **NOT** trigger recalibration. It is the intake valve that
produces a review artifact for a separate, human-reviewed recalibration
workflow (see `docs/DECISION_RULES.md` and `docs/WAVE2_PLAN.md`).
- Cohort metrics are descriptive only; they do not validate the pipeline, do
not control for selection bias from the pre-registered shortlist, and must
not be used to rewrite scoring weights after the fact.
- Synthetic example data is clearly labeled in every JSON file's `disclaimer`
field and in `examples/lab_results/README.md`. It exists solely to exercise
the intake workflow end-to-end. When real validated lab results arrive,
replace the example directory — the pipeline itself does not change.
25 changes: 24 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ help:
@echo " make wave0-5-evidence Re-generate evidence certificates"
@echo " make wave0-5b-generate Generate Wave 0.5b candidates (safety-optimized, no aromatics)"
@echo " make wave0-5b-filter Filter Wave 0.5b shortlist (depends on wave0-5b-generate)"
@echo " make test Run full test suite (1287 tests, ≥80% coverage)"
@echo " make lab-result-intake Join a pilot panel CSV with lab result JSON files"
@echo " make lab-result-intake-example Run intake on the synthetic example data"
@echo " make test Run full test suite (1614 tests, >=80% coverage)"
@echo " make coverage Test suite with per-module coverage report"
@echo " make lint Ruff lint check on src/ tests/ scripts/"
@echo " make typecheck mypy type check on src/"
Expand Down Expand Up @@ -242,6 +244,27 @@ novelty-broad: pilot
--references-csv examples/known_reference/amp_curated_references.csv \
--out outputs/novelty_broad_report.md

lab-result-intake-example:
PYTHONPATH=src $(PYTHON) -m openamp_foundry.cli calibration-intake \
--panel examples/lab_results_panel.csv \
--results-dir examples/lab_results \
--out-json outputs/calibration_intake_example.json \
--out-md outputs/calibration_intake_example.md

lab-result-intake:
@if [ -z "$(PANEL)" ] || [ -z "$(RESULTS_DIR)" ]; then \
echo "Usage: make lab-result-intake PANEL=<panel.csv> RESULTS_DIR=<lab_results_dir>"; \
echo ""; \
echo "Or run the synthetic example without arguments:"; \
echo " make lab-result-intake-example"; \
exit 1; \
fi
PYTHONPATH=src $(PYTHON) -m openamp_foundry.cli calibration-intake \
--panel "$(PANEL)" \
--results-dir "$(RESULTS_DIR)" \
--out-json outputs/calibration_intake.json \
--out-md outputs/calibration_intake.md

clean:
rm -rf outputs/*.jsonl outputs/*.md outputs/*.json outputs/evidence outputs/phase3_evidence .pytest_cache .ruff_cache

Expand Down
4 changes: 3 additions & 1 deletion docs/METRICS_CURRENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Machine-readable snapshot: `outputs/metrics_snapshot.json` regenerated with `mak
> **Purpose:** One authoritative table of current pipeline metrics. If any doc disagrees
> with this file, this file wins. Updated whenever benchmark/benchmark config changes.
>
> **Last updated:** 2026-07-03 (rich selectivity integrated into production pipeline)
> **Last updated:** 2026-07-04 (calibration intake module added)
> **New in v0.5.19:** `openamp-foundry calibration-intake` joins pilot panel predictions with lab result actuals. Descriptive only; does NOT trigger recalibration.
> **Pipeline version:** v0.5.x
> **Branch:** main

Expand Down Expand Up @@ -713,4 +714,5 @@ Decoys score low on activity. Selective AMPs score moderately on both.
| 2026-07-03 | **Rich selectivity integrated into production pipeline:** rich_selectivity_score now computed in score_candidates() (pipeline.py), replaces hemolysis_safety as the expert composite hemolysis-risk component (weight 0.10), used in pilot_priority formula, displayed in pilot panel report, and included in evidence certificates. Expert AUROC drops 0.7119→0.7097 (−0.0022) — acceptable tradeoff: the expert now includes a significant hemolysis detector (CI excludes 0.5) instead of the old non-significant one. | OpenAMP loop |
| 2026-07-03 | **Two-gate triage composite added:** gate_triage = activity × rich_selectivity, added to triage benchmark. First scorer to pass all three standard triage conditions with strong selective_vs_hemolytic separation (0.666). Top-20: 16 selective / 1 hemolytic / 3 decoy — best distribution. Does NOT pass strict triage (hem_vs_dec 0.489) — honest limitation. Must not replace ensemble activity gate. | OpenAMP loop |
| 2026-07-03 | **Feature decomposition benchmark added:** per-feature selective_vs_hemolytic AUROC for all 30 scalar physicochemical features. hydrophobic_fraction is the strongest single discriminative feature (0.6745, CI 0.58-0.77) but is NOT used by the selectivity proxy. 8/30 features have significant signal; 6 of those are unused. Provides actionable diagnostic for why composite scorers fail selective_vs_hemolytic discrimination. | OpenAMP loop |
| 2026-07-04 | **Calibration intake module added:** `openamp-foundry calibration-intake` joins a pilot panel CSV with a directory of validated lab result JSON files, produces a per-candidate prediction-vs-actual report with cohort metrics gated by `MIN_COHORT_SIZE=5`. Descriptive only — does NOT trigger recalibration, weight updates, or selection-rule changes. Synthetic example data in `examples/lab_results/` is clearly labeled in every file and in `examples/lab_results/README.md`. 29 new tests; total 1614 passing. | OpenAMP loop |
| 2026-06-29 | Initial — expanded benchmark (PR #110) | OpenAMP CI |
59 changes: 59 additions & 0 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,3 +433,62 @@ review (2026-06-28). Progress on these would materially raise breakthrough proba
| Wet-lab result integration (active-learning round 2) | Required to move from 15–30% to 50%+ credible probability | Requires wet-lab |
| ~~Pre-registration of assay protocol before synthesis~~ | ~~Strengthens causal inference; reduces reporting bias~~ | **Done** (docs/ASSAY_PREREGISTRATION.md — PRs #83; includes MRSA USA300, serum stability, Gate P3 aligned) |
| Public benchmark paper (replicable, cluster-split, open datasets) | Sets community standard; enables external validation | Large |

## v0.5.19 — Calibration Intake Module ✓ (2026-07-04)

The lab-result ingestion layer (`data/lab_results.py`, `reports/lab_result_report.py`,
`lab-result-report` CLI) was already in place, but there was no way to **join the
pilot panel predictions with the lab-result actuals** to see whether the pipeline's
predictions matched reality. Without that join, the wet-lab feedback loop has no
intake valve, and any future recalibration would be operating on raw assay data
without the prediction-vs-actual contrast it needs.

This version adds the first step of the closed wet-lab feedback loop: a
calibration-intake report that joins a pilot panel CSV with a directory of
validated lab result JSON files. It is descriptive only — it does not rewrite
selection rules, does not modify scoring weights, and does not upgrade assay
readouts into biological claims.

Changes:
- `src/openamp_foundry/calibration/__init__.py`: new package
- `src/openamp_foundry/calibration/intake.py`: new module
- `build_calibration_intake_report(panel_csv, results_dir)` — joins predictions
with actuals on `candidate_id`, produces a machine-readable report
- `write_calibration_intake_json(report, out_path)` and
`write_calibration_intake_markdown(report, out_path)` — output writers
- **Honest minimum-cohort-size gate**: aggregate cohort metrics are NOT
reported when `n < 5`. Below the gate the metric is marked
`insufficient_data: True` and no point estimate is produced. This prevents
small-sample theater where 2-3 MIC readouts could swing an "AUROC" by 0.30.
- Two pilot cohort metrics: `activity_vs_active_mic` (MIC ≤ 32 µg/mL active)
and `rich_selectivity_vs_high_hemolysis` (hemolysis ≥ 10% high-risk).
- Per-candidate join rows expose every prediction column plus the
aggregated actual outcomes.
- Control failures and orphan lab results (results with no matching
candidate) are surfaced as separate audit fields.
- `src/openamp_foundry/cli/commands/reports.py`: `_run_calibration_intake`
- `src/openamp_foundry/cli/main.py`: `calibration-intake` subcommand registered
- `examples/lab_results/`: 5 clearly-labeled SYNTHETIC JSON files demonstrating
the workflow (active hit, inactive candidate, low hemolysis, high hemolysis,
control failure). Disclaimer required in every file's `disclaimer` field.
- `examples/lab_results/README.md`: explicitly disclaims real-world use
- `examples/lab_results_panel.csv`: 8-candidate synthetic pilot panel
- `tests/test_calibration_intake.py`: 29 tests covering empty panels, missing
columns, orphan detection, control failure surfacing, cohort metric gating
(below/equal/above `MIN_COHORT_SIZE`), hemolysis direction logic,
synthetic-example schema validation, and threshold constants
- `Makefile`: `lab-result-intake` and `lab-result-intake-example` targets
- Total tests: 1614 passing (was 1585)

Key honest limitation: this module does NOT trigger recalibration. It is the
intake valve that produces a review artifact for a separate, human-reviewed
recalibration workflow (see `docs/DECISION_RULES.md` and `docs/WAVE2_PLAN.md`).
Cohort metrics are descriptive only; they do not validate the pipeline, do not
control for selection bias from the pre-registered shortlist, and must not be
used to rewrite scoring weights after the fact.

The synthetic example data is clearly labeled in every JSON file's `disclaimer`
field and in `examples/lab_results/README.md`. It exists solely to exercise the
intake workflow end-to-end so future agents and reviewers can verify the code
path without real wet-lab data. When real validated lab results arrive, replace
the example directory — the pipeline itself does not change.
40 changes: 40 additions & 0 deletions examples/lab_results/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Lab Result Examples (SYNTHETIC)

> **ALL FILES IN THIS DIRECTORY ARE SYNTHETIC EXAMPLE DATA.**
>
> These JSON files demonstrate the calibration intake workflow. They are
> **not** real wet-lab measurements, do not correspond to any actual
> peptide synthesis, and must not be cited as experimental evidence of
> activity, safety, or any other biological property. They exist solely
> to exercise `openamp-foundry calibration-intake` end-to-end so future
> agents and reviewers can verify the workflow without real data.

## Files

| File | Assay | Purpose |
|------|-------|---------|
| `RES-SYN-001.json` | MIC (E. coli) | Synthetic active hit, controls pass |
| `RES-SYN-002.json` | hemolysis_RBC | Synthetic low hemolysis |
| `RES-SYN-003.json` | MIC (E. coli) | Synthetic inactive candidate |
| `RES-SYN-004.json` | hemolysis_RBC | Synthetic high hemolysis |
| `RES-SYN-005.json` | MIC (E. coli) | Synthetic active hit, controls fail |

## How to use

```bash
make lab-result-intake-example
```

This runs `openamp-foundry calibration-intake` against the synthetic panel
`examples/lab_results_panel.csv` and the synthetic lab results in this
directory. The output is written to `outputs/calibration_intake_example/`.

## When real lab data exists

Replace this directory with the real validated lab result JSON files,
where each file matches `schemas/lab_result.schema.json`. The pipeline
itself does not need to change — only the input data does.

See `docs/WET_LAB_HANDOFF.md`, `docs/DECISION_RULES.md`, and
`docs/WAVE2_PLAN.md` for the workflow that converts these inputs into
recalibration decisions.
20 changes: 20 additions & 0 deletions examples/lab_results/RES-SYN-001.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"result_id": "RES-SYN-001",
"candidate_id": "SEED-005_VAR_049",
"assay_type": "MIC",
"organism_or_cell_line": "SYNTHETIC EXAMPLE - E. coli ATCC 25922",
"result_value": 8.0,
"result_unit": "µg/mL",
"result_qualitative": "active",
"positive_control_passed": true,
"negative_control_passed": true,
"positive_control_id": "ciprofloxacin 0.25 µg/mL",
"negative_control_id": "PBS",
"assay_date": "2026-07-04",
"replicate_count": 3,
"performed_by_lab": "SYNTHETIC EXAMPLE - University Test Lab",
"raw_data_sha256": null,
"computational_candidate_certificate_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"notes": "SYNTHETIC DATA: not a real wet-lab result. Included only to exercise the calibration intake workflow.",
"disclaimer": "SYNTHETIC EXAMPLE. This is not a real experimental result on a computationally nominated candidate and does not constitute a drug or clinical claim."
}
20 changes: 20 additions & 0 deletions examples/lab_results/RES-SYN-002.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"result_id": "RES-SYN-002",
"candidate_id": "SEED-005_VAR_049",
"assay_type": "hemolysis_RBC",
"organism_or_cell_line": "SYNTHETIC EXAMPLE - hRBC",
"result_value": 3.5,
"result_unit": "%",
"result_qualitative": "inactive",
"positive_control_passed": true,
"negative_control_passed": true,
"positive_control_id": "melittin 10 µM",
"negative_control_id": "PBS",
"assay_date": "2026-07-04",
"replicate_count": 3,
"performed_by_lab": "SYNTHETIC EXAMPLE - University Test Lab",
"raw_data_sha256": null,
"computational_candidate_certificate_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"notes": "SYNTHETIC DATA: not a real wet-lab result. Included only to exercise the calibration intake workflow.",
"disclaimer": "SYNTHETIC EXAMPLE. This is not a real experimental result on a computationally nominated candidate and does not constitute a drug or clinical claim."
}
20 changes: 20 additions & 0 deletions examples/lab_results/RES-SYN-003.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"result_id": "RES-SYN-003",
"candidate_id": "SEED-007_VAR_022",
"assay_type": "MIC",
"organism_or_cell_line": "SYNTHETIC EXAMPLE - E. coli ATCC 25922",
"result_value": 128.0,
"result_unit": "µg/mL",
"result_qualitative": "inactive",
"positive_control_passed": true,
"negative_control_passed": true,
"positive_control_id": "ciprofloxacin 0.25 µg/mL",
"negative_control_id": "PBS",
"assay_date": "2026-07-04",
"replicate_count": 3,
"performed_by_lab": "SYNTHETIC EXAMPLE - University Test Lab",
"raw_data_sha256": null,
"computational_candidate_certificate_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"notes": "SYNTHETIC DATA: not a real wet-lab result. Included only to exercise the calibration intake workflow.",
"disclaimer": "SYNTHETIC EXAMPLE. This is not a real experimental result on a computationally nominated candidate and does not constitute a drug or clinical claim."
}
20 changes: 20 additions & 0 deletions examples/lab_results/RES-SYN-004.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"result_id": "RES-SYN-004",
"candidate_id": "SEED-008_VAR_007",
"assay_type": "hemolysis_RBC",
"organism_or_cell_line": "SYNTHETIC EXAMPLE - hRBC",
"result_value": 42.0,
"result_unit": "%",
"result_qualitative": "toxic",
"positive_control_passed": true,
"negative_control_passed": true,
"positive_control_id": "melittin 10 µM",
"negative_control_id": "PBS",
"assay_date": "2026-07-04",
"replicate_count": 3,
"performed_by_lab": "SYNTHETIC EXAMPLE - University Test Lab",
"raw_data_sha256": null,
"computational_candidate_certificate_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"notes": "SYNTHETIC DATA: not a real wet-lab result. Included only to exercise the calibration intake workflow.",
"disclaimer": "SYNTHETIC EXAMPLE. This is not a real experimental result on a computationally nominated candidate and does not constitute a drug or clinical claim."
}
20 changes: 20 additions & 0 deletions examples/lab_results/RES-SYN-005.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"result_id": "RES-SYN-005",
"candidate_id": "SEED-001_VAR_064",
"assay_type": "MIC",
"organism_or_cell_line": "SYNTHETIC EXAMPLE - S. aureus ATCC 25923",
"result_value": 16.0,
"result_unit": "µg/mL",
"result_qualitative": "active",
"positive_control_passed": false,
"negative_control_passed": true,
"positive_control_id": "vancomycin 1.0 µg/mL",
"negative_control_id": "PBS",
"assay_date": "2026-07-04",
"replicate_count": 2,
"performed_by_lab": "SYNTHETIC EXAMPLE - University Test Lab",
"raw_data_sha256": null,
"computational_candidate_certificate_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"notes": "SYNTHETIC DATA: not a real wet-lab result. Positive control did NOT pass expected range; result must NOT be used for activity claims until retested with valid controls.",
"disclaimer": "SYNTHETIC EXAMPLE. This is not a real experimental result on a computationally nominated candidate and does not constitute a drug or clinical claim."
}
Loading
Loading