PR 4b: schema refinement — EFO assay term + stored molecular_layer - #8
Merged
Conversation
Replace the free-text `modality` string on StudyNode/DatasetNode with a grounded EFO `assay` term ID plus a derived coarse `molecular_layer` enum, consuming the PR 4 ontology stage. - models/graph_schema.py: drop StudyNode.modality; add `assay` (EFO ID) + `molecular_layer` (default UNKNOWN) on StudyNode and DatasetNode. - normalize/cellxgene.py: take each dataset's assay ID from Census's already- grounded payload (Census is authoritative — re-resolving via OLS could drift) and derive its molecular_layer via the resolver's EFO ancestor walk; build the StudyNode from the most-frequent dataset assay. Layer derivation memoised per assay; only EFO IDs are walked. - ontology/base.py: add `OntologyService` contract (TermResolver + molecular_layer) the normalizer depends on; offline fakes implement both. - ontology/layers.py: switch molecular_layer matching from exact EFO labels to ordered case-insensitive substring keywords. Validated against live EFO: the 10x family (the bulk of CELLxGENE) never reaches "RNA assay" — exact matching derived UNKNOWN for it. Ambiguous lineages (bare mass-spec, multi-omic terms) stay UNKNOWN by design. - Migrated schema/normalizer/orchestration tests; integration test now asserts exact layers (10x + scRNA → TRANSCRIPTOME, ATAC → EPIGENOME), verified live. Gates green incl. hermetic no-.env run: ruff check, ruff format --check (40), mypy (24), 133 unit tests. No dep changes; no new mypy exemptions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mengerj
marked this pull request as ready for review
June 27, 2026 08:20
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.
Scope (one roadmap item: PR 4b)
Consumes the PR 4 ontology stage to refine the canonical schema. On
StudyNode/DatasetNode, the free-textmodalitystring is replaced by a grounded EFOassayterm ID plus a derived coarsemolecular_layerenum.What changed
models/graph_schema.py— dropStudyNode.modality; addassay(EFO term ID, required) +molecular_layer(MolecularLayer, defaultUNKNOWN) to bothStudyNodeandDatasetNode.DatasetNode.assaynow holds the grounded EFO ID, not a free-text name.normalize/cellxgene.py— each dataset's assay ID is taken from Census's already-grounded payload (ontology_summary.assays, matched to the dominant assay name);molecular_layeris derived from it via the resolver's EFO ancestor walk.StudyNodeis built from the most-frequent dataset assay + that assay's layer. Layer derivation is memoised per assay; onlyEFO:IDs are walked (ungrounded →UNKNOWN, no network call).ontology/base.py— newOntologyServicecontract (TermResolver+molecular_layer) the normalizer depends on, so offline fakes inject both methods.ontology/layers.py— switchedmolecular_layermatching from exact EFO labels to ordered case-insensitive substring keywords.Key decisions (rationale in ARCHITECTURE §5)
EFO:0009922(10x 3' v3 — the bulk of CELLxGENE) never reachesRNA assay; it sits under…transcription profiling/library preparation, and ATAC/ChIP/methylation/WGS all collapse to a genericDNA assayancestor. Exact matching derivedUNKNOWNfor 10x. Ordered substring keywords classify all observed assays correctly; bare mass-spec and multi-omic terms stayUNKNOWNby design.Verification
All four CI gates pass, including a hermetic run with no
.env:ruff check .— cleanruff format --check .— 40 files formattedmypy src/parce— 24 files, no issuespytest -m "not integration"— 133 passedThe marked live-OLS integration suite (
tests/test_ontology_integration.py) was also run this session and passed (6 tests), confirming the tightenedmolecular_layerassertions end-to-end (10x + scRNA → TRANSCRIPTOME, ATAC → EPIGENOME).No dependency changes; no new mypy exemptions.
▶ Next up after this: PR 5 — GEO extraction agent.
🤖 Generated with Claude Code