Back to Changelog
Featurev1.8.0

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 / unmapped status if nothing resolves.
  • "sentinel" - the resolver returns an OMOP concept_id 0 record. ETL pipelines that need a writable row for every input get one, matching the OMOP CDM convention that unmapped concepts are concept_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.

Resources