Repository navigation
Conversation
- Add mesh_year to config.yaml (next to umls_version) and download
/online/mesh/rdf/{year}/mesh{year}.nt.gz. NLM stopped updating the top-level
mesh.nt.gz after MeSH 2024, so with UMLS 2026AA we were missing ~1,900 UMLS MSH
codes (mostly new SCRs). mesh{year}-dups.nt is the pre-dedup version of the same
data per the per-year readme, so it is not downloaded.
- Fail fast if the configured year is not published; no fallback to the stale
top-level file. The .gz is verified against NLM's .sha1.
- Per-year dumps use year-versioned record IRIs (mesh/2026/D002839).
normalize_mesh_nt() rewrites them to the unversioned form during decompression so
the existing SPARQL and get_mesh_id_from_iri() work unchanged, and raises if the
expected prefix never appears (wrong year or changed IRI scheme).
- Per-year RDF dropped meshv:registryNumber from MeSH 2025 on (mesh2024.nt: ~384k
triples; 2025/2026: none), which would have silently emptied the mesh_cas,
mesh_unii and NCBI_MESH concords. pull_mesh_registry() now stream-parses the
preferred concept's RegistryNumber from desc{year}.gz/supp{year}.gz (new
get_mesh_xml rule) and raises if it finds none. Against the old RDF registry,
158,927 pairs match; nearly all old-only pairs are on records NLM has deleted.
- Add reports/umls_mesh_skew.json (in all_reports): counts MRCONSO MSH codes with no
record in mesh.nt, so UMLS/MeSH version skew is visible. With 2026AA + MeSH 2026
only the 22 UMLS-only U "(MeSH Category)" codes remain. Informational only.
- Report MeSH in release-note version bumps, record the MeSH year/URL in the
NCBI_MESH concord metadata, and pass mesh_year through the pipeline test fixture.
- Tests use fixtures copied verbatim from mesh2026.nt, desc2026/supp2026 XML and
2026AA MRCONSO; a network test checks the configured year is on the FTP site.
- Docs: MeSH source data, IRI normalization and registry sourcing in
docs/sources/MESH/Ingestion.md; mesh_year/umls_version pairing in RunningBabel.md.
2026jul22's ids/MESH came from the stale top-level mesh.nt (MeSH 2024). With this PR's per-year download, the replay over MeSH 2026 ids/MESH and 2026jul22's ids/UMLS moves 46 more CUIs (33 protein -> chemicals), the 46 records two pipelines' ids/MESH share are unchanged (so the duplicate leader allowlist still covers them), and all 949 resolvable MeSH leaders are still predicted resolved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
version_bumps() only wrote a bullet when both configs had a key, so the first release with mesh_year (whose baseline config.yaml predates it) would have drafted no MeSH line at all, though MeSH moved from the stale 2024 dump to 2026. It now writes a visible to-do asking for the old version by hand, for this key and any added later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gaurav
added a commit
that referenced
this pull request
Oct 21, 2026
Regenerating 2026jul22.md/.csv with every pipeline's ids/MESH rebuilt from MeSH 2026 (#1144) gives identical output, so the proposed owners carry over. Also say get_mesh reuses an older mesh.nt without --forcerun. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
https://nlmpubs.nlm.nih.gov/projects/mesh/ serves the same files under the same paths as ftp://ftp.nlm.nih.gov/online/mesh/, and is the site NLM's download page links to. Its rdf/ directory lists one directory per MeSH year, so a browser is the quickest check for a release newer than mesh_year. We still download over FTP here; #1161 moves the downloads to HTTPS. Say so in config.yaml and the MeSH ingestion doc, so that an FTP failure before #1161 lands leads straight to the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skew report gains umls_msh_codes_missing_from_mesh_excluding_categories: the U "(MeSH Category)" headings UMLS adds to its MSH source are missing from every MeSH dump, so a limit on the raw count would only be offset by a constant. docs/sources/MESH/umls-mesh-skew/scripts/measure_skew.py runs the build's report against each year's per-year RDF dump. Against UMLS 2026AA, excluding U: MeSH 2026 misses 0 codes, MeSH 2025 829, MeSH 2024 1,856. That is the evidence for the build limit added next. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UMLS/MeSH skew report was informational, and nothing read it. Now rule check_umls_mesh_skew raises when more MSH codes than umls_mesh_skew_max_missing_codes (100) are missing from the MeSH dump, not counting the U category headings. 100 sits far from both measured cases: 0 with matched versions, 829 with MeSH a year behind UMLS 2026AA. rule umls_mesh_ownership takes the check's output as an input, so the five MeSH-owning pipelines stop before they build compendia on a skewed dump; only their ids/MESH rules run first. The check is a separate rule from the report, as with assert_no_unexpected_duplicate_clique_leaders, because Snakemake deletes the outputs of a job that raises and the JSON is what explains the failure. all_reports now requires the check rather than the bare report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
archive_build.py now requires reports/umls_mesh_skew.json, which every build since 2026jul22 writes; the releases already archived predate it. draft_release_notes.py prints its count under the version bumps, beside the UMLS and MeSH versions it depends on, and stays silent for an older build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MeSH is NLM's own spelling. docs/sources/README.md now says a source may be named for itself rather than its CURIE prefix where the two are spelled differently, as PubMed (prefix PMID) already was. The CURIE prefix stays MESH everywhere else: src/prefixes.py, babel_downloads/MESH, ids/MESH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gaurav
added a commit
that referenced
this pull request
Oct 21, 2026
#1144 renamed docs/sources/MESH to MeSH. Git's merge carried this branch's edits to existing files across but left the four files this branch adds under the old directory, which on a case-sensitive filesystem (the cluster, CI) would sit beside MeSH/ as a second directory. Move them, and point the references to them at the new path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 tasks
read_umls_mesh_codes() now skips the rows check_mrconso_line() rejects (SUPPRESS=O/E), as every other MRCONSO reader in Babel does. An obsolete atom for a deleted descriptor is missing from every MeSH dump, so counting it would shift check_umls_mesh_skew's limit by a constant. UMLS 2026AA has no such MSH rows (all are SUPPRESS=N or Y), so the measured skew table in docs/sources/MeSH/umls-mesh-skew/ is unchanged. Also: reuse MESH_IRI_PREFIX and get_mesh_id_from_iri() from the MeSH handler instead of a second, differently-shaped prefix constant, and fix the generate_umls_mesh_skew_report() docstring, which still called the report informational after check_umls_mesh_skew made it a build failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The current year's MeSH XML (MESH_FILES/xmlmesh) is updated daily, while the per-year RDF dump is a snapshot. On 2026-10-21 the XML had registry numbers for 100 SCRs that mesh2026.nt does not contain. The chemical concords filter by their ids file, but taxon's NCBI_MESH concord does not, so a txid on such a record would have put a MeSH ID with no label or type into a taxon clique. pull_mesh_registry() now keeps only records with a label in babel_downloads/MESH/labels and logs how many it skipped. pull_mesh_registry() also takes its desc/supp XML and labels paths as arguments instead of finding them with make_local_name(), so the rules' declared inputs are the files it reads and it can be tested on fixtures. Concord metadata for NCBI_MESH, mesh_cas and mesh_unii now names the XML files the registry numbers came from (recorded by get_mesh_xml in MESH/xml_sources.txt, since whether they came from the current or the archived directory is only known at download time) plus the RDF dump they are filtered to. NCBI_MESH previously named only the RDF dump, which has had no registryNumber triples since MeSH 2025. Download changes: - fetch_mesh_nt() hashes the RDF dump while it downloads, instead of reading the ~120 MB file once more just to hash it, and reports a missing .sha1 as a missing checksum file rather than an unpublished mesh_year. - mesh_xml_dir() is renamed fetch_mesh_xml_dir(), since it lists a remote FTP directory, and now closes its connection when login or nlst fails. - measure_skew.py downloads through fetch_mesh_nt() instead of its own unchecked urllib download, so its numbers come from the build's path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
NLM stopped updating the top-level
mesh.nt.gzafter MeSH 2024, so Babel's MeSH had fallen behind its UMLS: with UMLS 2026AA, 1,878 MSH codes on 2,162 CUIs had no MeSH record, mostly new Supplementary Concept Records such as foligluraxMESH:C000730045and befotertinibMESH:C000731667. This PR pins the MeSH release with a newmesh_yearinconfig.yaml, besideumls_version, and downloads that year's RDF dump. Per-year RDF has carried no registry numbers since MeSH 2025, so CAS, UNII and NCBITaxon registry numbers now come from the MeSH XML.So that this can't happen silently again, the build now fails early when
mesh_yearlagsumls_version, and each release archives the UMLS/MeSH skew report and prints its count in the drafted notes.The calls worth overruling
rule check_umls_mesh_skewraises when more UMLS MSH codes thanumls_mesh_skew_max_missing_codeshave no record in the dump. It leaves out theU"(MeSH Category)" headings UMLS adds itself, and theSUPPRESS=O/Erowscheck_mrconso_line()drops everywhere else (2026AA has none for MSH). Against UMLS 2026AA, MeSH 2026 misses 0, MeSH 2025 829 and MeSH 2024 1,856 (table and script). Stop the same CURIE leading cliques in two compendia, and fail the build when it does #1124'sumls_mesh_ownershipwaits for the check, so the five MeSH-owning pipelines stop before building compendia.pull_mesh_registry()stream-parses each record's preferred-conceptRegistryNumberfromdesc{year}.gz/supp{year}.gz(newget_mesh_xmlrule). The current year's XML is updated daily while the RDF is a snapshot, so on 2026-10-21 it had registry numbers for 100 SCRsmesh2026.ntlacks; only records with a label inMESH/labelsare kept, so the unfilteredNCBI_MESHconcord can't pull a label-less MeSH ID into a taxon clique. (Filtering it bytaxon/ids/MESHinstead would also drop existing links.) Against the old RDF registry (measured on 3a96a82), 158,927 pairs match; of the 2,327 only the RDF had, all but 11 are on records NLM has since deleted.get_meshandget_mesh_xmlfail rather than fall back to the stale top-level file. The dump is hashed as it downloads and checked against NLM's.sha1; a network test checks the year is published.normalize_mesh_nt()rewrites year-versioned record IRIs (http://id.nlm.nih.gov/mesh/2026/D002839) to the unversioned form, so the existing SPARQL andget_mesh_id_from_iri()work unchanged, and raises if the versioned prefix never appears.Details: Ingestion.md, Source data and Architecture.md, Build controls.
Also here
MESH/xml_sources.txt, since current vs. archived directory is only known at download time) and the RDF dump they are filtered to.archive_build.pyrequiresreports/umls_mesh_skew.json, anddraft_release_notes.pyprints its count and reports MeSH version bumps; for the first release after this one, whose baseline has nomesh_year, it drafts a to-do to name the old version by hand.config.yamland the ingestion doc point at https://nlmpubs.nlm.nih.gov/projects/mesh/rdf/ for checking whether a new MeSH year is out. Downloads stay on FTP here; Download MeSH over HTTPS from NLM's nlmpubs site instead of FTP #1161, stacked on this PR, moves them to HTTPS.What it does not do
Registry numbers on records already in the dump can still change from one day's XML to the next, so two downloads of the current
mesh_yearmay give different concords until NLM archives the year.Effect on #1124's MeSH ownership
Replaying #1124's rule on
ids/MESHrebuilt from MeSH 2026 (output): MeSH 2026 adds about 1,700 records and drops about 4,000 NLM deleted; the rule moves 46 more CUIs than on the 2024 dump, and all 949 resolvable MeSH duplicate leaders are still predicted resolved. The first cluster build confirms this (#1158).Verification
On 41b6dc2 the unit tests (730-odd) pass and ruff, snakefmt and rumdl are clean; fixtures are copied verbatim from
mesh2026.nt,desc2026/supp2026and 2026AAMRCONSO.RRF, and the download's checksum handling is tested against a mocked FTP server. On 41b6dc2get_meshandget_mesh_xmlran through Snakemake against NLM, andpull_mesh_registry()ran on the real XML. On c6a79c8 the full 2026 dump loaded through the existingMeshqueries, andcheck_umls_mesh_skewpassed with 0 missing codes on the local 2026AA and MeSH 2026 downloads and raised on the MeSH 2025 report.Nothing blocks this PR.
🤖 Generated with Claude Code