SDK v1.8.0 & MCP v1.5.0: FHIR-to-OMOP IG Conformance
Python & R SDKs v1.8.0 and MCP Server v1.5.0 land HL7 FHIR-to-OMOP IG patterns - Value-as-Concept decomposition, user-selected coding priority, and an opt-in unmapped sentinel so ETL pipelines always get a writable row.
Python and R SDKs v1.8.0 and the MCP Server v1.5.0 are out together. This release brings all three clients into alignment with the HL7 FHIR-to-OMOP Implementation Guide - specifically the Value-as-Concept and CodeableConcept patterns - and adds an opt-in sentinel so ETL callers no longer have to special-case unmapped codes.
Value-as-Concept Decomposition
The FHIR-to-OMOP IG describes how a composite source code like "Allergy to penicillin" should resolve to a standard concept ("Allergy to drug") plus a separate value ("Penicillin G") - the Value-as-Concept pattern. The server has supported this via Maps to value traversal for a while; v1.8.0 makes the decomposed fields first-class on each client.
Python: FhirResolution now types value_as_concept, value_target_field, concept_map_id, and mapping_note. Already passed through; now part of the typed response shape - mypy and editor autocomplete pick them up.
# Python
result = client.fhir.resolve(
system="http://snomed.info/sct",
code="91936005", # "Allergy to penicillin"
resource_type="Observation",
)
print(result["resolution"]["standard_concept"]["concept_name"]) # "Allergy to drug"
print(result["resolution"]["value_as_concept"]["concept_name"]) # "Penicillin G"
print(result["resolution"]["value_target_field"]) # "value_as_concept_id"
R: The resolve_batch(as_tibble = TRUE) tibble gains value_as_concept_id and value_as_concept_name columns - one row per input coding, value columns populated only when the resolver decomposes the source via Maps to value:
# R
client |>
fhir_resolve_batch(codings, as_tibble = TRUE) |>
filter(!is.na(value_as_concept_id)) |>
select(source_code, standard_concept_name, value_as_concept_name)
MCP: fhir_resolve surfaces the same fields. Your AI agent can now reason about composite concepts without an extra hop:
You: Resolve SNOMED 91936005 to OMOP
Assistant: Standard concept: Allergy to drug (4097658). Value-as-concept: Penicillin G (1759842), written to
value_as_concept_id.
User-Selected Coding Wins
The FHIR CodeableConcept.coding array can carry multiple codings for the same clinical concept - SNOMED, ICD-10-CM, an EHR-local code, etc. - any of which may be flagged userSelected: true. The IG says: respect the user's selection over your own vocabulary preference.
Both SDKs and the MCP server now plumb user_selected (and FHIR's camelCase userSelected) through to the resolver, and resolve_codeable_concept lets a user-selected coding override OHDSI vocabulary priority:
# Python
result = client.fhir.resolve_codeable_concept(
coding=[
{"system": "http://snomed.info/sct", "code": "44054006"},
{"system": "http://hl7.org/fhir/sid/icd-10-cm", "code": "E11.9", "user_selected": True},
],
resource_type="Condition",
)
# ICD-10 wins despite SNOMED being higher in OHDSI preference order,
# because the user explicitly selected it.
on_unmapped: The Sentinel Switch
Source codes that have no Maps to target now have a documented contract. All three clients gained an on_unmapped argument on resolve, resolve_batch, and resolve_codeable_concept:
"error"(default) - unchanged. The resolver returns a 404 /unmappedstatus if nothing resolves."sentinel"- the resolver returns an OMOPconcept_id0 record. ETL pipelines that need a writable row for every input get one, matching the OMOP CDM convention that unmapped concepts areconcept_id 0.
# Python
results = client.fhir.resolve_batch(
codings,
on_unmapped="sentinel", # always return a row, even for unmapped codes
)
# R
results <- client |>
fhir_resolve_batch(codings, as_tibble = TRUE, on_unmapped = "sentinel")
Use the MCP Server? Same flag:
fhir_resolve(..., on_unmapped="sentinel").
Behavior Change Worth Flagging (R)
In R, a coding that resolves to a source concept but has no standard Maps to target now reports status = "unmapped" (with standard_concept_id = 0) instead of "resolved". This matches OMOP's concept_id 0 convention and the FHIR-to-OMOP IG's unmapped-resolution semantics.
If your code does filter(status == "resolved"), it will no longer treat these sentinels as successful mappings - which is the intent. Re-run pipelines that touch unmapped codings to confirm.
The MCP Server made the parallel change: fhir_resolve now presents the concept_id 0 sentinel as Unmapped rather than as a successful resolution, so agents don't downstream-reason about "no matching concept" as a real mapping.
Install or Upgrade
# Python
pip install --upgrade omophub
# R
install.packages("omophub")
# MCP Server (self-hosted)
npm install -g @omophub/omophub-mcp@latest
npx users and mcp.omophub.com hosted clients get v1.5.0 automatically.