fhir-data-utils-ts 0.2.3
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/.codex/skills/govern-fhir-observation-components/SKILL.md +76 -0
- package/.codex/skills/govern-fhir-observation-components/agents/openai.yaml +4 -0
- package/.codex/skills/preserve-fhir-flat-coding-review/SKILL.md +57 -0
- package/.codex/skills/preserve-fhir-flat-coding-review/agents/openai.yaml +4 -0
- package/CHANGELOG.md +35 -0
- package/README.md +65 -0
- package/dist/coding-review-flat-claims.d.ts +134 -0
- package/dist/coding-review-flat-claims.js +160 -0
- package/dist/flat-claim-resource-graph.d.ts +48 -0
- package/dist/flat-claim-resource-graph.js +108 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/observation-claims.d.ts +134 -0
- package/dist/observation-claims.js +100 -0
- package/dist/search-parameters.d.ts +18 -0
- package/dist/search-parameters.js +34 -0
- package/dist/vital-sign-observations.d.ts +238 -0
- package/dist/vital-sign-observations.js +237 -0
- package/docs/UHC_FHIR_UTILS_MIGRATION.md +68 -0
- package/package.json +61 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { buildFlatClaimResourceEntry } from './flat-claim-resource-graph.js';
|
|
2
|
+
import { FhirCodeSystem, ObservationCategoryCodes, ObservationClaim, decodeObservationClaimList, encodeObservationClaimList, } from './observation-claims.js';
|
|
3
|
+
export { ObservationCategoryCodes, ObservationClaim } from './observation-claims.js';
|
|
4
|
+
function defineCoding(system, code, display) {
|
|
5
|
+
return Object.freeze({ system, code, display, claim: `${system}|${code}` });
|
|
6
|
+
}
|
|
7
|
+
/** Canonical LOINC descriptors for supported vital signs. */
|
|
8
|
+
export const VitalSignsCodes = Object.freeze({
|
|
9
|
+
BodyWeight: defineCoding(FhirCodeSystem.Loinc, '29463-7', 'Body weight'),
|
|
10
|
+
HeartRate: defineCoding(FhirCodeSystem.Loinc, '8867-4', 'Heart rate'),
|
|
11
|
+
BloodPressure: defineCoding(FhirCodeSystem.Loinc, '85354-9', 'Blood pressure panel'),
|
|
12
|
+
SystolicBloodPressure: defineCoding(FhirCodeSystem.Loinc, '8480-6', 'Systolic blood pressure'),
|
|
13
|
+
DiastolicBloodPressure: defineCoding(FhirCodeSystem.Loinc, '8462-4', 'Diastolic blood pressure'),
|
|
14
|
+
BodyTemperature: defineCoding(FhirCodeSystem.Loinc, '8310-5', 'Body temperature'),
|
|
15
|
+
OxygenSaturation: defineCoding(FhirCodeSystem.Loinc, '2708-6', 'Oxygen saturation'),
|
|
16
|
+
RespiratoryRate: defineCoding(FhirCodeSystem.Loinc, '9279-1', 'Respiratory rate'),
|
|
17
|
+
});
|
|
18
|
+
/** Canonical UCUM descriptors for supported vital signs. */
|
|
19
|
+
export const VitalSignsUnits = Object.freeze({
|
|
20
|
+
BeatsPerMinute: defineCoding(FhirCodeSystem.Ucum, '/min', 'beats/minute'),
|
|
21
|
+
MillimeterOfMercury: defineCoding(FhirCodeSystem.Ucum, 'mm[Hg]', 'mmHg'),
|
|
22
|
+
Celsius: defineCoding(FhirCodeSystem.Ucum, 'Cel', 'degrees Celsius'),
|
|
23
|
+
Percent: defineCoding(FhirCodeSystem.Ucum, '%', 'percent'),
|
|
24
|
+
Kilogram: defineCoding(FhirCodeSystem.Ucum, 'kg', 'kilogram'),
|
|
25
|
+
});
|
|
26
|
+
/** Governed FHIR/LOINC tokens used by every channel that captures vital signs. */
|
|
27
|
+
export const VitalSignCode = Object.freeze({
|
|
28
|
+
bloodPressure: VitalSignsCodes.BloodPressure.claim,
|
|
29
|
+
systolic: VitalSignsCodes.SystolicBloodPressure.claim,
|
|
30
|
+
diastolic: VitalSignsCodes.DiastolicBloodPressure.claim,
|
|
31
|
+
temperature: VitalSignsCodes.BodyTemperature.claim,
|
|
32
|
+
heartRate: VitalSignsCodes.HeartRate.claim,
|
|
33
|
+
oxygenSaturation: VitalSignsCodes.OxygenSaturation.claim,
|
|
34
|
+
respiratoryRate: VitalSignsCodes.RespiratoryRate.claim,
|
|
35
|
+
bodyWeight: VitalSignsCodes.BodyWeight.claim,
|
|
36
|
+
});
|
|
37
|
+
/** UCUM-compatible unit spellings persisted in flat claims. */
|
|
38
|
+
export const VitalSignUnit = Object.freeze({
|
|
39
|
+
bloodPressure: VitalSignsUnits.MillimeterOfMercury.claim,
|
|
40
|
+
temperature: VitalSignsUnits.Celsius.claim,
|
|
41
|
+
heartRate: VitalSignsUnits.BeatsPerMinute.claim,
|
|
42
|
+
oxygenSaturation: VitalSignsUnits.Percent.claim,
|
|
43
|
+
respiratoryRate: VitalSignsUnits.BeatsPerMinute.claim,
|
|
44
|
+
bodyWeight: VitalSignsUnits.Kilogram.claim,
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* Builds one timestamped Observation. The device is retained as source; this
|
|
48
|
+
* helper deliberately never creates a professional performer or attester.
|
|
49
|
+
*/
|
|
50
|
+
export function buildVitalSignObservation(input) {
|
|
51
|
+
const measuredAt = normalizeDateTime(input.measuredAt);
|
|
52
|
+
const claims = {
|
|
53
|
+
[ObservationClaim.Identifier]: required(input.entryId, 'vital_sign_entry_id_required'),
|
|
54
|
+
[ObservationClaim.Subject]: required(input.subjectReference, 'vital_sign_subject_required'),
|
|
55
|
+
[ObservationClaim.Status]: input.status,
|
|
56
|
+
[ObservationClaim.Category]: ObservationCategoryCodes.VitalSigns.claim,
|
|
57
|
+
[ObservationClaim.EffectiveDateTime]: measuredAt,
|
|
58
|
+
};
|
|
59
|
+
if (input.deviceReference)
|
|
60
|
+
claims[ObservationClaim.Device] = required(input.deviceReference, 'vital_sign_device_required');
|
|
61
|
+
if (input.note?.trim())
|
|
62
|
+
claims[ObservationClaim.Note] = input.note.trim();
|
|
63
|
+
if (input.userSelected !== undefined)
|
|
64
|
+
claims[ObservationClaim.UserSelected] = String(input.userSelected);
|
|
65
|
+
if (input.kind === 'blood-pressure') {
|
|
66
|
+
if (!validPositive(input.systolic) || !validPositive(input.diastolic) || input.systolic <= input.diastolic) {
|
|
67
|
+
throw new TypeError('blood_pressure_values_invalid');
|
|
68
|
+
}
|
|
69
|
+
claims[ObservationClaim.Code] = VitalSignCode.bloodPressure;
|
|
70
|
+
claims[ObservationClaim.CodeDisplay] = VitalSignsCodes.BloodPressure.display ?? '';
|
|
71
|
+
claims[ObservationClaim.ComponentCode] = encodeObservationClaimList([VitalSignCode.systolic, VitalSignCode.diastolic]);
|
|
72
|
+
claims[ObservationClaim.ComponentCodeDisplay] = encodeObservationClaimList([
|
|
73
|
+
VitalSignsCodes.SystolicBloodPressure.display ?? '',
|
|
74
|
+
VitalSignsCodes.DiastolicBloodPressure.display ?? '',
|
|
75
|
+
]);
|
|
76
|
+
claims[ObservationClaim.ComponentValueQuantityNumber] = encodeObservationClaimList([String(input.systolic), String(input.diastolic)]);
|
|
77
|
+
claims[ObservationClaim.ComponentValueQuantityUnit] = encodeObservationClaimList([VitalSignUnit.bloodPressure, VitalSignUnit.bloodPressure]);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
if (!validPositive(input.value))
|
|
81
|
+
throw new TypeError('vital_sign_value_invalid');
|
|
82
|
+
const scalar = scalarDefinition(input.kind);
|
|
83
|
+
claims[ObservationClaim.Code] = scalar.coding.claim;
|
|
84
|
+
if (scalar.coding.display)
|
|
85
|
+
claims[ObservationClaim.CodeDisplay] = scalar.coding.display;
|
|
86
|
+
claims[ObservationClaim.ValueQuantityNumber] = String(input.value);
|
|
87
|
+
claims[ObservationClaim.ValueQuantityUnit] = scalar.unit;
|
|
88
|
+
}
|
|
89
|
+
return buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'Observation', claims });
|
|
90
|
+
}
|
|
91
|
+
/** Restores aligned FHIR component values from canonical JSON or legacy CSV. */
|
|
92
|
+
export function parseObservationComponents(claims) {
|
|
93
|
+
const codes = decodeObservationClaimList(claims[ObservationClaim.ComponentCode]);
|
|
94
|
+
const displays = decodeObservationClaimList(claims[ObservationClaim.ComponentCodeDisplay]);
|
|
95
|
+
const values = decodeObservationClaimList(claims[ObservationClaim.ComponentValueQuantityNumber]);
|
|
96
|
+
const units = decodeObservationClaimList(claims[ObservationClaim.ComponentValueQuantityUnit]);
|
|
97
|
+
if (!codes.length && claims[ObservationClaim.Code] === VitalSignCode.bloodPressure) {
|
|
98
|
+
const systolic = Number(claims[ObservationClaim.BloodPressureSystolicNumber]);
|
|
99
|
+
const diastolic = Number(claims[ObservationClaim.BloodPressureDiastolicNumber]);
|
|
100
|
+
if (Number.isFinite(systolic) && Number.isFinite(diastolic)) {
|
|
101
|
+
return Object.freeze([
|
|
102
|
+
Object.freeze({ code: VitalSignCode.systolic, value: systolic, unit: VitalSignUnit.bloodPressure }),
|
|
103
|
+
Object.freeze({ code: VitalSignCode.diastolic, value: diastolic, unit: VitalSignUnit.bloodPressure }),
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (codes.length !== values.length || codes.length !== units.length)
|
|
108
|
+
throw new TypeError('observation_components_not_aligned');
|
|
109
|
+
if (displays.length && displays.length !== codes.length)
|
|
110
|
+
throw new TypeError('observation_components_not_aligned');
|
|
111
|
+
return Object.freeze(codes.map((code, index) => {
|
|
112
|
+
const value = Number(values[index]);
|
|
113
|
+
if (typeof code !== 'string' || typeof units[index] !== 'string' || !Number.isFinite(value)) {
|
|
114
|
+
throw new TypeError('observation_component_invalid');
|
|
115
|
+
}
|
|
116
|
+
const display = displays[index];
|
|
117
|
+
if (display !== undefined && typeof display !== 'string')
|
|
118
|
+
throw new TypeError('observation_component_invalid');
|
|
119
|
+
return Object.freeze({ code, ...(display ? { display } : {}), value, unit: units[index] });
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Creates generic reduced rows for component-level indexes.
|
|
124
|
+
*
|
|
125
|
+
* These are internal index projections, not independent native FHIR
|
|
126
|
+
* Observations. The missing status deliberately keeps them out of normal
|
|
127
|
+
* top-level Observation results.
|
|
128
|
+
*/
|
|
129
|
+
export function materializeObservationComponentIndexEntries(entry) {
|
|
130
|
+
if (entry.resourceType !== 'Observation')
|
|
131
|
+
throw new TypeError('vital_sign_observation_required');
|
|
132
|
+
const subject = required(entry.claims[ObservationClaim.Subject] ?? '', 'vital_sign_subject_required');
|
|
133
|
+
return Object.freeze(parseObservationComponents(entry.claims).map((component, componentIndex) => Object.freeze({
|
|
134
|
+
parentReference: entry.reference,
|
|
135
|
+
componentIndex,
|
|
136
|
+
claims: Object.freeze({
|
|
137
|
+
[ObservationClaim.Subject]: subject,
|
|
138
|
+
[ObservationClaim.Code]: component.code,
|
|
139
|
+
...(component.display ? { [ObservationClaim.CodeDisplay]: component.display } : {}),
|
|
140
|
+
[ObservationClaim.ValueQuantityNumber]: String(component.value),
|
|
141
|
+
[ObservationClaim.ValueQuantityUnit]: component.unit,
|
|
142
|
+
}),
|
|
143
|
+
})));
|
|
144
|
+
}
|
|
145
|
+
/** Projects the neutral flat contract at the explicit FHIR R4 boundary. */
|
|
146
|
+
export function projectVitalSignObservationR4(entry) {
|
|
147
|
+
return projectVitalSignObservation(entry);
|
|
148
|
+
}
|
|
149
|
+
/** Projects the neutral flat contract at the explicit FHIR R5 boundary. */
|
|
150
|
+
export function projectVitalSignObservationR5(entry) {
|
|
151
|
+
return projectVitalSignObservation(entry);
|
|
152
|
+
}
|
|
153
|
+
function projectVitalSignObservation(entry) {
|
|
154
|
+
if (entry.resourceType !== 'Observation')
|
|
155
|
+
throw new TypeError('vital_sign_observation_required');
|
|
156
|
+
const claims = entry.claims;
|
|
157
|
+
const base = {
|
|
158
|
+
resourceType: 'Observation',
|
|
159
|
+
id: required(claims[ObservationClaim.Identifier] ?? '', 'vital_sign_entry_id_required'),
|
|
160
|
+
status: required(claims[ObservationClaim.Status] ?? '', 'vital_sign_status_required'),
|
|
161
|
+
category: [{ coding: [codingFromClaim(required(claims[ObservationClaim.Category] ?? '', 'vital_sign_category_required'))] }],
|
|
162
|
+
code: { coding: [codingFromClaim(required(claims[ObservationClaim.Code] ?? '', 'vital_sign_code_required'), claims[ObservationClaim.CodeDisplay])] },
|
|
163
|
+
subject: { reference: required(claims[ObservationClaim.Subject] ?? '', 'vital_sign_subject_required') },
|
|
164
|
+
effectiveDateTime: required(claims[ObservationClaim.EffectiveDateTime] ?? '', 'vital_sign_measured_at_required'),
|
|
165
|
+
...(claims[ObservationClaim.Device] ? { device: { reference: claims[ObservationClaim.Device] } } : {}),
|
|
166
|
+
};
|
|
167
|
+
const components = parseObservationComponents(claims);
|
|
168
|
+
if (components.length) {
|
|
169
|
+
return Object.freeze({ ...base, component: Object.freeze(components.map(component => Object.freeze({
|
|
170
|
+
code: { coding: [codingFromClaim(component.code, component.display)] },
|
|
171
|
+
valueQuantity: quantity(component.value, component.unit),
|
|
172
|
+
}))) });
|
|
173
|
+
}
|
|
174
|
+
const value = Number(claims[ObservationClaim.ValueQuantityNumber]);
|
|
175
|
+
if (!Number.isFinite(value))
|
|
176
|
+
throw new TypeError('vital_sign_value_invalid');
|
|
177
|
+
return Object.freeze({ ...base, valueQuantity: quantity(value, required(claims[ObservationClaim.ValueQuantityUnit] ?? '', 'vital_sign_unit_required')) });
|
|
178
|
+
}
|
|
179
|
+
function codingFromClaim(value, display) {
|
|
180
|
+
const separator = value.lastIndexOf('|');
|
|
181
|
+
return separator < 0
|
|
182
|
+
? Object.freeze({ code: value, ...(display ? { display } : {}) })
|
|
183
|
+
: Object.freeze({ system: value.slice(0, separator), code: value.slice(separator + 1), ...(display ? { display } : {}) });
|
|
184
|
+
}
|
|
185
|
+
function quantity(value, unitClaim) {
|
|
186
|
+
const coding = codingFromClaim(unitClaim);
|
|
187
|
+
return Object.freeze({ value, unit: coding.code, ...coding });
|
|
188
|
+
}
|
|
189
|
+
/** Converts a device/API batch to independent preliminary Observations without aggregation. */
|
|
190
|
+
export function normalizeVitalSignDeviceBatch(input) {
|
|
191
|
+
const seen = new Set();
|
|
192
|
+
return Object.freeze(input.readings.map(reading => {
|
|
193
|
+
if (seen.has(reading.entryId))
|
|
194
|
+
throw new TypeError('vital_sign_entry_id_duplicate');
|
|
195
|
+
seen.add(reading.entryId);
|
|
196
|
+
return buildVitalSignObservation({
|
|
197
|
+
...reading,
|
|
198
|
+
subjectReference: input.subjectReference,
|
|
199
|
+
deviceReference: input.deviceReference,
|
|
200
|
+
status: 'preliminary',
|
|
201
|
+
});
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
function scalarDefinition(kind) {
|
|
205
|
+
switch (kind) {
|
|
206
|
+
case 'temperature': return { coding: VitalSignsCodes.BodyTemperature, unit: VitalSignUnit.temperature };
|
|
207
|
+
case 'heart-rate': return { coding: VitalSignsCodes.HeartRate, unit: VitalSignUnit.heartRate };
|
|
208
|
+
case 'oxygen-saturation': return { coding: VitalSignsCodes.OxygenSaturation, unit: VitalSignUnit.oxygenSaturation };
|
|
209
|
+
case 'respiratory-rate': return { coding: VitalSignsCodes.RespiratoryRate, unit: VitalSignUnit.respiratoryRate };
|
|
210
|
+
case 'body-weight': return { coding: VitalSignsCodes.BodyWeight, unit: VitalSignUnit.bodyWeight };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function normalizeDateTime(value) {
|
|
214
|
+
const date = new Date(value);
|
|
215
|
+
if (!value.trim() || Number.isNaN(date.valueOf()))
|
|
216
|
+
throw new TypeError('vital_sign_measured_at_invalid');
|
|
217
|
+
return date.toISOString();
|
|
218
|
+
}
|
|
219
|
+
function validPositive(value) {
|
|
220
|
+
return Number.isFinite(value) && value > 0;
|
|
221
|
+
}
|
|
222
|
+
function required(value, error) {
|
|
223
|
+
const normalized = value.trim();
|
|
224
|
+
if (!normalized || normalized.includes(','))
|
|
225
|
+
throw new TypeError(error);
|
|
226
|
+
return normalized;
|
|
227
|
+
}
|
|
228
|
+
/** Reusable neutral fixtures; consumers and tests must not duplicate wire examples. */
|
|
229
|
+
export const VITAL_SIGN_EXAMPLES = Object.freeze({
|
|
230
|
+
subjectReference: 'Patient/example-subject',
|
|
231
|
+
deviceReference: 'Device/example-home-monitor',
|
|
232
|
+
bloodPressure: Object.freeze({ entryId: 'bp-example-1', measuredAt: '2026-09-21T08:15:00-07:00', systolic: 121, diastolic: 79 }),
|
|
233
|
+
heartRateBatch: Object.freeze([
|
|
234
|
+
Object.freeze({ entryId: 'hr-example-1', kind: 'heart-rate', measuredAt: '2026-09-20T08:00:00Z', value: 68 }),
|
|
235
|
+
Object.freeze({ entryId: 'hr-example-2', kind: 'heart-rate', measuredAt: '2026-09-21T08:00:00Z', value: 72 }),
|
|
236
|
+
]),
|
|
237
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# UHC FHIR utilities migration
|
|
2
|
+
|
|
3
|
+
`uhc-fhir-utils-typescript` is an archaeological source, not a dependency of
|
|
4
|
+
this package. Its useful behavior must be recovered behind neutral, typed and
|
|
5
|
+
version-explicit contracts. Its product branding, obsolete security choices
|
|
6
|
+
and transport assumptions must not be copied.
|
|
7
|
+
|
|
8
|
+
## Migration rules
|
|
9
|
+
|
|
10
|
+
1. Add the smallest failing contract test here before porting behavior.
|
|
11
|
+
2. Use official FHIR R4/R5/R6 structures at import/export boundaries and flat
|
|
12
|
+
claims as the version-neutral representation between those boundaries.
|
|
13
|
+
3. Copy no raw claim, terminology, identifier or profile literal into a
|
|
14
|
+
consumer. Define it once in its owning neutral or sector data package.
|
|
15
|
+
4. Preserve historical inputs with explicit read adapters. New writers emit
|
|
16
|
+
only the current canonical representation.
|
|
17
|
+
5. Do not change `gdc-*` while those repositories are frozen. They are
|
|
18
|
+
read-only compatibility evidence until every consumer uses this package.
|
|
19
|
+
|
|
20
|
+
## Reuse, rewrite or retire
|
|
21
|
+
|
|
22
|
+
| Old area | Decision | Current destination or gate |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| `Bundle` and `Composition` traversal | Rewrite from tests | Neutral graph and explicit R4/R5/R6 adapters; preserve author, attester and section boundaries |
|
|
25
|
+
| `CodeableConcept`, `Identifier`, `Reference`, `Quantity` | Rewrite and reuse behavior | Small typed value modules with round-trip tests |
|
|
26
|
+
| `Observation` and resource parameter models | Rewrite | Canonical claim catalogs plus version-specific projection adapters |
|
|
27
|
+
| `Parameters` conversion | Rewrite | A typed FHIR Parameters module; never a transport-specific envelope |
|
|
28
|
+
| Anonymization field inventory | Test input only | A separately reviewed policy contract; old field lists do not become policy automatically |
|
|
29
|
+
| ATC, LOINC, SNOMED and EMA JSON | Validate before reuse | Terminology service fixtures, never authoritative embedded catalogs |
|
|
30
|
+
| DICOM parsing | Extract later | Separate imaging package; do not add `dicom-parser` to the neutral core |
|
|
31
|
+
| SHA-1 attachment hashing | Retire | No compatibility writer; historical read/verification belongs in a versioned adapter |
|
|
32
|
+
| `MessageHeader` transport conventions | Retire | Transport is outside the FHIR data contract |
|
|
33
|
+
| UHC-branded templates and defaults | Retire or move downstream | Product policy belongs in UHC packages, not here |
|
|
34
|
+
| Babel/Yarn/Jest build plumbing and broad `any` models | Retire | Node 24, TypeScript strict mode and `node:test` |
|
|
35
|
+
|
|
36
|
+
## Observation and component contract
|
|
37
|
+
|
|
38
|
+
FHIR blood pressure is one `Observation` with systolic and diastolic entries in
|
|
39
|
+
`Observation.component[]`. The flat representation stores aligned repeatable
|
|
40
|
+
component claims as JSON arrays because every flat value is a string and CSV
|
|
41
|
+
cannot represent arbitrary displays or text without ambiguity.
|
|
42
|
+
|
|
43
|
+
Indexing may derive one reduced internal row per component. Those rows use the
|
|
44
|
+
generic component code, numeric value and unit claims, carry no
|
|
45
|
+
`Observation.status`, and are not exported as independent FHIR Observations.
|
|
46
|
+
They retain their parent reference and ordinal but do not invent a persistent
|
|
47
|
+
identifier. A persistence adapter must preserve an existing historical
|
|
48
|
+
identifier or apply its own explicitly versioned identity policy. Canonical
|
|
49
|
+
component arrays on the parent remain the reconstruction source.
|
|
50
|
+
|
|
51
|
+
The historical `Observation.bp-systolic-number` and
|
|
52
|
+
`Observation.bp-diastolic-number` fields are accepted only when reading data
|
|
53
|
+
that has no canonical component claims. New writers and generic component
|
|
54
|
+
indexes never emit them. This prevents a new custom claim family for every
|
|
55
|
+
assessment scale.
|
|
56
|
+
|
|
57
|
+
## Remaining migration sequence
|
|
58
|
+
|
|
59
|
+
1. Inventory imports from `gdc-common-utils-ts` in SOS, Vet, UHC, portals and
|
|
60
|
+
assistants by resource family.
|
|
61
|
+
2. Move one neutral resource family at a time into this package with exact
|
|
62
|
+
compatibility vectors and R4/R5 projection tests.
|
|
63
|
+
3. Release this package, pin the immutable version in the lowest downstream
|
|
64
|
+
package, and run that consumer's complete local matrix.
|
|
65
|
+
4. Remove the corresponding frozen dependency only after no consumer imports
|
|
66
|
+
that family from it.
|
|
67
|
+
5. Migrate imaging/DICOM and terminology payloads as separate changes because
|
|
68
|
+
they have different dependencies, security rules and release gates.
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fhir-data-utils-ts",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "FHIR-version-neutral flat-claim and SearchParameter data utilities",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./coding-review-flat-claims": {
|
|
15
|
+
"types": "./dist/coding-review-flat-claims.d.ts",
|
|
16
|
+
"default": "./dist/coding-review-flat-claims.js"
|
|
17
|
+
},
|
|
18
|
+
"./flat-claim-resource-graph": {
|
|
19
|
+
"types": "./dist/flat-claim-resource-graph.d.ts",
|
|
20
|
+
"default": "./dist/flat-claim-resource-graph.js"
|
|
21
|
+
},
|
|
22
|
+
"./search-parameters": {
|
|
23
|
+
"types": "./dist/search-parameters.d.ts",
|
|
24
|
+
"default": "./dist/search-parameters.js"
|
|
25
|
+
},
|
|
26
|
+
"./observation-claims": {
|
|
27
|
+
"types": "./dist/observation-claims.d.ts",
|
|
28
|
+
"default": "./dist/observation-claims.js"
|
|
29
|
+
},
|
|
30
|
+
"./vital-sign-observations": {
|
|
31
|
+
"types": "./dist/vital-sign-observations.d.ts",
|
|
32
|
+
"default": "./dist/vital-sign-observations.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
".codex/skills",
|
|
38
|
+
"docs",
|
|
39
|
+
"README.md",
|
|
40
|
+
"CHANGELOG.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
44
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
45
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
46
|
+
"test": "npm run build && node --test tests/*.test.mjs",
|
|
47
|
+
"check": "npm run typecheck && npm test",
|
|
48
|
+
"prepack": "npm run check"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^24.10.0",
|
|
52
|
+
"typescript": "^5.9.3"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=24"
|
|
56
|
+
},
|
|
57
|
+
"private": false,
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|