Skip to content

Commit e618f8d

Browse files
author
alex-omophub
committed
Implement FHIR type interoperability and client helpers
- Added support for FHIR type interoperability in the resolver, allowing acceptance of various Coding-like inputs (dicts, TypedDicts, and objects with .system/.code attributes). - Introduced new FHIR type definitions (`Coding`, `CodeableConcept`) and runtime-checkable protocols (`CodingLike`, `CodeableConceptLike`) for structural matching. - Implemented FHIR client interop helpers to configure external libraries (`fhirpy`, `fhir.resources`) with OMOPHub's FHIR Terminology Service. - Enhanced `Fhir.resolve`, `Fhir.resolve_batch`, and `Fhir.resolve_codeable_concept` methods to accept mixed input types. - Updated documentation and examples to reflect new functionalities and usage patterns. - Added comprehensive unit and integration tests to validate new features and ensure compatibility.
1 parent 9292470 commit e618f8d

13 files changed

Lines changed: 1215 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,83 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.7.0] - 2026-04-14
9+
10+
### Added
11+
12+
- **FHIR type interoperability** (`client.fhir.*`): The resolver now
13+
accepts any Coding-like input via duck typing, so pipelines that
14+
already parse FHIR with `fhir.resources` or `fhirpy` can pass those
15+
objects directly - no manual conversion.
16+
- New `coding=` kwarg on `Fhir.resolve` / `AsyncFhir.resolve`. Accepts
17+
a plain dict, the `omophub.types.fhir.Coding` TypedDict, or any
18+
object exposing `.system` / `.code` / `.display` attributes (e.g.
19+
`fhir.resources.Coding`, `fhirpy` codings).
20+
- `Fhir.resolve_batch` / `AsyncFhir.resolve_batch` now accept a
21+
mixed list of dicts and Coding-like objects; each item is
22+
normalized to the wire format automatically.
23+
- `Fhir.resolve_codeable_concept` / async counterpart now accept
24+
either a list of Coding-likes (existing) or a full
25+
CodeableConcept-like object exposing `.coding` and `.text`
26+
(new); explicit `text=` kwarg still wins when both are passed.
27+
- Explicit `system` / `code` kwargs override the corresponding
28+
fields on a `coding=` input - handy for last-mile overrides.
29+
- **New FHIR type definitions** in `omophub.types.fhir`:
30+
- `Coding` and `CodeableConcept` lightweight TypedDicts
31+
- `CodingLike` and `CodeableConceptLike` runtime-checkable
32+
`Protocol`s for structural matching against external libraries.
33+
- **FHIR client interop helpers** (`omophub.fhir_interop`): Thin
34+
helpers for configuring an external FHIR client library against
35+
the OMOPHub FHIR Terminology Service.
36+
- `get_fhir_server_url(version)` returns the FHIR base URL for
37+
`"r4"` (default), `"r4b"`, `"r5"`, or `"r6"`.
38+
- `get_fhirpy_client(api_key, version)` and
39+
`get_async_fhirpy_client(api_key, version)` return `fhirpy`
40+
clients pre-wired with the right URL and `Authorization: Bearer`
41+
header. `fhirpy` is imported lazily, so it is never a required
42+
dependency; a helpful `ImportError` with install instructions is
43+
raised only when you actually call the helper.
44+
- All three helpers are re-exported from the top-level `omophub`
45+
namespace.
46+
- **`OMOPHub.fhir_server_url` / `AsyncOMOPHub.fhir_server_url`**:
47+
Convenience read-only property returning the R4 FHIR endpoint for
48+
drop-in use with external FHIR clients (`httpx`, `fhirpy`,
49+
`fhir.resources`).
50+
- **Optional extras** in `pyproject.toml`:
51+
- `pip install omophub[fhirpy]` pulls in `fhirpy>=1.4.0`.
52+
- `pip install omophub[fhir-resources]` pulls in
53+
`fhir.resources>=7.0.0`.
54+
Both are purely optional; duck typing means neither is required
55+
for core SDK use.
56+
57+
### Changed
58+
59+
- `Fhir.resolve_batch` signature broadened from
60+
`codings: list[dict[str, str | None]]` to
61+
`codings: list[CodingInput]` where `CodingInput` is the union of
62+
dict, `Coding` TypedDict, and `CodingLike` protocol. Existing
63+
call sites keep working unchanged.
64+
- `Fhir.resolve_codeable_concept` signature broadened from
65+
`coding: list[dict[str, str]]` to
66+
`coding: list[CodingInput] | CodeableConceptInput`, accepting
67+
either the legacy list-of-codings shape or a full
68+
CodeableConcept-like object.
69+
70+
### Tests
71+
72+
- 16 new unit tests covering `_extract_coding`, `_coding_to_dict`,
73+
and duck-typed inputs across `resolve`, `resolve_batch`, and
74+
`resolve_codeable_concept` on the sync client. Explicit-kwargs-win
75+
precedence is covered.
76+
- New `tests/unit/test_fhir_interop.py` with 10 cases: URL builder
77+
for all four FHIR versions, `fhirpy` lazy import with stubbed
78+
success and missing-module failure paths, and the
79+
`fhir_server_url` property on both sync and async clients.
80+
- 5 new integration tests in `tests/integration/test_fhir.py`
81+
exercising the new `coding=` kwarg, mixed dict + duck-typed batch
82+
inputs, CodeableConcept-like object resolution, and the
83+
`fhir_server_url` property against the live API.
84+
885
## [1.6.0] - 2026-04-10
986

1087
### Added

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ Working with OHDSI ATHENA vocabularies traditionally requires downloading multi-
3232

3333
```bash
3434
pip install omophub
35+
36+
# Optional extras for FHIR client interop
37+
pip install omophub[fhirpy] # Pre-wired fhirpy client
38+
pip install omophub[fhir-resources] # Install marker for fhir.resources
3539
```
3640

3741
## Quick Start
@@ -98,6 +102,70 @@ result = client.fhir.resolve_codeable_concept(
98102
print(result["best_match"]["resolution"]["source_concept"]["vocabulary_id"]) # "SNOMED"
99103
```
100104

105+
### Type Interoperability
106+
107+
The resolver accepts any Coding-like input via duck typing - a plain dict, omophub's lightweight `Coding` TypedDict, or any object with `.system` / `.code` attributes (e.g. `fhir.resources.Coding`, `fhirpy` codings).
108+
109+
```python
110+
from omophub.types.fhir import Coding
111+
112+
# omophub's TypedDict - IDE autocomplete, no extra deps
113+
coding: Coding = {"system": "http://snomed.info/sct", "code": "44054006"}
114+
result = client.fhir.resolve(coding=coding)
115+
116+
# fhir.resources objects work via duck typing - no conversion needed
117+
from fhir.resources.R4B.coding import Coding as FhirCoding
118+
fhir_coding = FhirCoding(system="http://snomed.info/sct", code="44054006")
119+
result = client.fhir.resolve(coding=fhir_coding)
120+
121+
# Mixed shapes in a single batch call
122+
result = client.fhir.resolve_batch([
123+
{"system": "http://snomed.info/sct", "code": "44054006"}, # dict
124+
FhirCoding(system="http://loinc.org", code="2339-0"), # fhir.resources
125+
])
126+
```
127+
128+
`fhir.resources` is **never** a required dependency. See [`examples/fhir_interop.py`](./examples/fhir_interop.py) for the full set of supported input shapes.
129+
130+
### FHIR Client Interop
131+
132+
Point external FHIR client libraries at OMOPHub's FHIR Terminology Service directly - useful when you need raw FHIR `Parameters` / `Bundle` responses instead of the Concept Resolver envelope.
133+
134+
```python
135+
from omophub import OMOPHub, get_fhir_server_url
136+
137+
client = OMOPHub(api_key="oh_xxx")
138+
139+
# Property on the client returns the R4 base URL
140+
print(client.fhir_server_url)
141+
# "https://fhir.omophub.com/fhir/r4"
142+
143+
# Helper for other FHIR versions
144+
print(get_fhir_server_url("r5"))
145+
# "https://fhir.omophub.com/fhir/r5"
146+
```
147+
148+
For `fhirpy`, install the optional extra and use the pre-wired client:
149+
150+
```bash
151+
pip install omophub[fhirpy]
152+
```
153+
154+
```python
155+
from omophub import get_fhirpy_client
156+
157+
fhir = get_fhirpy_client("oh_xxx")
158+
159+
# Call CodeSystem/$lookup directly via fhirpy
160+
params = fhir.execute(
161+
"CodeSystem/$lookup",
162+
method="GET",
163+
params={"system": "http://snomed.info/sct", "code": "44054006"},
164+
)
165+
```
166+
167+
**When to use which**: the Concept Resolver (`client.fhir.resolve`) gives you OMOP-enriched answers - standard concept ID, CDM target table, mapping quality. Use `fhirpy` via `get_fhirpy_client()` when you need raw FHIR responses for FHIR-native tooling.
168+
101169
## Semantic Search
102170

103171
Use natural language queries to find concepts using neural embeddings:

0 commit comments

Comments
 (0)