"""Tests for seeding the commons from public DMS (dee.core.dms_seed). ESM-free: pure aggregation. We construct synthetic 'assays' where a substitution's effect is controlled across studies, and check the k-anonymity floor (≥ MIN_USERS independent assays) and the de-identified output shape. """ import pytest from dee.core import aggregate as _agg from dee.core.dms_seed import parse_proteingym_csv, seed_rows def _assay(aid, effects): """Build one assay's [(label, score)] where each single-site 'X{i}Y' gets a base score = its intended effect (plus a couple of WT-ish low rows so the within-assay z-score has spread).""" recs = [(lab, val) for lab, val in effects] return (aid, recs) def test_seed_keeps_substitution_seen_in_enough_assays(): # 'W>L' favorable, measured in 3 independent assays → survives k-anon (=3). assays = [] for i in range(_agg.MIN_USERS): assays.append(_assay(f"study{i}", [ (f"W{10 + i}L", 2.0), # the favorable W>L (f"K{20 + i}D", -1.0), # spread so z-score is defined (f"A{30 + i}G", 0.0), ])) rows = seed_rows(assays, enforce_gate=False) subs = {r["substitution"] for r in rows} assert "W>L" in subs wl = next(r for r in rows if r["substitution"] == "W>L") assert wl["n_users"] >= _agg.MIN_USERS # backed by ≥ 3 studies assert wl["mean_effect"] > 0 # favorable, correct sign def test_seed_drops_substitution_below_k_anon(): # 'C>Y' appears in only ONE assay → dropped (privacy floor). assays = [ _assay("only_study", [("C5Y", 3.0), ("K6D", -1.0), ("A7G", 0.0)]), _assay("study2", [("K6D", -1.0), ("A7G", 0.5), ("M8I", 0.2)]), _assay("study3", [("K6D", -0.8), ("A7G", 0.3), ("M8I", 0.1)]), ] rows = seed_rows(assays, enforce_gate=False) assert "C>Y" not in {r["substitution"] for r in rows} def test_seed_handles_multi_mutant_colon_labels(): # ProteinGym multi-mutants ('A1C:D5E') must not crash and should contribute. assays = [ _assay(f"s{i}", [("A1C:D5E", 1.5), ("K2D", -1.0), ("M3I", 0.0)]) for i in range(_agg.MIN_USERS) ] rows = seed_rows(assays, enforce_gate=False) assert isinstance(rows, list) # decomposed by the aggregate ridge, no error def test_seed_empty_when_no_assays(): assert seed_rows([], enforce_gate=False) == [] def test_parse_proteingym_csv(): csv_text = "mutant,DMS_score\nA1C,1.2\nD5E,-0.4\nbad,notanumber\n" recs = parse_proteingym_csv(csv_text) assert recs == [("A1C", 1.2), ("D5E", -0.4)] # bad row skipped def test_parse_proteingym_csv_alt_columns(): csv_text = "mutation,fitness\nW10L,0.9\n" assert parse_proteingym_csv(csv_text) == [("W10L", 0.9)] def test_parse_proteingym_csv_missing_columns(): assert parse_proteingym_csv("foo,bar\n1,2\n") == []