sos-sdk-core-ts 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +7 -0
- package/dist/bundle-sync-draft.d.ts +61 -0
- package/dist/bundle-sync-draft.js +115 -0
- package/dist/communication-bundle.js +10 -1
- package/dist/encounter-transfer-communication.d.ts +28 -0
- package/dist/encounter-transfer-communication.js +51 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/CARE_EMERGENCY_AND_ALERT_ORCHESTRATION.md +7 -3
- package/docs/ENCOUNTER_TRANSFER_PROJECTION.md +37 -0
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.5.2 - 2026-10-04
|
|
6
|
+
|
|
7
|
+
- Add runtime-neutral accepted-handover orchestration that prepares the
|
|
8
|
+
Encounter/EncounterHistory projection together with the retained, added and
|
|
9
|
+
removed professional Group audience and a family-safe journey event.
|
|
10
|
+
- Preserve every inner resource identifier and project both provider-owned
|
|
11
|
+
Encounter snapshots with idempotent creates, so a subject index does not
|
|
12
|
+
require a prior copy of the source Encounter.
|
|
13
|
+
- Consume the neutral emergency Group builders and remove the obsolete
|
|
14
|
+
product-prefixed helper names from SDK tests and examples.
|
|
15
|
+
|
|
16
|
+
## 0.5.1 - 2026-10-04
|
|
17
|
+
|
|
18
|
+
- Add the shared Bundle synchronization draft contract for clinical documents,
|
|
19
|
+
Consent, emergency contacts and Encounter transfers. Optimistic records stay
|
|
20
|
+
visibly pending after ambiguous or transient provider failures, definite
|
|
21
|
+
rejections restore the last confirmed Bundle, and only authoritative
|
|
22
|
+
provider readback marks a mutation as confirmed.
|
|
23
|
+
|
|
5
24
|
- Add the claims-first Encounter transfer projection helper. It keeps a
|
|
6
25
|
returning source `on-hold`, completes a definitive source departure, starts
|
|
7
26
|
the destination `in-progress`, records both `EncounterHistory` milestones,
|
package/README.md
CHANGED
|
@@ -62,6 +62,13 @@ The shared scheduling and response-correlation boundary is documented in
|
|
|
62
62
|
Source-to-destination care movement and subject-index projection are documented
|
|
63
63
|
in [Encounter transfer projection](docs/ENCOUNTER_TRANSFER_PROJECTION.md).
|
|
64
64
|
|
|
65
|
+
`BundleSyncDraft` is the shared serializable state for editors that must keep a
|
|
66
|
+
local clinical document, Consent, emergency-contact or Encounter mutation
|
|
67
|
+
visible while its provider write is pending. A successful HTTP response is not
|
|
68
|
+
confirmation: the draft becomes `confirmed` only after authoritative provider
|
|
69
|
+
readback. Ambiguous and transport failures remain `retryable`; definite remote
|
|
70
|
+
rejections restore the last confirmed Bundle.
|
|
71
|
+
|
|
65
72
|
The emergency-group contract stores subject references only on an enumerated
|
|
66
73
|
FHIR Group. A group assistance request references that Group from one standard,
|
|
67
74
|
compact-coded ServiceRequest; it never copies members or persists split coding
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { BundleEntry, BundleJsonApi } from 'gdc-common-utils-ts/models/bundle';
|
|
2
|
+
export declare const BundleSyncKinds: Readonly<{
|
|
3
|
+
readonly ClinicalDocument: "clinical-document";
|
|
4
|
+
readonly EncounterTransfer: "encounter-transfer";
|
|
5
|
+
readonly Consent: "consent";
|
|
6
|
+
readonly EmergencyContact: "emergency-contact";
|
|
7
|
+
}>;
|
|
8
|
+
export type BundleSyncKind = typeof BundleSyncKinds[keyof typeof BundleSyncKinds];
|
|
9
|
+
export declare const BundleSyncStates: Readonly<{
|
|
10
|
+
readonly Pending: "pending";
|
|
11
|
+
readonly Submitting: "submitting";
|
|
12
|
+
readonly Retryable: "retryable";
|
|
13
|
+
readonly Reconciling: "reconciling";
|
|
14
|
+
readonly Confirmed: "confirmed";
|
|
15
|
+
readonly Rejected: "rejected";
|
|
16
|
+
}>;
|
|
17
|
+
export type BundleSyncState = typeof BundleSyncStates[keyof typeof BundleSyncStates];
|
|
18
|
+
export type BundleSyncPayload = BundleJsonApi<BundleEntry>;
|
|
19
|
+
export type BundleSyncDraft = Readonly<{
|
|
20
|
+
id: string;
|
|
21
|
+
kind: BundleSyncKind;
|
|
22
|
+
subjectReference: string;
|
|
23
|
+
state: BundleSyncState;
|
|
24
|
+
changesBundle: BundleSyncPayload;
|
|
25
|
+
lastConfirmedBundle: BundleSyncPayload;
|
|
26
|
+
optimisticBundle?: BundleSyncPayload;
|
|
27
|
+
visibleBundle: BundleSyncPayload;
|
|
28
|
+
pendingIdentifiers: readonly string[];
|
|
29
|
+
diagnostics: readonly string[];
|
|
30
|
+
createdAt: string;
|
|
31
|
+
updatedAt: string;
|
|
32
|
+
}>;
|
|
33
|
+
export type CreateBundleSyncDraftInput = Readonly<{
|
|
34
|
+
id: string;
|
|
35
|
+
kind: BundleSyncKind;
|
|
36
|
+
subjectReference: string;
|
|
37
|
+
changesBundle: BundleSyncPayload;
|
|
38
|
+
lastConfirmedBundle: BundleSyncPayload;
|
|
39
|
+
optimisticBundle: BundleSyncPayload;
|
|
40
|
+
createdAt: string;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* Creates the portable, serializable state shown while the BFF owns delivery.
|
|
44
|
+
* The optimistic Bundle remains visibly marked by `state`; it is never proof
|
|
45
|
+
* of provider persistence.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createBundleSyncDraft(input: CreateBundleSyncDraftInput): BundleSyncDraft;
|
|
48
|
+
/** Marks a durable draft as actively submitted without changing its preview. */
|
|
49
|
+
export declare function submitBundleSyncDraft(draft: BundleSyncDraft, updatedAt: string): BundleSyncDraft;
|
|
50
|
+
/** Retains the exact optimistic draft after a transient transport failure. */
|
|
51
|
+
export declare function markBundleSyncRetryable(draft: BundleSyncDraft, diagnostics: readonly string[], updatedAt: string): BundleSyncDraft;
|
|
52
|
+
/**
|
|
53
|
+
* Interprets the correlated remote Bundle response.
|
|
54
|
+
*
|
|
55
|
+
* Missing or ambiguous results keep the optimistic Bundle as a retryable
|
|
56
|
+
* draft. A definite rejection removes it. Successful submission moves to
|
|
57
|
+
* reconciling and still requires an authoritative provider readback.
|
|
58
|
+
*/
|
|
59
|
+
export declare function reconcileBundleSyncSubmission(draft: BundleSyncDraft, responseBundle: BundleSyncPayload, updatedAt: string): BundleSyncDraft;
|
|
60
|
+
/** Replaces the preview only after exact provider readback succeeds. */
|
|
61
|
+
export declare function confirmBundleSyncDraft(draft: BundleSyncDraft, authoritativeBundle: BundleSyncPayload, updatedAt: string): BundleSyncDraft;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Copyright 2026 Connecting Solution & Applications Ltd under the Apache License, Version 2.0.
|
|
2
|
+
import { analyzeBundleChangesResponse } from './bundle-changes-response.js';
|
|
3
|
+
export const BundleSyncKinds = Object.freeze({
|
|
4
|
+
ClinicalDocument: 'clinical-document',
|
|
5
|
+
EncounterTransfer: 'encounter-transfer',
|
|
6
|
+
Consent: 'consent',
|
|
7
|
+
EmergencyContact: 'emergency-contact',
|
|
8
|
+
});
|
|
9
|
+
export const BundleSyncStates = Object.freeze({
|
|
10
|
+
Pending: 'pending',
|
|
11
|
+
Submitting: 'submitting',
|
|
12
|
+
Retryable: 'retryable',
|
|
13
|
+
Reconciling: 'reconciling',
|
|
14
|
+
Confirmed: 'confirmed',
|
|
15
|
+
Rejected: 'rejected',
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Creates the portable, serializable state shown while the BFF owns delivery.
|
|
19
|
+
* The optimistic Bundle remains visibly marked by `state`; it is never proof
|
|
20
|
+
* of provider persistence.
|
|
21
|
+
*/
|
|
22
|
+
export function createBundleSyncDraft(input) {
|
|
23
|
+
requireText(input.id, 'bundle_sync_id_required');
|
|
24
|
+
requireText(input.subjectReference, 'bundle_sync_subject_required');
|
|
25
|
+
requireText(input.createdAt, 'bundle_sync_created_at_required');
|
|
26
|
+
if (!Object.values(BundleSyncKinds).includes(input.kind))
|
|
27
|
+
throw new TypeError('bundle_sync_kind_invalid');
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
...input,
|
|
30
|
+
state: BundleSyncStates.Pending,
|
|
31
|
+
visibleBundle: input.optimisticBundle,
|
|
32
|
+
pendingIdentifiers: Object.freeze([]),
|
|
33
|
+
diagnostics: Object.freeze([]),
|
|
34
|
+
updatedAt: input.createdAt,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Marks a durable draft as actively submitted without changing its preview. */
|
|
38
|
+
export function submitBundleSyncDraft(draft, updatedAt) {
|
|
39
|
+
if (![BundleSyncStates.Pending, BundleSyncStates.Retryable].includes(draft.state)) {
|
|
40
|
+
throw new TypeError('bundle_sync_not_submittable');
|
|
41
|
+
}
|
|
42
|
+
return Object.freeze({ ...draft, state: BundleSyncStates.Submitting, updatedAt });
|
|
43
|
+
}
|
|
44
|
+
/** Retains the exact optimistic draft after a transient transport failure. */
|
|
45
|
+
export function markBundleSyncRetryable(draft, diagnostics, updatedAt) {
|
|
46
|
+
if ([BundleSyncStates.Confirmed, BundleSyncStates.Rejected].includes(draft.state)) {
|
|
47
|
+
throw new TypeError('bundle_sync_retry_not_allowed');
|
|
48
|
+
}
|
|
49
|
+
return Object.freeze({
|
|
50
|
+
...draft,
|
|
51
|
+
state: BundleSyncStates.Retryable,
|
|
52
|
+
diagnostics: Object.freeze([...diagnostics]),
|
|
53
|
+
updatedAt,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Interprets the correlated remote Bundle response.
|
|
58
|
+
*
|
|
59
|
+
* Missing or ambiguous results keep the optimistic Bundle as a retryable
|
|
60
|
+
* draft. A definite rejection removes it. Successful submission moves to
|
|
61
|
+
* reconciling and still requires an authoritative provider readback.
|
|
62
|
+
*/
|
|
63
|
+
export function reconcileBundleSyncSubmission(draft, responseBundle, updatedAt) {
|
|
64
|
+
if (![BundleSyncStates.Pending, BundleSyncStates.Submitting, BundleSyncStates.Retryable].includes(draft.state)) {
|
|
65
|
+
throw new TypeError('bundle_sync_response_not_expected');
|
|
66
|
+
}
|
|
67
|
+
const analysis = analyzeBundleChangesResponse(draft.changesBundle, responseBundle);
|
|
68
|
+
if (analysis.failedIdentifiers.length) {
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
...draft,
|
|
71
|
+
state: BundleSyncStates.Rejected,
|
|
72
|
+
optimisticBundle: undefined,
|
|
73
|
+
visibleBundle: draft.lastConfirmedBundle,
|
|
74
|
+
pendingIdentifiers: Object.freeze([]),
|
|
75
|
+
diagnostics: Object.freeze([...analysis.diagnostics, ...analysis.entries.flatMap(({ diagnostics }) => diagnostics)]),
|
|
76
|
+
updatedAt,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (analysis.pendingIdentifiers.length) {
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
...draft,
|
|
82
|
+
state: BundleSyncStates.Retryable,
|
|
83
|
+
pendingIdentifiers: Object.freeze([...analysis.pendingIdentifiers]),
|
|
84
|
+
diagnostics: Object.freeze([...analysis.diagnostics]),
|
|
85
|
+
updatedAt,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
...draft,
|
|
90
|
+
state: BundleSyncStates.Reconciling,
|
|
91
|
+
pendingIdentifiers: Object.freeze([]),
|
|
92
|
+
diagnostics: Object.freeze([...analysis.diagnostics]),
|
|
93
|
+
updatedAt,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Replaces the preview only after exact provider readback succeeds. */
|
|
97
|
+
export function confirmBundleSyncDraft(draft, authoritativeBundle, updatedAt) {
|
|
98
|
+
if (draft.state !== BundleSyncStates.Reconciling)
|
|
99
|
+
throw new TypeError('bundle_sync_readback_not_expected');
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
...draft,
|
|
102
|
+
state: BundleSyncStates.Confirmed,
|
|
103
|
+
lastConfirmedBundle: authoritativeBundle,
|
|
104
|
+
optimisticBundle: undefined,
|
|
105
|
+
visibleBundle: authoritativeBundle,
|
|
106
|
+
pendingIdentifiers: Object.freeze([]),
|
|
107
|
+
diagnostics: Object.freeze([]),
|
|
108
|
+
updatedAt,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function requireText(value, issue) {
|
|
112
|
+
if (!value.trim())
|
|
113
|
+
throw new TypeError(issue);
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
@@ -153,7 +153,16 @@ export class CommunicationMessageEditor extends CommunicationAttachedBundleSessi
|
|
|
153
153
|
claims,
|
|
154
154
|
fullUrl: `urn:uuid:${identifier}`,
|
|
155
155
|
...(request ? { request } : {}),
|
|
156
|
-
})
|
|
156
|
+
});
|
|
157
|
+
const activePosition = this.getActiveEntryPosition();
|
|
158
|
+
if (activePosition === null)
|
|
159
|
+
throw new TypeError('communication_bundle_active_entry_required');
|
|
160
|
+
const bundle = this.getAttachedBundle();
|
|
161
|
+
const activeResource = bundle.data[activePosition]?.resource;
|
|
162
|
+
if (!activeResource)
|
|
163
|
+
throw new TypeError('communication_bundle_active_resource_required');
|
|
164
|
+
activeResource.id = identifier;
|
|
165
|
+
this.setAttachedBundle(bundle).saveAndReleaseActiveEntry();
|
|
157
166
|
return this;
|
|
158
167
|
}
|
|
159
168
|
/** Adds a PDF, image, audio or calendar file as an inner DocumentReference. */
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type ClinicalCoding, type FhirResource } from 'fhir-data-utils-ts';
|
|
2
|
+
import { type CareJourneyMovementKindValue, type EncounterCommunicationAudienceReconciliation } from 'sos-data-utils-ts/encounter-communication-audience';
|
|
3
|
+
import type { ProfessionalServiceGroupAssignment } from 'sos-data-utils-ts/organization-service-catalog';
|
|
4
|
+
export { CareJourneyMovementKind } from 'sos-data-utils-ts/encounter-communication-audience';
|
|
5
|
+
export type { CareJourneyMovementKindValue, EncounterCommunicationAudienceReconciliation, } from 'sos-data-utils-ts/encounter-communication-audience';
|
|
2
6
|
export type EncounterTransferSourceDisposition = 'returning' | 'not-returning';
|
|
3
7
|
export type EncounterTransferProjectionInput = Readonly<{
|
|
4
8
|
communicationIdentifier: string;
|
|
@@ -21,6 +25,18 @@ export type EncounterTransferProjectionInput = Readonly<{
|
|
|
21
25
|
destinationHistoryIdentifier: string;
|
|
22
26
|
encounterClass: ClinicalCoding;
|
|
23
27
|
}>;
|
|
28
|
+
export type EncounterTransferOrchestrationInput = EncounterTransferProjectionInput & Readonly<{
|
|
29
|
+
communicationThreadId: string;
|
|
30
|
+
movementKind: CareJourneyMovementKindValue;
|
|
31
|
+
sourceAssignments: readonly ProfessionalServiceGroupAssignment[];
|
|
32
|
+
destinationAssignments: readonly ProfessionalServiceGroupAssignment[];
|
|
33
|
+
currentProfessionalGroupReferences: readonly string[];
|
|
34
|
+
handoverAccepted: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
export type EncounterTransferOrchestration = Readonly<{
|
|
37
|
+
projectionCommunication: Record<string, unknown>;
|
|
38
|
+
audienceReconciliation: EncounterCommunicationAudienceReconciliation;
|
|
39
|
+
}>;
|
|
24
40
|
/**
|
|
25
41
|
* Builds the completed Communication used to project an already-committed
|
|
26
42
|
* provider transfer to the subject index.
|
|
@@ -38,3 +54,15 @@ export type EncounterTransferProjectionInput = Readonly<{
|
|
|
38
54
|
* into the FHIR sender, author or attester fields.
|
|
39
55
|
*/
|
|
40
56
|
export declare function prepareEncounterTransferProjectionCommunication(input: EncounterTransferProjectionInput): Record<string, unknown>;
|
|
57
|
+
/**
|
|
58
|
+
* Prepare one runtime-neutral transfer plan after the destination accepts
|
|
59
|
+
* handover.
|
|
60
|
+
*
|
|
61
|
+
* The product BFF authorizes the actor and resolves current service Group
|
|
62
|
+
* assignments before this call. Its persistence adapter then commits the
|
|
63
|
+
* Encounter/EncounterHistory transaction, the new future channel audience and
|
|
64
|
+
* the family-safe journey event under the same idempotency key. Historical
|
|
65
|
+
* Communication recipients remain immutable. This SDK neither stores state
|
|
66
|
+
* nor interprets Group membership as access authority.
|
|
67
|
+
*/
|
|
68
|
+
export declare function prepareEncounterTransferOrchestration(input: EncounterTransferOrchestrationInput): EncounterTransferOrchestration;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EncounterClaim, EncounterHistoryClaim, EncounterStatusR5, buildEncounterHistoryR5FlatEntry, } from 'fhir-data-utils-ts';
|
|
2
|
+
import { CareJourneyMovementKind, reconcileEncounterCommunicationAudience, } from 'sos-data-utils-ts/encounter-communication-audience';
|
|
2
3
|
import { CommunicationBundleEditor } from './communication-bundle.js';
|
|
4
|
+
export { CareJourneyMovementKind } from 'sos-data-utils-ts/encounter-communication-audience';
|
|
3
5
|
/**
|
|
4
6
|
* Builds the completed Communication used to project an already-committed
|
|
5
7
|
* provider transfer to the subject index.
|
|
@@ -53,13 +55,60 @@ export function prepareEncounterTransferProjectionCommunication(input) {
|
|
|
53
55
|
.setSent(input.occurredAt)
|
|
54
56
|
.setAttachmentTitle('encounter-transfer-bundle.json')
|
|
55
57
|
.setInnerBundleType('batch')
|
|
56
|
-
.addResource(sourceUpdated, { method: '
|
|
58
|
+
.addResource(sourceUpdated, { method: 'POST', url: 'Encounter' })
|
|
57
59
|
.addResource(sourceHistory, { method: 'POST', url: 'EncounterHistory' })
|
|
58
|
-
.addResource(destinationUpdated, { method: '
|
|
60
|
+
.addResource(destinationUpdated, { method: 'POST', url: 'Encounter' })
|
|
59
61
|
.addResource(destinationHistory, { method: 'POST', url: 'EncounterHistory' })
|
|
60
62
|
.doneCommunication();
|
|
61
63
|
return editor.build();
|
|
62
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Prepare one runtime-neutral transfer plan after the destination accepts
|
|
67
|
+
* handover.
|
|
68
|
+
*
|
|
69
|
+
* The product BFF authorizes the actor and resolves current service Group
|
|
70
|
+
* assignments before this call. Its persistence adapter then commits the
|
|
71
|
+
* Encounter/EncounterHistory transaction, the new future channel audience and
|
|
72
|
+
* the family-safe journey event under the same idempotency key. Historical
|
|
73
|
+
* Communication recipients remain immutable. This SDK neither stores state
|
|
74
|
+
* nor interprets Group membership as access authority.
|
|
75
|
+
*/
|
|
76
|
+
export function prepareEncounterTransferOrchestration(input) {
|
|
77
|
+
assertMovementMatchesDisposition(input.movementKind, input.sourceDisposition);
|
|
78
|
+
const source = normalizeEncounter(input.sourceEncounter, input.subjectReference);
|
|
79
|
+
const destination = normalizeEncounter(input.destinationEncounter, input.subjectReference);
|
|
80
|
+
const sourceStatus = input.sourceDisposition === 'returning'
|
|
81
|
+
? EncounterStatusR5.OnHold
|
|
82
|
+
: EncounterStatusR5.Completed;
|
|
83
|
+
const projectionCommunication = prepareEncounterTransferProjectionCommunication(input);
|
|
84
|
+
const audienceReconciliation = reconcileEncounterCommunicationAudience({
|
|
85
|
+
idempotencyKey: input.communicationIdentifier,
|
|
86
|
+
communicationThreadId: input.communicationThreadId,
|
|
87
|
+
subjectReference: input.subjectReference,
|
|
88
|
+
movementKind: input.movementKind,
|
|
89
|
+
sourceEncounterReference: `Encounter/${source.id}`,
|
|
90
|
+
sourceEncounterStatus: sourceStatus,
|
|
91
|
+
sourceHealthcareServiceReference: text(claimsOf(source)[EncounterClaim.ServiceType]),
|
|
92
|
+
sourceAssignments: input.sourceAssignments,
|
|
93
|
+
destinationEncounterReference: `Encounter/${destination.id}`,
|
|
94
|
+
destinationEncounterStatus: EncounterStatusR5.InProgress,
|
|
95
|
+
destinationHealthcareServiceReference: text(claimsOf(destination)[EncounterClaim.ServiceType]),
|
|
96
|
+
destinationAssignments: input.destinationAssignments,
|
|
97
|
+
currentProfessionalGroupReferences: input.currentProfessionalGroupReferences,
|
|
98
|
+
occurredAt: input.occurredAt,
|
|
99
|
+
handoverAccepted: input.handoverAccepted,
|
|
100
|
+
});
|
|
101
|
+
return Object.freeze({ projectionCommunication, audienceReconciliation });
|
|
102
|
+
}
|
|
103
|
+
function assertMovementMatchesDisposition(movementKind, sourceDisposition) {
|
|
104
|
+
const matches = movementKind === CareJourneyMovementKind.TemporaryTransfer
|
|
105
|
+
? sourceDisposition === 'returning'
|
|
106
|
+
: (movementKind === CareJourneyMovementKind.PermanentTransfer
|
|
107
|
+
|| movementKind === CareJourneyMovementKind.ReturnTransfer)
|
|
108
|
+
&& sourceDisposition === 'not-returning';
|
|
109
|
+
if (!matches)
|
|
110
|
+
throw new TypeError('encounter_transfer_movement_disposition_mismatch');
|
|
111
|
+
}
|
|
63
112
|
function normalizeEncounter(resource, subjectReference) {
|
|
64
113
|
if (resource.resourceType !== 'Encounter')
|
|
65
114
|
throw new TypeError('fhir_encounter_required');
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -14,14 +14,18 @@ human policy stays in Vet and UHC.
|
|
|
14
14
|
3. Admission, diagnostics, intensive care, ward and discharge are represented
|
|
15
15
|
by Encounter/location continuity. Controllers see only what current Consent
|
|
16
16
|
and actor/subject authorization permit.
|
|
17
|
-
4.
|
|
17
|
+
4. Accepted service handover reconciles the future professional audience from
|
|
18
|
+
exact HealthcareService Group assignments. Temporary movement retains the
|
|
19
|
+
source Groups; definitive movement removes them. Historical Communication
|
|
20
|
+
recipients are immutable.
|
|
21
|
+
5. An authorized practitioner may start voice/video contact or send an
|
|
18
22
|
encrypted message to the controller or permitted related recipients before
|
|
19
23
|
publishing stressful results.
|
|
20
|
-
|
|
24
|
+
6. Voice/video signaling carries no clinical Bundle. A preliminary clinical
|
|
21
25
|
Bundle may be delivered by encrypted message only with `updateIndex=false`.
|
|
22
26
|
A call or preliminary message must not publish, attest or update the subject
|
|
23
27
|
index. Final clinical publication is a separate workflow.
|
|
24
|
-
|
|
28
|
+
7. A missed call may create an encrypted text/audio message in the same thread;
|
|
25
29
|
its recipients and thread authorization are revalidated by the service.
|
|
26
30
|
|
|
27
31
|
The orchestrator returns a plan. The persistence owner must enforce the
|
|
@@ -18,6 +18,43 @@ After the provider has committed those four records, it can call
|
|
|
18
18
|
completed `Communication` whose inner batch Bundle projects the same four
|
|
19
19
|
changes to the subject-index provider. The Communication is continuity and
|
|
20
20
|
discovery evidence; it is not a substitute for the provider transaction.
|
|
21
|
+
Both Encounter snapshots use idempotent `POST Encounter` commands because the
|
|
22
|
+
provider may already hold an Encounter that the subject index has never seen.
|
|
23
|
+
Their stable resource identifiers let the receiver return create-or-existing
|
|
24
|
+
readback without requiring a prior index record. Each milestone uses
|
|
25
|
+
`POST EncounterHistory` and retains its own stable identifier.
|
|
26
|
+
|
|
27
|
+
For a channel that follows the patient's journey, call
|
|
28
|
+
`prepareEncounterTransferOrchestration(...)` after the destination accepts the
|
|
29
|
+
handover. It returns the same projection Communication together with an
|
|
30
|
+
idempotent professional-Group audience reconciliation and a family-safe journey
|
|
31
|
+
event:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const transferPlan = prepareEncounterTransferOrchestration({
|
|
35
|
+
...encounterProjectionInput,
|
|
36
|
+
communicationThreadId: emergencyThreadId,
|
|
37
|
+
movementKind: patientWillReturn
|
|
38
|
+
? CareJourneyMovementKind.TemporaryTransfer
|
|
39
|
+
: CareJourneyMovementKind.PermanentTransfer,
|
|
40
|
+
sourceAssignments: authorizedSourceServiceGroups,
|
|
41
|
+
destinationAssignments: authorizedDestinationServiceGroups,
|
|
42
|
+
currentProfessionalGroupReferences,
|
|
43
|
+
handoverAccepted: true,
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The runtime persists the provider Encounter transaction, future channel
|
|
48
|
+
audience and journey event under the same idempotency key. A permanent transfer
|
|
49
|
+
removes the departed service Groups. A temporary transfer retains them while
|
|
50
|
+
adding the temporary destination Groups. Returning is another accepted
|
|
51
|
+
movement with the temporary Encounter as source and the resumed Encounter as
|
|
52
|
+
destination.
|
|
53
|
+
|
|
54
|
+
Only future messages use the reconciled audience. Previously sent
|
|
55
|
+
Communications keep their original recipient snapshots. Family recipients
|
|
56
|
+
receive the separately authorized journey projection, never professional Group
|
|
57
|
+
membership or internal clinical messages.
|
|
21
58
|
|
|
22
59
|
The department Organization is `Communication.sender`. Each Encounter keeps an
|
|
23
60
|
explicit `HealthcareService`. The authenticated professional/device remains
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sos-sdk-core-ts",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Runtime-neutral shared orchestration for SOS, Vet and UHC products",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"./encounter-transfer-communication": {
|
|
48
48
|
"types": "./dist/encounter-transfer-communication.d.ts",
|
|
49
49
|
"default": "./dist/encounter-transfer-communication.js"
|
|
50
|
+
},
|
|
51
|
+
"./bundle-sync-draft": {
|
|
52
|
+
"types": "./dist/bundle-sync-draft.d.ts",
|
|
53
|
+
"default": "./dist/bundle-sync-draft.js"
|
|
50
54
|
}
|
|
51
55
|
},
|
|
52
56
|
"files": [
|
|
@@ -69,7 +73,7 @@
|
|
|
69
73
|
"@noble/post-quantum": "0.7.1",
|
|
70
74
|
"fhir-data-utils-ts": "0.6.1",
|
|
71
75
|
"gdc-common-utils-ts": "2.9.21",
|
|
72
|
-
"sos-data-utils-ts": "0.6.
|
|
76
|
+
"sos-data-utils-ts": "0.6.5"
|
|
73
77
|
},
|
|
74
78
|
"devDependencies": {
|
|
75
79
|
"@types/node": "^22.5.0",
|