gdc-common-utils-ts 2.8.7 → 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)
@@ -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<{
@@ -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.7",
3
+ "version": "2.8.8",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },