gdc-common-utils-ts 2.8.6 → 2.8.8

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/README.md CHANGED
@@ -417,6 +417,9 @@ The canonical API contract should live in JSDoc on exported code. The README act
417
417
  - Use instead of inline resource type strings.
418
418
  - [`CommunicationCategoryCodes`](src/constants/communication.ts)
419
419
  - Canonical `Communication.category` coding descriptors and `<system>|<code>` claims.
420
+ - Exactly the four FHIR R4 categories; use `CommunicationTopicCodes.BreakTheGlass`
421
+ for the HL7 v3 ActReason `BTG` topic and LOINC catalogs for clinical
422
+ section/document topics. See [Communication Layering 101](docs/101-COMMUNICATION_LAYERING.md).
420
423
  - [`ObservationCategoryCodes`, `VitalSignsCodes`, `VitalSignsUnits`](src/constants/vital-signs.ts)
421
424
  - Canonical Vital Signs category, code, and UCUM unit descriptors for `Observation`.
422
425
  - [`HealthcareSummarySections`, `HealthcareCoreSections`, `HealthcareAllSections`](src/constants/healthcare.ts)
@@ -522,6 +525,12 @@ The canonical API contract should live in JSDoc on exported code. The README act
522
525
  - The base token examples are read-only; richer scenario fixtures intentionally add `organization/Consent.cruds`.
523
526
  - [`src/examples/related-person.ts`](src/examples/related-person.ts)
524
527
  - RelatedPerson/family-member examples.
528
+ - [`buildFhirIpsCreatorProvenance(...)`](src/utils/fhir-ips-creator-identity.ts)
529
+ and [`docs/101-DIDCOMM-IDENTITY-BOUNDARY.md`](docs/101-DIDCOMM-IDENTITY-BOUNDARY.md)
530
+ - Keep `Composition.author`, `Composition.attester`, DIDComm sender and
531
+ signing key separate. A member-created fact may use the registered
532
+ RelatedPerson as both author and personal attester; a dictated
533
+ individual-created fact keeps the individual as author.
525
534
  - [`src/examples/frontend-session.ts`](src/examples/frontend-session.ts)
526
535
  - Frontend profile/session bootstrap examples.
527
536
  - [`src/examples/lifecycle.ts`](src/examples/lifecycle.ts)
@@ -10,6 +10,11 @@ export type CommunicationCategoryDescriptor = Readonly<{
10
10
  */
11
11
  claim: string;
12
12
  }>;
13
+ /**
14
+ * The complete FHIR R4 Communication category value set.
15
+ *
16
+ * @see https://www.hl7.org/fhir/valueset-communication-category.html
17
+ */
13
18
  export declare const CommunicationCategoryCodes: Readonly<{
14
19
  readonly Alert: Readonly<{
15
20
  system: typeof HL7_COMMUNICATION_CATEGORY_SYSTEM;
@@ -56,3 +61,23 @@ export declare const CommunicationCategoryCodes: Readonly<{
56
61
  claim: string;
57
62
  }>;
58
63
  }>;
64
+ export type CommunicationTopicDescriptor = Readonly<{
65
+ system: string;
66
+ code: string;
67
+ attributeValue: string;
68
+ }>;
69
+ /**
70
+ * Non-LOINC Communication topics explicitly governed by an HL7 code system.
71
+ * Clinical section and document topics continue to use the LOINC catalogs.
72
+ *
73
+ * `BTG` is the HL7 v3 ActReason override purpose used for break-glass alerts.
74
+ * @see http://terminology.hl7.org/CodeSystem/v3-ActReason#v3-ActReason-BTG
75
+ * @see https://profiles.ihe.net/ITI/PCF/Consent-ex-consent-advanced-normal-break-glass-restricted.json.html
76
+ */
77
+ export declare const CommunicationTopicCodes: Readonly<{
78
+ readonly BreakTheGlass: Readonly<{
79
+ system: string;
80
+ code: string;
81
+ attributeValue: string;
82
+ }>;
83
+ }>;
@@ -1,4 +1,5 @@
1
- export const HL7_COMMUNICATION_CATEGORY_SYSTEM = 'http://terminology.hl7.org/CodeSystem/communication-category';
1
+ import { FhirCodeSystems } from './fhir-code-systems.js';
2
+ export const HL7_COMMUNICATION_CATEGORY_SYSTEM = FhirCodeSystems.CommunicationCategory;
2
3
  function defineCommunicationCategory(code) {
3
4
  const attributeValue = `${HL7_COMMUNICATION_CATEGORY_SYSTEM}|${code}`;
4
5
  return Object.freeze({
@@ -8,9 +9,28 @@ function defineCommunicationCategory(code) {
8
9
  claim: attributeValue,
9
10
  });
10
11
  }
12
+ /**
13
+ * The complete FHIR R4 Communication category value set.
14
+ *
15
+ * @see https://www.hl7.org/fhir/valueset-communication-category.html
16
+ */
11
17
  export const CommunicationCategoryCodes = Object.freeze({
12
18
  Alert: defineCommunicationCategory('alert'),
13
19
  Notification: defineCommunicationCategory('notification'),
14
20
  Reminder: defineCommunicationCategory('reminder'),
15
21
  Instruction: defineCommunicationCategory('instruction'),
16
22
  });
23
+ function defineCommunicationTopic(system, code) {
24
+ return Object.freeze({ system, code, attributeValue: `${system}|${code}` });
25
+ }
26
+ /**
27
+ * Non-LOINC Communication topics explicitly governed by an HL7 code system.
28
+ * Clinical section and document topics continue to use the LOINC catalogs.
29
+ *
30
+ * `BTG` is the HL7 v3 ActReason override purpose used for break-glass alerts.
31
+ * @see http://terminology.hl7.org/CodeSystem/v3-ActReason#v3-ActReason-BTG
32
+ * @see https://profiles.ihe.net/ITI/PCF/Consent-ex-consent-advanced-normal-break-glass-restricted.json.html
33
+ */
34
+ export const CommunicationTopicCodes = Object.freeze({
35
+ BreakTheGlass: defineCommunicationTopic(FhirCodeSystems.ActReason, 'BTG'),
36
+ });
@@ -9,6 +9,7 @@ export declare const FhirCodeSystems: Readonly<{
9
9
  readonly SnomedCt: "http://snomed.info/sct";
10
10
  readonly Ucum: "http://unitsofmeasure.org";
11
11
  readonly CommunicationCategory: "http://terminology.hl7.org/CodeSystem/communication-category";
12
+ readonly ActReason: "http://terminology.hl7.org/CodeSystem/v3-ActReason";
12
13
  readonly ObservationCategory: "http://terminology.hl7.org/CodeSystem/observation-category";
13
14
  readonly EventTiming: "http://hl7.org/fhir/event-timing";
14
15
  }>;
@@ -9,6 +9,7 @@ export const FhirCodeSystems = Object.freeze({
9
9
  SnomedCt: 'http://snomed.info/sct',
10
10
  Ucum: 'http://unitsofmeasure.org',
11
11
  CommunicationCategory: 'http://terminology.hl7.org/CodeSystem/communication-category',
12
+ ActReason: 'http://terminology.hl7.org/CodeSystem/v3-ActReason',
12
13
  ObservationCategory: 'http://terminology.hl7.org/CodeSystem/observation-category',
13
14
  EventTiming: 'http://hl7.org/fhir/event-timing',
14
15
  });
@@ -237,6 +237,9 @@ export declare const EXAMPLE_EMPLOYEE_LIFECYCLE_RECORD: Readonly<{
237
237
  clientId: "client-employee-primary" | "client-employee-secondary";
238
238
  name: "installation-employee-primary" | "installation-employee-secondary" | "Shared browser profile";
239
239
  status: "active" | "revoked";
240
+ ownerContact: string;
241
+ ownerContactKind: "email";
242
+ activatedAt: 1786570800 | 1786570900;
240
243
  }>[];
241
244
  }>;
242
245
  }>;
@@ -171,6 +171,9 @@ export const EXAMPLE_EMPLOYEE_LIFECYCLE_RECORD = Object.freeze({
171
171
  || binding.deviceInfo.clientInstanceId
172
172
  || binding.clientId,
173
173
  status: binding.status,
174
+ ownerContact: EXAMPLE_EMPLOYEE_CONTROLLER_ACTIVE.email,
175
+ ownerContactKind: 'email',
176
+ activatedAt: binding.activatedAt,
174
177
  }))),
175
178
  }),
176
179
  });
@@ -5,6 +5,12 @@ export type OrganizationEmployeeDeviceRecord = Readonly<{
5
5
  clientId: string;
6
6
  name: string;
7
7
  status: DeviceBindingStatus;
8
+ /** Verified licensed contact that owns this installation. */
9
+ ownerContact?: string;
10
+ /** Contact channel represented by `ownerContact`. */
11
+ ownerContactKind?: 'email' | 'phone';
12
+ /** DCR activation time as Unix epoch seconds. */
13
+ activatedAt?: number;
8
14
  }>;
9
15
  /** License state attached to one employee directory entry. */
10
16
  export type OrganizationEmployeeLicenseRecord = Readonly<{
@@ -32,7 +32,12 @@ export type FhirIpsCreatorAuthor = Readonly<{
32
32
  entries: readonly FhirIpsCreatorBundleEntry[];
33
33
  }>;
34
34
  export type FhirIpsCreatorProvenanceInput = FhirIpsCreatorAuthorInput & Readonly<{
35
- /** Explicit source originator; defaults to organization, subject, or oneself by creator kind. */
35
+ /**
36
+ * Explicit source originator. Defaults to the owning organization or
37
+ * individual. When the authenticated member/professional actually authored
38
+ * the content, pass its registered RelatedPerson/PractitionerRole assignment
39
+ * reference; that same assignment may also remain the attester.
40
+ */
36
41
  compositionAuthorReference?: string;
37
42
  /** Defaults to professional for employees and personal for individual members. */
38
43
  attesterMode?: CompositionAttesterMode;
@@ -53,10 +58,13 @@ export type FhirIpsCreatorProvenance = Readonly<{
53
58
  /**
54
59
  * Builds source authorship separately from the person or role that attests it.
55
60
  *
56
- * Defaults model the ordinary delegated flows: an organization's professional
57
- * attests an organization-authored record, while a member/caregiver attests a
58
- * subject-authored record. `compositionAuthorReference` remains explicit for
59
- * imports and cases where the actual originator differs from those defaults.
61
+ * Defaults model delegated flows: an organization's professional attests an
62
+ * organization-authored record, while a member/caregiver attests a
63
+ * subject-authored record. For content actually created by that authenticated
64
+ * member or professional, set `compositionAuthorReference` to its registered
65
+ * RelatedPerson or PractitionerRole assignment. Author and attester may then
66
+ * intentionally reference the same assignment; neither is the transport
67
+ * sender or signing key.
60
68
  */
61
69
  export declare function buildFhirIpsCreatorProvenance(input: FhirIpsCreatorProvenanceInput): FhirIpsCreatorProvenance;
62
70
  /**
@@ -11,10 +11,13 @@ export const FhirIpsCreatorKinds = Object.freeze({
11
11
  /**
12
12
  * Builds source authorship separately from the person or role that attests it.
13
13
  *
14
- * Defaults model the ordinary delegated flows: an organization's professional
15
- * attests an organization-authored record, while a member/caregiver attests a
16
- * subject-authored record. `compositionAuthorReference` remains explicit for
17
- * imports and cases where the actual originator differs from those defaults.
14
+ * Defaults model delegated flows: an organization's professional attests an
15
+ * organization-authored record, while a member/caregiver attests a
16
+ * subject-authored record. For content actually created by that authenticated
17
+ * member or professional, set `compositionAuthorReference` to its registered
18
+ * RelatedPerson or PractitionerRole assignment. Author and attester may then
19
+ * intentionally reference the same assignment; neither is the transport
20
+ * sender or signing key.
18
21
  */
19
22
  export function buildFhirIpsCreatorProvenance(input) {
20
23
  if (input.kind === FhirIpsCreatorKinds.IndividualSubject) {
@@ -74,21 +74,33 @@ export function projectOrganizationEmployeeLifecycle(input) {
74
74
  const claims = record(employee.claims) || record(record(employee.meta)?.claims) || {};
75
75
  const resourceId = text(employee.id);
76
76
  const employeeDid = text(claims[ClaimsPersonSchemaorg.identifier]);
77
+ const employeeEmail = text(claims[ClaimsPersonSchemaorg.email]);
78
+ const employeeRole = text(claims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]);
77
79
  const license = licenses.find((candidate) => {
78
80
  const meta = record(candidate.meta) || {};
79
81
  const subjectId = text(meta.subjectId);
80
- return subjectId === resourceId || subjectId === employeeDid;
82
+ if (subjectId === resourceId || subjectId === employeeDid)
83
+ return true;
84
+ const licenseClaims = record(meta.claims) || record(candidate.claims) || {};
85
+ return Boolean(employeeEmail && employeeRole
86
+ && text(licenseClaims[ClaimsPersonSchemaorg.email]).toLowerCase() === employeeEmail.toLowerCase()
87
+ && text(licenseClaims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]) === employeeRole);
81
88
  });
82
89
  const status = normalizeEmployeeDirectoryStatus(text(employee.status) || text(record(employee.meta)?.status));
83
90
  if (!license)
84
91
  return {
85
92
  resourceId,
86
- email: text(claims[ClaimsPersonSchemaorg.email]),
87
- roleCode: text(claims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]),
93
+ email: employeeEmail,
94
+ roleCode: employeeRole,
88
95
  employeeDid,
89
96
  status,
90
97
  };
91
98
  const meta = record(license.meta) || {};
99
+ const licenseClaims = record(meta.claims) || record(license.claims) || {};
100
+ const licensedEmail = text(licenseClaims[ClaimsPersonSchemaorg.email]) || employeeEmail;
101
+ const licensedPhone = text(licenseClaims[ClaimsPersonSchemaorg.telephone]);
102
+ const ownerContact = licensedEmail || licensedPhone;
103
+ const ownerContactKind = licensedEmail ? 'email' : licensedPhone ? 'phone' : undefined;
92
104
  const bindings = Array.isArray(meta.deviceBindings) ? meta.deviceBindings.map(record).filter(isRecord) : [];
93
105
  const devices = bindings.map((binding) => {
94
106
  const info = record(binding.deviceInfo) || {};
@@ -99,12 +111,15 @@ export function projectOrganizationEmployeeLifecycle(input) {
99
111
  status: binding.status === DeviceBindingStatuses.Revoked
100
112
  ? DeviceBindingStatuses.Revoked
101
113
  : DeviceBindingStatuses.Active,
114
+ ...(ownerContact ? { ownerContact } : {}),
115
+ ...(ownerContactKind ? { ownerContactKind } : {}),
116
+ ...(positiveNumber(binding.activatedAt) ? { activatedAt: positiveNumber(binding.activatedAt) } : {}),
102
117
  };
103
118
  }).filter((device) => Boolean(device.clientId));
104
119
  return {
105
120
  resourceId,
106
- email: text(claims[ClaimsPersonSchemaorg.email]),
107
- roleCode: text(claims[ClaimsPersonSchemaorg.hasOccupationalRoleValue]),
121
+ email: employeeEmail,
122
+ roleCode: employeeRole,
108
123
  employeeDid,
109
124
  status,
110
125
  license: {
@@ -156,3 +171,7 @@ function positiveInteger(value) {
156
171
  const parsed = Number(value);
157
172
  return Number.isInteger(parsed) && parsed > 0 ? parsed : undefined;
158
173
  }
174
+ function positiveNumber(value) {
175
+ const parsed = Number(value);
176
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
177
+ }
@@ -1,4 +1,4 @@
1
- export declare const HL7_ACT_REASON_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/v3-ActReason";
1
+ export declare const HL7_ACT_REASON_CODE_SYSTEM: "http://terminology.hl7.org/CodeSystem/v3-ActReason";
2
2
  export type ServiceActReasonCoding = Readonly<{
3
3
  system: string;
4
4
  code: string;
@@ -1,5 +1,6 @@
1
1
  // Copyright 2026 Antifraud Services Inc. under the Apache License, Version 2.0.
2
- export const HL7_ACT_REASON_CODE_SYSTEM = 'http://terminology.hl7.org/CodeSystem/v3-ActReason';
2
+ import { FhirCodeSystems } from '../constants/fhir-code-systems.js';
3
+ export const HL7_ACT_REASON_CODE_SYSTEM = FhirCodeSystems.ActReason;
3
4
  function normalizeText(value) {
4
5
  return typeof value === 'string' ? value.trim() : '';
5
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.8.6",
3
+ "version": "2.8.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },