diff --git a/docs/WET_LAB_HANDOFF.md b/docs/WET_LAB_HANDOFF.md new file mode 100644 index 00000000..662fd1dc --- /dev/null +++ b/docs/WET_LAB_HANDOFF.md @@ -0,0 +1,156 @@ +# Wet-Lab Handoff Guide + +**Version:** 0.1.0 +**Status:** Working draft for expert review before synthesis ordering. + +--- + +## Purpose + +This document translates pipeline output scores into synthesis and assay decisions for wet-lab +scientists. Every nominated candidate has a JSON evidence certificate and a pre-synthesis QC +report. This guide explains what each number means, what action it recommends, and what to do +when fewer candidates pass the filters than expected. + +--- + +## Score Reference Table + +All scores are on a **[0, 1] scale**. Higher is better (except `disagreement`, which is lower is better). + +| Score key | What it measures | Low (< 0.4) | Medium (0.4–0.7) | High (> 0.7) | +|-----------|-----------------|------------|-----------------|-------------| +| `activity` | Physicochemical similarity to known AMPs (charge, hydrophobicity, amphipathicity) | Unlikely AMP-like | Possible candidate | Strong AMP signature | +| `safety` | Absence of hemolysis risk, toxic motif similarity, mammalian membrane affinity | High host-cell risk — exclude | Monitor carefully | Safe to proceed | +| `synthesis` | SPPS synthesis feasibility (length, Cys content, repeat runs) | Difficult / high failure risk | Moderate; check QC report | Straightforward synthesis | +| `novelty` | Sequence distance from known AMP references | Near-duplicate of known AMP | Moderate novelty | New structural territory | +| `ensemble` | Weighted combination of above four scores | Below priority threshold | Borderline | Priority candidate | +| `disagreement` | |activity_likeness − boman_activity| — scorer consensus | High consensus (good) | Some divergence | Low consensus — treat cautiously | + +### Recommended Action by Ensemble Score + +| Ensemble range | Recommendation | +|---------------|---------------| +| ≥ 0.70 | **Synthesise** — priority candidate | +| 0.55–0.69 | **Conditional synthesis** — review QC flags first | +| 0.40–0.54 | **Expert review required** — only if budget allows | +| < 0.40 | **Do not synthesise** — fails pipeline threshold | + +--- + +## Selection Thresholds + +The pipeline uses four gates before a candidate is marked `selected: true`. + +| Gate | `pipeline.yaml` | `phase3.yaml` | Why | +|------|----------------|--------------|-----| +| `min_novelty` | ≥ 0.20 | ≥ 0.05 | Near-duplicates waste budget on already-known peptides | +| `max_safety_risk` → min safety | ≤ 0.70 → safety ≥ 0.30 | ≤ 0.40 → safety ≥ 0.60 | Phase 3 is the final synthesis batch — only clean candidates | +| `max_disagreement` | ≤ 0.40 | ≤ 0.30 | When physchem and Boman scoring diverge > threshold, both models are guessing | +| Sequence validity | canonical AA only | same | Non-canonical residues are disqualified | + +A candidate must pass **all four gates** to be nominated. Failing any single gate → `selected: false`. + +--- + +## Pre-Synthesis QC Report Interpretation + +Run `make presynth-qc` (or `openamp-foundry presynth-qc --panel-csv `) on the final panel +before ordering. The report flags: + +| Flag | Risk | Action | +|------|------|--------| +| `CYSTEINE×N` | Disulfide formation, oxidation during storage | Order under inert atmosphere; store in N₂; check purity pre-assay | +| `MET×N` | Met oxidation to sulfoxide in air | Order with >95% purity; store at −80°C; re-check purity on thaw | +| `HYDROPHOBIC_RUN (XXXX)` | Aggregation; poor aqueous solubility | Test solubility at 1 mM in PBS before assay; add 0.1% DMSO if needed | +| `TRYPSIN_SITES×N` (N > 2) | Serum degradation < 2h | Use serum-free media for initial MIC; add protease inhibitors if needed | +| `DEAMIDATION_RISK` | N→D conversion at pH > 7.5 | Use pH 7.0 buffer; avoid long storage at room temp | +| `LOW_CHARGE` | Reduced membrane affinity | Lower expected potency; may need higher test concentrations | +| `LONG_PEPTIDE (>30aa)` | SPPS yield risk | Expect lower crude purity; order extra crude for purification | + +### Synthesis Difficulty Rating + +| Rating | Meaning | Action | +|--------|---------|--------| +| LOW | ≤ 0 flags | Standard SPPS protocol; Fmoc chemistry | +| MODERATE | 1–2 flags | Standard protocol + address specific flags above | +| HIGH | ≥ 3 flags | Discuss with peptide synthesis vendor; consider Boc chemistry or ligation | + +--- + +## Assay Recommendations + +### Initial Screen (MIC Assay) + +- **Organism priority:** *E. coli* ATCC 25922 (Gram-negative standard), *S. aureus* ATCC 29213 (Gram-positive standard) +- **Growth medium:** MHB (Mueller-Hinton Broth), cation-adjusted +- **Inoculum:** ~5×10⁵ CFU/mL (CLSI standard) +- **Concentration range:** 1–128 μg/mL (2-fold serial dilutions) +- **Replicates:** ≥ 3 biological replicates per candidate +- **Positive control:** Colistin (Gram-negative), Oxacillin (Gram-positive) +- **Negative control:** Solvent blank matching highest solvent concentration + +### Hemolysis Assay (Safety Gate) + +Run in parallel with MIC. The `hemolysis_start_conc` field in the presynth QC report gives the +recommended starting concentration based on the computed hydrophobic moment (μH): + +| μH range | Starting concentration | +|---------|----------------------| +| ≤ 0.55 | Start at MIC (low risk) | +| > 0.55 and ≤ 0.80 | Start at MIC/3 (moderate risk) | +| > 0.80 | Start at MIC/10 (high risk — very cautious) | + +Use human red blood cells (hRBCs) at 0.5% in PBS. Incubate 1h at 37°C. Read at A540. +Hemolysis > 10% at MIC is a fail. + +--- + +## Troubleshooting + +### Too Few Candidates Pass the Gates + +If fewer than 10 candidates are nominated from a batch: + +1. Check `disagreement` scores — if many candidates cluster near the configured `max_disagreement` + threshold (0.30 for phase3.yaml, 0.40 for pipeline.yaml), the two scorers may systematically + disagree for this peptide series. Consider whether to relax `max_disagreement` in a custom + config (document the change and the scientific justification). +2. Check `novelty` scores — if the reference set covers your template region well, novelty will + be low. Consider reducing `min_novelty` for that generation run only. +3. Check `safety` scores — if a hydrophobic template is used, many variants may have low safety. + Review the safety score components (`scoring/safety.py`) for the specific risk flag. +4. Run `openamp-foundry bench leakage` to confirm candidates are not near-duplicates of each other. + +### Synthesis Failure (No Product / Low Purity) + +1. Check the presynth QC report for HYDROPHOBIC_RUN or LONG_PEPTIDE flags. +2. Consider pseudoproline (Ψ-Pro) dipeptides for Ser/Thr-containing sequences. +3. Consider microwave-assisted SPPS for sequences with aggregation propensity. +4. For Cys-containing peptides: use Acm protection if disulfide formation is not desired. + +### All Candidates Fail Hemolysis + +1. Check μH values — a high hydrophobic moment across all candidates suggests the template + itself is hemolytic. +2. Consider substituting hydrophobic residues (L→A, F→Y) to reduce membrane disruption. +3. Re-score the modified variants through the pipeline before ordering. + +--- + +## Confidence Statement + +All scores are **heuristic proxies** derived from physicochemical features. They have not been +calibrated against wet-lab activity data for this specific peptide series. The pipeline is a +**nomination tool**, not a predictor of MIC values. Expect a hit rate of 20–40% in initial +screens (consistent with the AMP field literature for computationally nominated candidates). + +No antimicrobial activity claim is made until wet-lab confirmation. + +--- + +## References + +- CLSI M07-A10: Methods for Dilution Antimicrobial Susceptibility Tests for Bacteria That Grow Aerobically +- Boman HG (2003) Peptide antibiotics and their role in innate immunity. Annu Rev Immunol 13:61–92 +- Fjell CD et al. (2011) Designing antimicrobial peptides. Nat Rev Drug Discov 11:37–51 diff --git a/tests/test_config.py b/tests/test_config.py index 229b0e9b..32112626 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -53,6 +53,31 @@ def test_default_config_selection_present(self): assert "max_disagreement" in sel assert 0.0 <= sel["max_disagreement"] <= 1.0 + def test_phase3_config_selection_present(self): + phase3 = Path(__file__).parents[1] / "configs" / "phase3.yaml" + result = load_config(phase3) + sel = result["selection"] + assert "top_n" in sel + assert "min_novelty" in sel + assert "max_safety_risk" in sel + assert 0.0 < sel["max_safety_risk"] <= 1.0 + assert "max_disagreement" in sel + assert 0.0 <= sel["max_disagreement"] <= 1.0 + + def test_phase3_config_weights_are_positive(self): + phase3 = Path(__file__).parents[1] / "configs" / "phase3.yaml" + result = load_config(phase3) + for name, w in result["weights"].items(): + assert w > 0, f"phase3.yaml weight '{name}' must be positive, got {w}" + + def test_phase3_config_filters_present(self): + phase3 = Path(__file__).parents[1] / "configs" / "phase3.yaml" + result = load_config(phase3) + filters = result["filters"] + assert "min_length" in filters + assert "max_length" in filters + assert filters["min_length"] < filters["max_length"] + def test_string_path_accepted(self): with tempfile.TemporaryDirectory() as d: cfg_path = Path(d) / "config.yaml" diff --git a/tests/test_physchem.py b/tests/test_physchem.py index 308b24f4..2bdbe6cd 100644 --- a/tests/test_physchem.py +++ b/tests/test_physchem.py @@ -182,3 +182,16 @@ def test_all_canonical_aa_no_crash(self): seq = "ACDEFGHIKLMNPQRSTVWY" feat = compute_features(seq) assert feat["length"] == 20 + + def test_compute_features_supplies_synthesis_feasibility_keys(self): + # synthesis_feasibility_score() does direct dict access (no .get()) on three keys. + # This test pins the contract so a refactor that removes any key fails loudly here + # rather than crashing mid-pipeline during candidate scoring. + from openamp_foundry.scoring.synthesis import synthesis_feasibility_score + feat = compute_features("KWKLFKKIGAVLKVL") + assert "length" in feat + assert "longest_repeat_run" in feat + assert "cysteine_fraction" in feat + # Also verify the function runs without KeyError — the real integration check. + score = synthesis_feasibility_score(feat, valid_sequence=True) + assert 0.0 <= score <= 1.0