fhir-data-utils-ts 0.6.17 → 0.6.19
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 +10 -0
- package/dist/builders/immunization-graph.d.ts +12 -0
- package/dist/builders/immunization-graph.js +37 -11
- package/dist/claims/healthcare-service-claims.d.ts +1 -0
- package/dist/claims/healthcare-service-claims.js +2 -0
- package/dist/claims/immunization-claims.d.ts +1 -0
- package/dist/claims/immunization-claims.js +2 -0
- package/dist/converters/convert-healthcare-service.js +6 -0
- package/dist/converters/convert-immunization.js +7 -1
- package/docs/HEALTHCARE_SERVICE_DEPARTMENT_CATALOG.md +9 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Add the official `HealthcareService.appointmentRequired` flat claim and
|
|
6
|
+
R4/R5 materialization round trip. Availability remains authoritative in the
|
|
7
|
+
associated Schedule and Slot resources and is not duplicated on the service.
|
|
8
|
+
|
|
9
|
+
## 0.6.18 - 2026-10-08
|
|
10
|
+
|
|
11
|
+
- Add the canonical Immunization series-dose claim, its FHIR R4 round trip,
|
|
12
|
+
reusable dose-order validation and a standalone claims-first
|
|
13
|
+
ImmunizationRecommendation builder for progressive clinical authoring.
|
|
14
|
+
|
|
5
15
|
## 0.6.17 - 2026-10-08
|
|
6
16
|
|
|
7
17
|
- Add the preferred HL7 Observation category catalogue for shared clinical
|
|
@@ -32,5 +32,17 @@ export type ImmunizationFlatInput = Readonly<{
|
|
|
32
32
|
}>;
|
|
33
33
|
attachments?: readonly FlatClaimResourceEntry[];
|
|
34
34
|
}>;
|
|
35
|
+
export type ImmunizationRecommendationFlatInput = Readonly<{
|
|
36
|
+
entryId: string;
|
|
37
|
+
immunizationEntryId: string;
|
|
38
|
+
subjectReference: string;
|
|
39
|
+
recommendationDate: string;
|
|
40
|
+
nextDoseDate: string;
|
|
41
|
+
vaccineCode: ImmunizationCoding;
|
|
42
|
+
}>;
|
|
43
|
+
/** Validates the optional positive dose number and its optional series total. */
|
|
44
|
+
export declare function validateImmunizationDoseSeries(doseSequence?: string, seriesDoses?: string): void;
|
|
45
|
+
/** Builds the recommendation related to one staged Immunization without reminder transport. */
|
|
46
|
+
export declare function buildImmunizationRecommendationFlatEntry(input: ImmunizationRecommendationFlatInput): FlatClaimResourceEntry;
|
|
35
47
|
/** Builds the shared claims-first human/animal immunization resource graph. */
|
|
36
48
|
export declare function buildImmunizationFlatGraph(input: ImmunizationFlatInput): readonly FlatClaimResourceEntry[];
|
|
@@ -5,6 +5,30 @@ import { ImmunizationClaim } from '../claims/immunization-claims.js';
|
|
|
5
5
|
import { ImmunizationRecommendationClaim, ImmunizationRecommendationCodes, } from '../claims/immunization-recommendation-claims.js';
|
|
6
6
|
import { MedicationClaim } from '../claims/medication-claims.js';
|
|
7
7
|
import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
|
|
8
|
+
/** Validates the optional positive dose number and its optional series total. */
|
|
9
|
+
export function validateImmunizationDoseSeries(doseSequence, seriesDoses) {
|
|
10
|
+
const dose = positiveInteger(doseSequence, 'immunization_dose_sequence_invalid');
|
|
11
|
+
const total = positiveInteger(seriesDoses, 'immunization_series_doses_invalid');
|
|
12
|
+
if (dose !== undefined && total !== undefined && total < dose) {
|
|
13
|
+
throw new TypeError('immunization_series_doses_invalid');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Builds the recommendation related to one staged Immunization without reminder transport. */
|
|
17
|
+
export function buildImmunizationRecommendationFlatEntry(input) {
|
|
18
|
+
return buildFlatClaimResourceEntry({
|
|
19
|
+
entryId: required(input.entryId, 'immunization_recommendation_id_required'),
|
|
20
|
+
resourceType: ResourceTypesFhirR4.ImmunizationRecommendation,
|
|
21
|
+
claims: {
|
|
22
|
+
[ImmunizationRecommendationClaim.Patient]: required(input.subjectReference, 'immunization_subject_required'),
|
|
23
|
+
[ImmunizationRecommendationClaim.Date]: isoDate(input.recommendationDate, 'immunization_recommendation_date_invalid'),
|
|
24
|
+
[ImmunizationRecommendationClaim.VaccineCode]: codingToken(input.vaccineCode),
|
|
25
|
+
[ImmunizationRecommendationClaim.ForecastStatus]: ImmunizationRecommendationCodes.ForecastDue,
|
|
26
|
+
[ImmunizationRecommendationClaim.DateCriterionCode]: ImmunizationRecommendationCodes.RecommendedDate,
|
|
27
|
+
[ImmunizationRecommendationClaim.DateCriterionValue]: isoDate(input.nextDoseDate, 'immunization_next_dose_date_invalid'),
|
|
28
|
+
[ImmunizationRecommendationClaim.SupportingImmunization]: `${ResourceTypesFhirR4.Immunization}/${required(input.immunizationEntryId, 'immunization_id_required')}`,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
8
32
|
/** Builds the shared claims-first human/animal immunization resource graph. */
|
|
9
33
|
export function buildImmunizationFlatGraph(input) {
|
|
10
34
|
const vaccineCode = codingToken(input.vaccineCode);
|
|
@@ -33,18 +57,13 @@ export function buildImmunizationFlatGraph(input) {
|
|
|
33
57
|
children.push(buildMedicationEntry(input.medication));
|
|
34
58
|
if (input.nextDoseDate) {
|
|
35
59
|
const recommendationId = `${input.entryId}-recommendation`;
|
|
36
|
-
const recommendation =
|
|
60
|
+
const recommendation = buildImmunizationRecommendationFlatEntry({
|
|
37
61
|
entryId: recommendationId,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
[ImmunizationRecommendationClaim.ForecastStatus]: ImmunizationRecommendationCodes.ForecastDue,
|
|
44
|
-
[ImmunizationRecommendationClaim.DateCriterionCode]: ImmunizationRecommendationCodes.RecommendedDate,
|
|
45
|
-
[ImmunizationRecommendationClaim.DateCriterionValue]: isoDate(input.nextDoseDate, 'immunization_next_dose_date_invalid'),
|
|
46
|
-
[ImmunizationRecommendationClaim.SupportingImmunization]: `${ResourceTypesFhirR4.Immunization}/${input.entryId}`,
|
|
47
|
-
},
|
|
62
|
+
immunizationEntryId: input.entryId,
|
|
63
|
+
subjectReference: input.subjectReference,
|
|
64
|
+
recommendationDate: required(input.recommendationDate, 'immunization_recommendation_date_required'),
|
|
65
|
+
nextDoseDate: input.nextDoseDate,
|
|
66
|
+
vaccineCode: input.vaccineCode,
|
|
48
67
|
});
|
|
49
68
|
const tasks = buildImmunizationReminderTasks({
|
|
50
69
|
entryIdPrefix: recommendationId,
|
|
@@ -113,3 +132,10 @@ function required(value, error) {
|
|
|
113
132
|
throw new TypeError(error);
|
|
114
133
|
return value.trim();
|
|
115
134
|
}
|
|
135
|
+
function positiveInteger(value, error) {
|
|
136
|
+
if (value === undefined || value === '')
|
|
137
|
+
return undefined;
|
|
138
|
+
if (!/^\d+$/.test(value) || Number(value) <= 0)
|
|
139
|
+
throw new TypeError(error);
|
|
140
|
+
return Number(value);
|
|
141
|
+
}
|
|
@@ -8,6 +8,7 @@ import type { ClaimSpec } from './claim-types.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const HealthcareServiceClaim: Readonly<{
|
|
10
10
|
readonly Active: "HealthcareService.active";
|
|
11
|
+
readonly AppointmentRequired: "HealthcareService.appointment-required";
|
|
11
12
|
readonly Characteristic: "HealthcareService.characteristic";
|
|
12
13
|
readonly CoverageArea: "HealthcareService.coverage-area";
|
|
13
14
|
readonly Endpoint: "HealthcareService.endpoint";
|
|
@@ -9,6 +9,7 @@ import { defineFlatSearchParameter } from '../search-parameters.js';
|
|
|
9
9
|
*/
|
|
10
10
|
export const HealthcareServiceClaim = Object.freeze({
|
|
11
11
|
Active: 'HealthcareService.active',
|
|
12
|
+
AppointmentRequired: 'HealthcareService.appointment-required',
|
|
12
13
|
Characteristic: 'HealthcareService.characteristic',
|
|
13
14
|
CoverageArea: 'HealthcareService.coverage-area',
|
|
14
15
|
Endpoint: 'HealthcareService.endpoint',
|
|
@@ -39,6 +40,7 @@ export const HealthcareServiceR4SearchParameterCatalog = Object.freeze([
|
|
|
39
40
|
]);
|
|
40
41
|
export const HealthcareServiceClaimSpecs = Object.freeze([
|
|
41
42
|
{ key: HealthcareServiceClaim.Active, meaning: 'Whether the service is active.', example: 'true' },
|
|
43
|
+
{ key: HealthcareServiceClaim.AppointmentRequired, meaning: 'Whether an appointment is required before receiving the service.', example: 'true' },
|
|
42
44
|
{ key: HealthcareServiceClaim.Characteristic, meaning: 'Repeatable service characteristic tokens as CSV.', example: 'https://example.org/CodeSystem/service-characteristic|24-hours' },
|
|
43
45
|
{ key: HealthcareServiceClaim.CoverageArea, meaning: 'Repeatable intended coverage Location references as CSV.', example: 'Location/british-columbia' },
|
|
44
46
|
{ key: HealthcareServiceClaim.Endpoint, meaning: 'Repeatable technical Endpoint references as CSV.', example: 'Endpoint/emergency-group-inbox' },
|
|
@@ -20,6 +20,7 @@ export declare const ImmunizationClaim: {
|
|
|
20
20
|
readonly VaccineCodeText: "Immunization.vaccine-code-text";
|
|
21
21
|
readonly VaccineCodeDisplay: "Immunization.vaccine-code-display";
|
|
22
22
|
readonly DoseSequence: "Immunization.dose-sequence";
|
|
23
|
+
readonly SeriesDoses: "Immunization.series-doses";
|
|
23
24
|
readonly Subject: "Immunization.subject";
|
|
24
25
|
readonly Note: "Immunization.note";
|
|
25
26
|
readonly Route: "Immunization.route";
|
|
@@ -21,6 +21,7 @@ export const ImmunizationClaim = {
|
|
|
21
21
|
VaccineCodeText: 'Immunization.vaccine-code-text',
|
|
22
22
|
VaccineCodeDisplay: 'Immunization.vaccine-code-display',
|
|
23
23
|
DoseSequence: 'Immunization.dose-sequence',
|
|
24
|
+
SeriesDoses: 'Immunization.series-doses',
|
|
24
25
|
Subject: 'Immunization.subject',
|
|
25
26
|
Note: 'Immunization.note',
|
|
26
27
|
Route: 'Immunization.route',
|
|
@@ -49,6 +50,7 @@ export const ImmunizationClaimSpecs = [
|
|
|
49
50
|
{ key: ImmunizationClaim.VaccineCodeText, meaning: 'Local-language vaccine label used by forms/UI.', example: 'Vacuna COVID-19' },
|
|
50
51
|
{ key: ImmunizationClaim.VaccineCodeDisplay, meaning: 'Canonical/international vaccine display label.', example: 'COVID-19 vaccine' },
|
|
51
52
|
{ key: ImmunizationClaim.DoseSequence, meaning: 'Dose sequence string.', example: '2' },
|
|
53
|
+
{ key: ImmunizationClaim.SeriesDoses, meaning: 'Total number of doses in the vaccination series.', example: '3' },
|
|
52
54
|
{ key: ImmunizationClaim.Subject, meaning: 'Canonical subject reference.', example: 'did:web:patient.example.org' },
|
|
53
55
|
{ key: ImmunizationClaim.Note, meaning: 'Clinical note text.', example: 'No adverse reaction recorded.' },
|
|
54
56
|
];
|
|
@@ -23,6 +23,9 @@ export function healthcareServiceFlatToFhirR4(claims) {
|
|
|
23
23
|
active: claims[HealthcareServiceClaim.Active] === undefined
|
|
24
24
|
? undefined
|
|
25
25
|
: claims[HealthcareServiceClaim.Active] === 'true',
|
|
26
|
+
appointmentRequired: claims[HealthcareServiceClaim.AppointmentRequired] === undefined
|
|
27
|
+
? undefined
|
|
28
|
+
: claims[HealthcareServiceClaim.AppointmentRequired] === 'true',
|
|
26
29
|
providedBy: claims[HealthcareServiceClaim.Organization]
|
|
27
30
|
? { reference: claims[HealthcareServiceClaim.Organization] }
|
|
28
31
|
: undefined,
|
|
@@ -42,6 +45,9 @@ export function healthcareServiceFlatToFhirR4(claims) {
|
|
|
42
45
|
export function healthcareServiceFhirR4ToFlat(resource) {
|
|
43
46
|
return {
|
|
44
47
|
[HealthcareServiceClaim.Active]: resource.active === undefined ? undefined : String(resource.active),
|
|
48
|
+
[HealthcareServiceClaim.AppointmentRequired]: resource.appointmentRequired === undefined
|
|
49
|
+
? undefined
|
|
50
|
+
: String(resource.appointmentRequired),
|
|
45
51
|
[HealthcareServiceClaim.Characteristic]: conceptsToCsv(resource.characteristic),
|
|
46
52
|
[HealthcareServiceClaim.CoverageArea]: referenceListToCsv(resource.coverageArea),
|
|
47
53
|
[HealthcareServiceClaim.Endpoint]: referenceListToCsv(resource.endpoint),
|
|
@@ -33,12 +33,15 @@ export function immunizationFlatToFhirR4(claims) {
|
|
|
33
33
|
reasonReference: claims[ImmunizationClaim.ReasonReference] ? [{ reference: claims[ImmunizationClaim.ReasonReference] }] : undefined,
|
|
34
34
|
statusReason: claims[ImmunizationClaim.StatusReason] ? { coding: codingFromValue(claims[ImmunizationClaim.StatusReason]) } : undefined,
|
|
35
35
|
note: claims[ImmunizationClaim.Note] ? [{ text: claims[ImmunizationClaim.Note] }] : undefined,
|
|
36
|
-
protocolApplied: (claims[ImmunizationClaim.Series] || claims[ImmunizationClaim.TargetDisease] || claims[ImmunizationClaim.DoseSequence]) ? [{
|
|
36
|
+
protocolApplied: (claims[ImmunizationClaim.Series] || claims[ImmunizationClaim.TargetDisease] || claims[ImmunizationClaim.DoseSequence] || claims[ImmunizationClaim.SeriesDoses]) ? [{
|
|
37
37
|
series: claims[ImmunizationClaim.Series],
|
|
38
38
|
targetDisease: claims[ImmunizationClaim.TargetDisease] ? [{ coding: codingFromValue(claims[ImmunizationClaim.TargetDisease]) }] : undefined,
|
|
39
39
|
...(claims[ImmunizationClaim.DoseSequence] && /^\d+$/.test(claims[ImmunizationClaim.DoseSequence])
|
|
40
40
|
? { doseNumberPositiveInt: Number(claims[ImmunizationClaim.DoseSequence]) }
|
|
41
41
|
: { doseNumberString: claims[ImmunizationClaim.DoseSequence] }),
|
|
42
|
+
...(claims[ImmunizationClaim.SeriesDoses] && /^\d+$/.test(claims[ImmunizationClaim.SeriesDoses])
|
|
43
|
+
? { seriesDosesPositiveInt: Number(claims[ImmunizationClaim.SeriesDoses]) }
|
|
44
|
+
: {}),
|
|
42
45
|
}] : undefined,
|
|
43
46
|
reaction: claims[ImmunizationClaim.ReactionDate] ? [{ date: claims[ImmunizationClaim.ReactionDate] }] : undefined,
|
|
44
47
|
};
|
|
@@ -70,6 +73,9 @@ export function immunizationFhirR4ToFlat(resource) {
|
|
|
70
73
|
[ImmunizationClaim.DoseSequence]: protocolApplied?.doseNumberPositiveInt === undefined
|
|
71
74
|
? protocolApplied?.doseNumberString
|
|
72
75
|
: String(protocolApplied.doseNumberPositiveInt),
|
|
76
|
+
[ImmunizationClaim.SeriesDoses]: protocolApplied?.seriesDosesPositiveInt === undefined
|
|
77
|
+
? protocolApplied?.seriesDosesString
|
|
78
|
+
: String(protocolApplied.seriesDosesPositiveInt),
|
|
73
79
|
[ImmunizationClaim.Subject]: referenceToValue(resource.patient),
|
|
74
80
|
[ImmunizationClaim.Note]: resource.note?.[0]?.text,
|
|
75
81
|
[ImmunizationClaim.Route]: codingToValue(resource.route?.coding?.[0]),
|
|
@@ -18,6 +18,12 @@ donation independently. Each service can own different Location references and
|
|
|
18
18
|
be the actor of a different Schedule; Slot duration and availability follow
|
|
19
19
|
that Schedule rather than the department.
|
|
20
20
|
|
|
21
|
+
`HealthcareService.appointmentRequired` states whether a person or animal must
|
|
22
|
+
have an appointment before receiving the service. It does not describe when
|
|
23
|
+
the service is available. Operational availability remains authoritative in
|
|
24
|
+
the service's `Schedule` and its `Slot` resources; clients must not copy those
|
|
25
|
+
intervals into a second service-level availability model.
|
|
26
|
+
|
|
21
27
|
`buildServiceScheduleFlatGraph(...)` implements that boundary. It emits one
|
|
22
28
|
independent claims-first `Schedule` plus independent `Slot` rows. A 15-minute
|
|
23
29
|
blood-donation interval and a one-hour plasma-donation interval are represented
|
|
@@ -143,6 +149,9 @@ Repeatable coding identities remain complete `system|code` tokens; labels are
|
|
|
143
149
|
resolved by the terminology service and are not trusted storage identity.
|
|
144
150
|
`HealthcareService.comment` and R5 `Organization.description` are governed
|
|
145
151
|
materialization claims, but are not falsely advertised as R4 SearchParameters.
|
|
152
|
+
The same applies to `HealthcareService.appointmentRequired`: it is an official
|
|
153
|
+
R4/R5 element and a governed materialization claim, not a resource-specific R4
|
|
154
|
+
SearchParameter.
|
|
146
155
|
|
|
147
156
|
`materializeClaimsFirstResourceToFhirR5(...)` accepts only minimal resource
|
|
148
157
|
identity plus `resource.meta.claims`. It rejects nested native FHIR fields and
|