sos-sdk-core-ts 0.5.1 → 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 CHANGED
@@ -2,6 +2,17 @@
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
+
5
16
  ## 0.5.1 - 2026-10-04
6
17
 
7
18
  - Add the shared Bundle synchronization draft contract for clinical documents,
@@ -153,7 +153,16 @@ export class CommunicationMessageEditor extends CommunicationAttachedBundleSessi
153
153
  claims,
154
154
  fullUrl: `urn:uuid:${identifier}`,
155
155
  ...(request ? { request } : {}),
156
- }).saveAndReleaseActiveEntry();
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: 'PUT', url: `Encounter/${sourceUpdated.id}` })
58
+ .addResource(sourceUpdated, { method: 'POST', url: 'Encounter' })
57
59
  .addResource(sourceHistory, { method: 'POST', url: 'EncounterHistory' })
58
- .addResource(destinationUpdated, { method: 'PUT', url: `Encounter/${destinationUpdated.id}` })
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');
@@ -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. An authorized practitioner may start voice/video contact or send an
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
- 5. Voice/video signaling carries no clinical Bundle. A preliminary clinical
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
- 6. A missed call may create an encrypted text/audio message in the same thread;
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.1",
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",
@@ -73,7 +73,7 @@
73
73
  "@noble/post-quantum": "0.7.1",
74
74
  "fhir-data-utils-ts": "0.6.1",
75
75
  "gdc-common-utils-ts": "2.9.21",
76
- "sos-data-utils-ts": "0.6.0"
76
+ "sos-data-utils-ts": "0.6.5"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@types/node": "^22.5.0",