Metadata fields¶
Twelve declared fields in metadata/fields.py, as data rather than code. Two
halves, chosen for two different reasons.
Grounded: what a search index needs¶
Seven fields, each naming the ontologies its values may come from. The set and its ontologies follow the CELLxGENE schema, so output lands in the vocabulary the single-cell community already indexes on rather than in one invented here.
| field | ontology | multivalued | notes |
|---|---|---|---|
organism |
NCBITaxon | no | usually needs no lookup: ENA returns tax_id |
tissue |
UBERON | yes | |
cell_type |
CL | yes | a sorted study can legitimately carry two |
disease |
MONDO | yes | normal is a value, not an absence |
development_stage |
HsapDv / MmusDv / UBERON | no | ontology depends on the organism |
sex |
PATO | no | not UBERON; see below |
assay |
EFO | no | deliberately overlaps the assay layer |
All nine ontology ids used here were checked against OLS4 before being
declared: uberon (26,624 terms), cl (19,167), mondo (63,460), efo
(94,312), pato (8,625), ncbitaxon (2,854,537), hsapdv (260), mmusdv
(178), hancestro (1,310). A wrong id makes grounding return nothing, silently.
sex declares PATO, and that is load-bearing¶
Constrained to UBERON, ZOOMA answers the property value male with
UBERON:0003101 male organism at confidence GOOD -- an anatomical entity
where a phenotypic quality was asked for. Constrained to PATO it answers
PATO:0000384 male at HIGH.
The field declaring the ontology that can actually express it is what stops the wrong question being asked. The pathology.
development_stage declares one ontology per organism¶
HsapDv covers human and MmusDv covers mouse, and neither covers the other, so grounding a mouse stage against HsapDv would either fail or find a false friend. An unlisted taxon falls back to UBERON rather than to nothing: a generic lifecycle term is a worse answer than a species-specific one and a better answer than refusing to ground anything for every organism this project has not enumerated.
Structural: what a pipeline needs and no standard records¶
Five fields with no ontology. They carry a normalised string and no CURIE, because there is nothing to ground an opaque submitter label against and anything a lookup found would be a false friend.
| field | why it is here |
|---|---|
donor_id |
which libraries came from one individual. Named by only 11% of datasets, and the single most valuable structural field |
sample_grouping |
the key a grouping was computed on. A treatment or a timepoint is not a grouping key; a donor identifier is |
multiplexing |
the pooling scheme, where several donors share one library |
timepoint |
the submitter's own label, for time-course designs |
age |
as written, with units where given |
age is deliberately not normalised to a number. E14.5, 8 weeks,
60-65, adult and P0 are all real values, and collapsing them to a float
would invent precision. Where a comparable claim can be made it is made by
grounding development_stage instead.
Scope: every field describes a sample¶
All twelve are sample-scoped, and a test asserts it. That is what makes the study-scope rule checkable: a source declared study-scoped can never legitimately assign any of them.
The rule exists because of PRJNA434002, whose study_title names Autism
Spectrum Disorder for all 56 runs, controls included, while the per-sample
disease column is empty for every one. Applying it would mislabel the
controls. See
the metadata layer.
Cardinality is not conflict¶
A field being multivalued means one source may legitimately name several
things: a submitter writing lung, lymph node is describing a mixture.
It does not mean two sources naming different things is acceptable. Conflict
is detected per source, so ENA saying lung where GEO says brain is reported
as a disagreement even on a multivalued field. Keying conflict detection on
cardinality instead reported the second case as a mixture and hid it.
Adding a field¶
A frozen dataclass instance, and nothing else:
Field(
name="self_reported_ethnicity",
scope=Scope.SAMPLE,
summary="Self-reported ancestry of the donor.",
ontologies=("hancestro",),
)
tests/test_metadata_fields.py::TestTheDeclarationIsData asserts that a new
field needs no change to the core, and that a field declaring no ontology is
valid.