Use Babel's preferred_name for clique labels#386
Merged
Conversation
Babel now emits a clique-level preferred_name on every compendium line, but
NodeNorm persisted nothing for it. Rather than stand up a new Redis DB, grow the
existing info_content_db (db 5) into a clique-property store: its value becomes a
JSON dict {"preferred_name", "ic"} keyed by canonical id, written for every clique
(not just IC-bearing ones). This is the first step toward consolidating clique-level
properties (issue #306); the config key name is kept for now to avoid churn.
The integration test asserts the new db-5 JSON format.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…as fallback create_node now uses the stored preferred_name as the node label instead of recomputing it (issue #299). The lookup key is the primary subclique's canonical id (eids[0]['i']), so conflated cliques get the gene-first name. When no preferred_name is stored — e.g. running against an older DB load — it falls through to the legacy label-selection algorithm, which is now marked frozen (do not modify; delete once all deployments load preferred_name). Babel's replacement lives at babel_utils.py#L581. info_content_db values are read through a new _clique_props() helper that tolerates both the new JSON dict and the old bare-float format. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a Disease.txt resource (one clique from Babel 1.18) that carries a non-empty preferred_name and a numeric ic, and asserts both round-trip through db 5. The existing Cell.txt case stays as the empty-preferred_name / string-ic branch, so both db-5 value shapes are exercised. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… taxa The per-identifier `t` (taxon) field is already persisted in id_to_eqids_db as part of the identifiers blob, so the clique-level `taxa` field is redundant and should not be copied into the db-5 clique-property store. Adds a PhenotypicFeature.txt resource with a human-taxon clique and asserts the taxon round-trips via id_to_eqids_db, and records the rationale in a loader CLAUDE.md so future edits don't add taxa to db 5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR shifts NodeNormalization’s clique labeling to use Babel’s clique-level preferred_name field, storing it in Redis (db 5) alongside ic as a JSON “clique properties” document. It also preserves the legacy preferred-label algorithm as a fallback for older Redis loads that lack the new db-5 JSON format.
Changes:
- Loader now writes a clique-properties JSON value (
{"preferred_name", "ic"}) toinfo_content_db(db 5) for every clique. - Normalizer reads
preferred_namefrom db 5 and uses it increate_node()when present, falling back to the legacy label-selection algorithm otherwise. - Integration test expanded to validate preferred_name + ic JSON round-tripping, and adds a taxa round-trip assertion via
id_to_eqids_db.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
node_normalizer/loader/loader.py |
Writes clique-level properties JSON (preferred_name, optional ic) into db 5 for every clique. |
node_normalizer/normalizer.py |
Adds _clique_props() parser; reads preferred_name from db 5 and threads it into create_node() label selection. |
tests/test_loader_integration.py |
Extends integration load coverage to assert db-5 JSON clique props and taxa retrieval via id_to_eqids_db. |
tests/resources/Disease.txt |
Adds a test compendium line that includes preferred_name + numeric ic. |
tests/resources/PhenotypicFeature.txt |
Adds a test compendium line that includes preferred_name + per-identifier t taxa. |
node_normalizer/loader/CLAUDE.md |
Documents the intentional choice not to store clique-level taxa in db 5. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds fast unit tests (no Redis) covering the branch introduced in this PR: a stored preferred_name is used verbatim for the clique label, an empty preferred_name falls through to the legacy algorithm, and _clique_props tolerates the new JSON and legacy bare-float formats. Unit tests suffice because only the conflation fallback touches Redis; the preferred_name branch and non-conflation fallback do not, so create_node is callable with app=None. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The new preferred_name mget spelled the codec 'utf-8' while the adjacent info_content_db reads use 'utf8'. Same codec; align for consistency. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment-only; no behavioral change to the deprecated label-selection block. Flagged by Copilot review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drives the real get_normalized_nodes conflation path against a mock Redis: a gene/protein clique whose gene_protein_db entry lists the gene first must resolve to the gene's identifier and its preferred_name, not the queried protein's. Guards the leading-sub-clique db-5 keying. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Real Babel data: the CDK2 gene clique (NCBIGene:1017, preferred_name "CDK2") plus four UniProtKB protein cliques, and the GeneProtein conflation listing them gene-first. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extends the loader integration fixture to load the CDK2 gene/protein compendia and the GeneProtein conflation, then adds two tests: - test_gene_protein_conflation_loaded: every clique member maps to the gene-first list in gene_protein_db, and the gene's preferred_name lands in db 5. - test_query_gene_protein_conflation_uses_gene_preferred_name: drives the real get_normalized_nodes against the loaded Redis; any protein in the clique normalizes to NCBIGene:1017 / "CDK2" under conflation, and keeps its own identity/name without it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Captures the non-obvious bits of the gene/protein end-to-end test: reusing the loader fixture's redis_config.yaml for the frontend factory, resetting the process-wide RedisConnectionFactory.connections cache, and faking the app with a pre-seeded ancestor_map to avoid a Biolink Toolkit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Test-authoring guidance is more discoverable in a tests-scoped CLAUDE.md (auto-loaded when editing tests) than buried in the loader doc. Loader.md keeps a pointer and the loader-driving mechanics; tests/CLAUDE.md also records the shared-app-singleton import-time-mutation pitfall. Co-Authored-By: Claude Opus 4.8 <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.
Context
NodeNorm picks a preferred label for every clique. What began as "first label in the list" has grown into a ~90-line algorithm in
create_node()(conflation label-hunting, boost-prefix sorting, CHEMBL/length filtering) that tries to replicate the label Babel would pick. Babel now computes this itself and emits a clique-levelpreferred_nameon every compendium line, so per #299 NodeNorm should just use that field.The catch:
preferred_nameis clique-level and was stored nowhere in Redis — the loader persists per-CURIEl/d/tinside theid_to_eqids_dbblob, and clique-leveltype/icin their own DBs, but notpreferred_name.This is adjacent to #306 (consolidate all clique-level properties into one DB). Rather than the full overhaul, this takes the first step: repurpose the existing
info_content_db(db 5) into a clique-property store whose value is a JSON dict. For 2.5 it holdspreferred_nameandic; later (#306)typeetc. move in. No new database is provisioned — db 5's values just get richer.What changed (by commit)
preferred_namein Redis —load_compendiumwrites{"preferred_name", "ic"}JSON to db 5 for every clique (previously only IC-bearing cliques got a key). Config key name kept to avoid churn; rename toclique_props_dbdeferred to Redesign Redis databases to support arbitrary properties on identifiers and cliques #306. Integration test asserts the new format.create_nodeuses the storedpreferred_name(looked up by the primary subclique's canonical ideids[0]['i'], so conflated cliques get the gene-first name). When absent — e.g. querying an older DB load — it falls through to the legacy algorithm, now marked do-not-modify and slated for deletion once every deployment loadspreferred_name(Babel's replacement:babel_utils.py#L581). A new_clique_props()helper reads db 5, tolerating both the new JSON and the old bare-float format.test_normalizer.pyunit tests coveringcreate_node's new branch (see Verification), aligned the new db-5 read onencoding='utf8'with its neighbours, and fixed an "algorithem" comment typo in the frozen fallback (Copilot review).Verification
pytest -m "not integration"→ 29 passed (query fixtures have nopreferred_name, so they exercise the fallback unchanged — no regression). Newtest_normalizer.pyunit tests exercise the read path directly (no Redis — only the conflation fallback touches it):preferred_nameis used verbatim for the label (and bypasses the fallback's CHEMBL/length filters)""preferred_name falls through to the legacy algorithm_clique_props()tolerates the new JSON dict and the legacy bare-float/int formatsget_normalized_nodesreturns the leading sub-clique's identifier andpreferred_name(mock-Redis: a gene/protein clique resolves to the gene's name, not the queried protein's)pytest -m integration(real Redis testcontainer) → passes. The loader test covers all three clique properties end-to-end:""(Cell) and populated (Disease) via db 5"100"(Cell) and float100.0(Disease) via db 5, tolerating both source formatstfield round-trips viaid_to_eqids_db, confirming taxa are retrievable without a db-5 copyNCBIGene:1017conflated with four UniProtKB proteins, gene-first) — two integration tests over the same freshly-loaded Redis:gene_protein_db, and the gene'spreferred_name"CDK2" lands in db 5RedisConnectionFactoryis attached to the loaded Redis andget_normalized_nodesis driven directly: any protein in the clique normalizes toNCBIGene:1017/ "CDK2" under conflation, and keeps its own identity/name without itScope / follow-ups
info_content_db→clique_props_dband migratingtype(etc.) into it: deferred to Redesign Redis databases to support arbitrary properties on identifiers and cliques #306. The JSON value makes each a one-liner on both sides.taxais deliberately excluded — it stays inid_to_eqids_db(see above).ictype is inconsistent in the source data — Babel emits it as a JSON string in some compendia ("100") and a number in others (100.0), and the loader passes it through untyped, so db 5 stores whichever came in._clique_props()tolerates both. Normalizing to float on write is a candidate cleanup if any consumer starts doing math on it.preferred_name_boost_prefixes/demote_labels_longer_thanconfig stays (used only by the frozen fallback); delete alongside the fallback.🤖 Generated with Claude Code