Skip to content

Commit e6b36a3

Browse files
committed
Update README with features checklist and profile examples
1 parent eb6cf28 commit e6b36a3

3 files changed

Lines changed: 92 additions & 133 deletions

File tree

CLAUDE.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,6 @@ Located in `src/api/writer-generator/`:
267267
4. Use `build()` instead of `generate()` for testing
268268
5. Run `bun run quality` before committing (combines typecheck, lint, test:unit)
269269

270-
## Roadmap Context
271-
272-
This toolkit focuses on type generation and code generation:
273-
- **Current**: TypeScript, Python, C# interface/class generation from FHIR R4
274-
- **In Progress**: R5 support, profile/extension enhancements
275-
- **Planned**: Rust, GraphQL, OpenAPI, validation functions, mock data generation
276-
277270
## Useful External Resources
278271

279272
- [FHIR Specification](https://www.hl7.org/fhir/)

README.md

Lines changed: 63 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
- [Generation](#generation)
2424
- [1. Writer-Based Generation (Programmatic)](#1-writer-based-generation-programmatic)
2525
- [2. Mustache Template-Based Generation (Declarative)](#2-mustache-template-based-generation-declarative)
26-
- [Roadmap](#roadmap)
2726
- [Support](#support)
2827
- [Footnotes](#footnotes)
2928

@@ -41,14 +40,32 @@ Guides:
4140

4241
## Features
4342

44-
- 🚀 **High-Performance** - Built with Bun runtime for blazing-fast generation
45-
- 🔧 **Extensible Architecture** - Three-stage pipeline:
46-
- FHIR package management & canonical resolution
47-
- Optimized intermediate FHIR data entities representation via Type Schema
48-
- Generation for different programming languages
49-
- 📦 **Multi-Package Support** - Generate from a list of FHIR packages
50-
- 🎯 **Type-Safe** - Generates fully typed interfaces with proper inheritance
51-
- 🛠️ **Developer Friendly** - Fluent API
43+
- [x] **Multi-Package Support** — Load packages from the [FHIR registry](examples/typescript-r4/), [remote TGZ files](examples/typescript-sql-on-fhir/), or a [local folder with custom StructureDefinitions](examples/local-package-folder/)
44+
- Tested with hl7.fhir.r4.core, US Core, C-CDA, SQL on FHIR, etc.
45+
- [x] **Resources & Complex Types** — Generates typed definitions with proper inheritance
46+
- [x] **Value Set Bindings** — Strongly-typed enums from FHIR terminology bindings
47+
- [x] **Profiles & Extensions** — Factory methods with auto-populated fixed values and required slices ([R4 profiles](examples/typescript-r4/profile-bp.test.ts), [US Core](examples/typescript-us-core/))
48+
- Extensions — flat typed accessors (e.g. `setRace()` on US Core Patient), [standalone extension profiles](examples/typescript-r4/extension-profile.test.ts)
49+
- Slicing — typed get/set accessors with discriminator matching
50+
- Validation — runtime `validate()` for required fields, fixed values, slice cardinality, enums, references
51+
- [x] **Extensible Architecture** — Three-stage pipeline: FHIR packages → [TypeSchema](https://www.health-samurai.io/articles/type-schema-a-pragmatic-approach-to-build-fhir-sdk) IR → code generation
52+
- TypeSchema is a universal intermediate representation — add a new language by writing only the final generation stage
53+
- Built-in generators: TypeScript, Python/Pydantic, C#, and Mustache templates
54+
- [x] **TypeSchema Transformations**:
55+
- [x] Tree Shaking — include only the resources and fields you need; automatically resolves dependencies
56+
- [x] Logical Model Promotion — promote FHIR logical models (e.g. CDA ClinicalDocument) to first-class resources
57+
- [ ] Renaming — custom naming conventions for generated types, fields, packages, etc.
58+
- [ ] **Search Builders** — type-safe FHIR search query construction
59+
- [ ] **Operation Generation** — type-safe FHIR operation calls
60+
61+
| Feature | TypeSchema | TypeScript | Python | C# | Mustache |
62+
|---|---|---|---|---|---|
63+
| Resources & Complex Types | yes | yes | yes | yes | template |
64+
| Value Set Bindings | yes | inline | inline | enum | template |
65+
| Profiles & Extensions | yes | yes | no | no | no |
66+
| Tree Shaking | yes |||||
67+
| Logical Model Promotion | yes |||||
68+
5269

5370
## Versions & Release Cycle
5471

@@ -298,107 +315,61 @@ Templates enable flexible code generation for any language or format (Go, Rust,
298315

299316
### Profile Classes
300317

301-
When generating TypeScript with `generateProfile: true`, the generator creates profile wrapper classes that provide a fluent API for working with FHIR profiles (US Core, etc.). These classes handle complex profile constraints like slicing and extensions automatically.
318+
When generating TypeScript with `generateProfile: true`, the generator creates profile wrapper classes that provide a fluent API for working with FHIR profiles. These classes handle complex profile constraints like slicing and extensions automatically.
302319

303320
```typescript
304-
import { Patient } from "./fhir-types/hl7-fhir-r4-core/Patient";
305-
import { USCorePatientProfileProfile } from "./fhir-types/hl7-fhir-us-core/profiles/UscorePatientProfile";
306-
307-
// Wrap a FHIR resource with a profile class
308-
const resource: Patient = { resourceType: "Patient" };
309-
const profile = new USCorePatientProfileProfile(resource);
321+
import { observation_bpProfile as bpProfile } from "./profiles/Observation_observation_bp";
310322

311-
// Set extensions using flat API - complex extensions are simplified
312-
profile.setRace({
313-
ombCategory: { system: "urn:oid:2.16.840.1.113883.6.238", code: "2106-3", display: "White" },
314-
text: "White",
323+
// create() auto-sets fixed values (code, meta.profile) and required slice stubs
324+
const bp = bpProfile.create({
325+
status: "final",
326+
subject: { reference: "Patient/pt-1" },
315327
});
316328

317-
// Set simple extensions directly
318-
profile.setSex({ system: "http://hl7.org/fhir/administrative-gender", code: "male" });
319-
320-
// Get extension values - flat API returns simplified object
321-
const race = profile.getRace();
322-
console.log(race?.ombCategory?.display); // "White"
329+
// Slice setters — discriminator values (LOINC codes) applied automatically
330+
// Single-variant choice types (value[x] → valueQuantity) are flattened:
331+
bp.setVSCat({ text: "Vital Signs" })
332+
.setSystolicBP({ value: 120, unit: "mmHg" })
333+
.setDiastolicBP({ value: 80, unit: "mmHg" })
334+
.setEffectiveDateTime("2024-06-15");
323335

324-
// Get raw FHIR Extension when needed
325-
const raceExtension = profile.getRaceExtension();
326-
console.log(raceExtension?.url); // "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"
336+
bp.validate(); // [] — valid
327337

328-
// Get the underlying resource
329-
const patientResource = profile.toResource();
338+
// Get plain FHIR JSON — ready for API calls, storage, etc.
339+
const obs = bp.toResource();
340+
// obs.component[0].valueQuantity.value === 120
341+
// obs.component[0].code.coding[0].code === "8480-6"
330342
```
331343
332-
**Slicing Support:**
333-
334-
Profile classes also handle FHIR slicing, automatically applying discriminator values:
344+
**Slicing & Choice Type Flattening:**
335345
336346
```typescript
337-
import { Observation } from "./fhir-types/hl7-fhir-r4-core/Observation";
338-
import { USCoreBloodPressureProfileProfile } from "./fhir-types/hl7-fhir-us-core/profiles/UscoreBloodPressureProfile";
339-
340-
const obs: Observation = { resourceType: "Observation", status: "final", code: {} };
341-
const bp = new USCoreBloodPressureProfileProfile(obs);
347+
// Simplified getter — discriminator stripped, choice type flattened
348+
bp.getSystolicBP(); // { value: 120, unit: "mmHg" }
342349

343-
// Set slices - discriminator is applied automatically
344-
// No input needed when all fields are part of the discriminator
345-
bp.setSystolic({ valueQuantity: { value: 120, unit: "mmHg" } });
346-
bp.setDiastolic({ valueQuantity: { value: 80, unit: "mmHg" } });
347-
348-
// Get simplified slice (without discriminator fields)
349-
const systolic = bp.getSystolic();
350-
console.log(systolic?.valueQuantity?.value); // 120
351-
352-
// Get raw slice (includes discriminator)
353-
const systolicRaw = bp.getSystolicRaw();
354-
console.log(systolicRaw?.code?.coding?.[0]?.code); // "8480-6" (LOINC code for systolic BP)
350+
// Raw getter — full FHIR element including discriminator values
351+
bp.getSystolicBPRaw(); // { code: { coding: [...] }, valueQuantity: { value: 120, ... } }
355352
```
356353
357-
See [examples/typescript-us-core/](examples/typescript-us-core/) for complete profile usage examples.
358-
359-
## Roadmap
360-
361-
- [x] TypeScript generation
362-
- [x] FHIR R4 core package support
363-
- [x] Configuration file support
364-
- [x] Comprehensive test suite (72+ tests)
365-
- [x] **Value Set Generation** - Strongly-typed enums from FHIR bindings
366-
- [x] **Profile & Extension Support** - Basic parsing (US Core in development)
367-
- [ ] **Complete Multi-Package Support** - Custom packages and dependencies
368-
- [ ] **Smart Chained Search** - Intelligent search builders
354+
**Wrapping Existing Resources:**
369355
370-
```typescript
371-
// Intelligent search builders
372-
const results = await client.Patient
373-
.search()
374-
.name().contains('Smith')
375-
.birthdate().greaterThan('2000-01-01')
376-
.address().city().equals('Boston')
377-
.include('Patient:organization')
378-
.sort('birthdate', 'desc')
379-
.execute();
380-
```
356+
```typescript
357+
// Wrap any resource to read slices
358+
const bp2 = bpProfile.from(existingObservation);
359+
bp2.getSystolicBP(); // { value: 120, unit: "mmHg" }
360+
bp2.getVSCat(); // { text: "Vital Signs" }
361+
bp2.getEffectiveDateTime(); // "2024-06-15"
362+
```
381363
382-
- [ ] **Operation Generation** - Type-safe FHIR operations
364+
**Validation:**
383365
384-
```typescript
385-
// Type-safe FHIR operations
386-
const result = await client.Patient
387-
.operation('$match')
388-
.withParameters({
389-
resource: patient,
390-
onlyCertainMatches: true
391-
})
392-
.execute();
393-
```
366+
```typescript
367+
const errors = bp.validate();
368+
// [] — empty means valid
369+
// ["effective: at least one of effectiveDateTime, effectivePeriod is required"]
370+
```
394371
395-
- [x] **Python generation**
396-
- [x] **C# generation**
397-
- [ ] **Rust generation**
398-
- [ ] **GraphQL schema generation**
399-
- [ ] **OpenAPI specification generation**
400-
- [ ] **Validation functions**
401-
- [ ] **Mock data generation**
372+
See [examples/typescript-r4/](examples/typescript-r4/) for R4 profile tests and [examples/typescript-us-core/](examples/typescript-us-core/) for US Core profile examples.
402373
403374
## Support
404375
Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,55 @@
1-
# TypeScript Profile Classes in Atomic EHR Codegen
1+
# Profile codegen for TypeScript
22

3-
We've added FHIR profile support to the TypeScript generator in [Atomic EHR Codegen](https://github.com/atomic-ehr/codegen). Profiles generate as wrapper classes with typed accessors, automatic slice handling, and runtime validation -- all on top of plain FHIR JSON.
4-
5-
## Quick look
3+
[@atomic-ehr/codegen](https://github.com/atomic-ehr/codegen) is an open-source toolkit that generates strongly-typed code from FHIR packages. We've added **FHIR profile support** for TypeScript (dev preview) -- propagation of fixed values, slice & extension getters/setters, and primitive client-side validation:
64

75
```typescript
86
import { observation_bpProfile } from "./profiles/Observation_observation_bp";
97

8+
// create() auto-sets fixed values and required slice stubs
109
const bp = observation_bpProfile.create({
1110
status: "final",
1211
subject: { reference: "Patient/pt-1" },
1312
});
1413

15-
// Slice setters -- discriminator values (LOINC codes) applied automatically
16-
// Single-variant choice types (value[x] → valueQuantity) are flattened:
14+
// Slice setters -- discriminator values applied automatically
15+
// Choice types constrained to a single variant are flattened (value[x] → Quantity fields):
1716
bp.setVSCat({ text: "Vital Signs" })
1817
.setSystolicBP({ value: 120, unit: "mmHg" })
1918
.setDiastolicBP({ value: 80, unit: "mmHg" })
2019
.setEffectiveDateTime("2024-06-15");
2120

22-
// Validate against profile constraints
2321
bp.validate(); // [] -- valid
2422

25-
// Get plain FHIR JSON -- ready for API calls, storage, etc.
23+
// Plain FHIR JSON -- ready for API calls, storage, etc.
2624
const obs = bp.toResource();
27-
// {
28-
// resourceType: "Observation",
29-
// meta: { profile: ["http://hl7.org/fhir/StructureDefinition/bp"] },
30-
// status: "final",
31-
// code: { coding: [{ code: "85354-9", system: "http://loinc.org" }] },
32-
// category: [{ coding: [{ code: "vital-signs", system: "http://...observation-category" }] }],
33-
// subject: { reference: "Patient/pt-1" },
34-
// effectiveDateTime: "2024-06-15",
35-
// component: [
36-
// { code: { coding: [{ code: "8480-6", system: "http://loinc.org" }] }, valueQuantity: { value: 120, unit: "mmHg" } },
37-
// { code: { coding: [{ code: "8462-4", system: "http://loinc.org" }] }, valueQuantity: { value: 80, unit: "mmHg" } },
38-
// ],
39-
// }
40-
41-
// Wrap any Observation back into a profile to read slices
42-
const bp2 = observation_bpProfile.from(obs);
43-
44-
bp2.getSystolicBP(); // { value: 120, unit: "mmHg" } -- flattened from valueQuantity
45-
bp2.getDiastolicBP(); // { value: 80, unit: "mmHg" }
46-
bp2.getVSCat(); // { text: "Vital Signs" }
25+
```
26+
27+
Wrapping an existing resource to read slices back:
28+
29+
```typescript
30+
const bp2 = observation_bpProfile.from(existingObservation);
31+
32+
bp2.getSystolicBP(); // { value: 120, unit: "mmHg" } -- flattened
33+
bp2.getDiastolicBP(); // { value: 80, unit: "mmHg" }
34+
bp2.getVSCat(); // { text: "Vital Signs" }
4735
bp2.getEffectiveDateTime(); // "2024-06-15"
4836

4937
// Raw getters return the full FHIR element including discriminator values
50-
bp2.getSystolicBPRaw(); // { code: { coding: [{ code: "8480-6", ... }] }, valueQuantity: { value: 120, ... } }
38+
bp2.getSystolicBPRaw();
39+
// { code: { coding: [{ code: "8480-6", ... }] }, valueQuantity: { value: 120, ... } }
5140
```
5241

53-
Supports resource profiles, simple/complex extension profiles, slice accessors, choice types, and `validate()` checking required fields, fixed values, slice cardinality, enum bindings, and reference types.
42+
What's supported: resource profiles, extension profiles (simple and complex), slice accessors, choice type flattening, and `validate()` checking required fields, fixed values, slice cardinality, enum bindings, and reference types.
43+
44+
Working examples:
45+
46+
- [R4 profiles](https://github.com/atomic-ehr/codegen/tree/main/examples/typescript-r4) -- blood pressure, bodyweight, vitals, extensions
47+
- [US Core profiles](https://github.com/atomic-ehr/codegen/tree/main/examples/typescript-us-core) -- patient, conditions, observations, diagnostics
48+
49+
## Current status and what's next
5450

55-
## Looking for feedback
51+
This is a **dev preview**. Our main focus right now is stabilization across different profile shapes and edge cases. We're using it ourselves for a FHIR-to-CCDA converter, which is a good stress test for the generator. After stabilization, the plan is to bring profile support to Python next.
5652

57-
This is an early iteration. We'd appreciate hearing about profiles that don't generate correctly, API patterns that feel awkward, or validation gaps.
53+
We'd appreciate any feedback -- profiles that don't generate correctly, API patterns that feel awkward, validation gaps, or anything else. Issues and discussions welcome on [GitHub](https://github.com/atomic-ehr/codegen).
5854

59-
GitHub: https://github.com/atomic-ehr/codegen
60-
NPM: `@atomic-ehr/codegen`
55+
NPM: [`@atomic-ehr/codegen`](https://www.npmjs.com/package/@atomic-ehr/codegen)

0 commit comments

Comments
 (0)