fhir-data-utils-ts 0.3.5 → 0.3.7

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 (36) hide show
  1. package/.codex/skills/govern-fhir-data-utilities/SKILL.md +17 -0
  2. package/CHANGELOG.md +18 -0
  3. package/README.md +16 -0
  4. package/dist/builders/diagnostic-report-graph.d.ts +31 -0
  5. package/dist/builders/diagnostic-report-graph.js +86 -0
  6. package/dist/builders/index.d.ts +1 -0
  7. package/dist/builders/index.js +1 -0
  8. package/dist/care-emergency-alert.d.ts +70 -0
  9. package/dist/care-emergency-alert.js +135 -0
  10. package/dist/claim-helpers/claims-helpers-diagnostic-report.d.ts +12 -0
  11. package/dist/claim-helpers/claims-helpers-diagnostic-report.js +40 -0
  12. package/dist/claims/communication-request-claims.d.ts +21 -0
  13. package/dist/claims/communication-request-claims.js +27 -0
  14. package/dist/claims/diagnostic-report-claims.d.ts +3 -36
  15. package/dist/claims/diagnostic-report-claims.js +4 -12
  16. package/dist/claims/encounter-claims.d.ts +1 -0
  17. package/dist/claims/encounter-claims.js +2 -0
  18. package/dist/claims/index.d.ts +2 -0
  19. package/dist/claims/index.js +2 -0
  20. package/dist/claims/service-request-claims.d.ts +24 -0
  21. package/dist/claims/service-request-claims.js +33 -0
  22. package/dist/constants/fhir-resource-types.d.ts +3 -0
  23. package/dist/constants/fhir-resource-types.js +1 -0
  24. package/dist/converters/convert-communication-request.d.ts +5 -0
  25. package/dist/converters/convert-communication-request.js +40 -0
  26. package/dist/converters/convert-encounter.js +2 -0
  27. package/dist/converters/convert-service-request.d.ts +5 -0
  28. package/dist/converters/convert-service-request.js +47 -0
  29. package/dist/converters/index.d.ts +2 -0
  30. package/dist/converters/index.js +2 -0
  31. package/dist/flat-claim-resource-graph.js +7 -6
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +1 -0
  34. package/docs/CARE_EMERGENCY_ALERT_CONTRACTS.md +50 -0
  35. package/docs/UHC_FHIR_UTILS_MIGRATION.md +7 -0
  36. package/package.json +5 -1
@@ -38,6 +38,23 @@ description: Complete or audit migrations, claim catalogs, converters, builders,
38
38
  storage remain in a separately versioned imaging/storage package.
39
39
  - Preserve `Encounter.appointment`; an Appointment lifecycle transition must
40
40
  not sever the encounter from the scheduling event that originated it.
41
+ - Represent actual care movement as independent child Encounters linked with
42
+ `Encounter.partOf` and their own Location. The graph is descriptive and does
43
+ not grant family or professional access.
44
+ - Represent resilient emergency intake as a canonical `ServiceRequest` and
45
+ acknowledgement/triage as a separate durable `Task` focused on it. Never
46
+ infer authorization from requester, performer or owner references.
47
+ - Represent a population campaign with one `CommunicationRequest` against a
48
+ frozen enumerated Group snapshot. Consent, exclusions, channel status and
49
+ eligibility are rechecked per recipient outside this neutral package.
50
+ - Emit coded values as compact `system|code`. Do not create split
51
+ `code-system`, `code-value` or stored `code-display` claims; preserve local
52
+ text separately and resolve trusted displays through terminology.
53
+ - Build laboratory panels with `buildDiagnosticReportFlatGraph(...)`.
54
+ `DiagnosticReport.code` is mandatory and names the report/panel; result
55
+ measurements are independent `Observation` entries and formatted PDF/image
56
+ evidence is an independent `DocumentReference`. Preliminary state does not
57
+ itself authorize ledger or research projection.
41
58
 
42
59
  ## Change with executable contracts
43
60
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.7 - 2026-09-26
4
+
5
+ - Add claims-first FHIR contracts for Location-linked Encounter care journeys,
6
+ emergency ServiceRequest plus triage Task, and Group-snapshot
7
+ CommunicationRequest campaigns.
8
+ - Add R4 projections for ServiceRequest and CommunicationRequest and preserve
9
+ `Encounter.partOf` without introducing split coding claims or persisted code
10
+ displays.
11
+
12
+ ## 0.3.6 - 2026-09-26
13
+
14
+ - Add a product-neutral claims-first DiagnosticReport graph builder for coded
15
+ laboratory panels, independent Observation results and PDF/image
16
+ DocumentReference evidence.
17
+ - Correct the document builder to emit the canonical DocumentReference claim
18
+ catalog and complete list helpers for report results, performers, specimens
19
+ and graph references.
20
+
3
21
  ## 0.3.5 - 2026-09-26
4
22
 
5
23
  - Preserve the originating `Encounter.appointment` reference and expose the
package/README.md CHANGED
@@ -29,6 +29,13 @@ through `<ResourceType>.contained-reference-list`; children do not carry
29
29
  as `<ResourceType>.language`, `.narrative-status` and `.xhtml`. Native FHIR is
30
30
  materialized only by an explicit R4/R5/R6 import or export adapter.
31
31
 
32
+ Laboratory authoring uses `buildDiagnosticReportFlatGraph(...)`.
33
+ `DiagnosticReport.code` is the coded report or panel type, atomic results are
34
+ independent LOINC-coded `Observation` entries, and PDF/JPEG/PNG evidence is an
35
+ independent `DocumentReference`. Preliminary reports can be reviewed and
36
+ securely exchanged; ledger and research eligibility remain downstream policy
37
+ and normally require the confirmed final lifecycle state.
38
+
32
39
  ## Public contract
33
40
 
34
41
  The root export and stable subpaths provide:
@@ -143,6 +150,15 @@ its canonical `Communication.recipient` claim.
143
150
  coverage over converters, builders and core FHIR utilities. The package also
144
151
  tests every stable export subpath. Release validation additionally inspects the
145
152
  packed artifact and installs it in an isolated consumer before publication.
153
+
154
+ ## Care, emergency and population-alert graphs
155
+
156
+ The package provides the neutral FHIR foundation for the three SOSChain
157
+ journeys proven first in VetChain/PetChain: Location-linked Encounter care
158
+ journeys, emergency ServiceRequest plus triage Task, and cohort
159
+ CommunicationRequest against a frozen Group snapshot. See
160
+ [the contract guide](docs/CARE_EMERGENCY_ALERT_CONTRACTS.md). Product policy,
161
+ access, encryption and delivery do not belong in these builders.
146
162
  # Coding-review workflow
147
163
 
148
164
  Imported wording remains in the resource's canonical `*-text` flat claim. Each
@@ -0,0 +1,31 @@
1
+ import { type DiagnosticReportStatus } from '../constants/clinical-statuses.js';
2
+ import { type FlatClaimResourceEntry } from '../flat-claim-resource-graph.js';
3
+ export type DiagnosticReportCoding = Readonly<{
4
+ system: string;
5
+ code: string;
6
+ display?: string;
7
+ text?: string;
8
+ }>;
9
+ export type DiagnosticReportFlatInput = Readonly<{
10
+ entryId: string;
11
+ identifier: string;
12
+ status: DiagnosticReportStatus;
13
+ subjectReference: string;
14
+ issuedAt: string;
15
+ reportCode: DiagnosticReportCoding;
16
+ encounterReference?: string;
17
+ performerReferences?: readonly string[];
18
+ results?: readonly FlatClaimResourceEntry[];
19
+ attachments?: readonly FlatClaimResourceEntry[];
20
+ }>;
21
+ /**
22
+ * Builds a claims-first DiagnosticReport graph for human, animal, or One Health
23
+ * laboratory reports. `DiagnosticReport.code` identifies the report/panel;
24
+ * atomic measurements remain independent `Observation` entries and PDF/JPEG/
25
+ * PNG evidence remains independent `DocumentReference` entries.
26
+ *
27
+ * `preliminary` reports can be exchanged and reviewed, but downstream ledger
28
+ * and secondary-use policy decides whether only `final` is eligible for those
29
+ * projections. This builder does not confer authority or consent.
30
+ */
31
+ export declare function buildDiagnosticReportFlatGraph(input: DiagnosticReportFlatInput): readonly FlatClaimResourceEntry[];
@@ -0,0 +1,86 @@
1
+ // Copyright 2026 ConnectHealth.info (Connecting Solution & Applications Ltd.) under the Apache License, Version 2.0.
2
+ import { DiagnosticReportClaim } from '../claims/diagnostic-report-claims.js';
3
+ import { DiagnosticReportStatuses } from '../constants/clinical-statuses.js';
4
+ import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
5
+ import { buildFlatClaimResourceEntry, linkFlatClaimResourceEntries, } from '../flat-claim-resource-graph.js';
6
+ const DIAGNOSTIC_REPORT_STATUSES = new Set(Object.values(DiagnosticReportStatuses));
7
+ /**
8
+ * Builds a claims-first DiagnosticReport graph for human, animal, or One Health
9
+ * laboratory reports. `DiagnosticReport.code` identifies the report/panel;
10
+ * atomic measurements remain independent `Observation` entries and PDF/JPEG/
11
+ * PNG evidence remains independent `DocumentReference` entries.
12
+ *
13
+ * `preliminary` reports can be exchanged and reviewed, but downstream ledger
14
+ * and secondary-use policy decides whether only `final` is eligible for those
15
+ * projections. This builder does not confer authority or consent.
16
+ */
17
+ export function buildDiagnosticReportFlatGraph(input) {
18
+ const status = required(input.status, 'diagnostic_report_status_required');
19
+ if (!DIAGNOSTIC_REPORT_STATUSES.has(status))
20
+ throw new TypeError('diagnostic_report_status_invalid');
21
+ const claims = {
22
+ [DiagnosticReportClaim.Identifier]: required(input.identifier, 'diagnostic_report_identifier_required'),
23
+ [DiagnosticReportClaim.Status]: status,
24
+ [DiagnosticReportClaim.Subject]: required(input.subjectReference, 'diagnostic_report_subject_required'),
25
+ [DiagnosticReportClaim.Date]: isoDateTime(input.issuedAt, 'diagnostic_report_date_invalid'),
26
+ [DiagnosticReportClaim.Code]: codingToken(input.reportCode),
27
+ };
28
+ if (input.reportCode.display?.trim())
29
+ claims[DiagnosticReportClaim.CodeDisplay] = input.reportCode.display.trim();
30
+ if (input.reportCode.text?.trim())
31
+ claims[DiagnosticReportClaim.CodeText] = input.reportCode.text.trim();
32
+ if (input.encounterReference?.trim()) {
33
+ claims[DiagnosticReportClaim.Encounter] = required(input.encounterReference, 'diagnostic_report_encounter_invalid');
34
+ }
35
+ if (input.performerReferences?.length) {
36
+ claims[DiagnosticReportClaim.Performer] = referenceList(input.performerReferences, 'diagnostic_report_performer_invalid');
37
+ }
38
+ const results = [...(input.results ?? [])];
39
+ if (results.some(entry => entry.resourceType !== ResourceTypesFhirR4.Observation)) {
40
+ throw new TypeError('diagnostic_report_result_invalid');
41
+ }
42
+ const attachments = [...(input.attachments ?? [])];
43
+ if (attachments.some(entry => entry.resourceType !== ResourceTypesFhirR4.DocumentReference)) {
44
+ throw new TypeError('diagnostic_report_attachment_invalid');
45
+ }
46
+ if (results.length)
47
+ claims[DiagnosticReportClaim.Result] = results.map(entry => entry.reference).join(',');
48
+ const report = buildFlatClaimResourceEntry({
49
+ entryId: input.entryId,
50
+ resourceType: ResourceTypesFhirR4.DiagnosticReport,
51
+ claims,
52
+ });
53
+ const children = [...results, ...attachments];
54
+ const graph = [report, ...children];
55
+ return children.length
56
+ ? linkFlatClaimResourceEntries(graph, {
57
+ parentEntryId: report.entryId,
58
+ childEntryIds: children.map(entry => entry.entryId),
59
+ })
60
+ : graph;
61
+ }
62
+ function codingToken(value) {
63
+ const system = required(value.system, 'diagnostic_report_code_system_required');
64
+ const code = required(value.code, 'diagnostic_report_code_required');
65
+ if (system.includes('|') || code.includes('|') || system.includes(',') || code.includes(',')) {
66
+ throw new TypeError('diagnostic_report_code_invalid');
67
+ }
68
+ return `${system}|${code}`;
69
+ }
70
+ function referenceList(values, error) {
71
+ const normalized = [...new Set(values.map(value => required(value, error)))];
72
+ if (normalized.some(value => value.includes(',')))
73
+ throw new TypeError(error);
74
+ return normalized.join(',');
75
+ }
76
+ function isoDateTime(value, error) {
77
+ const normalized = required(value, error);
78
+ if (!/^\d{4}-\d{2}-\d{2}T/.test(normalized) || Number.isNaN(Date.parse(normalized)))
79
+ throw new TypeError(error);
80
+ return normalized;
81
+ }
82
+ function required(value, error) {
83
+ if (typeof value !== 'string' || !value.trim())
84
+ throw new TypeError(error);
85
+ return value.trim();
86
+ }
@@ -1,2 +1,3 @@
1
+ export * from './diagnostic-report-graph.js';
1
2
  export * from './immunization-graph.js';
2
3
  export * from './immunization-reminders.js';
@@ -1,2 +1,3 @@
1
+ export * from './diagnostic-report-graph.js';
1
2
  export * from './immunization-graph.js';
2
3
  export * from './immunization-reminders.js';
@@ -0,0 +1,70 @@
1
+ import { type FlatClaimResourceEntry } from './flat-claim-resource-graph.js';
2
+ export type ClinicalCoding = Readonly<{
3
+ system: string;
4
+ code: string;
5
+ display?: string;
6
+ text?: string;
7
+ }>;
8
+ export type EncounterJourneyNode = Readonly<{
9
+ entryId: string;
10
+ identifier: string;
11
+ status: 'planned' | 'arrived' | 'triaged' | 'in-progress' | 'onleave' | 'finished' | 'cancelled';
12
+ periodStart: string;
13
+ periodEnd?: string;
14
+ locationReference: string;
15
+ type?: ClinicalCoding;
16
+ }>;
17
+ /**
18
+ * Builds one actual-care Encounter graph from an originating Appointment.
19
+ * Child service/location encounters inherit subject and provider and reference
20
+ * the parent with FHIR `Encounter.partOf`; Appointment remains planning input.
21
+ */
22
+ export declare function buildEncounterCareJourneyFlatGraph(input: Readonly<{
23
+ parent: EncounterJourneyNode & Readonly<{
24
+ subjectReference: string;
25
+ appointmentReference?: string;
26
+ serviceProviderReference: string;
27
+ }>;
28
+ stages?: readonly EncounterJourneyNode[];
29
+ }>): readonly FlatClaimResourceEntry[];
30
+ /** Builds one standard claims-first ServiceRequest; caller identity is input, not inferred authority. */
31
+ export declare function buildServiceRequestFlatEntry(input: Readonly<{
32
+ entryId: string;
33
+ identifier: string;
34
+ status: 'draft' | 'active' | 'on-hold' | 'revoked' | 'completed' | 'entered-in-error' | 'unknown';
35
+ intent: 'proposal' | 'plan' | 'directive' | 'order' | 'original-order' | 'reflex-order' | 'filler-order' | 'instance-order' | 'option';
36
+ priority?: 'routine' | 'urgent' | 'asap' | 'stat';
37
+ category?: ClinicalCoding;
38
+ code: ClinicalCoding;
39
+ subjectReference: string;
40
+ encounterReference?: string;
41
+ requesterReference: string;
42
+ performerReferences: readonly string[];
43
+ authoredAt: string;
44
+ occurrence?: string;
45
+ reasonCodes?: readonly ClinicalCoding[];
46
+ reasonReferences?: readonly string[];
47
+ }>): FlatClaimResourceEntry;
48
+ /** Builds the independent durable Task used to acknowledge and triage one ServiceRequest. */
49
+ export declare function buildServiceRequestTriageTaskFlatEntry(input: Readonly<{
50
+ entryId: string;
51
+ identifier: string;
52
+ serviceRequestReference: string;
53
+ subjectReference: string;
54
+ requesterReference: string;
55
+ ownerReference: string;
56
+ authoredAt: string;
57
+ }>): FlatClaimResourceEntry;
58
+ /** Builds one campaign-level request against a frozen enumerated Group snapshot. */
59
+ export declare function buildCohortCommunicationRequestFlatEntry(input: Readonly<{
60
+ entryId: string;
61
+ identifier: string;
62
+ status: 'draft' | 'active' | 'on-hold' | 'revoked' | 'completed' | 'entered-in-error' | 'unknown';
63
+ priority?: 'routine' | 'urgent' | 'asap' | 'stat';
64
+ category: ClinicalCoding;
65
+ subjectReference: string;
66
+ requesterReference: string;
67
+ authoredAt: string;
68
+ occurrence?: string;
69
+ reasonReferences: readonly string[];
70
+ }>): FlatClaimResourceEntry;
@@ -0,0 +1,135 @@
1
+ import { CommunicationRequestClaim, EncounterClaim, ServiceRequestClaim, TaskClaim, } from './claims/index.js';
2
+ import { buildFlatClaimResourceEntry } from './flat-claim-resource-graph.js';
3
+ /**
4
+ * Builds one actual-care Encounter graph from an originating Appointment.
5
+ * Child service/location encounters inherit subject and provider and reference
6
+ * the parent with FHIR `Encounter.partOf`; Appointment remains planning input.
7
+ */
8
+ export function buildEncounterCareJourneyFlatGraph(input) {
9
+ const parent = encounterEntry(input.parent);
10
+ const stages = (input.stages ?? []).map(stage => encounterEntry({
11
+ ...stage,
12
+ subjectReference: input.parent.subjectReference,
13
+ serviceProviderReference: input.parent.serviceProviderReference,
14
+ partOfReference: parent.reference,
15
+ }));
16
+ return Object.freeze([parent, ...stages]);
17
+ }
18
+ /** Builds one standard claims-first ServiceRequest; caller identity is input, not inferred authority. */
19
+ export function buildServiceRequestFlatEntry(input) {
20
+ const claims = {
21
+ [ServiceRequestClaim.Identifier]: required(input.identifier, 'service_request_identifier_required'),
22
+ [ServiceRequestClaim.Status]: input.status,
23
+ [ServiceRequestClaim.Intent]: input.intent,
24
+ [ServiceRequestClaim.Code]: coding(input.code, 'service_request_code_invalid'),
25
+ [ServiceRequestClaim.Subject]: reference(input.subjectReference, 'service_request_subject_invalid'),
26
+ [ServiceRequestClaim.Requester]: reference(input.requesterReference, 'service_request_requester_invalid'),
27
+ [ServiceRequestClaim.Performer]: references(input.performerReferences, 'service_request_performer_required'),
28
+ [ServiceRequestClaim.AuthoredOn]: dateTime(input.authoredAt, 'service_request_authored_on_invalid'),
29
+ };
30
+ if (input.priority)
31
+ claims[ServiceRequestClaim.Priority] = input.priority;
32
+ if (input.category)
33
+ claims[ServiceRequestClaim.Category] = coding(input.category, 'service_request_category_invalid');
34
+ if (input.code.text?.trim())
35
+ claims[ServiceRequestClaim.CodeText] = input.code.text.trim();
36
+ if (input.encounterReference)
37
+ claims[ServiceRequestClaim.Encounter] = reference(input.encounterReference, 'service_request_encounter_invalid');
38
+ if (input.occurrence)
39
+ claims[ServiceRequestClaim.Occurrence] = dateTime(input.occurrence, 'service_request_occurrence_invalid');
40
+ if (input.reasonCodes?.length)
41
+ claims[ServiceRequestClaim.ReasonCode] = input.reasonCodes.map(value => coding(value, 'service_request_reason_code_invalid')).join(',');
42
+ if (input.reasonReferences?.length)
43
+ claims[ServiceRequestClaim.ReasonReference] = references(input.reasonReferences, 'service_request_reason_reference_invalid');
44
+ return buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'ServiceRequest', claims });
45
+ }
46
+ /** Builds the independent durable Task used to acknowledge and triage one ServiceRequest. */
47
+ export function buildServiceRequestTriageTaskFlatEntry(input) {
48
+ return buildFlatClaimResourceEntry({
49
+ entryId: input.entryId,
50
+ resourceType: 'Task',
51
+ claims: {
52
+ [TaskClaim.Identifier]: required(input.identifier, 'triage_task_identifier_required'),
53
+ [TaskClaim.Status]: 'requested',
54
+ [TaskClaim.Intent]: 'order',
55
+ [TaskClaim.Code]: 'triage',
56
+ [TaskClaim.For]: reference(input.subjectReference, 'triage_task_subject_invalid'),
57
+ [TaskClaim.Requester]: reference(input.requesterReference, 'triage_task_requester_invalid'),
58
+ [TaskClaim.Owner]: reference(input.ownerReference, 'triage_task_owner_invalid'),
59
+ [TaskClaim.Focus]: reference(input.serviceRequestReference, 'triage_task_focus_invalid'),
60
+ [TaskClaim.AuthoredOn]: dateTime(input.authoredAt, 'triage_task_authored_on_invalid'),
61
+ },
62
+ });
63
+ }
64
+ /** Builds one campaign-level request against a frozen enumerated Group snapshot. */
65
+ export function buildCohortCommunicationRequestFlatEntry(input) {
66
+ const claims = {
67
+ [CommunicationRequestClaim.Identifier]: required(input.identifier, 'communication_request_identifier_required'),
68
+ [CommunicationRequestClaim.Status]: input.status,
69
+ [CommunicationRequestClaim.Category]: coding(input.category, 'communication_request_category_invalid'),
70
+ [CommunicationRequestClaim.Subject]: groupReference(input.subjectReference, 'communication_request_subject_group_required'),
71
+ [CommunicationRequestClaim.Requester]: reference(input.requesterReference, 'communication_request_requester_invalid'),
72
+ [CommunicationRequestClaim.AuthoredOn]: dateTime(input.authoredAt, 'communication_request_authored_on_invalid'),
73
+ [CommunicationRequestClaim.ReasonReference]: references(input.reasonReferences, 'communication_request_reason_reference_required'),
74
+ };
75
+ if (input.priority)
76
+ claims[CommunicationRequestClaim.Priority] = input.priority;
77
+ if (input.occurrence)
78
+ claims[CommunicationRequestClaim.Occurrence] = dateTime(input.occurrence, 'communication_request_occurrence_invalid');
79
+ return buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'CommunicationRequest', claims });
80
+ }
81
+ function encounterEntry(input) {
82
+ const claims = {
83
+ [EncounterClaim.Identifier]: required(input.identifier, 'encounter_identifier_required'),
84
+ [EncounterClaim.Status]: input.status,
85
+ [EncounterClaim.Subject]: reference(input.subjectReference, 'encounter_subject_invalid'),
86
+ [EncounterClaim.ServiceProvider]: reference(input.serviceProviderReference, 'encounter_service_provider_invalid'),
87
+ [EncounterClaim.PeriodStart]: dateTime(input.periodStart, 'encounter_period_start_invalid'),
88
+ [EncounterClaim.Location]: reference(input.locationReference, 'encounter_location_invalid'),
89
+ };
90
+ if (input.periodEnd)
91
+ claims[EncounterClaim.PeriodEnd] = dateTime(input.periodEnd, 'encounter_period_end_invalid');
92
+ if (input.appointmentReference)
93
+ claims[EncounterClaim.Appointment] = reference(input.appointmentReference, 'encounter_appointment_invalid');
94
+ if (input.partOfReference)
95
+ claims[EncounterClaim.PartOf] = reference(input.partOfReference, 'encounter_part_of_invalid');
96
+ if (input.type)
97
+ claims[EncounterClaim.Type] = coding(input.type, 'encounter_type_invalid');
98
+ return buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'Encounter', claims });
99
+ }
100
+ function coding(value, error) {
101
+ const system = required(value.system, error);
102
+ const code = required(value.code, error);
103
+ if (system.includes('|') || code.includes('|') || system.includes(',') || code.includes(','))
104
+ throw new TypeError(error);
105
+ return `${system}|${code}`;
106
+ }
107
+ function references(values, error) {
108
+ const normalized = [...new Set(values.map(value => reference(value, error)))];
109
+ if (!normalized.length)
110
+ throw new TypeError(error);
111
+ return normalized.join(',');
112
+ }
113
+ function reference(value, error) {
114
+ const normalized = required(value, error);
115
+ if (normalized.includes(','))
116
+ throw new TypeError(error);
117
+ return normalized;
118
+ }
119
+ function groupReference(value, error) {
120
+ const normalized = reference(value, error);
121
+ if (!normalized.startsWith('Group/'))
122
+ throw new TypeError(error);
123
+ return normalized;
124
+ }
125
+ function dateTime(value, error) {
126
+ const normalized = required(value, error);
127
+ if (!/^\d{4}-\d{2}-\d{2}T/.test(normalized) || Number.isNaN(Date.parse(normalized)))
128
+ throw new TypeError(error);
129
+ return normalized;
130
+ }
131
+ function required(value, error) {
132
+ if (typeof value !== 'string' || !value.trim())
133
+ throw new TypeError(error);
134
+ return value.trim();
135
+ }
@@ -8,8 +8,20 @@ export declare function getDiagnosticReportIdentifier(claims: DiagnosticReportIn
8
8
  export declare function setDiagnosticReportIdentifier(claims: DiagnosticReportInteroperableClaims, value: string): DiagnosticReportInteroperableClaims;
9
9
  export declare function getDiagnosticReportResultList(claims: DiagnosticReportInteroperableClaims): string[];
10
10
  export declare function setDiagnosticReportResultList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
11
+ export declare function addDiagnosticReportResultList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
12
+ export declare function removeDiagnosticReportResultList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
13
+ export declare function getDiagnosticReportPerformerList(claims: DiagnosticReportInteroperableClaims): string[];
14
+ export declare function setDiagnosticReportPerformerList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
15
+ export declare function addDiagnosticReportPerformerList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
16
+ export declare function removeDiagnosticReportPerformerList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
17
+ export declare function getDiagnosticReportSpecimenList(claims: DiagnosticReportInteroperableClaims): string[];
18
+ export declare function setDiagnosticReportSpecimenList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
19
+ export declare function addDiagnosticReportSpecimenList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
20
+ export declare function removeDiagnosticReportSpecimenList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
11
21
  export declare function getDiagnosticReportContainedResourceReferenceList(claims: DiagnosticReportInteroperableClaims): string[];
12
22
  export declare function setDiagnosticReportContainedResourceReferenceList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
23
+ export declare function addDiagnosticReportContainedResourceReferenceList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
24
+ export declare function removeDiagnosticReportContainedResourceReferenceList(claims: DiagnosticReportInteroperableClaims, values: string | readonly string[]): DiagnosticReportInteroperableClaims;
13
25
  /** @deprecated Use `getDiagnosticReportContainedResourceReferenceList`. */
14
26
  export declare function getDiagnosticReportContainedDocumentIdentifierList(claims: DiagnosticReportInteroperableClaims): string[];
15
27
  /** @deprecated Use `setDiagnosticReportContainedResourceReferenceList`. */
@@ -24,6 +24,36 @@ export function getDiagnosticReportResultList(claims) {
24
24
  export function setDiagnosticReportResultList(claims, values) {
25
25
  return setDiagnosticReportClaimList(claims, DiagnosticReportClaim.Result, values);
26
26
  }
27
+ export function addDiagnosticReportResultList(claims, values) {
28
+ return addDiagnosticReportClaimList(claims, DiagnosticReportClaim.Result, values);
29
+ }
30
+ export function removeDiagnosticReportResultList(claims, values) {
31
+ return removeDiagnosticReportClaimList(claims, DiagnosticReportClaim.Result, values);
32
+ }
33
+ export function getDiagnosticReportPerformerList(claims) {
34
+ return getDiagnosticReportClaimList(claims, DiagnosticReportClaim.Performer);
35
+ }
36
+ export function setDiagnosticReportPerformerList(claims, values) {
37
+ return setDiagnosticReportClaimList(claims, DiagnosticReportClaim.Performer, values);
38
+ }
39
+ export function addDiagnosticReportPerformerList(claims, values) {
40
+ return addDiagnosticReportClaimList(claims, DiagnosticReportClaim.Performer, values);
41
+ }
42
+ export function removeDiagnosticReportPerformerList(claims, values) {
43
+ return removeDiagnosticReportClaimList(claims, DiagnosticReportClaim.Performer, values);
44
+ }
45
+ export function getDiagnosticReportSpecimenList(claims) {
46
+ return getDiagnosticReportClaimList(claims, DiagnosticReportClaim.Specimen);
47
+ }
48
+ export function setDiagnosticReportSpecimenList(claims, values) {
49
+ return setDiagnosticReportClaimList(claims, DiagnosticReportClaim.Specimen, values);
50
+ }
51
+ export function addDiagnosticReportSpecimenList(claims, values) {
52
+ return addDiagnosticReportClaimList(claims, DiagnosticReportClaim.Specimen, values);
53
+ }
54
+ export function removeDiagnosticReportSpecimenList(claims, values) {
55
+ return removeDiagnosticReportClaimList(claims, DiagnosticReportClaim.Specimen, values);
56
+ }
27
57
  export function getDiagnosticReportContainedResourceReferenceList(claims) {
28
58
  const canonical = getDiagnosticReportClaimList(claims, DiagnosticReportClaim.ContainedReferenceList);
29
59
  const legacy = getDiagnosticReportClaimList(claims, DiagnosticReportClaim.ContainedDocuments);
@@ -37,6 +67,16 @@ export function setDiagnosticReportContainedResourceReferenceList(claims, values
37
67
  delete cleaned[DiagnosticReportClaim.ContainedDocuments];
38
68
  return cleaned;
39
69
  }
70
+ export function addDiagnosticReportContainedResourceReferenceList(claims, values) {
71
+ return setDiagnosticReportContainedResourceReferenceList(claims, [
72
+ ...getDiagnosticReportContainedResourceReferenceList(claims),
73
+ ...(Array.isArray(values) ? values : [values]),
74
+ ]);
75
+ }
76
+ export function removeDiagnosticReportContainedResourceReferenceList(claims, values) {
77
+ const removals = new Set(Array.isArray(values) ? values : [values]);
78
+ return setDiagnosticReportContainedResourceReferenceList(claims, getDiagnosticReportContainedResourceReferenceList(claims).filter(value => !removals.has(value)));
79
+ }
40
80
  /** @deprecated Use `getDiagnosticReportContainedResourceReferenceList`. */
41
81
  export function getDiagnosticReportContainedDocumentIdentifierList(claims) {
42
82
  return getDiagnosticReportContainedResourceReferenceList(claims);
@@ -0,0 +1,21 @@
1
+ import type { ClaimSpec } from './claim-types.js';
2
+ /** Canonical claims-first keys for FHIR CommunicationRequest. */
3
+ export declare const CommunicationRequestClaim: Readonly<{
4
+ readonly Identifier: "CommunicationRequest.identifier";
5
+ readonly BasedOn: "CommunicationRequest.based-on";
6
+ readonly Status: "CommunicationRequest.status";
7
+ readonly Priority: "CommunicationRequest.priority";
8
+ readonly Category: "CommunicationRequest.category";
9
+ readonly Subject: "CommunicationRequest.subject";
10
+ readonly About: "CommunicationRequest.about";
11
+ readonly Encounter: "CommunicationRequest.encounter";
12
+ readonly Requester: "CommunicationRequest.requester";
13
+ readonly Recipient: "CommunicationRequest.recipient";
14
+ readonly AuthoredOn: "CommunicationRequest.authored-on";
15
+ readonly Occurrence: "CommunicationRequest.occurrence";
16
+ readonly ReasonCode: "CommunicationRequest.reason-code";
17
+ readonly ReasonReference: "CommunicationRequest.reason-reference";
18
+ readonly PayloadContent: "CommunicationRequest.payload-content";
19
+ }>;
20
+ export type CommunicationRequestClaimKey = typeof CommunicationRequestClaim[keyof typeof CommunicationRequestClaim];
21
+ export declare const CommunicationRequestClaimSpecs: readonly ClaimSpec[];
@@ -0,0 +1,27 @@
1
+ /** Canonical claims-first keys for FHIR CommunicationRequest. */
2
+ export const CommunicationRequestClaim = Object.freeze({
3
+ Identifier: 'CommunicationRequest.identifier',
4
+ BasedOn: 'CommunicationRequest.based-on',
5
+ Status: 'CommunicationRequest.status',
6
+ Priority: 'CommunicationRequest.priority',
7
+ Category: 'CommunicationRequest.category',
8
+ Subject: 'CommunicationRequest.subject',
9
+ About: 'CommunicationRequest.about',
10
+ Encounter: 'CommunicationRequest.encounter',
11
+ Requester: 'CommunicationRequest.requester',
12
+ Recipient: 'CommunicationRequest.recipient',
13
+ AuthoredOn: 'CommunicationRequest.authored-on',
14
+ Occurrence: 'CommunicationRequest.occurrence',
15
+ ReasonCode: 'CommunicationRequest.reason-code',
16
+ ReasonReference: 'CommunicationRequest.reason-reference',
17
+ PayloadContent: 'CommunicationRequest.payload-content',
18
+ });
19
+ export const CommunicationRequestClaimSpecs = Object.freeze([
20
+ { key: CommunicationRequestClaim.Identifier, meaning: 'Business identifier.', example: 'urn:uuid:campaign-1' },
21
+ { key: CommunicationRequestClaim.Status, meaning: 'Request lifecycle status.', example: 'active' },
22
+ { key: CommunicationRequestClaim.Priority, meaning: 'Delivery priority.', example: 'routine' },
23
+ { key: CommunicationRequestClaim.Category, meaning: 'Communication category token.', example: 'http://terminology.hl7.org/CodeSystem/communication-category|notification' },
24
+ { key: CommunicationRequestClaim.Subject, meaning: 'Subject or evaluated Group reference.', example: 'Group/snapshot-1' },
25
+ { key: CommunicationRequestClaim.Requester, meaning: 'Requesting organization or service.', example: 'Organization/blood-bank-1' },
26
+ { key: CommunicationRequestClaim.AuthoredOn, meaning: 'Request authoring time.', example: '2026-09-26T11:00:00Z' },
27
+ ]);
@@ -33,31 +33,6 @@ export declare const DiagnosticReportClaim: {
33
33
  */
34
34
  readonly ContainedDocuments: "DiagnosticReport.contained-documents";
35
35
  };
36
- /**
37
- * TODO(ips-next):
38
- * Add resource-level claim helpers in `src/utils/claims-helpers-diagnostic-report.ts`
39
- * following the same contract already used by Consent/Medication/Condition/Allergy:
40
- * - `getDiagnosticReportClaimList(...)`
41
- * - `setDiagnosticReportClaimList(...)`
42
- * - `addDiagnosticReportClaimList(...)`
43
- * - `removeDiagnosticReportClaimList(...)`
44
- * - focused helpers for:
45
- * - `get/set/add/removeDiagnosticReportResultList`
46
- * - `get/set/add/removeDiagnosticReportPerformerList`
47
- * - `get/set/add/removeDiagnosticReportSpecimenList`
48
- * - `get/set/add/removeDiagnosticReportContainedResourceReferences`
49
- * - `get/setDiagnosticReportPresentedFormContentType`
50
- * - `get/setDiagnosticReportPresentedFormData`
51
- * - `get/setDiagnosticReportPresentedFormUrl`
52
- *
53
- * Keep naming aligned with canonical FHIR search parameter names
54
- * (lowercase with `-`) and reuse the existing `SearchParamNames` + contextualized
55
- * claim-key mapping pattern.
56
- *
57
- * This file is intentionally type-first only for now so GW Core and
58
- * `common-utils` can already exercise the rest of the bundle workflow without
59
- * committing to the DiagnosticReport editing surface yet.
60
- */
61
36
  export type DiagnosticReportClaimKey = typeof DiagnosticReportClaim[keyof typeof DiagnosticReportClaim];
62
37
  export declare const DiagnosticReportClaimSpecs: ClaimSpec[];
63
38
  export declare const DiagnosticReportSearchParamNames: {
@@ -95,15 +70,7 @@ export declare enum DiagnosticReportClaimsFhirApi {
95
70
  }
96
71
  export declare const DiagnosticReportSearchParamToClaimKey: Record<DiagnosticReportSearchParamName, DiagnosticReportClaimsFhirApi>;
97
72
  /**
98
- * TODO(ips-next):
99
- * Add `DiagnosticReportClaimsFhirApiExtended` only if/when the bundle editor
100
- * needs operational flat fields beyond the official search parameter surface.
101
- *
102
- * Do not invent camelCase names. Prefer official FHIR search parameter names
103
- * or explicit attachment aliases such as `presented-form-*` and
104
- * `contained-reference-list`.
105
- *
106
- * Keep `DiagnosticReport` paused at the typing/mapping layer until:
107
- * 1. `claims-helpers-diagnostic-report.ts` exists
108
- * 2. GW Core readers/tests consume the same shared claim keys
73
+ * Operational attachment and graph claims remain separate from the official
74
+ * SearchParameter catalog above. They support claims-first authoring without
75
+ * pretending that those aliases are searchable FHIR parameters.
109
76
  */
@@ -54,7 +54,7 @@ export const DiagnosticReportClaimSpecs = [
54
54
  { key: DiagnosticReportClaim.PresentedFormContentType, meaning: 'FHIR presentedForm attachment content type.', example: 'application/pdf' },
55
55
  { key: DiagnosticReportClaim.PresentedFormData, meaning: 'FHIR presentedForm inline base64 payload.', example: 'JVBERi0xLjc...' },
56
56
  { key: DiagnosticReportClaim.PresentedFormUrl, meaning: 'FHIR presentedForm external URL.', example: 'https://example.org/report.pdf' },
57
- { key: DiagnosticReportClaim.ContainedDocuments, meaning: 'CSV of linked DocumentReference identifiers stored in the same bundle.', example: 'docref-example-001,docref-example-002' },
57
+ { key: DiagnosticReportClaim.ContainedReferenceList, meaning: 'Canonical CSV of independent result and evidence resource references linked by this report.', example: 'Observation/result-1,DocumentReference/report-1' },
58
58
  ];
59
59
  export const DiagnosticReportSearchParamNames = {
60
60
  BasedOn: 'based-on',
@@ -106,15 +106,7 @@ export const DiagnosticReportSearchParamToClaimKey = {
106
106
  [DiagnosticReportSearchParamNames.Subject]: DiagnosticReportClaimsFhirApi.Subject,
107
107
  };
108
108
  /**
109
- * TODO(ips-next):
110
- * Add `DiagnosticReportClaimsFhirApiExtended` only if/when the bundle editor
111
- * needs operational flat fields beyond the official search parameter surface.
112
- *
113
- * Do not invent camelCase names. Prefer official FHIR search parameter names
114
- * or explicit attachment aliases such as `presented-form-*` and
115
- * `contained-reference-list`.
116
- *
117
- * Keep `DiagnosticReport` paused at the typing/mapping layer until:
118
- * 1. `claims-helpers-diagnostic-report.ts` exists
119
- * 2. GW Core readers/tests consume the same shared claim keys
109
+ * Operational attachment and graph claims remain separate from the official
110
+ * SearchParameter catalog above. They support claims-first authoring without
111
+ * pretending that those aliases are searchable FHIR parameters.
120
112
  */
@@ -14,6 +14,7 @@ export declare const EncounterClaim: {
14
14
  readonly ReasonCode: "Encounter.reason-code";
15
15
  readonly Diagnosis: "Encounter.diagnosis";
16
16
  readonly Location: "Encounter.location";
17
+ readonly PartOf: "Encounter.part-of";
17
18
  };
18
19
  /** Canonical FHIR R4 Encounter lifecycle codes. */
19
20
  export declare enum EncounterStatus {
@@ -15,6 +15,7 @@ export const EncounterClaim = {
15
15
  ReasonCode: 'Encounter.reason-code',
16
16
  Diagnosis: 'Encounter.diagnosis',
17
17
  Location: 'Encounter.location',
18
+ PartOf: 'Encounter.part-of',
18
19
  };
19
20
  /** Canonical FHIR R4 Encounter lifecycle codes. */
20
21
  export var EncounterStatus;
@@ -44,4 +45,5 @@ export const EncounterClaimSpecs = [
44
45
  { key: EncounterClaim.ReasonCode, meaning: 'Reason code token.', example: 'http://snomed.info/sct|65363002' },
45
46
  { key: EncounterClaim.Diagnosis, meaning: 'Diagnosis references (CSV).', example: 'Condition/cond-1' },
46
47
  { key: EncounterClaim.Location, meaning: 'Location references (CSV).', example: 'Location/loc-1' },
48
+ { key: EncounterClaim.PartOf, meaning: 'Parent Encounter reference.', example: 'Encounter/encounter-1' },
47
49
  ];
@@ -5,6 +5,7 @@ export * from './appointment-response-claims.js';
5
5
  export * from './care-plan-claims.js';
6
6
  export * from './clinical-impression-claims.js';
7
7
  export * from './communication-claims.js';
8
+ export * from './communication-request-claims.js';
8
9
  export * from './composition-claims.js';
9
10
  export * from './consent-claims.js';
10
11
  export * from './condition-claims.js';
@@ -27,4 +28,5 @@ export * from './practitioner-role-claims.js';
27
28
  export * from './procedure-claims.js';
28
29
  export * from './related-person-claims.js';
29
30
  export * from './research-subject-claims.js';
31
+ export * from './service-request-claims.js';
30
32
  export * from './task-claims.js';
@@ -5,6 +5,7 @@ export * from './appointment-response-claims.js';
5
5
  export * from './care-plan-claims.js';
6
6
  export * from './clinical-impression-claims.js';
7
7
  export * from './communication-claims.js';
8
+ export * from './communication-request-claims.js';
8
9
  export * from './composition-claims.js';
9
10
  export * from './consent-claims.js';
10
11
  export * from './condition-claims.js';
@@ -27,4 +28,5 @@ export * from './practitioner-role-claims.js';
27
28
  export * from './procedure-claims.js';
28
29
  export * from './related-person-claims.js';
29
30
  export * from './research-subject-claims.js';
31
+ export * from './service-request-claims.js';
30
32
  export * from './task-claims.js';
@@ -0,0 +1,24 @@
1
+ import type { ClaimSpec } from './claim-types.js';
2
+ /** Canonical claims-first keys for FHIR ServiceRequest. */
3
+ export declare const ServiceRequestClaim: Readonly<{
4
+ readonly Identifier: "ServiceRequest.identifier";
5
+ readonly Status: "ServiceRequest.status";
6
+ readonly Intent: "ServiceRequest.intent";
7
+ readonly Priority: "ServiceRequest.priority";
8
+ readonly Category: "ServiceRequest.category";
9
+ readonly Code: "ServiceRequest.code";
10
+ readonly CodeText: "ServiceRequest.code-text";
11
+ readonly Subject: "ServiceRequest.subject";
12
+ readonly Encounter: "ServiceRequest.encounter";
13
+ readonly Requester: "ServiceRequest.requester";
14
+ readonly Performer: "ServiceRequest.performer";
15
+ readonly AuthoredOn: "ServiceRequest.authored-on";
16
+ readonly Occurrence: "ServiceRequest.occurrence";
17
+ readonly ReasonCode: "ServiceRequest.reason-code";
18
+ readonly ReasonReference: "ServiceRequest.reason-reference";
19
+ readonly SupportingInfo: "ServiceRequest.supporting-info";
20
+ readonly Specimen: "ServiceRequest.specimen";
21
+ readonly BodySite: "ServiceRequest.body-site";
22
+ }>;
23
+ export type ServiceRequestClaimKey = typeof ServiceRequestClaim[keyof typeof ServiceRequestClaim];
24
+ export declare const ServiceRequestClaimSpecs: readonly ClaimSpec[];
@@ -0,0 +1,33 @@
1
+ /** Canonical claims-first keys for FHIR ServiceRequest. */
2
+ export const ServiceRequestClaim = Object.freeze({
3
+ Identifier: 'ServiceRequest.identifier',
4
+ Status: 'ServiceRequest.status',
5
+ Intent: 'ServiceRequest.intent',
6
+ Priority: 'ServiceRequest.priority',
7
+ Category: 'ServiceRequest.category',
8
+ Code: 'ServiceRequest.code',
9
+ CodeText: 'ServiceRequest.code-text',
10
+ Subject: 'ServiceRequest.subject',
11
+ Encounter: 'ServiceRequest.encounter',
12
+ Requester: 'ServiceRequest.requester',
13
+ Performer: 'ServiceRequest.performer',
14
+ AuthoredOn: 'ServiceRequest.authored-on',
15
+ Occurrence: 'ServiceRequest.occurrence',
16
+ ReasonCode: 'ServiceRequest.reason-code',
17
+ ReasonReference: 'ServiceRequest.reason-reference',
18
+ SupportingInfo: 'ServiceRequest.supporting-info',
19
+ Specimen: 'ServiceRequest.specimen',
20
+ BodySite: 'ServiceRequest.body-site',
21
+ });
22
+ export const ServiceRequestClaimSpecs = Object.freeze([
23
+ { key: ServiceRequestClaim.Identifier, meaning: 'Business identifier.', example: 'urn:uuid:request-1' },
24
+ { key: ServiceRequestClaim.Status, meaning: 'Request lifecycle status.', example: 'active' },
25
+ { key: ServiceRequestClaim.Intent, meaning: 'Request intent.', example: 'order' },
26
+ { key: ServiceRequestClaim.Priority, meaning: 'Request priority.', example: 'stat' },
27
+ { key: ServiceRequestClaim.Category, meaning: 'Category token.', example: 'http://snomed.info/sct|409971007' },
28
+ { key: ServiceRequestClaim.Code, meaning: 'Requested service token.', example: 'http://snomed.info/sct|225358003' },
29
+ { key: ServiceRequestClaim.Subject, meaning: 'Subject reference.', example: 'Patient/subject-1' },
30
+ { key: ServiceRequestClaim.Requester, meaning: 'Requester reference.', example: 'RelatedPerson/requester-1' },
31
+ { key: ServiceRequestClaim.Performer, meaning: 'Requested performer references (CSV).', example: 'HealthcareService/emergency-1' },
32
+ { key: ServiceRequestClaim.AuthoredOn, meaning: 'Request authoring time.', example: '2026-09-26T10:00:00Z' },
33
+ ]);
@@ -13,6 +13,7 @@ export declare const ResourceTypesFhirR4: Readonly<{
13
13
  readonly CarePlan: "CarePlan";
14
14
  readonly ClinicalImpression: "ClinicalImpression";
15
15
  readonly Communication: "Communication";
16
+ readonly CommunicationRequest: "CommunicationRequest";
16
17
  readonly Composition: "Composition";
17
18
  readonly Condition: "Condition";
18
19
  readonly Consent: "Consent";
@@ -68,6 +69,7 @@ export declare const FhirResourceTypes: Readonly<{
68
69
  readonly CarePlan: "CarePlan";
69
70
  readonly ClinicalImpression: "ClinicalImpression";
70
71
  readonly Communication: "Communication";
72
+ readonly CommunicationRequest: "CommunicationRequest";
71
73
  readonly Composition: "Composition";
72
74
  readonly Condition: "Condition";
73
75
  readonly Consent: "Consent";
@@ -119,6 +121,7 @@ export declare const ResourceTypesFhirR5: Readonly<{
119
121
  readonly CarePlan: "CarePlan";
120
122
  readonly ClinicalImpression: "ClinicalImpression";
121
123
  readonly Communication: "Communication";
124
+ readonly CommunicationRequest: "CommunicationRequest";
122
125
  readonly Composition: "Composition";
123
126
  readonly Condition: "Condition";
124
127
  readonly Consent: "Consent";
@@ -13,6 +13,7 @@ export const ResourceTypesFhirR4 = Object.freeze({
13
13
  CarePlan: 'CarePlan',
14
14
  ClinicalImpression: 'ClinicalImpression',
15
15
  Communication: 'Communication',
16
+ CommunicationRequest: 'CommunicationRequest',
16
17
  Composition: 'Composition',
17
18
  Condition: 'Condition',
18
19
  Consent: 'Consent',
@@ -0,0 +1,5 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared.js';
2
+ /** Projects one governed communication request to the FHIR R4 wire shape. */
3
+ export declare function communicationRequestFlatToFhirR4(claims: FlatClaims): FhirResource;
4
+ /** Reads a FHIR R4 CommunicationRequest into canonical flat claims. */
5
+ export declare function communicationRequestFhirR4ToFlat(resource: FhirResource): FlatClaims;
@@ -0,0 +1,40 @@
1
+ import { CommunicationRequestClaim } from '../claims/communication-request-claims.js';
2
+ import { codingFromValue, codingListFromCsv, codingListToCsv, codingToValue, referenceListToCsv, referenceToValue } from './convert-shared.js';
3
+ /** Projects one governed communication request to the FHIR R4 wire shape. */
4
+ export function communicationRequestFlatToFhirR4(claims) {
5
+ return {
6
+ resourceType: 'CommunicationRequest', identifier: claims[CommunicationRequestClaim.Identifier] ? [{ value: claims[CommunicationRequestClaim.Identifier] }] : undefined,
7
+ basedOn: referenceCsv(claims[CommunicationRequestClaim.BasedOn]), status: claims[CommunicationRequestClaim.Status], priority: claims[CommunicationRequestClaim.Priority],
8
+ category: claims[CommunicationRequestClaim.Category] ? [{ coding: codingFromValue(claims[CommunicationRequestClaim.Category]) }] : undefined,
9
+ subject: claims[CommunicationRequestClaim.Subject] ? { reference: claims[CommunicationRequestClaim.Subject] } : undefined,
10
+ about: referenceCsv(claims[CommunicationRequestClaim.About]), encounter: claims[CommunicationRequestClaim.Encounter] ? { reference: claims[CommunicationRequestClaim.Encounter] } : undefined,
11
+ requester: claims[CommunicationRequestClaim.Requester] ? { reference: claims[CommunicationRequestClaim.Requester] } : undefined,
12
+ recipient: referenceCsv(claims[CommunicationRequestClaim.Recipient]), authoredOn: claims[CommunicationRequestClaim.AuthoredOn],
13
+ occurrenceDateTime: claims[CommunicationRequestClaim.Occurrence],
14
+ reasonCode: claims[CommunicationRequestClaim.ReasonCode] ? [{ coding: codingListFromCsv(claims[CommunicationRequestClaim.ReasonCode]) }] : undefined,
15
+ reasonReference: referenceCsv(claims[CommunicationRequestClaim.ReasonReference]),
16
+ payload: claims[CommunicationRequestClaim.PayloadContent] ? [{ contentString: claims[CommunicationRequestClaim.PayloadContent] }] : undefined,
17
+ };
18
+ }
19
+ /** Reads a FHIR R4 CommunicationRequest into canonical flat claims. */
20
+ export function communicationRequestFhirR4ToFlat(resource) {
21
+ return {
22
+ [CommunicationRequestClaim.Identifier]: resource.identifier?.[0]?.value,
23
+ [CommunicationRequestClaim.BasedOn]: referenceListToCsv(resource.basedOn),
24
+ [CommunicationRequestClaim.Status]: resource.status, [CommunicationRequestClaim.Priority]: resource.priority,
25
+ [CommunicationRequestClaim.Category]: codingToValue(resource.category?.[0]?.coding?.[0]),
26
+ [CommunicationRequestClaim.Subject]: referenceToValue(resource.subject),
27
+ [CommunicationRequestClaim.About]: referenceListToCsv(resource.about),
28
+ [CommunicationRequestClaim.Encounter]: referenceToValue(resource.encounter),
29
+ [CommunicationRequestClaim.Requester]: referenceToValue(resource.requester),
30
+ [CommunicationRequestClaim.Recipient]: referenceListToCsv(resource.recipient),
31
+ [CommunicationRequestClaim.AuthoredOn]: resource.authoredOn, [CommunicationRequestClaim.Occurrence]: resource.occurrenceDateTime,
32
+ [CommunicationRequestClaim.ReasonCode]: codingListToCsv(resource.reasonCode?.flatMap(value => value.coding ?? [])),
33
+ [CommunicationRequestClaim.ReasonReference]: referenceListToCsv(resource.reasonReference),
34
+ [CommunicationRequestClaim.PayloadContent]: resource.payload?.[0]?.contentString,
35
+ };
36
+ }
37
+ function referenceCsv(value) {
38
+ const references = value?.split(',').map(reference => reference.trim()).filter(Boolean) ?? [];
39
+ return references.length ? references.map(reference => ({ reference })) : undefined;
40
+ }
@@ -34,6 +34,7 @@ export function encounterFlatToFhirR4(claims) {
34
34
  location: claims[EncounterClaim.Location]
35
35
  ? claims[EncounterClaim.Location].split(',').map((reference) => ({ location: { reference: reference.trim() } }))
36
36
  : undefined,
37
+ partOf: claims[EncounterClaim.PartOf] ? { reference: claims[EncounterClaim.PartOf] } : undefined,
37
38
  };
38
39
  }
39
40
  export function encounterFhirR4ToFlat(resource) {
@@ -68,5 +69,6 @@ export function encounterFhirR4ToFlat(resource) {
68
69
  .filter((item) => Boolean(item))
69
70
  .join(',')
70
71
  : undefined,
72
+ [EncounterClaim.PartOf]: referenceToValue(resource.partOf),
71
73
  };
72
74
  }
@@ -0,0 +1,5 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared.js';
2
+ /** Projects canonical ServiceRequest flat claims to the FHIR R4 wire shape. */
3
+ export declare function serviceRequestFlatToFhirR4(claims: FlatClaims): FhirResource;
4
+ /** Reads a FHIR R4 ServiceRequest into canonical compact-token flat claims. */
5
+ export declare function serviceRequestFhirR4ToFlat(resource: FhirResource): FlatClaims;
@@ -0,0 +1,47 @@
1
+ import { ServiceRequestClaim } from '../claims/service-request-claims.js';
2
+ import { codingFromValue, codingListFromCsv, codingListToCsv, codingToValue, referenceListToCsv, referenceToValue } from './convert-shared.js';
3
+ /** Projects canonical ServiceRequest flat claims to the FHIR R4 wire shape. */
4
+ export function serviceRequestFlatToFhirR4(claims) {
5
+ const codeCoding = codingFromValue(claims[ServiceRequestClaim.Code])?.[0];
6
+ return {
7
+ resourceType: 'ServiceRequest',
8
+ identifier: claims[ServiceRequestClaim.Identifier] ? [{ value: claims[ServiceRequestClaim.Identifier] }] : undefined,
9
+ status: claims[ServiceRequestClaim.Status], intent: claims[ServiceRequestClaim.Intent], priority: claims[ServiceRequestClaim.Priority],
10
+ category: claims[ServiceRequestClaim.Category] ? [{ coding: codingFromValue(claims[ServiceRequestClaim.Category]) }] : undefined,
11
+ code: codeCoding || claims[ServiceRequestClaim.CodeText] ? { coding: codeCoding ? [codeCoding] : undefined, text: claims[ServiceRequestClaim.CodeText] } : undefined,
12
+ subject: claims[ServiceRequestClaim.Subject] ? { reference: claims[ServiceRequestClaim.Subject] } : undefined,
13
+ encounter: claims[ServiceRequestClaim.Encounter] ? { reference: claims[ServiceRequestClaim.Encounter] } : undefined,
14
+ requester: claims[ServiceRequestClaim.Requester] ? { reference: claims[ServiceRequestClaim.Requester] } : undefined,
15
+ performer: referenceCsv(claims[ServiceRequestClaim.Performer]), authoredOn: claims[ServiceRequestClaim.AuthoredOn],
16
+ occurrenceDateTime: claims[ServiceRequestClaim.Occurrence],
17
+ reasonCode: claims[ServiceRequestClaim.ReasonCode] ? [{ coding: codingListFromCsv(claims[ServiceRequestClaim.ReasonCode]) }] : undefined,
18
+ reasonReference: referenceCsv(claims[ServiceRequestClaim.ReasonReference]),
19
+ supportingInfo: referenceCsv(claims[ServiceRequestClaim.SupportingInfo]), specimen: referenceCsv(claims[ServiceRequestClaim.Specimen]),
20
+ bodySite: claims[ServiceRequestClaim.BodySite] ? [{ coding: codingListFromCsv(claims[ServiceRequestClaim.BodySite]) }] : undefined,
21
+ };
22
+ }
23
+ /** Reads a FHIR R4 ServiceRequest into canonical compact-token flat claims. */
24
+ export function serviceRequestFhirR4ToFlat(resource) {
25
+ const code = resource.code;
26
+ return {
27
+ [ServiceRequestClaim.Identifier]: resource.identifier?.[0]?.value,
28
+ [ServiceRequestClaim.Status]: resource.status, [ServiceRequestClaim.Intent]: resource.intent,
29
+ [ServiceRequestClaim.Priority]: resource.priority,
30
+ [ServiceRequestClaim.Category]: codingToValue(resource.category?.[0]?.coding?.[0]),
31
+ [ServiceRequestClaim.Code]: codingToValue(code?.coding?.[0]), [ServiceRequestClaim.CodeText]: code?.text,
32
+ [ServiceRequestClaim.Subject]: referenceToValue(resource.subject),
33
+ [ServiceRequestClaim.Encounter]: referenceToValue(resource.encounter),
34
+ [ServiceRequestClaim.Requester]: referenceToValue(resource.requester),
35
+ [ServiceRequestClaim.Performer]: referenceListToCsv(resource.performer),
36
+ [ServiceRequestClaim.AuthoredOn]: resource.authoredOn, [ServiceRequestClaim.Occurrence]: resource.occurrenceDateTime,
37
+ [ServiceRequestClaim.ReasonCode]: codingListToCsv(resource.reasonCode?.flatMap(value => value.coding ?? [])),
38
+ [ServiceRequestClaim.ReasonReference]: referenceListToCsv(resource.reasonReference),
39
+ [ServiceRequestClaim.SupportingInfo]: referenceListToCsv(resource.supportingInfo),
40
+ [ServiceRequestClaim.Specimen]: referenceListToCsv(resource.specimen),
41
+ [ServiceRequestClaim.BodySite]: codingListToCsv(resource.bodySite?.flatMap(value => value.coding ?? [])),
42
+ };
43
+ }
44
+ function referenceCsv(value) {
45
+ const references = value?.split(',').map(reference => reference.trim()).filter(Boolean) ?? [];
46
+ return references.length ? references.map(reference => ({ reference })) : undefined;
47
+ }
@@ -4,6 +4,7 @@ export * from './convert-appointment-response.js';
4
4
  export * from './convert-appointment.js';
5
5
  export * from './convert-care-plan.js';
6
6
  export * from './convert-clinical-impression.js';
7
+ export * from './convert-communication-request.js';
7
8
  export * from './convert-composition.js';
8
9
  export * from './convert-consent.js';
9
10
  export * from './convert-condition.js';
@@ -23,3 +24,4 @@ export * from './convert-organization.js';
23
24
  export * from './convert-practitioner-role.js';
24
25
  export * from './convert-procedure.js';
25
26
  export * from './convert-related-person.js';
27
+ export * from './convert-service-request.js';
@@ -4,6 +4,7 @@ export * from './convert-appointment-response.js';
4
4
  export * from './convert-appointment.js';
5
5
  export * from './convert-care-plan.js';
6
6
  export * from './convert-clinical-impression.js';
7
+ export * from './convert-communication-request.js';
7
8
  export * from './convert-composition.js';
8
9
  export * from './convert-consent.js';
9
10
  export * from './convert-condition.js';
@@ -23,3 +24,4 @@ export * from './convert-organization.js';
23
24
  export * from './convert-practitioner-role.js';
24
25
  export * from './convert-procedure.js';
25
26
  export * from './convert-related-person.js';
27
+ export * from './convert-service-request.js';
@@ -70,16 +70,16 @@ export function normalizeLegacyContainedClaims(entry) {
70
70
  */
71
71
  export function buildDocumentReferenceFlatEntry(input) {
72
72
  const claims = {
73
- 'DocumentReference.identifier': requiredToken(input.identifier, 'document_reference_identifier_required'),
74
- 'DocumentReference.content-type': input.contentType,
75
- 'DocumentReference.contenthash': requiredToken(input.contentHash, 'document_reference_contenthash_required'),
73
+ [DocumentReferenceClaim.Identifier]: requiredToken(input.identifier, 'document_reference_identifier_required'),
74
+ [DocumentReferenceClaim.ContentType]: input.contentType,
75
+ [DocumentReferenceClaim.ContentHash]: requiredToken(input.contentHash, 'document_reference_contenthash_required'),
76
76
  };
77
77
  if (input.title?.trim())
78
- claims['DocumentReference.title'] = input.title.trim();
78
+ claims[DocumentReferenceClaim.Description] = input.title.trim();
79
79
  if (input.url?.trim())
80
- claims['DocumentReference.url'] = input.url.trim();
80
+ claims[DocumentReferenceClaim.Location] = input.url.trim();
81
81
  if (input.dataBase64?.trim())
82
- claims['DocumentReference.data'] = input.dataBase64.trim();
82
+ claims[DocumentReferenceClaim.ContentData] = input.dataBase64.trim();
83
83
  return buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'DocumentReference', claims });
84
84
  }
85
85
  function splitCsv(value) {
@@ -106,3 +106,4 @@ function requiredResourceType(value) {
106
106
  throw new TypeError('flat_claim_resource_type_invalid');
107
107
  return normalized;
108
108
  }
109
+ import { DocumentReferenceClaim } from './claims/document-reference-claims.js';
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from './fhir-bundle.js';
14
14
  export * from './terminology-models/index.js';
15
15
  export * from './constants/index.js';
16
16
  export * from './clinical-terminology.js';
17
+ export * from './care-emergency-alert.js';
17
18
  export * from './immunization-flat-claims.js';
18
19
  export * from './immunization-reminder-plan.js';
19
20
  export * from './builders/index.js';
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export * from './fhir-bundle.js';
14
14
  export * from './terminology-models/index.js';
15
15
  export * from './constants/index.js';
16
16
  export * from './clinical-terminology.js';
17
+ export * from './care-emergency-alert.js';
17
18
  export * from './immunization-flat-claims.js';
18
19
  export * from './immunization-reminder-plan.js';
19
20
  export * from './builders/index.js';
@@ -0,0 +1,50 @@
1
+ # Care, emergency and population-alert FHIR contracts
2
+
3
+ This package owns only the product-neutral FHIR representation used by the
4
+ three SOSChain journeys that are first proven in VetChain and PetChain.
5
+ Authorization, controller discovery, encryption, transport, storage and UI
6
+ policy remain in their owning product or service layers.
7
+
8
+ ## Hospital or clinic care journey
9
+
10
+ An `Appointment` plans care. Marking it fulfilled can originate one parent
11
+ `Encounter`; it must not erase the `Encounter.appointment` reference. Admission,
12
+ imaging, intensive care and discharge are independent child `Encounter`
13
+ resources linked through `Encounter.partOf`, each with its actual `Location`.
14
+ This graph supports a bounded location timeline. It does not itself grant a
15
+ relative permission to see locations or clinical records.
16
+
17
+ A voice/video call or encrypted missed-call message may occur before a report
18
+ is published. Transport events are not clinical attestation and must not
19
+ silently change an Encounter, a daily draft or the individual's index.
20
+
21
+ ## Resilient emergency request
22
+
23
+ An emergency request is a standard claims-first `ServiceRequest`. Its service
24
+ and category are compact `system|code` tokens; writers never emit split
25
+ `code-system`, `code-value` or stored `code-display` claims. A local phrase may
26
+ remain in `ServiceRequest.code-text`; a trusted display is resolved from the
27
+ code at the terminology boundary.
28
+
29
+ Acknowledgement and triage use a separate durable `Task` whose `focus`
30
+ references the request. Requester, subject, intended performer and task owner
31
+ are explicit inputs. None of those references is inferred as authorization.
32
+
33
+ ## Donation and public-risk campaign
34
+
35
+ The campaign-level `CommunicationRequest.subject` is a frozen, enumerated
36
+ `Group` snapshot. Its `reasonReference` points to the definitional cohort used
37
+ to create that snapshot. Per-recipient eligibility, current Consent,
38
+ subscription/channel status, exclusions and delivery audit must be rechecked
39
+ outside this neutral package immediately before creating each individual
40
+ `Communication`.
41
+
42
+ The snapshot and request are evidence of evaluation and intent, not permission
43
+ to disclose group membership or send a message.
44
+
45
+ ## Public API
46
+
47
+ Use `fhir-data-utils-ts/care-emergency-alert` for graph builders and the package
48
+ root or `fhir-data-utils-ts/converters` for R4 projections. Persistence remains
49
+ flat claims. Native FHIR is materialized only at an explicit import/export
50
+ boundary.
@@ -83,6 +83,13 @@ one native `Observation.component[]`; it does not export three independent
83
83
  native Observations. Component entries do not use `is-contained`,
84
84
  `contained-parent-reference` or `contained-reference-list`.
85
85
 
86
+ Diagnostic laboratory authoring is owned here by
87
+ `buildDiagnosticReportFlatGraph(...)`: `DiagnosticReport.code` identifies the
88
+ report/panel, independent Observation entries carry atomic results, and
89
+ independent DocumentReference entries carry PDF/image evidence. Sector roles,
90
+ confirmation, ledger eligibility, consent and research policy remain outside
91
+ this package.
92
+
86
93
  Aligned JSON arrays and the older comma-separated `Observation.component-*`
87
94
  claims are accepted only by the migration reader. No current writer emits
88
95
  either representation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fhir-data-utils-ts",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "FHIR-version-neutral flat-claim and SearchParameter data utilities",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -19,6 +19,10 @@
19
19
  "types": "./dist/flat-claim-resource-graph.d.ts",
20
20
  "default": "./dist/flat-claim-resource-graph.js"
21
21
  },
22
+ "./care-emergency-alert": {
23
+ "types": "./dist/care-emergency-alert.d.ts",
24
+ "default": "./dist/care-emergency-alert.js"
25
+ },
22
26
  "./search-parameters": {
23
27
  "types": "./dist/search-parameters.d.ts",
24
28
  "default": "./dist/search-parameters.js"