fhir-data-utils-ts 0.6.16 → 0.6.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.6.18 - 2026-10-08
6
+
7
+ - Add the canonical Immunization series-dose claim, its FHIR R4 round trip,
8
+ reusable dose-order validation and a standalone claims-first
9
+ ImmunizationRecommendation builder for progressive clinical authoring.
10
+
11
+ ## 0.6.17 - 2026-10-08
12
+
13
+ - Add the preferred HL7 Observation category catalogue for shared clinical
14
+ editors. English and Spanish labels retain a compact `HL7|code` suffix while
15
+ the stored option remains the complete canonical `system|code` token.
16
+
5
17
  ## 0.6.16 - 2026-10-07
6
18
 
7
19
  - Export the required FHIR R4 code bindings used by reusable IPS authoring
@@ -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 = buildFlatClaimResourceEntry({
60
+ const recommendation = buildImmunizationRecommendationFlatEntry({
37
61
  entryId: recommendationId,
38
- resourceType: ResourceTypesFhirR4.ImmunizationRecommendation,
39
- claims: {
40
- [ImmunizationRecommendationClaim.Patient]: input.subjectReference,
41
- [ImmunizationRecommendationClaim.Date]: isoDate(required(input.recommendationDate, 'immunization_recommendation_date_required'), 'immunization_recommendation_date_invalid'),
42
- [ImmunizationRecommendationClaim.VaccineCode]: vaccineCode,
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
+ }
@@ -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
  ];
@@ -7,7 +7,7 @@ export type FhirClinicalCodedOption = Readonly<{
7
7
  }>;
8
8
  export type FhirClinicalCodedOptionSet = Readonly<{
9
9
  fhirVersion: 'R4';
10
- binding: 'required';
10
+ binding: 'required' | 'preferred';
11
11
  system: string;
12
12
  options: readonly FhirClinicalCodedOption[];
13
13
  }>;
@@ -4,9 +4,10 @@ import { ConditionClaim } from './claims/condition-claims.js';
4
4
  import { ImmunizationClaim } from './claims/immunization-claims.js';
5
5
  import { MedicationStatementClaim } from './claims/medication-statement-claims.js';
6
6
  import { ProcedureClaim } from './claims/procedure-claims.js';
7
- import { ObservationClaim } from './observation-claims.js';
7
+ import { ObservationCategoryCodes, ObservationClaim } from './observation-claims.js';
8
8
  const option = (code, en, es) => Object.freeze({ code, labels: Object.freeze({ en, es }) });
9
9
  const requiredSet = (system, options) => Object.freeze({ fhirVersion: 'R4', binding: 'required', system, options: Object.freeze(options) });
10
+ const preferredSet = (system, options) => Object.freeze({ fhirVersion: 'R4', binding: 'preferred', system, options: Object.freeze(options) });
10
11
  const enteredInError = option('entered-in-error', 'Entered in error', 'Introducido por error');
11
12
  const unknown = option('unknown', 'Unknown', 'Desconocido');
12
13
  /**
@@ -27,6 +28,17 @@ export const FHIR_IPS_CLINICAL_CODED_OPTION_SETS = Object.freeze({
27
28
  [ConditionClaim.VerificationStatus]: requiredSet('http://terminology.hl7.org/CodeSystem/condition-ver-status', [option('unconfirmed', 'Unconfirmed', 'No confirmado'), option('provisional', 'Provisional', 'Provisional'), option('differential', 'Differential', 'Diferencial'), option('confirmed', 'Confirmed', 'Confirmado'), option('refuted', 'Refuted', 'Refutado'), enteredInError]),
28
29
  [ProcedureClaim.Status]: requiredSet('http://hl7.org/fhir/event-status', [option('preparation', 'Preparation', 'Preparación'), option('in-progress', 'In progress', 'En curso'), option('not-done', 'Not done', 'No realizado'), option('on-hold', 'On hold', 'En espera'), option('stopped', 'Stopped', 'Detenido'), option('completed', 'Completed', 'Completado'), enteredInError, unknown]),
29
30
  [ObservationClaim.Status]: requiredSet('http://hl7.org/fhir/observation-status', [option('registered', 'Registered', 'Registrado'), option('preliminary', 'Preliminary', 'Preliminar'), option('final', 'Final', 'Final'), option('amended', 'Amended', 'Modificado'), option('corrected', 'Corrected', 'Corregido'), option('cancelled', 'Cancelled', 'Cancelado'), enteredInError, unknown]),
31
+ [ObservationClaim.Category]: preferredSet(ObservationCategoryCodes.VitalSigns.system, [
32
+ option(ObservationCategoryCodes.SocialHistory.claim, 'Social history (HL7|social-history)', 'Historia social (HL7|social-history)'),
33
+ option(ObservationCategoryCodes.VitalSigns.claim, 'Vital signs (HL7|vital-signs)', 'Signos vitales (HL7|vital-signs)'),
34
+ option(ObservationCategoryCodes.Imaging.claim, 'Imaging (HL7|imaging)', 'Diagnóstico por imagen (HL7|imaging)'),
35
+ option(ObservationCategoryCodes.Laboratory.claim, 'Laboratory (HL7|laboratory)', 'Laboratorio (HL7|laboratory)'),
36
+ option(ObservationCategoryCodes.Procedure.claim, 'Procedure (HL7|procedure)', 'Procedimiento (HL7|procedure)'),
37
+ option(ObservationCategoryCodes.Survey.claim, 'Survey (HL7|survey)', 'Cuestionario (HL7|survey)'),
38
+ option(ObservationCategoryCodes.Exam.claim, 'Exam (HL7|exam)', 'Exploración (HL7|exam)'),
39
+ option(ObservationCategoryCodes.Therapy.claim, 'Therapy (HL7|therapy)', 'Terapia (HL7|therapy)'),
40
+ option(ObservationCategoryCodes.Activity.claim, 'Activity (HL7|activity)', 'Actividad (HL7|activity)'),
41
+ ]),
30
42
  });
31
43
  /** Returns localized labels while preserving the exact canonical code value. */
32
44
  export function clinicalArtifactCodedOptionsForClaim(claim, locale) {
@@ -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]),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhir-data-utils-ts",
3
- "version": "0.6.16",
3
+ "version": "0.6.18",
4
4
  "description": "FHIR-version-neutral flat-claim and SearchParameter data utilities",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",