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
47 changes: 47 additions & 0 deletions tests/test_batch_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,37 @@ def test_risk_thresholds_present(self):
assert "risk_thresholds" in report
assert "hydrophobic_fraction_high" in report["risk_thresholds"]

def test_high_charge_density_flagged(self):
cand = [_make_candidate("HIGH-CHG", "KKKKKKKKK")]
cand[0]["features"]["charge_density"] = 0.70
report = toxicity_hemolysis_risk_report(cand)
assert "high_charge_density" in report["candidates"][0]["risk_flags"]

def test_high_cysteine_fraction_flagged(self):
cand = [_make_candidate("HIGH-CYS", "CCCCCKKKK")]
cand[0]["features"]["cysteine_fraction"] = 0.55
report = toxicity_hemolysis_risk_report(cand)
assert "high_cysteine_fraction" in report["candidates"][0]["risk_flags"]

def test_long_repeat_run_flagged(self):
cand = [_make_candidate("REPEAT", "KKKKKKRRL")]
cand[0]["features"]["longest_repeat_run"] = 6
report = toxicity_hemolysis_risk_report(cand)
assert "long_repeat_run" in report["candidates"][0]["risk_flags"]

def test_length_exceeds_35aa_flagged(self):
long_seq = "KWKLFKKIGAVLKVLKWKLFKKIGAVLKVLKWKLFK" # 36 AA (> 35 threshold)
cand = [_make_candidate("LONG", long_seq)]
report = toxicity_hemolysis_risk_report(cand)
assert "length_exceeds_35aa" in report["candidates"][0]["risk_flags"]

def test_n_with_risk_flags_counts_correctly(self):
clean = _make_candidate("CLEAN", "KWKLFKKIGAVLKVL")
risky = _make_candidate("RISKY", "LLLLLLLLLLL")
risky["features"]["hydrophobic_fraction"] = 0.90
report = toxicity_hemolysis_risk_report([clean, risky])
assert report["n_with_risk_flags"] == 1


class TestSynthesisFeasibilityReport:
def test_report_type_field(self):
Expand Down Expand Up @@ -382,3 +413,19 @@ def test_markdown_requires_human_review(self, ranked_jsonl, tmp_path):
write_batch_pack_markdown(pack, md_path)
content = md_path.read_text()
assert "Human Review" in content or "human expert" in content.lower()

def test_markdown_truncates_clusters_beyond_20(self, tmp_path):
_CANONICAL = list("ACDEFGHIKLMNPQRSTVWY")
rows = []
for i, aa in enumerate(_CANONICAL):
rows.append(_make_candidate(f"CAND-{i:03d}", aa * 10, novelty=0.5 + i * 0.001))
rows.append(_make_candidate("CAND-020", "KWKLFKKIGAVLKVLKWKLFK", novelty=0.6))
jsonl_path = tmp_path / "big.jsonl"
with open(jsonl_path, "w") as f:
for r in rows:
f.write(json.dumps(r) + "\n")
pack = generate_batch_pack(jsonl_path)
md_path = tmp_path / "big.md"
write_batch_pack_markdown(pack, md_path)
content = md_path.read_text()
assert "more clusters" in content
71 changes: 71 additions & 0 deletions tests/test_lab_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
- Invalid lab results are rejected with useful errors
- summarise_lab_results produces correct aggregates
- candidate_result_map groups correctly
- load_lab_results_dir loads all valid files, skips invalid, returns sorted
"""
from __future__ import annotations

import json
import warnings

import pytest

from openamp_foundry.data.lab_results import (
candidate_result_map,
load_lab_result,
load_lab_results_dir,
summarise_lab_results,
)

Expand Down Expand Up @@ -172,3 +175,71 @@ def test_unknown_candidate_not_in_map(self):

def test_empty_results_gives_empty_map(self):
assert candidate_result_map([]) == {}


class TestLoadLabResultsDir:
def test_empty_directory_returns_empty_list(self, tmp_path):
results = load_lab_results_dir(tmp_path)
assert results == []

def test_loads_all_valid_files(self, results_dir):
results = load_lab_results_dir(results_dir)
assert len(results) == 3

def test_sorted_by_assay_date(self, tmp_path):
date_map = {0: "2026-07-03", 1: "2026-07-01", 2: "2026-07-02"}
for i in range(3):
result = _valid_result(
result_id=f"RES-{i:03d}",
candidate_id=f"CAND-{i:03d}",
assay_date=date_map[i],
)
(tmp_path / f"RES-{i:03d}.json").write_text(json.dumps(result))
results = load_lab_results_dir(tmp_path)
dates = [r["assay_date"] for r in results]
assert dates == sorted(dates)
assert dates[0] == "2026-07-01"

def test_sorted_by_result_id_within_same_date(self, tmp_path):
for i in [3, 1, 2]:
result = _valid_result(
result_id=f"RES-{i:03d}",
candidate_id=f"CAND-{i:03d}",
assay_date="2026-07-10",
)
(tmp_path / f"RES-{i:03d}.json").write_text(json.dumps(result))
results = load_lab_results_dir(tmp_path)
ids = [r["result_id"] for r in results]
assert ids == sorted(ids)

def test_skips_invalid_json_file_with_warning(self, tmp_path):
valid = _valid_result(result_id="GOOD-001", candidate_id="CAND-A", assay_date="2026-07-01")
(tmp_path / "good.json").write_text(json.dumps(valid))
(tmp_path / "bad.json").write_text("this is not valid json {{{")
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
results = load_lab_results_dir(tmp_path)
assert len(results) == 1
assert results[0]["result_id"] == "GOOD-001"
assert any("bad.json" in str(w.message) or "Skipped" in str(w.message) for w in caught)

def test_skips_schema_invalid_file_with_warning(self, tmp_path):
valid = _valid_result(result_id="GOOD-001", candidate_id="CAND-A", assay_date="2026-07-01")
invalid = _valid_result()
del invalid["candidate_id"]
(tmp_path / "good.json").write_text(json.dumps(valid))
(tmp_path / "invalid_schema.json").write_text(json.dumps(invalid))
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
results = load_lab_results_dir(tmp_path)
assert len(results) == 1
assert results[0]["result_id"] == "GOOD-001"
assert any("Skipped" in str(w.message) for w in caught)

def test_non_json_files_ignored(self, tmp_path):
valid = _valid_result(result_id="R001", candidate_id="C001", assay_date="2026-07-01")
(tmp_path / "result.json").write_text(json.dumps(valid))
(tmp_path / "notes.txt").write_text("not a json file")
(tmp_path / "data.csv").write_text("col1,col2\n1,2")
results = load_lab_results_dir(tmp_path)
assert len(results) == 1
Loading