From 073fb0a84a8ff006bd6ec4a5885451a44138aff2 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Wed, 8 Jul 2026 22:51:30 +0700 Subject: [PATCH] Add 10 feature and data infrastructure files Closes #263, Closes #264, Closes #265, Closes #266, Closes #267, Closes #268, Closes #269, Closes #270, Closes #271, Closes #272 --- CLAUDE.md | 26 +++++++-------- docs/evidence/GUIDE_ARTIFACT_IDENTITY.md | 19 +++++++++++ docs/evidence/GUIDE_FEATURE_PROVENANCE.md | 26 +++++++++++++++ docs/evidence/GUIDE_FEATURE_RANGE_CHECKS.md | 17 ++++++++++ docs/evidence/GUIDE_SEQUENCE_VALIDATION.md | 23 +++++++++++++ docs/evidence/POLICY_MISSING_VALUES.md | 18 +++++++++++ docs/evidence/REGISTRY_SCORER_COMPONENTS.md | 21 ++++++++++++ docs/evidence/SCHEMA_RANK_OUTPUT.md | 32 +++++++++++++++++++ .../getting-started/CONTRIBUTOR_CURRICULUM.md | 4 +-- docs/getting-started/HUMAN_ONBOARDING.md | 2 +- docs/getting-started/QUICKSTART.md | 4 +-- tests/test_csv_dialect.py | 24 ++++++++++++++ tests/test_duplicate_records.py | 29 +++++++++++++++++ tests/test_scorer_registry_coverage.py | 19 +++++++++++ 14 files changed, 246 insertions(+), 18 deletions(-) create mode 100644 docs/evidence/GUIDE_ARTIFACT_IDENTITY.md create mode 100644 docs/evidence/GUIDE_FEATURE_PROVENANCE.md create mode 100644 docs/evidence/GUIDE_FEATURE_RANGE_CHECKS.md create mode 100644 docs/evidence/GUIDE_SEQUENCE_VALIDATION.md create mode 100644 docs/evidence/POLICY_MISSING_VALUES.md create mode 100644 docs/evidence/REGISTRY_SCORER_COMPONENTS.md create mode 100644 docs/evidence/SCHEMA_RANK_OUTPUT.md create mode 100644 tests/test_csv_dialect.py create mode 100644 tests/test_duplicate_records.py create mode 100644 tests/test_scorer_registry_coverage.py diff --git a/CLAUDE.md b/CLAUDE.md index 6f8892f7..c4d6bb17 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,12 +20,12 @@ OpenAMP Foundry is a verification-first, safety-constrained antimicrobial peptid ## Fast orientation - [docs/PROJECT_INDEX.md](docs/PROJECT_INDEX.md) — navigation hub for humans and agents. -- [docs/TRUST_CENTER.md](docs/TRUST_CENTER.md) — trust architecture. -- [docs/COMMAND_SURFACE.md](docs/COMMAND_SURFACE.md) — command workflows and claim boundaries. -- [docs/HUMAN_AGENT_COLLABORATION.md](docs/HUMAN_AGENT_COLLABORATION.md) — human-agent division of labor. -- [docs/REVIEWER_ONBOARDING.md](docs/REVIEWER_ONBOARDING.md) — review expectations. -- [docs/NEXT_100_PR_MAP.md](docs/NEXT_100_PR_MAP.md) — PR-sized backlog. -- [docs/PROOF_LADDER.md](docs/PROOF_LADDER.md) — claim ladder and evidence levels. +- [docs/trust/TRUST_CENTER.md](docs/trust/TRUST_CENTER.md) — trust architecture. +- [docs/getting-started/COMMAND_SURFACE.md](docs/getting-started/COMMAND_SURFACE.md) — command workflows and claim boundaries. +- [docs/operations/HUMAN_AGENT_COLLABORATION.md](docs/operations/HUMAN_AGENT_COLLABORATION.md) — human-agent division of labor. +- [docs/getting-started/REVIEWER_ONBOARDING.md](docs/getting-started/REVIEWER_ONBOARDING.md) — review expectations. +- [docs/research/NEXT_100_PR_MAP.md](docs/research/NEXT_100_PR_MAP.md) — PR-sized backlog. +- [docs/evidence/PROOF_LADDER.md](docs/evidence/PROOF_LADDER.md) — claim ladder and evidence levels. ## Key docs @@ -39,13 +39,13 @@ OpenAMP Foundry is a verification-first, safety-constrained antimicrobial peptid - [RESPONSIBLE_USE.md](RESPONSIBLE_USE.md) - [MODEL_RELEASE_POLICY.md](MODEL_RELEASE_POLICY.md) - [DATA_LICENSE_NOTICE.md](DATA_LICENSE_NOTICE.md) -- [docs/ARTIFACT_VERSIONING.md](docs/ARTIFACT_VERSIONING.md) -- [docs/BENCHMARK_GOVERNANCE.md](docs/BENCHMARK_GOVERNANCE.md) -- [docs/CI_AND_QUALITY_GATES.md](docs/CI_AND_QUALITY_GATES.md) -- [docs/CLAIM_REVIEW_CHECKLIST.md](docs/CLAIM_REVIEW_CHECKLIST.md) -- [docs/DATA_GOVERNANCE.md](docs/DATA_GOVERNANCE.md) -- [docs/MODEL_CARD_TEMPLATE.md](docs/MODEL_CARD_TEMPLATE.md) -- [docs/RELEASE_CHECKLIST.md](docs/RELEASE_CHECKLIST.md) +- [docs/engineering/ARTIFACT_VERSIONING.md](docs/engineering/ARTIFACT_VERSIONING.md) +- [docs/evidence/BENCHMARK_GOVERNANCE.md](docs/evidence/BENCHMARK_GOVERNANCE.md) +- [docs/engineering/CI_AND_QUALITY_GATES.md](docs/engineering/CI_AND_QUALITY_GATES.md) +- [docs/evidence/CLAIM_REVIEW_CHECKLIST.md](docs/evidence/CLAIM_REVIEW_CHECKLIST.md) +- [docs/trust/DATA_GOVERNANCE.md](docs/trust/DATA_GOVERNANCE.md) +- [docs/trust/MODEL_CARD_TEMPLATE.md](docs/trust/MODEL_CARD_TEMPLATE.md) +- [docs/trust/RELEASE_CHECKLIST.md](docs/trust/RELEASE_CHECKLIST.md) ## Default work loop diff --git a/docs/evidence/GUIDE_ARTIFACT_IDENTITY.md b/docs/evidence/GUIDE_ARTIFACT_IDENTITY.md new file mode 100644 index 00000000..391cdef1 --- /dev/null +++ b/docs/evidence/GUIDE_ARTIFACT_IDENTITY.md @@ -0,0 +1,19 @@ +# Artifact Identity Helper Based on Hashes + +Helper for identifying artifacts by their content hash. + +## Usage +```python +from scripts.verify_lab_peptides import _sha256 + +# Get artifact identity +file_hash = _sha256(open("outputs/demo_ranked.jsonl").read()) +print(f"Artifact identity: {file_hash}") +``` + +## Rules +- Artifact identity is the SHA-256 hash of the file contents. +- Two artifacts with the same hash are identical. +- Hash-based identity works across machines and time. +- If the artifact changes, the identity changes. +- Use artifact identity to verify that expected outputs match received outputs. diff --git a/docs/evidence/GUIDE_FEATURE_PROVENANCE.md b/docs/evidence/GUIDE_FEATURE_PROVENANCE.md new file mode 100644 index 00000000..de1caf3f --- /dev/null +++ b/docs/evidence/GUIDE_FEATURE_PROVENANCE.md @@ -0,0 +1,26 @@ +# Feature Provenance Records to Scoring Outputs + +Tracks which features contributed to which scores. + +## Format +Each scored candidate includes feature values used in scoring: +```json +{ + "candidate_id": "AMPF-000001", + "features": { + "charge_density_ph74": 0.4545, + "hydrophobic_fraction": 0.3636, + "hydrophobic_moment": 0.5741 + }, + "scores": { + "activity": 0.8387, + "charge_bias": 0.2345 + } +} +``` + +## Rules +- Feature values are included in the ranked JSONL output. +- Feature provenance enables debugging and auditing. +- If a feature is derived from other features, document the derivation. +- Feature provenance should be preserved through the scoring pipeline. diff --git a/docs/evidence/GUIDE_FEATURE_RANGE_CHECKS.md b/docs/evidence/GUIDE_FEATURE_RANGE_CHECKS.md new file mode 100644 index 00000000..d40a81a7 --- /dev/null +++ b/docs/evidence/GUIDE_FEATURE_RANGE_CHECKS.md @@ -0,0 +1,17 @@ +# Feature Range Sanity Checks + +Sanity checks for feature values in the pipeline. + +| Feature | Min | Max | Unit | Notes | +|---------|:---:|:---:|:----:|-------| +| length | 8 | 35 | AA | Filter range | +| charge_density | -1.0 | 2.0 | e/AA | Physically possible range | +| hydrophobic_fraction | 0.0 | 1.0 | ratio | Fraction of hydrophobic AAs | +| aromatic_fraction | 0.0 | 0.5 | ratio | Fraction of F, W, Y | +| helix_propensity | 0.5 | 1.5 | Pα | Chou-Fasman scale | + +## Rules +- Features outside these ranges should be flagged as warnings. +- The pipeline should not crash on out-of-range features. +- Out-of-range features should be investigated. +- If a feature is consistently out of range, check the source data. diff --git a/docs/evidence/GUIDE_SEQUENCE_VALIDATION.md b/docs/evidence/GUIDE_SEQUENCE_VALIDATION.md new file mode 100644 index 00000000..aa102826 --- /dev/null +++ b/docs/evidence/GUIDE_SEQUENCE_VALIDATION.md @@ -0,0 +1,23 @@ +# Sequence Field Validation Report + +Validates sequence fields in pipeline inputs. + +## Checks Performed +- All characters are valid amino acids (A, C, D, E, F, G, H, I, K, L, M, N, P, Q, R, S, T, V, W, Y). +- Sequence length is within configured range (8-35 AA). +- Sequence is not empty. +- Sequence is uppercase. + +## Example +```bash +python -c " +from openamp_foundry.data.loaders import is_valid_sequence +print(is_valid_sequence('ACDEFGHIKL')) # True +print(is_valid_sequence('ACB')) # False (B is invalid) +" +``` + +## Rules +- Invalid sequences should be rejected at the loading stage. +- Invalid sequences should be logged with the candidate ID. +- The pipeline should continue processing valid sequences after rejecting invalid ones. diff --git a/docs/evidence/POLICY_MISSING_VALUES.md b/docs/evidence/POLICY_MISSING_VALUES.md new file mode 100644 index 00000000..23f1ed3e --- /dev/null +++ b/docs/evidence/POLICY_MISSING_VALUES.md @@ -0,0 +1,18 @@ +# Per-Feature Missing-Value Policy + +How missing feature values are handled. + +## Default Values +| Feature | Default When Missing | Reason | +|---------|:-------------------:|--------| +| charge_density | 0.0 | Neutral charge assumption | +| hydrophobic_moment | 0.0 | No amphipathicity | +| helix_propensity | 1.0 | Indifferent helix propensity | +| boman_index | 0.0 | No binding potential | + +## Rules +- Features should not be missing under normal pipeline operation. +- If a feature is missing, log a warning with the feature name. +- Use the default value that makes the least assumption. +- Missing features should be investigated and fixed at the source. +- The pipeline should not crash on missing features. diff --git a/docs/evidence/REGISTRY_SCORER_COMPONENTS.md b/docs/evidence/REGISTRY_SCORER_COMPONENTS.md new file mode 100644 index 00000000..3c3347e1 --- /dev/null +++ b/docs/evidence/REGISTRY_SCORER_COMPONENTS.md @@ -0,0 +1,21 @@ +# Scorer Component Registry + +Registry of all scoring components in the pipeline. + +| Scorer | Module | Purpose | Weight | +|--------|--------|---------|:------:| +| activity | scoring/activity.py | Antimicrobial activity-likeness | 0.40 | +| safety | scoring/safety.py | Hemolysis/toxicity risk | 0.25 | +| synthesis | scoring/synthesis.py | Synthesis feasibility | 0.15 | +| novelty | scoring/novelty.py | Novelty vs known references | 0.20 | +| ensemble | scoring/ensemble.py | Weighted combination | — | +| boman | scoring/boman.py | Boman index (binding potential) | — | +| serum_stability | scoring/stability.py | Protease resistance | — | +| hemolysis_risk | scoring/hemolysis.py | Dedicated hemolysis risk | — | +| rich_selectivity | scoring/selectivity_rich.py | Evidence-based selectivity | — | +| expert | scoring/expert.py | Safety-aware composite | — | + +## Rules +- New scorers should be registered here. +- The registry should be updated when weights change. +- Each scorer should have a module in `src/openamp_foundry/scoring/`. diff --git a/docs/evidence/SCHEMA_RANK_OUTPUT.md b/docs/evidence/SCHEMA_RANK_OUTPUT.md new file mode 100644 index 00000000..7e49ee2a --- /dev/null +++ b/docs/evidence/SCHEMA_RANK_OUTPUT.md @@ -0,0 +1,32 @@ +# Rank Output Schema for JSONL Records + +Schema for ranked candidate JSONL output. + +## Record Structure +```json +{ + "candidate_id": "AMPF-000001", + "sequence": "RRWQWRMKKLG", + "source": "demo", + "valid": true, + "selected": true, + "scores": { + "activity": 0.8387, + "safety": 0.9638, + "ensemble": 0.8889, + "charge_bias": 0.2345 + }, + "features": { + "length": 11, + "charge_density": 0.4545, + "hydrophobic_fraction": 0.3636 + }, + "selection_reason": ["High transparent activity-likeness score"] +} +``` + +## Rules +- Every record must have `candidate_id`, `sequence`, `scores`. +- `scores` must include at least `ensemble`. +- `selected` is a boolean indicating if the candidate passed all gates. +- `selection_reason` explains why a candidate was selected or rejected. diff --git a/docs/getting-started/CONTRIBUTOR_CURRICULUM.md b/docs/getting-started/CONTRIBUTOR_CURRICULUM.md index 747e8399..031be945 100644 --- a/docs/getting-started/CONTRIBUTOR_CURRICULUM.md +++ b/docs/getting-started/CONTRIBUTOR_CURRICULUM.md @@ -8,14 +8,14 @@ A staged path from first reading to a useful first pull request. - Python 3.11+ - Basic familiarity with command line and git -- Read [CONTRIBUTING.md](../CONTRIBUTING.md) +- Read [CONTRIBUTING.md](../../CONTRIBUTING.md) ## Stage 1: Orientation (30 min) Read these in order: 1. `README.md` — project overview and quickstart -2. `docs/evidence/LIMITATIONS_OVERVIEW.md` — honest limitations +2. [`docs/evidence/LIMITATIONS_OVERVIEW.md`](../evidence/LIMITATIONS_OVERVIEW.md) — honest limitations 3. `docs/getting-started/QUICKSTART.md` — run the demo 4. `AGENTS.md` — operating principles (useful even for humans) 5. `AGENT_TASKS.json` — safe task categories diff --git a/docs/getting-started/HUMAN_ONBOARDING.md b/docs/getting-started/HUMAN_ONBOARDING.md index 915c2c3d..aa0a0211 100644 --- a/docs/getting-started/HUMAN_ONBOARDING.md +++ b/docs/getting-started/HUMAN_ONBOARDING.md @@ -48,7 +48,7 @@ Read the docs that match your role. ### Engineer path - [`docs/engineering/ARCHITECTURE.md`](../engineering/ARCHITECTURE.md) -- [`CONTRIBUTING.md`](../) +- [`CONTRIBUTING.md`](../../CONTRIBUTING.md) - [`docs/evidence/BENCHMARKING.md`](../evidence/BENCHMARKING.md) - [`docs/evidence/DECISION_RULES.md`](../evidence/DECISION_RULES.md) diff --git a/docs/getting-started/QUICKSTART.md b/docs/getting-started/QUICKSTART.md index 52804287..2e6cb8d1 100644 --- a/docs/getting-started/QUICKSTART.md +++ b/docs/getting-started/QUICKSTART.md @@ -63,6 +63,6 @@ python3 -m pytest -q --tb=short ## Next Steps -- Read [`docs/evidence/LIMITATIONS_OVERVIEW.md`](docs/evidence/LIMITATIONS_OVERVIEW.md) for honest limitations -- Read [`CONTRIBUTING.md`](../CONTRIBUTING.md) for contribution guidelines +- Read [`docs/evidence/LIMITATIONS_OVERVIEW.md`](../evidence/LIMITATIONS_OVERVIEW.md) for honest limitations +- Read [`CONTRIBUTING.md`](../../CONTRIBUTING.md) for contribution guidelines - Read [`AGENTS.md`](../AGENTS.md) for the agent operating contract diff --git a/tests/test_csv_dialect.py b/tests/test_csv_dialect.py new file mode 100644 index 00000000..5260f504 --- /dev/null +++ b/tests/test_csv_dialect.py @@ -0,0 +1,24 @@ +"""Test CSV dialect and encoding.""" +from pathlib import Path +import csv + + +def test_demo_csv_is_readable(): + with open("examples/sequences/demo_candidates.csv", newline="") as f: + reader = csv.DictReader(f) + rows = list(reader) + assert len(rows) >= 2, "Should have at least 2 demo candidates" + assert "id" in reader.fieldnames or "candidate_id" in reader.fieldnames + + +def test_demo_csv_utf8(): + text = Path("examples/sequences/demo_candidates.csv").read_bytes() + text.decode("utf-8") # Should not raise + + +def test_reference_csv_is_readable(): + with open("examples/known_reference/demo_known_amps.csv", newline="") as f: + reader = csv.DictReader(f) + rows = list(reader) + assert len(rows) >= 2 + assert "id" in reader.fieldnames or "candidate_id" in reader.fieldnames diff --git a/tests/test_duplicate_records.py b/tests/test_duplicate_records.py new file mode 100644 index 00000000..fc86ce5f --- /dev/null +++ b/tests/test_duplicate_records.py @@ -0,0 +1,29 @@ +"""Test duplicate record handling.""" +from pathlib import Path + + +def test_demo_candidates_no_duplicates(): + """Demo candidates should not have duplicate IDs.""" + ids = [] + with open("examples/sequences/demo_candidates.csv") as f: + for line in f.readlines()[1:]: + cid = line.strip().split(",")[0] + ids.append(cid) + assert len(ids) == len(set(ids)), "Duplicate candidate IDs found" + + +def test_demo_references_no_duplicates(): + ids = [] + with open("examples/known_reference/demo_known_amps.csv") as f: + for line in f.readlines()[1:]: + cid = line.strip().split(",")[0] + ids.append(cid) + assert len(ids) == len(set(ids)) + + +def test_evidence_certificates_unique(): + ev_dir = Path("outputs/evidence") + if ev_dir.exists(): + files = list(ev_dir.glob("*.json")) + ids = [f.stem for f in files] + assert len(ids) == len(set(ids)) diff --git a/tests/test_scorer_registry_coverage.py b/tests/test_scorer_registry_coverage.py new file mode 100644 index 00000000..8a823c2d --- /dev/null +++ b/tests/test_scorer_registry_coverage.py @@ -0,0 +1,19 @@ +"""Test scorer registry coverage.""" +from pathlib import Path + + +def test_scoring_modules_exist(): + scoring_dir = Path("src/openamp_foundry/scoring") + assert scoring_dir.exists() + modules = [f.stem for f in scoring_dir.glob("*.py") if f.stem != "__init__"] + assert len(modules) >= 10, f"Only {len(modules)} scoring modules found" + + +def test_ensemble_scorer_importable(): + from openamp_foundry.scoring.ensemble import ensemble_score + assert callable(ensemble_score) + + +def test_activity_scorer_importable(): + from openamp_foundry.scoring.activity import activity_likeness_score + assert callable(activity_likeness_score)