fhir-data-utils-ts 0.3.4 → 0.3.5

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.
@@ -33,6 +33,11 @@ description: Complete or audit migrations, claim catalogs, converters, builders,
33
33
  Bundle parsing separate from the claims-first Communication being delivered.
34
34
  This package owns their reusable types and validation, never HTTP delivery,
35
35
  subscriber persistence, authentication or product notification policy.
36
+ - Keep ImagingStudy searchable metadata, clinical references and protected
37
+ endpoint references in neutral flat claims. DICOM parsing, pixels and object
38
+ storage remain in a separately versioned imaging/storage package.
39
+ - Preserve `Encounter.appointment`; an Appointment lifecycle transition must
40
+ not sever the encounter from the scheduling event that originated it.
36
41
 
37
42
  ## Change with executable contracts
38
43
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.5 - 2026-09-26
4
+
5
+ - Preserve the originating `Encounter.appointment` reference and expose the
6
+ canonical FHIR R4 Encounter lifecycle vocabulary.
7
+ - Add product-neutral ImagingStudy flat claims, helpers and R4 projection for
8
+ searchable clinical metadata and protected endpoint references without
9
+ embedding DICOM payloads.
10
+ - Cover Encounter and ImagingStudy with rich round-trip, catalog, helper,
11
+ documentation and package-surface contracts.
12
+
3
13
  ## 0.3.4 - 2026-09-23
4
14
 
5
15
  - Add native FHIR R5 `SubscriptionTopic` and HTTPS rest-hook `Subscription`
package/README.md CHANGED
@@ -53,6 +53,13 @@ those catalogs. The old `/immunization-flat-claims` and
53
53
  `/immunization-reminder-plan` paths are deprecated compatibility re-exports;
54
54
  new code imports `/builders` or `/builders/immunization`.
55
55
 
56
+ Encounter projection preserves the originating `Appointment` reference and
57
+ uses the canonical R4 lifecycle (`in-progress` while care is underway,
58
+ `finished` when it ends). ImagingStudy projection stores searchable study
59
+ metadata and protected endpoint references only. DICOM instances and pixel
60
+ payloads remain outside this neutral package and are never embedded as flat
61
+ claims.
62
+
56
63
  No consumer should copy claim paths or rebuild these structures with literals.
57
64
  Use `fhir-data-utils-ts/claims`, `/converters`, `/fhir-bundle`,
58
65
  `/fhir-search-parameters`, `/clinical-terminology`, `/group-r5` or
@@ -8,3 +8,5 @@ export declare function getEncounterIdentifier(claims: EncounterInteroperableCla
8
8
  export declare function setEncounterIdentifier(claims: EncounterInteroperableClaims, value: string): EncounterInteroperableClaims;
9
9
  export declare function getEncounterParticipantList(claims: EncounterInteroperableClaims): string[];
10
10
  export declare function setEncounterParticipantList(claims: EncounterInteroperableClaims, values: string | readonly string[]): EncounterInteroperableClaims;
11
+ export declare function getEncounterAppointmentList(claims: EncounterInteroperableClaims): string[];
12
+ export declare function setEncounterAppointmentList(claims: EncounterInteroperableClaims, values: string | readonly string[]): EncounterInteroperableClaims;
@@ -24,3 +24,9 @@ export function getEncounterParticipantList(claims) {
24
24
  export function setEncounterParticipantList(claims, values) {
25
25
  return setEncounterClaimList(claims, EncounterClaim.Participant, values);
26
26
  }
27
+ export function getEncounterAppointmentList(claims) {
28
+ return getEncounterClaimList(claims, EncounterClaim.Appointment);
29
+ }
30
+ export function setEncounterAppointmentList(claims, values) {
31
+ return setEncounterClaimList(claims, EncounterClaim.Appointment, values);
32
+ }
@@ -0,0 +1,8 @@
1
+ import { type GenericInteroperableClaims } from './claim-list-helpers.js';
2
+ export type ImagingStudyInteroperableClaims = GenericInteroperableClaims;
3
+ export declare function getImagingStudyClaimList(claims: ImagingStudyInteroperableClaims, claimKey: string): string[];
4
+ export declare function setImagingStudyClaimList(claims: ImagingStudyInteroperableClaims, claimKey: string, values: string | readonly string[]): ImagingStudyInteroperableClaims;
5
+ export declare function addImagingStudyClaimList(claims: ImagingStudyInteroperableClaims, claimKey: string, values: string | readonly string[]): ImagingStudyInteroperableClaims;
6
+ export declare function removeImagingStudyClaimList(claims: ImagingStudyInteroperableClaims, claimKey: string, values: string | readonly string[]): ImagingStudyInteroperableClaims;
7
+ export declare function getImagingStudyIdentifier(claims: ImagingStudyInteroperableClaims): string;
8
+ export declare function setImagingStudyIdentifier(claims: ImagingStudyInteroperableClaims, value: string): ImagingStudyInteroperableClaims;
@@ -0,0 +1,20 @@
1
+ import { ImagingStudyClaim } from '../claims/imaging-study-claims.js';
2
+ import { addClaimValues, getClaimValues, normalizeClaimScalar, removeClaimValues, setClaimValues, } from './claim-list-helpers.js';
3
+ export function getImagingStudyClaimList(claims, claimKey) {
4
+ return getClaimValues(claims, claimKey);
5
+ }
6
+ export function setImagingStudyClaimList(claims, claimKey, values) {
7
+ return setClaimValues(claims, claimKey, values);
8
+ }
9
+ export function addImagingStudyClaimList(claims, claimKey, values) {
10
+ return addClaimValues(claims, claimKey, values);
11
+ }
12
+ export function removeImagingStudyClaimList(claims, claimKey, values) {
13
+ return removeClaimValues(claims, claimKey, values);
14
+ }
15
+ export function getImagingStudyIdentifier(claims) {
16
+ return normalizeClaimScalar(claims[ImagingStudyClaim.Identifier]);
17
+ }
18
+ export function setImagingStudyIdentifier(claims, value) {
19
+ return { ...claims, [ImagingStudyClaim.Identifier]: normalizeClaimScalar(value) };
20
+ }
@@ -15,6 +15,7 @@ export * from './claims-helpers-document-reference.js';
15
15
  export * from './claims-helpers-encounter.js';
16
16
  export * from './claims-helpers-flag.js';
17
17
  export * from './claims-helpers-immunization.js';
18
+ export * from './claims-helpers-imaging-study.js';
18
19
  export * from './claims-helpers-location.js';
19
20
  export * from './claims-helpers-medication-statement.js';
20
21
  export * from './claims-helpers-observation.js';
@@ -15,6 +15,7 @@ export * from './claims-helpers-document-reference.js';
15
15
  export * from './claims-helpers-encounter.js';
16
16
  export * from './claims-helpers-flag.js';
17
17
  export * from './claims-helpers-immunization.js';
18
+ export * from './claims-helpers-imaging-study.js';
18
19
  export * from './claims-helpers-location.js';
19
20
  export * from './claims-helpers-medication-statement.js';
20
21
  export * from './claims-helpers-observation.js';
@@ -1,6 +1,7 @@
1
1
  import type { ClaimSpec } from './claim-types.js';
2
2
  export declare const EncounterClaim: {
3
3
  readonly Identifier: "Encounter.identifier";
4
+ readonly Appointment: "Encounter.appointment";
4
5
  readonly Status: "Encounter.status";
5
6
  readonly Class: "Encounter.class";
6
7
  readonly Type: "Encounter.type";
@@ -14,5 +15,17 @@ export declare const EncounterClaim: {
14
15
  readonly Diagnosis: "Encounter.diagnosis";
15
16
  readonly Location: "Encounter.location";
16
17
  };
18
+ /** Canonical FHIR R4 Encounter lifecycle codes. */
19
+ export declare enum EncounterStatus {
20
+ Planned = "planned",
21
+ Arrived = "arrived",
22
+ Triaged = "triaged",
23
+ InProgress = "in-progress",
24
+ OnLeave = "onleave",
25
+ Finished = "finished",
26
+ Cancelled = "cancelled",
27
+ EnteredInError = "entered-in-error",
28
+ Unknown = "unknown"
29
+ }
17
30
  export type EncounterClaimKey = typeof EncounterClaim[keyof typeof EncounterClaim];
18
31
  export declare const EncounterClaimSpecs: ClaimSpec[];
@@ -2,6 +2,7 @@
2
2
  // File: src/models/interoperable-claims/encounter-claims.ts
3
3
  export const EncounterClaim = {
4
4
  Identifier: 'Encounter.identifier',
5
+ Appointment: 'Encounter.appointment',
5
6
  Status: 'Encounter.status',
6
7
  Class: 'Encounter.class',
7
8
  Type: 'Encounter.type',
@@ -15,8 +16,22 @@ export const EncounterClaim = {
15
16
  Diagnosis: 'Encounter.diagnosis',
16
17
  Location: 'Encounter.location',
17
18
  };
19
+ /** Canonical FHIR R4 Encounter lifecycle codes. */
20
+ export var EncounterStatus;
21
+ (function (EncounterStatus) {
22
+ EncounterStatus["Planned"] = "planned";
23
+ EncounterStatus["Arrived"] = "arrived";
24
+ EncounterStatus["Triaged"] = "triaged";
25
+ EncounterStatus["InProgress"] = "in-progress";
26
+ EncounterStatus["OnLeave"] = "onleave";
27
+ EncounterStatus["Finished"] = "finished";
28
+ EncounterStatus["Cancelled"] = "cancelled";
29
+ EncounterStatus["EnteredInError"] = "entered-in-error";
30
+ EncounterStatus["Unknown"] = "unknown";
31
+ })(EncounterStatus || (EncounterStatus = {}));
18
32
  export const EncounterClaimSpecs = [
19
33
  { key: EncounterClaim.Identifier, meaning: 'Business identifier.', example: 'enc-001' },
34
+ { key: EncounterClaim.Appointment, meaning: 'Originating appointment references (CSV).', example: 'Appointment/appointment-1' },
20
35
  { key: EncounterClaim.Status, meaning: 'Encounter status.', example: 'finished' },
21
36
  { key: EncounterClaim.Class, meaning: 'Encounter class token.', example: 'http://terminology.hl7.org/CodeSystem/v3-ActCode|AMB' },
22
37
  { key: EncounterClaim.Type, meaning: 'Encounter type token.', example: 'http://snomed.info/sct|185349003' },
@@ -0,0 +1,25 @@
1
+ import type { ClaimSpec } from './claim-types.js';
2
+ /** Product-neutral flat claims for searchable FHIR R4 ImagingStudy metadata. */
3
+ export declare const ImagingStudyClaim: {
4
+ readonly Identifier: "ImagingStudy.identifier";
5
+ readonly Status: "ImagingStudy.status";
6
+ readonly Modality: "ImagingStudy.modality";
7
+ readonly Subject: "ImagingStudy.subject";
8
+ readonly Patient: "ImagingStudy.patient";
9
+ readonly Encounter: "ImagingStudy.encounter";
10
+ readonly Started: "ImagingStudy.started";
11
+ readonly BasedOn: "ImagingStudy.based-on";
12
+ readonly Referrer: "ImagingStudy.referrer";
13
+ readonly Interpreter: "ImagingStudy.interpreter";
14
+ readonly Endpoint: "ImagingStudy.endpoint";
15
+ readonly ProcedureReference: "ImagingStudy.procedure-reference";
16
+ readonly ProcedureCode: "ImagingStudy.procedure-code";
17
+ readonly ReasonCode: "ImagingStudy.reason-code";
18
+ readonly ReasonReference: "ImagingStudy.reason-reference";
19
+ readonly Location: "ImagingStudy.location";
20
+ readonly NumberOfSeries: "ImagingStudy.number-of-series";
21
+ readonly NumberOfInstances: "ImagingStudy.number-of-instances";
22
+ readonly Description: "ImagingStudy.description";
23
+ };
24
+ export type ImagingStudyClaimKey = typeof ImagingStudyClaim[keyof typeof ImagingStudyClaim];
25
+ export declare const ImagingStudyClaimSpecs: readonly ClaimSpec[];
@@ -0,0 +1,43 @@
1
+ /** Product-neutral flat claims for searchable FHIR R4 ImagingStudy metadata. */
2
+ export const ImagingStudyClaim = {
3
+ Identifier: 'ImagingStudy.identifier',
4
+ Status: 'ImagingStudy.status',
5
+ Modality: 'ImagingStudy.modality',
6
+ Subject: 'ImagingStudy.subject',
7
+ Patient: 'ImagingStudy.patient',
8
+ Encounter: 'ImagingStudy.encounter',
9
+ Started: 'ImagingStudy.started',
10
+ BasedOn: 'ImagingStudy.based-on',
11
+ Referrer: 'ImagingStudy.referrer',
12
+ Interpreter: 'ImagingStudy.interpreter',
13
+ Endpoint: 'ImagingStudy.endpoint',
14
+ ProcedureReference: 'ImagingStudy.procedure-reference',
15
+ ProcedureCode: 'ImagingStudy.procedure-code',
16
+ ReasonCode: 'ImagingStudy.reason-code',
17
+ ReasonReference: 'ImagingStudy.reason-reference',
18
+ Location: 'ImagingStudy.location',
19
+ NumberOfSeries: 'ImagingStudy.number-of-series',
20
+ NumberOfInstances: 'ImagingStudy.number-of-instances',
21
+ Description: 'ImagingStudy.description',
22
+ };
23
+ export const ImagingStudyClaimSpecs = Object.freeze([
24
+ { key: ImagingStudyClaim.Identifier, meaning: 'Business or DICOM study identifier.', example: 'imaging-study-1' },
25
+ { key: ImagingStudyClaim.Status, meaning: 'ImagingStudy lifecycle status.', example: 'available' },
26
+ { key: ImagingStudyClaim.Modality, meaning: 'Repeatable DICOM modality token.', example: 'http://dicom.nema.org/resources/ontology/DCM|DX' },
27
+ { key: ImagingStudyClaim.Subject, meaning: 'Canonical subject reference.', example: 'did:web:subject.example' },
28
+ { key: ImagingStudyClaim.Patient, meaning: 'FHIR R4 patient search alias.', example: 'did:web:subject.example' },
29
+ { key: ImagingStudyClaim.Encounter, meaning: 'Encounter context reference.', example: 'Encounter/encounter-1' },
30
+ { key: ImagingStudyClaim.Started, meaning: 'Study start instant.', example: '2026-09-26T10:15:00Z' },
31
+ { key: ImagingStudyClaim.BasedOn, meaning: 'Originating request references (CSV).', example: 'ServiceRequest/radiography-1' },
32
+ { key: ImagingStudyClaim.Referrer, meaning: 'Referring practitioner reference.', example: 'PractitionerRole/veterinarian-1' },
33
+ { key: ImagingStudyClaim.Interpreter, meaning: 'Interpreter references (CSV).', example: 'PractitionerRole/radiologist-1' },
34
+ { key: ImagingStudyClaim.Endpoint, meaning: 'Protected imaging endpoint references (CSV).', example: 'Endpoint/dicomweb-1' },
35
+ { key: ImagingStudyClaim.ProcedureReference, meaning: 'Performed procedure reference.', example: 'Procedure/radiography-1' },
36
+ { key: ImagingStudyClaim.ProcedureCode, meaning: 'Performed procedure coding token.', example: 'http://snomed.info/sct|363680008' },
37
+ { key: ImagingStudyClaim.ReasonCode, meaning: 'Study reason coding token.', example: 'http://snomed.info/sct|125605004' },
38
+ { key: ImagingStudyClaim.ReasonReference, meaning: 'Study reason references (CSV).', example: 'Condition/fracture-1' },
39
+ { key: ImagingStudyClaim.Location, meaning: 'Acquisition location reference.', example: 'Location/imaging-1' },
40
+ { key: ImagingStudyClaim.NumberOfSeries, meaning: 'Declared series count.', example: '1' },
41
+ { key: ImagingStudyClaim.NumberOfInstances, meaning: 'Declared instance count.', example: '2' },
42
+ { key: ImagingStudyClaim.Description, meaning: 'Study description.', example: 'Left forelimb radiographs' },
43
+ ]);
@@ -17,6 +17,7 @@ export * from './encounter-claims.js';
17
17
  export * from './flag-claims.js';
18
18
  export * from './immunization-claims.js';
19
19
  export * from './immunization-recommendation-claims.js';
20
+ export * from './imaging-study-claims.js';
20
21
  export * from './invoice-claims.js';
21
22
  export * from './location-claims.js';
22
23
  export * from './medication-statement-claims.js';
@@ -17,6 +17,7 @@ export * from './encounter-claims.js';
17
17
  export * from './flag-claims.js';
18
18
  export * from './immunization-claims.js';
19
19
  export * from './immunization-recommendation-claims.js';
20
+ export * from './imaging-study-claims.js';
20
21
  export * from './invoice-claims.js';
21
22
  export * from './location-claims.js';
22
23
  export * from './medication-statement-claims.js';
@@ -1,12 +1,15 @@
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
3
  import { EncounterClaim } from '../claims/encounter-claims.js';
4
- import { codingFromValue, codingToValue, referenceToValue } from './convert-shared.js';
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];
7
7
  return {
8
8
  resourceType: 'Encounter',
9
9
  identifier: claims[EncounterClaim.Identifier] ? [{ value: claims[EncounterClaim.Identifier] }] : undefined,
10
+ appointment: claims[EncounterClaim.Appointment]
11
+ ? claims[EncounterClaim.Appointment].split(',').map((reference) => ({ reference: reference.trim() }))
12
+ : undefined,
10
13
  status: claims[EncounterClaim.Status],
11
14
  class: claims[EncounterClaim.Class]
12
15
  ? (() => {
@@ -37,6 +40,7 @@ export function encounterFhirR4ToFlat(resource) {
37
40
  const klass = resource.class;
38
41
  return {
39
42
  [EncounterClaim.Identifier]: resource.identifier?.[0]?.value,
43
+ [EncounterClaim.Appointment]: referenceListToCsv(resource.appointment),
40
44
  [EncounterClaim.Status]: resource.status,
41
45
  [EncounterClaim.Class]: klass?.code ? (klass.system ? `${klass.system}|${klass.code}` : klass.code) : undefined,
42
46
  [EncounterClaim.Type]: codingToValue(resource.type?.[0]?.coding?.[0]),
@@ -0,0 +1,4 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared.js';
2
+ /** Projects searchable ImagingStudy metadata; DICOM bytes remain in protected imaging storage. */
3
+ export declare function imagingStudyFlatToFhirR4(claims: FlatClaims): FhirResource;
4
+ export declare function imagingStudyFhirR4ToFlat(resource: FhirResource): FlatClaims;
@@ -0,0 +1,82 @@
1
+ import { ImagingStudyClaim } from '../claims/imaging-study-claims.js';
2
+ import { codingFromValue, codingToValue, referenceListToCsv, referenceToValue } from './convert-shared.js';
3
+ function csv(values) {
4
+ if (!Array.isArray(values))
5
+ return undefined;
6
+ const normalized = values.map((value) => String(value || '').trim()).filter(Boolean);
7
+ return normalized.length ? normalized.join(',') : undefined;
8
+ }
9
+ function codingCsv(values) {
10
+ if (!Array.isArray(values))
11
+ return undefined;
12
+ return csv(values.map((value) => codingToValue(value)));
13
+ }
14
+ function codingList(value) {
15
+ if (typeof value !== 'string' || !value.trim())
16
+ return undefined;
17
+ const codings = value.split(',').flatMap((token) => codingFromValue(token.trim()) || []);
18
+ return codings.length ? codings : undefined;
19
+ }
20
+ /** Projects searchable ImagingStudy metadata; DICOM bytes remain in protected imaging storage. */
21
+ export function imagingStudyFlatToFhirR4(claims) {
22
+ const subject = claims[ImagingStudyClaim.Subject] ?? claims[ImagingStudyClaim.Patient];
23
+ return {
24
+ resourceType: 'ImagingStudy',
25
+ identifier: claims[ImagingStudyClaim.Identifier] ? [{ value: claims[ImagingStudyClaim.Identifier] }] : undefined,
26
+ status: claims[ImagingStudyClaim.Status],
27
+ modality: codingList(claims[ImagingStudyClaim.Modality]),
28
+ subject: subject ? { reference: subject } : undefined,
29
+ encounter: claims[ImagingStudyClaim.Encounter] ? { reference: claims[ImagingStudyClaim.Encounter] } : undefined,
30
+ started: claims[ImagingStudyClaim.Started],
31
+ basedOn: references(claims[ImagingStudyClaim.BasedOn]),
32
+ referrer: claims[ImagingStudyClaim.Referrer] ? { reference: claims[ImagingStudyClaim.Referrer] } : undefined,
33
+ interpreter: references(claims[ImagingStudyClaim.Interpreter]),
34
+ endpoint: references(claims[ImagingStudyClaim.Endpoint]),
35
+ procedureReference: claims[ImagingStudyClaim.ProcedureReference] ? { reference: claims[ImagingStudyClaim.ProcedureReference] } : undefined,
36
+ procedureCode: claims[ImagingStudyClaim.ProcedureCode] ? [{ coding: codingFromValue(claims[ImagingStudyClaim.ProcedureCode]) }] : undefined,
37
+ reasonCode: claims[ImagingStudyClaim.ReasonCode] ? [{ coding: codingFromValue(claims[ImagingStudyClaim.ReasonCode]) }] : undefined,
38
+ reasonReference: references(claims[ImagingStudyClaim.ReasonReference]),
39
+ location: claims[ImagingStudyClaim.Location] ? { reference: claims[ImagingStudyClaim.Location] } : undefined,
40
+ numberOfSeries: numberValue(claims[ImagingStudyClaim.NumberOfSeries]),
41
+ numberOfInstances: numberValue(claims[ImagingStudyClaim.NumberOfInstances]),
42
+ description: claims[ImagingStudyClaim.Description],
43
+ };
44
+ }
45
+ export function imagingStudyFhirR4ToFlat(resource) {
46
+ return {
47
+ [ImagingStudyClaim.Identifier]: resource.identifier?.[0]?.value,
48
+ [ImagingStudyClaim.Status]: resource.status,
49
+ [ImagingStudyClaim.Modality]: codingCsv(resource.modality),
50
+ [ImagingStudyClaim.Subject]: referenceToValue(resource.subject),
51
+ [ImagingStudyClaim.Patient]: referenceToValue(resource.subject),
52
+ [ImagingStudyClaim.Encounter]: referenceToValue(resource.encounter),
53
+ [ImagingStudyClaim.Started]: resource.started,
54
+ [ImagingStudyClaim.BasedOn]: referenceListToCsv(resource.basedOn),
55
+ [ImagingStudyClaim.Referrer]: referenceToValue(resource.referrer),
56
+ [ImagingStudyClaim.Interpreter]: referenceListToCsv(resource.interpreter),
57
+ [ImagingStudyClaim.Endpoint]: referenceListToCsv(resource.endpoint),
58
+ [ImagingStudyClaim.ProcedureReference]: referenceToValue(resource.procedureReference),
59
+ [ImagingStudyClaim.ProcedureCode]: codingToValue(resource.procedureCode?.[0]?.coding?.[0]),
60
+ [ImagingStudyClaim.ReasonCode]: codingToValue(resource.reasonCode?.[0]?.coding?.[0]),
61
+ [ImagingStudyClaim.ReasonReference]: referenceListToCsv(resource.reasonReference),
62
+ [ImagingStudyClaim.Location]: referenceToValue(resource.location),
63
+ [ImagingStudyClaim.NumberOfSeries]: scalarNumber(resource.numberOfSeries),
64
+ [ImagingStudyClaim.NumberOfInstances]: scalarNumber(resource.numberOfInstances),
65
+ [ImagingStudyClaim.Description]: resource.description,
66
+ };
67
+ }
68
+ function references(value) {
69
+ if (typeof value !== 'string' || !value.trim())
70
+ return undefined;
71
+ const output = value.split(',').map((reference) => reference.trim()).filter(Boolean).map((reference) => ({ reference }));
72
+ return output.length ? output : undefined;
73
+ }
74
+ function scalarNumber(value) {
75
+ return typeof value === 'number' && Number.isFinite(value) ? String(value) : undefined;
76
+ }
77
+ function numberValue(value) {
78
+ if (typeof value !== 'string' || !value.trim())
79
+ return undefined;
80
+ const number = Number(value);
81
+ return Number.isFinite(number) ? number : undefined;
82
+ }
@@ -15,6 +15,7 @@ export * from './convert-document-reference.js';
15
15
  export * from './convert-encounter.js';
16
16
  export * from './convert-flag.js';
17
17
  export * from './convert-immunization.js';
18
+ export * from './convert-imaging-study.js';
18
19
  export * from './convert-location.js';
19
20
  export * from './convert-medication-statement.js';
20
21
  export * from './convert-observation.js';
@@ -15,6 +15,7 @@ export * from './convert-document-reference.js';
15
15
  export * from './convert-encounter.js';
16
16
  export * from './convert-flag.js';
17
17
  export * from './convert-immunization.js';
18
+ export * from './convert-imaging-study.js';
18
19
  export * from './convert-location.js';
19
20
  export * from './convert-medication-statement.js';
20
21
  export * from './convert-observation.js';
@@ -28,6 +28,7 @@ and transport assumptions must not be copied.
28
28
  | Anonymization field inventory | Test input only | A separately reviewed policy contract; old field lists do not become policy automatically |
29
29
  | ATC, LOINC, SNOMED and EMA JSON | Validate before reuse | Terminology service fixtures, never authoritative embedded catalogs |
30
30
  | DICOM parsing | Extract later | Separate imaging package; do not add `dicom-parser` to the neutral core |
31
+ | ImagingStudy metadata | Rewrite | Neutral flat claims and R4 projection for subject, encounter, modality, procedure and protected endpoint references; DICOM bytes remain outside |
31
32
  | SHA-1 attachment hashing | Retire | No compatibility writer; historical read/verification belongs in a versioned adapter |
32
33
  | `MessageHeader` transport conventions | Retire | Transport is outside the FHIR data contract |
33
34
  | UHC-branded templates and defaults | Retire or move downstream | Product policy belongs in UHC packages, not here |
@@ -51,6 +52,8 @@ The migration surface is executable rather than documentary only:
51
52
  - every migrated claim family has a named catalog and shared list helpers;
52
53
  - every migrated product-neutral converter participates in the converter
53
54
  round-trip matrix, with rich optional-field fixtures for complex resources;
55
+ - Encounter retains its originating Appointment and ImagingStudy retains
56
+ searchable metadata without embedding DICOM object bytes;
54
57
  - FHIR primitives, typed searches, Bundle traversal and fallback conversion
55
58
  have strict success and rejection tests;
56
59
  - Immunization graphs and reminders live once under `src/builders/`; the former
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhir-data-utils-ts",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "FHIR-version-neutral flat-claim and SearchParameter data utilities",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",