US Core-Ressourcen von Hand zu erstellen ist mühsam. Sie stempeln meta.profile, schlagen LOINC-Codes nach, schreiben die verschachtelte us-core-race-Extension manuell — jedes Feld ist ein Tippfehler, der darauf wartet zu passieren, jedes Profil ist eine eigene Variante derselben Zeremonie.
@atomic-ehr/codegen lässt diesen Boilerplate verschwinden. Verweisen Sie das Tool auf den US Core IG und Sie erhalten ein Pydantic-Modell pro Basistyp sowie eine einfache Python-Wrapper-Klasse pro Profil — mit typisierten Accessoren für feste Werte, Extensions und Slices sowie einem validate(), das weiß, was das Profil erfordert.
Dieses Tutorial führt Sie von Anfang bis Ende durch zwei US Core Profile: US Core Patient und US Core Blood Pressure.
Was Sie erstellen werden
Einen CSV-zu-FHIR-Konverter, Schritt für Schritt:
- Profil-Klassen für US Core Patient und US Core Blood Pressure aus
hl7.fhir.us.core@8.0.1generieren, - jede Zeile in einen US Core Patient umwandeln — typisierte Extension-Setter und
apply(), - jede Zeile in einen US Core Blood Pressure umwandeln — typisierte Slices, fester LOINC und
validate(), - diese als Bundle paketieren,
- das Bundle mit typisierten Gettern zurücklesen, um einen durchschnittlichen Blutdruckwert zu berechnen,
- das Bundle über den fhirpy-Client an einen lokalen Aidbox-Server senden.
Voraussetzungen
- Node.js 20+ (oder Bun) — der Generator selbst ist das Node-Paket
@atomic-ehr/codegen. Sie führen es einmalig aus, um Python-Code zu erzeugen; danach benötigen Sie Node nicht mehr. Das Generierungsskript umfasst wenige Zeilen TypeScript (siehe unten). - Python 3.12+ — der generierte Code zielt auf modernes Python (PEP 604
X | None-Unions, generische Modelle viatyping_extensions). - Pydantic v2 (
pydantic>=2.11) und fhirpy — generierte Modelle sind Pydantic v2; mit dem Standard-fhirpy-Client lassen sie sich auch in fhirpys asynchronen Client einbinden (Schritt 6). Beide sind in der generiertenrequirements.txtfestgelegt (siehe Schritt 1); übergeben Sieclient: "none"für reines Pydantic ohne Client-Code. - Grundkenntnisse in FHIR und US Core (es genügt zu wissen, was „Profil" und „Slice" bedeuten).
Schritt 1 — Profil-Klassen generieren
Die Code-Generierung läuft über das Node-Tool; richten Sie daher ein kleines Generator-Projekt neben Ihrer Python-Anwendung ein:
mkdir py-us-core-tutorial && cd py-us-core-tutorial
npm init -y
npm install --save-dev @atomic-ehr/codegen tsx typescript
Erstellen Sie generate.ts:
import { APIBuilder, mkCodegenLogger, prettyReport } from "@atomic-ehr/codegen";
const main = async () => {
const logger = mkCodegenLogger({
suppressTags: ["#fieldTypeNotFound", "#duplicateSchema", "#duplicateCanonical", "#largeValueSet"],
});
const builder = new APIBuilder({ logger })
.fromPackage("hl7.fhir.us.core", "8.0.1")
.typeSchema({
treeShake: {
"hl7.fhir.us.core": {
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient": {},
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-blood-pressure": {},
},
"hl7.fhir.r4.core": {
"http://hl7.org/fhir/StructureDefinition/Bundle": {},
},
},
})
.python({
generateProfile: true,
allowExtraFields: false,
primitiveTypeExtension: true,
})
.outputTo("./fhir_types")
.cleanOutput(true);
const report = await builder.generate();
console.log(prettyReport(report));
if (!report.success) process.exit(1);
};
main();
Die relevanten Optionen:
generateProfile: true— erzeugt eine Wrapper-Klasse pro Profil mit typisierten Accessoren für Extensions, Slices und feste Werte. Ohne diese Option erhalten Sie nur die Basis-R4-Pydantic-Modelle.allowExtraFields: false— generierte Modelle verwenden Pydanticsextra="forbid", sodass ein unbekanntes Feld beim Parsen einen Fehler auslöst, anstatt still ignoriert zu werden.primitiveTypeExtension: true— generiert zusätzlich die FHIR-Primitive-Extension-Geschwister (die_field-Begleiter, z. B.birthDateExtension), damit Sie Extensions undids an primitive Werte anhängen können.treeShake: { ... }— nur die aufgeführten Canonicals und ihre transitiven Abhängigkeiten werden generiert (~20 Dateien statt Hunderte).
Führen Sie es aus. prettyReport(report) gibt eine gruppierte Zusammenfassung aus, damit Sie sehen, was erzeugt wurde, ohne das Ausgabeverzeichnis zu durchsuchen:
$ npx tsx generate.ts
# generation logs omitted; this is the prettyReport summary
Generated files (24 files, 12 kloc):
python (23 files, 3.6 kloc):
- fhir_types/ (4 files, 622 loc)
- fhir_types/hl7_fhir_r4_core/ (8 files, 1.1 kloc)
- fhir_types/hl7_fhir_r4_core/profiles/ (2 files, 164 loc)
- fhir_types/hl7_fhir_us_core/profiles/ (9 files, 1.8 kloc)
ir-report (1 files, 8.2 kloc):
- fhir_types/README.md (8223 loc)
Duration: 8097ms
Status: 🟩 Success
Die Verzeichnisstruktur sieht folgendermaßen aus:
fhir_types/
├── hl7_fhir_r4_core/ # Base R4 Pydantic models
│ ├── base.py # Element, Coding, CodeableConcept, Quantity, ...
│ ├── resource.py # Resource, DomainResource, Meta
│ ├── patient.py
│ ├── observation.py
│ ├── bundle.py
│ ├── profiles/ # base R4 profiles US Core builds on
│ │ └── observation_observation_vitalsigns.py # vital-signs base (BP derives from it)
│ └── ...
├── hl7_fhir_us_core/
│ └── profiles/
│ ├── __init__.py # re-exports the profile classes
│ ├── patient_uscore_patient_profile.py
│ ├── observation_uscore_blood_pressure_profile.py
│ ├── extension_uscore_race_extension.py
│ └── ...
├── fhirpy_base_model.py # fhirpy client base model (default fhirpy client)
├── profile_helpers.py # Runtime helpers shared by all profile classes
├── README.md # IR report — human-readable dump of the generated types
└── requirements.txt # pydantic, fhirpy (+ pytest, requests for tests/Step 6)
Python-Virtualumgebung einrichten
python3.14 -m venv venv
source venv/bin/activate
Verweisen Sie Ihre Python-Anwendung auf das erzeugte fhir_types/-Verzeichnis und installieren Sie die Abhängigkeiten:
pip install -r fhir_types/requirements.txt
Die generierte requirements.txt legt Pydantic und fhirpy sowie pytest und requests für Tests und Beispiele fest.
Der vollständige Tutorial-Code befindet sich in Aidbox/examples — generate.ts, load.py, avg.py, post.py, die CSV sowie das committete fhir_types/-Verzeichnis, damit Sie den generierten Code einsehen können, ohne den Generator ausführen zu müssen. Zur umfassenderen Erkundung der Profil-API enthält das Codegen-Repository auch ein python-r4-us-core-Testbeispiel. Beide verwenden standardmäßig camelCase-Attributnamen, genau wie die Code-Schnipsel hier. (Übergeben Sie fieldFormat: "snake_case", wenn Sie Attribute lieber als birth_date, effective_date_time schreiben möchten; die Serialisierung gibt in jedem Fall FHIR-konformes camelCase-JSON aus.)
Schritt 2 — Zeile zu einem US Core Patient
Die Eingabe ist patients.csv — grundlegende demografische Daten plus eine Blutdruckmessung pro Patient. Die Ethnizität verwendet die OMB-Kategorie-Codes, die US Core erwartet:
mrn,family,given,birthDate,gender,raceCode,raceDisplay,effectiveDateTime,systolic,diastolic
MRN-001,Lovelace,Ada,1815-12-10,female,2106-3,White,2026-04-15,120,80
MRN-002,Turing,Alan,1912-06-23,male,2106-3,White,2026-04-15,118,76
MRN-003,Curie,Marie,1867-11-07,female,2106-3,White,2026-04-16,125,82
MRN-004,Carver,George,1864-01-01,male,2054-5,Black or African American,2026-04-16,135,88
MRN-005,Ochoa,Ellen,1958-05-10,female,2054-5,Black or African American,2026-04-17,128,84
csv.DictReader übergibt jede Zeile als einfaches dict[str, str]; die numerische Umwandlung erfolgt später, wenn Werte an typisierte Profil-Setter übergeben werden.
Das US Core Patient-Profil fügt einige Extensions hinzu und macht identifier und name zu Pflichtfeldern. Die generierte Klasse verfügt über einen typisierten Setter für jedes davon:
from fhir_types.hl7_fhir_r4_core.base import Identifier, HumanName, Coding
from fhir_types.hl7_fhir_r4_core.patient import Patient
from fhir_types.hl7_fhir_us_core.profiles import UscorePatientProfile
def row_to_patient(row: dict[str, str]) -> UscorePatientProfile:
base_patient = Patient(
resourceType="Patient",
identifier=[Identifier(system="http://hospital.example.org/mrn", value=row["mrn"])],
name=[HumanName(family=row["family"], given=[row["given"]])],
gender=row["gender"], # gender is a Literal type — Pydantic validates the value
birthDate=row["birthDate"], # default camelCase attrs match the FHIR wire names
)
patient = UscorePatientProfile.apply(base_patient)
patient.set_race({
"ombCategory": {"system": "urn:oid:2.16.840.1.113883.6.238", "code": row["raceCode"], "display": row["raceDisplay"]},
"text": row["raceDisplay"],
})
return patient
Zwei Phasen:
- Den einfachen
Patientaufbauen — profilpflichtige (identifier,name) und must-support-Felder (gender,birthDate) als typisiertes R4-Pydantic-Modell. Konstruieren Sie mit den standardmäßigen camelCase-Attributnamen (birthDate, die FHIR-Wire-Namen); Werte werden sofort validiert (z. B. istgendereinLiteral["male", "female", "other", "unknown"]). - Dann stampelt
UscorePatientProfile.apply(base_patient)meta.profileund gibt eine Profil-Instanz mit typisierten Accessoren für die US Core Extensions zurück.apply()umschließt die Ressource an Ort und Stelle — das Profil mutiert dasselbePatient-Objekt.
Drei Hinweise dazu, was die Profil-API für Sie erledigt:
- Drei Setter-Formen für Extensions.
set_race({ "ombCategory": ..., "text": ... })akzeptiert flache Eingabe — beachten Sie, dass die Sub-Extension-Schlüssel (ombCategory,detailed,text) die camelCase-Slice-Namen sind — und erzeugt die verschachtelteextension[]-Struktur. Derselbe Setter akzeptiert auch eine typisierte Extension-Profil-Instanz (UscoreRaceExtension) oder eine roheExtensionund löst einen Fehler aus, wenn dieurleiner rohen Extension nicht übereinstimmt. - Einwertige Extensions erhalten den Wert direkt.
us-core-individual-sexträgt ein einzelnesvalueCoding, daher nimmtset_sex(Coding(code="female"))eineCoding-Instanz (oder eine roheExtension) entgegen. - Keine Setter für must-support-Basisfelder.
gender,birthDateundaddresswerden von US Core nicht weiter profiliert, daher erzeugt die Profil-Klasse keine.set_gender()-artigen Wrapper — befüllen Sie diese als normalePatient-Felder.validate()gibt dennoch eine Warnung aus, wenn ein must-support-Feld fehlt.
Pydantic gibt eine
UserWarningaus, wenn eineextension[]-Liste einfache Dicts stattExtension-Instanzen enthält — dies ist beim aktuellen Flat-Dict-Mechanismus zu erwarten. Unterdrücken Sie sie mitwarnings.filterwarnings("ignore", category=UserWarning, module="pydantic").
Schritt 3 — Zeile zu einem US Core Blood Pressure
Das BP-Profil ist der Bereich, in dem Codegen seinen größten Nutzen entfaltet. Das US Core Blood Pressure-Profil:
- legt
codeauf LOINC 85354-9 fest („Blood pressure panel"), - legt einen
vital-signs-Kategorie-Slice fest, - definiert
component[systolic]- undcomponent[diastolic]-Slices mit spezifischen LOINC-Diskriminatoren (8480-6 und 8462-4), - erfordert ein
effectiveDateTimeodereffectivePeriod, - erfordert
valueQuantityinnerhalb jedes Slices.
Das manuell pro Zeile zu schreiben ist genau die Art von Aufgabe, die Codegen eliminiert. Die generierte Klasse reduziert es auf drei Setter:
from fhir_types.hl7_fhir_r4_core.base import Reference
from fhir_types.hl7_fhir_us_core.profiles import UscoreBloodPressureProfile
def row_to_bp(row: dict[str, str], patient_urn: str) -> UscoreBloodPressureProfile:
bp = UscoreBloodPressureProfile.create(
status="final",
subject=Reference(reference=patient_urn),
)
(
bp.set_effective_date_time(row["effectiveDateTime"])
.set_systolic({"value": float(row["systolic"]), "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]"})
.set_diastolic({"value": float(row["diastolic"]), "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]"})
)
errors = bp.validate()["errors"]
if errors:
raise ValueError(f"{row['mrn']}: {'; '.join(errors)}")
return bp
Was im Hintergrund geschieht:
create()erledigt die Zeremonie. Es stempeltmeta.profile, füllt den festencode(LOINC 85354-9), hängt den vital-signs-Kategorie-Slice an und fügt leerecomponent[systolic]- /component[diastolic]-Stubs mit bereits gesetzten Diskriminator-Codes hinzu.create()nimmt nur Schlüsselwort-Argumente entgegen;create_resource()ist identisch, gibt aber ein einfachesObservation-Objekt statt eines Profil-Wrappers zurück.set_systolic({ "value": ..., "unit": ... })füllt dievalueQuantityim systolischen Slice. Der Diskriminator-codefür diese Komponente ist bereits durchcreate()gesetzt — Sie liefern nur den Messwert.validate()gibt{"errors": [...], "warnings": [...]}zurück. Fehler blockieren (Pflichtfelder, ausgeschlossene Felder, unzulässige Choice-Varianten, Slice-Kardinalität). Warnungen weisen auf must-support-Probleme hin. Eine fehlerhafte Zeile schlägt sofort mit der MRN fehl — Sie entdecken es nicht erst beim POST.
Sie haben die Diskriminator-Codes nicht eingegeben. Sie mussten sich 85354-9 nicht merken. Die Setter lassen sich fließend verketten (jeder gibt das Profil zurück), genau wie die TypeScript-API.
Schritt 4 — Das Bundle zusammenstellen
Jede Zeile erzeugt einen Patient und eine BP-Observation, die über den urn:uuid-Platzhalter des Patienten verknüpft sind. Paketieren Sie diese als Transaktions-Einträge. Die generierte Bundle- und BundleEntry-Klasse ist generisch über die enthaltene Ressource, sodass ein Bundle[Patient | Observation] entry[].resource auf diese Union typisiert hält. (row_to_patient und row_to_bp sind die Funktionen aus Schritt 2 und 3; im Beispiel befinden sie sich alle in einer load.py und sind daher bereits im Geltungsbereich.)
import json
import csv
import uuid
from fhir_types.hl7_fhir_r4_core.patient import Patient
from fhir_types.hl7_fhir_r4_core.observation import Observation
from fhir_types.hl7_fhir_r4_core.bundle import Bundle, BundleEntry, BundleEntryRequest
def row_to_entries(row: dict[str, str]) -> list[BundleEntry[Patient | Observation]]:
patient_urn = f"urn:uuid:{uuid.uuid4()}"
patient = row_to_patient(row)
bp = row_to_bp(row, patient_urn)
return [
BundleEntry(fullUrl=patient_urn, resource=patient.to_resource(),
request=BundleEntryRequest(method="POST", url="Patient")),
BundleEntry(fullUrl=f"urn:uuid:{uuid.uuid4()}", resource=bp.to_resource(),
request=BundleEntryRequest(method="POST", url="Observation")),
]
rows = list(csv.DictReader(open("patients.csv")))
print(f"Loaded {len(rows)} rows")
entries = [entry for row in rows for entry in row_to_entries(row)]
bundle = Bundle[Patient | Observation](
resourceType="Bundle",
type="transaction",
entry=entries,
)
with open("bundle.json", "w") as f:
json.dump(bundle.model_dump(by_alias=True, exclude_none=True), f, indent=2)
print(f"Wrote bundle with {len(entries)} entries")
$ python load.py
Loaded 5 rows
Wrote bundle with 10 entries
Wichtige Hinweise:
to_resource()liefert das einfache Modell — die zugrunde liegende Pydantic-Ressource ohne Wrapper, bereit zur Verwendung in einemBundleEntry.model_dump(by_alias=True, exclude_none=True)erzeugt FHIR-JSON —by_aliasserialisiert über die FHIR-Wire-Aliase (sodass ein snake_case-Build dennocheffectiveDateTimeausgibt) undexclude_noneentferntNone-wertige Felder. Dies ist der eine Serialisierungsaufruf, den Sie überall verwenden werden.urn:uuid-Referenzen. DiefullUrldes Patienten und diesubject.referenceder Observation teilen sich eine UUID; der Server löst diese beim Commit in eine echte ID auf.
Schritt 5 — Rücklesen: Durchschnittlicher Blutdruck aus dem Bundle
Lesen Sie es nun zurück. Parsen Sie bundle.json und berechnen Sie den durchschnittlichen systolischen/diastolischen Wert, um die Lese-API zu erproben:
import json
from typing import Any
from fhir_types.hl7_fhir_r4_core.observation import Observation
from fhir_types.hl7_fhir_us_core.profiles import UscoreBloodPressureProfile
bundle = json.load(open("bundle.json"))
def is_us_core_bp(resource: dict[str, Any]) -> bool:
return (
resource.get("resourceType") == "Observation"
and UscoreBloodPressureProfile.canonical_url in (resource.get("meta", {}).get("profile") or [])
)
bps = [
UscoreBloodPressureProfile.from_resource(Observation.model_validate(entry["resource"]))
for entry in bundle.get("entry", [])
if is_us_core_bp(entry["resource"])
]
def avg(xs: list[float]) -> float:
return sum(xs) / len(xs)
# get_systolic()/get_diastolic() are Optional, so guard with a walrus before indexing.
systolic = [s["value"] for bp in bps if (s := bp.get_systolic()) is not None]
diastolic = [d["value"] for bp in bps if (d := bp.get_diastolic()) is not None]
print(f"Avg BP: {avg(systolic):.1f}/{avg(diastolic):.1f} mmHg (n={len(bps)})")
$ python avg.py
Avg BP: 125.2/82.0 mmHg (n=5)
Drei Dinge, die das Profil hier leistet:
from_resource(obs)validiert beim Umschließen. Es prüft, obmeta.profiledie kanonische URL enthält, und gibt eine Profil-Instanz zurück; ist eine Ressource, die das Profil beansprucht, fehlerhaft, wird eine Ausnahme ausgelöst — ein defektes Bundle schlägt also beim Lesen fehl, nicht erst beim nächsten Feldzugriff.- Kein eingebauter Type-Guard. Anders als das TypeScript-API-Prädikat
is()liefern die Python-Klassen keinen.filter()-artigen Guard. Sie wählen Kandidaten selbst aus — prüfen SieresourceTypeundcanonical_url in meta.profile(siehe oben) oder umhüllen Siefrom_resource()mittry/except ValueError. In beiden Fällen istcanonical_urlals Klassenattribut genau für diesen Zweck verfügbar. get_systolic()/get_diastolic()geben den flachen Slice-Wert zurück. Kein manuelles Traversieren voncomponent[].code.coding[].code, um LOINC-Codes abzugleichen — das Profil weiß bereits, welcher Slice welcher ist, und liefert Ihnen dieQuantity-Daten als einfaches Dict.
Das ist der vollständige Kreislauf: CSV → typisierte Profile → validiertes Bundle → typisiertes Rücklesen mit profilbewussten Gettern. Dieselben wenigen Zeilen würden BPs verarbeiten, die von einem FHIR-Server abgerufen, aus einer Datei geladen oder über eine Subscription empfangen wurden — das typisierte Profil ist die gemeinsame Form, unabhängig von der Quelle.
Schritt 6 — Das Bundle auf einem FHIR-Server ablegen
Die typisierte Pipeline ist nur die halbe Geschichte. Um die Transaktion tatsächlich zu committen — Patienten-IDs vergeben, urn:uuid-Referenzen umgeschrieben, Ressourcen gespeichert und durchsuchbar —, benötigen Sie einen FHIR-Server. Starten Sie Aidbox:
curl -JO https://aidbox.app/runme && docker compose up -d
Öffnen Sie http://localhost:8080 in Ihrem Browser, um eine kostenlose Entwicklerlizenz zu erhalten, und lesen Sie dann das Root-Client-Secret aus docker-compose.yaml in eine Umgebungsvariable — diese wird von den curl-Aufrufen und dem Python-Skript unten wiederverwendet:
export BOX_ROOT_CLIENT_SECRET=$(awk '/BOX_ROOT_CLIENT_SECRET:/{print $2}' docker-compose.yaml)
Prüfen Sie, ob der FHIR-Endpunkt verfügbar ist:
curl -u "root:$BOX_ROOT_CLIENT_SECRET" http://localhost:8080/fhir/metadata
Sie sollten ein JSON-CapabilityStatement erhalten.
Senden Sie die soeben erstellte bundle.json mit dem asynchronen fhirpy-Client:
import asyncio
import base64
import json
import os
from fhirpy import AsyncFHIRClient
from fhir_types.hl7_fhir_r4_core import Bundle
secret = os.environ["BOX_ROOT_CLIENT_SECRET"] # exported above
auth = base64.b64encode(f"root:{secret}".encode()).decode()
async def main() -> None:
client = AsyncFHIRClient("http://localhost:8080/fhir", authorization=f"Basic {auth}")
bundle = json.load(open("bundle.json"))
resp: Bundle = await client.execute("/", method="post", data=bundle)
if resp.entry is None:
return
for entry in resp.entry:
if entry.response is None:
continue
print(entry.response.status, entry.response.location)
asyncio.run(main())
Aidbox gibt ein transaction-response-Bundle zurück — einen Eintrag pro Eingabe, jeder mit 201 Created und einem location, der auf die gespeicherte Ressource verweist:
$ python post.py
201 Created Patient/<id>/_history/1
201 Created Observation/<id>/_history/1
...
Fragen Sie eine Observation zurück und betrachten Sie deren subject:
$ curl -u "root:$BOX_ROOT_CLIENT_SECRET" \
"http://localhost:8080/fhir/Observation?code=http://loinc.org|85354-9" \
| jq '.entry[].resource.subject.reference'
"Patient/01J..."
"Patient/01J..."
Kein urn:uuid — Aidbox hat die Platzhalter beim Commit atomisch umgeschrieben.
Die Pipeline typprüfen
Die generierten Modelle sind Pydantic v2, sodass der Konverter mit mypy typgeprüft werden kann — bereits in der generierten requirements.txt festgelegt. Eine Anforderung: Aktivieren Sie das Pydantic mypy-Plugin (es wird mit Pydantic mitgeliefert, keine zusätzliche Installation nötig), sonst kann mypy nicht erkennen, dass ein Field(None, ...)-Standard ein Feld optional macht, und überschwemmt Sie bei jedem konstruierten Modell mit falschen „missing argument"-Fehlern.
Legen Sie eine mypy.ini neben Ihren Code:
[mypy]
strict = True
plugins = pydantic.mypy
Führen Sie es dann aus:
$ mypy .
Success: no issues found in 35 source files
Sowohl Ihre Konverter-Module — load.py, avg.py, post.py — als auch das generierte fhir_types/-Paket werden ohne Beanstandungen geprüft. Die typisierten Factories, to_resource() und das generische Bundle[Patient | Observation] bestehen allesamt die Prüfung, sodass ein falscher Feldtyp oder ein fehlendes Pflichtargument erkannt wird, bevor Sie den Server jemals erreichen. Die generierte Profil-Schicht besteht die Typprüfung auch unter vollem --strict — der eigentliche Grund, warum mypy.ini aus nur diesen zwei Zeilen besteht: kein disable_error_code, kein strict_optional = False, nichts manuell innerhalb von fhir_types/ bearbeitet. Das Einzige, was Ihr eigener Code beiträgt, ist eine gewöhnliche None-Prüfung beim Lesen eines optionalen Felds — das Walrus-Operator-Muster in avg.py oben oder if entries is None: ... vor dem Indexieren einer Liste. Das ist normales strict-mode-Python, keine Eigenheit des Generators.
Wie es weitergeht
- Mehr zur Profil-API. Weitere Factories, Getter und Slice-/Extension-Formen werden in den Codegen-Beispieltests demonstriert:
test_profile_patient.py,test_profile_bp.py,test_profile_bodyweight.pyundtest_profile_typed_bundle.py. - Typisierte Bundles mit benannten Eintrags-Slices. Ein profiliertes Bundle generiert Setter/Getter pro Slice (
set_patient_entry,get_organization_entry), wobei einfache und unbegrenzte Kardinalität (max: *) automatisch berücksichtigt wird — siehe den oben erwähnten typed-bundle-Test. - Die Ausgabe an Ihre Codebasis anpassen.
fieldFormat(snake_case/camelCase),client("fhirpy"/"none"),allowExtraFieldsundprimitiveTypeExtensionsind allesamt Optionen für.python({ ... }). - Profile aus mehreren Paketen kombinieren.
APIBuilder.fromPackage()lässt sich verketten — US Core zusammen mit Ihrem eigenen IG oder einem regionalen Basispaket.localStructureDefinitions()lädt Profile direkt aus einem Ordner mitStructureDefinition-JSON-Dateien.
Fazit
Der Generator erzeugt sowohl die Basis-R4-Pydantic-Modelle als auch eine schlanke Profil-Klassen-Schicht darüber — keine Runtime-DSL, kein ORM, kein Framework. to_resource() liefert immer eine einfache Pydantic-Ressource, und model_dump(by_alias=True, exclude_none=True) liefert immer einfaches FHIR-JSON, das Sie an jeden Server senden können.
@atomic-ehr/codegen ist MIT-lizenziert; Issues und Pull Requests sind willkommen.
GitHub | NPM | US Core IG




