Specimen tissue as a UBERON ontology term, matched from LIMS text
- Dominant language
- Python
- Stars
- 30
- Forks
- 3
- Avg merge
- 9h 28m
- Merged PRs (30d)
- 42
Description
🤖 Written by Claude
Split out of #1706, where the comment "there's currently no way to create tissue, either" turned out to
be bigger than that issue's scope.
`Specimen.tissue` is a FK to a local `Tissue` table (`patients/models.py:315`) that has no rows on any
deployment — no seed data, no importer path (`patients/import_records.py:360` is commented out), no API
field (`SpecimenSerializer` omits it), and no creation path outside the Django admin. So the specimen
page renders a tissue `` that is always empty.
The fix is to stop owning the vocabulary: make it a **UBERON** term in the `ontology` app, imported in
the standard manner alongside the DOID import already queued there, and keep the raw string the LIMS
sent beside the resolved term.
**Why UBERON**
* GA4GH Phenopackets recommends it for `Biosample.sampled_tissue` — the same field.
* CC BY 3.0, so it ships without a licence negotiation. (SNOMED CT's specimen hierarchy is the
LIMS-native vocabulary and arguably a better fit, but it needs an Affiliate licence plus registration
— free for Australia via the NCTS, not redistributable, and each non-Australian deployment would need
its own. The design below lets a deployment that holds SNOMED map its own codes into the same field.)
* OBO/OWL, which `ontology_import` already parses with `pronto`, so the loader is the same shape as
`load_hpo`.
* Covers what accessioning sends: blood `UBERON:0000178`, bone marrow `UBERON:0002371`, buccal mucosa
`UBERON:0006956`, saliva `UBERON:0001836`, skin of body `UBERON:0002097`, plus every organ a solid
tumour block comes from.
**Take what the LIMS gives**
LIMS send `FFPE`, `EDTA blood`, `BM aspirate`, `Buccal` and local codes, and a specimen with an
unrecognised tissue string is still a perfectly good specimen. So the text is the record and the term is
the interpretation: keep `tissue_text` as sent, resolve to a nullable UBERON `tissue_term`, and carry a
match status so an unresolved value reads as unresolved. Two precedents to follow —
`ExtractionMatchMixin` (`patients/models.py:478`), already in this app and already this shape, and
`ConditionText`/`ConditionTextMatch`, which keys normalized text per lab so mapping a string once
resolves every future specimen that carries it.
Preservation and processing (FFPE, fresh frozen, EDTA tube) are a separate axis from anatomy —
phenopackets puts them on `sample_processing`/`sample_type`. Out of scope; those values sit in the raw
text until someone needs that axis modelled.
**Design**: [`claude/plans/1706_remaining_work_plan.md`](https://github.com/SACGF/variantgrid/blob/master/claude/plans/1706_remaining_work_plan.md)
§B — the ontology comparison, the `ontology` app integration points (including keeping UBERON out of
`CONDITION_ONTOLOGIES` and out of `OntologyVersion`), the matching model, and where tissue gets set
(specimen page, patient CSV column, API field). Moving to its own plan doc under this issue's number.
Related: #1707 (the API field), and the DOID import this rides with.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.