smoke_v0 disease mapping (D7): proposal for approval

Refs #430, #439 (decisions D6 and D7), #296, #244. Playbook: docs/mods_smoke_run_1_playbook.md §1.3, §3 (D6, D7) and §5.1 step 3.

Outcome (2026-09-29). This table was approved in PR #460 (2026-09-28), and backend/scripts/prs/build_smoke_v0_mapping_tsv.py now writes it. Gate F then released 590 of the 594 scores, over all 109 anchors, as smoke_v0 (release run c3be9df0). The four it held back were non-pick alternates that failed the post-D3 panel-coverage rule. Below is the proposal as it stood on 2026-09-28.

Nothing here is written to code or to the database. Choosing a disease anchor is an ontology mapping, so under the CLAUDE.md hard rule Richard approves the table before #430, #296 or #244 writes it. Every number below was measured on 2026-09-28 against the live quarantined E2 rows. They are re-measured on the E3 rows before release (§8). The document holds ids and counts only: no raw, z or percentile score values.

Summary

  • The #296 funnel reproduces exactly through R7 (3,752 → 1,861). R8, counted the way #296 counted it, gives 736 scores over 148 anchors, against #296’s 738 over 149. The whole gap is basal cell carcinoma (2 scores). It has no node in the composition under either spelling of its id (J10).
  • D7’s v0 exclusions then take the set to 594 scores over 109 diseases. They remove 24 multi-trait scores and 118 scores whose disease PrimeKG holds only inside a grouped node. The playbook’s “738 scores over 149 diseases” (D6, and the check in §5.1 step 3) is the count before D7. If this table is approved, that check should expect 594 / 109 on the E3 rows.
  • The CRC anchor is confirmed as MONDO:0005575 “Colorectal Cancer”. PrimeKG’s colorectal cancer node is merged into it under the padded CURIE, alongside CIViC and ClinVar. It has 7 drug neighbours: 5 through treats, 1 through contraindicated_for and 1 through has_off_label_use. The playbook’s “10 / 2 / 2” counts export lines, and every drug–disease pair is written in both directions. PrimeKG’s MONDO:24331 “colorectal carcinoma” has gene associations only (§5).
  • Of the 109 diseases, 94 are unambiguous and 15 are flagged, across nine judgement calls (J1–J9). J10 is an exclusion, so it has no row.
  • Two traps for whoever implements #430.
    • The scoring-file header maps all 9 v0 colorectal cancer scores to EFO_0005842. A mapping read from mods_prs_score_manifest.trait_efo would therefore lose the index disease.
    • The pickle reader settles ties in computed_at order. The chosen score for each disease therefore has to be written down explicitly (§8).

1. The funnel

StepRuleScoresAnchors
—live quarantined rows (E2 run 029be6e3, callset gate_a.n_records 16,964,243)3,752
R1the weight scale is additive2,825
R2inside the theoretical bounds, and reconciles2,825
R3at least one locus scored2,825
R4structural overlap ≥ 0.902,162
R5zero palindromic rows1,871
R6complement-only rate ≤ 5%1,865
R7genotype callability ≥ 0.901,861
R8has a MONDO trait with a node in maureen_focus_v1, grouped nodes included (#296’s count)736148
D7single-trait scores only712133
D7no PrimeKG grouped anchors594109
axisone score per disease109109

R1–R7 match #296’s comment row for row.

R8 removes 1,125 scores:

  • 1,123 carry no MONDO trait: EFO only 835, OBA only 239, HP only 35, EFO + OBA 10, and EFO + PATO 4. D7 excludes HP-only and EFO disease-like traits, and this is the step where they go.
  • 2 carry a MONDO trait that no node in the composition carries: basal cell carcinoma (J10).

“Anchors” counts graph nodes. Several MONDO ids can share one grouped node, so the R8 and single-trait rows count fewer anchors than MONDO ids.

2. The rules applied

R1–R7, read from each row’s metadata:

  • R1: gate_c.weight_scale = 'additive'.
  • R2: gate_c.within_theoretical_bounds and gate_c.reconciles.
  • R3: gate_c.outcomes.scored ≥ 1.
  • R4: coverage_v1.structural_overlap, matched_rows / scoring_rows ≥ 0.90.
  • R5: gate_c.outcomes.ambiguous_palindromic = 0. These are the palindromic rows that reached orientation, the ones whose value depends on the pending palindrome decision.
  • R6: gate_c.n_complement_only_match / (scored + ref_allele_mismatch) ≤ 0.05, the same rate mods_prs_pipeline_service.strand_profile uses.
  • R7: coverage_v1.genotype_callability, callable_loci / matched_rows ≥ 0.90.

R8 and D7:

  • Trait source. The cached PGS REST metadata.json (trait_efo), cached 2026-07-09 to 2026-07-11. The REST value is the current mapping. The scoring-file header is what mods_prs_score_manifest.trait_efo holds, and it disagrees on 50 eligible scores. 36 of those are colorectal cancer, where the header still says EFO_0005842.
  • MONDO only. EFO, HP, OBA and PATO traits are not mapped.
  • Single trait. The REST trait list has exactly one entry. J9 covers how to read this rule.
  • Anchor. Take a node that carries the trait’s MONDO number under either spelling (MONDO:5148 or MONDO:0005148) and is in the composition:
    • if two such nodes exist, take the one that carries PrimeKG’s source (J1);
    • if there is no PrimeKG node, take the single CIViC/ClinVar/OncoKB node;
    • if only a MONDO_grouped: node carries the id, exclude it (D7);
    • if no node carries it, exclude it.
  • Vocabulary. maureen_focus_v1 as registered today: version v1, is_locked = false, last updated 2026-05-18 20:34 UTC. Its members are civic, clinvar, oncokb, pharmgkb_variants, and prime_kg minus the anatomy, cellular_component, molecular_function and exposure domains. The entity set is ModsKGCompositionService.build_filter’s SQL, verbatim. Relation counts follow materialize(): the relation comes from a member source, and both of its ends are in the set.
    • Cross-check: all 109 anchors appear in the de-facto foundation’s export on disk (916bdd2c, runs/2026-05-18_maureen_focus_v1/data/{train,valid,test}.txt).
    • The final check is against the new export’s vocabulary, after #432.
  • One score per disease. Take the most scored loci: snps_used, which equals Gate C’s outcomes.scored on every surviving row. This is today’s reader rule.
    • Tie-break: the lower PGS accession number.
    • J6–J8 propose overrides where the rule’s pick is wrong for her.

3. How many rows are clear

Diseasesv0 scores
Unambiguous94492
Flagged by J1–J915102
Total109594

How each anchor was resolved:

  • exact (93): PrimeKG’s MONDO:<n>, the only node that carries the id.
  • exact, twin (5): PrimeKG’s MONDO:<n>, with a padded twin also in the vocabulary (J1).
  • padded, merged (8): only the padded CURIE is in the vocabulary, and PrimeKG’s node is merged into it.
  • padded, no PrimeKG (3): only the padded CURIE, carried by CIViC or ClinVar.

These are notes, not calls:

  • 27 anchors (62 v0 scores) have no drug neighbour in the composition (no drugs). The personalization adjustment is one hop (evaluate_personalized_maple.py), so their PRS cannot move any drug. The edge-type breakdown in M7 will show this.

  • 8 anchors are thin (thin): 5 or fewer in-composition relation ends. Check these first against the #432 export.

  • Umbrella classes have the most drug neighbours, and most of them are contraindications. Liver disorder has 644, every one through contraindicated_for. Hypertensive disorder has 403, and diabetes mellitus 381. The adjustment is edge-blind, so these push drugs the same way an indication would (#420). Worth knowing when reading M7.

  • Some diseases share the axis with their parent or child class. D7 keys by exact id and merges nothing, so a drug next to both nodes gets both adjustments. Examples:

    • urinary bladder cancer and urinary bladder carcinoma, which PrimeKG links to the same 12 drugs;
    • colorectal cancer and colorectal carcinoma (J3);
    • melanoma and cutaneous melanoma;
    • heart failure and congestive heart failure.

    That is acceptable for a machinery test.

4. Judgement calls (the flagged rows)

#CallAffectsRecommendation
J1MONDO twins. Both spellings of the id are in the vocabulary. The padded twins are ClinVar or CIViC nodes, some of them merged with a GP-KG node that is named by a UMLS CUI. They have 1–16 in-composition relation ends and no drug neighbours. The PrimeKG nodes have 72–364.stroke disorder (17 scores); acquired polycythemia vera, lung carcinoma, seborrheic keratosis, urinary bladder cancer (1 each)Key to PrimeKG’s node, MONDO:<n>. The same rule, “take the node that carries PrimeKG’s source”, is what makes CRC’s anchor the padded MONDO:0005575. The twin defect itself is #438.
J2Hypothyroidism. The only non-grouped node is ClinVar’s MONDO:0005420, with 1 relation end and no drugs. PrimeKG holds hypothyroidism only inside MONDO_grouped:5420_16412_15777 (3 conditions, 115 drug neighbours), which D7 excludes.hypothyroidism (10 scores)Keep it, keyed to MONDO:0005420. It is the exact class, and it is in the vocabulary. The position will be measured, and it will move no drug. If its one relation does not survive the #432 export, drop the disease. The alternative is to drop it now.
J3Colorectal carcinoma. The Catalog maps 8 scores to MONDO_0024331, but all of them report “Colorectal cancer” (one says “Colorectal neoplasm”). PrimeKG’s MONDO:24331 has gene associations only.colorectal carcinoma → MONDO:24331 (8 scores)Keep it as its own position. Don’t fold it into the index anchor. Folding would be a carcinoma → cancer mapping. The index pick is the same either way: the best MONDO_0024331 score has 1,073,979 loci, against PGS003395’s 1,116,726.
J4The index disease’s score. Most loci picks PGS003395, a PRS-CSx --meta score (Xin 2023). Its discovery GWAS was 65.6% EAS and 34.4% EUR, of 324,079 people. Two of its three evaluations were in EUR samples.colorectal cancer (9 scores)Keep PGS003395. The EUR-only alternative is PGS003431 (LDpred2-inf, Briggs 2022, 1,077,026 loci). Its GWAS is 67,878 EUR people. PGS003395’s EUR share alone is about 111,000.
J5Tie. PGS003972 and PGS003973 both score 1,090,924 loci. They are PRSAAA and PRSAAA_woUKB: the same PRS-CS model, the second trained without UK Biobank.abdominal aortic aneurysm (4 scores)Accept the tie-break: PGS003972.
J6Six-way tie. PGS005073–PGS005078 score 1,252,829 loci each. They are Gunn 2024 LDpred builds that differ only in their GWAS summary statistics and LD reference. REST records the same ancestry for all six, so only their names tell them apart. The tie-break would take PGS005073: AFR summary statistics, AFR LD.congestive heart failure (15 scores)Override to PGS005074 (EURss_eurld: EUR summary statistics, EUR LD).
J7The same six-way tie, PGS005034–PGS005039. The tie-break would take PGS005034 (AFR/AFR).myocardial infarction (13 scores)Override to PGS005035 (EURss_eurld).
J8A pick trained on another ancestry. For CKD, most loci picks PGS005090, “CKD_PRS_AA” (Jones 2024). It was built for Black American populations, and its GWAS and evaluation were 100% AFR. It beats PGS003988 by 114 of 1,113,329 loci. PGS003988 is DBSLMM-auto on GWAS Catalog GCST008059, developed in a 100% EUR sample. Pulmonary embolism’s only v0 score, PGS003861 (278 loci), is EAS-only.chronic kidney disease (18 scores); pulmonary embolism (1)CKD: override to PGS003988, the runner-up. PE: keep it, since there is no alternative. Picks whose GWAS includes Europeans stay as they are, including the multi-ancestry “MAE” picks: Graves, Hashimoto, thyroid carcinoma, preeclampsia, ulcerative colitis and nephrotic syndrome. So does CRC (J4). Glioblastoma’s only score reports no ancestry.
J9What “multi-trait” means. This table reads it as “the REST trait list has more than one entry, in any ontology”, which removes 24 scores. The looser reading, “two or more MONDO traits”, would remove 16. It would keep 8 more: PGS001844 and PGS002056 (phlebitis + HP thrombophlebitis), PGS001861 and PGS002072 (cholelithiasis + HP cholecystitis), PGS002045 (eye disorder + EFO eye inflammation), PGS002768 (osteoporosis + EFO heel bone mineral density), and PGS005262 and PGS005263 (nodular goiter + EFO “benign”).cholelithiasis and osteoporosis (their picks would change); phlebitis, eye disorder and nodular goiter (would be added)The strict reading, as tabled. Each of the 8 is a combined or proxy phenotype. The loose reading would swap osteoporosis’s pick for a heel-BMD score (PGS002768), and cholelithiasis’s 12-locus pick for PGS002072 (cholecystitis + cholelithiasis).
J10Basal cell carcinoma, MONDO_0020804 (PGS003416, PGS003417). No node in the composition carries this id, under either spelling. RTX-KG2 has one, but RTX-KG2 is not a member. The nearest node in the vocabulary is MONDO:0005341 “Basal Cell Carcinoma” (PrimeKG and CIViC). RTX-KG2 records it as a subclass of MONDO:0020804, and also, contradictorily, as an exact_match.not in the tableExclude it from v0. A parent → child mapping is not exact. This one exclusion is the whole gap between 738 / 149 and 736 / 148.

5. The CRC index anchor, checked against the data

  • MONDO:0005575 “Colorectal Cancer” (entity 257724). Its sources:

    • civic (civic_disease:9256);
    • clinvar;
    • prime_kg (PrimeKG node 28849, merged under the padded CURIE);
    • rtx_kg2.

    There is no MONDO:5575 entity.

  • Every drug edge on it is PrimeKG’s, and each is stored in both directions:

    • treats: fluorouracil, trifluridine, tipiracil, carmofur and cetuximab;
    • has_off_label_use: oxaliplatin;
    • contraindicated_for: indomethacin.
  • In the 916bdd2c export on disk, the node appears in 1,797 triples: 10 treats lines, 2 contraindicated_for and 2 has_off_label_use. These are the playbook’s numbers, and they count each drug–disease pair twice.

  • MONDO:24331 “colorectal carcinoma” (PrimeKG node 36480) has no drug edges. In the export it appears in 1,416 triples, all of them gene associations and subclass_of. MONDO:0024331 exists only in RTX-KG2.

  • The index position has 9 v0 scores. The chosen score is PGS003395, with 1,116,726 scored loci (J4).

  • The header trap: all 9 of those scores carry EFO_0005842 in the scoring-file header. Only the REST record gives MONDO_0005575.

6. The table

Legend.

  • Drugs: distinct drug neighbours over every in-composition edge. In brackets, the drugs reached through treats / contraindicated_for / has_off_label_use.
  • Flags:
    • INDEX: the required index disease.
    • J1–J9: see §4.
    • no drugs: no drug neighbour.
    • thin: 5 or fewer in-composition relation ends.
    • hdr: for at least one v0 score, the file header’s trait differs from REST.
    • CUI name: the node’s display name is a bare UMLS CUI, a GP-KG merge artifact. The CURIE is still the right class.
Disease (REST label)MONDO traitAnchor in the vocabularyResolvedv0 scoresChosen PGSScored lociDrugs: any (t/c/o)Flag
abdominal aortic aneurysmMONDO_0005350MONDO:5350exact4PGS0039721,090,9240 (0/0/0)J5, no drugs
acquired polycythemia veraMONDO_0009891MONDO:9891exact, twin1PGS0018106510 (5/2/3)J1
alcoholic liver cirrhosisMONDO_0006644MONDO:6644exact1PGS004913200 (0/0/0)no drugs
appendicitisMONDO_0005649MONDO:5649exact1PGS001785888,22920 (1/19/0)
asthmaMONDO_0004979MONDO:4979exact34PGS0023111,081,767166 (39/124/5)
atopic eczemaMONDO_0004980MONDO:4980exact1PGS0027551,065,11044 (31/10/10)
atrial fibrillationMONDO_0004981MONDO:4981exact31PGS0013562,903,76848 (14/13/21)
attention deficit-hyperactivity disorderMONDO_0007743MONDO:7743exact1PGS00375334,21519 (11/1/7)
autoimmune diseaseMONDO_0007179MONDO:7179exact3PGS0023121,081,76713 (7/6/0)
benign colon neoplasmMONDO_0002278MONDO:2278exact2PGS002019651,8110 (0/0/0)no drugs
benign prostatic hyperplasiaMONDO_0010811MONDO:10811exact2PGS002076612,538176 (9/165/2)
biliary tract disorderMONDO_0004868MONDO:4868exact2PGS002073355,32037 (3/34/0)
bilirubin metabolism diseaseMONDO_0024431MONDO:24431exact2PGS00203219,2700 (0/0/0)no drugs
bone fractureMONDO_0005315MONDO:5315exact2PGS002137720,57517 (0/17/0)
brain cancerMONDO_0001657MONDO:1657exact1PGS00180811441 (3/37/1)
breast carcinomaMONDO_0004989MONDO:0004989padded, merged26PGS0022426,275,9639 (7/0/2)
cardiovascular disorderMONDO_0004995MONDO:4995exact4PGS0052533,614,240170 (1/169/0)
celiac diseaseMONDO_0005130MONDO:5130exact3PGS00206756,8917 (0/0/7)
cellulitisMONDO_0005230MONDO:5230exact2PGS002081625,7820 (0/0/0)no drugs
cerebrovascular disorderMONDO_0011057MONDO:11057exact1PGS002053585,630141 (13/126/2)
cervical carcinomaMONDO_0005131MONDO:5131exact3PGS0034282,805,66019 (3/13/3)
cholelithiasisMONDO_0012672MONDO:12672exact1PGS0125821223 (3/20/0)J9
chronic kidney diseaseMONDO_0005300MONDO:5300exact18PGS0050901,113,32948 (0/48/0)J8
chronic obstructive pulmonary diseaseMONDO_0005002MONDO:5002exact4PGS001788886,99782 (7/68/7)
cirrhosis of liverMONDO_0005155MONDO:5155exact1PGS0046211,063,01657 (0/57/0)
colorectal cancerMONDO_0005575MONDO:0005575padded, merged9PGS0033951,116,7267 (5/1/1)INDEX, J4, hdr
colorectal carcinomaMONDO_0024331MONDO:24331exact8PGS0045801,073,9790 (0/0/0)J3, no drugs
congenital vitamin K-dependent coagulation factors deficiencyMONDO_0015722MONDO:15722exact2PGS00203431,8310 (0/0/0)no drugs
congestive heart failureMONDO_0005009MONDO:5009exact15PGS0050731,252,829109 (24/87/0)J6
cor pulmonaleMONDO_0004596MONDO:4596exact2PGS002049217,03449 (0/49/0)
coronary artery disorderMONDO_0005010MONDO:5010exact33PGS0022446,324,270258 (1/257/0)
coronary atherosclerosisMONDO_0021661MONDO:21661exact2PGS002048743,88347 (0/47/0)
COVID-19MONDO_0100096MONDO:100096exact1PGS004938933,8190 (0/0/0)no drugs
cutaneous melanomaMONDO_0005012MONDO:0005012padded, no PrimeKG2PGS0033826390 (0/0/0)no drugs, thin, CUI name
dementiaMONDO_0001627MONDO:1627exact7PGS0051701,283,85527 (3/24/0)
diabetes mellitusMONDO_0005015MONDO:5015exact2PGS0023211,081,767381 (49/332/0)
diabetic retinopathyMONDO_0005266MONDO:5266exact2PGS002027379,92911 (1/9/1)
digestive system disorderMONDO_0004335MONDO:4335exact2PGS002069730,1166 (0/6/0)
disorder of appendixMONDO_0056798MONDO:56798exact2PGS002064485,4960 (0/0/0)no drugs
disorder of pilosebaceous unitMONDO_0002917MONDO:2917exact1PGS002086156,8340 (0/0/0)no drugs
drug allergyMONDO_0000775MONDO:775exact2PGS002097725,1150 (0/0/0)no drugs, thin
duodenitisMONDO_0004627MONDO:4627exact1PGS0018521906 (0/6/0)
endometriosisMONDO_0005133MONDO:5133exact2PGS002077521,74130 (11/17/2)
epilepsyMONDO_0005027MONDO:5027exact1PGS002760816,250327 (23/302/4)
ganglion or cyst of synovium/tendon/bursaMONDO_0004874MONDO:4874exact1PGS0018792040 (0/0/0)no drugs, thin
glioblastomaMONDO_0018177MONDO:18177exact1PGS0033848665 (2/2/1)
goutMONDO_0005393MONDO:5393exact21PGS0027621,066,557113 (20/89/4)
Graves diseaseMONDO_0005364MONDO:5364exact2PGS0052651,058,988200 (4/190/6)
Hashimoto thyroiditisMONDO_0007699MONDO:7699exact2PGS0052711,058,97212 (1/11/0)
head and neck squamous cell carcinomaMONDO_0010150MONDO:0010150padded, no PrimeKG2PGS012586947,8280 (0/0/0)no drugs
headache disorderMONDO_0021146MONDO:21146exact2PGS002145703,45718 (17/1/0)
hearing disorderMONDO_0021945MONDO:21945exact2PGS002104848,41811 (0/11/0)
heart failureMONDO_0005252MONDO:5252exact11PGS0125521,874,26046 (7/39/0)
hypertensive disorderMONDO_0005044MONDO:5044exact18PGS0029981,086,276403 (103/298/2)
hypothyroidismMONDO_0005420MONDO:0005420padded, no PrimeKG10PGS0052181,082,5190 (0/0/0)J2, no drugs, thin
insomniaMONDO_0013600MONDO:13600exact4PGS0033231,086,27662 (36/22/4)
intracerebral hemorrhageMONDO_0013792MONDO:13792exact1PGS0049432,062,23925 (0/25/0)
iron metabolism diseaseMONDO_0002279MONDO:2279exact2PGS0020316,56831 (3/28/0)
liver disorderMONDO_0005154MONDO:5154exact2PGS002071344,182644 (0/644/0)
lung carcinomaMONDO_0005138MONDO:5138exact, twin1PGS004691919,7590 (0/0/0)J1, no drugs
melanomaMONDO_0005105MONDO:0005105padded, merged1PGS0034306413 (4/9/0)
multiple sclerosisMONDO_0005301MONDO:5301exact3PGS002038126,17814 (12/2/0)
myocardial infarctionMONDO_0005068MONDO:5068exact13PGS0050341,252,829226 (9/209/8)J7
nephrotic syndromeMONDO_0005377MONDO:5377exact1PGS0125391,055,27518 (9/9/0)
neuroblastomaMONDO_0005072MONDO:0005072padded, merged1PGS002261229 (4/1/4)
nicotine dependenceMONDO_0008575MONDO:8575exact2PGS002037827,4902 (2/0/0)
obesity disorderMONDO_0011122MONDO:11122exact1PGS005235692,748103 (20/81/2)
obstructive sleep apnea syndromeMONDO_0007147MONDO:7147exact5PGS005220960,70061 (1/60/0)
occlusion precerebral arteryMONDO_0003718MONDO:3718exact1PGS002052478,4960 (0/0/0)no drugs
open-angle glaucomaMONDO_0005338MONDO:5338exact1PGS001797863,09274 (30/44/0)
oral cavity cancerMONDO_0005515MONDO:5515exact1PGS012587947,8280 (0/0/0)no drugs
oropharynx cancerMONDO_0004608MONDO:0004608padded, merged1PGS012588947,8280 (0/0/0)no drugs
osteoporosisMONDO_0005298MONDO:5298exact2PGS002095643,10378 (31/46/4)J9
ovarian carcinomaMONDO_0005140MONDO:0005140padded, merged1PGS0051662,7350 (0/0/0)no drugs
pelvic organ prolapseMONDO_0000082MONDO:82exact1PGS0022883,138,8760 (0/0/0)no drugs, thin
peripheral arterial diseaseMONDO_0005386MONDO:5386exact1PGS0052171,261,15367 (5/61/1)
peripheral nervous system disorderMONDO_0003620MONDO:3620exact2PGS002039780,16749 (0/49/0)
peripheral vascular diseaseMONDO_0005294MONDO:5294exact2PGS002055585,31766 (5/60/1)
polymyalgia rheumaticaMONDO_0019735MONDO:19735exact2PGS002091126,7097 (1/0/6)
preeclampsiaMONDO_0005081MONDO:5081exact3PGS0045931,076,09510 (1/9/0)
prolapse of female genital organMONDO_0001592MONDO:1592exact2PGS002078755,9320 (0/0/0)no drugs
prostate carcinomaMONDO_0005159MONDO:0005159padded, merged21PGS0022416,278,60319 (6/13/0)
psoriasisMONDO_0005083MONDO:5083exact5PGS0023441,081,76736 (16/20/3)
pulmonary embolismMONDO_0005279MONDO:5279exact1PGS00386127846 (13/33/0)J8
renal cell carcinomaMONDO_0005086MONDO:5086exact1PGS0042451211 (11/0/0)
respiratory system disorderMONDO_0005087MONDO:5087exact2PGS0023471,081,7670 (0/0/0)no drugs
retinal detachmentMONDO_0008375MONDO:8375exact2PGS002040689,8666 (0/6/0)
Rh isoimmunizationMONDO_0006953MONDO:6953exact2PGS00208031,6230 (0/0/0)no drugs, thin
rheumatoid arthritisMONDO_0008383MONDO:8383exact17PGS0027691,058,22176 (67/6/3)
seborrheic keratosisMONDO_0008420MONDO:8420exact, twin1PGS002085469,67247 (40/8/0)J1
skin disorderMONDO_0005093MONDO:5093exact2PGS0023201,081,76722 (14/8/0)
skin sarcoidosisMONDO_0006611MONDO:6611exact2PGS002084393,9460 (0/0/0)no drugs, thin
sleep apnea syndromeMONDO_0005296MONDO:5296exact3PGS0032131,083,75160 (0/60/0)
stroke disorderMONDO_0005098MONDO:5098exact, twin17PGS0125511,931,817116 (11/103/2)J1
substance-related disorderMONDO_0002494MONDO:2494exact2PGS003849569,241113 (0/113/0)
systemic lupus erythematosusMONDO_0007915MONDO:7915exact1PGS0008034097 (12/82/3)
thyroid gland carcinomaMONDO_0015075MONDO:0015075padded, merged6PGS0052591,058,9883 (3/0/0)
thyroid gland disorderMONDO_0003240MONDO:3240exact2PGS0023511,081,76716 (2/14/0)
thyrotoxicosisMONDO_0010138MONDO:10138exact2PGS002023272,940200 (4/190/6)
type 2 diabetes mellitusMONDO_0005148MONDO:5148exact111PGS0022436,216,78268 (48/20/0)hdr
ulcerative colitisMONDO_0005101MONDO:5101exact3PGS004253725,855110 (11/95/4)
urinary bladder cancerMONDO_0001187MONDO:1187exact, twin1PGS0037361212 (2/3/7)J1
urinary bladder carcinomaMONDO_0004986MONDO:4986exact2PGS002017498,51612 (2/3/7)
urolithiasisMONDO_0024647MONDO:24647exact2PGS002075713,8903 (0/3/0)
uterine carcinomaMONDO_0005213MONDO:5213exact1PGS001795888,5290 (0/0/0)no drugs
uterine corpus cancerMONDO_0006003MONDO:6003exact1PGS0037401534 (3/26/5)
vascular disorderMONDO_0005385MONDO:5385exact2PGS002059590,1984 (3/1/0)
venous thromboembolismMONDO_0005399MONDO:5399exact5PGS0033321,066,0260 (0/0/0)no drugs, thin
vitiligoMONDO_0008661MONDO:8661exact1PGS000760424 (2/2/0)

7. What D7 leaves out

The grouped exclusion is the biggest cut: 118 scores, over 28 MONDO ids in 24 PrimeKG groups. It drops:

  • Alzheimer disease (24 scores);
  • type 1 diabetes (17);
  • inflammatory bowel disease (16);
  • major depressive disorder (9);
  • dilated cardiomyopathy (8).

It is also the right cut for v0. Five traits sit in a group that PrimeKG names for a different disease:

  • depressive disorder, eating disorder and sleep disorder are all in the “anxiety disorder” group;
  • uterine benign neoplasm is in “uterine cervix neoplasm”;
  • esophageal adenocarcinoma is in “esophageal adenosquamous carcinoma”.

Label-matched groups (Alzheimer disease, type 1 diabetes, IBD) are candidates for run #2, not for v0.

The 118 grouped-anchor scores, by MONDO trait
MONDO traitLabelv0 candidates lostPrimeKG group it sits inGroup sizeDrugs
MONDO_0004975Alzheimer disease24Alzheimer disease2164
MONDO_0005147type 1 diabetes mellitus17type 1 diabetes mellitus2126
MONDO_0005265inflammatory bowel disease16inflammatory bowel disease31147
MONDO_0002009major depressive disorder9major depressive disorder325
MONDO_0005021dilated cardiomyopathy8dilated cardiomyopathy417
MONDO_0005150age-related macular degeneration4macular degeneration187
MONDO_0005041glaucoma4glaucoma395
MONDO_0005306ankylosing spondylitis3ankylosing spondylitis231
MONDO_0004872hemorrhoid3hemorrhoid223
MONDO_0007079alcohol dependence2substance abuse/dependence8247
MONDO_0000385benign digestive system neoplasm2digestive system neoplasm30
MONDO_0005129cataract2cataract460
MONDO_0018102corneal dystrophy2corneal dystrophy60
MONDO_0002050depressive disorder2anxiety disorder12232
MONDO_0005045hypertrophic cardiomyopathy2hypertrophic cardiomyopathy2344
MONDO_0004670lupus erythematosus2lupus erythematosus322
MONDO_0021107narcolepsy2narcolepsy420
MONDO_0005178osteoarthritis2osteoarthritis638
MONDO_0002656skin carcinoma2skin carcinoma63
MONDO_0000632uterine benign neoplasm2uterine cervix neoplasm30
MONDO_0005011Crohn disease1Crohn disease229
MONDO_0005451eating disorder1anxiety disorder12232
MONDO_0005361eosinophilic esophagitis1eosinophilic esophagitis29
MONDO_0005028esophageal adenocarcinoma1esophageal adenosquamous carcinoma30
MONDO_0006629osteoarthritis, hip1osteoarthritis638
MONDO_0005416osteoarthritis, knee1osteoarthritis638
MONDO_0005349otosclerosis1otosclerosis90
MONDO_0100081sleep disorder1anxiety disorder12232
The 24 multi-trait scores
PGSREST traits
PGS000781MONDO_0021042 glioma; MONDO_0021211 brain neoplasm
PGS001814MONDO_0000334 multinodular goiter; MONDO_0001658 nontoxic goiter
PGS001825EFO_0005935 overweight body mass index status; MONDO_0003916 overnutrition; MONDO_0011122 obesity disorder
PGS001841MONDO_0004981 atrial fibrillation; MONDO_0005310 atrial flutter
PGS001844HP_0004418 Thrombophlebitis; MONDO_0004625 phlebitis
PGS001851MONDO_0001409 esophagitis; MONDO_0007186 gastroesophageal reflux disease
PGS001859MONDO_0021398 polyp of rectum; MONDO_0060766 anal polyp
PGS001861HP_0001082 Cholecystitis; MONDO_0012672 cholelithiasis
PGS002022MONDO_0000334 multinodular goiter; MONDO_0001658 nontoxic goiter
PGS002033EFO_0005935 overweight body mass index status; MONDO_0003916 overnutrition; MONDO_0011122 obesity disorder
PGS002045EFO_0005752 eye inflammation; MONDO_0005328 eye disorder
PGS002050MONDO_0004981 atrial fibrillation; MONDO_0005310 atrial flutter
PGS002056HP_0004418 Thrombophlebitis; MONDO_0004625 phlebitis
PGS002063MONDO_0001409 esophagitis; MONDO_0007186 gastroesophageal reflux disease
PGS002070MONDO_0021398 polyp of rectum; MONDO_0060766 anal polyp
PGS002072HP_0001082 Cholecystitis; MONDO_0012672 cholelithiasis
PGS002324MONDO_0005015 diabetes mellitus; MONDO_0005151 endocrine system disorder
PGS002690MONDO_0005015 diabetes mellitus; MONDO_0005151 endocrine system disorder
PGS002768EFO_0009270 heel bone mineral density; MONDO_0005298 osteoporosis
PGS003387MONDO_0005028 esophageal adenocarcinoma; MONDO_0013662 Barrett esophagus
PGS005262EFO_0002751 benign; MONDO_0006869 nodular goiter
PGS005263EFO_0002751 benign; MONDO_0006869 nodular goiter
PGS005273EFO_0002751 benign; MONDO_0006869 nodular goiter; MONDO_0015075 thyroid gland carcinoma
PGS005274EFO_0002751 benign; MONDO_0006869 nodular goiter; MONDO_0015075 thyroid gland carcinoma

8. For whoever implements #430, #296 and #244

These are notes, not decisions.

  1. Read traits from REST, or key by pgs_id from this table. Don’t read them from mods_prs_score_manifest.trait_efo, which holds the header. The header maps 36 colorectal cancer scores to EFO_0005842, including all 9 v0 ones.

  2. Write down the chosen score for each disease. resolve_prs_vector keeps the first row with the most snps_used while iterating in computed_at DESC order. That means:

    • the exact ties (J5–J7) would be settled by whichever row E3 happened to write last;
    • the reader would still take the AFR CKD score (J8).

    Either stamp disease_cui only on each disease’s chosen row, or give the reader the per-disease choice.

  3. The anchor is a CURIE, not a UMLS CUI, even though the column is named disease_cui. Carry it onto axis v2 verbatim. Don’t re-derive it with sorted(curies)[0] (#430).

  4. Re-run Appendix A.1 on the E3 rows once E3 has finished. The callset is the same and the policy is the default, so every count and every pick should reproduce exactly. If they don’t, stop (playbook §5.1 step 3). Don’t refresh the REST cache between now and the release, or else re-run this table.

  5. After #432, check all 109 anchors against the new export’s entity vocabulary. Start with the 8 thin ones.

  6. maureen_focus_v1 is unlocked. Lock it, or pin its member list in the #432 manifest, so that the vocabulary can’t drift underneath the mapping.

  7. A trap: PrimeKG’s mods_kg_entity_source.external_id is PrimeKG’s node index, not a MONDO number. external_id = '20804' is the drug Ioxilan (CHEBI:135884), not basal cell carcinoma.

Appendix A. The code behind every count

Every script is read-only. The container scripts open with SET TRANSACTION READ ONLY and select ids and counts, never raw_score, z_score or percentile. Run them like this:

docker exec -i maple-worker-1 python - < smoke_v0_d7_repro.py > v0_repro.json   # §1-§4, §6, §7, Appendix B
docker exec -i maple-worker-1 python - < smoke_v0_catalog_checks.py             # #430's catalog numbers, the header-vs-REST split
docker exec -i maple-worker-1 python - < smoke_v0_anchor_checks.py              # §5, J10, the composition row
python smoke_v0_host_checks.py v0_repro.json                                    # the 916bdd2c export check; J4-J8 lookups
A.1 smoke_v0_d7_repro.py: the funnel, R8, D7, the picks and the table
"""smoke_v0 D7 disease mapping: the whole funnel in one read-only pass.
 
Run inside a worker container (it has DATABASE_URL and mounts the PGS cache):
    docker exec -i maple-worker-1 python - < smoke_v0_d7_repro.py > v0_repro.json
 
Read-only: SET TRANSACTION READ ONLY on the DB, and only reads of the cached PGS REST
metadata (/app/worker/data/pgs_catalog/PGS######/metadata.json). Selects ids and counts
only -- never raw_score, z_score or percentile.
"""
import json, os, sys, collections
from sqlalchemy import create_engine, text
 
PATIENT = 'PATIENT_UUID'
COMPOSITION = 'maureen_focus_v1'
CACHE = '/app/worker/data/pgs_catalog'
DRUG_PREDICATES = ('biolink:treats', 'biolink:contraindicated_for', 'biolink:has_off_label_use')
 
# ---- R1-R7 (#296), over the live quarantined rows ---------------------------------
RULES = """
WITH base AS (
  SELECT pgs_id, snps_used,
         metadata->'gate_c'      AS g,
         metadata->'coverage_v1' AS cov
  FROM mods_polygenic_score
  WHERE patient_id = :patient AND validity = 'quarantined'
), f AS (
  SELECT pgs_id, snps_used, g, cov,
    (g->>'weight_scale') = 'additive'                                             AS r1,
    coalesce((g->>'within_theoretical_bounds')::boolean, false)
      AND coalesce((g->>'reconciles')::boolean, false)                            AS r2,
    coalesce((g->'outcomes'->>'scored')::bigint, 0) >= 1                          AS r3,
    (cov->'structural_overlap'->>'matched_rows')::numeric
      / nullif((cov->'structural_overlap'->>'scoring_rows')::numeric, 0) >= 0.90  AS r4,
    coalesce((g->'outcomes'->>'ambiguous_palindromic')::bigint, 0) = 0            AS r5,
    coalesce((g->>'n_complement_only_match')::numeric
      / nullif(coalesce((g->'outcomes'->>'scored')::bigint,0)
             + coalesce((g->'outcomes'->>'ref_allele_mismatch')::bigint,0), 0), 0) <= 0.05 AS r6,
    (cov->'genotype_callability'->>'callable_loci')::numeric
      / nullif((cov->'genotype_callability'->>'matched_rows')::numeric, 0) >= 0.90 AS r7
  FROM base
)
"""
FUNNEL = RULES + """
SELECT count(*)                                                        AS quarantined,
       count(*) FILTER (WHERE r1)                                      AS r1,
       count(*) FILTER (WHERE r1 AND r2)                               AS r2,
       count(*) FILTER (WHERE r1 AND r2 AND r3)                        AS r3,
       count(*) FILTER (WHERE r1 AND r2 AND r3 AND r4)                 AS r4,
       count(*) FILTER (WHERE r1 AND r2 AND r3 AND r4 AND r5)          AS r5,
       count(*) FILTER (WHERE r1 AND r2 AND r3 AND r4 AND r5 AND r6)   AS r6,
       count(*) FILTER (WHERE r1 AND r2 AND r3 AND r4 AND r5 AND r6 AND coalesce(r7, false)) AS r7
FROM f
"""
SURVIVORS = RULES + """
SELECT f.pgs_id, f.snps_used, (f.g->'outcomes'->>'scored')::bigint AS scored,
       m.trait_efo AS header_trait            -- the scoring-file header, for the J-note only
FROM f LEFT JOIN mods_prs_score_manifest m ON m.pgs_id = f.pgs_id
WHERE r1 AND r2 AND r3 AND r4 AND r5 AND r6 AND coalesce(r7, false)
ORDER BY f.pgs_id
"""
# ---- every node that carries a MONDO number, and whether it is in the composition ---
ANCHORS = r"""
WITH nums AS (SELECT DISTINCT unnest(CAST(:nums AS int[])) AS n),
comp_ent AS (            -- ModsKGCompositionService.build_filter(), verbatim
  SELECT DISTINCT e.id
  FROM mods_kg_entity e
  JOIN mods_kg_entity_source es ON es.entity_id = e.id
  JOIN mods_kg_composition_member m
    ON m.source_id = es.source_id AND m.composition_id = :cid
  WHERE (m.include_domains IS NULL
         OR e.domain = ANY (SELECT jsonb_array_elements_text(m.include_domains)))
    AND (m.exclude_domains IS NULL
         OR NOT (e.domain = ANY (SELECT jsonb_array_elements_text(m.exclude_domains))))
),
cand AS (
  SELECT n.n, e.id, e.curie, e.name, e.domain,
         CASE WHEN e.curie = 'MONDO:' || n.n::text THEN 'unpadded' ELSE 'padded' END AS kind
  FROM nums n
  JOIN mods_kg_entity e
    ON e.curie IN ('MONDO:' || n.n::text, 'MONDO:' || lpad(n.n::text, 7, '0'))
  UNION ALL
  SELECT n.n, e.id, e.curie, e.name, e.domain, 'grouped' AS kind
  FROM nums n
  JOIN mods_kg_entity e
    ON e.curie LIKE 'MONDO\_grouped:%'
   AND n.n::text = ANY (string_to_array(split_part(e.curie, ':', 2), '_'))
),
ce AS (SELECT DISTINCT id FROM cand),
rel_side AS (            -- materialize() semantics: member source, both ends in the set
  SELECT ce.id AS eid, r.predicate, r.object_id AS other
  FROM ce JOIN mods_kg_relation r ON r.subject_id = ce.id
  JOIN mods_kg_composition_member m ON m.source_id = r.source_id AND m.composition_id = :cid
  WHERE r.object_id IN (SELECT id FROM comp_ent)
  UNION ALL
  SELECT ce.id AS eid, r.predicate, r.subject_id AS other
  FROM ce JOIN mods_kg_relation r ON r.object_id = ce.id
  JOIN mods_kg_composition_member m ON m.source_id = r.source_id AND m.composition_id = :cid
  WHERE r.subject_id IN (SELECT id FROM comp_ent)
),
rel AS (
  SELECT rs.eid, count(*) AS n_rel_ends,
         count(DISTINCT rs.other) FILTER (WHERE o.domain = 'drug') AS n_drug_any,
         count(DISTINCT rs.other) FILTER (WHERE rs.predicate = ANY(:dp) AND o.domain = 'drug') AS n_drug_nbrs,
         count(DISTINCT rs.other) FILTER (WHERE rs.predicate = 'biolink:treats' AND o.domain = 'drug') AS t,
         count(DISTINCT rs.other) FILTER (WHERE rs.predicate = 'biolink:contraindicated_for' AND o.domain = 'drug') AS c,
         count(DISTINCT rs.other) FILTER (WHERE rs.predicate = 'biolink:has_off_label_use' AND o.domain = 'drug') AS o
  FROM rel_side rs JOIN mods_kg_entity o ON o.id = rs.other
  GROUP BY rs.eid
)
SELECT c.n, c.id, c.curie, c.name, c.domain, c.kind,
       (c.id IN (SELECT id FROM comp_ent)) AS in_comp,
       (SELECT string_agg(DISTINCT es.source_id, ',' ORDER BY es.source_id)
          FROM mods_kg_entity_source es WHERE es.entity_id = c.id) AS sources,
       coalesce(rel.n_rel_ends, 0) AS n_rel_ends, coalesce(rel.n_drug_any, 0) AS n_drug_any,
       coalesce(rel.n_drug_nbrs, 0) AS n_drug_nbrs,
       coalesce(rel.t, 0) AS t, coalesce(rel.c, 0) AS c, coalesce(rel.o, 0) AS o
FROM cand c LEFT JOIN rel ON rel.eid = c.id
ORDER BY c.n, c.kind, c.curie
"""
 
 
def rest_traits(pgs_id):
    """The PGS REST trait list (the current mapping; the file header disagrees on 50 scores)."""
    with open(os.path.join(CACHE, pgs_id, 'metadata.json'), encoding='utf-8') as fh:
        return [(t['id'], t.get('label')) for t in (json.load(fh).get('trait_efo') or [])]
 
 
def main():
    eng = create_engine(os.environ['DATABASE_URL'])
    with eng.connect() as c:
        c.execute(text('SET TRANSACTION READ ONLY'))
        live = [list(map(str, r)) for r in c.execute(text("""
            SELECT run_id, metadata->'gate_a'->>'n_records', count(*) FROM mods_polygenic_score
            WHERE patient_id = :patient AND validity = 'quarantined' GROUP BY 1, 2"""), {'patient': PATIENT})]
        funnel = dict(c.execute(text(FUNNEL), {'patient': PATIENT}).mappings().one())
        surv = {r.pgs_id: r for r in c.execute(text(SURVIVORS), {'patient': PATIENT})}
        assert all(r.snps_used == r.scored for r in surv.values())
        traits = {p: rest_traits(p) for p in surv}
 
        def mondo(p):
            return [(int(i.split('_')[1]), l) for i, l in traits[p] if i.startswith('MONDO_')]
 
        nums = sorted({n for p in surv for n, _ in mondo(p)})
        by_n = collections.defaultdict(list)
        for a in c.execute(text(ANCHORS), {'nums': nums, 'cid': COMPOSITION,
                                           'dp': list(DRUG_PREDICATES)}).mappings():
            by_n[a['n']].append(dict(a))
 
    def resolve(n):
        """The v0 anchor rule for one MONDO number -> (route, node or None)."""
        inc = [a for a in by_n.get(n, []) if a['in_comp']]
        std = [a for a in inc if a['kind'] != 'grouped']
        if not inc:
            return 'none', None
        if not std:
            return 'grouped', None
        pk = [a for a in std if 'prime_kg' in (a['sources'] or '').split(',')]
        if len(pk) > 1:
            return 'ambiguous', None
        if pk:  # the node that carries PrimeKG's edges (J1: twins resolve here)
            node = pk[0]
            return ('exact' if node['kind'] == 'unpadded' else 'padded (PrimeKG node merged)'), node
        if len(std) > 1:
            return 'ambiguous', None
        return 'padded (no PrimeKG node)', std[0]
 
    def anchor_nodes(n):
        """Node ids a MONDO number anchors to: its resolved node, or its grouped node(s)."""
        route, node = resolve(n)
        if node is not None:
            return {node['id']}
        if route == 'grouped':
            return {a['id'] for a in by_n[n] if a['in_comp'] and a['kind'] == 'grouped'}
        return set()
 
    S = sorted(surv)
    # R8 as #296 counted it: a MONDO trait with any in-composition node, grouped included
    r8 = [p for p in S if any(anchor_nodes(n) for n, _ in mondo(p))]
    r8_nodes = set().union(*(anchor_nodes(n) for p in r8 for n, _ in mondo(p)))
    # D7: one trait only (any ontology), then no grouped anchor
    single = [p for p in r8 if len(traits[p]) == 1]
    single_nodes = set().union(*(anchor_nodes(n) for p in single for n, _ in mondo(p)))
    v0 = [p for p in single if resolve(mondo(p)[0][0])[1] is not None]
    by_anchor = collections.defaultdict(list)
    for p in v0:
        by_anchor[resolve(mondo(p)[0][0])[1]['curie']].append(p)
    rows = []
    for curie, ps in sorted(by_anchor.items()):
        n, label = mondo(ps[0])[0]
        route, node = resolve(n)
        ranked = sorted(ps, key=lambda p: (-surv[p].scored, int(p[3:])))   # most loci, then lowest accession
        others = [a for a in by_n[n] if a['in_comp'] and a['id'] != node['id']]
        rows.append({'label': label, 'mondo': 'MONDO_%07d' % n, 'anchor': curie, 'route': route,
                     'anchor_name': node['name'], 'sources': node['sources'],
                     'also_in_vocab': [(a['kind'], a['curie'][:40], a['name'], a['sources'],
                                        a['n_rel_ends'], a['n_drug_any']) for a in others],
                     'n_v0': len(ps), 'chosen': ranked[0], 'scored_loci': surv[ranked[0]].scored,
                     'runner_up': [ranked[1], surv[ranked[1]].scored] if len(ranked) > 1 else None,
                     'tied_at_top': [q for q in ranked if surv[q].scored == surv[ranked[0]].scored],
                     'rel_ends': node['n_rel_ends'], 'drug_any': node['n_drug_any'],
                     't': node['t'], 'c': node['c'], 'o': node['o'],
                     'n_header_not_rest': sum(1 for q in ps if (surv[q].header_trait or '')
                                              != ','.join(i for i, _ in traits[q])),
                     'v0_pgs_ids': sorted(ps)})
    no_mondo = collections.Counter('+'.join(sorted({i.split('_')[0] for i, _ in traits[p]}))
                                   for p in S if not mondo(p))
    grouped_out = []
    for p in single:
        n, label = mondo(p)[0]
        if resolve(n)[0] == 'grouped':
            g = [a for a in by_n[n] if a['in_comp'] and a['kind'] == 'grouped'][0]
            grouped_out.append([p, 'MONDO_%07d' % n, label, g['name'],
                                len(g['curie'].split(':', 1)[1].split('_')), g['n_drug_any']])
    out = {'live_rows': live,   # [run_id, gate_a.n_records, count] of the rows the funnel reads
           'funnel': funnel,
           'r8': [len(r8), len(r8_nodes)],
           'r8_removed_no_mondo': dict(no_mondo),
           'r8_removed_unanchored': [[p, ['MONDO_%07d' % n for n, _ in mondo(p)]]
                                     for p in S if mondo(p) and p not in r8],
           'd7_single_trait': [len(single), len(single_nodes)],
           'd7_multi_trait_removed': [[p, ['%s %s' % (i, l) for i, l in traits[p]]]
                                      for p in r8 if len(traits[p]) > 1],
           # J9: what the loose reading (drop only 2+ MONDO traits) would add back
           'd7_multi_loose_would_add': [[p, resolve(mondo(p)[0][0])[1]['curie'], surv[p].scored]
                                        for p in r8 if len(traits[p]) > 1 and len(mondo(p)) == 1
                                        and resolve(mondo(p)[0][0])[1] is not None],
           'd7_no_grouped': [len(v0), len(rows)],
           'd7_grouped_removed': grouped_out,
           'rows': rows}
    json.dump(out, sys.stdout)
 
 
main()
A.2 smoke_v0_catalog_checks.py: #430's numbers (1,620 / 245 / 42 / 173 / 50)
"""Cross-checks of #430's catalog numbers over the Gate B eligible set, read-only.
 
    docker exec -i maple-worker-1 python - < smoke_v0_catalog_checks.py
"""
import json, os, collections
from sqlalchemy import create_engine, text
 
CACHE = '/app/worker/data/pgs_catalog'
eng = create_engine(os.environ['DATABASE_URL'])
with eng.connect() as c:
    c.execute(text('SET TRANSACTION READ ONLY'))
    elig = c.execute(text("""
        SELECT pgs_id, trait_efo FROM mods_prs_score_manifest
        WHERE production_eligible ORDER BY pgs_id
    """)).fetchall()
 
 
def rest(p):
    with open(os.path.join(CACHE, p, 'metadata.json'), encoding='utf-8') as fh:
        return [t['id'] for t in (json.load(fh).get('trait_efo') or [])]
 
 
rest_ids = {p: rest(p) for p, _ in elig}
header_ids = {p: sorted(x.strip() for x in (te or '').replace('|', ',').split(',') if x.strip())
              for p, te in elig}
mondo = {p: [i for i in ids if i.startswith('MONDO_')] for p, ids in rest_ids.items()}
with_mondo = [p for p in mondo if mondo[p]]
per_id = collections.Counter(i for p in with_mondo for i in mondo[p])
disagree = [p for p in rest_ids if sorted(rest_ids[p]) != header_ids[p]]
print('eligible scores:', len(elig))
print('with >=1 MONDO trait (REST):', len(with_mondo), '| distinct MONDO ids:', len(per_id))
print('with >=2 MONDO traits:', sum(1 for p in with_mondo if len(mondo[p]) >= 2))
print('MONDO ids with >1 eligible score:', sum(1 for v in per_id.values() if v > 1))
print('header trait != REST trait:', len(disagree))
print(collections.Counter((','.join(header_ids[p]), ','.join(rest_ids[p])) for p in disagree).most_common())

It printed: 3,753 eligible; 1,620 with a MONDO trait, over 245 ids; 42 with two or more MONDO traits; 173 ids with more than one eligible score; 50 where the header disagrees with REST. Of those 50, 36 are EFO_0005842 → MONDO_0005575.

A.3 smoke_v0_anchor_checks.py: the CRC anchor, the BCC gap, the bladder drug sets and the Ioxilan trap
"""The CRC index anchor and the basal cell carcinoma gap, read-only.
 
    docker exec -i maple-worker-1 python - < smoke_v0_anchor_checks.py
"""
import os
from sqlalchemy import create_engine, text
 
eng = create_engine(os.environ['DATABASE_URL'])
Q = lambda c, sql, **kw: c.execute(text(sql), kw).fetchall()
with eng.connect() as c:
    c.execute(text('SET TRANSACTION READ ONLY'))
    print('composition:', Q(c, """SELECT id, version, is_locked, updated_at FROM mods_kg_composition
                                  WHERE id = 'maureen_focus_v1'"""))
    print('members:', Q(c, """SELECT source_id, exclude_domains FROM mods_kg_composition_member
                              WHERE composition_id = 'maureen_focus_v1' ORDER BY 1"""))
    for curie in ('MONDO:0005575', 'MONDO:5575', 'MONDO:24331', 'MONDO:0024331',
                  'MONDO:0020804', 'MONDO:20804', 'MONDO:0005341'):
        ent = Q(c, "SELECT id, name, domain FROM mods_kg_entity WHERE curie = :cu", cu=curie)
        if not ent:
            print(curie, '-> no entity')
            continue
        eid = ent[0][0]
        src = Q(c, """SELECT source_id, external_id FROM mods_kg_entity_source
                      WHERE entity_id = :i ORDER BY 1""", i=eid)
        drug = Q(c, """SELECT r.source_id, r.predicate,
                              CASE WHEN r.subject_id = :i THEN 'out' ELSE 'in' END, count(*)
                       FROM mods_kg_relation r
                       WHERE (r.subject_id = :i OR r.object_id = :i)
                         AND r.predicate IN ('biolink:treats', 'biolink:contraindicated_for',
                                             'biolink:has_off_label_use')
                       GROUP BY 1, 2, 3 ORDER BY 1, 2, 3""", i=eid)
        print(curie, tuple(ent[0]), '| sources:', src, '| drug relations:', drug)
    print('CRC drug partners:', Q(c, """
        SELECT DISTINCT r.predicate, d.curie, d.name
        FROM mods_kg_relation r
        JOIN mods_kg_entity e ON e.curie = 'MONDO:0005575'
        JOIN mods_kg_entity d ON d.id = CASE WHEN r.subject_id = e.id THEN r.object_id ELSE r.subject_id END
        WHERE (r.subject_id = e.id OR r.object_id = e.id)
          AND r.predicate IN ('biolink:treats', 'biolink:contraindicated_for', 'biolink:has_off_label_use')
        ORDER BY 1, 2"""))
    print('BCC 0005341 <-> 0020804:', Q(c, """
        SELECT s.curie, r.predicate, o.curie, r.source_id
        FROM mods_kg_relation r
        JOIN mods_kg_entity s ON s.id = r.subject_id JOIN mods_kg_entity o ON o.id = r.object_id
        WHERE s.curie IN ('MONDO:0005341', 'MONDO:0020804') AND o.curie IN ('MONDO:0005341', 'MONDO:0020804')"""))
    print('grouped nodes containing 20804:', Q(c, r"""
        SELECT id, curie FROM mods_kg_entity
        WHERE curie LIKE 'MONDO\_grouped:%'
          AND '20804' = ANY (string_to_array(split_part(curie, ':', 2), '_'))"""))
    drugsets = {}
    for cu in ('MONDO:1187', 'MONDO:4986'):   # urinary bladder cancer / carcinoma
        drugsets[cu] = {r[0] for r in Q(c, """
            SELECT DISTINCT d.curie FROM mods_kg_entity e
            JOIN mods_kg_relation r ON (r.subject_id = e.id OR r.object_id = e.id)
            JOIN mods_kg_entity d ON d.id = CASE WHEN r.subject_id = e.id THEN r.object_id ELSE r.subject_id END
            WHERE e.curie = :cu AND d.domain = 'drug' AND r.source_id = 'prime_kg'""", cu=cu)}
    print('bladder cancer vs carcinoma drug sets:', {k: len(s) for k, s in drugsets.items()},
          'identical:', drugsets['MONDO:1187'] == drugsets['MONDO:4986'])
    print("trap: prime_kg external_id '20804' is:", Q(c, """
        SELECT e.curie, e.name FROM mods_kg_entity_source es JOIN mods_kg_entity e ON e.id = es.entity_id
        WHERE es.source_id = 'prime_kg' AND es.external_id = '20804'"""))
A.4 smoke_v0_host_checks.py: the export on disk, and the REST records behind J4–J8
"""Host-side checks on the reproduction's output (v0_repro.json), read-only.
 
1. Every anchor is in the de-facto foundation's export (916bdd2c, on disk), and what the two
   colorectal nodes carry there.
2. The REST records behind judgement calls J3-J8, and the REST cache dates.
 
    python smoke_v0_host_checks.py v0_repro.json
"""
import datetime, json, os, sys, collections
 
EXPORT = r'C:\Maple\runs\2026-05-18_maureen_focus_v1\data'
CACHE = r'C:\Maple\worker\data\pgs_catalog'
v0 = json.load(open(sys.argv[1], encoding='utf-8'))
rows = {r['anchor']: r for r in v0['rows']}
anchors = set(rows)
 
ends = collections.Counter()
preds = collections.defaultdict(collections.Counter)
for split in ('train.txt', 'valid.txt', 'test.txt'):
    with open(os.path.join(EXPORT, split), encoding='utf-8') as fh:
        for line in fh:
            h, r, t = line.rstrip('\r\n').split('\t')
            for x in (h, t):
                if x in anchors:
                    ends[x] += 1
                    if x in ('MONDO:0005575', 'MONDO:24331'):
                        preds[x][r] += 1
print('anchors checked:', len(anchors), '| absent from the 916bdd2c export:',
      sorted(a for a in anchors if not ends[a]))
for x in ('MONDO:0005575', 'MONDO:24331'):
    print(x, 'appears', ends[x], 'times:', dict(preds[x]))
 
 
def meta(p):
    with open(os.path.join(CACHE, p, 'metadata.json'), encoding='utf-8') as fh:
        d = json.load(fh)
    pub = d.get('publication') or {}
    return {'name': d.get('name'), 'trait_reported': d.get('trait_reported'),
            'method': d.get('method_name'), 'params': d.get('method_params'),
            'pub': f"{pub.get('firstauthor')} {pub.get('date_publication')}: {pub.get('title')}",
            'ancestry': d.get('ancestry_distribution')}
 
 
for anchor in ('MONDO:0005575', 'MONDO:5350', 'MONDO:5009', 'MONDO:5068', 'MONDO:5300', 'MONDO:5279'):
    r = rows[anchor]
    print(f"\n== {r['label']} ({anchor}), {r['n_v0']} v0 scores; chosen {r['chosen']}; tied at top: {r['tied_at_top']}")
    extra = [r['runner_up'][0]] if r['runner_up'] and r['runner_up'][0] not in r['tied_at_top'] else []
    for p in r['tied_at_top'] + extra:
        print('  ', p, json.dumps(meta(p)))
 
print('\n== colorectal carcinoma (MONDO:24331): trait_reported of its v0 scores')
print(collections.Counter(meta(p)['trait_reported'] for p in rows['MONDO:24331']['v0_pgs_ids']))
 
print('\n== chosen scores whose REST GWAS ancestry is under 50% EUR, or not recorded as EUR at all')
for r in v0['rows']:
    g = ((meta(r['chosen'])['ancestry'] or {}).get('gwas') or {}).get('dist') or {}
    if g and float(g.get('EUR', 0)) < 50:
        print('  ', r['label'], r['chosen'], g, 'n_v0 =', r['n_v0'])
 
stamps = [os.path.getmtime(os.path.join(CACHE, p, 'metadata.json'))
          for r in v0['rows'] for p in r['v0_pgs_ids']]
day = lambda t: datetime.datetime.fromtimestamp(t).strftime('%Y-%m-%d')
print('\nREST metadata.json cached', day(min(stamps)), 'to', day(max(stamps)), 'for', len(stamps), 'v0 scores')

Appendix B. The v0 scores for each disease

This lists every v0 score for each disease. §6 has the rule’s pick, and §4 has the proposed overrides (J6–J8). How the scores that are not picked get stamped at release is §8, item 2.

594 scores over 109 diseases
  • abdominal aortic aneurysm (MONDO:5350, 4): PGS001784, PGS002054, PGS003972, PGS003973
  • acquired polycythemia vera (MONDO:9891, 1): PGS001810
  • alcoholic liver cirrhosis (MONDO:6644, 1): PGS004913
  • appendicitis (MONDO:5649, 1): PGS001785
  • asthma (MONDO:4979, 34): PGS001782, PGS001787, PGS001849, PGS002061, PGS002311, PGS002358, PGS002677, PGS002727, PGS002754, PGS004275, PGS004276, PGS004277, PGS004278, PGS004279, PGS005121, PGS005122, PGS005123, PGS005124, PGS005125, PGS005126, PGS005127, PGS005128, PGS005129, PGS005130, PGS005131, PGS005132, PGS005133, PGS005134, PGS005135, PGS005136, PGS005137, PGS005138, PGS005139, PGS005140
  • atopic eczema (MONDO:4980, 1): PGS002755
  • atrial fibrillation (MONDO:4981, 31): PGS001356, PGS002756, PGS004290, PGS004291, PGS004292, PGS004293, PGS004294, PGS004612, PGS004613, PGS004929, PGS005060, PGS005061, PGS005062, PGS005063, PGS005064, PGS005065, PGS005066, PGS005067, PGS005068, PGS005069, PGS005070, PGS005071, PGS005072, PGS005168, PGS005287, PGS005288, PGS005289, PGS005290, PGS005291, PGS005292, PGS005313
  • attention deficit-hyperactivity disorder (MONDO:7743, 1): PGS003753
  • autoimmune disease (MONDO:7179, 3): PGS002312, PGS002359, PGS002678
  • benign colon neoplasm (MONDO:2278, 2): PGS001811, PGS002019
  • benign prostatic hyperplasia (MONDO:10811, 2): PGS001865, PGS002076
  • biliary tract disorder (MONDO:4868, 2): PGS001862, PGS002073
  • bilirubin metabolism disease (MONDO:24431, 2): PGS001824, PGS002032
  • bone fracture (MONDO:5315, 2): PGS001921, PGS002137
  • brain cancer (MONDO:1657, 1): PGS001808
  • breast carcinoma (MONDO:0004989, 26): PGS001778, PGS001804, PGS002015, PGS002242, PGS003397, PGS003758, PGS003983, PGS003999, PGS004025, PGS004040, PGS004053, PGS004069, PGS004083, PGS004107, PGS004123, PGS004137, PGS004153, PGS004295, PGS004296, PGS004297, PGS004298, PGS004579, PGS005345, PGS005346, PGS005347, PGS005348
  • cardiovascular disorder (MONDO:4995, 4): PGS002316, PGS002361, PGS002682, PGS005253
  • celiac disease (MONDO:5130, 3): PGS001856, PGS002067, PGS002107
  • cellulitis (MONDO:5230, 2): PGS001869, PGS002081
  • cerebrovascular disorder (MONDO:11057, 1): PGS002053
  • cervical carcinoma (MONDO:5131, 3): PGS000073, PGS003389, PGS003428
  • cholelithiasis (MONDO:12672, 1): PGS012582
  • chronic kidney disease (MONDO:5300, 18): PGS002237, PGS002757, PGS003988, PGS004004, PGS004016, PGS004030, PGS004045, PGS004058, PGS004074, PGS004088, PGS004101, PGS004112, PGS004128, PGS004142, PGS004158, PGS004889, PGS005090, PGS005113
  • chronic obstructive pulmonary disease (MONDO:5002, 4): PGS001783, PGS001788, PGS001850, PGS002062
  • cirrhosis of liver (MONDO:5155, 1): PGS004621
  • colorectal cancer (MONDO:0005575, 9): PGS001802, PGS002013, PGS002758, PGS003395, PGS003431, PGS003432, PGS003433, PGS003434, PGS003435
  • colorectal carcinoma (MONDO:24331, 8): PGS003979, PGS004300, PGS004302, PGS004303, PGS004580, PGS004912, PGS005162, PGS005275
  • congenital vitamin K-dependent coagulation factors deficiency (MONDO:15722, 2): PGS001826, PGS002034
  • congestive heart failure (MONDO:5009, 15): PGS001842, PGS002051, PGS005073, PGS005074, PGS005075, PGS005076, PGS005077, PGS005078, PGS005079, PGS005080, PGS005081, PGS005082, PGS005083, PGS005084, PGS005085
  • cor pulmonale (MONDO:4596, 2): PGS001840, PGS002049
  • coronary artery disorder (MONDO:5010, 33): PGS001355, PGS001780, PGS002244, PGS003355, PGS003356, PGS003446, PGS003725, PGS003726, PGS003727, PGS004237, PGS004305, PGS004306, PGS004307, PGS004308, PGS004309, PGS004696, PGS004697, PGS004698, PGS004888, PGS004941, PGS005091, PGS005092, PGS005093, PGS005094, PGS005112, PGS005236, PGS005293, PGS005294, PGS005295, PGS005296, PGS005297, PGS005298, PGS012558
  • coronary atherosclerosis (MONDO:21661, 2): PGS001839, PGS002048
  • COVID-19 (MONDO:100096, 1): PGS004938
  • cutaneous melanoma (MONDO:0005012, 2): PGS000339, PGS003382
  • dementia (MONDO:1627, 7): PGS001827, PGS002035, PGS004281, PGS004282, PGS004283, PGS004284, PGS005170
  • diabetes mellitus (MONDO:5015, 2): PGS002321, PGS002687
  • diabetic retinopathy (MONDO:5266, 2): PGS001819, PGS002027
  • digestive system disorder (MONDO:4335, 2): PGS001858, PGS002069
  • disorder of appendix (MONDO:56798, 2): PGS001853, PGS002064
  • disorder of pilosebaceous unit (MONDO:2917, 1): PGS002086
  • drug allergy (MONDO:775, 2): PGS001885, PGS002097
  • duodenitis (MONDO:4627, 1): PGS001852
  • endometriosis (MONDO:5133, 2): PGS001866, PGS002077
  • epilepsy (MONDO:5027, 1): PGS002760
  • ganglion or cyst of synovium/tendon/bursa (MONDO:4874, 1): PGS001879
  • glioblastoma (MONDO:18177, 1): PGS003384
  • gout (MONDO:5393, 21): PGS001789, PGS001822, PGS002030, PGS002762, PGS003990, PGS004006, PGS004018, PGS004032, PGS004047, PGS004060, PGS004076, PGS004090, PGS004114, PGS004130, PGS004144, PGS004160, PGS004265, PGS004266, PGS004267, PGS004268, PGS004269
  • Graves disease (MONDO:5364, 2): PGS005265, PGS005266
  • Hashimoto thyroiditis (MONDO:7699, 2): PGS005271, PGS005272
  • head and neck squamous cell carcinoma (MONDO:0010150, 2): PGS003390, PGS012586
  • headache disorder (MONDO:21146, 2): PGS001928, PGS002145
  • hearing disorder (MONDO:21945, 2): PGS001891, PGS002104
  • heart failure (MONDO:5252, 11): PGS001790, PGS005097, PGS005299, PGS005300, PGS005301, PGS005302, PGS005303, PGS005304, PGS005305, PGS005306, PGS012552
  • hypertensive disorder (MONDO:5044, 18): PGS001838, PGS002047, PGS002335, PGS002701, PGS002765, PGS002998, PGS003003, PGS003008, PGS003013, PGS003018, PGS003020, PGS003021, PGS003023, PGS003025, PGS003026, PGS003028, PGS004234, PGS004236
  • hypothyroidism (MONDO:0005420, 10): PGS000759, PGS000761, PGS001816, PGS002024, PGS002336, PGS002702, PGS002766, PGS005218, PGS005268, PGS005269
  • insomnia (MONDO:13600, 4): PGS001932, PGS002149, PGS003323, PGS003328
  • intracerebral hemorrhage (MONDO:13792, 1): PGS004943
  • iron metabolism disease (MONDO:2279, 2): PGS001823, PGS002031
  • liver disorder (MONDO:5154, 2): PGS001860, PGS002071
  • lung carcinoma (MONDO:5138, 1): PGS004691
  • melanoma (MONDO:0005105, 1): PGS003430
  • multiple sclerosis (MONDO:5301, 3): PGS001831, PGS002038, PGS004699
  • myocardial infarction (MONDO:5068, 13): PGS005034, PGS005035, PGS005036, PGS005037, PGS005038, PGS005039, PGS005040, PGS005041, PGS005042, PGS005043, PGS005044, PGS005045, PGS005046
  • nephrotic syndrome (MONDO:5377, 1): PGS012539
  • neuroblastoma (MONDO:0005072, 1): PGS002261
  • nicotine dependence (MONDO:8575, 2): PGS001830, PGS002037
  • obesity disorder (MONDO:11122, 1): PGS005235
  • obstructive sleep apnea syndrome (MONDO:7147, 5): PGS003479, PGS005219, PGS005220, PGS012528, PGS012529
  • occlusion precerebral artery (MONDO:3718, 1): PGS002052
  • open-angle glaucoma (MONDO:5338, 1): PGS001797
  • oral cavity cancer (MONDO:5515, 1): PGS012587
  • oropharynx cancer (MONDO:0004608, 1): PGS012588
  • osteoporosis (MONDO:5298, 2): PGS001883, PGS002095
  • ovarian carcinoma (MONDO:0005140, 1): PGS005166
  • pelvic organ prolapse (MONDO:82, 1): PGS002288
  • peripheral arterial disease (MONDO:5386, 1): PGS005217
  • peripheral nervous system disorder (MONDO:3620, 2): PGS001832, PGS002039
  • peripheral vascular disease (MONDO:5294, 2): PGS001843, PGS002055
  • polymyalgia rheumatica (MONDO:19735, 2): PGS001878, PGS002091
  • preeclampsia (MONDO:5081, 3): PGS003586, PGS003587, PGS004593
  • prolapse of female genital organ (MONDO:1592, 2): PGS001867, PGS002078
  • prostate carcinoma (MONDO:0005159, 21): PGS001805, PGS002016, PGS002240, PGS002241, PGS003985, PGS004001, PGS004027, PGS004042, PGS004055, PGS004071, PGS004085, PGS004099, PGS004109, PGS004125, PGS004139, PGS004155, PGS004581, PGS005237, PGS005238, PGS005239, PGS005240
  • psoriasis (MONDO:5083, 5): PGS001871, PGS002083, PGS002344, PGS002710, PGS004316
  • pulmonary embolism (MONDO:5279, 1): PGS003861
  • renal cell carcinoma (MONDO:5086, 1): PGS004245
  • respiratory system disorder (MONDO:5087, 2): PGS002347, PGS002713
  • retinal detachment (MONDO:8375, 2): PGS001833, PGS002040
  • Rh isoimmunization (MONDO:6953, 2): PGS001868, PGS002080
  • rheumatoid arthritis (MONDO:8383, 17): PGS001875, PGS002088, PGS002769, PGS003994, PGS004010, PGS004021, PGS004049, PGS004064, PGS004079, PGS004094, PGS004103, PGS004118, PGS004133, PGS004148, PGS004163, PGS004257, PGS004326
  • seborrheic keratosis (MONDO:8420, 1): PGS002085
  • skin disorder (MONDO:5093, 2): PGS002320, PGS002686
  • skin sarcoidosis (MONDO:6611, 2): PGS001872, PGS002084
  • sleep apnea syndrome (MONDO:5296, 3): PGS003208, PGS003213, PGS003218
  • stroke disorder (MONDO:5098, 17): PGS001793, PGS001798, PGS002770, PGS003984, PGS004000, PGS004015, PGS004026, PGS004041, PGS004054, PGS004070, PGS004084, PGS004098, PGS004108, PGS004124, PGS004138, PGS004154, PGS012551
  • substance-related disorder (MONDO:2494, 2): PGS003849, PGS005215
  • systemic lupus erythematosus (MONDO:7915, 1): PGS000803
  • thyroid gland carcinoma (MONDO:0015075, 6): PGS001794, PGS001799, PGS001809, PGS002018, PGS005259, PGS005260
  • thyroid gland disorder (MONDO:3240, 2): PGS002351, PGS002717
  • thyrotoxicosis (MONDO:10138, 2): PGS001815, PGS002023
  • type 2 diabetes mellitus (MONDO:5148, 111): PGS001357, PGS001781, PGS001818, PGS002026, PGS002243, PGS002308, PGS002354, PGS002379, PGS002720, PGS002771, PGS003093, PGS003098, PGS003103, PGS003108, PGS003113, PGS003118, PGS003123, PGS003443, PGS003444, PGS003445, PGS003867, PGS003982, PGS003998, PGS004014, PGS004024, PGS004039, PGS004052, PGS004068, PGS004082, PGS004106, PGS004122, PGS004136, PGS004152, PGS004310, PGS004311, PGS004312, PGS004313, PGS004314, PGS004618, PGS004619, PGS004620, PGS004859, PGS004887, PGS005021, PGS005022, PGS005023, PGS005024, PGS005025, PGS005026, PGS005027, PGS005028, PGS005029, PGS005030, PGS005031, PGS005032, PGS005033, PGS005108, PGS005110, PGS005111, PGS005115, PGS005116, PGS005117, PGS005118, PGS005119, PGS005242, PGS005243, PGS005244, PGS005245, PGS005246, PGS005247, PGS005314, PGS005324, PGS005325, PGS005326, PGS005327, PGS005328, PGS005329, PGS005330, PGS005333, PGS005334, PGS005335, PGS005336, PGS005341, PGS005342, PGS005343, PGS005344, PGS005353, PGS005354, PGS005355, PGS005356, PGS005357, PGS005358, PGS005359, PGS005360, PGS005361, PGS005362, PGS005363, PGS005364, PGS005365, PGS005366, PGS005367, PGS005368, PGS005369, PGS005370, PGS005371, PGS005372, PGS005373, PGS005374, PGS005375, PGS005376, PGS005377
  • ulcerative colitis (MONDO:5101, 3): PGS001855, PGS002066, PGS004253
  • urinary bladder cancer (MONDO:1187, 1): PGS003736
  • urinary bladder carcinoma (MONDO:4986, 2): PGS001807, PGS002017
  • urolithiasis (MONDO:24647, 2): PGS001864, PGS002075
  • uterine carcinoma (MONDO:5213, 1): PGS001795
  • uterine corpus cancer (MONDO:6003, 1): PGS003740
  • vascular disorder (MONDO:5385, 2): PGS001847, PGS002059
  • venous thromboembolism (MONDO:5399, 5): PGS001796, PGS002772, PGS003332, PGS012519, PGS012527
  • vitiligo (MONDO:8661, 1): PGS000760