Skip to content

PR 4b: schema refinement — EFO assay term + stored molecular_layer - #8

Merged
mengerj merged 1 commit into
mainfrom
pr4b-schema-refinement
Jun 27, 2026
Merged

PR 4b: schema refinement — EFO assay term + stored molecular_layer#8
mengerj merged 1 commit into
mainfrom
pr4b-schema-refinement

Conversation

@mengerj

@mengerj mengerj commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Scope (one roadmap item: PR 4b)

Consumes the PR 4 ontology stage to refine the canonical schema. On StudyNode/DatasetNode, the free-text modality string is replaced by a grounded EFO assay term ID plus a derived coarse molecular_layer enum.

What changed

  • models/graph_schema.py — drop StudyNode.modality; add assay (EFO term ID, required) + molecular_layer (MolecularLayer, default UNKNOWN) to both StudyNode and DatasetNode. DatasetNode.assay now 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_layer is derived from it via the resolver's EFO ancestor walk. StudyNode is built from the most-frequent dataset assay + that assay's layer. Layer derivation is memoised per assay; only EFO: IDs are walked (ungrounded → UNKNOWN, no network call).
  • ontology/base.py — new OntologyService contract (TermResolver + molecular_layer) the normalizer depends on, so offline fakes inject both methods.
  • ontology/layers.py — switched molecular_layer matching from exact EFO labels to ordered case-insensitive substring keywords.

Key decisions (rationale in ARCHITECTURE §5)

  • Assay taken from Census payload, not re-resolved via OLS. Census is authoritative for its own data; re-resolving could drift. The resolver is used only for the lineage walk. Free-text sources (GEO/PRIDE) will resolve the assay string first.
  • Exact-label → substring-keyword matching. Validating the PR 4 anchors against live EFO confirmed the flagged gotcha: EFO:0009922 (10x 3' v3 — the bulk of CELLxGENE) never reaches RNA assay; it sits under …transcription profiling/library preparation, and ATAC/ChIP/methylation/WGS all collapse to a generic DNA assay ancestor. Exact matching derived UNKNOWN for 10x. Ordered substring keywords classify all observed assays correctly; bare mass-spec and multi-omic terms stay UNKNOWN by design.

Verification

All four CI gates pass, including a hermetic run with no .env:

  • ruff check . — clean
  • ruff format --check . — 40 files formatted
  • mypy src/parce — 24 files, no issues
  • pytest -m "not integration"133 passed

The marked live-OLS integration suite (tests/test_ontology_integration.py) was also run this session and passed (6 tests), confirming the tightened molecular_layer assertions 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

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
mengerj marked this pull request as ready for review June 27, 2026 08:20
@mengerj
mengerj merged commit 4dcd5b7 into main Jun 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant