oehrpy Integration¶
This page describes how Open CIS integrates oehrpy to replace raw HTTP calls with type-safe SDK usage. See PRD-0005 for the full integration plan.
Before and After¶
Before: Manual FLAT Construction¶
# Fragile: path strings can have typos, no validation
flat_data = {
"ctx/language": "en",
"ctx/territory": "US",
"ctx/composer_name": composer,
"vital_signs_observations/vital_signs/blood_pressure/systolic|magnitude": systolic,
"vital_signs_observations/vital_signs/blood_pressure/systolic|unit": "mm[Hg]",
"vital_signs_observations/vital_signs/blood_pressure/diastolic|magnitude": diastolic,
"vital_signs_observations/vital_signs/blood_pressure/diastolic|unit": "mm[Hg]",
# ... more paths prone to typos
}
# Raw HTTP call
async with httpx.AsyncClient() as client:
response = await client.post(
f"{EHRBASE_URL}/ehr/{ehr_id}/composition",
json=flat_data,
headers={"Content-Type": "application/openehr.flat+json"},
)
After: oehrpy VitalSignsBuilder¶
from openehr_sdk.templates import VitalSignsBuilder
from openehr_sdk.client import EHRBaseClient
# Type-safe builder with IDE autocomplete
builder = VitalSignsBuilder(
composer_name=composer,
start_time=recorded_at,
)
builder.add_blood_pressure(systolic=systolic, diastolic=diastolic)
builder.add_pulse(rate=pulse)
# Validated FLAT output
flat_data = builder.build()
# Async client handles auth, headers, errors
async with EHRBaseClient(...) as client:
uid = await client.create_composition(
ehr_id=ehr_id,
template_id=VitalSignsBuilder.template_id,
composition=flat_data,
format="flat",
)
Architecture¶
graph TB
subgraph Services["Open CIS Service Layer"]
VS[vital_signs.py]
PS[patients.py]
end
subgraph oehrpy["oehrpy SDK"]
Builder[VitalSignsBuilder]
Client[EHRBaseClient]
RM[RM Classes - 134 Pydantic models]
end
subgraph EHRBase
REST[REST API]
end
VS --> Builder
VS --> Client
PS --> Client
Builder --> RM
Client -->|HTTP| REST
What oehrpy Replaces¶
| Component | Before (raw httpx) | After (oehrpy) |
|---|---|---|
| EHR operations | Manual HTTP calls in ehrbase/client.py |
EHRBaseClient.create_ehr(), .get_ehr() |
| Composition creation | Manual FLAT dict construction | VitalSignsBuilder with type-safe API |
| AQL queries | Raw query strings via HTTP POST | EHRBaseClient.execute_aql() |
| Template management | Manual HTTP calls | EHRBaseClient.list_templates(), .upload_template() |
| Validation | None until EHRBase HTTP 422 | Pydantic validation at build time |
Dependency Injection¶
oehrpy integrates with FastAPI's dependency injection:
from openehr_sdk.client import EHRBaseClient
from fastapi import Depends
async def get_ehrbase_client():
async with EHRBaseClient(
base_url=settings.ehrbase_url,
username=settings.ehrbase_user,
password=settings.ehrbase_password,
) as client:
yield client
@router.post("/vital-signs")
async def create_vital_signs(
data: VitalSignsCreate,
client: EHRBaseClient = Depends(get_ehrbase_client),
):
...
Benefits¶
- Type safety -- no more path string typos, full IDE autocomplete
- Validation -- catch errors before sending to EHRBase
- Less code -- ~150 lines of integration code reduced to ~50
- Testability -- mock
EHRBaseClientin tests instead of HTTP responses - Consistency -- single client handles auth, headers, error handling