gdc-common-utils-ts 2.8.2 → 2.8.4

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.
@@ -1727,6 +1727,7 @@ export declare const HealthcareActorRoles: Readonly<{
1727
1727
  readonly MidwiferyProfessional: "ISCO-08|2222";
1728
1728
  readonly Paramedic: "ISCO-08|2240";
1729
1729
  readonly Veterinarian: "ISCO-08|2250";
1730
+ readonly VeterinaryTechnicianOrAssistant: "ISCO-08|3240";
1730
1731
  /** Administrative health occupation; it does not imply clinical authority. */
1731
1732
  readonly MedicalSecretary: "ISCO-08|3344";
1732
1733
  }>;
@@ -1749,6 +1750,7 @@ export declare const HealthcareActorRoleCodes: Readonly<{
1749
1750
  readonly MidwiferyProfessional: "2222";
1750
1751
  readonly Paramedic: "2240";
1751
1752
  readonly Veterinarian: "2250";
1753
+ readonly VeterinaryTechnicianOrAssistant: "3240";
1752
1754
  readonly MedicalSecretary: "3344";
1753
1755
  }>;
1754
1756
  export declare const HealthcareRolesByFamily: Readonly<{
@@ -1786,11 +1788,11 @@ export declare const HealthcareProfessionalRoleCodesBySector: Readonly<{
1786
1788
  readonly "health-research": readonly ("RESPRSN" | "221" | "2211" | "2212" | "3344")[];
1787
1789
  readonly "health-tech": readonly "RESPRSN"[];
1788
1790
  readonly "health-insurance": readonly "RESPRSN"[];
1789
- readonly "animal-care": readonly ("RESPRSN" | "2250")[];
1790
- readonly "animal-research": readonly ("RESPRSN" | "2250")[];
1791
+ readonly "animal-care": readonly ("RESPRSN" | "2250" | "3240")[];
1792
+ readonly "animal-research": readonly ("RESPRSN" | "2250" | "3240")[];
1791
1793
  readonly "animal-insurance": readonly "RESPRSN"[];
1792
1794
  readonly "animal-tech": readonly "RESPRSN"[];
1793
- readonly "onehealth-research": readonly ("RESPRSN" | "221" | "2211" | "2212" | "2250" | "3344")[];
1795
+ readonly "onehealth-research": readonly ("RESPRSN" | "221" | "2211" | "2212" | "2250" | "3240" | "3344")[];
1794
1796
  readonly "onehealth-tech": readonly "RESPRSN"[];
1795
1797
  }>;
1796
1798
  export declare const HealthcareProfessionalRolesBySector: Readonly<{
@@ -359,6 +359,7 @@ export const HealthcareActorRoles = Object.freeze({
359
359
  MidwiferyProfessional: 'ISCO-08|2222',
360
360
  Paramedic: 'ISCO-08|2240',
361
361
  Veterinarian: 'ISCO-08|2250',
362
+ VeterinaryTechnicianOrAssistant: 'ISCO-08|3240',
362
363
  /** Administrative health occupation; it does not imply clinical authority. */
363
364
  MedicalSecretary: 'ISCO-08|3344',
364
365
  });
@@ -381,6 +382,7 @@ export const HealthcareActorRoleCodes = Object.freeze({
381
382
  MidwiferyProfessional: '2222',
382
383
  Paramedic: '2240',
383
384
  Veterinarian: '2250',
385
+ VeterinaryTechnicianOrAssistant: '3240',
384
386
  MedicalSecretary: '3344',
385
387
  });
386
388
  function buildProfessionalIscoRoles() {
@@ -462,6 +464,17 @@ function buildProfessionalIscoRoles() {
462
464
  titleEn: 'Veterinarian',
463
465
  }),
464
466
  ],
467
+ [
468
+ HealthcareActorRoleCodes.VeterinaryTechnicianOrAssistant,
469
+ Object.freeze({
470
+ family: HealthcareRoleFamilies.ProfessionalOccupationIsco08,
471
+ codingSystem: ISCO08_CODING_SYSTEM,
472
+ code: HealthcareActorRoleCodes.VeterinaryTechnicianOrAssistant,
473
+ claim: HealthcareActorRoles.VeterinaryTechnicianOrAssistant,
474
+ i18nKey: `${ISCO08_I18N_NAMESPACE}.${HealthcareActorRoleCodes.VeterinaryTechnicianOrAssistant}`,
475
+ titleEn: 'Veterinary technician or assistant',
476
+ }),
477
+ ],
465
478
  [
466
479
  HealthcareActorRoleCodes.MedicalSecretary,
467
480
  Object.freeze({
@@ -553,10 +566,12 @@ export const HealthcareProfessionalRoleCodesBySector = Object.freeze({
553
566
  [DataspaceSectors.AnimalCare]: Object.freeze([
554
567
  HealthcareActorRoleCodes.Controller,
555
568
  HealthcareActorRoleCodes.Veterinarian,
569
+ HealthcareActorRoleCodes.VeterinaryTechnicianOrAssistant,
556
570
  ]),
557
571
  [DataspaceSectors.AnimalResearch]: Object.freeze([
558
572
  HealthcareActorRoleCodes.Controller,
559
573
  HealthcareActorRoleCodes.Veterinarian,
574
+ HealthcareActorRoleCodes.VeterinaryTechnicianOrAssistant,
560
575
  ]),
561
576
  [DataspaceSectors.AnimalInsurance]: Object.freeze([HealthcareActorRoleCodes.Controller]),
562
577
  [DataspaceSectors.AnimalTech]: Object.freeze([HealthcareActorRoleCodes.Controller]),
@@ -566,6 +581,7 @@ export const HealthcareProfessionalRoleCodesBySector = Object.freeze({
566
581
  HealthcareActorRoleCodes.GeneralistMedicalPractitioner,
567
582
  HealthcareActorRoleCodes.SpecialistMedicalPractitioner,
568
583
  HealthcareActorRoleCodes.Veterinarian,
584
+ HealthcareActorRoleCodes.VeterinaryTechnicianOrAssistant,
569
585
  HealthcareActorRoleCodes.MedicalSecretary,
570
586
  ]),
571
587
  [DataspaceSectors.OneHealthTech]: Object.freeze([HealthcareActorRoleCodes.Controller]),
@@ -0,0 +1,34 @@
1
+ export declare const ClinicalResourceReplacementDecision: Readonly<{
2
+ readonly AllowOrganizationSuccessor: "allow-organization-successor";
3
+ readonly Deny: "deny";
4
+ }>;
5
+ export type ClinicalResourceReplacementDecisionValue = typeof ClinicalResourceReplacementDecision[keyof typeof ClinicalResourceReplacementDecision];
6
+ export type ClinicalResourceVersionIdentity = Readonly<{
7
+ resourceId: string;
8
+ authorOwnerIdentifier: string;
9
+ documentDate: string;
10
+ }>;
11
+ export type ClinicalDocumentAuthorOrganization = Readonly<{
12
+ authorReference: string;
13
+ organizationReference: string;
14
+ documentDate: string;
15
+ }>;
16
+ /**
17
+ * Resolves document ownership from the FHIR graph carried by an IPS Bundle.
18
+ * A Composition may name its Organization directly or name a PractitionerRole
19
+ * whose `organization` reference names it. Custodian is a compatibility
20
+ * fallback only when the author is a Practitioner represented without a
21
+ * PractitionerRole organization link.
22
+ */
23
+ export declare function resolveClinicalDocumentAuthorOrganization(bundle: unknown): ClinicalDocumentAuthorOrganization | undefined;
24
+ /**
25
+ * Evaluates the narrow provenance-preserving replacement exception.
26
+ *
27
+ * This does not authorize an actor or a delete. Gateways call it only after
28
+ * authenticating the sender and resolving both document authors to their
29
+ * registered owning organizations.
30
+ */
31
+ export declare function evaluateClinicalResourceReplacement(input: Readonly<{
32
+ existing: ClinicalResourceVersionIdentity;
33
+ incoming: ClinicalResourceVersionIdentity;
34
+ }>): ClinicalResourceReplacementDecisionValue;
@@ -0,0 +1,83 @@
1
+ export const ClinicalResourceReplacementDecision = Object.freeze({
2
+ AllowOrganizationSuccessor: 'allow-organization-successor',
3
+ Deny: 'deny',
4
+ });
5
+ function cleanReference(value) {
6
+ return typeof value === 'string' ? value.trim() : '';
7
+ }
8
+ function resourceReference(resource) {
9
+ const resourceType = cleanReference(resource?.resourceType);
10
+ const id = cleanReference(resource?.id);
11
+ return resourceType && id ? `${resourceType}/${id}` : '';
12
+ }
13
+ /**
14
+ * Resolves document ownership from the FHIR graph carried by an IPS Bundle.
15
+ * A Composition may name its Organization directly or name a PractitionerRole
16
+ * whose `organization` reference names it. Custodian is a compatibility
17
+ * fallback only when the author is a Practitioner represented without a
18
+ * PractitionerRole organization link.
19
+ */
20
+ export function resolveClinicalDocumentAuthorOrganization(bundle) {
21
+ const entries = Array.isArray(bundle?.entry)
22
+ ? bundle.entry
23
+ : [];
24
+ const composition = entries.find(({ resource }) => resource?.resourceType === 'Composition')?.resource;
25
+ const authorReference = cleanReference(composition?.author?.[0]?.reference);
26
+ const documentDate = cleanReference(composition?.date);
27
+ if (!composition || !authorReference || !documentDate || !Number.isFinite(Date.parse(documentDate))) {
28
+ return undefined;
29
+ }
30
+ const findResource = (reference) => entries.find((entry) => {
31
+ const fullUrl = cleanReference(entry.fullUrl);
32
+ const relativeReference = resourceReference(entry.resource);
33
+ return reference === fullUrl
34
+ || reference === relativeReference
35
+ || (fullUrl && relativeReference && reference === `${fullUrl.replace(/\/$/, '')}/${relativeReference}`);
36
+ })?.resource;
37
+ const author = findResource(authorReference);
38
+ let organizationReference = '';
39
+ if (author?.resourceType === 'Organization') {
40
+ organizationReference = authorReference;
41
+ }
42
+ else if (author?.resourceType === 'PractitionerRole') {
43
+ organizationReference = cleanReference(author.organization?.reference);
44
+ }
45
+ else if (author?.resourceType === 'Practitioner') {
46
+ const role = entries.find(({ resource }) => resource?.resourceType === 'PractitionerRole'
47
+ && cleanReference(resource.practitioner?.reference) === authorReference)?.resource;
48
+ organizationReference = cleanReference(role?.organization?.reference);
49
+ }
50
+ if (!organizationReference) {
51
+ organizationReference = cleanReference(composition.custodian?.reference);
52
+ }
53
+ const organization = findResource(organizationReference);
54
+ if (!organizationReference || organization?.resourceType !== 'Organization') {
55
+ return undefined;
56
+ }
57
+ return { authorReference, organizationReference, documentDate };
58
+ }
59
+ /**
60
+ * Evaluates the narrow provenance-preserving replacement exception.
61
+ *
62
+ * This does not authorize an actor or a delete. Gateways call it only after
63
+ * authenticating the sender and resolving both document authors to their
64
+ * registered owning organizations.
65
+ */
66
+ export function evaluateClinicalResourceReplacement(input) {
67
+ const existingResourceId = String(input.existing.resourceId || '').trim();
68
+ const incomingResourceId = String(input.incoming.resourceId || '').trim();
69
+ const existingOwner = String(input.existing.authorOwnerIdentifier || '').trim();
70
+ const incomingOwner = String(input.incoming.authorOwnerIdentifier || '').trim();
71
+ const existingDate = Date.parse(String(input.existing.documentDate || '').trim());
72
+ const incomingDate = Date.parse(String(input.incoming.documentDate || '').trim());
73
+ if (!existingResourceId
74
+ || incomingResourceId !== existingResourceId
75
+ || !existingOwner
76
+ || incomingOwner !== existingOwner
77
+ || !Number.isFinite(existingDate)
78
+ || !Number.isFinite(incomingDate)
79
+ || incomingDate <= existingDate) {
80
+ return ClinicalResourceReplacementDecision.Deny;
81
+ }
82
+ return ClinicalResourceReplacementDecision.AllowOrganizationSuccessor;
83
+ }
@@ -59,6 +59,7 @@ export * from './related-person-list';
59
59
  export * from './clinical-resource-converters';
60
60
  export * from './clinical-bundle-summary';
61
61
  export * from './clinical-resource-view';
62
+ export * from './clinical-resource-replacement';
62
63
  export * from './fhir-validator';
63
64
  export * from './family-registration-test-data';
64
65
  export * from './individual-form-pdf';
@@ -59,6 +59,7 @@ export * from './related-person-list.js';
59
59
  export * from './clinical-resource-converters.js';
60
60
  export * from './clinical-bundle-summary.js';
61
61
  export * from './clinical-resource-view.js';
62
+ export * from './clinical-resource-replacement.js';
62
63
  export * from './fhir-validator.js';
63
64
  export * from './family-registration-test-data.js';
64
65
  export * from './individual-form-pdf.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.8.2",
3
+ "version": "2.8.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },