fhir-data-utils-ts 0.4.0 → 0.6.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.
Files changed (70) hide show
  1. package/.codex/skills/govern-fhir-data-utilities/SKILL.md +35 -0
  2. package/CHANGELOG.md +52 -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/claims/appointment-claims.d.ts +6 -0
  15. package/dist/claims/appointment-claims.js +6 -0
  16. package/dist/claims/device-metric-claims.d.ts +16 -0
  17. package/dist/claims/device-metric-claims.js +25 -0
  18. package/dist/claims/healthcare-service-claims.d.ts +32 -0
  19. package/dist/claims/healthcare-service-claims.js +54 -0
  20. package/dist/claims/index.d.ts +4 -0
  21. package/dist/claims/index.js +4 -0
  22. package/dist/claims/location-claims.d.ts +3 -0
  23. package/dist/claims/location-claims.js +6 -0
  24. package/dist/claims/organization-claims.d.ts +3 -1
  25. package/dist/claims/organization-claims.js +4 -1
  26. package/dist/claims/procedure-claims.d.ts +2 -0
  27. package/dist/claims/procedure-claims.js +4 -0
  28. package/dist/claims/regulated-authorization-claims.d.ts +17 -0
  29. package/dist/claims/regulated-authorization-claims.js +27 -0
  30. package/dist/claims/schedule-slot-claims.d.ts +45 -0
  31. package/dist/claims/schedule-slot-claims.js +70 -0
  32. package/dist/constants/fhir-resource-types.d.ts +9 -0
  33. package/dist/constants/fhir-resource-types.js +3 -0
  34. package/dist/converters/convert-appointment-communication.d.ts +14 -0
  35. package/dist/converters/convert-appointment-communication.js +97 -0
  36. package/dist/converters/convert-appointment.js +16 -7
  37. package/dist/converters/convert-device-metric.d.ts +3 -0
  38. package/dist/converters/convert-device-metric.js +37 -0
  39. package/dist/converters/convert-document-reference.js +5 -0
  40. package/dist/converters/convert-healthcare-service.d.ts +7 -0
  41. package/dist/converters/convert-healthcare-service.js +60 -0
  42. package/dist/converters/convert-location.d.ts +4 -0
  43. package/dist/converters/convert-location.js +41 -3
  44. package/dist/converters/convert-organization.d.ts +2 -0
  45. package/dist/converters/convert-organization.js +20 -3
  46. package/dist/converters/convert-procedure.d.ts +2 -0
  47. package/dist/converters/convert-procedure.js +21 -0
  48. package/dist/converters/convert-regulated-authorization.d.ts +3 -0
  49. package/dist/converters/convert-regulated-authorization.js +34 -0
  50. package/dist/converters/convert-schedule-slot.d.ts +9 -0
  51. package/dist/converters/convert-schedule-slot.js +96 -0
  52. package/dist/converters/convert-shared.d.ts +10 -0
  53. package/dist/converters/convert-shared.js +19 -0
  54. package/dist/converters/index.d.ts +6 -0
  55. package/dist/converters/index.js +6 -0
  56. package/dist/converters/materialize-claims-first-resource.d.ts +16 -0
  57. package/dist/converters/materialize-claims-first-resource.js +61 -0
  58. package/dist/fhir-bundle.d.ts +21 -0
  59. package/dist/fhir-bundle.js +74 -0
  60. package/dist/group-r5.d.ts +12 -0
  61. package/dist/group-r5.js +20 -0
  62. package/dist/healthcare-service-specialty-map.d.ts +32 -0
  63. package/dist/healthcare-service-specialty-map.js +151 -0
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +2 -0
  66. package/docs/CLINICAL_TYPED_ACCESSOR_BOUNDARY.md +44 -0
  67. package/docs/COMMUNICATION_BUNDLE_CONTRACT.md +61 -0
  68. package/docs/HEALTHCARE_SERVICE_DEPARTMENT_CATALOG.md +190 -0
  69. package/docs/UHC_FHIR_UTILS_MIGRATION.md +20 -1
  70. package/package.json +5 -1
@@ -0,0 +1,70 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { defineFlatSearchParameter } from '../search-parameters.js';
3
+ /** Canonical flat claims for the official FHIR R5 Schedule search surface. */
4
+ export const ScheduleClaim = Object.freeze({
5
+ Active: 'Schedule.active',
6
+ Actor: 'Schedule.actor',
7
+ Date: 'Schedule.date',
8
+ Identifier: 'Schedule.identifier',
9
+ Name: 'Schedule.name',
10
+ ServiceCategory: 'Schedule.service-category',
11
+ ServiceType: 'Schedule.service-type',
12
+ ServiceTypeReference: 'Schedule.service-type-reference',
13
+ Specialty: 'Schedule.specialty',
14
+ PlanningHorizonStart: 'Schedule.planning-horizon-start',
15
+ PlanningHorizonEnd: 'Schedule.planning-horizon-end',
16
+ Comment: 'Schedule.comment',
17
+ });
18
+ /** Canonical flat claims for the official FHIR R5 Slot search surface. */
19
+ export const SlotClaim = Object.freeze({
20
+ AppointmentType: 'Slot.appointment-type',
21
+ Identifier: 'Slot.identifier',
22
+ Schedule: 'Slot.schedule',
23
+ ServiceCategory: 'Slot.service-category',
24
+ ServiceType: 'Slot.service-type',
25
+ ServiceTypeReference: 'Slot.service-type-reference',
26
+ Specialty: 'Slot.specialty',
27
+ Start: 'Slot.start',
28
+ Status: 'Slot.status',
29
+ End: 'Slot.end',
30
+ Overbooked: 'Slot.overbooked',
31
+ Comment: 'Slot.comment',
32
+ });
33
+ export const ScheduleR5SearchParameterCatalog = Object.freeze([
34
+ defineFlatSearchParameter({ claim: ScheduleClaim.Active, resourceType: 'Schedule', elementPath: 'Schedule.active', type: 'token' }),
35
+ defineFlatSearchParameter({ claim: ScheduleClaim.Actor, resourceType: 'Schedule', elementPath: 'Schedule.actor', type: 'reference' }),
36
+ defineFlatSearchParameter({ claim: ScheduleClaim.Date, resourceType: 'Schedule', elementPath: 'Schedule.planningHorizon', type: 'date' }),
37
+ defineFlatSearchParameter({ claim: ScheduleClaim.Identifier, resourceType: 'Schedule', elementPath: 'Schedule.identifier', type: 'token' }),
38
+ defineFlatSearchParameter({ claim: ScheduleClaim.Name, resourceType: 'Schedule', elementPath: 'Schedule.name', type: 'string' }),
39
+ defineFlatSearchParameter({ claim: ScheduleClaim.ServiceCategory, resourceType: 'Schedule', elementPath: 'Schedule.serviceCategory', type: 'token' }),
40
+ defineFlatSearchParameter({ claim: ScheduleClaim.ServiceType, resourceType: 'Schedule', elementPath: 'Schedule.serviceType.concept', type: 'token' }),
41
+ defineFlatSearchParameter({ claim: ScheduleClaim.ServiceTypeReference, resourceType: 'Schedule', elementPath: 'Schedule.serviceType.reference', type: 'reference' }),
42
+ defineFlatSearchParameter({ claim: ScheduleClaim.Specialty, resourceType: 'Schedule', elementPath: 'Schedule.specialty', type: 'token' }),
43
+ ]);
44
+ export const SlotR5SearchParameterCatalog = Object.freeze([
45
+ defineFlatSearchParameter({ claim: SlotClaim.AppointmentType, resourceType: 'Slot', elementPath: 'Slot.appointmentType', type: 'token' }),
46
+ defineFlatSearchParameter({ claim: SlotClaim.Identifier, resourceType: 'Slot', elementPath: 'Slot.identifier', type: 'token' }),
47
+ defineFlatSearchParameter({ claim: SlotClaim.Schedule, resourceType: 'Slot', elementPath: 'Slot.schedule', type: 'reference' }),
48
+ defineFlatSearchParameter({ claim: SlotClaim.ServiceCategory, resourceType: 'Slot', elementPath: 'Slot.serviceCategory', type: 'token' }),
49
+ defineFlatSearchParameter({ claim: SlotClaim.ServiceType, resourceType: 'Slot', elementPath: 'Slot.serviceType.concept', type: 'token' }),
50
+ defineFlatSearchParameter({ claim: SlotClaim.ServiceTypeReference, resourceType: 'Slot', elementPath: 'Slot.serviceType.reference', type: 'reference' }),
51
+ defineFlatSearchParameter({ claim: SlotClaim.Specialty, resourceType: 'Slot', elementPath: 'Slot.specialty', type: 'token' }),
52
+ defineFlatSearchParameter({ claim: SlotClaim.Start, resourceType: 'Slot', elementPath: 'Slot.start', type: 'date' }),
53
+ defineFlatSearchParameter({ claim: SlotClaim.Status, resourceType: 'Slot', elementPath: 'Slot.status', type: 'token' }),
54
+ ]);
55
+ export const ScheduleClaimSpecs = Object.freeze([
56
+ { key: ScheduleClaim.Identifier, meaning: 'Logical schedule identifier.', example: 'urn:uuid:blood-donation-weekday' },
57
+ { key: ScheduleClaim.Active, meaning: 'Whether this schedule is in active use.', example: 'true' },
58
+ { key: ScheduleClaim.Actor, meaning: 'Repeatable independently schedulable actors as CSV references.', example: 'HealthcareService/blood-donation,Location/blood-donation-room' },
59
+ { key: ScheduleClaim.ServiceType, meaning: 'Repeatable service-type concept tokens as CSV.', example: 'http://terminology.hl7.org/CodeSystem/service-type|117' },
60
+ { key: ScheduleClaim.ServiceTypeReference, meaning: 'Repeatable HealthcareService references corresponding to service-type concepts.', example: 'HealthcareService/blood-donation' },
61
+ { key: ScheduleClaim.PlanningHorizonStart, meaning: 'Start of the period covered by this schedule.', example: '2026-10-01' },
62
+ { key: ScheduleClaim.PlanningHorizonEnd, meaning: 'End of the period covered by this schedule.', example: '2026-10-31' },
63
+ ]);
64
+ export const SlotClaimSpecs = Object.freeze([
65
+ { key: SlotClaim.Identifier, meaning: 'Logical slot identifier.', example: 'urn:uuid:blood-20261001-0900' },
66
+ { key: SlotClaim.Schedule, meaning: 'The Schedule that owns this independent slot.', example: 'Schedule/blood-donation-weekday' },
67
+ { key: SlotClaim.Status, meaning: 'FHIR Slot status.', example: 'free' },
68
+ { key: SlotClaim.Start, meaning: 'Start instant.', example: '2026-10-01T09:00:00Z' },
69
+ { key: SlotClaim.End, meaning: 'End instant; duration is derived from start and end.', example: '2026-10-01T09:15:00Z' },
70
+ ]);
@@ -25,6 +25,7 @@ export declare const ResourceTypesFhirR4: Readonly<{
25
25
  readonly Encounter: "Encounter";
26
26
  readonly Flag: "Flag";
27
27
  readonly Group: "Group";
28
+ readonly HealthcareService: "HealthcareService";
28
29
  readonly ImagingStudy: "ImagingStudy";
29
30
  readonly Immunization: "Immunization";
30
31
  readonly ImmunizationRecommendation: "ImmunizationRecommendation";
@@ -47,7 +48,9 @@ export declare const ResourceTypesFhirR4: Readonly<{
47
48
  readonly RelatedPerson: "RelatedPerson";
48
49
  readonly ResearchStudy: "ResearchStudy";
49
50
  readonly ResearchSubject: "ResearchSubject";
51
+ readonly Schedule: "Schedule";
50
52
  readonly Specimen: "Specimen";
53
+ readonly Slot: "Slot";
51
54
  readonly ServiceRequest: "ServiceRequest";
52
55
  readonly Task: "Task";
53
56
  readonly DetectedIssue: "DetectedIssue";
@@ -81,6 +84,7 @@ export declare const FhirResourceTypes: Readonly<{
81
84
  readonly Encounter: "Encounter";
82
85
  readonly Flag: "Flag";
83
86
  readonly Group: "Group";
87
+ readonly HealthcareService: "HealthcareService";
84
88
  readonly ImagingStudy: "ImagingStudy";
85
89
  readonly Immunization: "Immunization";
86
90
  readonly ImmunizationRecommendation: "ImmunizationRecommendation";
@@ -103,7 +107,9 @@ export declare const FhirResourceTypes: Readonly<{
103
107
  readonly RelatedPerson: "RelatedPerson";
104
108
  readonly ResearchStudy: "ResearchStudy";
105
109
  readonly ResearchSubject: "ResearchSubject";
110
+ readonly Schedule: "Schedule";
106
111
  readonly Specimen: "Specimen";
112
+ readonly Slot: "Slot";
107
113
  readonly ServiceRequest: "ServiceRequest";
108
114
  readonly Task: "Task";
109
115
  readonly DetectedIssue: "DetectedIssue";
@@ -133,6 +139,7 @@ export declare const ResourceTypesFhirR5: Readonly<{
133
139
  readonly Encounter: "Encounter";
134
140
  readonly Flag: "Flag";
135
141
  readonly Group: "Group";
142
+ readonly HealthcareService: "HealthcareService";
136
143
  readonly ImagingStudy: "ImagingStudy";
137
144
  readonly Immunization: "Immunization";
138
145
  readonly ImmunizationRecommendation: "ImmunizationRecommendation";
@@ -155,7 +162,9 @@ export declare const ResourceTypesFhirR5: Readonly<{
155
162
  readonly RelatedPerson: "RelatedPerson";
156
163
  readonly ResearchStudy: "ResearchStudy";
157
164
  readonly ResearchSubject: "ResearchSubject";
165
+ readonly Schedule: "Schedule";
158
166
  readonly Specimen: "Specimen";
167
+ readonly Slot: "Slot";
159
168
  readonly ServiceRequest: "ServiceRequest";
160
169
  readonly Task: "Task";
161
170
  readonly DetectedIssue: "DetectedIssue";
@@ -25,6 +25,7 @@ export const ResourceTypesFhirR4 = Object.freeze({
25
25
  Encounter: 'Encounter',
26
26
  Flag: 'Flag',
27
27
  Group: 'Group',
28
+ HealthcareService: 'HealthcareService',
28
29
  ImagingStudy: 'ImagingStudy',
29
30
  Immunization: 'Immunization',
30
31
  ImmunizationRecommendation: 'ImmunizationRecommendation',
@@ -47,7 +48,9 @@ export const ResourceTypesFhirR4 = Object.freeze({
47
48
  RelatedPerson: 'RelatedPerson',
48
49
  ResearchStudy: 'ResearchStudy',
49
50
  ResearchSubject: 'ResearchSubject',
51
+ Schedule: 'Schedule',
50
52
  Specimen: 'Specimen',
53
+ Slot: 'Slot',
51
54
  ServiceRequest: 'ServiceRequest',
52
55
  Task: 'Task',
53
56
  DetectedIssue: 'DetectedIssue',
@@ -0,0 +1,14 @@
1
+ import { type FlatClaimResourceEntry } from '../flat-claim-resource-graph.js';
2
+ export type SupportedAppointmentFhirRelease = 'r4' | 'r5';
3
+ export type AppointmentCommunicationFlatGraph = Readonly<{
4
+ communications: readonly FlatClaimResourceEntry[];
5
+ appointments: readonly FlatClaimResourceEntry[];
6
+ documentReferences: readonly FlatClaimResourceEntry[];
7
+ entries: readonly FlatClaimResourceEntry[];
8
+ }>;
9
+ /**
10
+ * Projects a validated appointment exchange into independently persisted flat
11
+ * claim resources. Transport parsing is delegated to the shared Bundle Reader;
12
+ * native FHIR remains an adapter input and is never persisted beside claims.
13
+ */
14
+ export declare function projectAppointmentCommunicationBatchToFlatGraph(input: unknown, release: SupportedAppointmentFhirRelease): AppointmentCommunicationFlatGraph;
@@ -0,0 +1,97 @@
1
+ import { AppointmentClaim } from '../claims/appointment-claims.js';
2
+ import { CommunicationClaim } from '../claims/communication-claims.js';
3
+ import { DocumentReferenceClaim } from '../claims/document-reference-claims.js';
4
+ import { readBundleResources, readCommunicationBundlePayloads } from '../fhir-bundle.js';
5
+ import { buildFlatClaimResourceEntry } from '../flat-claim-resource-graph.js';
6
+ import { appointmentFhirR4ToFlat } from './convert-appointment.js';
7
+ import { documentReferenceFhirR4ToFlat } from './convert-document-reference.js';
8
+ /**
9
+ * Projects a validated appointment exchange into independently persisted flat
10
+ * claim resources. Transport parsing is delegated to the shared Bundle Reader;
11
+ * native FHIR remains an adapter input and is never persisted beside claims.
12
+ */
13
+ export function projectAppointmentCommunicationBatchToFlatGraph(input, release) {
14
+ if (release !== 'r4' && release !== 'r5')
15
+ throw new TypeError('fhir_release_unsupported');
16
+ const communications = [];
17
+ const appointments = [];
18
+ const documentReferences = [];
19
+ for (const { communication, attachment, bundle } of readCommunicationBundlePayloads(input)) {
20
+ const communicationId = resourceId(communication, 'communication_identifier_required');
21
+ communications.push(buildFlatClaimResourceEntry({
22
+ entryId: communicationId,
23
+ resourceType: 'Communication',
24
+ claims: compactClaims({
25
+ [CommunicationClaim.Identifier]: communicationId,
26
+ [CommunicationClaim.Status]: stringValue(communication.status),
27
+ [CommunicationClaim.Subject]: referenceValue(communication.subject),
28
+ [CommunicationClaim.Recipient]: referenceList(communication.recipient),
29
+ [CommunicationClaim.Sender]: referenceValue(communication.sender),
30
+ [CommunicationClaim.Sent]: stringValue(communication.sent),
31
+ [CommunicationClaim.ContentAttachmentType]: attachment.contentType,
32
+ [CommunicationClaim.ContentAttachmentData]: attachment.data,
33
+ [CommunicationClaim.ContentAttachmentTitle]: attachment.title,
34
+ }),
35
+ }));
36
+ for (const child of readBundleResources(bundle)) {
37
+ if (child.resourceType === 'Appointment') {
38
+ const entryId = resourceId(child, 'appointment_identifier_required');
39
+ const structural = appointmentFhirR4ToFlat(child);
40
+ appointments.push(buildFlatClaimResourceEntry({
41
+ entryId,
42
+ resourceType: 'Appointment',
43
+ claims: compactClaims({
44
+ ...structural,
45
+ [AppointmentClaim.Identifier]: structural[AppointmentClaim.Identifier] || entryId,
46
+ }),
47
+ }));
48
+ }
49
+ else if (child.resourceType === 'DocumentReference') {
50
+ const entryId = resourceId(child, 'document_reference_identifier_required');
51
+ const structural = documentReferenceFhirR4ToFlat(child);
52
+ documentReferences.push(buildFlatClaimResourceEntry({
53
+ entryId,
54
+ resourceType: 'DocumentReference',
55
+ claims: compactClaims({
56
+ ...structural,
57
+ [DocumentReferenceClaim.Identifier]: structural[DocumentReferenceClaim.Identifier] || entryId,
58
+ }),
59
+ }));
60
+ }
61
+ }
62
+ }
63
+ return Object.freeze({
64
+ communications: Object.freeze(communications),
65
+ appointments: Object.freeze(appointments),
66
+ documentReferences: Object.freeze(documentReferences),
67
+ entries: Object.freeze([...communications, ...appointments, ...documentReferences]),
68
+ });
69
+ }
70
+ function resourceId(resource, error) {
71
+ const identifier = Array.isArray(resource.identifier)
72
+ ? resource.identifier.find(item => typeof item?.value === 'string')?.value
73
+ : undefined;
74
+ const value = String(resource.id || identifier || '').trim();
75
+ if (!value)
76
+ throw new TypeError(error);
77
+ return value;
78
+ }
79
+ function compactClaims(input) {
80
+ return Object.freeze(Object.fromEntries(Object.entries(input)
81
+ .filter((entry) => entry[1] !== undefined && entry[1] !== null && String(entry[1]).trim() !== '')
82
+ .map(([name, value]) => [name, String(value).trim()])));
83
+ }
84
+ function stringValue(value) {
85
+ return typeof value === 'string' && value.trim() ? value.trim() : undefined;
86
+ }
87
+ function referenceValue(value) {
88
+ if (!value || typeof value !== 'object' || Array.isArray(value))
89
+ return undefined;
90
+ return stringValue(value.reference);
91
+ }
92
+ function referenceList(value) {
93
+ if (!Array.isArray(value))
94
+ return undefined;
95
+ const references = value.map(referenceValue).filter((item) => Boolean(item));
96
+ return references.length ? [...new Set(references)].join(',') : undefined;
97
+ }
@@ -21,6 +21,8 @@ export function appointmentFlatToFhirR4(claims) {
21
21
  note: claims[AppointmentClaim.NoteText] ? [{ text: claims[AppointmentClaim.NoteText] }] : undefined,
22
22
  patientInstruction: claims[AppointmentClaim.PatientInstruction],
23
23
  basedOn: claims[AppointmentClaim.BasedOn] ? claims[AppointmentClaim.BasedOn].split(',').map((reference) => ({ reference: reference.trim() })) : undefined,
24
+ slot: claims[AppointmentClaim.Slot] ? claims[AppointmentClaim.Slot].split(',').map((reference) => ({ reference: reference.trim() })) : undefined,
25
+ supportingInformation: claims[AppointmentClaim.SupportingInfo] ? claims[AppointmentClaim.SupportingInfo].split(',').map((reference) => ({ reference: reference.trim() })) : undefined,
24
26
  participant: claims[AppointmentClaim.ParticipantActor]
25
27
  ? claims[AppointmentClaim.ParticipantActor].split(',').map((reference) => ({
26
28
  actor: { reference: reference.trim() },
@@ -32,11 +34,17 @@ export function appointmentFlatToFhirR4(claims) {
32
34
  }
33
35
  export function appointmentFhirR4ToFlat(resource) {
34
36
  const participant = Array.isArray(resource.participant) ? resource.participant[0] : undefined;
37
+ const actorReferences = Array.isArray(resource.participant)
38
+ ? resource.participant
39
+ .map((item) => referenceToValue(item?.actor))
40
+ .filter((item) => Boolean(item))
41
+ : [];
42
+ const serviceType = resource.serviceType?.[0];
35
43
  return {
36
44
  [AppointmentClaim.Identifier]: resource.identifier?.[0]?.value,
37
45
  [AppointmentClaim.Status]: resource.status,
38
46
  [AppointmentClaim.ServiceCategory]: codingToValue(resource.serviceCategory?.[0]?.coding?.[0]),
39
- [AppointmentClaim.ServiceType]: codingToValue(resource.serviceType?.[0]?.coding?.[0]),
47
+ [AppointmentClaim.ServiceType]: codingToValue(serviceType?.concept?.coding?.[0] ?? serviceType?.coding?.[0]),
40
48
  [AppointmentClaim.Specialty]: codingToValue(resource.specialty?.[0]?.coding?.[0]),
41
49
  [AppointmentClaim.AppointmentType]: codingToValue(resource.appointmentType?.coding?.[0]),
42
50
  [AppointmentClaim.ReasonCode]: codingToValue(resource.reasonCode?.[0]?.coding?.[0]),
@@ -49,13 +57,14 @@ export function appointmentFhirR4ToFlat(resource) {
49
57
  [AppointmentClaim.NoteText]: resource.note?.[0]?.text,
50
58
  [AppointmentClaim.PatientInstruction]: resource.patientInstruction,
51
59
  [AppointmentClaim.BasedOn]: referenceListToCsv(resource.basedOn),
52
- [AppointmentClaim.ParticipantActor]: Array.isArray(resource.participant)
53
- ? resource.participant
54
- .map((item) => referenceToValue(item?.actor))
55
- .filter((item) => Boolean(item))
56
- .join(',')
57
- : undefined,
60
+ [AppointmentClaim.ParticipantActor]: actorReferences.length ? actorReferences.join(',') : undefined,
58
61
  [AppointmentClaim.ParticipantStatus]: participant?.status,
59
62
  [AppointmentClaim.ParticipantType]: codingToValue(participant?.type?.[0]?.coding?.[0]),
63
+ [AppointmentClaim.Date]: resource.start,
64
+ [AppointmentClaim.Location]: actorReferences.filter(reference => reference.startsWith('Location/')).join(',') || undefined,
65
+ [AppointmentClaim.Patient]: actorReferences.filter(reference => reference.startsWith('Patient/')).join(',') || undefined,
66
+ [AppointmentClaim.Practitioner]: actorReferences.filter(reference => reference.startsWith('Practitioner/') || reference.startsWith('PractitionerRole/')).join(',') || undefined,
67
+ [AppointmentClaim.Slot]: referenceListToCsv(resource.slot),
68
+ [AppointmentClaim.SupportingInfo]: referenceListToCsv(resource.supportingInformation),
60
69
  };
61
70
  }
@@ -0,0 +1,3 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared.js';
2
+ export declare function deviceMetricFlatToFhirR5(claims: FlatClaims): FhirResource;
3
+ export declare function deviceMetricFhirR5ToFlat(resource: FhirResource): FlatClaims;
@@ -0,0 +1,37 @@
1
+ import { DeviceMetricClaim } from '../claims/device-metric-claims.js';
2
+ import { codingFromValue, codingToValue, referenceToValue } from './convert-shared.js';
3
+ export function deviceMetricFlatToFhirR5(claims) {
4
+ const calibration = claims[DeviceMetricClaim.CalibrationType]
5
+ || claims[DeviceMetricClaim.CalibrationState]
6
+ || claims[DeviceMetricClaim.CalibrationTime]
7
+ ? [{
8
+ type: claims[DeviceMetricClaim.CalibrationType],
9
+ state: claims[DeviceMetricClaim.CalibrationState],
10
+ time: claims[DeviceMetricClaim.CalibrationTime],
11
+ }]
12
+ : undefined;
13
+ return {
14
+ resourceType: 'DeviceMetric',
15
+ identifier: claims[DeviceMetricClaim.Identifier] ? [{ value: claims[DeviceMetricClaim.Identifier] }] : undefined,
16
+ type: { coding: codingFromValue(claims[DeviceMetricClaim.Type]) },
17
+ unit: claims[DeviceMetricClaim.Unit] ? { coding: codingFromValue(claims[DeviceMetricClaim.Unit]) } : undefined,
18
+ device: claims[DeviceMetricClaim.Device] ? { reference: claims[DeviceMetricClaim.Device] } : undefined,
19
+ operationalStatus: claims[DeviceMetricClaim.OperationalStatus],
20
+ category: claims[DeviceMetricClaim.Category],
21
+ calibration,
22
+ };
23
+ }
24
+ export function deviceMetricFhirR5ToFlat(resource) {
25
+ const calibration = resource.calibration?.[0];
26
+ return {
27
+ [DeviceMetricClaim.Identifier]: resource.identifier?.[0]?.value,
28
+ [DeviceMetricClaim.Type]: codingToValue(resource.type?.coding?.[0]),
29
+ [DeviceMetricClaim.Unit]: codingToValue(resource.unit?.coding?.[0]),
30
+ [DeviceMetricClaim.Device]: referenceToValue(resource.device),
31
+ [DeviceMetricClaim.OperationalStatus]: resource.operationalStatus,
32
+ [DeviceMetricClaim.Category]: resource.category,
33
+ [DeviceMetricClaim.CalibrationType]: calibration?.type,
34
+ [DeviceMetricClaim.CalibrationState]: calibration?.state,
35
+ [DeviceMetricClaim.CalibrationTime]: calibration?.time,
36
+ };
37
+ }
@@ -25,6 +25,9 @@ export function documentReferenceFlatToFhirR4(claims) {
25
25
  }
26
26
  : undefined,
27
27
  date: claims[DocumentReferenceClaim.Date],
28
+ context: claims[DocumentReferenceClaim.EventReference]
29
+ ? { related: claims[DocumentReferenceClaim.EventReference].split(',').map((reference) => ({ reference: reference.trim() })) }
30
+ : undefined,
28
31
  content: [{ attachment: {
29
32
  contentType: claims[DocumentReferenceClaim.ContentType],
30
33
  data: claims[DocumentReferenceClaim.ContentData],
@@ -45,6 +48,8 @@ export function documentReferenceFhirR4ToFlat(resource) {
45
48
  [DocumentReferenceClaim.TypeText]: resource.type?.text,
46
49
  [DocumentReferenceClaim.TypeDisplay]: resource.type?.coding?.[0]?.display,
47
50
  [DocumentReferenceClaim.Date]: resource.date,
51
+ [DocumentReferenceClaim.EventReference]: resource.context
52
+ ?.related?.map((related) => related.reference).filter((reference) => Boolean(reference)).join(',') || undefined,
48
53
  [DocumentReferenceClaim.ContentType]: attachment?.contentType,
49
54
  [DocumentReferenceClaim.ContentData]: attachment?.data,
50
55
  [DocumentReferenceClaim.Location]: attachment?.url,
@@ -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;