Skip to content

feat: add NCATS GARD rare-disease registry as a biolink:Disease source - #980

Open
SkyeAv wants to merge 4 commits into
mainfrom
feat/gard-disease
Open

feat: add NCATS GARD rare-disease registry as a biolink:Disease source#980
SkyeAv wants to merge 4 commits into
mainfrom
feat/gard-disease

Conversation

@SkyeAv

@SkyeAv SkyeAv commented Jul 28, 2026

Copy link
Copy Markdown
Member

Adds GARD — the NCATS Genetic and Rare Diseases rare-disease registry — as a biolink:Disease source in the existing disease (diseasephenotype) pipeline. GARD is a flat CSV of rare-disease terms (CURIE, preferred label, pipe-separated synonyms); it carries no cross-references, so it contributes identifiers and labels/synonyms only — no concord — and every term is typed biolink:Disease.

Ingest

  • New handler: src/datahandlers/gard.py downloads the Salesforce ContentVersion CSV (pull_gard) and parses it to per-prefix labels/synonyms files (pull_gard_labels_and_synonyms). The DisplayName is emitted as a label and as an exact synonym; each pipe-separated Synonyms value becomes its own OIO:hasExactSynonym row (matching the Orphanet/DOID convention). The URL column is read for reference only and is not ingested — the CURIE resolves via the Biolink prefix map.
  • Download: get_gard / get_gard_labels_and_synonyms rules in src/snakefiles/datacollect.snakefile; disease_gard_ids (an awk transform of the labels file, mirroring DOID/Orphanet) in src/snakefiles/diseasephenotype.snakefile. The download URL is configured as gard_download_url in config.yaml (a query-string Salesforce link with no stable filename, fetched directly rather than via pull_via_urllib); pin or repoint it there for a reproducible build.
  • Wiring: GARD added to disease_ids and disease_labelsandsynonyms; GARD joins the existing Disease.txt (no new compendium file), so the util.py aggregators and report_tables.py need no change.

Design (load-bearing)

  • extra_prefixes=[GARD]: GARD is not in the Biolink Model's disease id_prefixes (verified against the pinned biolink_version), so write_compendium would silently drop every GARD CURIE; the disease build passes extra_prefixes=[GARD] at the write_compendium call site in build_compendium (the documented escape hatch). Filing GARD with the Biolink team is the long-term fix — the same situation GTDB is in (PR feat: add GTDB as a biolink:OrganismTaxon source in the taxon pipeline #978). Two network-marked tests in tests/datahandlers/test_gard.py lock this in: one asserts GARD is absent from disease id_prefixes (and flips to prompt dropping the line once registered), the other asserts NodeFactory.create_node(..., extra_prefixes=[GARD]) for biolink:Disease does not raise (mirroring test_all_override_target_types_are_writable). The committed source-impact report's section-4 samples are flagged "NOT emitted — prefix not registered", which is exactly this precondition made visible.
  • No concords: GARD provides no xrefs to other disease vocabularies, so there is no GARD concord file. A GARD term no other source already maps joins the build as a single-identifier clique (a new clique); the impact report confirms this (see below).
  • Parse safety net: the handler logs a parsed/skipped/empty-name summary at the end of the parse so a future NCATS format change (e.g. a header or ID-column rename) can't silently zero the output. A scan of the published CSV found no DisplayName/Synonyms value with an embedded tab or newline and no row with an empty DisplayName.

Impact report

Generated (synthetic mode) and committed at docs/sources/GARD/impact-report.md (+ impact-report/ detail CSVs). The baseline disease intermediates were assembled from the 2025dec11 published build snapshot; that snapshot's concords predate the MP-in-disease addition, so MP's concord was rebuilt locally from UberGraph for the baseline (the report's Babel commit header pins the exact tree). GARD's own intermediates were built locally. Results: 16,214 identifiers / 16,214 new single-identifier cliques (a 6.72% increase, 241,269 → 257,483 total cliques), 0 merges, 0 modified cliques, 0 xrefs — the addition is purely additive; GARD disturbs no existing clique.

Docs

  • docs/sources/GARD/README.md (new) + an entry in the docs/sources/ index + the committed impact-report.md.
  • Also adds [tool.ruff.format] exclude = ["*.md"] to pyproject.toml: origin/main lacks this CI fix and ruff 0.16.0 began formatting Python code fences inside Markdown (overlapping rumdl), which would fail this PR's "Check Python formatting with ruff" job. It is a prerequisite for this PR's CI and is documented inline.

Testing

  • uv run pytest tests/datahandlers/test_gard.py -m unit --no-cov -q2 passed, 2 deselected
  • uv run pytest tests/datahandlers/test_gard.py --network --no-cov -q4 passed
  • uv run pytest -m unit --no-cov -q404 passed, 107 deselected
  • uv run ruff check . / ruff format --check . / snakefmt --check . / rumdl check . (committed files) → all clean
  • uv run snakemake -c 1 -n get_gard get_gard_labels_and_synonyms disease_gard_ids → 3 jobs resolve in the correct chain
  • uv run source-impact-report --source GARD --mode synthetic --intermediate-root /tmp/impact/intermediate → report written (16,214 new cliques, 0 merges)

Deferred

  • Biolink registration: registering GARD in the Biolink Model's disease id_prefixes is the long-term fix for the extra_prefixes=[GARD] escape hatch (and removes the report's "NOT emitted" flag).

Questions for the reviewer

  • extra_prefixes vs. Biolink-first. This PR ships the extra_prefixes=[GARD] escape hatch so the ~16k rare-disease terms actually surface in Disease.txt now (rather than being silently dropped), with Biolink registration tracked as the long-term fix — is that the preferred path, or should GARD wait on upstream Biolink registration before being kept?

@SkyeAv SkyeAv added enhancement New feature or request new data source A new data source to include in Babel documentation Improvements or additions to documentation discussion needed testing Related to the test suite or testing infrastructure Priority: High labels Jul 28, 2026
@SkyeAv

SkyeAv commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Update: no-URL exclusion + source-impact report

Per the GARD URL field: a row with no URL has no public rarediseases.info.nih.gov page and is a candidate/provisional term — not a real rare disease — so it is now excluded from the ingest. This drops ~9,949 of ~16,214 rows, keeping 6,265 real rare-disease terms.

  • The filter is the single if not (row.get("URL") or "").strip(): continue line in src/datahandlers/gard.py; because disease_gard_ids derives ids from the labels file via awk, the exclusion cascades into the ids file and the compendium automatically. The parse logs a kept/skipped/no-URL/empty-name summary so a future NCATS format change can't silently zero the output.
  • A source-impact report (synthetic mode) is now generated and committed at docs/sources/GARD/impact-report.md (+ impact-report/ detail CSVs). Baseline disease intermediates came from the 2025dec11 published snapshot (MP's concord, absent from that snapshot, was rebuilt locally from UberGraph). Result: 6,265 new single-identifier cliques (241,269 → 247,534), 0 merges / 0 modified / 0 xrefs — purely additive, no existing clique disturbed.
  • The report's section-4 samples are flagged "NOT emitted — prefix not registered in Biolink Model for biolink:Disease"; that flag is why the build passes extra_prefixes=[GARD] (see the PR body's Design section). Registering GARD upstream removes both the flag and the need for the escape hatch.

@SkyeAv

SkyeAv commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Reverted: the URL-field filter was a false positive

A missing GARD URL does not mean a row isn't a real rare disease — a term may simply lack a GARD page. Per maintainer feedback: GARD:0001049 "Cerebral arteriopathy ... type 1" (CADASIL) has a URL, while GARD:0024558 "Cerebral arteriopathy with subcortical infarcts and leukoencephalopathy" (also CADASIL) does not — same disease family, so excluding the no-URL one would drop a real rare disease.

  • The if not (row.get("URL") or "").strip(): continue filter in src/datahandlers/gard.py is removed; all 16,214 GARD terms are ingested (no URL gating). The URL column is back to "reference only, not ingested" (the CURIE resolves via the Biolink prefix map).
  • The source-impact report is regenerated: 16,214 new cliques (241,269 → 257,483, +6.72%), 0 merges / 0 modified / 0 xrefs — still purely additive.
  • Tests updated: the verbatim fixture (one row with a URL+synonyms, one without either) now asserts both are kept.

@SkyeAv

SkyeAv commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

@gaurav this branch successfully rebuilt the Disease Compendia at /projects/babel/runs/goetzs/NIH-GARD-1.18 on HT1 with the GARD ontology. Do you want me to put anything from that here or is a path to the sucessful output enough to merge this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request new data source A new data source to include in Babel Priority: High testing Related to the test suite or testing infrastructure

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant