Back to Claude Scientific Skills

Curation rules

skills/ontology-term-resolution/references/curation-rules.md

2.57.05.3 KB
Original Source

Curation rules

How to choose among candidates, and what to do when the honest answer is "no term".

The decision procedure

Run it per string. Stop at the first step that gives a defensible answer.

  1. Exact label match in the expected ontology, from its defining ontology. Accept.
  2. Exact synonym match. Accept, but record the primary label, not the synonym. Metadata files should carry the ontology's own label so they diff cleanly against the ontology release.
  3. Exact match, wrong ontology. Usually a category error in the source column, not a naming problem — hepatocyte in a tissue field means the column mixes tissue and cell type. Fix the column, do not force a match.
  4. Partial match only. Do not accept silently. Either:
    • normalise the input and retry (see below), or
    • present the top candidates with their labels and let a human choose, or
    • mark it unresolved.
  5. Nothing. Mark unresolved and say so. An unresolved row is a correct output.

resolve_terms.py implements steps 1–4's search side and labels every hit exact_label, exact_synonym, or partial. The judgement about whether a partial is acceptable is yours; the tool will not make it for you.

Normalisations worth retrying

Cheap rewrites that convert a partial into an exact_label, in rough order of yield:

  • Drop qualifiers the source added: liver (donor), Liver - left lobe [FFPE].
  • Expand lab shorthand: PBMCperipheral blood mononuclear cell, WT → the actual genotype, M/Fmale/female.
  • Reverse an inverted phrase: ventricle, leftleft ventricle, cortex, kidneykidney cortex.
  • Singularise: hepatocyteshepatocyte. Ontology labels are singular.
  • Anglicise or Americanise: ontology labels vary; try both oesophagus and esophagus.
  • Strip species prefixes: human liverliver (species belongs in a separate NCBITaxon field).

Do not normalise away hyphens, Greek letters, digits, or capitalised gene symbols — CD4-positive and alpha-beta T cell mean what they say, and normalize_label() deliberately folds only case and whitespace.

When plain search keeps failing on lab shorthand, try ZOOMA with an ontology filter (ols4-api.md). It matches against how curators previously mapped that exact string, which is a different and often better signal than lexical search.

What "unresolved" should look like

Never invent an ID to fill a cell. An unresolved row carries the original string, an empty ID, and the reason. Downstream that is a visible gap; an invented UBERON:0002108 is a silent error that survives review because it looks exactly like a real ID.

If a concept genuinely has no term and the project depends on it, the route is a new-term request to the ontology (GitHub issue on the ontology's tracker, with a definition and a reference), not a locally minted identifier.

Auditing an existing metadata table

The high-yield checks, in order:

  1. Every ID exists. validate_terms.py --input table.tsv.
  2. No obsolete IDs. Obsolete terms carry term_replaced_by often enough that the fix is mechanical — but apply replacements deliberately, since a replacement can be broader or narrower than the original.
  3. Labels match IDs. Supply the label column. Mismatches are where copy-paste drift and hallucinated IDs surface: the ID is real, the label is real, and they describe different things.
  4. Right ontology per column. --expect-ontology.
  5. Right branch per column. --branch, remembering it does not exclude cell types from anatomy (ontology-registry.md).

--strict turns warnings into failures, which is the right setting for a CI gate. Warnings are matched_synonym (label is a synonym rather than the primary label), imported_only (the home ontology no longer asserts this ID), and not_a_class.

Obsolete terms

Obsoletion is not deletion — the ID keeps resolving, and its label is usually prefixed obsolete_. That prefix is a useful smell in any metadata file:

EFO:0001067  obsolete_parasitic infection  ->  replaced by MONDO:0005135

Some obsolete terms have no replacement, only a consider annotation or nothing at all. Then the term must be re-curated by hand; there is no automatic answer.

Cross-ontology mapping

OxO is retired and returns HTML with HTTP 200. Two workable routes:

  • Term cross-references. term_detail(curie)["annotation"]["database_cross_reference"] lists equivalents — UBERON:0002107 carries MESH:D008099, NCIT:C12392, FMA:7197, UMLS:C0023884, and more.
  • SSSOM mapping sets published by Monarch and the OBO community, when provenance and mapping predicates (skos:exactMatch vs closeMatch) matter.

Cross-references are asserted by curators at varying confidence and are not all exactMatch. Treat a single xref as a lead, not a proof, when the mapping drives analysis rather than display.

python
from ols_client import term_detail
xrefs = (term_detail("UBERON:0002107") or {}).get("annotation", {}).get(
    "database_cross_reference", []
)

Reporting

When you hand back resolved terms, give the ID and the label, and say how each was matched. A table of bare IDs cannot be reviewed — no reader can tell UBERON:0002107 from UBERON:0002108 by eye, which is precisely why invented IDs survive review.