feat: cBioPortal→OMOP showcase — slice-0 concept mapping + slice-1 FK-closed OMOP shape with cross-study identity - #116
Merged
Conversation
This was referenced Jul 22, 2026
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Generalize OncoTree/cBioPortal literals in domain_prompts, few_shot_healthcare, and few_shot_healthcare_stage_a; relocate HIERARCHICAL_VOCABULARIES to models/vocab_knowledge.py. Add tests/unit/test_engine_coupling.py asserting the R29 denylist returns EMPTY against src/sema/engine/ (US-001 guard prerequisite). Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Single R29_POLICY constant (denylist + core paths + allowlist) scanned by scripts/check_engine_coupling.py, wired as a unit-marked test so pytest fails on a leak. Case-explicit denylist; missing core paths are skipped so the guard tightens automatically as resolve/compile spine modules land. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
§1.5(f) confusion-matrix harness (mapping_goldset + _utils) scoring distinct-code, row-weighted, and per-frequency-bucket; scaffolds the 64 distinct ONCOTREE_CODE gold artifact (UNLABELLED — human-label gate left for US-012). No resolver. 29 unit tests + skip-guarded live coverage test. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…005) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…m) (US-006) VocabularyResolver composes the four §4 stages (candidate match in the source vocabulary, 'Maps to' standardization, the domain gate, single-model tail disambiguation) into one per-code decision. The code-bearing hot path is pure SQL with no LLM; resolve_and_store is the sole writer of value-mapping-store rows (one row per §1.5(a) grain incl NO_MAP). The R29-scanned engine.py names no domain literal — all concept-level construction lives in engine_utils.py. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Add FieldMap.status (§1.5(d)), the §1.5(e) coverage rule (NO_MAP/CONSTANT-NULL/ rejected never cover), derive_verdict any_review_pending from field-map statuses, and Slice0PlanAssembler composing assertions into a verdicted MappingPlan. Staging obligation authored in the allowlisted OMOP policy; assembler stays R29-generic. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…-009) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…) (US-011) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Make the Slice-0 chain multi-warehouse: the US-012A `sema fit` CLI runs on DuckDB or live Databricks via an injected StagingBackend strategy, with no new chain logic. DUCKDB_BACKEND is the default (temp-build + scoped swap, byte-identical to before); DATABRICKS_BACKEND writes the §1.5(b) staging table with an atomic Delta INSERT ... REPLACE WHERE scoped on source_schema/table. The value-mapping store stays DuckDB (US-005 canonical) on every backend. Adds hermetic unit tests (fake cursor asserts the rendered Databricks SQL and drives the whole databricks CLI chain), a skip-guarded live e2e test, and a runbook. The live run + metric acceptance remain a human gate (workspace credentials + human-labelled 100% gold coverage, US-002/US-012). Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Sign-off and signed via DCO. Signed-off-by: deanban <dean@datadepo.us> Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…-strict The Slice-0 assembler's §1.5(e) staging coverage gate was dead in the live path: build_slice0_fit_request used an ad-hoc single-field obligation under a different ref namespace, and make_slice0_staging_obligation() had only test callers. Wire the real 3-field obligation (condition_concept_id + resolver_policy_ref + vocab_release) and emit the two run-constant columns as CONSTANT MappingAssertions so the MappingPlan faithfully covers all three and the coverage gate runs end-to-end (bug-365). Add `sema fit --strict`: exit 3 on a failed Gate D-lite or a non-ACCEPTED eval verdict; default behavior unchanged (bug-366). Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Handoff captures the review, the two wiring fixes (737338a), the measured resolver behavior (63 single-survivor / 1 NO_MAP / 0 ties), the corrected Databricks diagnosis (concept tables were unloaded), and the remaining critical path (human labels + live run). Adds the OncoTree API NCIt/UMLS reference sheet as the independent oracle for hand-labelling the 64 gold codes. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
delete_study_scoped wiped every structural node schema-wide before per-table processing; on --resume the wiped tables were then skipped and never rebuilt (or lost outright if the run was interrupted). Add loader.delete_table_scoped (physical-anchor + orphan-GC, no schema-wide edge wipe) and clear only tables lacking cached assertions on resume; fresh builds keep the full schema wipe. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…gated (bug-369 F4) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…ot gold ACCEPTED (bug-369 F1) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…contract machine-readable (bug-369 F2) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…g-369 F3) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…re (bug-383) run_fit built the strict-gating report from decisions_from_store, which reads the entire value-mapping store filtered only by target/policy/vocab release. A stale cached mapping for a code absent from the current source could contradict a gold label and fail --strict even when Gate D-lite and contract conformance passed for the run. Build the report from run_mappings (the exact current-run grain), matching the F1 conformance gate. Adds a two-run regression test. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Durable plan for renaming the resolver_policy_ref and retiring the manifest `vocabulary: SNOMED` anchor, now unblocked by the bug-374/bug-383 run-scoping fixes. Captures the two identity-key hazards (store grain key + :VocabularyBinding MERGE key), the grepped touch-point map, three locked decisions with recommendations, and a TDD execution order. Not executed. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…alog (bug-384, bug-385) bug-384: VocabLookupProducer.produce read the whole persistent store via DecisionSource.read_all(), scoped only by binding — so a stale same-policy row leaked its status/confidence into the column assertion and masked an empty run's NoResolvedDecisionError. Fold the run's Sequence[ValueMapping] directly (third fix of the unscoped-read_all class, after bug-383/bug-374). bug-385: ValueMappingStore.column_names() filtered information_schema.columns by schema+table only, double-counting when a second catalog (poc.duckdb) is attached with the same table. Scope to current_database(). Also correct the repoint plan's test-file count (15 -> 8). Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
The run-scoping fixes (bug-374/383/384) replaced store.read_all() — which the store PK collapses via ON CONFLICT(GRAIN_KEY) — with run_mappings, which is one row per INPUT code and NOT grain-collapsed. Non-distinct source codes would then inline duplicate VALUES rows and inflate the staging join. Add _dedupe_to_grain (last-wins, mirroring the store upsert) right after resolve_and_store. Found by Codex adversarial review; regression test with duplicated source_codes. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Folds in the plan review: D1 must rename the manifest's top-level `vocabularies:` declaration alongside the binding slot (a sentinel with no declaration falls back to INLINE and the normalizer raises DanglingRefError); D2's cleanup DELETE is now schema-qualified and backup-guarded; the ":Term not re-keyed" claim is scoped to the current manifest shape; and the runtime consumers of resolver_policy_ref plus the policy-registry key are listed as auto-covered touch points. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…re SNOMED-named anchor) Retire the SNOMED-specific legacy anchor in the policy-ref key. Rename the OMOP_ONCOTREE_CONDITION_REF value omop.oncotree_to_snomed_condition -> omop.oncotree_condition (constant NAME and registry key unchanged, so dispatch flows through automatically) and the slice-0 manifest resolver_policy_ref. Tests now import the constant instead of hardcoding the literal, so a future rename stays one-line. Policy docstring/comments corrected to say OMOP standard-Condition (vocabulary-agnostic), not SNOMED. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Retire the SNOMED-named binding anchor. Rename BOTH the top-level vocabularies: declaration (name: OMOP-Condition) AND the binding vocabulary: slot in the same edit — the slot is resolved against the declaration via vocab_source_index, so renaming only the slot would fall back to INLINE and raise DanglingRefError in the normalizer. The value now names the OMOP governance scope, not a source vocabulary. Add a manifest-load regression test proving the clean load, plus a binding.vocabulary_name assertion. Align the _omop_binding live-test helper to the same sentinel. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Move the decision doc's Deferred items to Done, mark the plan EXECUTED, and record the two-commit repoint + D2 cache wipe + live Databricks verification in .wolf/cerebrum.md and memory.md. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…sting garbage Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…tion (S1-02) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…ath (S1-03) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…ition_start_date (S1-04/D4) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…n write (S1-06) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…r OMOP shape (S1-07) Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…ope S1-08 Databricks bridge Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
DuckDB temp-swap vs Databricks atomic Delta INSERT ... REPLACE WHERE (no BEGIN/COMMIT, no client temp table); FkClosedCompiler and run_fk_closed_qa become dialect-agnostic via a backend strategy, mirroring StagingBackend. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…S1-08) Mirror the whole registry into a Delta table (CREATE OR REPLACE TABLE + batched INSERT VALUES) so the live parent SELECT DISTINCT entity_id and child FK join can read it in-warehouse; full idempotent replace. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
…S1-08) Wire resolve -> registry bridge -> FK-closed materialize -> Gate-D-lite for one study on DuckDB or Databricks; OMOP physical specs via make_omop_fk_specs in the R29-allowlisted policy. Verified live on cbioportal_msk_chord_2024. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
showcase/ lives at the repo root, not under src/, so the editable install never puts it on sys.path — the showcase commands resolved only by accident of CWD. The two except-ImportError swallow sites dropped fit/fit-omop-shape/ collapse-omop-identities whenever sema ran outside the repo. Add ensure_showcase_importable() (no-op for wheel/uv-tool installs), guard the imports with find_spec, and log a warning on a genuinely broken showcase. Also: reject divergent per-study shape in run_stage_b_collapse (the rebuild borrows the head's specs/decisions for all sources) and correct the rebuild_after_collapse docstring on the FK-closure guard's true reach. Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
Signed-off-by: deanban <3989225+deanban@users.noreply.github.com>
deanban
force-pushed
the
ralph/feat/omop-shape-identity
branch
from
July 22, 2026 22:32
80afbd9 to
88da67e
Compare
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.
Summary
Delivers the cBioPortal → OMOP mapping showcase end to end, in two vertical slices, keeping the SEMA core domain-generic (R29):
omop.person+omop.condition_occurrence, FK-closed, with a two-level identity registry and deterministic cross-study person dedup (Stage B). All OMOP/cBio specifics live inshowcase/cbioportal_to_omop/; the FK-closed harness, identity registry, and collapse engine stay generic in core.Live results (Databricks
workspace.omop_stage_a)Two MSK studies materialized and de-duplicated on the shared MSK-DMP
P-key:person: 53,562 (= chord 24,950 + impact 48,179 − 19,567 cross-study duplicates retired)condition_occurrence: 79,371 (chord 25,040 + impact 54,331), FK-closed, 0 orphans0sentinel)Dedup was verified against an adjudication set: 19,567 merges, 0 sex mismatches across 19,437 corroborable pairs, all over-collapse guards clean (P- namespace only, 0 blank keys, 0 cross-namespace
TCGAcollision, patient key a true PK in both studies), 0 residual keys split across >1person_id.Key design points
(source_namespace, key) → uid → person_id) so Stage B can revise identity without rewriting surrogate PKs.person_idis registry-assigned, never a hash — a hash would be incompatible with dedup.condition_start_dateisnullable(cBio carries only relative day-offsets, no absolute date); missing patient keys route to review; unmapped concepts use the standard OMOP0sentinel.Testing
src/change since the last green suite.Follow-ups
Filed as separate issues:
condition_start_offset_daysprovenance (D4)Also advances #79 (extract cBioPortal to its own package) via the
showcase/cbioportal_to_omop/relocation.