feat(data): build a case from a real published patient
`make published-case` reads a GA4GH phenopacket from Monarch's Phenopacket Store and takes two things from it verbatim: the HPO terms the authors reported and the variant they called causal. The default is the TGFBR2 proband from Loeys et al., Nat Genet 2005 (doi:10.1038/ng1511), the paper that first defined Loeys-Dietz syndrome -- 30 reported terms and NM_003242.6:c.1069G>T p.(Gly357Trp). The rest of that patient's genome is not public, so background variants come from GIAB HG002 around the locus. They are drawn from coding exons where possible, via Ensembl's REST API: of ~4,000 HG002 variants in the window only 9 are coding, so a random sample is entirely intronic, the consequence filter discards all of it, and the causal variant is left as the only candidate -- a funnel that proves nothing. The real run ranks TGFBR2 first at 0.897 against an OSBPL10 missense at 0.547. Both are rare missense variants the model scores identically (0.887); only the phenotype separates them, which is the argument for phenotype-driven triage in one table. Documented with three caveats rather than left implicit: the phenotype match is partly circular because HPO's gene annotations are themselves curated from published cases; rarity contributes nothing without the VEP cache (--af_gnomade is rejected with --database, and plain --af returns nothing even for rs429358 at ~15% global frequency); and one healthy genome is not a diagnostic exome.
This commit is contained in:
@@ -29,6 +29,7 @@ benchmarks variant callers against, so it is both realistic and unambiguously sh
|
||||
| **1000 Genomes** 30x | optional cohort/trio data | EBI FTP, `s3://1000genomes` | fully open, no access restriction | Byrska-Bishop et al., *Cell* 185(18):3426–3440.e19, 2022. [10.1016/j.cell.2022.08.004](https://doi.org/10.1016/j.cell.2022.08.004) |
|
||||
| **MANE Select** | one transcript per gene, if transcript choice ever matters | Ensembl/RefSeq | open | Morales et al., *Nature* 604:310–315, 2022. [10.1038/s41586-022-04558-8](https://doi.org/10.1038/s41586-022-04558-8) |
|
||||
| **Human Phenotype Ontology** gene-to-phenotype | what the phenotype half of the ranking matches against (`make hpo`) | `purl.obolibrary.org/obo/hp/hpoa/genes_to_phenotype.txt` | free to use with attribution | Gargano et al., *Nucleic Acids Res* 52(D1):D1333–D1346, 2024. [10.1093/nar/gkad1005](https://doi.org/10.1093/nar/gkad1005) |
|
||||
| **Phenopacket Store** | published patients: the reported phenotype and causal variant of a real case (`make published-case`) | `github.com/monarch-initiative/phenopacket-store` | BSD-3-Clause | Danis et al., *HGG Adv* 6(1):100371, 2025. [10.1016/j.xhgg.2024.100371](https://doi.org/10.1016/j.xhgg.2024.100371) |
|
||||
|
||||
## Tools and scores
|
||||
|
||||
@@ -59,6 +60,66 @@ instead of the 25 GB cache. It returns no gnomAD frequencies, so every variant l
|
||||
gnomAD and the rarity term stops discriminating. Fine for showing the mechanics; use the cache for
|
||||
anything you would quote.
|
||||
|
||||
## A published case
|
||||
|
||||
`make published-case` builds a case around a patient who actually exists in the literature. It
|
||||
reads a GA4GH phenopacket from Monarch's Phenopacket Store, which curates published case reports
|
||||
into machine-readable records and keeps the PMID on each one, and takes two things from it
|
||||
verbatim: the phenotype terms the authors reported, and the variant they called causal.
|
||||
|
||||
The default is **Loeys-Dietz syndrome**, from the paper that first defined it — Loeys et al.,
|
||||
*Nat Genet* 37:275–281, 2005, [10.1038/ng1511](https://doi.org/10.1038/ng1511) (PMID 15731757).
|
||||
Family 4, individual II-1: 30 reported HPO terms, from hypertelorism and a bifid uvula to arterial
|
||||
tortuosity and an aortic root aneurysm, and a heterozygous *TGFBR2* missense variant,
|
||||
`NM_003242.6:c.1069G>T` `p.(Gly357Trp)`, at GRCh38 chr3:30672252 G>T.
|
||||
|
||||
The phenotype and the answer are real. The rest of that patient's genome is not public, and
|
||||
triage means nothing if the causal variant is the only variant in the file, so background variants
|
||||
come from GIAB HG002 in a 3 Mb window around the locus. The file is therefore a published
|
||||
diagnosis inside a public background genome — not anyone's exome. That is the standard
|
||||
construction for benchmarking phenotype-driven triage, and it is the reason this case can be
|
||||
redistributed at all.
|
||||
|
||||
The background is taken from coding exons wherever possible, using coding-exon coordinates from
|
||||
Ensembl's public REST API. This matters more than it sounds: of the ~4,000 HG002 variants in that
|
||||
window only 9 fall in coding exons, so a random sample is entirely intronic, the consequence
|
||||
filter throws all of it away, and the causal variant ends up the only candidate left — a funnel
|
||||
that proves nothing. Real coding variants give the ranking something it has to rank *against*.
|
||||
|
||||
What that run looks like: 21 variants in, 21 "rare" (see the caveat below), **2** surviving the
|
||||
consequence filter, **1** matching the phenotype.
|
||||
|
||||
| | score | phenotype | rarity | consequence | model |
|
||||
|---|---|---|---|---|---|
|
||||
| *TGFBR2* 3:30672252 missense | **0.897** | 1.00 (30/30 terms) | 1.00 | 0.60 | 0.887 |
|
||||
| *OSBPL10* 3:31748090 missense | 0.547 | 0.00 | 1.00 | 0.60 | 0.887 |
|
||||
|
||||
This is the whole argument for phenotype-driven triage in one table. Both are rare missense
|
||||
variants; the model scores them **identically**, to three decimal places, because nothing about
|
||||
the variants themselves distinguishes them. What separates the published diagnosis from an
|
||||
incidental variant in a lipid-transport gene is the patient's phenotype, and nothing else.
|
||||
ClinVar's "pathogenic" on the first row is shown afterwards as independent confirmation — it is
|
||||
not an input to the rank.
|
||||
|
||||
Three things to say out loud when showing it:
|
||||
|
||||
- **The phenotype match is partly circular.** HPO's gene-to-phenotype annotations are themselves
|
||||
curated from published cases, quite possibly including this one. A 30/30 term match against
|
||||
*TGFBR2* is evidence the plumbing works, not evidence the ranking would find a novel gene.
|
||||
- **Rarity is not doing any work without a VEP cache.** See the caveat above: in database mode
|
||||
every variant looks absent from gnomAD, so the funnel's rarity step passes everything and every
|
||||
variant scores a full 1.0 on rarity. `--af_gnomade` is rejected outright with `--database`, and
|
||||
plain `--af` returns nothing even for common variants — checked against rs429358, roughly 15%
|
||||
globally. Frequencies need the cache; there is no shortcut.
|
||||
- **The background is one healthy genome, not a diagnostic exome.** A real case would have
|
||||
thousands of rare coding variants to discard, not a handful.
|
||||
|
||||
Other cases work the same way — any phenopacket with GRCh38 coordinates will do:
|
||||
|
||||
```
|
||||
scripts/make-published-case.py --phenopacket <raw phenopacket-store JSON URL>
|
||||
```
|
||||
|
||||
## The model, and what its numbers mean
|
||||
|
||||
`make training-set` builds a training table straight from ClinVar rather than running VEP over
|
||||
|
||||
Reference in New Issue
Block a user