sos-sdk-core-ts 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,312 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
12
+ var _CommunicationBundleEditor_instances, _CommunicationBundleEditor_messages, _CommunicationBundleEditor_committed, _CommunicationBundleEditor_commitCommunication, _CommunicationMessageEditor_parent, _CommunicationMessageEditor_status, _CommunicationMessageEditor_sender, _CommunicationMessageEditor_recipients, _CommunicationMessageEditor_sent, _CommunicationMessageEditor_attachmentTitle, _CommunicationBundleReader_communicationEntries, _CommunicationMessageReader_resource, _CommunicationMessageReader_claims;
13
+ import { BundleEditor, BundleOperations, BundleTypes } from 'gdc-common-utils-ts/utils/bundle-editor';
14
+ import { BundleReader } from 'gdc-common-utils-ts/utils/bundle-reader';
15
+ import { CommunicationAttachedBundleSession, CommunicationClaimsContext, } from 'gdc-common-utils-ts/utils/communication-attached-bundle-session';
16
+ import { decodeAttachedBundleFromCommunicationClaims } from 'gdc-common-utils-ts/utils/communication-didcomm-payload';
17
+ import { CommunicationClaim } from 'gdc-common-utils-ts/models/interoperable-claims/communication-claims';
18
+ import { AppointmentClaim, DocumentReferenceClaim } from 'fhir-data-utils-ts';
19
+ /**
20
+ * SOS/Vet/UHC extension of the existing GDC BundleEditor.
21
+ *
22
+ * The inherited editor owns the canonical outer claims-first batch. Each
23
+ * staged message is a CommunicationAttachedBundleSession, so the existing
24
+ * attachment synchronization and Bundle lifecycle remain the source of truth.
25
+ * FHIR data utilities are used only at import/materialization boundaries.
26
+ */
27
+ export class CommunicationBundleEditor extends BundleEditor {
28
+ constructor() {
29
+ super();
30
+ _CommunicationBundleEditor_instances.add(this);
31
+ _CommunicationBundleEditor_messages.set(this, []);
32
+ _CommunicationBundleEditor_committed.set(this, false);
33
+ this.setBundleOperation(BundleOperations.create)
34
+ .setBundleType(BundleTypes.batch)
35
+ .setAllowedResourceType('Communication');
36
+ }
37
+ newCommunication() {
38
+ if (__classPrivateFieldGet(this, _CommunicationBundleEditor_committed, "f"))
39
+ throw new TypeError('communication_bundle_already_built');
40
+ const message = new CommunicationMessageEditor(this);
41
+ __classPrivateFieldGet(this, _CommunicationBundleEditor_messages, "f").push(message);
42
+ return message;
43
+ }
44
+ /** Builds the inherited claims-first outer batch after validating every attached Bundle. */
45
+ build() {
46
+ if (!__classPrivateFieldGet(this, _CommunicationBundleEditor_messages, "f").length)
47
+ throw new TypeError('communication_required');
48
+ if (!__classPrivateFieldGet(this, _CommunicationBundleEditor_committed, "f")) {
49
+ for (const message of __classPrivateFieldGet(this, _CommunicationBundleEditor_messages, "f"))
50
+ __classPrivateFieldGet(this, _CommunicationBundleEditor_instances, "m", _CommunicationBundleEditor_commitCommunication).call(this, message);
51
+ __classPrivateFieldSet(this, _CommunicationBundleEditor_committed, true, "f");
52
+ }
53
+ return super.build();
54
+ }
55
+ }
56
+ _CommunicationBundleEditor_messages = new WeakMap(), _CommunicationBundleEditor_committed = new WeakMap(), _CommunicationBundleEditor_instances = new WeakSet(), _CommunicationBundleEditor_commitCommunication = function _CommunicationBundleEditor_commitCommunication(message) {
57
+ message.validateForBuild();
58
+ const claims = message.getCompleteCommunicationClaims();
59
+ const identifier = requireText(claims[CommunicationClaim.Identifier], 'communication_identifier_required');
60
+ const entry = this.newEntry(identifier);
61
+ for (const [claim, value] of Object.entries(claims))
62
+ entry.setClaim(claim, value);
63
+ entry.create();
64
+ };
65
+ /**
66
+ * Authors the standard FHIR Appointment delivery flow for every sector.
67
+ *
68
+ * Product SDKs use this class directly. They must not introduce human or
69
+ * veterinary Appointment wrappers unless they add a genuinely sector-owned
70
+ * rule that is absent from the FHIR and shared data contracts.
71
+ */
72
+ export class AppointmentCommunicationBundleEditor extends CommunicationBundleEditor {
73
+ addAppointmentCommunication(input) {
74
+ if (input.appointment.resourceType !== 'Appointment')
75
+ throw new TypeError('fhir_appointment_required');
76
+ const appointment = normalizeAppointmentIdentifier(input.appointment);
77
+ const message = this.newCommunication()
78
+ .setIdentifier(input.communicationIdentifier)
79
+ .setStatus(input.status)
80
+ .setSubject(input.subjectReference)
81
+ .setSender(input.senderReference)
82
+ .setRecipients(input.recipientReferences)
83
+ .setAttachmentTitle('appointment-bundle.json')
84
+ .setInnerBundleType('collection')
85
+ .addResource(appointment);
86
+ if (input.category)
87
+ message.setCommunicationCategory(input.category);
88
+ if (input.topic)
89
+ message.setCommunicationTopic(input.topic);
90
+ if (input.text)
91
+ message.setCommunicationText(input.text);
92
+ if (input.sent)
93
+ message.setSent(input.sent);
94
+ if (input.calendarDocument) {
95
+ message.addAttachmentDocument({
96
+ ...input.calendarDocument,
97
+ subjectReference: input.subjectReference,
98
+ eventReferences: [`Appointment/${input.appointment.id}`],
99
+ });
100
+ }
101
+ message.doneCommunication();
102
+ return this;
103
+ }
104
+ }
105
+ /** Authors one standard Appointment delivery without a product-specific wrapper. */
106
+ export function prepareAppointmentCommunicationBundle(input) {
107
+ return new AppointmentCommunicationBundleEditor()
108
+ .addAppointmentCommunication(input)
109
+ .build();
110
+ }
111
+ /** High-level message editor extending the established attached-Bundle session. */
112
+ export class CommunicationMessageEditor extends CommunicationAttachedBundleSession {
113
+ constructor(parent) {
114
+ super({
115
+ communicationClaims: { '@context': CommunicationClaimsContext },
116
+ initialBundle: { resourceType: 'Bundle', type: 'batch', data: [] },
117
+ });
118
+ _CommunicationMessageEditor_parent.set(this, void 0);
119
+ _CommunicationMessageEditor_status.set(this, void 0);
120
+ _CommunicationMessageEditor_sender.set(this, void 0);
121
+ _CommunicationMessageEditor_recipients.set(this, []);
122
+ _CommunicationMessageEditor_sent.set(this, void 0);
123
+ _CommunicationMessageEditor_attachmentTitle.set(this, void 0);
124
+ __classPrivateFieldSet(this, _CommunicationMessageEditor_parent, parent, "f");
125
+ }
126
+ setIdentifier(value) { this.setCommunicationIdentifier(value); return this; }
127
+ getIdentifier() { return optionalText(this.getCommunicationIdentifier()); }
128
+ setStatus(value) { __classPrivateFieldSet(this, _CommunicationMessageEditor_status, requireText(value, 'communication_status_required'), "f"); return this; }
129
+ getStatus() { return __classPrivateFieldGet(this, _CommunicationMessageEditor_status, "f"); }
130
+ setSubject(value) { this.setCommunicationSubject(value); return this; }
131
+ getSubject() { return optionalText(this.getCommunicationSubject()); }
132
+ setSender(value) { __classPrivateFieldSet(this, _CommunicationMessageEditor_sender, requireText(value, 'communication_sender_required'), "f"); return this; }
133
+ getSender() { return __classPrivateFieldGet(this, _CommunicationMessageEditor_sender, "f"); }
134
+ setRecipients(values) { __classPrivateFieldSet(this, _CommunicationMessageEditor_recipients, uniqueReferences(values, 'communication_recipient_required'), "f"); return this; }
135
+ getRecipients() { return Object.freeze([...__classPrivateFieldGet(this, _CommunicationMessageEditor_recipients, "f")]); }
136
+ setSent(value) { __classPrivateFieldSet(this, _CommunicationMessageEditor_sent, requireText(value, 'communication_sent_required'), "f"); return this; }
137
+ getSent() { return __classPrivateFieldGet(this, _CommunicationMessageEditor_sent, "f"); }
138
+ setAttachmentTitle(value) { __classPrivateFieldSet(this, _CommunicationMessageEditor_attachmentTitle, requireText(value, 'communication_attachment_title_required'), "f"); return this; }
139
+ getAttachmentTitle() { return __classPrivateFieldGet(this, _CommunicationMessageEditor_attachmentTitle, "f"); }
140
+ setInnerBundleType(value) {
141
+ if (!['document', 'batch', 'collection'].includes(value))
142
+ throw new TypeError('fhir_inner_bundle_type_invalid');
143
+ this.setAttachedBundle({ ...this.getAttachedBundle(), type: value });
144
+ return this;
145
+ }
146
+ getInnerBundleType() { return this.getAttachedBundle().type; }
147
+ /** Adds one canonical claims-first resource to the existing attached-Bundle session. */
148
+ addResource(resource, request) {
149
+ const claims = claimsFromResource(resource);
150
+ const identifier = resourceIdentifier(resource, claims);
151
+ this.upsertActiveEntry({
152
+ resourceType: resource.resourceType,
153
+ claims,
154
+ fullUrl: `urn:uuid:${identifier}`,
155
+ ...(request ? { request } : {}),
156
+ }).saveAndReleaseActiveEntry();
157
+ return this;
158
+ }
159
+ /** Adds a PDF, image, audio or calendar file as an inner DocumentReference. */
160
+ addAttachmentDocument(input) {
161
+ const identifier = requireText(input.identifier, 'document_reference_identifier_required');
162
+ const subjectReference = requireText(input.subjectReference, 'document_reference_subject_required');
163
+ const contentType = requireText(input.contentType, 'document_reference_content_type_required');
164
+ if (!input.dataBase64 && !input.url)
165
+ throw new TypeError('document_reference_content_required');
166
+ const claims = compactClaims({
167
+ '@context': CommunicationClaimsContext,
168
+ [DocumentReferenceClaim.Identifier]: identifier,
169
+ [DocumentReferenceClaim.Subject]: subjectReference,
170
+ [DocumentReferenceClaim.ContentType]: contentType,
171
+ [DocumentReferenceClaim.ContentData]: input.dataBase64,
172
+ [DocumentReferenceClaim.Location]: input.url,
173
+ [DocumentReferenceClaim.Description]: input.description || input.title,
174
+ [DocumentReferenceClaim.Date]: input.date,
175
+ [DocumentReferenceClaim.Author]: input.authorReferences?.join(','),
176
+ [DocumentReferenceClaim.EventReference]: input.eventReferences?.join(','),
177
+ });
178
+ this.upsertActiveDocumentReferenceEntry({ claims, fullUrl: `urn:uuid:${identifier}` }).saveAndReleaseActiveEntry();
179
+ return this;
180
+ }
181
+ doneCommunication() { return __classPrivateFieldGet(this, _CommunicationMessageEditor_parent, "f"); }
182
+ /** @internal */
183
+ validateForBuild() {
184
+ requireText(this.getCommunicationIdentifier(), 'communication_identifier_required');
185
+ requireText(__classPrivateFieldGet(this, _CommunicationMessageEditor_status, "f"), 'communication_status_required');
186
+ requireText(this.getCommunicationSubject(), 'communication_subject_required');
187
+ validateAttachedBundle(this.getAttachedBundle());
188
+ }
189
+ /** @internal */
190
+ getCompleteCommunicationClaims() {
191
+ return compactClaims({
192
+ ...this.getCommunicationClaims(),
193
+ [CommunicationClaim.Status]: __classPrivateFieldGet(this, _CommunicationMessageEditor_status, "f"),
194
+ [CommunicationClaim.Sender]: __classPrivateFieldGet(this, _CommunicationMessageEditor_sender, "f"),
195
+ [CommunicationClaim.Recipient]: __classPrivateFieldGet(this, _CommunicationMessageEditor_recipients, "f").length ? __classPrivateFieldGet(this, _CommunicationMessageEditor_recipients, "f").join(',') : undefined,
196
+ [CommunicationClaim.Sent]: __classPrivateFieldGet(this, _CommunicationMessageEditor_sent, "f"),
197
+ [CommunicationClaim.ContentAttachmentTitle]: __classPrivateFieldGet(this, _CommunicationMessageEditor_attachmentTitle, "f"),
198
+ });
199
+ }
200
+ }
201
+ _CommunicationMessageEditor_parent = new WeakMap(), _CommunicationMessageEditor_status = new WeakMap(), _CommunicationMessageEditor_sender = new WeakMap(), _CommunicationMessageEditor_recipients = new WeakMap(), _CommunicationMessageEditor_sent = new WeakMap(), _CommunicationMessageEditor_attachmentTitle = new WeakMap();
202
+ /** Read-only extension of the established BundleReader for an outer Communication batch. */
203
+ export class CommunicationBundleReader extends BundleReader {
204
+ constructor(outerBatch) {
205
+ super(outerBatch);
206
+ _CommunicationBundleReader_communicationEntries.set(this, void 0);
207
+ if (this.getBundleType() !== 'batch')
208
+ throw new TypeError('fhir_batch_bundle_required');
209
+ __classPrivateFieldSet(this, _CommunicationBundleReader_communicationEntries, Object.freeze(this.getEntries().filter(entry => entry.resource?.resourceType === 'Communication')), "f");
210
+ if (!__classPrivateFieldGet(this, _CommunicationBundleReader_communicationEntries, "f").length)
211
+ throw new TypeError('fhir_communication_required');
212
+ }
213
+ getCommunicationCount() { return __classPrivateFieldGet(this, _CommunicationBundleReader_communicationEntries, "f").length; }
214
+ openCommunication(index) {
215
+ if (!Number.isInteger(index) || index < 0 || index >= __classPrivateFieldGet(this, _CommunicationBundleReader_communicationEntries, "f").length)
216
+ throw new RangeError('communication_index_invalid');
217
+ return new CommunicationMessageReader(__classPrivateFieldGet(this, _CommunicationBundleReader_communicationEntries, "f")[index]);
218
+ }
219
+ }
220
+ _CommunicationBundleReader_communicationEntries = new WeakMap();
221
+ /** High-level getters over one claims-first Communication and its attached Bundle. */
222
+ export class CommunicationMessageReader extends BundleReader {
223
+ constructor(entry) {
224
+ const resource = entry.resource;
225
+ if (resource?.resourceType !== 'Communication')
226
+ throw new TypeError('fhir_communication_required');
227
+ const claims = claimsFromResource(resource);
228
+ const attachedBundle = decodeAttachedBundleFromCommunicationClaims(claims);
229
+ super(attachedBundle);
230
+ _CommunicationMessageReader_resource.set(this, void 0);
231
+ _CommunicationMessageReader_claims.set(this, void 0);
232
+ validateAttachedBundle(attachedBundle);
233
+ __classPrivateFieldSet(this, _CommunicationMessageReader_resource, clone(resource), "f");
234
+ __classPrivateFieldSet(this, _CommunicationMessageReader_claims, clone(claims), "f");
235
+ }
236
+ getCommunication() { return clone(__classPrivateFieldGet(this, _CommunicationMessageReader_resource, "f")); }
237
+ getInnerBundle() { return clone(decodeAttachedBundleFromCommunicationClaims(__classPrivateFieldGet(this, _CommunicationMessageReader_claims, "f"))); }
238
+ getIdentifier() { return requireText(__classPrivateFieldGet(this, _CommunicationMessageReader_claims, "f")[CommunicationClaim.Identifier], 'communication_identifier_required'); }
239
+ getStatus() { return optionalText(__classPrivateFieldGet(this, _CommunicationMessageReader_claims, "f")[CommunicationClaim.Status]); }
240
+ getSubject() { return optionalText(__classPrivateFieldGet(this, _CommunicationMessageReader_claims, "f")[CommunicationClaim.Subject]); }
241
+ getSender() { return optionalText(__classPrivateFieldGet(this, _CommunicationMessageReader_claims, "f")[CommunicationClaim.Sender]); }
242
+ getRecipients() { return csv(__classPrivateFieldGet(this, _CommunicationMessageReader_claims, "f")[CommunicationClaim.Recipient]); }
243
+ getSent() { return optionalText(__classPrivateFieldGet(this, _CommunicationMessageReader_claims, "f")[CommunicationClaim.Sent]); }
244
+ getAttachmentTitle() { return optionalText(__classPrivateFieldGet(this, _CommunicationMessageReader_claims, "f")[CommunicationClaim.ContentAttachmentTitle]); }
245
+ getInnerBundleType() { return this.getBundleType(); }
246
+ getResourcesByType(resourceType) {
247
+ return Object.freeze(this.getEntries().flatMap(entry => {
248
+ const resource = entry.resource;
249
+ return resource?.resourceType === resourceType ? [clone(resource)] : [];
250
+ }));
251
+ }
252
+ getAppointments() { return this.getResourcesByType('Appointment'); }
253
+ getDocumentReferences() { return this.getResourcesByType('DocumentReference'); }
254
+ }
255
+ _CommunicationMessageReader_resource = new WeakMap(), _CommunicationMessageReader_claims = new WeakMap();
256
+ function claimsFromResource(resource) {
257
+ const meta = resource.meta;
258
+ const claims = meta && typeof meta === 'object' && !Array.isArray(meta) ? meta.claims : undefined;
259
+ if (!claims || typeof claims !== 'object' || Array.isArray(claims))
260
+ throw new TypeError('claims_first_resource_required');
261
+ return clone(claims);
262
+ }
263
+ function resourceIdentifier(resource, claims) {
264
+ return requireText(resource.id || claims[`${resource.resourceType}.identifier`], 'resource_identifier_required');
265
+ }
266
+ function normalizeAppointmentIdentifier(appointment) {
267
+ const meta = appointment.meta;
268
+ const claims = meta?.claims && typeof meta.claims === 'object' && !Array.isArray(meta.claims)
269
+ ? meta.claims
270
+ : undefined;
271
+ if (!claims)
272
+ throw new TypeError('claims_first_resource_required');
273
+ if (claims[AppointmentClaim.Identifier] !== undefined)
274
+ return appointment;
275
+ return {
276
+ ...appointment,
277
+ meta: {
278
+ ...meta,
279
+ claims: { ...claims, [AppointmentClaim.Identifier]: [appointment.id] },
280
+ },
281
+ };
282
+ }
283
+ function validateAttachedBundle(value) {
284
+ if (!value || typeof value !== 'object' || Array.isArray(value))
285
+ throw new TypeError('fhir_bundle_required');
286
+ const bundle = value;
287
+ if (bundle.resourceType !== 'Bundle')
288
+ throw new TypeError('fhir_bundle_required');
289
+ const type = optionalText(bundle.type);
290
+ if (!type || !['document', 'batch', 'collection'].includes(type))
291
+ throw new TypeError('fhir_inner_bundle_type_invalid');
292
+ const entries = Array.isArray(bundle.data) ? bundle.data : [];
293
+ if (type === 'document' && entries[0]?.resource?.resourceType !== 'Composition')
294
+ throw new TypeError('fhir_document_composition_first_required');
295
+ if (type === 'batch' && entries.some(entry => {
296
+ const request = entry.request;
297
+ return !optionalText(request?.method) || !optionalText(request?.url);
298
+ }))
299
+ throw new TypeError('fhir_batch_request_required');
300
+ }
301
+ function csv(value) {
302
+ if (Array.isArray(value))
303
+ return Object.freeze(value.map(String).map(item => item.trim()).filter(Boolean));
304
+ const normalized = optionalText(value);
305
+ return Object.freeze(normalized ? normalized.split(',').map(item => item.trim()).filter(Boolean) : []);
306
+ }
307
+ function uniqueReferences(values, error) { return [...new Set(values.map(value => requireText(value, error)))]; }
308
+ function compactClaims(input) { return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined && value !== null && value !== '')); }
309
+ function optionalText(value) { return typeof value === 'string' && value.trim() ? value.trim() : undefined; }
310
+ function requireText(value, error) { const normalized = optionalText(value); if (!normalized)
311
+ throw new TypeError(error); return normalized; }
312
+ function clone(value) { return JSON.parse(JSON.stringify(value)); }
@@ -1,4 +1,4 @@
1
- import type { EmergencyAuthorizationEvidence, EmergencyCapability, EvidenceDomain } from 'sos-data-utils-ts/emergency-evidence';
1
+ import type { EmergencyAuthorizationEvidence, EmergencyCapability } from 'sos-data-utils-ts/emergency-evidence';
2
2
  export type { EmergencyAuthorizationEvidence, EmergencyCapability, EvidenceDomain, } from 'sos-data-utils-ts/emergency-evidence';
3
3
  /** Runtime-neutral endpoint selected after authorization succeeds. */
4
4
  export type EmergencyDeliveryTarget = Readonly<{
@@ -18,13 +18,3 @@ export declare function authorizeEmergencyAction(input: Readonly<{
18
18
  evidence: readonly EmergencyAuthorizationEvidence[];
19
19
  now?: string;
20
20
  }>): readonly string[];
21
- /** @deprecated Use `EmergencyDeliveryTarget`. */
22
- export type SoschainEmergencyDeliveryTarget = EmergencyDeliveryTarget;
23
- /** @deprecated Import `EmergencyAuthorizationEvidence` from this module. */
24
- export type SoschainEmergencyAuthorizationEvidence = EmergencyAuthorizationEvidence;
25
- /** @deprecated Import `EmergencyCapability` from this module. */
26
- export type SoschainEmergencyCapability = EmergencyCapability;
27
- /** @deprecated Import `EvidenceDomain` from this module. */
28
- export type SoschainEvidenceDomain = EvidenceDomain;
29
- /** @deprecated Use `authorizeEmergencyAction`. */
30
- export declare const authorizeSoschainEmergencyAction: typeof authorizeEmergencyAction;
@@ -17,5 +17,3 @@ export function authorizeEmergencyAction(input) {
17
17
  }
18
18
  return [...evidenceIds];
19
19
  }
20
- /** @deprecated Use `authorizeEmergencyAction`. */
21
- export const authorizeSoschainEmergencyAction = authorizeEmergencyAction;
@@ -0,0 +1,40 @@
1
+ import { type ClinicalCoding, type FhirResource } from 'fhir-data-utils-ts';
2
+ export type EncounterTransferSourceDisposition = 'returning' | 'not-returning';
3
+ export type EncounterTransferProjectionInput = Readonly<{
4
+ communicationIdentifier: string;
5
+ subjectReference: string;
6
+ /** Department Organization that performed and communicates the transfer. */
7
+ departmentSenderReference: string;
8
+ /** Owning subject-index provider, not the emergency contact recipient. */
9
+ indexRecipientReferences: readonly string[];
10
+ occurredAt: string;
11
+ sourceDisposition: EncounterTransferSourceDisposition;
12
+ sourceEncounter: FhirResource & Readonly<{
13
+ resourceType: 'Encounter';
14
+ id: string;
15
+ }>;
16
+ destinationEncounter: FhirResource & Readonly<{
17
+ resourceType: 'Encounter';
18
+ id: string;
19
+ }>;
20
+ sourceHistoryIdentifier: string;
21
+ destinationHistoryIdentifier: string;
22
+ encounterClass: ClinicalCoding;
23
+ }>;
24
+ /**
25
+ * Builds the completed Communication used to project an already-committed
26
+ * provider transfer to the subject index.
27
+ *
28
+ * This helper is not the provider transaction itself. The authorized provider
29
+ * first persists the two Encounter updates and two EncounterHistory milestones;
30
+ * only then may this Communication be marked `completed`. If the person will
31
+ * return, the source Encounter becomes `on-hold`; otherwise it becomes
32
+ * `completed`. `discharged` is reserved for the distinct clinical discharge
33
+ * stage and is never inferred merely from movement. The destination becomes
34
+ * `in-progress` on arrival.
35
+ *
36
+ * `Communication.sender` remains the department Organization. The authenticated
37
+ * professional/device is transport audit evidence and must not be substituted
38
+ * into the FHIR sender, author or attester fields.
39
+ */
40
+ export declare function prepareEncounterTransferProjectionCommunication(input: EncounterTransferProjectionInput): Record<string, unknown>;
@@ -0,0 +1,120 @@
1
+ import { EncounterClaim, EncounterHistoryClaim, EncounterStatusR5, buildEncounterHistoryR5FlatEntry, } from 'fhir-data-utils-ts';
2
+ import { CommunicationBundleEditor } from './communication-bundle.js';
3
+ /**
4
+ * Builds the completed Communication used to project an already-committed
5
+ * provider transfer to the subject index.
6
+ *
7
+ * This helper is not the provider transaction itself. The authorized provider
8
+ * first persists the two Encounter updates and two EncounterHistory milestones;
9
+ * only then may this Communication be marked `completed`. If the person will
10
+ * return, the source Encounter becomes `on-hold`; otherwise it becomes
11
+ * `completed`. `discharged` is reserved for the distinct clinical discharge
12
+ * stage and is never inferred merely from movement. The destination becomes
13
+ * `in-progress` on arrival.
14
+ *
15
+ * `Communication.sender` remains the department Organization. The authenticated
16
+ * professional/device is transport audit evidence and must not be substituted
17
+ * into the FHIR sender, author or attester fields.
18
+ */
19
+ export function prepareEncounterTransferProjectionCommunication(input) {
20
+ requireDateTime(input.occurredAt);
21
+ const source = normalizeEncounter(input.sourceEncounter, input.subjectReference);
22
+ const destination = normalizeEncounter(input.destinationEncounter, input.subjectReference);
23
+ if (source.id === destination.id)
24
+ throw new TypeError('distinct_encounters_required');
25
+ const sourceStatus = input.sourceDisposition === 'returning'
26
+ ? EncounterStatusR5.OnHold
27
+ : input.sourceDisposition === 'not-returning'
28
+ ? EncounterStatusR5.Completed
29
+ : (() => { throw new TypeError('source_disposition_invalid'); })();
30
+ const sourceUpdated = withEncounterStatus(source, sourceStatus, input.occurredAt);
31
+ const destinationUpdated = withEncounterStatus(destination, EncounterStatusR5.InProgress, input.occurredAt);
32
+ const sourceHistory = historyResource({
33
+ identifier: input.sourceHistoryIdentifier,
34
+ encounter: sourceUpdated,
35
+ status: sourceStatus,
36
+ occurredAt: input.occurredAt,
37
+ encounterClass: input.encounterClass,
38
+ });
39
+ const destinationHistory = historyResource({
40
+ identifier: input.destinationHistoryIdentifier,
41
+ encounter: destinationUpdated,
42
+ status: EncounterStatusR5.InProgress,
43
+ occurredAt: input.occurredAt,
44
+ encounterClass: input.encounterClass,
45
+ });
46
+ const editor = new CommunicationBundleEditor();
47
+ editor.newCommunication()
48
+ .setIdentifier(input.communicationIdentifier)
49
+ .setStatus('completed')
50
+ .setSubject(input.subjectReference)
51
+ .setSender(input.departmentSenderReference)
52
+ .setRecipients(input.indexRecipientReferences)
53
+ .setSent(input.occurredAt)
54
+ .setAttachmentTitle('encounter-transfer-bundle.json')
55
+ .setInnerBundleType('batch')
56
+ .addResource(sourceUpdated, { method: 'PUT', url: `Encounter/${sourceUpdated.id}` })
57
+ .addResource(sourceHistory, { method: 'POST', url: 'EncounterHistory' })
58
+ .addResource(destinationUpdated, { method: 'PUT', url: `Encounter/${destinationUpdated.id}` })
59
+ .addResource(destinationHistory, { method: 'POST', url: 'EncounterHistory' })
60
+ .doneCommunication();
61
+ return editor.build();
62
+ }
63
+ function normalizeEncounter(resource, subjectReference) {
64
+ if (resource.resourceType !== 'Encounter')
65
+ throw new TypeError('fhir_encounter_required');
66
+ const claims = claimsOf(resource);
67
+ if (claims[EncounterClaim.Subject] !== subjectReference)
68
+ throw new TypeError('encounter_subject_mismatch');
69
+ if (!text(claims[EncounterClaim.ServiceType]))
70
+ throw new TypeError('encounter_service_required');
71
+ return clone(resource);
72
+ }
73
+ function withEncounterStatus(resource, status, occurredAt) {
74
+ const claims = claimsOf(resource);
75
+ const nextClaims = {
76
+ ...claims,
77
+ [EncounterClaim.Status]: status,
78
+ ...(status === EncounterStatusR5.Completed ? { [EncounterClaim.ActualPeriodEnd]: occurredAt } : {}),
79
+ ...(status === EncounterStatusR5.InProgress && !claims[EncounterClaim.ActualPeriodStart]
80
+ ? { [EncounterClaim.ActualPeriodStart]: occurredAt }
81
+ : {}),
82
+ };
83
+ return { ...clone(resource), meta: { ...resource.meta, claims: nextClaims } };
84
+ }
85
+ function historyResource(input) {
86
+ const encounterClaims = claimsOf(input.encounter);
87
+ const id = uuidIdentifier(input.identifier);
88
+ const entry = buildEncounterHistoryR5FlatEntry({
89
+ entryId: id,
90
+ identifier: input.identifier,
91
+ encounterReference: `Encounter/${input.encounter.id}`,
92
+ status: input.status,
93
+ class: input.encounterClass,
94
+ serviceReference: text(encounterClaims[EncounterClaim.ServiceType]),
95
+ subjectReference: text(encounterClaims[EncounterClaim.Subject]),
96
+ actualPeriodStart: input.occurredAt,
97
+ locationReference: text(encounterClaims[EncounterClaim.Location]),
98
+ });
99
+ if (entry.claims[EncounterHistoryClaim.Status] !== input.status)
100
+ throw new TypeError('encounter_history_status_mismatch');
101
+ return { resourceType: 'EncounterHistory', id, meta: { claims: entry.claims } };
102
+ }
103
+ function claimsOf(resource) {
104
+ const meta = resource.meta;
105
+ if (!meta?.claims || typeof meta.claims !== 'object' || Array.isArray(meta.claims))
106
+ throw new TypeError('claims_first_resource_required');
107
+ return clone(meta.claims);
108
+ }
109
+ function uuidIdentifier(value) {
110
+ const normalized = text(value);
111
+ if (!normalized?.startsWith('urn:uuid:') || normalized.length <= 'urn:uuid:'.length)
112
+ throw new TypeError('encounter_history_identifier_invalid');
113
+ return normalized.slice('urn:uuid:'.length);
114
+ }
115
+ function requireDateTime(value) {
116
+ if (!/^\d{4}-\d{2}-\d{2}T/.test(value) || Number.isNaN(Date.parse(value)))
117
+ throw new TypeError('transfer_occurred_at_invalid');
118
+ }
119
+ function text(value) { return typeof value === 'string' && value.trim() ? value.trim() : undefined; }
120
+ function clone(value) { return JSON.parse(JSON.stringify(value)); }
@@ -0,0 +1,39 @@
1
+ import { buildDeviceCalibrationFlatGraph, buildDeviceOperatingAuthorizationFlatResource } from 'fhir-data-utils-ts';
2
+ export type EquipmentComplianceAction = 'record-calibration' | 'publish-operating-authorization';
3
+ export type EquipmentComplianceActorContext = Readonly<{
4
+ actorReference: string;
5
+ departmentReference: string;
6
+ }>;
7
+ export type EquipmentComplianceDecision = EquipmentComplianceActorContext & Readonly<{
8
+ action: EquipmentComplianceAction;
9
+ }>;
10
+ /**
11
+ * Runtime-neutral authorization boundary for equipment compliance records.
12
+ * Products decide whether the authenticated assignment in its department may perform the action;
13
+ * the shared SDK never infers authority from a job-title string.
14
+ */
15
+ export interface EquipmentComplianceAuthorizer {
16
+ authorize(decision: EquipmentComplianceDecision): boolean;
17
+ }
18
+ type CalibrationInput = Parameters<typeof buildDeviceCalibrationFlatGraph>[0];
19
+ type OperatingAuthorizationInput = Parameters<typeof buildDeviceOperatingAuthorizationFlatResource>[0];
20
+ /** Prepares standard claims-first records after an explicit product authorization decision. */
21
+ export declare class EquipmentComplianceManager {
22
+ private readonly authorizer;
23
+ constructor(authorizer: EquipmentComplianceAuthorizer);
24
+ prepareCalibration(input: CalibrationInput, actorContext: EquipmentComplianceActorContext): readonly Readonly<{
25
+ resourceType: "DocumentReference" | "Procedure" | "DeviceMetric" | "RegulatedAuthorization";
26
+ id: string;
27
+ meta: Readonly<{
28
+ claims: Readonly<Record<string, string>>;
29
+ }>;
30
+ }>[];
31
+ prepareOperatingAuthorization(input: OperatingAuthorizationInput, actorContext: EquipmentComplianceActorContext): Readonly<{
32
+ resourceType: "DocumentReference" | "Procedure" | "DeviceMetric" | "RegulatedAuthorization";
33
+ id: string;
34
+ meta: Readonly<{
35
+ claims: Readonly<Record<string, string>>;
36
+ }>;
37
+ }>;
38
+ }
39
+ export {};
@@ -0,0 +1,18 @@
1
+ import { buildDeviceCalibrationFlatGraph, buildDeviceOperatingAuthorizationFlatResource, } from 'fhir-data-utils-ts';
2
+ /** Prepares standard claims-first records after an explicit product authorization decision. */
3
+ export class EquipmentComplianceManager {
4
+ constructor(authorizer) {
5
+ this.authorizer = authorizer;
6
+ }
7
+ prepareCalibration(input, actorContext) {
8
+ if (!this.authorizer.authorize({ action: 'record-calibration', ...actorContext }))
9
+ throw new Error('equipment_calibration_forbidden');
10
+ return buildDeviceCalibrationFlatGraph(input);
11
+ }
12
+ prepareOperatingAuthorization(input, actorContext) {
13
+ if (!this.authorizer.authorize({ action: 'publish-operating-authorization', ...actorContext })) {
14
+ throw new Error('equipment_operating_authorization_forbidden');
15
+ }
16
+ return buildDeviceOperatingAuthorizationFlatResource(input);
17
+ }
18
+ }
@@ -0,0 +1,40 @@
1
+ export declare const FHIR_SCHEDULING_REQUEST_TIMEOUT_MS = 3000;
2
+ export type FhirSchedulingResourceType = 'Appointment' | 'AppointmentResponse' | 'Schedule' | 'Slot' | 'Subscription';
3
+ export type FhirSchedulingResourceCapability = Readonly<{
4
+ resourceType: FhirSchedulingResourceType;
5
+ supported: boolean;
6
+ interactions: readonly string[];
7
+ }>;
8
+ export type FhirSchedulingCapabilitySummary = Readonly<{
9
+ fhirVersion: string;
10
+ softwareName?: string;
11
+ resources: readonly FhirSchedulingResourceCapability[];
12
+ }>;
13
+ export type FhirAccessTokenProvider = Readonly<{
14
+ getAccessToken(): Promise<string>;
15
+ }>;
16
+ export type FhirFetchResponse = Readonly<{
17
+ ok: boolean;
18
+ status: number;
19
+ text(): Promise<string>;
20
+ }>;
21
+ export type FhirFetch = (url: string, init?: Readonly<Record<string, unknown>>) => Promise<FhirFetchResponse>;
22
+ export type FhirSchedulingClientOptions = Readonly<{
23
+ baseUrl: string;
24
+ fetchFn?: FhirFetch;
25
+ accessTokenProvider?: FhirAccessTokenProvider;
26
+ timeoutMs?: number;
27
+ accept?: string;
28
+ }>;
29
+ /** Capability-driven FHIR scheduling client shared by every sector. */
30
+ export declare class FhirSchedulingClient {
31
+ #private;
32
+ private readonly options;
33
+ readonly baseUrl: string;
34
+ constructor(options: FhirSchedulingClientOptions);
35
+ discoverCapabilities(): Promise<FhirSchedulingCapabilitySummary>;
36
+ searchAppointments(parameters: Readonly<Record<string, string>>): Promise<Record<string, unknown>>;
37
+ searchSlots(parameters: Readonly<Record<string, string>>): Promise<Record<string, unknown>>;
38
+ createAppointment(appointment: Readonly<Record<string, unknown>>): Promise<Record<string, unknown>>;
39
+ createAppointmentResponse(response: Readonly<Record<string, unknown>>): Promise<Record<string, unknown>>;
40
+ }