fhir-data-utils-ts 0.4.0 → 0.6.1

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.
Files changed (82) hide show
  1. package/.codex/skills/govern-fhir-data-utilities/SKILL.md +35 -0
  2. package/CHANGELOG.md +61 -0
  3. package/README.md +35 -0
  4. package/dist/appointment-communication-test-data.d.ts +63 -0
  5. package/dist/appointment-communication-test-data.js +77 -0
  6. package/dist/builders/department-service-graph.d.ts +32 -0
  7. package/dist/builders/department-service-graph.js +102 -0
  8. package/dist/builders/device-compliance-graph.d.ts +51 -0
  9. package/dist/builders/device-compliance-graph.js +115 -0
  10. package/dist/builders/index.d.ts +3 -0
  11. package/dist/builders/index.js +3 -0
  12. package/dist/builders/service-schedule-graph.d.ts +58 -0
  13. package/dist/builders/service-schedule-graph.js +149 -0
  14. package/dist/care-emergency-alert.d.ts +18 -0
  15. package/dist/care-emergency-alert.js +29 -1
  16. package/dist/claims/appointment-claims.d.ts +6 -0
  17. package/dist/claims/appointment-claims.js +6 -0
  18. package/dist/claims/device-metric-claims.d.ts +16 -0
  19. package/dist/claims/device-metric-claims.js +25 -0
  20. package/dist/claims/encounter-claims.d.ts +15 -0
  21. package/dist/claims/encounter-claims.js +19 -0
  22. package/dist/claims/encounter-history-claims.d.ts +20 -0
  23. package/dist/claims/encounter-history-claims.js +34 -0
  24. package/dist/claims/healthcare-service-claims.d.ts +32 -0
  25. package/dist/claims/healthcare-service-claims.js +54 -0
  26. package/dist/claims/index.d.ts +5 -0
  27. package/dist/claims/index.js +5 -0
  28. package/dist/claims/location-claims.d.ts +3 -0
  29. package/dist/claims/location-claims.js +6 -0
  30. package/dist/claims/organization-claims.d.ts +3 -1
  31. package/dist/claims/organization-claims.js +4 -1
  32. package/dist/claims/procedure-claims.d.ts +2 -0
  33. package/dist/claims/procedure-claims.js +4 -0
  34. package/dist/claims/regulated-authorization-claims.d.ts +17 -0
  35. package/dist/claims/regulated-authorization-claims.js +27 -0
  36. package/dist/claims/schedule-slot-claims.d.ts +45 -0
  37. package/dist/claims/schedule-slot-claims.js +70 -0
  38. package/dist/constants/fhir-resource-types.d.ts +10 -0
  39. package/dist/constants/fhir-resource-types.js +4 -0
  40. package/dist/converters/convert-appointment-communication.d.ts +14 -0
  41. package/dist/converters/convert-appointment-communication.js +97 -0
  42. package/dist/converters/convert-appointment.js +16 -7
  43. package/dist/converters/convert-device-metric.d.ts +3 -0
  44. package/dist/converters/convert-device-metric.js +37 -0
  45. package/dist/converters/convert-document-reference.js +5 -0
  46. package/dist/converters/convert-encounter-history.d.ts +5 -0
  47. package/dist/converters/convert-encounter-history.js +83 -0
  48. package/dist/converters/convert-encounter.d.ts +4 -0
  49. package/dist/converters/convert-encounter.js +73 -1
  50. package/dist/converters/convert-healthcare-service.d.ts +7 -0
  51. package/dist/converters/convert-healthcare-service.js +60 -0
  52. package/dist/converters/convert-location.d.ts +4 -0
  53. package/dist/converters/convert-location.js +41 -3
  54. package/dist/converters/convert-organization.d.ts +2 -0
  55. package/dist/converters/convert-organization.js +20 -3
  56. package/dist/converters/convert-procedure.d.ts +2 -0
  57. package/dist/converters/convert-procedure.js +21 -0
  58. package/dist/converters/convert-regulated-authorization.d.ts +3 -0
  59. package/dist/converters/convert-regulated-authorization.js +34 -0
  60. package/dist/converters/convert-schedule-slot.d.ts +9 -0
  61. package/dist/converters/convert-schedule-slot.js +96 -0
  62. package/dist/converters/convert-shared.d.ts +10 -0
  63. package/dist/converters/convert-shared.js +19 -0
  64. package/dist/converters/index.d.ts +7 -0
  65. package/dist/converters/index.js +7 -0
  66. package/dist/converters/materialize-claims-first-resource.d.ts +16 -0
  67. package/dist/converters/materialize-claims-first-resource.js +65 -0
  68. package/dist/encounter-transfer-test-data.d.ts +40 -0
  69. package/dist/encounter-transfer-test-data.js +37 -0
  70. package/dist/fhir-bundle.d.ts +21 -0
  71. package/dist/fhir-bundle.js +74 -0
  72. package/dist/group-r5.d.ts +12 -0
  73. package/dist/group-r5.js +20 -0
  74. package/dist/healthcare-service-specialty-map.d.ts +32 -0
  75. package/dist/healthcare-service-specialty-map.js +151 -0
  76. package/dist/index.d.ts +3 -0
  77. package/dist/index.js +3 -0
  78. package/docs/CLINICAL_TYPED_ACCESSOR_BOUNDARY.md +44 -0
  79. package/docs/COMMUNICATION_BUNDLE_CONTRACT.md +61 -0
  80. package/docs/HEALTHCARE_SERVICE_DEPARTMENT_CATALOG.md +190 -0
  81. package/docs/UHC_FHIR_UTILS_MIGRATION.md +20 -1
  82. package/package.json +5 -1
@@ -0,0 +1,83 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { EncounterHistoryClaim } from '../claims/encounter-history-claims.js';
3
+ import { EncounterStatusR5 } from '../claims/encounter-claims.js';
4
+ import { codingFromValue, codingToValue, referenceToValue } from './convert-shared.js';
5
+ const R5_STATUSES = new Set(Object.values(EncounterStatusR5));
6
+ /** Materializes canonical EncounterHistory claims as native FHIR R5. */
7
+ export function encounterHistoryFlatToFhirR5(claims) {
8
+ const status = claims[EncounterHistoryClaim.Status];
9
+ if (!status || !R5_STATUSES.has(status))
10
+ throw new TypeError('encounter_history_r5_status_invalid');
11
+ const klass = claims[EncounterHistoryClaim.Class];
12
+ if (!klass)
13
+ throw new TypeError('encounter_history_r5_class_required');
14
+ return {
15
+ resourceType: 'EncounterHistory',
16
+ identifier: claims[EncounterHistoryClaim.Identifier] ? [{ value: claims[EncounterHistoryClaim.Identifier] }] : undefined,
17
+ encounter: claims[EncounterHistoryClaim.Encounter] ? { reference: claims[EncounterHistoryClaim.Encounter] } : undefined,
18
+ status,
19
+ class: { coding: codingFromValue(klass) },
20
+ type: concepts(claims[EncounterHistoryClaim.Type]),
21
+ serviceType: codeableReferences(claims[EncounterHistoryClaim.ServiceType]),
22
+ subject: claims[EncounterHistoryClaim.Subject] ? { reference: claims[EncounterHistoryClaim.Subject] } : undefined,
23
+ subjectStatus: claims[EncounterHistoryClaim.SubjectStatus] ? { coding: codingFromValue(claims[EncounterHistoryClaim.SubjectStatus]) } : undefined,
24
+ actualPeriod: period(claims[EncounterHistoryClaim.ActualPeriodStart], claims[EncounterHistoryClaim.ActualPeriodEnd]),
25
+ plannedStartDate: claims[EncounterHistoryClaim.PlannedStartDate],
26
+ plannedEndDate: claims[EncounterHistoryClaim.PlannedEndDate],
27
+ location: locations(claims[EncounterHistoryClaim.Location], claims[EncounterHistoryClaim.LocationForm]),
28
+ };
29
+ }
30
+ /** Extracts supported native FHIR R5 EncounterHistory fields into flat claims. */
31
+ export function encounterHistoryFhirR5ToFlat(resource) {
32
+ const klass = resource.class;
33
+ const actualPeriod = resource.actualPeriod;
34
+ const location = resource.location?.[0];
35
+ return {
36
+ [EncounterHistoryClaim.Identifier]: resource.identifier?.[0]?.value,
37
+ [EncounterHistoryClaim.Encounter]: referenceToValue(resource.encounter),
38
+ [EncounterHistoryClaim.Status]: resource.status,
39
+ [EncounterHistoryClaim.Class]: codingToValue(klass?.coding?.[0]),
40
+ [EncounterHistoryClaim.Type]: conceptsToCsv(resource.type),
41
+ [EncounterHistoryClaim.ServiceType]: codeableReferencesToCsv(resource.serviceType),
42
+ [EncounterHistoryClaim.Subject]: referenceToValue(resource.subject),
43
+ [EncounterHistoryClaim.SubjectStatus]: codingToValue(resource.subjectStatus?.coding?.[0]),
44
+ [EncounterHistoryClaim.ActualPeriodStart]: actualPeriod?.start,
45
+ [EncounterHistoryClaim.ActualPeriodEnd]: actualPeriod?.end,
46
+ [EncounterHistoryClaim.PlannedStartDate]: resource.plannedStartDate,
47
+ [EncounterHistoryClaim.PlannedEndDate]: resource.plannedEndDate,
48
+ [EncounterHistoryClaim.Location]: location?.location?.reference,
49
+ [EncounterHistoryClaim.LocationForm]: codingToValue(location?.form?.coding?.[0]),
50
+ };
51
+ }
52
+ function period(start, end) {
53
+ return start || end ? { start, end } : undefined;
54
+ }
55
+ function concepts(value) {
56
+ const values = value?.split(',').map(item => item.trim()).filter(Boolean) ?? [];
57
+ return values.length ? values.map(item => ({ coding: codingFromValue(item) })) : undefined;
58
+ }
59
+ function conceptsToCsv(value) {
60
+ if (!Array.isArray(value))
61
+ return undefined;
62
+ const values = value.map(item => codingToValue(item?.coding?.[0])).filter(Boolean);
63
+ return values.length ? values.join(',') : undefined;
64
+ }
65
+ function codeableReferences(value) {
66
+ const values = value?.split(',').map(item => item.trim()).filter(Boolean) ?? [];
67
+ return values.length ? values.map(reference => ({ reference: { reference } })) : undefined;
68
+ }
69
+ function codeableReferencesToCsv(value) {
70
+ if (!Array.isArray(value))
71
+ return undefined;
72
+ const values = value.map(item => referenceToValue(item?.reference)).filter(Boolean);
73
+ return values.length ? values.join(',') : undefined;
74
+ }
75
+ function locations(locationValue, formValue) {
76
+ const values = locationValue?.split(',').map(item => item.trim()).filter(Boolean) ?? [];
77
+ if (!values.length)
78
+ return undefined;
79
+ return values.map((reference, index) => ({
80
+ location: { reference },
81
+ form: index === 0 && formValue ? { coding: codingFromValue(formValue) } : undefined,
82
+ }));
83
+ }
@@ -1,3 +1,7 @@
1
1
  import type { FhirResource, FlatClaims } from './convert-shared.js';
2
2
  export declare function encounterFlatToFhirR4(claims: FlatClaims): FhirResource;
3
3
  export declare function encounterFhirR4ToFlat(resource: FhirResource): FlatClaims;
4
+ /** Materializes canonical Encounter claims as native FHIR R5. */
5
+ export declare function encounterFlatToFhirR5(claims: FlatClaims): FhirResource;
6
+ /** Extracts the supported R5 Encounter fields into canonical flat claims. */
7
+ export declare function encounterFhirR5ToFlat(resource: FhirResource): FlatClaims;
@@ -1,6 +1,6 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/utils/convert-encounter.ts
3
- import { EncounterClaim } from '../claims/encounter-claims.js';
3
+ import { EncounterClaim, EncounterStatusR5 } from '../claims/encounter-claims.js';
4
4
  import { codingFromValue, codingToValue, referenceListToCsv, referenceToValue } from './convert-shared.js';
5
5
  export function encounterFlatToFhirR4(claims) {
6
6
  const subject = claims[EncounterClaim.Subject] ?? claims[EncounterClaim.Patient];
@@ -72,3 +72,75 @@ export function encounterFhirR4ToFlat(resource) {
72
72
  [EncounterClaim.PartOf]: referenceToValue(resource.partOf),
73
73
  };
74
74
  }
75
+ const R5_STATUSES = new Set(Object.values(EncounterStatusR5));
76
+ /** Materializes canonical Encounter claims as native FHIR R5. */
77
+ export function encounterFlatToFhirR5(claims) {
78
+ const status = claims[EncounterClaim.Status];
79
+ if (status && !R5_STATUSES.has(status))
80
+ throw new TypeError('encounter_r5_status_invalid');
81
+ const subject = claims[EncounterClaim.Subject] ?? claims[EncounterClaim.Patient];
82
+ const actualPeriodStart = claims[EncounterClaim.ActualPeriodStart] ?? claims[EncounterClaim.PeriodStart];
83
+ const actualPeriodEnd = claims[EncounterClaim.ActualPeriodEnd] ?? claims[EncounterClaim.PeriodEnd];
84
+ return {
85
+ resourceType: 'Encounter',
86
+ identifier: claims[EncounterClaim.Identifier] ? [{ value: claims[EncounterClaim.Identifier] }] : undefined,
87
+ appointment: referenceList(claims[EncounterClaim.Appointment]),
88
+ status,
89
+ class: claims[EncounterClaim.Class] ? { coding: codingFromValue(claims[EncounterClaim.Class]) } : undefined,
90
+ type: claims[EncounterClaim.Type] ? [{ coding: codingFromValue(claims[EncounterClaim.Type]) }] : undefined,
91
+ serviceType: codeableReferenceList(claims[EncounterClaim.ServiceType]),
92
+ subject: subject ? { reference: subject } : undefined,
93
+ participant: claims[EncounterClaim.Participant]
94
+ ? claims[EncounterClaim.Participant].split(',').map(reference => ({ actor: { reference: reference.trim() } }))
95
+ : undefined,
96
+ serviceProvider: claims[EncounterClaim.ServiceProvider] ? { reference: claims[EncounterClaim.ServiceProvider] } : undefined,
97
+ actualPeriod: (actualPeriodStart || actualPeriodEnd) ? { start: actualPeriodStart, end: actualPeriodEnd } : undefined,
98
+ location: claims[EncounterClaim.Location]
99
+ ? claims[EncounterClaim.Location].split(',').map(reference => ({ location: { reference: reference.trim() } }))
100
+ : undefined,
101
+ partOf: claims[EncounterClaim.PartOf] ? { reference: claims[EncounterClaim.PartOf] } : undefined,
102
+ };
103
+ }
104
+ /** Extracts the supported R5 Encounter fields into canonical flat claims. */
105
+ export function encounterFhirR5ToFlat(resource) {
106
+ const klass = resource.class;
107
+ const actualPeriod = resource.actualPeriod;
108
+ return {
109
+ [EncounterClaim.Identifier]: resource.identifier?.[0]?.value,
110
+ [EncounterClaim.Appointment]: referenceListToCsv(resource.appointment),
111
+ [EncounterClaim.Status]: resource.status,
112
+ [EncounterClaim.Class]: codingToValue(klass?.coding?.[0]),
113
+ [EncounterClaim.Type]: codingToValue(resource.type?.[0]?.coding?.[0]),
114
+ [EncounterClaim.ServiceType]: codeableReferencesToCsv(resource.serviceType),
115
+ [EncounterClaim.Subject]: referenceToValue(resource.subject),
116
+ [EncounterClaim.Patient]: referenceToValue(resource.subject),
117
+ [EncounterClaim.Participant]: Array.isArray(resource.participant)
118
+ ? resource.participant.map(item => item.actor?.reference).filter(Boolean).join(',') || undefined
119
+ : undefined,
120
+ [EncounterClaim.ServiceProvider]: referenceToValue(resource.serviceProvider),
121
+ [EncounterClaim.ActualPeriodStart]: actualPeriod?.start,
122
+ [EncounterClaim.ActualPeriodEnd]: actualPeriod?.end,
123
+ [EncounterClaim.Location]: Array.isArray(resource.location)
124
+ ? resource.location.map(item => item.location?.reference).filter(Boolean).join(',') || undefined
125
+ : undefined,
126
+ [EncounterClaim.PartOf]: referenceToValue(resource.partOf),
127
+ };
128
+ }
129
+ function referenceList(value) {
130
+ const references = value?.split(',').map(reference => reference.trim()).filter(Boolean) ?? [];
131
+ return references.length ? references.map(reference => ({ reference })) : undefined;
132
+ }
133
+ function codeableReferenceList(value) {
134
+ const references = referenceList(value);
135
+ return references?.map(reference => ({ reference }));
136
+ }
137
+ function codeableReferencesToCsv(value) {
138
+ if (!Array.isArray(value))
139
+ return undefined;
140
+ const references = value.map(item => {
141
+ if (!item || typeof item !== 'object')
142
+ return undefined;
143
+ return referenceToValue(item.reference);
144
+ }).filter((item) => Boolean(item));
145
+ return references.length ? references.join(',') : undefined;
146
+ }
@@ -0,0 +1,7 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared.js';
2
+ /** Materializes canonical HealthcareService claims as native FHIR R4. */
3
+ export declare function healthcareServiceFlatToFhirR4(claims: FlatClaims): FhirResource;
4
+ /** Extracts only official R4 HealthcareService SearchParameters into flat claims. */
5
+ export declare function healthcareServiceFhirR4ToFlat(resource: FhirResource): FlatClaims;
6
+ /** Materializes canonical HealthcareService claims as native FHIR R5. */
7
+ export declare const healthcareServiceFlatToFhirR5: typeof healthcareServiceFlatToFhirR4;
@@ -0,0 +1,60 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { HealthcareServiceClaim } from '../claims/healthcare-service-claims.js';
3
+ import { codingListFromCsv, codingListToCsv, identifierFromValue, identifierToValue, referenceListToCsv, referenceToValue, } from './convert-shared.js';
4
+ const conceptsFromCsv = (value) => {
5
+ const coding = codingListFromCsv(value);
6
+ return coding?.map(item => ({ coding: [item] }));
7
+ };
8
+ const conceptsToCsv = (value) => codingListToCsv((Array.isArray(value) ? value : [])
9
+ .flatMap(item => item && typeof item === 'object' && Array.isArray(item.coding)
10
+ ? item.coding ?? []
11
+ : []));
12
+ const referencesFromCsv = (value) => {
13
+ const references = String(value || '').split(',').map(item => item.trim()).filter(Boolean);
14
+ return references.length ? references.map(reference => ({ reference })) : undefined;
15
+ };
16
+ /** Materializes canonical HealthcareService claims as native FHIR R4. */
17
+ export function healthcareServiceFlatToFhirR4(claims) {
18
+ return {
19
+ resourceType: 'HealthcareService',
20
+ identifier: claims[HealthcareServiceClaim.Identifier]
21
+ ? [identifierFromValue(claims[HealthcareServiceClaim.Identifier])]
22
+ : undefined,
23
+ active: claims[HealthcareServiceClaim.Active] === undefined
24
+ ? undefined
25
+ : claims[HealthcareServiceClaim.Active] === 'true',
26
+ providedBy: claims[HealthcareServiceClaim.Organization]
27
+ ? { reference: claims[HealthcareServiceClaim.Organization] }
28
+ : undefined,
29
+ category: conceptsFromCsv(claims[HealthcareServiceClaim.ServiceCategory]),
30
+ type: conceptsFromCsv(claims[HealthcareServiceClaim.ServiceType]),
31
+ specialty: conceptsFromCsv(claims[HealthcareServiceClaim.Specialty]),
32
+ location: referencesFromCsv(claims[HealthcareServiceClaim.Location]),
33
+ name: claims[HealthcareServiceClaim.Name],
34
+ comment: claims[HealthcareServiceClaim.Comment],
35
+ coverageArea: referencesFromCsv(claims[HealthcareServiceClaim.CoverageArea]),
36
+ program: conceptsFromCsv(claims[HealthcareServiceClaim.Program]),
37
+ characteristic: conceptsFromCsv(claims[HealthcareServiceClaim.Characteristic]),
38
+ endpoint: referencesFromCsv(claims[HealthcareServiceClaim.Endpoint]),
39
+ };
40
+ }
41
+ /** Extracts only official R4 HealthcareService SearchParameters into flat claims. */
42
+ export function healthcareServiceFhirR4ToFlat(resource) {
43
+ return {
44
+ [HealthcareServiceClaim.Active]: resource.active === undefined ? undefined : String(resource.active),
45
+ [HealthcareServiceClaim.Characteristic]: conceptsToCsv(resource.characteristic),
46
+ [HealthcareServiceClaim.CoverageArea]: referenceListToCsv(resource.coverageArea),
47
+ [HealthcareServiceClaim.Endpoint]: referenceListToCsv(resource.endpoint),
48
+ [HealthcareServiceClaim.Identifier]: identifierToValue(resource.identifier?.[0]),
49
+ [HealthcareServiceClaim.Location]: referenceListToCsv(resource.location),
50
+ [HealthcareServiceClaim.Name]: resource.name,
51
+ [HealthcareServiceClaim.Comment]: resource.comment,
52
+ [HealthcareServiceClaim.Organization]: referenceToValue(resource.providedBy),
53
+ [HealthcareServiceClaim.Program]: conceptsToCsv(resource.program),
54
+ [HealthcareServiceClaim.ServiceCategory]: conceptsToCsv(resource.category),
55
+ [HealthcareServiceClaim.ServiceType]: conceptsToCsv(resource.type),
56
+ [HealthcareServiceClaim.Specialty]: conceptsToCsv(resource.specialty),
57
+ };
58
+ }
59
+ /** Materializes canonical HealthcareService claims as native FHIR R5. */
60
+ export const healthcareServiceFlatToFhirR5 = healthcareServiceFlatToFhirR4;
@@ -1,3 +1,7 @@
1
1
  import type { FhirResource, FlatClaims } from './convert-shared.js';
2
2
  export declare function locationFlatToFhirR4(claims: FlatClaims): FhirResource;
3
3
  export declare function locationFhirR4ToFlat(resource: FhirResource): FlatClaims;
4
+ /** Materializes canonical Location claims as native FHIR R5 (`form`, not the removed R4 `physicalType`). */
5
+ export declare function locationFlatToFhirR5(claims: FlatClaims): FhirResource;
6
+ /** Extracts the version-neutral Location claims from native FHIR R5. */
7
+ export declare function locationFhirR5ToFlat(resource: FhirResource): FlatClaims;
@@ -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/convert/convert-location.ts
3
3
  import { LocationClaim } from '../claims/location-claims.js';
4
- import { codingFromValue, codingToValue, referenceToValue } from './convert-shared.js';
4
+ import { codingFromValue, codingToValue, identifierFromValue, identifierToValue, referenceToValue } from './convert-shared.js';
5
5
  function splitCsv(value) {
6
6
  return String(value || '')
7
7
  .split(',')
@@ -11,7 +11,7 @@ function splitCsv(value) {
11
11
  export function locationFlatToFhirR4(claims) {
12
12
  return {
13
13
  resourceType: 'Location',
14
- identifier: claims[LocationClaim.Identifier] ? [{ value: claims[LocationClaim.Identifier] }] : undefined,
14
+ identifier: claims[LocationClaim.Identifier] ? [identifierFromValue(claims[LocationClaim.Identifier])] : undefined,
15
15
  status: claims[LocationClaim.Status],
16
16
  name: claims[LocationClaim.Name],
17
17
  description: claims[LocationClaim.Description],
@@ -22,6 +22,15 @@ export function locationFlatToFhirR4(claims) {
22
22
  physicalType: claims[LocationClaim.PhysicalType] ? { coding: codingFromValue(claims[LocationClaim.PhysicalType]) } : undefined,
23
23
  managingOrganization: claims[LocationClaim.ManagingOrganization] ? { reference: claims[LocationClaim.ManagingOrganization] } : undefined,
24
24
  partOf: claims[LocationClaim.PartOf] ? { reference: claims[LocationClaim.PartOf] } : undefined,
25
+ position: claims[LocationClaim.PositionLongitude] !== undefined && claims[LocationClaim.PositionLatitude] !== undefined
26
+ ? {
27
+ longitude: Number(claims[LocationClaim.PositionLongitude]),
28
+ latitude: Number(claims[LocationClaim.PositionLatitude]),
29
+ ...(claims[LocationClaim.PositionAltitude] !== undefined
30
+ ? { altitude: Number(claims[LocationClaim.PositionAltitude]) }
31
+ : {}),
32
+ }
33
+ : undefined,
25
34
  };
26
35
  }
27
36
  export function locationFhirR4ToFlat(resource) {
@@ -29,7 +38,7 @@ export function locationFhirR4ToFlat(resource) {
29
38
  ?.map((item) => item?.value)
30
39
  .filter((item) => Boolean(item));
31
40
  return {
32
- [LocationClaim.Identifier]: resource.identifier?.[0]?.value,
41
+ [LocationClaim.Identifier]: identifierToValue(resource.identifier?.[0]),
33
42
  [LocationClaim.Status]: resource.status,
34
43
  [LocationClaim.Name]: resource.name,
35
44
  [LocationClaim.Description]: resource.description,
@@ -40,5 +49,34 @@ export function locationFhirR4ToFlat(resource) {
40
49
  [LocationClaim.PhysicalType]: codingToValue(resource.physicalType?.coding?.[0]),
41
50
  [LocationClaim.ManagingOrganization]: referenceToValue(resource.managingOrganization),
42
51
  [LocationClaim.PartOf]: referenceToValue(resource.partOf),
52
+ [LocationClaim.PositionLongitude]: resource.position?.longitude?.toString(),
53
+ [LocationClaim.PositionLatitude]: resource.position?.latitude?.toString(),
54
+ [LocationClaim.PositionAltitude]: resource.position?.altitude?.toString(),
55
+ };
56
+ }
57
+ /** Materializes canonical Location claims as native FHIR R5 (`form`, not the removed R4 `physicalType`). */
58
+ export function locationFlatToFhirR5(claims) {
59
+ const r4 = locationFlatToFhirR4(claims);
60
+ const { physicalType: _physicalType, telecom: _telecom, ...common } = r4;
61
+ const telecom = _telecom;
62
+ return {
63
+ ...common,
64
+ contact: telecom?.length ? [{ telecom }] : undefined,
65
+ form: claims[LocationClaim.PhysicalType]
66
+ ? { coding: codingFromValue(claims[LocationClaim.PhysicalType]) }
67
+ : undefined,
68
+ };
69
+ }
70
+ /** Extracts the version-neutral Location claims from native FHIR R5. */
71
+ export function locationFhirR5ToFlat(resource) {
72
+ const flat = locationFhirR4ToFlat(resource);
73
+ const telecom = resource.contact
74
+ ?.flatMap(contact => contact.telecom ?? [])
75
+ .map(point => point.value)
76
+ .filter((value) => Boolean(value));
77
+ return {
78
+ ...flat,
79
+ [LocationClaim.Telecom]: telecom?.length ? telecom.join(',') : undefined,
80
+ [LocationClaim.PhysicalType]: codingToValue(resource.form?.coding?.[0]),
43
81
  };
44
82
  }
@@ -1,3 +1,5 @@
1
1
  import type { FhirResource, FlatClaims } from './convert-shared.js';
2
2
  export declare function organizationFlatToFhirR4(claims: FlatClaims): FhirResource;
3
3
  export declare function organizationFhirR4ToFlat(resource: FhirResource): FlatClaims;
4
+ /** Materializes canonical Organization claims as native FHIR R5. */
5
+ export declare function organizationFlatToFhirR5(claims: FlatClaims): FhirResource;
@@ -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/convert/convert-organization.ts
3
3
  import { OrganizationClaim } from '../claims/organization-claims.js';
4
- import { codingFromValue, codingToValue, referenceToValue } from './convert-shared.js';
4
+ import { codingFromValue, codingToValue, identifierFromValue, identifierToValue, referenceToValue } from './convert-shared.js';
5
5
  function splitCsv(value) {
6
6
  return String(value || '')
7
7
  .split(',')
@@ -11,7 +11,7 @@ function splitCsv(value) {
11
11
  export function organizationFlatToFhirR4(claims) {
12
12
  return {
13
13
  resourceType: 'Organization',
14
- identifier: claims[OrganizationClaim.Identifier] ? [{ value: claims[OrganizationClaim.Identifier] }] : undefined,
14
+ identifier: claims[OrganizationClaim.Identifier] ? [identifierFromValue(claims[OrganizationClaim.Identifier])] : undefined,
15
15
  active: claims[OrganizationClaim.Active] === undefined ? undefined : claims[OrganizationClaim.Active] === 'true',
16
16
  type: claims[OrganizationClaim.Type] ? [{ coding: codingFromValue(claims[OrganizationClaim.Type]) }] : undefined,
17
17
  name: claims[OrganizationClaim.Name],
@@ -27,7 +27,7 @@ export function organizationFhirR4ToFlat(resource) {
27
27
  ?.map((item) => item?.value)
28
28
  .filter((item) => Boolean(item));
29
29
  return {
30
- [OrganizationClaim.Identifier]: resource.identifier?.[0]?.value,
30
+ [OrganizationClaim.Identifier]: identifierToValue(resource.identifier?.[0]),
31
31
  [OrganizationClaim.Active]: resource.active === undefined ? undefined : String(resource.active),
32
32
  [OrganizationClaim.Type]: codingToValue(resource.type?.[0]?.coding?.[0]),
33
33
  [OrganizationClaim.Name]: resource.name,
@@ -37,3 +37,20 @@ export function organizationFhirR4ToFlat(resource) {
37
37
  [OrganizationClaim.Address]: resource.address?.[0]?.text,
38
38
  };
39
39
  }
40
+ /** Materializes canonical Organization claims as native FHIR R5. */
41
+ export function organizationFlatToFhirR5(claims) {
42
+ const identifier = identifierFromValue(claims[OrganizationClaim.Identifier]);
43
+ const telecom = splitCsv(claims[OrganizationClaim.Telecom]).map((value) => ({ value }));
44
+ const address = claims[OrganizationClaim.Address] ? [{ text: claims[OrganizationClaim.Address] }] : [];
45
+ return {
46
+ resourceType: 'Organization',
47
+ identifier: identifier ? [identifier] : undefined,
48
+ active: claims[OrganizationClaim.Active] === undefined ? undefined : claims[OrganizationClaim.Active] === 'true',
49
+ type: claims[OrganizationClaim.Type] ? [{ coding: codingFromValue(claims[OrganizationClaim.Type]) }] : undefined,
50
+ name: claims[OrganizationClaim.Name],
51
+ alias: splitCsv(claims[OrganizationClaim.Alias]),
52
+ description: claims[OrganizationClaim.Description],
53
+ partOf: claims[OrganizationClaim.PartOf] ? { reference: claims[OrganizationClaim.PartOf] } : undefined,
54
+ contact: telecom.length || address.length ? [{ telecom, address }] : undefined,
55
+ };
56
+ }
@@ -1,3 +1,5 @@
1
1
  import type { FhirResource, FlatClaims } from './convert-shared.js';
2
2
  export declare function procedureFlatToFhirR4(claims: FlatClaims): FhirResource;
3
3
  export declare function procedureFhirR4ToFlat(resource: FhirResource): FlatClaims;
4
+ /** Explicit FHIR R5 projection, including equipment calibration and inspection. */
5
+ export declare function procedureFlatToFhirR5(claims: FlatClaims): FhirResource;
@@ -62,3 +62,24 @@ export function procedureFhirR4ToFlat(resource) {
62
62
  [ProcedureClaim.Note]: resource.note?.[0]?.text,
63
63
  };
64
64
  }
65
+ /** Explicit FHIR R5 projection, including equipment calibration and inspection. */
66
+ export function procedureFlatToFhirR5(claims) {
67
+ const resource = procedureFlatToFhirR4(claims);
68
+ const { performedDateTime, reasonCode, reasonReference, ...common } = resource;
69
+ return {
70
+ ...common,
71
+ occurrenceDateTime: performedDateTime,
72
+ reason: reasonCode || reasonReference
73
+ ? [
74
+ ...(Array.isArray(reasonCode) ? reasonCode.map(concept => ({ concept })) : []),
75
+ ...(Array.isArray(reasonReference) ? reasonReference.map(reference => ({ reference })) : []),
76
+ ]
77
+ : undefined,
78
+ focalDevice: claims[ProcedureClaim.FocalDevice]
79
+ ? claims[ProcedureClaim.FocalDevice].split(',').map(reference => ({ manipulated: { reference: reference.trim() } }))
80
+ : undefined,
81
+ report: claims[ProcedureClaim.Report]
82
+ ? claims[ProcedureClaim.Report].split(',').map(reference => ({ reference: reference.trim() }))
83
+ : undefined,
84
+ };
85
+ }
@@ -0,0 +1,3 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared.js';
2
+ export declare function regulatedAuthorizationFlatToFhirR5(claims: FlatClaims): FhirResource;
3
+ export declare function regulatedAuthorizationFhirR5ToFlat(resource: FhirResource): FlatClaims;
@@ -0,0 +1,34 @@
1
+ import { RegulatedAuthorizationClaim } from '../claims/regulated-authorization-claims.js';
2
+ import { codingFromValue, codingToValue, referenceToValue } from './convert-shared.js';
3
+ export function regulatedAuthorizationFlatToFhirR5(claims) {
4
+ return {
5
+ resourceType: 'RegulatedAuthorization',
6
+ identifier: claims[RegulatedAuthorizationClaim.Identifier] ? [{ value: claims[RegulatedAuthorizationClaim.Identifier] }] : undefined,
7
+ subject: claims[RegulatedAuthorizationClaim.Subject] ? claims[RegulatedAuthorizationClaim.Subject].split(',').map(reference => ({ reference: reference.trim() })) : undefined,
8
+ type: claims[RegulatedAuthorizationClaim.Type] ? { coding: codingFromValue(claims[RegulatedAuthorizationClaim.Type]) } : undefined,
9
+ region: claims[RegulatedAuthorizationClaim.Region] ? [{ coding: codingFromValue(claims[RegulatedAuthorizationClaim.Region]) }] : undefined,
10
+ status: claims[RegulatedAuthorizationClaim.Status] ? { coding: [{ code: claims[RegulatedAuthorizationClaim.Status] }] } : undefined,
11
+ statusDate: claims[RegulatedAuthorizationClaim.StatusDate],
12
+ validityPeriod: claims[RegulatedAuthorizationClaim.ValidityStart] || claims[RegulatedAuthorizationClaim.ValidityEnd]
13
+ ? { start: claims[RegulatedAuthorizationClaim.ValidityStart], end: claims[RegulatedAuthorizationClaim.ValidityEnd] }
14
+ : undefined,
15
+ basis: claims[RegulatedAuthorizationClaim.Basis] ? [{ coding: codingFromValue(claims[RegulatedAuthorizationClaim.Basis]) }] : undefined,
16
+ holder: claims[RegulatedAuthorizationClaim.Holder] ? { reference: claims[RegulatedAuthorizationClaim.Holder] } : undefined,
17
+ regulator: claims[RegulatedAuthorizationClaim.Regulator] ? { reference: claims[RegulatedAuthorizationClaim.Regulator] } : undefined,
18
+ };
19
+ }
20
+ export function regulatedAuthorizationFhirR5ToFlat(resource) {
21
+ return {
22
+ [RegulatedAuthorizationClaim.Identifier]: resource.identifier?.[0]?.value,
23
+ [RegulatedAuthorizationClaim.Subject]: Array.isArray(resource.subject) ? resource.subject.map(item => item.reference).filter(Boolean).join(',') || undefined : undefined,
24
+ [RegulatedAuthorizationClaim.Type]: codingToValue(resource.type?.coding?.[0]),
25
+ [RegulatedAuthorizationClaim.Region]: codingToValue(resource.region?.[0]?.coding?.[0]),
26
+ [RegulatedAuthorizationClaim.Status]: resource.status?.coding?.[0]?.code,
27
+ [RegulatedAuthorizationClaim.StatusDate]: resource.statusDate,
28
+ [RegulatedAuthorizationClaim.ValidityStart]: resource.validityPeriod?.start,
29
+ [RegulatedAuthorizationClaim.ValidityEnd]: resource.validityPeriod?.end,
30
+ [RegulatedAuthorizationClaim.Basis]: codingToValue(resource.basis?.[0]?.coding?.[0]),
31
+ [RegulatedAuthorizationClaim.Holder]: referenceToValue(resource.holder),
32
+ [RegulatedAuthorizationClaim.Regulator]: referenceToValue(resource.regulator),
33
+ };
34
+ }
@@ -0,0 +1,9 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared.js';
2
+ /** Materializes canonical Schedule claims as native FHIR R5. */
3
+ export declare function scheduleFlatToFhirR5(claims: FlatClaims): FhirResource;
4
+ /** Extracts official Schedule search claims plus required materialization fields. */
5
+ export declare function scheduleFhirR5ToFlat(resource: FhirResource): FlatClaims;
6
+ /** Materializes canonical Slot claims as native FHIR R5. */
7
+ export declare function slotFlatToFhirR5(claims: FlatClaims): FhirResource;
8
+ /** Extracts official Slot search claims plus required materialization fields. */
9
+ export declare function slotFhirR5ToFlat(resource: FhirResource): FlatClaims;
@@ -0,0 +1,96 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { ScheduleClaim, SlotClaim } from '../claims/schedule-slot-claims.js';
3
+ import { codingListFromCsv, codingListToCsv, identifierFromValue, identifierToValue, referenceListToCsv, referenceToValue, } from './convert-shared.js';
4
+ const splitCsv = (value) => String(value || '').split(',').map(item => item.trim()).filter(Boolean);
5
+ const referencesFromCsv = (value) => {
6
+ const values = splitCsv(value);
7
+ return values.length ? values.map(reference => ({ reference })) : undefined;
8
+ };
9
+ const conceptsFromCsv = (value) => {
10
+ const codings = codingListFromCsv(value);
11
+ return codings?.map(coding => ({ coding: [coding] }));
12
+ };
13
+ const conceptsToCsv = (value) => codingListToCsv((Array.isArray(value) ? value : []).flatMap(item => item && typeof item === 'object' && Array.isArray(item.coding)
14
+ ? item.coding ?? []
15
+ : []));
16
+ const codeableReferencesFromClaims = (concepts, references) => {
17
+ const conceptList = conceptsFromCsv(concepts) ?? [];
18
+ const referenceList = referencesFromCsv(references) ?? [];
19
+ const length = Math.max(conceptList.length, referenceList.length);
20
+ if (!length)
21
+ return undefined;
22
+ return Array.from({ length }, (_, index) => ({
23
+ ...(conceptList[index] ? { concept: conceptList[index] } : {}),
24
+ ...(referenceList[index] ? { reference: referenceList[index] } : {}),
25
+ }));
26
+ };
27
+ const conceptsFromCodeableReferences = (value) => conceptsToCsv((Array.isArray(value) ? value : []).map(item => item?.concept));
28
+ const referencesFromCodeableReferences = (value) => referenceListToCsv((Array.isArray(value) ? value : []).map(item => item?.reference ?? {}));
29
+ /** Materializes canonical Schedule claims as native FHIR R5. */
30
+ export function scheduleFlatToFhirR5(claims) {
31
+ return {
32
+ resourceType: 'Schedule',
33
+ identifier: claims[ScheduleClaim.Identifier] ? [identifierFromValue(claims[ScheduleClaim.Identifier])] : undefined,
34
+ active: claims[ScheduleClaim.Active] === undefined ? undefined : claims[ScheduleClaim.Active] === 'true',
35
+ serviceCategory: conceptsFromCsv(claims[ScheduleClaim.ServiceCategory]),
36
+ serviceType: codeableReferencesFromClaims(claims[ScheduleClaim.ServiceType], claims[ScheduleClaim.ServiceTypeReference]),
37
+ specialty: conceptsFromCsv(claims[ScheduleClaim.Specialty]),
38
+ name: claims[ScheduleClaim.Name],
39
+ actor: referencesFromCsv(claims[ScheduleClaim.Actor]),
40
+ planningHorizon: claims[ScheduleClaim.PlanningHorizonStart] || claims[ScheduleClaim.PlanningHorizonEnd]
41
+ ? { start: claims[ScheduleClaim.PlanningHorizonStart], end: claims[ScheduleClaim.PlanningHorizonEnd] }
42
+ : undefined,
43
+ comment: claims[ScheduleClaim.Comment],
44
+ };
45
+ }
46
+ /** Extracts official Schedule search claims plus required materialization fields. */
47
+ export function scheduleFhirR5ToFlat(resource) {
48
+ const planningHorizon = resource.planningHorizon;
49
+ return {
50
+ [ScheduleClaim.Identifier]: identifierToValue(resource.identifier?.[0]),
51
+ [ScheduleClaim.Active]: resource.active === undefined ? undefined : String(resource.active),
52
+ [ScheduleClaim.ServiceCategory]: conceptsToCsv(resource.serviceCategory),
53
+ [ScheduleClaim.ServiceType]: conceptsFromCodeableReferences(resource.serviceType),
54
+ [ScheduleClaim.ServiceTypeReference]: referencesFromCodeableReferences(resource.serviceType),
55
+ [ScheduleClaim.Specialty]: conceptsToCsv(resource.specialty),
56
+ [ScheduleClaim.Name]: resource.name,
57
+ [ScheduleClaim.Actor]: referenceListToCsv(resource.actor),
58
+ [ScheduleClaim.PlanningHorizonStart]: planningHorizon?.start,
59
+ [ScheduleClaim.PlanningHorizonEnd]: planningHorizon?.end,
60
+ [ScheduleClaim.Comment]: resource.comment,
61
+ };
62
+ }
63
+ /** Materializes canonical Slot claims as native FHIR R5. */
64
+ export function slotFlatToFhirR5(claims) {
65
+ return {
66
+ resourceType: 'Slot',
67
+ identifier: claims[SlotClaim.Identifier] ? [identifierFromValue(claims[SlotClaim.Identifier])] : undefined,
68
+ serviceCategory: conceptsFromCsv(claims[SlotClaim.ServiceCategory]),
69
+ serviceType: codeableReferencesFromClaims(claims[SlotClaim.ServiceType], claims[SlotClaim.ServiceTypeReference]),
70
+ specialty: conceptsFromCsv(claims[SlotClaim.Specialty]),
71
+ appointmentType: conceptsFromCsv(claims[SlotClaim.AppointmentType]),
72
+ schedule: claims[SlotClaim.Schedule] ? { reference: claims[SlotClaim.Schedule] } : undefined,
73
+ status: claims[SlotClaim.Status],
74
+ start: claims[SlotClaim.Start],
75
+ end: claims[SlotClaim.End],
76
+ overbooked: claims[SlotClaim.Overbooked] === undefined ? undefined : claims[SlotClaim.Overbooked] === 'true',
77
+ comment: claims[SlotClaim.Comment],
78
+ };
79
+ }
80
+ /** Extracts official Slot search claims plus required materialization fields. */
81
+ export function slotFhirR5ToFlat(resource) {
82
+ return {
83
+ [SlotClaim.Identifier]: identifierToValue(resource.identifier?.[0]),
84
+ [SlotClaim.ServiceCategory]: conceptsToCsv(resource.serviceCategory),
85
+ [SlotClaim.ServiceType]: conceptsFromCodeableReferences(resource.serviceType),
86
+ [SlotClaim.ServiceTypeReference]: referencesFromCodeableReferences(resource.serviceType),
87
+ [SlotClaim.Specialty]: conceptsToCsv(resource.specialty),
88
+ [SlotClaim.AppointmentType]: conceptsToCsv(resource.appointmentType),
89
+ [SlotClaim.Schedule]: referenceToValue(resource.schedule),
90
+ [SlotClaim.Status]: resource.status,
91
+ [SlotClaim.Start]: resource.start,
92
+ [SlotClaim.End]: resource.end,
93
+ [SlotClaim.Overbooked]: resource.overbooked === undefined ? undefined : String(resource.overbooked),
94
+ [SlotClaim.Comment]: resource.comment,
95
+ };
96
+ }
@@ -15,6 +15,16 @@ export declare function codingToValue(coding?: {
15
15
  system?: string;
16
16
  code?: string;
17
17
  }): string | undefined;
18
+ /** Materializes one canonical FHIR token as an Identifier without losing its system. */
19
+ export declare function identifierFromValue(value?: string): {
20
+ system?: string;
21
+ value: string;
22
+ } | undefined;
23
+ /** Extracts one Identifier as the canonical `system|value` token used by search claims. */
24
+ export declare function identifierToValue(identifier?: {
25
+ system?: string;
26
+ value?: string;
27
+ }): string | undefined;
18
28
  export declare function referenceToValue(reference?: {
19
29
  reference?: string;
20
30
  }): string | undefined;
@@ -25,6 +25,25 @@ export function codingToValue(coding) {
25
25
  return undefined;
26
26
  return coding.system ? `${coding.system}|${coding.code}` : coding.code;
27
27
  }
28
+ /** Materializes one canonical FHIR token as an Identifier without losing its system. */
29
+ export function identifierFromValue(value) {
30
+ if (!value)
31
+ return undefined;
32
+ const separator = value.indexOf('|');
33
+ if (separator < 0)
34
+ return { value };
35
+ const system = value.slice(0, separator).trim();
36
+ const identifierValue = value.slice(separator + 1).trim();
37
+ if (!identifierValue)
38
+ return undefined;
39
+ return system ? { system, value: identifierValue } : { value: identifierValue };
40
+ }
41
+ /** Extracts one Identifier as the canonical `system|value` token used by search claims. */
42
+ export function identifierToValue(identifier) {
43
+ if (!identifier?.value)
44
+ return undefined;
45
+ return identifier.system ? `${identifier.system}|${identifier.value}` : identifier.value;
46
+ }
28
47
  export function referenceToValue(reference) {
29
48
  return reference?.reference;
30
49
  }
@@ -2,6 +2,7 @@ export * from './convert-shared.js';
2
2
  export * from './convert-allergy-intolerance.js';
3
3
  export * from './convert-appointment-response.js';
4
4
  export * from './convert-appointment.js';
5
+ export * from './convert-appointment-communication.js';
5
6
  export * from './convert-care-plan.js';
6
7
  export * from './convert-clinical-impression.js';
7
8
  export * from './convert-communication-request.js';
@@ -11,17 +12,23 @@ export * from './convert-condition.js';
11
12
  export * from './convert-coverage.js';
12
13
  export * from './convert-device-use-statement.js';
13
14
  export * from './convert-device.js';
15
+ export * from './convert-device-metric.js';
14
16
  export * from './convert-diagnostic-report.js';
15
17
  export * from './convert-document-reference.js';
16
18
  export * from './convert-encounter.js';
19
+ export * from './convert-encounter-history.js';
17
20
  export * from './convert-flag.js';
18
21
  export * from './convert-immunization.js';
19
22
  export * from './convert-imaging-study.js';
20
23
  export * from './convert-location.js';
24
+ export * from './convert-healthcare-service.js';
21
25
  export * from './convert-medication-statement.js';
22
26
  export * from './convert-observation.js';
23
27
  export * from './convert-organization.js';
24
28
  export * from './convert-practitioner-role.js';
25
29
  export * from './convert-procedure.js';
30
+ export * from './convert-regulated-authorization.js';
26
31
  export * from './convert-related-person.js';
27
32
  export * from './convert-service-request.js';
33
+ export * from './convert-schedule-slot.js';
34
+ export * from './materialize-claims-first-resource.js';