Document where NodeNorm's data comes from, and serve the API docs that were being discarded#403
Document where NodeNorm's data comes from, and serve the API docs that were being discarded#403gaurav wants to merge 6 commits into
Conversation
NodeNorm does not decide which identifiers are equivalent, which one is preferred, or what a clique is called -- Babel does. That means most "why did I get this answer?" questions are really questions about Babel, and until now nothing in this repository said so. This adds a single page covering the Babel behaviour that shows up directly in a NodeNorm response: cliques and preferred identifiers, why the label is not necessarily the preferred identifier's label, information content, the conflation ordering guarantees, where descriptions come from, four gotchas that surprise people, and which tracker to file against. It is also deliberately the *only* file in this repository that links into Babel's docs/ tree. Everything else links here instead, so a reorganisation on the Babel side breaks one file rather than twenty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The README carried two examples that no longer describe anything real. The "compendia file" sample predated Biolink-prefixed types (it showed chemical_substance and a bare PUBCHEM: prefix) and was in any case a normalization *response*, not a compendium line. The config.json sample listed Redis credentials, which the loader reads only from redis_config.yaml, and old-style filenames the pipeline no longer produces. Both are replaced by pointers to the documents that are actually maintained. API.md gains the things it had fallen behind on: the include_taxa and taxa fields, the defaults for every parameter, the note that a CURIE which cannot be normalized comes back as null rather than being omitted, the biolink_model block returned by /status, and a finished sentence for the deprecated TRAPI section, which previously trailed off after "These methods". It also now flags that drug_chemical_conflate defaults to true on GET but false on POST (#398). Attribution of the information content calculation is corrected in passing: Babel takes the lowest value of any identifier in the clique (min() in its src/node.py) and NodeNorm returns that stored value unchanged. Finally, the root path is recorded as / rather than /1.3 or /1.5/. The TRAPI-versioned prefixes are deprecated and will be removed; the only place one should still appear is the OpenAPI servers block, which is itself temporary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ding
construct_open_api_schema() regenerates paths from the FastAPI route
decorators and copies back only info, tags and servers. Everything under
paths: in resources/openapi.yml -- 235 lines of it -- was therefore never
served to anyone. That included the only written statement of the conflation
ordering guarantees ("the gene(s) always appear first", "the active ingredient
appears before any formulations") and the /get_setid stability guarantee,
neither of which existed anywhere else in the repository.
That prose now lives on the decorators, where it is actually rendered, and the
dead paths: block is deleted. Every operation gains a real 200 description in
place of FastAPI's default "Successful Response", and apidocs.py grows a
comment saying where per-endpoint documentation belongs so this does not
happen again.
The response of /get_normalized_nodes is now described by NormalizedNode,
NormalizedNodeId and EquivalentIdentifier, plus a worked example. These are
attached via responses={200: {"model": ...}} rather than response_model=,
deliberately: create_node() builds those dictionaries by hand and omits keys
that do not apply, so letting FastAPI filter its output through a model would
risk silently dropping a field the model forgot. The schema is documented;
serialization is untouched.
Also fixed along the way:
- The MIT license in openapi.yml was parsed and then dropped, so the served
schema had no license. It is now copied across.
- apidocs.py called app.openapi_schema() on a dict, which would raise
TypeError. Unreachable today only because server.py assigns the attribute
directly, but it would fire the moment anything called app.openapi().
- CurieList used title= rather than description= on every field. Swagger
renders titles as labels, not help text, so those fields showed no
description at all. Its example also omitted two parameters and contradicted
a declared default.
- The /query and /asyncquery summaries both claimed to normalize a "TRAPI
response object"; /query takes a Query, and the two endpoints are not
identical. Both now say what they do and name their replacement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
construct_open_api_schema() regenerates `paths` from the route decorators and merges back only document-level metadata, so both halves fail silently: metadata that stops being copied just vanishes from the served schema, and endpoint documentation written into openapi.yml is never served at all. That is how 235 lines of prose sat unserved until #403. Five tests: the yml metadata (including the license, which was dropped for a long time) reaches the schema; openapi.yml has no `paths` section and the routes supply them; no operation is left on FastAPI's placeholder "Successful Response"; construct_open_api_schema returns the cached schema rather than calling a dict; and get_app_info reads what it claims to. Also records the split in CLAUDE.md, next to the note that the Dockerfile has to COPY ./skills for /llms.txt to work in a deployed image. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NodeNormalization, Babel and NameResolution all use main as their default branch. A /blob/master/ URL resolves today only through GitHub's post-rename redirect, which is exactly the kind of thing that disappears quietly -- so these are link rot rather than an acceptable alias. 25 links across five files: the endpoint descriptions in server.py (12), this branch's new Babel.md (6), the /status examples in API.md (2), openapi.yml (2), and the notebook including its Colab badge (3). Most predate this branch; they are fixed here because this is the documentation PR and because the skill branch stacked on it inherits them. raw.githubusercontent.com/biolink/biolink-model/master/... is deliberately not touched: biolink-model really does default to master. Verified: every rewritten github.com URL returns 200, the notebook is still valid JSON, and tests/frontend passes (22 passed, 4 skipped). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PR #403 fixed the reference problem — every endpoint now carries a real description and the response schema is documented. What an agent still lacks is judgement: when to reach for NodeNorm at all, which conflation flags to set, how to batch a whole column of identifiers, and the handful of behaviours that silently produce wrong answers rather than errors. This is that document. Every factual claim in it was measured against the live RENCI instance rather than written from memory, including the equivalent- identifier counts used to illustrate what conflation actually does. Three things it exists to prevent, all of which fail silently: - GET and POST disagree on the conflation defaults, so the same query returns 206 equivalent identifiers one way and 30 the other (#398). The rule taught is to set both flags on every request. - /get_setid takes `conflation` (singular) on GET and `conflations` (plural) in the POST body, defaults to no conflation unlike GET /get_normalized_nodes, and silently ignores the wrong name — returning HTTP 200 and an unconflated hash. Anyone comparing a gene against its protein this way concludes they are different concepts. - A conflated clique routinely holds several identifiers sharing one prefix (the DMD clique has three ENSEMBL entries: one gene, two proteins), so matching on prefix alone is a coin flip. The recipe uses individual_types and filters on type. Format is skills/nodenorm/SKILL.md with YAML frontmatter, which is what Claude Code actually discovers; the body is plain Markdown usable by any agent. The `description` names concrete CURIE prefixes, since that is what the model matches on when deciding whether the skill is relevant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
construct_open_api_schema() regenerates `paths` from the route decorators and merges back only document-level metadata, so both halves fail silently: metadata that stops being copied just vanishes from the served schema, and endpoint documentation written into openapi.yml is never served at all. That is how 235 lines of prose sat unserved until #403. Five tests: the yml metadata (including the license, which was dropped for a long time) reaches the schema; openapi.yml has no `paths` section and the routes supply them; no operation is left on FastAPI's placeholder "Successful Response"; construct_open_api_schema returns the cached schema rather than calling a dict; and get_app_info reads what it claims to. Also records the split in CLAUDE.md, next to the note that the Dockerfile has to COPY ./skills for /llms.txt to work in a deployed image. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two remaining master URLs, both in this PR's files. The install curl in LLMs.md matters most: it is the one line a reader actually executes. Both targets 404 right now, because skills/nodenorm/SKILL.md and documentation/Babel.md only exist on these two branches. main is still the right target -- an install instruction should hand people the merged file, and pinning it to a branch would rot as soon as the branch is deleted. They resolve once #403 and this PR land. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Pushed a commit here repointing every cross-repo link at NodeNormalization, Babel and NameResolution all default to Most of them predate this branch; they are here because this is the documentation PR and because #405 is stacked on it and inherits them. Verified: every rewritten Worth considering as a follow-up: NameRes now has |
Without something failing CI, the master links just fixed drift back. This is NameResolution's tests/test_docs_links.py adapted to this repo's paths, so the two services enforce the same rules: relative links resolve, heading anchors exist, the API.md anchors used in the endpoint descriptions exist, and no link is pinned to `master`. All offline -- a link check that fails when GitHub is slow is one people learn to ignore. It earned its place immediately: it found 12 more links, in releases/, that a grep for NCATSTranslator/<repo>/blob/master/ had missed because they use the pre-rename TranslatorSRI org as well as the pre-rename branch. Those are now NCATSTranslator/Babel/blob/main/ and all five distinct targets return 200. The other three checks passed on this repo unchanged, so the whole file came across rather than just the master ban. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PR #403 fixed the reference problem — every endpoint now carries a real description and the response schema is documented. What an agent still lacks is judgement: when to reach for NodeNorm at all, which conflation flags to set, how to batch a whole column of identifiers, and the handful of behaviours that silently produce wrong answers rather than errors. This is that document. Every factual claim in it was measured against the live RENCI instance rather than written from memory, including the equivalent- identifier counts used to illustrate what conflation actually does. Three things it exists to prevent, all of which fail silently: - GET and POST disagree on the conflation defaults, so the same query returns 206 equivalent identifiers one way and 30 the other (#398). The rule taught is to set both flags on every request. - /get_setid takes `conflation` (singular) on GET and `conflations` (plural) in the POST body, defaults to no conflation unlike GET /get_normalized_nodes, and silently ignores the wrong name — returning HTTP 200 and an unconflated hash. Anyone comparing a gene against its protein this way concludes they are different concepts. - A conflated clique routinely holds several identifiers sharing one prefix (the DMD clique has three ENSEMBL entries: one gene, two proteins), so matching on prefix alone is a coin flip. The recipe uses individual_types and filters on type. Format is skills/nodenorm/SKILL.md with YAML frontmatter, which is what Claude Code actually discovers; the body is plain Markdown usable by any agent. The `description` names concrete CURIE prefixes, since that is what the model matches on when deciding whether the skill is relevant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
construct_open_api_schema() regenerates `paths` from the route decorators and merges back only document-level metadata, so both halves fail silently: metadata that stops being copied just vanishes from the served schema, and endpoint documentation written into openapi.yml is never served at all. That is how 235 lines of prose sat unserved until #403. Five tests: the yml metadata (including the license, which was dropped for a long time) reaches the schema; openapi.yml has no `paths` section and the routes supply them; no operation is left on FastAPI's placeholder "Successful Response"; construct_open_api_schema returns the cached schema rather than calling a dict; and get_app_info reads what it claims to. Also records the split in CLAUDE.md, next to the note that the Dockerfile has to COPY ./skills for /llms.txt to work in a deployed image. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two remaining master URLs, both in this PR's files. The install curl in LLMs.md matters most: it is the one line a reader actually executes. Both targets 404 right now, because skills/nodenorm/SKILL.md and documentation/Babel.md only exist on these two branches. main is still the right target -- an install instruction should hand people the merged file, and pinning it to a branch would rot as soon as the branch is deleted. They resolve once #403 and this PR land. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Added It earned its place on the first run: 12 more The other three checks passed unchanged, so the whole file came across rather than just the
|
NodeNormalization moved to NCATSTranslator; these four still named the old org in places a user actually lands on: - The release badge in README.md linked to TranslatorSRI while its image already pointed at NCATSTranslator, so clicking the badge left the org the badge itself reported. - Two PR #323 links in server.py are served in the OpenAPI descriptions, so they show up in Swagger. - One more in documentation/API.md. An org-rename redirect is more durable than the branch-rename one cleaned up earlier, so this is consistency rather than an outage waiting to happen. The ~77 remaining references are all in releases/, i.e. historical notes recording what was true at the time; whether to sweep those is tracked separately in NameResolution#292, which covers the same question for that repo. Left alone deliberately: TranslatorSRI/r3 in README.md. That repository really does still live under TranslatorSRI -- NCATSTranslator/r3 is a 404. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
construct_open_api_schema() regenerates `paths` from the route decorators and merges back only document-level metadata, so both halves fail silently: metadata that stops being copied just vanishes from the served schema, and endpoint documentation written into openapi.yml is never served at all. That is how 235 lines of prose sat unserved until #403. Five tests: the yml metadata (including the license, which was dropped for a long time) reaches the schema; openapi.yml has no `paths` section and the routes supply them; no operation is left on FastAPI's placeholder "Successful Response"; construct_open_api_schema returns the cached schema rather than calling a dict; and get_app_info reads what it claims to. Also records the split in CLAUDE.md, next to the note that the Dockerfile has to COPY ./skills for /llms.txt to work in a deployed image. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two remaining master URLs, both in this PR's files. The install curl in LLMs.md matters most: it is the one line a reader actually executes. Both targets 404 right now, because skills/nodenorm/SKILL.md and documentation/Babel.md only exist on these two branches. main is still the right target -- an install instruction should hand people the merged file, and pinning it to a branch would rot as soon as the branch is deleted. They resolve once #403 and this PR land. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NodeNorm is a thin frontend over data whose complexity is almost entirely Babel's, and that inheritance was undocumented in one direction and stale in the other. This splits the two clearly, and fixes the largest documentation hole in the API along the way.
The division
Three buckets, decided once so it stays decided:
The new
documentation/Babel.mdholds the middle bucket and is deliberately the only file in this repository that links into Babel'sdocs/tree. Everything else links to it. A reorganisation on the Babel side now breaks one file instead of twenty.The OpenAPI fix
construct_open_api_schema()regeneratespathsfrom the route decorators and copies back onlyinfo,tagsandservers. Everything underpaths:inresources/openapi.yml— 235 lines — has therefore never been served to anyone. That included the only written statement of the conflation ordering guarantees ("the gene(s) always appear first", "the active ingredient appears before any formulations") and the/get_setidstability guarantee, neither of which existed anywhere else in the repo.That prose now lives on the decorators, and the dead block is deleted. Every operation gains a real 200 description instead of FastAPI's default
"Successful Response", and/get_normalized_nodesgoes from documenting its output as an untyped object to a full schema plus a worked example.The response models are attached via
responses={200: {"model": ...}}rather thanresponse_model=, deliberately —create_node()builds those dicts by hand and omits keys that don't apply, so letting FastAPI filter output through a model risks silently dropping a field the model forgot. Verifiedresponse_field is Noneon both routes: schema documented, serialization untouched.Also fixed: the MIT license was parsed then dropped from the served schema;
apidocs.pycalledapp.openapi_schema()on a dict;CurieListusedtitle=instead ofdescription=, so Swagger showed no help text for any of its fields; and both TRAPI summaries claimed to normalize a "response object".Stale content corrected
The README's compendia example predated Biolink-prefixed types (
chemical_substance, barePUBCHEM:) and was a normalization response, not a compendium line. Itsconfig.jsonexample listed Redis credentials, which the loader reads only fromredis_config.yaml. API.md was missinginclude_taxa,taxa, all parameter defaults, thebiolink_modelblock from/status, and had a sentence that trailed off after "These methods".Root path is now recorded as
/; the TRAPI-versioned prefixes are deprecated, and the only place one should still appear is the OpenAPIserversblock.Two corrections found by checking rather than trusting
DrugChemicalSmaller.txt.gzis a SapBERT training export, not a conflation file — so Babel's "only two conflation files" is right, and I dropped a planned edit.min(ICs)happens in Babel (src/node.py:443); NodeNorm returns the stored per-clique value unchanged. Corrected in both repos.Verification
pytest -m "not integration"— 27 passed, 4 skipped./openapi.json: license,x-translator,x-trapiand theSERVER_ROOT/TRAPI_VERSIONserver override all still present; all 8 paths carry real descriptions.documentation/Babel.mdlinks into Babel'sdocs/tree, and that all six link targets exist.blackwas not run: the repo is not black-clean at baseline (10 files, including several this PR never touches), so reformatting would bury the diff. Confirmed these changes add no new violations.Companion PR
Babel-side fixes are in NCATSTranslator/Babel#973, including correcting its claim that NodeNorm does not use
preferred_name.Follow-ups filed
#398 (
drug_chemical_conflatedefaults totrueon GET butfalseon POST — a real behavioural inconsistency, documented here as a known divergence), #399 (Retry(method_whitelist=)removed in urllib3 2.0), #400 (no v2.5.x release notes), #401 (docker-compose-redis.ymldoes not exist), #402 (duplicate handler names, OpenAPI version mismatch).🤖 Generated with Claude Code