fhir-data-utils-ts 0.3.1 → 0.3.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2 - 2026-09-23
4
+
5
+ - Preserve every coding identity from repeatable AllergyIntolerance and
6
+ MedicationStatement CodeableConcepts instead of retaining only `coding[0]`.
7
+ - Keep the MedicationStatement dose quantity's human `unit` separate from its
8
+ coded `system|code`, with read compatibility for records written by 0.3.1.
9
+ - Add executable regressions using the exact allergy and medication examples
10
+ from the HL7 IPS all-sections Bundle.
11
+
3
12
  ## 0.3.1 - 2026-09-21
4
13
 
5
14
  - Separate canonical claim catalogs from graph builders and move Immunization
package/README.md CHANGED
@@ -55,6 +55,14 @@ Use `fhir-data-utils-ts/claims`, `/converters`, `/fhir-bundle`,
55
55
  `/fhir-search-parameters`, `/clinical-terminology`, `/group-r5` or
56
56
  `/research-study-r5` as appropriate.
57
57
 
58
+ Repeatable `CodeableConcept.coding[]` identities are serialized in the owning
59
+ token claim as a comma-separated list of complete `system|code` tokens. The
60
+ first `*-display` remains source evidence for compatibility; authoritative
61
+ labels for every coding are resolved from each token at the terminology
62
+ boundary. FHIR `Quantity.unit` remains separate from its coded identity:
63
+ MedicationStatement dosage uses `dose-quantity-unit` for the human unit and
64
+ `dose-quantity` for `system|code`.
65
+
58
66
  ## Observation components
59
67
 
60
68
  Composite observations use a claims-first resource graph. The primary
@@ -1,7 +1,7 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/utils/convert-allergy-intolerance.ts
3
3
  import { AllergyIntoleranceClaim } from '../claims/allergy-intolerance-claims.js';
4
- import { codingFromValue, codingToValue, requireDidWeb, requireSubjectIdentifier } from './convert-shared.js';
4
+ import { codingFromValue, codingListFromCsv, codingListToCsv, codingToValue, requireDidWeb, requireSubjectIdentifier, } from './convert-shared.js';
5
5
  export function allergyIntoleranceFlatToFhirR4(claims) {
6
6
  const patient = claims[AllergyIntoleranceClaim.Subject] ?? claims[AllergyIntoleranceClaim.Patient];
7
7
  const recorder = claims[AllergyIntoleranceClaim.Recorder];
@@ -23,9 +23,9 @@ export function allergyIntoleranceFlatToFhirR4(claims) {
23
23
  patient: { reference: patient },
24
24
  code: claims[AllergyIntoleranceClaim.Code]
25
25
  ? {
26
- coding: codingFromValue(claims[AllergyIntoleranceClaim.Code])?.map((coding) => ({
26
+ coding: codingListFromCsv(claims[AllergyIntoleranceClaim.Code])?.map((coding, index) => ({
27
27
  ...coding,
28
- ...(claims[AllergyIntoleranceClaim.CodeDisplay]
28
+ ...(index === 0 && claims[AllergyIntoleranceClaim.CodeDisplay]
29
29
  ? { display: claims[AllergyIntoleranceClaim.CodeDisplay] }
30
30
  : {}),
31
31
  })),
@@ -64,7 +64,7 @@ export function allergyIntoleranceFhirR4ToFlat(resource) {
64
64
  [AllergyIntoleranceClaim.Identifier]: resource.identifier?.[0]?.value,
65
65
  [AllergyIntoleranceClaim.Subject]: subject,
66
66
  [AllergyIntoleranceClaim.Patient]: subject,
67
- [AllergyIntoleranceClaim.Code]: codingToValue(code?.coding?.[0]) || code?.text,
67
+ [AllergyIntoleranceClaim.Code]: codingListToCsv(code?.coding) || code?.text,
68
68
  [AllergyIntoleranceClaim.CodeText]: code?.text,
69
69
  [AllergyIntoleranceClaim.CodeDisplay]: code?.coding?.[0]?.display,
70
70
  [AllergyIntoleranceClaim.ClinicalStatus]: resource.clinicalStatus?.coding?.[0]?.code,
@@ -1,7 +1,18 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/utils/convert-medication-statement.ts
3
- import { MedicationStatementClaim } from '../claims/medication-statement-claims.js';
4
- import { codingFromValue, codingToValue, requireClaim } from './convert-shared.js';
3
+ import { MedicationStatementClaim, MedicationStatementClaimsFhirApiExtended, } from '../claims/medication-statement-claims.js';
4
+ import { codingFromValue, codingListFromCsv, codingListToCsv, codingToValue, requireClaim, } from './convert-shared.js';
5
+ function isCodingToken(value) {
6
+ return Boolean(value?.includes('|'));
7
+ }
8
+ function conceptCodings(claims) {
9
+ return codingListFromCsv(claims[MedicationStatementClaim.Code])?.map((coding, index) => ({
10
+ ...coding,
11
+ ...(index === 0 && claims[MedicationStatementClaim.CodeDisplay]
12
+ ? { display: claims[MedicationStatementClaim.CodeDisplay] }
13
+ : {}),
14
+ }));
15
+ }
5
16
  export function medicationStatementFlatToFhirR4(claims) {
6
17
  const subject = requireClaim(claims, MedicationStatementClaim.Subject);
7
18
  const status = requireClaim(claims, MedicationStatementClaim.Status);
@@ -24,10 +35,7 @@ export function medicationStatementFlatToFhirR4(claims) {
24
35
  id: containedMedicationId,
25
36
  identifier: medicationIdentifier ? [{ value: medicationIdentifier }] : undefined,
26
37
  code: claims[MedicationStatementClaim.Code] || medicationText ? {
27
- coding: codingFromValue(claims[MedicationStatementClaim.Code])?.map((coding) => ({
28
- ...coding,
29
- ...(claims[MedicationStatementClaim.CodeDisplay] ? { display: claims[MedicationStatementClaim.CodeDisplay] } : {}),
30
- })),
38
+ coding: conceptCodings(claims),
31
39
  ...(medicationText ? { text: medicationText } : {}),
32
40
  } : undefined,
33
41
  batch: medicationSerialNumber || medicationExpirationDate ? {
@@ -40,7 +48,10 @@ export function medicationStatementFlatToFhirR4(claims) {
40
48
  const dosagePeriod = claims[MedicationStatementClaim.TimingPeriod];
41
49
  const dosagePeriodUnit = claims[MedicationStatementClaim.TimingPeriodUnit];
42
50
  const doseValue = claims[MedicationStatementClaim.DoseQuantityValue];
43
- const doseUnit = claims[MedicationStatementClaim.DoseQuantityUnit];
51
+ const storedDoseUnit = claims[MedicationStatementClaim.DoseQuantityUnit];
52
+ const doseUnitToken = claims[MedicationStatementClaimsFhirApiExtended.DoseQuantity]
53
+ || (isCodingToken(storedDoseUnit) ? storedDoseUnit : undefined);
54
+ const doseUnitText = isCodingToken(storedDoseUnit) ? undefined : storedDoseUnit;
44
55
  const hasDosage = claims[MedicationStatementClaim.DosageInstruction] || dosageRoute || dosageFrequency || dosagePeriod || doseValue;
45
56
  return {
46
57
  resourceType: 'MedicationStatement',
@@ -54,10 +65,7 @@ export function medicationStatementFlatToFhirR4(claims) {
54
65
  } : undefined,
55
66
  medicationCodeableConcept: !medicationReference && !hasContainedMedication && claims[MedicationStatementClaim.Code]
56
67
  ? {
57
- coding: codingFromValue(claims[MedicationStatementClaim.Code])?.map((coding) => ({
58
- ...coding,
59
- ...(claims[MedicationStatementClaim.CodeDisplay] ? { display: claims[MedicationStatementClaim.CodeDisplay] } : {}),
60
- })),
68
+ coding: conceptCodings(claims),
61
69
  ...(medicationText ? { text: medicationText } : {}),
62
70
  }
63
71
  : (!medicationReference && !hasContainedMedication && medicationText ? { text: medicationText } : undefined),
@@ -76,8 +84,8 @@ export function medicationStatementFlatToFhirR4(claims) {
76
84
  } } : undefined,
77
85
  doseAndRate: doseValue ? [{ doseQuantity: {
78
86
  value: Number(doseValue),
79
- unit: doseUnit,
80
- ...(codingFromValue(doseUnit)?.[0] || {}),
87
+ ...(doseUnitText ? { unit: doseUnitText } : {}),
88
+ ...(codingFromValue(doseUnitToken)?.[0] || {}),
81
89
  } }] : undefined,
82
90
  }] : undefined,
83
91
  note: claims[MedicationStatementClaim.Note] ? [{ text: claims[MedicationStatementClaim.Note] }] : undefined,
@@ -94,7 +102,8 @@ export function medicationStatementFhirR4ToFlat(resource) {
94
102
  const containedMedicationText = containedMedicationConcept?.text;
95
103
  const batch = containedMedication?.batch;
96
104
  const medicationConcept = resource.medicationCodeableConcept;
97
- const medicationCode = codingToValue(medicationConcept?.coding?.[0] || containedMedicationConcept?.coding?.[0]);
105
+ const medicationCodings = medicationConcept?.coding || containedMedicationConcept?.coding;
106
+ const medicationCode = codingListToCsv(medicationCodings);
98
107
  const medicationText = containedMedicationText || medicationConcept?.text || undefined;
99
108
  const dosage = resource.dosage?.[0];
100
109
  const repeat = dosage?.timing
@@ -119,7 +128,10 @@ export function medicationStatementFhirR4ToFlat(resource) {
119
128
  [MedicationStatementClaim.Source]: resource.informationSource?.reference,
120
129
  [MedicationStatementClaim.DosageRoute]: codingToValue(routeCoding),
121
130
  [MedicationStatementClaim.DoseQuantityValue]: doseQuantity?.value === undefined ? undefined : String(doseQuantity.value),
122
- [MedicationStatementClaim.DoseQuantityUnit]: codingToValue(doseQuantity?.system || doseQuantity?.code ? { system: doseQuantity?.system, code: doseQuantity?.code } : undefined) || doseQuantity?.unit,
131
+ [MedicationStatementClaimsFhirApiExtended.DoseQuantity]: codingToValue(doseQuantity?.system || doseQuantity?.code
132
+ ? { system: doseQuantity?.system, code: doseQuantity?.code }
133
+ : undefined),
134
+ [MedicationStatementClaim.DoseQuantityUnit]: doseQuantity?.unit,
123
135
  [MedicationStatementClaim.TimingFrequency]: repeat?.frequency === undefined ? undefined : String(repeat.frequency),
124
136
  [MedicationStatementClaim.TimingPeriod]: repeat?.period === undefined ? undefined : String(repeat.period),
125
137
  [MedicationStatementClaim.TimingPeriodUnit]: repeat?.periodUnit,
@@ -6,6 +6,11 @@ export declare function codingFromValue(value?: string): Array<{
6
6
  system?: string;
7
7
  code: string;
8
8
  }> | undefined;
9
+ /** Convert one canonical comma-separated token list into FHIR Coding entries. */
10
+ export declare function codingListFromCsv(value?: string): Array<{
11
+ system?: string;
12
+ code: string;
13
+ }> | undefined;
9
14
  export declare function codingToValue(coding?: {
10
15
  system?: string;
11
16
  code?: string;
@@ -11,6 +11,15 @@ export function codingFromValue(value) {
11
11
  return [{ code: system }];
12
12
  return system ? [{ system, code }] : [{ code }];
13
13
  }
14
+ /** Convert one canonical comma-separated token list into FHIR Coding entries. */
15
+ export function codingListFromCsv(value) {
16
+ const codings = String(value || '')
17
+ .split(',')
18
+ .map((token) => token.trim())
19
+ .filter(Boolean)
20
+ .flatMap((token) => codingFromValue(token) ?? []);
21
+ return codings.length ? codings : undefined;
22
+ }
14
23
  export function codingToValue(coding) {
15
24
  if (!coding?.code)
16
25
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhir-data-utils-ts",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "FHIR-version-neutral flat-claim and SearchParameter data utilities",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",