babelfhir-ts 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -25
- package/out/fhir-r4.d.ts +4 -0
- package/out/fhir-r4b.d.ts +4 -0
- package/out/src/generator/core/constants.js +77 -2
- package/out/src/generator/core/sdDiagnostics.js +108 -0
- package/out/src/generator/core/utils.js +22 -2
- package/out/src/generator/emitters/class/classGenerator.js +120 -258
- package/out/src/generator/emitters/class/classGeneratorHelpers.js +122 -0
- package/out/src/generator/emitters/class/classGeneratorTypes.js +4 -0
- package/out/src/generator/emitters/class/classHeuristics.js +19 -0
- package/out/src/generator/emitters/class/classTemplate.js +121 -0
- package/out/src/generator/emitters/class/enrichResourceBundleHandler.js +256 -0
- package/out/src/generator/emitters/class/enrichResourceFixers.js +184 -0
- package/out/src/generator/emitters/class/enrichResourceHandlers.js +84 -151
- package/out/src/generator/emitters/class/enrichResourceObservationHandler.js +254 -0
- package/out/src/generator/emitters/class/enrichResourceTemplate.js +110 -327
- package/out/src/generator/emitters/class/fieldPatternExtractor.js +103 -18
- package/out/src/generator/emitters/class/randomSupportGenerator.js +2 -1
- package/out/src/generator/emitters/class/randomUtilitiesTemplate.js +22 -1
- package/out/src/generator/emitters/class/sliceElementDefaults.js +467 -0
- package/out/src/generator/emitters/class/sliceElementGenerator.js +131 -367
- package/out/src/generator/emitters/interface/importManager.js +3 -0
- package/out/src/generator/emitters/interface/interfaceFieldProcessor.js +6 -77
- package/out/src/generator/emitters/interface/interfaceFieldUtils.js +52 -0
- package/out/src/generator/emitters/interface/interfaceGenerator.js +41 -3
- package/out/src/generator/emitters/interface/postProcessExtensions.js +16 -10
- package/out/src/generator/emitters/interface/processNestedField.js +19 -3
- package/out/src/generator/emitters/validator/sliceBackboneValidation.js +370 -0
- package/out/src/generator/emitters/validator/sliceExtensionValidation.js +244 -0
- package/out/src/generator/emitters/validator/sliceValidatorGenerator.js +197 -194
- package/out/src/generator/emitters/validator/sliceValidatorUtils.js +95 -0
- package/out/src/generator/emitters/validator/validatorBindingBuilder.js +304 -0
- package/out/src/generator/emitters/validator/validatorBindingLeafEmitters.js +303 -0
- package/out/src/generator/emitters/validator/validatorConstraintBuilders.js +374 -0
- package/out/src/generator/emitters/validator/validatorFieldBuilders.js +265 -0
- package/out/src/generator/emitters/validator/validatorGenerator.js +90 -382
- package/out/src/generator/emitters/validator/validatorTemplates.js +106 -0
- package/out/src/generator/emitters/validator/validatorTypes.js +1 -0
- package/out/src/generator/emitters/zod/zodRefinementBuilder.js +151 -0
- package/out/src/generator/emitters/zod/zodSchemaGenerator.js +133 -182
- package/out/src/generator/fhir/corePackageResolver.js +288 -0
- package/out/src/generator/fhir/corePackageResolver.ts +371 -0
- package/out/src/generator/fhir/createRules.js +158 -0
- package/out/src/generator/fhir/createRules.ts +188 -0
- package/out/src/generator/fhir/fallback/r4.json +15468 -0
- package/out/src/generator/fhir/fallback/r4b.json +15287 -0
- package/out/src/generator/fhir/r4/base/well-known-system-codes.json +46 -1
- package/out/src/generator/fhir/types.js +55 -12
- package/out/src/generator/fhir/types.ts +71 -16
- package/out/src/generator/fhir/versionRegistry.js +28 -0
- package/out/src/generator/fhir/versionRegistry.ts +45 -0
- package/out/src/generator/generationHelpers.js +202 -0
- package/out/src/generator/index.js +74 -277
- package/out/src/generator/parser/packageParser.js +218 -0
- package/out/src/generator/parser/sdFetcher.js +39 -1
- package/out/src/generator/parser/sdParser.js +65 -6
- package/out/src/generator/parser/sliceAggregator.js +203 -161
- package/out/src/generator/parser/sliceAggregatorComposition.js +194 -0
- package/out/src/generator/parser/vsParser.js +8 -2
- package/out/src/generator/sdProcessor.js +104 -302
- package/out/src/generator/sdProcessorHelpers.js +98 -0
- package/out/src/generator/sdProcessorRequiredFields.js +129 -0
- package/out/src/main.js +37 -246
- package/package.json +3 -3
- package/out/src/generator/fhir/r4/base/Bundle.json +0 -2154
- package/out/src/generator/fhir/r4/base/Composition.json +0 -3346
- package/out/src/generator/fhir/r4/base/Endpoint.json +0 -1051
- package/out/src/generator/fhir/r4/base/README.md +0 -28
- package/out/src/generator/fhir/r4/base/Subscription.json +0 -1072
- package/out/src/generator/fhir/r4/base/ValueSet.json +0 -4021
- package/out/src/generator/fhir/r4/context.js +0 -21
- package/out/src/generator/fhir/r4/context.ts +0 -24
- package/out/src/generator/fhir/r4/dataTypes.json +0 -858
- package/out/src/generator/fhir/r4/interfaces.json +0 -664
- package/out/src/generator/fhir/r4/resources.json +0 -150
- package/out/src/generator/fhir/r4/rules.js +0 -207
- package/out/src/generator/fhir/r4/rules.ts +0 -226
- package/out/src/generator/fhir/r4b/context.js +0 -21
- package/out/src/generator/fhir/r4b/context.ts +0 -24
- package/out/src/generator/fhir/r4b/dataTypes.json +0 -860
- package/out/src/generator/fhir/r4b/interfaces.json +0 -684
- package/out/src/generator/fhir/r4b/resources.json +0 -145
- package/out/src/generator/fhir/r4b/rules.js +0 -171
- package/out/src/generator/fhir/r4b/rules.ts +0 -190
package/README.md
CHANGED
|
@@ -35,38 +35,104 @@ Every pull request runs two independent CI pipelines that validate generated cod
|
|
|
35
35
|
4. Generates `empty()` and `random()` test resources for every profile
|
|
36
36
|
5. Validates those resources against two external FHIR validators
|
|
37
37
|
|
|
38
|
-
### Tested Implementation Guides
|
|
38
|
+
### Tested Implementation Guides (28 packages)
|
|
39
39
|
|
|
40
|
-
| Implementation Guide | Package |
|
|
40
|
+
| Category | Implementation Guide | Package |
|
|
41
41
|
|---|---|---|
|
|
42
|
-
| US Core | `hl7.fhir.us.core@8.0.0` |
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
42
|
+
| US | US Core | `hl7.fhir.us.core@8.0.0` |
|
|
43
|
+
| US | QI-Core | `hl7.fhir.us.qicore@6.0.0` |
|
|
44
|
+
| US | mCODE | `hl7.fhir.us.mcode@4.0.0` |
|
|
45
|
+
| US | SDOH Clinical Care | `hl7.fhir.us.sdoh-clinicalcare@2.2.0` |
|
|
46
|
+
| US | NDH (National Directory) | `hl7.fhir.us.ndh@1.0.0` |
|
|
47
|
+
| US | CARIN BB | `hl7.fhir.us.carin-bb@2.1.0` |
|
|
48
|
+
| US | CQF Measures | `hl7.fhir.us.cqfmeasures@4.0.0` |
|
|
49
|
+
| US | Physical Activity | `hl7.fhir.us.physical-activity@1.0.0` |
|
|
50
|
+
| DaVinci | PAS | `hl7.fhir.us.davinci-pas@2.0.1` |
|
|
51
|
+
| DaVinci | CDex | `hl7.fhir.us.davinci-cdex@2.1.0` |
|
|
52
|
+
| DaVinci | PDex | `hl7.fhir.us.davinci-pdex@2.1.0` |
|
|
53
|
+
| DaVinci | DTR | `hl7.fhir.us.davinci-dtr@2.1.0` |
|
|
54
|
+
| DaVinci | Alerts | `hl7.fhir.us.davinci-alerts@1.0.0` |
|
|
55
|
+
| DaVinci | DEQM | `hl7.fhir.us.davinci-deqm@4.0.0` |
|
|
56
|
+
| DaVinci | Drug Formulary | `hl7.fhir.us.davinci-drug-formulary@2.1.0` |
|
|
57
|
+
| Universal | IPS | `hl7.fhir.uv.ips@2.0.0` |
|
|
58
|
+
| Universal | SMART App Launch | `hl7.fhir.uv.smart-app-launch@2.2.0` |
|
|
59
|
+
| Universal | SDC (Structured Data Capture) | `hl7.fhir.uv.sdc@3.0.0` |
|
|
60
|
+
| Universal | Genomics Reporting | `hl7.fhir.uv.genomics-reporting@3.0.0` |
|
|
61
|
+
| Universal | CPG (Clinical Practice Guidelines) | `hl7.fhir.uv.cpg@2.0.0` |
|
|
62
|
+
| DE | ISiK Basis | `de.gematik.isik-basismodul@4.0.3` |
|
|
63
|
+
| DE | ISiK Medikation | `de.gematik.isik-medikation@4.0.1` |
|
|
64
|
+
| DE | KBV eRezept | `kbv.ita.erp@1.1.1` |
|
|
65
|
+
| DE | DE Basisprofil | `de.basisprofil.r4@1.5.0` |
|
|
66
|
+
| CH | CH Core (Switzerland) | `ch.fhir.ig.ch-core@5.0.0` |
|
|
67
|
+
| AU | AU Core (Australia) | `hl7.fhir.au.core@1.0.0` |
|
|
68
|
+
| IHE | PIXm | `ihe.iti.pixm@3.0.4` |
|
|
69
|
+
| IHE | MHD | `ihe.iti.mhd@4.2.2` |
|
|
48
70
|
|
|
49
71
|
### Validation with Firely .NET SDK
|
|
50
72
|
|
|
51
|
-
The first pipeline validates generated resources using the [Firely .NET SDK validator](https://docs.fire.ly/projects/Firely-NET-SDK/) (v3.1.
|
|
52
|
-
|
|
53
|
-
 (Firely.Fhir.Validation.R4 v3.1.1). Results are published as live badges:
|
|
74
|
+
|
|
75
|
+

|
|
76
|
+

|
|
77
|
+

|
|
78
|
+

|
|
79
|
+

|
|
80
|
+

|
|
81
|
+

|
|
82
|
+

|
|
83
|
+

|
|
84
|
+

|
|
85
|
+

|
|
86
|
+

|
|
87
|
+

|
|
88
|
+

|
|
89
|
+

|
|
90
|
+

|
|
91
|
+

|
|
92
|
+

|
|
93
|
+

|
|
94
|
+

|
|
95
|
+

|
|
96
|
+

|
|
97
|
+

|
|
98
|
+

|
|
99
|
+

|
|
100
|
+

|
|
101
|
+

|
|
102
|
+

|
|
59
103
|
|
|
60
104
|
### Validation with HL7 Java Validator
|
|
61
105
|
|
|
62
|
-
The second pipeline validates using the [official HL7 FHIR Validator](https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator) (v6.9.
|
|
63
|
-
|
|
64
|
-
 (v6.9.4), the reference implementation for FHIR conformance checking:
|
|
107
|
+
|
|
108
|
+

|
|
109
|
+

|
|
110
|
+

|
|
111
|
+

|
|
112
|
+

|
|
113
|
+

|
|
114
|
+

|
|
115
|
+

|
|
116
|
+

|
|
117
|
+

|
|
118
|
+

|
|
119
|
+

|
|
120
|
+

|
|
121
|
+

|
|
122
|
+

|
|
123
|
+

|
|
124
|
+

|
|
125
|
+

|
|
126
|
+

|
|
127
|
+

|
|
128
|
+

|
|
129
|
+

|
|
130
|
+

|
|
131
|
+

|
|
132
|
+

|
|
133
|
+

|
|
134
|
+

|
|
135
|
+

|
|
70
136
|
|
|
71
137
|
> Terminology validation requires a tx server. The pipeline uses `--tx-server https://tx.fhir.org/r4` during generation to expand ValueSets and produce valid codes.
|
|
72
138
|
|
|
@@ -194,4 +260,4 @@ For security issues, please see [SECURITY.md](SECURITY.md) for our security poli
|
|
|
194
260
|
- [GitHub repository](https://github.com/Max-Health-Inc/BabelFHIR-TS)
|
|
195
261
|
- [Validation report](https://max-health-inc.github.io/BabelFHIR-TS/)
|
|
196
262
|
- [Issue tracker](https://github.com/Max-Health-Inc/BabelFHIR-TS/issues)
|
|
197
|
-
- [Changelog](
|
|
263
|
+
- [Changelog / Releases](https://github.com/Max-Health-Inc/BabelFHIR-TS/releases)
|
package/out/fhir-r4.d.ts
CHANGED
|
@@ -673,4 +673,8 @@ declare module 'fhir/r4' {
|
|
|
673
673
|
export type VisionPrescription = fhir4.VisionPrescription;
|
|
674
674
|
export type VisionPrescriptionLensSpecification = fhir4.VisionPrescriptionLensSpecification;
|
|
675
675
|
export type VisionPrescriptionLensSpecificationPrism = fhir4.VisionPrescriptionLensSpecificationPrism;
|
|
676
|
+
/** Constraint profile on Quantity — missing from @types/fhir */
|
|
677
|
+
export type MoneyQuantity = fhir4.Quantity;
|
|
678
|
+
/** Constraint profile on Quantity — missing from @types/fhir */
|
|
679
|
+
export type SimpleQuantity = fhir4.Quantity;
|
|
676
680
|
}
|
package/out/fhir-r4b.d.ts
CHANGED
|
@@ -693,4 +693,8 @@ declare module 'fhir/r4b' {
|
|
|
693
693
|
export type VisionPrescription = fhir4b.VisionPrescription;
|
|
694
694
|
export type VisionPrescriptionLensSpecification = fhir4b.VisionPrescriptionLensSpecification;
|
|
695
695
|
export type VisionPrescriptionLensSpecificationPrism = fhir4b.VisionPrescriptionLensSpecificationPrism;
|
|
696
|
+
/** Constraint profile on Quantity — missing from @types/fhir */
|
|
697
|
+
export type MoneyQuantity = fhir4b.Quantity;
|
|
698
|
+
/** Constraint profile on Quantity — missing from @types/fhir */
|
|
699
|
+
export type SimpleQuantity = fhir4b.Quantity;
|
|
696
700
|
}
|
|
@@ -81,7 +81,7 @@ export const DEFAULT_COUNTRY = 'XX';
|
|
|
81
81
|
export const DEFAULT_BASE64 = 'ZXhhbXBsZQ=='; // "example"
|
|
82
82
|
export const DEFAULT_BASE64_HELLO = 'SGVsbG8gV29ybGQ='; // "Hello World"
|
|
83
83
|
export const DEFAULT_CONTENT_TYPE = 'application/pdf';
|
|
84
|
-
export const EXAMPLE_ID_SYSTEM = 'https://
|
|
84
|
+
export const EXAMPLE_ID_SYSTEM = 'https://babelfhir.dev/ids';
|
|
85
85
|
// ─── Resource status defaults (data-driven map) ─────────────────────────────
|
|
86
86
|
// ─── Preferred code values ───────────────────────────────────────────────────
|
|
87
87
|
//
|
|
@@ -150,7 +150,11 @@ export const VALUESET_DEFAULTS = {
|
|
|
150
150
|
'http://hl7.org/fhir/uv/ips/ValueSet/results-status-uv-ips': { code: 'final' },
|
|
151
151
|
'http://hl7.org/fhir/uv/ips/ValueSet/imaging-study-status-uv-ips': { code: 'available' },
|
|
152
152
|
'http://hl7.org/fhir/us/davinci-hrex/ValueSet/hrex-task-status': { code: 'requested' },
|
|
153
|
+
'http://hl7.org/fhir/us/physical-activity/ValueSet/pa-task-status': { code: 'requested' },
|
|
153
154
|
'http://fhir.de/ValueSet/EncounterStatusDe': { code: 'finished' },
|
|
155
|
+
// ── Narrative status ValueSets ─────────────────────────────────────────
|
|
156
|
+
'http://hl7.org/fhir/ValueSet/narrative-status': { code: 'generated' },
|
|
157
|
+
'http://hl7.org/fhir/us/core/ValueSet/us-core-narrative-status': { code: 'generated' },
|
|
154
158
|
// ── CodeableConcept clinical status (required binding) ────────────────────
|
|
155
159
|
'http://hl7.org/fhir/ValueSet/condition-clinical': { system: TS.CONDITION_CLINICAL, code: 'active' },
|
|
156
160
|
'http://hl7.org/fhir/ValueSet/condition-ver-status': { system: TS.CONDITION_VER_STATUS, code: 'confirmed' },
|
|
@@ -169,6 +173,28 @@ export const VALUESET_DEFAULTS = {
|
|
|
169
173
|
'http://hl7.org/fhir/ValueSet/subscriber-relationship': { system: TS.SUBSCRIBER_REL, code: 'self' },
|
|
170
174
|
'http://hl7.org/fhir/ValueSet/c80-doc-typecodes': { system: TS.LOINC, code: '34133-9', display: 'Summary of episode note' },
|
|
171
175
|
'http://hl7.org/fhir/ValueSet/document-classcodes': { system: TS.LOINC, code: '34133-9' },
|
|
176
|
+
// ── Additional status ValueSets for specific resource types ───────────────
|
|
177
|
+
'http://hl7.org/fhir/ValueSet/clinicalimpression-status': { code: 'completed' },
|
|
178
|
+
'http://hl7.org/fhir/ValueSet/verificationresult-status': { code: 'attested' },
|
|
179
|
+
'http://hl7.org/fhir/ValueSet/list-status': { code: 'current' },
|
|
180
|
+
'http://hl7.org/fhir/ValueSet/consent-state-codes': { code: 'active' },
|
|
181
|
+
'http://hl7.org/fhir/ValueSet/publication-status': { code: 'active' },
|
|
182
|
+
// ── Group type ValueSet (code type, required binding) ───────────────────────
|
|
183
|
+
'http://hl7.org/fhir/ValueSet/group-type': { code: 'person' },
|
|
184
|
+
// ── Consent ValueSets (code type, required binding) ────────────────────────
|
|
185
|
+
'http://hl7.org/fhir/ValueSet/consent-provision-type': { code: 'permit' },
|
|
186
|
+
'http://hl7.org/fhir/ValueSet/consent-data-meaning': { code: 'instance' },
|
|
187
|
+
// ── Code fields for definition resources (PlanDefinition, ActivityDefinition) ─
|
|
188
|
+
'http://hl7.org/fhir/ValueSet/action-condition-kind': { code: 'applicability' },
|
|
189
|
+
'http://hl7.org/fhir/ValueSet/action-relationship-type': { code: 'before-start' },
|
|
190
|
+
'http://hl7.org/fhir/ValueSet/action-participant-type': { code: 'practitioner' },
|
|
191
|
+
'http://hl7.org/fhir/ValueSet/request-resource-types': { code: 'Task' },
|
|
192
|
+
'http://hl7.org/fhir/ValueSet/resource-types': { code: 'Patient' },
|
|
193
|
+
'http://hl7.org/fhir/ValueSet/FHIR-version': { code: '4.0.1' },
|
|
194
|
+
// ── Claim/EoB ValueSets (CARIN BB) ────────────────────────────────────────
|
|
195
|
+
'http://hl7.org/fhir/ValueSet/claim-type': { system: 'http://terminology.hl7.org/CodeSystem/claim-type', code: 'institutional' },
|
|
196
|
+
'http://hl7.org/fhir/ValueSet/process-priority': { code: 'normal' },
|
|
197
|
+
'http://hl7.org/fhir/ValueSet/remittance-outcome': { code: 'complete' },
|
|
172
198
|
};
|
|
173
199
|
/**
|
|
174
200
|
* Look up a safe default for a given ValueSet binding URI.
|
|
@@ -255,18 +281,61 @@ const procedure = (mp) => `{ resourceType: 'Procedure', id: randomId()${mp}, sta
|
|
|
255
281
|
const device = (mp) => `{ resourceType: 'Device', id: randomId()${mp}, type: { coding: [{ system: '${TS.SNOMED}', code: '49062001', display: 'Device' }] } }`;
|
|
256
282
|
const claimResponse = (mp) => `{ resourceType: 'ClaimResponse', id: randomId()${mp}, status: 'active', type: { coding: [{ system: '${TS.CLAIM_TYPE}', code: 'professional' }] }, use: 'preauthorization', patient: { reference: 'Patient/' + randomId() }, created: new Date().toISOString().split('T')[0], insurer: { reference: 'Organization/' + randomId() }, outcome: 'complete' }`;
|
|
257
283
|
const claim = (mp) => `{ resourceType: 'Claim', id: randomId()${mp}, status: 'active', type: { coding: [{ system: '${TS.CLAIM_TYPE}', code: 'professional' }] }, use: 'preauthorization', patient: { reference: 'Patient/' + randomId() }, created: new Date().toISOString().split('T')[0], provider: { reference: 'Practitioner/' + randomId() }, priority: { coding: [{ system: '${TS.PROCESS_PRIORITY}', code: 'normal' }] }, insurance: [{ sequence: 1, focal: true, coverage: { reference: 'Coverage/' + randomId() } }] }`;
|
|
284
|
+
/**
|
|
285
|
+
* Placeholder values for Composition fields that profiles may require (min >= 1)
|
|
286
|
+
* but the base skeleton does not include.
|
|
287
|
+
*/
|
|
288
|
+
const COMPOSITION_FIELD_PLACEHOLDERS = {
|
|
289
|
+
identifier: `identifier: { value: 'example-id', system: 'urn:ietf:rfc:3986' }`,
|
|
290
|
+
language: `language: 'en'`,
|
|
291
|
+
confidentiality: `confidentiality: 'N'`,
|
|
292
|
+
category: `category: [{ coding: [{ system: '${TS.LOINC}', code: '11503-0' }] }]`,
|
|
293
|
+
encounter: `encounter: { reference: 'Encounter/' + randomId() }`,
|
|
294
|
+
custodian: `custodian: { reference: 'Organization/' + randomId() }`,
|
|
295
|
+
attester: `attester: [{ mode: 'professional', party: { reference: 'Practitioner/' + randomId() } }]`,
|
|
296
|
+
event: `event: [{ period: { start: '2024-01-01' } }]`,
|
|
297
|
+
relatesTo: `relatesTo: [{ code: 'replaces', targetReference: { reference: 'Composition/' + randomId() } }]`,
|
|
298
|
+
};
|
|
258
299
|
export function compositionSkeleton(metaProfile, opts = {}) {
|
|
259
300
|
const loincCode = opts.loincCode ?? '60591-5';
|
|
260
301
|
const textStatus = opts.textStatus ?? 'generated';
|
|
261
302
|
const sectionExpr = opts.sections && opts.sections.length > 0
|
|
262
303
|
? opts.sections.map(s => `{ title: '${s.title}', code: { coding: [{ system: '${TS.LOINC}', code: '${s.loincCode}' }] }, text: { status: 'generated', div: '<div xmlns="http://www.w3.org/1999/xhtml">${s.title}</div>' }, emptyReason: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/list-empty-reason', code: 'unavailable', display: 'Unavailable' }] } }`).join(', ')
|
|
263
304
|
: `{ title: 'Section', text: { status: 'generated', div: '<div xmlns="http://www.w3.org/1999/xhtml">Content</div>' } }`;
|
|
264
|
-
|
|
305
|
+
// Build extra fields required by the Composition profile but not in the base skeleton
|
|
306
|
+
const extraFields = (opts.requiredFields ?? [])
|
|
307
|
+
.map(f => COMPOSITION_FIELD_PLACEHOLDERS[f])
|
|
308
|
+
.filter(Boolean)
|
|
309
|
+
.join(', ');
|
|
310
|
+
const extraExpr = extraFields ? `, ${extraFields}` : '';
|
|
311
|
+
// Build primitive extensions (e.g., _confidentiality with required extension)
|
|
312
|
+
const primExtParts = [];
|
|
313
|
+
for (const pe of (opts.primitiveExtensions ?? [])) {
|
|
314
|
+
// Default to valueCodeableConcept with data-absent-reason (always valid, avoids example URL bans)
|
|
315
|
+
const valueExpr = pe.valueType === 'string'
|
|
316
|
+
? `valueString: 'placeholder'`
|
|
317
|
+
: `valueCodeableConcept: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/data-absent-reason', code: 'unknown' }] }`;
|
|
318
|
+
primExtParts.push(`'_${pe.field}': { extension: [{ url: '${pe.url}', ${valueExpr} }] }`);
|
|
319
|
+
}
|
|
320
|
+
const primExtExpr = primExtParts.length > 0 ? `, ${primExtParts.join(', ')}` : '';
|
|
321
|
+
return `{ resourceType: 'Composition', id: 'comp-' + randomId()${metaProfile}${extraExpr}${primExtExpr}, text: { status: '${textStatus}', div: '<div xmlns="http://www.w3.org/1999/xhtml">Composition Narrative</div>' }, status: 'final', type: { coding: [{ system: '${TS.LOINC}', code: '${loincCode}' }], text: '${loincCode}' }, subject: { reference: 'Patient/' + randomId(), display: 'Example Patient' }, date: new Date().toISOString(), author: [{ reference: 'Practitioner/' + randomId(), display: 'Example Practitioner' }], title: 'Document Title', section: [${sectionExpr}] }`;
|
|
265
322
|
}
|
|
266
323
|
/**
|
|
267
324
|
* Registry of resource skeletons indexed by resource type.
|
|
268
325
|
* Used for inline resource generation (Bundle entry.resource, slice fallbacks).
|
|
269
326
|
*/
|
|
327
|
+
const group = (mp) => {
|
|
328
|
+
// Profile-specific code: pdex-member-match-group requires code from PDexMultiMemberMatchResultVS
|
|
329
|
+
const code = mp.includes('pdex-member-match-group')
|
|
330
|
+
? `code: { coding: [{ system: 'http://hl7.org/fhir/us/davinci-pdex/CodeSystem/PdexMultiMemberMatchResultCS', code: 'match' }] }, `
|
|
331
|
+
: '';
|
|
332
|
+
return `{ resourceType: 'Group', id: randomId()${mp}, type: 'person', actual: true, ${code}}`;
|
|
333
|
+
};
|
|
334
|
+
const coverage = (mp) => `{ resourceType: 'Coverage', id: randomId()${mp}, status: 'active', subscriberId: randomId(), beneficiary: { reference: 'Patient/' + randomId() }, relationship: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/subscriber-relationship', code: 'self' }] }, payor: [{ reference: 'Organization/' + randomId() }] }`;
|
|
335
|
+
const consent = (mp) => `{ resourceType: 'Consent', id: randomId()${mp}, status: 'active', scope: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/consentscope', code: 'patient-privacy' }] }, category: [{ coding: [{ system: '${TS.ACT_CODE}', code: 'IDSCL' }] }], patient: { reference: 'Patient/' + randomId() }, performer: [{ reference: 'Organization/' + randomId() }], sourceReference: { reference: 'DocumentReference/' + randomId() }, policy: [{ uri: 'http://example.org/policy' }], provision: { type: 'permit', period: { start: '2024-01-01T00:00:00.000Z', end: '2025-01-01T00:00:00.000Z' }, actor: [{ role: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/provenance-participant-type', code: 'performer' }] }, reference: { reference: 'Organization/' + randomId() } }, { role: { coding: [{ system: 'http://terminology.hl7.org/CodeSystem/v3-ParticipationType', code: 'IRCP' }] }, reference: { reference: 'Organization/' + randomId() } }], action: [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/consentaction', code: 'disclose' }] }] } }`;
|
|
336
|
+
const list = (mp) => `{ resourceType: 'List', id: randomId()${mp}, status: 'current', mode: 'working', code: { coding: [{ system: '${TS.LOINC}', code: '57024-2' }] }, subject: { reference: 'Patient/' + randomId() }, date: new Date().toISOString() }`;
|
|
337
|
+
const documentReference = (mp) => `{ resourceType: 'DocumentReference', id: randomId()${mp}, status: 'current', content: [{ attachment: { contentType: 'text/plain', url: 'https://babelfhir.dev/' + randomId() } }] }`;
|
|
338
|
+
const binary = (mp) => `{ resourceType: 'Binary', id: randomId()${mp}, contentType: 'application/pdf', data: '${DEFAULT_BASE64}' }`;
|
|
270
339
|
export const RESOURCE_SKELETONS = {
|
|
271
340
|
Patient: patient,
|
|
272
341
|
Practitioner: practitioner,
|
|
@@ -280,6 +349,12 @@ export const RESOURCE_SKELETONS = {
|
|
|
280
349
|
Device: device,
|
|
281
350
|
ClaimResponse: claimResponse,
|
|
282
351
|
Claim: claim,
|
|
352
|
+
Group: group,
|
|
353
|
+
Coverage: coverage,
|
|
354
|
+
Consent: consent,
|
|
355
|
+
List: list,
|
|
356
|
+
DocumentReference: documentReference,
|
|
357
|
+
Binary: binary,
|
|
283
358
|
};
|
|
284
359
|
/**
|
|
285
360
|
* Build a Bundle entry object expression from a skeleton.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StructureDefinition Quality Diagnostics
|
|
3
|
+
*
|
|
4
|
+
* Collects structured diagnostics about SD quality issues discovered
|
|
5
|
+
* during code generation. Unlike log messages (which are transient),
|
|
6
|
+
* diagnostics are accumulated and emitted as a machine-readable report.
|
|
7
|
+
*
|
|
8
|
+
* This turns BabelFHIR-TS into a **profile validator** — not just a
|
|
9
|
+
* resource validator. When a profile has a structural flaw (unmatchable
|
|
10
|
+
* discriminator, missing snapshot, unresolvable type), we flag the
|
|
11
|
+
* *profile*, not the resources that happen to be validated against it.
|
|
12
|
+
*/
|
|
13
|
+
// ── Collector ────────────────────────────────────────────────────────────────
|
|
14
|
+
const diagnostics = [];
|
|
15
|
+
const knownProfiles = new Set();
|
|
16
|
+
/** Reset all collected diagnostics (call before a new generation run). */
|
|
17
|
+
export function resetDiagnostics() {
|
|
18
|
+
diagnostics.length = 0;
|
|
19
|
+
knownProfiles.clear();
|
|
20
|
+
}
|
|
21
|
+
/** Register a profile as processed (even if no diagnostics are emitted for it). */
|
|
22
|
+
export function registerProfile(profileUrl, profileName) {
|
|
23
|
+
knownProfiles.add(profileUrl || profileName);
|
|
24
|
+
}
|
|
25
|
+
/** Record a diagnostic. */
|
|
26
|
+
export function addDiagnostic(d) {
|
|
27
|
+
diagnostics.push(d);
|
|
28
|
+
knownProfiles.add(d.profileUrl || d.profileName);
|
|
29
|
+
}
|
|
30
|
+
/** Get all collected diagnostics. */
|
|
31
|
+
export function getDiagnostics() {
|
|
32
|
+
return diagnostics;
|
|
33
|
+
}
|
|
34
|
+
/** Build the final quality report. */
|
|
35
|
+
export function buildQualityReport() {
|
|
36
|
+
const profiles = {};
|
|
37
|
+
const affectedUrls = new Set();
|
|
38
|
+
for (const d of diagnostics) {
|
|
39
|
+
const key = d.profileUrl || d.profileName;
|
|
40
|
+
affectedUrls.add(key);
|
|
41
|
+
if (!profiles[key]) {
|
|
42
|
+
profiles[key] = {
|
|
43
|
+
profileUrl: d.profileUrl,
|
|
44
|
+
profileName: d.profileName,
|
|
45
|
+
diagnostics: [],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
profiles[key].diagnostics.push({
|
|
49
|
+
severity: d.severity,
|
|
50
|
+
category: d.category,
|
|
51
|
+
elementPath: d.elementPath,
|
|
52
|
+
message: d.message,
|
|
53
|
+
impact: d.impact,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
generated: new Date().toISOString(),
|
|
58
|
+
summary: {
|
|
59
|
+
errors: diagnostics.filter(d => d.severity === 'error').length,
|
|
60
|
+
warnings: diagnostics.filter(d => d.severity === 'warning').length,
|
|
61
|
+
info: diagnostics.filter(d => d.severity === 'info').length,
|
|
62
|
+
profilesAffected: affectedUrls.size,
|
|
63
|
+
profilesClean: knownProfiles.size - affectedUrls.size,
|
|
64
|
+
},
|
|
65
|
+
profiles,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Format the report as a human-readable CLI summary. */
|
|
69
|
+
export function formatReportSummary(report) {
|
|
70
|
+
const { summary } = report;
|
|
71
|
+
const total = summary.errors + summary.warnings + summary.info;
|
|
72
|
+
if (total === 0) {
|
|
73
|
+
return `\n✅ SD Quality: ${summary.profilesClean} profiles analyzed, no issues detected.\n`;
|
|
74
|
+
}
|
|
75
|
+
const lines = [];
|
|
76
|
+
lines.push('');
|
|
77
|
+
lines.push(`🔍 SD Quality Report: ${total} issue(s) across ${summary.profilesAffected} profile(s)`);
|
|
78
|
+
if (summary.errors > 0)
|
|
79
|
+
lines.push(` ❌ ${summary.errors} error(s) — structural flaws that will cause validator false positives`);
|
|
80
|
+
if (summary.warnings > 0)
|
|
81
|
+
lines.push(` ⚠️ ${summary.warnings} warning(s) — constraints that cannot be fully evaluated`);
|
|
82
|
+
if (summary.info > 0)
|
|
83
|
+
lines.push(` ℹ️ ${summary.info} info — limitations noted`);
|
|
84
|
+
lines.push(` ✅ ${summary.profilesClean} profile(s) are clean`);
|
|
85
|
+
lines.push('');
|
|
86
|
+
// Group by category for a compact view
|
|
87
|
+
const byCat = new Map();
|
|
88
|
+
for (const d of diagnostics) {
|
|
89
|
+
if (!byCat.has(d.category))
|
|
90
|
+
byCat.set(d.category, []);
|
|
91
|
+
byCat.get(d.category).push(d);
|
|
92
|
+
}
|
|
93
|
+
const catLabels = {
|
|
94
|
+
discriminator: 'Discriminator defects',
|
|
95
|
+
snapshot: 'Missing/incomplete snapshots',
|
|
96
|
+
type: 'Unresolvable element types',
|
|
97
|
+
constraint: 'Unevaluable FHIRPath constraints',
|
|
98
|
+
binding: 'Unresolvable ValueSet bindings',
|
|
99
|
+
'profile-ref': 'Profile reference failures',
|
|
100
|
+
structure: 'Structural issues',
|
|
101
|
+
};
|
|
102
|
+
for (const [cat, items] of byCat) {
|
|
103
|
+
const profiles = new Set(items.map(d => d.profileName));
|
|
104
|
+
lines.push(` ${catLabels[cat]}: ${items.length} (${profiles.size} profile(s))`);
|
|
105
|
+
}
|
|
106
|
+
lines.push('');
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
@@ -17,7 +17,7 @@ export function stripVersionFromCanonicalUrl(url) {
|
|
|
17
17
|
export function writeInterfaceAndValidatorToFile(outputDir, interfaceName, interfaceContent, validateFunctionContent) {
|
|
18
18
|
const filePath = path.join(outputDir, `${interfaceName}.ts`);
|
|
19
19
|
const content = `${interfaceContent}\n\n${validateFunctionContent}`;
|
|
20
|
-
|
|
20
|
+
caseAwareWriteFile(filePath, content);
|
|
21
21
|
}
|
|
22
22
|
export function extractFieldsFromInterface(interfaceDecl) {
|
|
23
23
|
return interfaceDecl.getProperties().map((property) => {
|
|
@@ -188,9 +188,29 @@ export function ensureDirectoryExists(dirPath) {
|
|
|
188
188
|
}
|
|
189
189
|
/**
|
|
190
190
|
* Writes content to a file at the specified path.
|
|
191
|
+
* On case-insensitive filesystems (Windows/macOS), removes any existing file
|
|
192
|
+
* whose name differs only in casing to prevent TS1261 errors.
|
|
191
193
|
*/
|
|
192
194
|
export function writeToFile(filePath, content) {
|
|
193
|
-
|
|
195
|
+
caseAwareWriteFile(filePath, content);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Writes a file, first removing any existing file with the same case-insensitive
|
|
199
|
+
* name but different casing. Prevents TS1261/TS1149 on case-insensitive filesystems.
|
|
200
|
+
*/
|
|
201
|
+
function caseAwareWriteFile(filePath, content) {
|
|
202
|
+
const dir = path.dirname(filePath);
|
|
203
|
+
const filename = path.basename(filePath);
|
|
204
|
+
if (fs.existsSync(dir)) {
|
|
205
|
+
const filenameLower = filename.toLowerCase();
|
|
206
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
207
|
+
if (entry.toLowerCase() === filenameLower && entry !== filename) {
|
|
208
|
+
fs.unlinkSync(path.join(dir, entry));
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
194
214
|
}
|
|
195
215
|
/**
|
|
196
216
|
* Extracts the base resource name from a baseDefinition URL.
|