From 61079420bc94fda5373255724dc9e4594a2c7213 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Fri, 10 Jul 2026 13:10:01 +0700 Subject: [PATCH] feat: Phase A A8 toy artifact examples -- 3-candidate toy ranked JSONL (TOY- IDs), toy evidence certificate, README; all dry_lab_only=true; 49 tests (#944) --- examples/toy_pipeline_output/README.md | 23 ++ .../toy_certificate_example.json | 27 ++ .../toy_ranked_example.jsonl | 3 + tests/test_test_count_regression.py | 2 +- tests/test_toy_examples.py | 260 ++++++++++++++++++ 5 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 examples/toy_pipeline_output/README.md create mode 100644 examples/toy_pipeline_output/toy_certificate_example.json create mode 100644 examples/toy_pipeline_output/toy_ranked_example.jsonl create mode 100644 tests/test_toy_examples.py diff --git a/examples/toy_pipeline_output/README.md b/examples/toy_pipeline_output/README.md new file mode 100644 index 00000000..ce2b8da2 --- /dev/null +++ b/examples/toy_pipeline_output/README.md @@ -0,0 +1,23 @@ +# Toy Pipeline Output Examples + +This directory contains **pre-generated toy examples** showing what OpenAMP Foundry pipeline outputs look like. + +**IMPORTANT**: All candidate IDs use the `TOY-` prefix. All sequences are made up and do NOT represent real antimicrobial peptide candidates. These files exist solely to show output format and structure. + +## Files + +| File | Description | +|------|-------------| +| `toy_ranked_example.jsonl` | 3 toy ranked candidates (one JSON object per line) | +| `toy_certificate_example.json` | Example evidence certificate for a toy candidate | + +## What These Show + +- Field names and value ranges in ranked output +- Evidence certificate structure and required fields +- The claim boundary notice format +- How dry-lab-only status is encoded + +## What These Are NOT + +These are NOT real candidates. They are NOT nominations for synthesis. They exist only to illustrate output format. diff --git a/examples/toy_pipeline_output/toy_certificate_example.json b/examples/toy_pipeline_output/toy_certificate_example.json new file mode 100644 index 00000000..7c544f1d --- /dev/null +++ b/examples/toy_pipeline_output/toy_certificate_example.json @@ -0,0 +1,27 @@ +{ + "cert_id": "TOY-CERT-0001", + "candidate_id": "TOY-0001", + "sequence": "KWKLFKKIEK", + "dry_lab_only": true, + "claim_class": "computational_nomination", + "proof_ladder_level": "multi_signal_candidate_evidence", + "unsupported_claims": ["in_vitro_activity", "in_vivo_efficacy", "toxicity_cleared", "clinical_relevance"], + "scores": { + "activity": 0.82, + "safety": 0.91, + "novelty": 0.75, + "hemolysis_risk": 0.12, + "ensemble": 0.85 + }, + "baseline_caveat": "Charge, length, and hydrophobicity baselines all pass. Method advantage over cheap heuristics is not yet demonstrated.", + "selection_reason": ["High activity-likeness score", "Low predicted safety risk"], + "known_failure_modes": [ + "No wet-lab antimicrobial activity assay has been run.", + "Scores are heuristics, not validated biological predictors.", + "Cheap baselines (charge, length, hydrophobicity) are not excluded as explanations." + ], + "run_id": "TOY-RUN-0001", + "run_manifest_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "human_review_required": false, + "issued_by": "openamp_foundry_toy_example" +} diff --git a/examples/toy_pipeline_output/toy_ranked_example.jsonl b/examples/toy_pipeline_output/toy_ranked_example.jsonl new file mode 100644 index 00000000..a96d8702 --- /dev/null +++ b/examples/toy_pipeline_output/toy_ranked_example.jsonl @@ -0,0 +1,3 @@ +{"candidate_id": "TOY-0001", "sequence": "KWKLFKKIEK", "source": "toy", "valid": true, "selected": true, "scores": {"activity": 0.82, "safety": 0.91, "novelty": 0.75, "hemolysis_risk": 0.12, "ensemble": 0.85, "selectivity_proxy": 0.88}, "selection_reason": ["High activity-likeness score", "Low predicted safety risk"], "known_failure_modes": ["No wet-lab antimicrobial activity assay has been run.", "Scores are heuristics, not validated biological predictors."], "dry_lab_only": true} +{"candidate_id": "TOY-0002", "sequence": "FLPKLKKLLK", "source": "toy", "valid": true, "selected": true, "scores": {"activity": 0.71, "safety": 0.88, "novelty": 0.62, "hemolysis_risk": 0.19, "ensemble": 0.78, "selectivity_proxy": 0.81}, "selection_reason": ["Moderate activity-likeness score", "Acceptable safety profile"], "known_failure_modes": ["No wet-lab antimicrobial activity assay has been run.", "Scores are heuristics, not validated biological predictors."], "dry_lab_only": true} +{"candidate_id": "TOY-0003", "sequence": "GIKCKILKKLR", "source": "toy", "valid": true, "selected": false, "scores": {"activity": 0.48, "safety": 0.72, "novelty": 0.55, "hemolysis_risk": 0.38, "ensemble": 0.52, "selectivity_proxy": 0.61}, "selection_reason": [], "known_failure_modes": ["Insufficient activity score", "No wet-lab antimicrobial activity assay has been run.", "Scores are heuristics, not validated biological predictors."], "dry_lab_only": true} diff --git a/tests/test_test_count_regression.py b/tests/test_test_count_regression.py index 80c9d4e2..f3514252 100644 --- a/tests/test_test_count_regression.py +++ b/tests/test_test_count_regression.py @@ -3,7 +3,7 @@ import subprocess import math -BASELINE = 8536 +BASELINE = 8585 def test_test_count_regression(): diff --git a/tests/test_toy_examples.py b/tests/test_toy_examples.py new file mode 100644 index 00000000..08a55630 --- /dev/null +++ b/tests/test_toy_examples.py @@ -0,0 +1,260 @@ +"""Tests for A8 toy pipeline output examples (tests/test_toy_examples.py).""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + + +_TOY_DIR = Path(__file__).parent.parent / "examples" / "toy_pipeline_output" +_RANKED_FILE = _TOY_DIR / "toy_ranked_example.jsonl" +_CERT_FILE = _TOY_DIR / "toy_certificate_example.json" +_README_FILE = _TOY_DIR / "README.md" + + +class TestToyDirectoryExists: + def test_toy_dir_exists(self): + assert _TOY_DIR.is_dir() + + def test_ranked_file_exists(self): + assert _RANKED_FILE.exists() + + def test_cert_file_exists(self): + assert _CERT_FILE.exists() + + def test_readme_exists(self): + assert _README_FILE.exists() + + def test_toy_dir_has_files(self): + files = list(_TOY_DIR.iterdir()) + assert len(files) >= 3 + + +class TestToyReadme: + def test_readme_nonempty(self): + content = _README_FILE.read_text() + assert len(content) > 0 + + def test_readme_mentions_toy(self): + content = _README_FILE.read_text().lower() + assert "toy" in content + + def test_readme_mentions_not_real(self): + content = _README_FILE.read_text() + assert "NOT" in content or "not real" in content.lower() + + def test_readme_mentions_toy_ranked(self): + content = _README_FILE.read_text() + assert "toy_ranked_example" in content + + def test_readme_mentions_toy_certificate(self): + content = _README_FILE.read_text() + assert "toy_certificate_example" in content + + def test_readme_dry_lab_mentioned(self): + content = _README_FILE.read_text().lower() + assert "dry-lab" in content or "dry_lab" in content + + +class TestToyRankedFile: + def _load_lines(self): + lines = [] + with _RANKED_FILE.open() as f: + for line in f: + line = line.strip() + if line: + lines.append(json.loads(line)) + return lines + + def test_file_is_valid_jsonl(self): + lines = self._load_lines() + assert len(lines) > 0 + + def test_has_multiple_candidates(self): + lines = self._load_lines() + assert len(lines) >= 2 + + def test_all_ids_have_toy_prefix(self): + lines = self._load_lines() + for obj in lines: + assert obj["candidate_id"].startswith("TOY-"), \ + f"Expected TOY- prefix, got {obj['candidate_id']}" + + def test_all_have_sequence_field(self): + lines = self._load_lines() + for obj in lines: + assert "sequence" in obj + + def test_sequences_are_uppercase(self): + lines = self._load_lines() + for obj in lines: + assert obj["sequence"] == obj["sequence"].upper() + + def test_sequences_are_nonempty(self): + lines = self._load_lines() + for obj in lines: + assert len(obj["sequence"]) > 0 + + def test_all_have_scores_field(self): + lines = self._load_lines() + for obj in lines: + assert "scores" in obj + + def test_all_have_valid_field(self): + lines = self._load_lines() + for obj in lines: + assert "valid" in obj + + def test_all_have_selected_field(self): + lines = self._load_lines() + for obj in lines: + assert "selected" in obj + + def test_all_have_dry_lab_only_true(self): + lines = self._load_lines() + for obj in lines: + assert obj.get("dry_lab_only") is True + + def test_all_have_known_failure_modes(self): + lines = self._load_lines() + for obj in lines: + assert "known_failure_modes" in obj + assert len(obj["known_failure_modes"]) > 0 + + def test_activity_score_in_range(self): + lines = self._load_lines() + for obj in lines: + score = obj["scores"]["activity"] + assert 0.0 <= score <= 1.0 + + def test_safety_score_in_range(self): + lines = self._load_lines() + for obj in lines: + score = obj["scores"]["safety"] + assert 0.0 <= score <= 1.0 + + def test_novelty_score_in_range(self): + lines = self._load_lines() + for obj in lines: + score = obj["scores"]["novelty"] + assert 0.0 <= score <= 1.0 + + def test_hemolysis_risk_in_range(self): + lines = self._load_lines() + for obj in lines: + score = obj["scores"]["hemolysis_risk"] + assert 0.0 <= score <= 1.0 + + def test_source_is_toy(self): + lines = self._load_lines() + for obj in lines: + assert obj.get("source") == "toy" + + def test_no_real_ampf_ids(self): + lines = self._load_lines() + for obj in lines: + assert not obj["candidate_id"].startswith("AMPF-") + + def test_selected_candidates_have_selection_reason(self): + lines = self._load_lines() + for obj in lines: + if obj["selected"]: + assert len(obj.get("selection_reason", [])) > 0 + + def test_failure_modes_mention_no_wet_lab(self): + lines = self._load_lines() + for obj in lines: + modes_text = " ".join(obj["known_failure_modes"]).lower() + assert "wet-lab" in modes_text or "assay" in modes_text + + +class TestToyCertificate: + def _load(self): + with _CERT_FILE.open() as f: + return json.load(f) + + def test_cert_is_valid_json(self): + cert = self._load() + assert isinstance(cert, dict) + + def test_cert_id_has_toy_prefix(self): + cert = self._load() + assert cert["cert_id"].startswith("TOY-") + + def test_candidate_id_has_toy_prefix(self): + cert = self._load() + assert cert["candidate_id"].startswith("TOY-") + + def test_dry_lab_only_is_true(self): + cert = self._load() + assert cert["dry_lab_only"] is True + + def test_has_unsupported_claims(self): + cert = self._load() + assert "unsupported_claims" in cert + assert len(cert["unsupported_claims"]) > 0 + + def test_in_vitro_in_unsupported(self): + cert = self._load() + assert "in_vitro_activity" in cert["unsupported_claims"] + + def test_in_vivo_in_unsupported(self): + cert = self._load() + assert "in_vivo_efficacy" in cert["unsupported_claims"] + + def test_proof_ladder_level_present(self): + cert = self._load() + assert "proof_ladder_level" in cert + + def test_proof_ladder_not_validated(self): + cert = self._load() + level = cert["proof_ladder_level"] + assert "clinical" not in level + assert "validated" not in level + + def test_has_baseline_caveat(self): + cert = self._load() + assert "baseline_caveat" in cert + assert len(cert["baseline_caveat"]) > 0 + + def test_has_known_failure_modes(self): + cert = self._load() + assert "known_failure_modes" in cert + assert len(cert["known_failure_modes"]) > 0 + + def test_has_scores(self): + cert = self._load() + assert "scores" in cert + + def test_has_sequence(self): + cert = self._load() + assert "sequence" in cert + + def test_sequence_uppercase(self): + cert = self._load() + assert cert["sequence"] == cert["sequence"].upper() + + def test_has_run_id(self): + cert = self._load() + assert "run_id" in cert + + def test_run_id_toy_prefix(self): + cert = self._load() + assert cert["run_id"].startswith("TOY-") + + def test_claim_class_present(self): + cert = self._load() + assert "claim_class" in cert + + def test_claim_class_not_biological_proof(self): + cert = self._load() + claim = cert["claim_class"] + assert "biological_proof" not in claim + assert "validated" not in claim + + def test_no_real_candidate_ids(self): + cert = self._load() + assert not cert["candidate_id"].startswith("AMPF-") + assert not cert["cert_id"].startswith("AMPF-")