gdc-common-utils-ts 2.3.16 → 2.3.18

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.
Files changed (36) hide show
  1. package/dist/convert/convert-care-plan.js +12 -1
  2. package/dist/convert/convert-consent.d.ts +3 -0
  3. package/dist/convert/convert-consent.js +32 -0
  4. package/dist/convert/convert-device.js +12 -1
  5. package/dist/convert/convert-document-reference.js +20 -1
  6. package/dist/convert/convert-observation.js +15 -7
  7. package/dist/convert/convert-practitioner-role.d.ts +3 -0
  8. package/dist/convert/convert-practitioner-role.js +34 -0
  9. package/dist/convert/index.d.ts +2 -0
  10. package/dist/convert/index.js +2 -0
  11. package/dist/models/consent-rule.d.ts +2 -0
  12. package/dist/models/consent-rule.js +2 -0
  13. package/dist/models/index.d.ts +1 -0
  14. package/dist/models/index.js +1 -0
  15. package/dist/models/interoperable-claims/care-plan-claims.d.ts +2 -0
  16. package/dist/models/interoperable-claims/care-plan-claims.js +4 -0
  17. package/dist/models/interoperable-claims/device-claims.d.ts +2 -0
  18. package/dist/models/interoperable-claims/device-claims.js +4 -0
  19. package/dist/models/interoperable-claims/document-reference-claims.d.ts +2 -0
  20. package/dist/models/interoperable-claims/document-reference-claims.js +4 -0
  21. package/dist/models/interoperable-claims/index.d.ts +1 -0
  22. package/dist/models/interoperable-claims/index.js +1 -0
  23. package/dist/models/interoperable-claims/practitioner-role-claims.d.ts +9 -0
  24. package/dist/models/interoperable-claims/practitioner-role-claims.js +24 -0
  25. package/dist/models/terminology.d.ts +59 -0
  26. package/dist/models/terminology.js +1 -0
  27. package/dist/utils/bundle-document-builder.js +5 -1
  28. package/dist/utils/clinical-resource-converters.d.ts +8 -0
  29. package/dist/utils/clinical-resource-converters.js +12 -0
  30. package/dist/utils/clinical-resource-view.d.ts +8 -2
  31. package/dist/utils/clinical-resource-view.js +39 -23
  32. package/dist/utils/index.d.ts +1 -0
  33. package/dist/utils/index.js +1 -0
  34. package/dist/utils/local-terminology-provider.d.ts +27 -0
  35. package/dist/utils/local-terminology-provider.js +154 -0
  36. package/package.json +1 -1
@@ -4,13 +4,22 @@ import { CarePlanClaim } from '../models/interoperable-claims/care-plan-claims.j
4
4
  import { codingFromValue, codingToValue, referenceListToCsv, referenceToValue } from './convert-shared.js';
5
5
  export function carePlanFlatToFhirR4(claims) {
6
6
  const subject = claims[CarePlanClaim.Subject] ?? claims[CarePlanClaim.Patient];
7
+ const categoryCoding = codingFromValue(claims[CarePlanClaim.Category])?.map((coding) => ({
8
+ ...coding,
9
+ ...(claims[CarePlanClaim.CategoryDisplay] ? { display: claims[CarePlanClaim.CategoryDisplay] } : {}),
10
+ }));
7
11
  return {
8
12
  resourceType: 'CarePlan',
9
13
  identifier: claims[CarePlanClaim.Identifier] ? [{ value: claims[CarePlanClaim.Identifier] }] : undefined,
10
14
  status: claims[CarePlanClaim.Status],
11
15
  intent: claims[CarePlanClaim.Intent],
12
16
  subject: subject ? { reference: subject } : undefined,
13
- category: claims[CarePlanClaim.Category] ? [{ coding: codingFromValue(claims[CarePlanClaim.Category]) }] : undefined,
17
+ category: claims[CarePlanClaim.Category] || claims[CarePlanClaim.CategoryText] || claims[CarePlanClaim.CategoryDisplay]
18
+ ? [{
19
+ ...(categoryCoding ? { coding: categoryCoding } : {}),
20
+ ...(claims[CarePlanClaim.CategoryText] ? { text: claims[CarePlanClaim.CategoryText] } : {}),
21
+ }]
22
+ : undefined,
14
23
  created: claims[CarePlanClaim.Date],
15
24
  note: claims[CarePlanClaim.Note] ? [{ text: claims[CarePlanClaim.Note] }] : undefined,
16
25
  basedOn: claims[CarePlanClaim.BasedOn] ? claims[CarePlanClaim.BasedOn].split(',').map((reference) => ({ reference: reference.trim() })) : undefined,
@@ -41,6 +50,8 @@ export function carePlanFhirR4ToFlat(resource) {
41
50
  [CarePlanClaim.BasedOn]: referenceListToCsv(resource.basedOn),
42
51
  [CarePlanClaim.CareTeam]: referenceListToCsv(resource.careTeam),
43
52
  [CarePlanClaim.Category]: codingToValue(resource.category?.[0]?.coding?.[0]),
53
+ [CarePlanClaim.CategoryText]: resource.category?.[0]?.text,
54
+ [CarePlanClaim.CategoryDisplay]: resource.category?.[0]?.coding?.[0]?.display,
44
55
  [CarePlanClaim.Condition]: referenceListToCsv(resource.addresses),
45
56
  [CarePlanClaim.Date]: resource.created || period?.start,
46
57
  [CarePlanClaim.Encounter]: referenceToValue(resource.encounter),
@@ -0,0 +1,3 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared';
2
+ export declare function consentFhirR4ToFlat(resource: FhirResource, context?: string): FlatClaims;
3
+ export declare function consentFlatToFhirR4(claims: FlatClaims): FhirResource;
@@ -0,0 +1,32 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { ClaimConsent } from '../models/consent-rule.js';
3
+ import { codingFromValue, codingToValue, fhirResourceToFlatClaims, flatClaimsToFhirResource, } from './convert-shared.js';
4
+ export function consentFhirR4ToFlat(resource, context = 'org.hl7.fhir.r4') {
5
+ const concept = resource.category?.[0];
6
+ return {
7
+ ...fhirResourceToFlatClaims(resource, context),
8
+ [ClaimConsent.category]: codingToValue(concept?.coding?.[0]),
9
+ [ClaimConsent.categoryText]: concept?.text,
10
+ [ClaimConsent.categoryDisplay]: concept?.coding?.[0]?.display,
11
+ };
12
+ }
13
+ export function consentFlatToFhirR4(claims) {
14
+ const structuralClaims = { ...claims };
15
+ delete structuralClaims[ClaimConsent.category];
16
+ delete structuralClaims[ClaimConsent.categoryText];
17
+ delete structuralClaims[ClaimConsent.categoryDisplay];
18
+ const resource = flatClaimsToFhirResource(structuralClaims);
19
+ const coding = codingFromValue(claims[ClaimConsent.category])?.map((item) => ({
20
+ ...item,
21
+ ...(claims[ClaimConsent.categoryDisplay]
22
+ ? { display: claims[ClaimConsent.categoryDisplay] }
23
+ : {}),
24
+ }));
25
+ if (coding || claims[ClaimConsent.categoryText]) {
26
+ resource.category = [{
27
+ ...(coding ? { coding } : {}),
28
+ ...(claims[ClaimConsent.categoryText] ? { text: claims[ClaimConsent.categoryText] } : {}),
29
+ }];
30
+ }
31
+ return resource;
32
+ }
@@ -3,6 +3,10 @@
3
3
  import { DeviceClaim } from '../models/interoperable-claims/device-claims.js';
4
4
  import { codingFromValue, codingToValue, referenceToValue } from './convert-shared.js';
5
5
  export function deviceFlatToFhirR4(claims) {
6
+ const typeCoding = codingFromValue(claims[DeviceClaim.Type])?.map((coding) => ({
7
+ ...coding,
8
+ ...(claims[DeviceClaim.TypeDisplay] ? { display: claims[DeviceClaim.TypeDisplay] } : {}),
9
+ }));
6
10
  return {
7
11
  resourceType: 'Device',
8
12
  identifier: claims[DeviceClaim.Identifier] ? [{ value: claims[DeviceClaim.Identifier] }] : undefined,
@@ -14,7 +18,12 @@ export function deviceFlatToFhirR4(claims) {
14
18
  patient: claims[DeviceClaim.Patient] ? { reference: claims[DeviceClaim.Patient] } : undefined,
15
19
  serialNumber: claims[DeviceClaim.SerialNumber],
16
20
  status: claims[DeviceClaim.Status],
17
- type: claims[DeviceClaim.Type] ? { coding: codingFromValue(claims[DeviceClaim.Type]) } : undefined,
21
+ type: claims[DeviceClaim.Type] || claims[DeviceClaim.TypeText] || claims[DeviceClaim.TypeDisplay]
22
+ ? {
23
+ ...(typeCoding ? { coding: typeCoding } : {}),
24
+ ...(claims[DeviceClaim.TypeText] ? { text: claims[DeviceClaim.TypeText] } : {}),
25
+ }
26
+ : undefined,
18
27
  udiCarrier: claims[DeviceClaim.UdiCarrier] ? [{ carrierHRF: claims[DeviceClaim.UdiCarrier] }] : undefined,
19
28
  url: claims[DeviceClaim.Url],
20
29
  };
@@ -31,6 +40,8 @@ export function deviceFhirR4ToFlat(resource) {
31
40
  [DeviceClaim.SerialNumber]: resource.serialNumber,
32
41
  [DeviceClaim.Status]: resource.status,
33
42
  [DeviceClaim.Type]: codingToValue(resource.type?.coding?.[0]),
43
+ [DeviceClaim.TypeText]: resource.type?.text,
44
+ [DeviceClaim.TypeDisplay]: resource.type?.coding?.[0]?.display,
34
45
  [DeviceClaim.UdiCarrier]: resource.udiCarrier?.[0]?.carrierHRF,
35
46
  [DeviceClaim.Url]: resource.url,
36
47
  };
@@ -1,14 +1,29 @@
1
1
  // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
2
  // File: src/utils/convert-document-reference.ts
3
3
  import { DocumentReferenceClaim } from '../models/interoperable-claims/document-reference-claims.js';
4
- import { requireClaim } from './convert-shared.js';
4
+ import { codingFromValue, codingToValue, requireClaim } from './convert-shared.js';
5
5
  export function documentReferenceFlatToFhirR4(claims) {
6
6
  const subject = requireClaim(claims, DocumentReferenceClaim.Subject);
7
+ const typeCoding = codingFromValue(claims[DocumentReferenceClaim.Type])?.map((coding) => ({
8
+ ...coding,
9
+ ...(claims[DocumentReferenceClaim.TypeDisplay] ? { display: claims[DocumentReferenceClaim.TypeDisplay] } : {}),
10
+ }));
7
11
  return {
8
12
  resourceType: 'DocumentReference',
9
13
  identifier: claims[DocumentReferenceClaim.Identifier] ? [{ value: claims[DocumentReferenceClaim.Identifier] }] : undefined,
10
14
  subject: { reference: subject },
11
15
  description: claims[DocumentReferenceClaim.Description],
16
+ category: claims[DocumentReferenceClaim.Category]
17
+ ? [{ coding: codingFromValue(claims[DocumentReferenceClaim.Category]) }]
18
+ : undefined,
19
+ type: claims[DocumentReferenceClaim.Type]
20
+ || claims[DocumentReferenceClaim.TypeText]
21
+ || claims[DocumentReferenceClaim.TypeDisplay]
22
+ ? {
23
+ ...(typeCoding ? { coding: typeCoding } : {}),
24
+ ...(claims[DocumentReferenceClaim.TypeText] ? { text: claims[DocumentReferenceClaim.TypeText] } : {}),
25
+ }
26
+ : undefined,
12
27
  date: claims[DocumentReferenceClaim.Date],
13
28
  content: [{ attachment: {
14
29
  contentType: claims[DocumentReferenceClaim.ContentType],
@@ -25,6 +40,10 @@ export function documentReferenceFhirR4ToFlat(resource) {
25
40
  [DocumentReferenceClaim.Identifier]: resource.identifier?.[0]?.value,
26
41
  [DocumentReferenceClaim.Subject]: resource.subject?.reference,
27
42
  [DocumentReferenceClaim.Description]: resource.description,
43
+ [DocumentReferenceClaim.Category]: codingToValue(resource.category?.[0]?.coding?.[0]),
44
+ [DocumentReferenceClaim.Type]: codingToValue(resource.type?.coding?.[0]),
45
+ [DocumentReferenceClaim.TypeText]: resource.type?.text,
46
+ [DocumentReferenceClaim.TypeDisplay]: resource.type?.coding?.[0]?.display,
28
47
  [DocumentReferenceClaim.Date]: resource.date,
29
48
  [DocumentReferenceClaim.ContentType]: attachment?.contentType,
30
49
  [DocumentReferenceClaim.ContentData]: attachment?.data,
@@ -23,6 +23,16 @@ export function observationFromFlatToFhirR4(claims) {
23
23
  code: claims[ObservationClaim.ValueConceptValue],
24
24
  });
25
25
  const valueQuantityCoding = codingFromValue(claims[ObservationClaim.ValueQuantityUnit])?.[0];
26
+ const codeCoding = codingFromValue(codeToken)?.map((coding) => ({
27
+ ...coding,
28
+ ...(claims[ObservationClaim.CodeDisplay] ? { display: claims[ObservationClaim.CodeDisplay] } : {}),
29
+ }));
30
+ const valueConceptCoding = codingFromValue(valueConceptToken)?.map((coding) => ({
31
+ ...coding,
32
+ ...(claims[ObservationClaim.ValueConceptDisplay]
33
+ ? { display: claims[ObservationClaim.ValueConceptDisplay] }
34
+ : {}),
35
+ }));
26
36
  return {
27
37
  resourceType: 'Observation',
28
38
  identifier: claims[ObservationClaim.Identifier] ? [{ value: claims[ObservationClaim.Identifier] }] : undefined,
@@ -30,10 +40,8 @@ export function observationFromFlatToFhirR4(claims) {
30
40
  category: claims[ObservationClaim.Category] ? claims[ObservationClaim.Category].split(',').map((value) => ({ coding: codingFromValue(value.trim()) })) : undefined,
31
41
  code: codeToken
32
42
  ? {
33
- coding: codingFromValue(codeToken),
34
- ...(claims[ObservationClaim.CodeText] || claims[ObservationClaim.CodeDisplay]
35
- ? { text: claims[ObservationClaim.CodeText] ?? claims[ObservationClaim.CodeDisplay] }
36
- : {}),
43
+ coding: codeCoding,
44
+ ...(claims[ObservationClaim.CodeText] ? { text: claims[ObservationClaim.CodeText] } : {}),
37
45
  }
38
46
  : undefined,
39
47
  subject: subject ? { reference: subject } : undefined,
@@ -42,9 +50,9 @@ export function observationFromFlatToFhirR4(claims) {
42
50
  performer: claims[ObservationClaim.Performer] ? claims[ObservationClaim.Performer].split(',').map((reference) => ({ reference: reference.trim() })) : undefined,
43
51
  valueCodeableConcept: valueConceptToken
44
52
  ? {
45
- coding: codingFromValue(valueConceptToken),
46
- ...(claims[ObservationClaim.ValueConceptText] || claims[ObservationClaim.ValueConceptDisplay]
47
- ? { text: claims[ObservationClaim.ValueConceptText] ?? claims[ObservationClaim.ValueConceptDisplay] }
53
+ coding: valueConceptCoding,
54
+ ...(claims[ObservationClaim.ValueConceptText]
55
+ ? { text: claims[ObservationClaim.ValueConceptText] }
48
56
  : {}),
49
57
  }
50
58
  : undefined,
@@ -0,0 +1,3 @@
1
+ import type { FhirResource, FlatClaims } from './convert-shared';
2
+ export declare function practitionerRoleFhirR4ToFlat(resource: FhirResource, context?: string): FlatClaims;
3
+ export declare function practitionerRoleFlatToFhirR4(claims: FlatClaims): FhirResource;
@@ -0,0 +1,34 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ import { PractitionerRoleClaim } from '../models/interoperable-claims/practitioner-role-claims.js';
3
+ import { codingFromValue, codingToValue, fhirResourceToFlatClaims, flatClaimsToFhirResource, } from './convert-shared.js';
4
+ export function practitionerRoleFhirR4ToFlat(resource, context = 'org.hl7.fhir.r4') {
5
+ const concept = resource.code?.[0];
6
+ return {
7
+ ...fhirResourceToFlatClaims(resource, context),
8
+ [PractitionerRoleClaim.Code]: codingToValue(concept?.coding?.[0]),
9
+ [PractitionerRoleClaim.CodeText]: concept?.text,
10
+ [PractitionerRoleClaim.CodeDisplay]: concept?.coding?.[0]?.display,
11
+ };
12
+ }
13
+ export function practitionerRoleFlatToFhirR4(claims) {
14
+ const structuralClaims = { ...claims };
15
+ delete structuralClaims[PractitionerRoleClaim.Code];
16
+ delete structuralClaims[PractitionerRoleClaim.CodeText];
17
+ delete structuralClaims[PractitionerRoleClaim.CodeDisplay];
18
+ const resource = flatClaimsToFhirResource(structuralClaims);
19
+ const coding = codingFromValue(claims[PractitionerRoleClaim.Code])?.map((item) => ({
20
+ ...item,
21
+ ...(claims[PractitionerRoleClaim.CodeDisplay]
22
+ ? { display: claims[PractitionerRoleClaim.CodeDisplay] }
23
+ : {}),
24
+ }));
25
+ if (coding || claims[PractitionerRoleClaim.CodeText]) {
26
+ resource.code = [{
27
+ ...(coding ? { coding } : {}),
28
+ ...(claims[PractitionerRoleClaim.CodeText]
29
+ ? { text: claims[PractitionerRoleClaim.CodeText] }
30
+ : {}),
31
+ }];
32
+ }
33
+ return resource;
34
+ }
@@ -20,4 +20,6 @@ export * from './convert-encounter';
20
20
  export * from './convert-related-person';
21
21
  export * from './convert-coverage';
22
22
  export * from './convert-clinical-impression';
23
+ export * from './convert-consent';
24
+ export * from './convert-practitioner-role';
23
25
  export * from './schemaorg-to-gaia-x';
@@ -20,4 +20,6 @@ export * from './convert-encounter.js';
20
20
  export * from './convert-related-person.js';
21
21
  export * from './convert-coverage.js';
22
22
  export * from './convert-clinical-impression.js';
23
+ export * from './convert-consent.js';
24
+ export * from './convert-practitioner-role.js';
23
25
  export * from './schemaorg-to-gaia-x.js';
@@ -2,6 +2,8 @@ export declare enum ClaimConsent {
2
2
  'decision' = "Consent.decision",
3
3
  'action' = "Consent.action",
4
4
  'category' = "Consent.category",
5
+ 'categoryText' = "Consent.category-text",
6
+ 'categoryDisplay' = "Consent.category-display",
5
7
  'resourceType' = "Consent.resourceType",
6
8
  'subject' = "Consent.subject",
7
9
  'actorIdentifier' = "Consent.actor-identifier",
@@ -4,6 +4,8 @@ export var ClaimConsent;
4
4
  ClaimConsent["decision"] = "Consent.decision";
5
5
  ClaimConsent["action"] = "Consent.action";
6
6
  ClaimConsent["category"] = "Consent.category";
7
+ ClaimConsent["categoryText"] = "Consent.category-text";
8
+ ClaimConsent["categoryDisplay"] = "Consent.category-display";
7
9
  ClaimConsent["resourceType"] = "Consent.resourceType";
8
10
  ClaimConsent["subject"] = "Consent.subject";
9
11
  ClaimConsent["actorIdentifier"] = "Consent.actor-identifier";
@@ -49,6 +49,7 @@ export * from './relationship-access';
49
49
  export * from './response';
50
50
  export * from './subject-identifier-ledger';
51
51
  export * from './subject-identity-binding';
52
+ export * from './terminology';
52
53
  export * from './urlPath';
53
54
  export * from './verifiable-credential';
54
55
  export * from './wallet';
@@ -49,6 +49,7 @@ export * from './relationship-access.js';
49
49
  export * from './response.js';
50
50
  export * from './subject-identifier-ledger.js';
51
51
  export * from './subject-identity-binding.js';
52
+ export * from './terminology.js';
52
53
  export * from './urlPath.js';
53
54
  export * from './verifiable-credential.js';
54
55
  export * from './wallet.js';
@@ -6,6 +6,8 @@ export declare const CarePlanClaim: {
6
6
  readonly BasedOn: "CarePlan.based-on";
7
7
  readonly CareTeam: "CarePlan.care-team";
8
8
  readonly Category: "CarePlan.category";
9
+ readonly CategoryText: "CarePlan.category-text";
10
+ readonly CategoryDisplay: "CarePlan.category-display";
9
11
  readonly Condition: "CarePlan.condition";
10
12
  readonly Date: "CarePlan.date";
11
13
  readonly Encounter: "CarePlan.encounter";
@@ -7,6 +7,8 @@ export const CarePlanClaim = {
7
7
  BasedOn: 'CarePlan.based-on',
8
8
  CareTeam: 'CarePlan.care-team',
9
9
  Category: 'CarePlan.category',
10
+ CategoryText: 'CarePlan.category-text',
11
+ CategoryDisplay: 'CarePlan.category-display',
10
12
  Condition: 'CarePlan.condition',
11
13
  Date: 'CarePlan.date',
12
14
  Encounter: 'CarePlan.encounter',
@@ -28,6 +30,8 @@ export const CarePlanClaimSpecs = [
28
30
  { key: CarePlanClaim.BasedOn, meaning: 'Based-on reference.', example: 'CarePlan/plan-parent' },
29
31
  { key: CarePlanClaim.CareTeam, meaning: 'Care team references (CSV).', example: 'CareTeam/team-1' },
30
32
  { key: CarePlanClaim.Category, meaning: 'Care plan category token.', example: 'http://snomed.info/sct|736373009' },
33
+ { key: CarePlanClaim.CategoryText, meaning: 'Local-language care plan category label.', example: 'Plan de control de peso' },
34
+ { key: CarePlanClaim.CategoryDisplay, meaning: 'Canonical/international care plan category display.', example: 'Weight management plan' },
31
35
  { key: CarePlanClaim.Condition, meaning: 'Condition references (CSV).', example: 'Condition/cond-1' },
32
36
  { key: CarePlanClaim.Date, meaning: 'Care plan creation or period date.', example: '2026-06-01T10:00:00Z' },
33
37
  { key: CarePlanClaim.Encounter, meaning: 'Encounter reference.', example: 'Encounter/enc-1' },
@@ -10,6 +10,8 @@ export declare const DeviceClaim: {
10
10
  readonly SerialNumber: "Device.serial-number";
11
11
  readonly Status: "Device.status";
12
12
  readonly Type: "Device.type";
13
+ readonly TypeText: "Device.type-text";
14
+ readonly TypeDisplay: "Device.type-display";
13
15
  readonly UdiCarrier: "Device.udi-carrier";
14
16
  readonly Url: "Device.url";
15
17
  readonly Note: "Device.note";
@@ -11,6 +11,8 @@ export const DeviceClaim = {
11
11
  SerialNumber: 'Device.serial-number',
12
12
  Status: 'Device.status',
13
13
  Type: 'Device.type',
14
+ TypeText: 'Device.type-text',
15
+ TypeDisplay: 'Device.type-display',
14
16
  UdiCarrier: 'Device.udi-carrier',
15
17
  Url: 'Device.url',
16
18
  Note: 'Device.note',
@@ -26,6 +28,8 @@ export const DeviceClaimSpecs = [
26
28
  { key: DeviceClaim.SerialNumber, meaning: 'Serial number.', example: 'SN-12345' },
27
29
  { key: DeviceClaim.Status, meaning: 'Device status.', example: 'active' },
28
30
  { key: DeviceClaim.Type, meaning: 'Device type token.', example: 'http://snomed.info/sct|706172005' },
31
+ { key: DeviceClaim.TypeText, meaning: 'Local-language device type label.', example: 'Bomba de insulina' },
32
+ { key: DeviceClaim.TypeDisplay, meaning: 'Canonical/international device type display.', example: 'Insulin pump' },
29
33
  { key: DeviceClaim.UdiCarrier, meaning: 'UDI carrier string.', example: '(01)09504000059118(17)220101(10)ABCD1234' },
30
34
  { key: DeviceClaim.Url, meaning: 'Canonical URL.', example: 'https://device.example.org/catalogue/123' },
31
35
  { key: DeviceClaim.Note, meaning: 'Clinical note text.', example: 'Patient-owned continuous glucose monitor.' },
@@ -22,6 +22,8 @@ export declare const DocumentReferenceClaim: {
22
22
  readonly Relation: "DocumentReference.relation";
23
23
  readonly Subject: "DocumentReference.subject";
24
24
  readonly Type: "DocumentReference.type";
25
+ readonly TypeText: "DocumentReference.type-text";
26
+ readonly TypeDisplay: "DocumentReference.type-display";
25
27
  readonly UserSelected: "DocumentReference.user-selected";
26
28
  };
27
29
  export type DocumentReferenceClaimKey = typeof DocumentReferenceClaim[keyof typeof DocumentReferenceClaim];
@@ -24,6 +24,8 @@ export const DocumentReferenceClaim = {
24
24
  Relation: 'DocumentReference.relation',
25
25
  Subject: 'DocumentReference.subject',
26
26
  Type: 'DocumentReference.type',
27
+ TypeText: 'DocumentReference.type-text',
28
+ TypeDisplay: 'DocumentReference.type-display',
27
29
  UserSelected: 'DocumentReference.user-selected',
28
30
  };
29
31
  /**
@@ -53,6 +55,8 @@ export const DocumentReferenceClaimSpecs = [
53
55
  { key: DocumentReferenceClaim.Relation, meaning: 'Relation type to related document.', example: 'appends' },
54
56
  { key: DocumentReferenceClaim.Subject, meaning: 'URN for the section in the individual index.', example: 'urn:uhix:section:vitals' },
55
57
  { key: DocumentReferenceClaim.Type, meaning: 'Lower-level, sector-specific type.', example: 'http://hl7.org/fhir/ValueSet/c80-doc-typecodes|34133-9' },
58
+ { key: DocumentReferenceClaim.TypeText, meaning: 'Local-language document type label.', example: 'Resumen clínico' },
59
+ { key: DocumentReferenceClaim.TypeDisplay, meaning: 'Canonical/international document type display.', example: 'Patient summary Document' },
56
60
  { key: DocumentReferenceClaim.UserSelected, meaning: 'Custom provenance flag telling whether the document was explicitly created or selected by the end user/controller.', example: 'true' },
57
61
  ];
58
62
  /**
@@ -13,6 +13,7 @@ export * from './device-claims';
13
13
  export * from './observation-claims';
14
14
  export * from './immunization-claims';
15
15
  export * from './procedure-claims';
16
+ export * from './practitioner-role-claims';
16
17
  export * from './care-plan-claims';
17
18
  export * from './flag-claims';
18
19
  export * from './appointment-claims';
@@ -15,6 +15,7 @@ export * from './device-claims.js';
15
15
  export * from './observation-claims.js';
16
16
  export * from './immunization-claims.js';
17
17
  export * from './procedure-claims.js';
18
+ export * from './practitioner-role-claims.js';
18
19
  export * from './care-plan-claims.js';
19
20
  export * from './flag-claims.js';
20
21
  export * from './appointment-claims.js';
@@ -0,0 +1,9 @@
1
+ import type { ClaimSpec } from './types';
2
+ /** Canonical claims used to preserve the primary coded PractitionerRole label. */
3
+ export declare const PractitionerRoleClaim: {
4
+ readonly Code: "PractitionerRole.code";
5
+ readonly CodeText: "PractitionerRole.code-text";
6
+ readonly CodeDisplay: "PractitionerRole.code-display";
7
+ };
8
+ export type PractitionerRoleClaimKey = typeof PractitionerRoleClaim[keyof typeof PractitionerRoleClaim];
9
+ export declare const PractitionerRoleClaimSpecs: ClaimSpec[];
@@ -0,0 +1,24 @@
1
+ // Copyright 2026 Conéctate Soluciones y Aplicaciones SL under the Apache License, Version 2.0.
2
+ /** Canonical claims used to preserve the primary coded PractitionerRole label. */
3
+ export const PractitionerRoleClaim = {
4
+ Code: 'PractitionerRole.code',
5
+ CodeText: 'PractitionerRole.code-text',
6
+ CodeDisplay: 'PractitionerRole.code-display',
7
+ };
8
+ export const PractitionerRoleClaimSpecs = [
9
+ {
10
+ key: PractitionerRoleClaim.Code,
11
+ meaning: 'Primary practitioner role code token.',
12
+ example: 'http://snomed.info/sct|158965000',
13
+ },
14
+ {
15
+ key: PractitionerRoleClaim.CodeText,
16
+ meaning: 'Local-language practitioner role label.',
17
+ example: 'Médico',
18
+ },
19
+ {
20
+ key: PractitionerRoleClaim.CodeDisplay,
21
+ meaning: 'Canonical/international practitioner role display.',
22
+ example: 'Doctor',
23
+ },
24
+ ];
@@ -0,0 +1,59 @@
1
+ /**
2
+ * One terminology block in the legacy JSON catalog format.
3
+ *
4
+ * `id` is normally the canonical coding-system URI. The compatibility alias
5
+ * `ips` is accepted for the SNOMED IPS catalog used by
6
+ * the predecessor FHIR utility package.
7
+ */
8
+ export type TerminologyCatalogResource = Readonly<{
9
+ id: string;
10
+ language?: string;
11
+ attributes: Readonly<Record<string, string>>;
12
+ meta?: Readonly<Record<string, unknown>>;
13
+ }>;
14
+ /**
15
+ * Loadable local terminology document compatible with the historic
16
+ * `data[].attributes[code] = display` JSON shape.
17
+ */
18
+ export type TerminologyCatalogDocument = Readonly<{
19
+ id?: string;
20
+ name?: string;
21
+ language?: string;
22
+ system?: string;
23
+ version?: string;
24
+ jurisdiction?: string;
25
+ data: readonly TerminologyCatalogResource[];
26
+ meta?: Readonly<Record<string, unknown>>;
27
+ }>;
28
+ /** Input for a synchronous local terminology label lookup. */
29
+ export type TerminologyLookupInput = Readonly<{
30
+ system: string;
31
+ code: string;
32
+ language: string;
33
+ jurisdiction?: string;
34
+ }>;
35
+ /** Input for an offline/local terminology text search. */
36
+ export type TerminologySearchInput = Readonly<{
37
+ text: string;
38
+ language: string;
39
+ jurisdiction?: string;
40
+ systems?: readonly string[];
41
+ limit?: number;
42
+ }>;
43
+ /** One terminology option suitable for a coded clinical form control. */
44
+ export type TerminologySearchResult = Readonly<{
45
+ system: string;
46
+ code: string;
47
+ display: string;
48
+ language: string;
49
+ }>;
50
+ /**
51
+ * Synchronous terminology contract used by cached and offline applications.
52
+ *
53
+ * A future remote terminology client should populate a local implementation
54
+ * of this contract before synchronous clinical-card rendering.
55
+ */
56
+ export interface TerminologyProvider {
57
+ lookup(input: TerminologyLookupInput): string | undefined;
58
+ search(input: TerminologySearchInput): readonly TerminologySearchResult[];
59
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -5,7 +5,7 @@ import { ConditionClaim } from '../models/interoperable-claims/condition-claims.
5
5
  import { DiagnosticReportClaim } from '../models/interoperable-claims/diagnostic-report-claims.js';
6
6
  import { MedicationStatementClaim } from '../models/interoperable-claims/medication-statement-claims.js';
7
7
  import { ClaimConsent } from '../models/consent-rule.js';
8
- import { allergyIntoleranceFlatToFhirR4, appointmentFlatToFhirR4, carePlanFlatToFhirR4, clinicalImpressionFlatToFhirR4, compositionFlatToFhirR4, conditionFlatToFhirR4, convertFhirResourceToClaims, coverageFlatToFhirR4, deviceFlatToFhirR4, documentReferenceFlatToFhirR4, diagnosticReportFlatToFhirR4, encounterFlatToFhirR4, flagFlatToFhirR4, flatClaimsToFhirResource, immunizationFlatToFhirR4, locationFlatToFhirR4, medicationStatementFlatToFhirR4, observationFromFlatToFhirR4, organizationFlatToFhirR4, procedureFlatToFhirR4, relatedPersonFlatToFhirR4, } from './clinical-resource-converters.js';
8
+ import { allergyIntoleranceFlatToFhirR4, appointmentFlatToFhirR4, carePlanFlatToFhirR4, clinicalImpressionFlatToFhirR4, compositionFlatToFhirR4, conditionFlatToFhirR4, consentFlatToFhirR4, convertFhirResourceToClaims, coverageFlatToFhirR4, deviceFlatToFhirR4, documentReferenceFlatToFhirR4, diagnosticReportFlatToFhirR4, encounterFlatToFhirR4, flagFlatToFhirR4, flatClaimsToFhirResource, immunizationFlatToFhirR4, locationFlatToFhirR4, medicationStatementFlatToFhirR4, observationFromFlatToFhirR4, organizationFlatToFhirR4, procedureFlatToFhirR4, practitionerRoleFlatToFhirR4, relatedPersonFlatToFhirR4, } from './clinical-resource-converters.js';
9
9
  function asTrimmedString(value) {
10
10
  if (value === undefined || value === null)
11
11
  return '';
@@ -378,6 +378,10 @@ function convertClaimsToFhirResourceByType(flatClaims, resourceType) {
378
378
  return coverageFlatToFhirR4(flatClaims);
379
379
  case 'ClinicalImpression':
380
380
  return clinicalImpressionFlatToFhirR4(flatClaims);
381
+ case ResourceTypesFhirR4.Consent:
382
+ return consentFlatToFhirR4(flatClaims);
383
+ case ResourceTypesFhirR4.PractitionerRole:
384
+ return practitionerRoleFlatToFhirR4(flatClaims);
381
385
  default:
382
386
  return flatClaimsToFhirResource(flatClaims);
383
387
  }
@@ -21,6 +21,8 @@ export { encounterFlatToFhirR4, encounterFhirR4ToFlat, } from '../convert/conver
21
21
  export { relatedPersonFlatToFhirR4, relatedPersonFhirR4ToFlat, } from '../convert/convert-related-person';
22
22
  export { coverageFlatToFhirR4, coverageFhirR4ToFlat, } from '../convert/convert-coverage';
23
23
  export { clinicalImpressionFlatToFhirR4, clinicalImpressionFhirR4ToFlat, } from '../convert/convert-clinical-impression';
24
+ export { consentFlatToFhirR4, consentFhirR4ToFlat, } from '../convert/convert-consent';
25
+ export { practitionerRoleFlatToFhirR4, practitionerRoleFhirR4ToFlat, } from '../convert/convert-practitioner-role';
24
26
  import type { FhirResource, FlatClaims } from '../convert/convert-shared';
25
27
  import { medicationStatementFhirR4ToFlat, medicationStatementFlatToFhirR4 } from '../convert/convert-medication-statement';
26
28
  import { allergyIntoleranceFhirR4ToFlat, allergyIntoleranceFlatToFhirR4 } from '../convert/convert-allergy-intolerance';
@@ -43,6 +45,8 @@ import { encounterFhirR4ToFlat, encounterFlatToFhirR4 } from '../convert/convert
43
45
  import { relatedPersonFhirR4ToFlat, relatedPersonFlatToFhirR4 } from '../convert/convert-related-person';
44
46
  import { coverageFhirR4ToFlat, coverageFlatToFhirR4 } from '../convert/convert-coverage';
45
47
  import { clinicalImpressionFhirR4ToFlat, clinicalImpressionFlatToFhirR4 } from '../convert/convert-clinical-impression';
48
+ import { consentFhirR4ToFlat, consentFlatToFhirR4 } from '../convert/convert-consent';
49
+ import { practitionerRoleFhirR4ToFlat, practitionerRoleFlatToFhirR4 } from '../convert/convert-practitioner-role';
46
50
  export declare const medicationStatementFlatToFhir: typeof medicationStatementFlatToFhirR4;
47
51
  export declare const medicationStatementFhirToFlat: typeof medicationStatementFhirR4ToFlat;
48
52
  export declare const allergyIntoleranceFlatToFhir: typeof allergyIntoleranceFlatToFhirR4;
@@ -85,6 +89,10 @@ export declare const coverageFlatToFhir: typeof coverageFlatToFhirR4;
85
89
  export declare const coverageFhirToFlat: typeof coverageFhirR4ToFlat;
86
90
  export declare const clinicalImpressionFlatToFhir: typeof clinicalImpressionFlatToFhirR4;
87
91
  export declare const clinicalImpressionFhirToFlat: typeof clinicalImpressionFhirR4ToFlat;
92
+ export declare const consentFlatToFhir: typeof consentFlatToFhirR4;
93
+ export declare const consentFhirToFlat: typeof consentFhirR4ToFlat;
94
+ export declare const practitionerRoleFlatToFhir: typeof practitionerRoleFlatToFhirR4;
95
+ export declare const practitionerRoleFhirToFlat: typeof practitionerRoleFhirR4ToFlat;
88
96
  /**
89
97
  * Converts a FHIR resource to the semantic flat-claims contract when a
90
98
  * resource-specific converter exists. Unsupported resources fall back to the
@@ -22,6 +22,8 @@ export { encounterFlatToFhirR4, encounterFhirR4ToFlat, } from '../convert/conver
22
22
  export { relatedPersonFlatToFhirR4, relatedPersonFhirR4ToFlat, } from '../convert/convert-related-person.js';
23
23
  export { coverageFlatToFhirR4, coverageFhirR4ToFlat, } from '../convert/convert-coverage.js';
24
24
  export { clinicalImpressionFlatToFhirR4, clinicalImpressionFhirR4ToFlat, } from '../convert/convert-clinical-impression.js';
25
+ export { consentFlatToFhirR4, consentFhirR4ToFlat, } from '../convert/convert-consent.js';
26
+ export { practitionerRoleFlatToFhirR4, practitionerRoleFhirR4ToFlat, } from '../convert/convert-practitioner-role.js';
25
27
  import { fhirResourceToFlatClaims } from '../convert/convert-shared.js';
26
28
  import { medicationStatementFhirR4ToFlat, medicationStatementFlatToFhirR4 } from '../convert/convert-medication-statement.js';
27
29
  import { allergyIntoleranceFhirR4ToFlat, allergyIntoleranceFlatToFhirR4 } from '../convert/convert-allergy-intolerance.js';
@@ -44,6 +46,8 @@ import { encounterFhirR4ToFlat, encounterFlatToFhirR4 } from '../convert/convert
44
46
  import { relatedPersonFhirR4ToFlat, relatedPersonFlatToFhirR4 } from '../convert/convert-related-person.js';
45
47
  import { coverageFhirR4ToFlat, coverageFlatToFhirR4 } from '../convert/convert-coverage.js';
46
48
  import { clinicalImpressionFhirR4ToFlat, clinicalImpressionFlatToFhirR4 } from '../convert/convert-clinical-impression.js';
49
+ import { consentFhirR4ToFlat, consentFlatToFhirR4 } from '../convert/convert-consent.js';
50
+ import { practitionerRoleFhirR4ToFlat, practitionerRoleFlatToFhirR4 } from '../convert/convert-practitioner-role.js';
47
51
  export const medicationStatementFlatToFhir = medicationStatementFlatToFhirR4;
48
52
  export const medicationStatementFhirToFlat = medicationStatementFhirR4ToFlat;
49
53
  export const allergyIntoleranceFlatToFhir = allergyIntoleranceFlatToFhirR4;
@@ -86,6 +90,10 @@ export const coverageFlatToFhir = coverageFlatToFhirR4;
86
90
  export const coverageFhirToFlat = coverageFhirR4ToFlat;
87
91
  export const clinicalImpressionFlatToFhir = clinicalImpressionFlatToFhirR4;
88
92
  export const clinicalImpressionFhirToFlat = clinicalImpressionFhirR4ToFlat;
93
+ export const consentFlatToFhir = consentFlatToFhirR4;
94
+ export const consentFhirToFlat = consentFhirR4ToFlat;
95
+ export const practitionerRoleFlatToFhir = practitionerRoleFlatToFhirR4;
96
+ export const practitionerRoleFhirToFlat = practitionerRoleFhirR4ToFlat;
89
97
  /**
90
98
  * Converts a FHIR resource to the semantic flat-claims contract when a
91
99
  * resource-specific converter exists. Unsupported resources fall back to the
@@ -142,6 +150,10 @@ function convertFhirResourceToClaimsByType(resource, context) {
142
150
  return coverageFhirR4ToFlat(resource);
143
151
  case 'ClinicalImpression':
144
152
  return clinicalImpressionFhirR4ToFlat(resource);
153
+ case 'Consent':
154
+ return consentFhirR4ToFlat(resource, context);
155
+ case 'PractitionerRole':
156
+ return practitionerRoleFhirR4ToFlat(resource, context);
145
157
  default:
146
158
  return fhirResourceToFlatClaims(resource, context);
147
159
  }
@@ -37,11 +37,17 @@ export type ClinicalResourceDisplayOptions = Readonly<{
37
37
  /**
38
38
  * Product terminology lookup keyed by canonical `system|code`.
39
39
  *
40
- * The token is resolved claims-first, so translations remain available when
41
- * a `$summary` readback keeps `Coding.display` but omits native coding fields.
40
+ * The token is resolved claims-first from the resource-specific primary
41
+ * coded claim (for example `Observation.code`, `Immunization.vaccine-code`
42
+ * or `Device.type`). Other coded claims on the resource are never guessed.
43
+ * This keeps translations available when a `$summary` readback retains
44
+ * `Coding.display` but omits native coding identity fields.
42
45
  *
43
46
  * Return `undefined` when the product has no translation so the shared
44
47
  * reader can fall back to the international `Coding.display`.
48
+ * `createClinicalCodeTranslator(new LocalTerminologyProvider(catalogs))`
49
+ * provides the synchronous MVP/offline implementation. Remote terminology
50
+ * results must be fetched and cached before this render callback runs.
45
51
  */
46
52
  translateCode?: (input: ClinicalTerminologyTranslationInput) => string | undefined;
47
53
  }>;
@@ -3,10 +3,17 @@ import { ResourceTypesFhirR4 } from '../constants/fhir-resource-types.js';
3
3
  import { ClaimConsent } from '../models/consent-rule.js';
4
4
  import { AllergyIntoleranceClaim, AllergyIntoleranceClaimsFhirApi, } from '../models/interoperable-claims/allergy-intolerance-claims.js';
5
5
  import { CommunicationClaim } from '../models/interoperable-claims/communication-claims.js';
6
+ import { CarePlanClaim } from '../models/interoperable-claims/care-plan-claims.js';
6
7
  import { ConditionClaim, ConditionClaimsFhirApi, } from '../models/interoperable-claims/condition-claims.js';
8
+ import { DeviceClaim } from '../models/interoperable-claims/device-claims.js';
9
+ import { DiagnosticReportClaim } from '../models/interoperable-claims/diagnostic-report-claims.js';
10
+ import { DocumentReferenceClaim } from '../models/interoperable-claims/document-reference-claims.js';
11
+ import { FlagClaim } from '../models/interoperable-claims/flag-claims.js';
7
12
  import { ImmunizationClaim } from '../models/interoperable-claims/immunization-claims.js';
8
13
  import { MedicationStatementClaim, MedicationStatementClaimsFhirApi, MedicationStatementClaimsFhirApiExtended, } from '../models/interoperable-claims/medication-statement-claims.js';
9
14
  import { ObservationClaim } from '../models/interoperable-claims/observation-claims.js';
15
+ import { PractitionerRoleClaim } from '../models/interoperable-claims/practitioner-role-claims.js';
16
+ import { ProcedureClaim } from '../models/interoperable-claims/procedure-claims.js';
10
17
  const CONSENT_ACTOR_REFERENCE_CLAIM = 'Consent.actor-reference';
11
18
  const GENERIC_CREATOR_CLAIM_SUFFIX = '.creator';
12
19
  const GENERIC_PERFORMER_CLAIM_SUFFIX = '.performer';
@@ -615,31 +622,40 @@ function findFirstClaimBySuffixes(claims, suffixes) {
615
622
  return firstDefinedText(suffixes.map((suffix) => findBySuffix(claims, suffix)));
616
623
  }
617
624
  function resolvePrimaryClaimCodeToken(resourceType, claims) {
618
- const suffixes = resourceType === ResourceTypesFhirR4.Immunization
619
- || resourceType === ResourceTypesFhirR4.ImmunizationRecommendation
620
- ? ['vaccine-code', 'code']
621
- : resourceType === ResourceTypesFhirR4.Device
622
- || resourceType === ResourceTypesFhirR4.DocumentReference
623
- || resourceType === ResourceTypesFhirR4.Specimen
624
- ? ['type', 'code']
625
- : resourceType === ResourceTypesFhirR4.ImagingStudy
626
- ? ['procedure-code', 'code']
627
- : resourceType === ResourceTypesFhirR4.CarePlan
628
- ? ['category', 'code']
629
- : ['code'];
630
- const normalizedResourceType = resourceType.toLowerCase();
631
- for (const suffix of suffixes) {
632
- const token = findBySuffix(claims, `${normalizedResourceType}.${suffix}`);
633
- const firstToken = splitCsv(token)[0];
634
- if (firstToken)
635
- return firstToken;
636
- }
637
- const system = findBySuffix(claims, `${normalizedResourceType}.code-system`);
638
- const code = findBySuffix(claims, `${normalizedResourceType}.code-value`);
639
- if (code)
640
- return system ? `${system}|${code}` : code;
625
+ const claimKey = resourceType === ResourceTypesFhirR4.Observation
626
+ ? ObservationClaim.Code
627
+ : resourceType === ResourceTypesFhirR4.Flag
628
+ ? FlagClaim.Code
629
+ : resourceType === ResourceTypesFhirR4.Immunization
630
+ ? ImmunizationClaim.VaccineCode
631
+ : resourceType === ResourceTypesFhirR4.Procedure
632
+ ? ProcedureClaim.Code
633
+ : resourceType === ResourceTypesFhirR4.DiagnosticReport
634
+ ? DiagnosticReportClaim.Code
635
+ : resourceType === ResourceTypesFhirR4.Device
636
+ ? DeviceClaim.Type
637
+ : resourceType === ResourceTypesFhirR4.DocumentReference
638
+ ? DocumentReferenceClaim.Type
639
+ : resourceType === ResourceTypesFhirR4.CarePlan
640
+ ? CarePlanClaim.Category
641
+ : resourceType === ResourceTypesFhirR4.PractitionerRole
642
+ ? PractitionerRoleClaim.Code
643
+ : undefined;
644
+ const token = claimKey ? readCanonicalClaimValue(claims, claimKey) : undefined;
645
+ if (token)
646
+ return splitCsv(token)[0];
647
+ if (resourceType === ResourceTypesFhirR4.Observation) {
648
+ const system = readCanonicalClaimValue(claims, ObservationClaim.CodeSystem);
649
+ const code = readCanonicalClaimValue(claims, ObservationClaim.CodeValue);
650
+ if (code)
651
+ return system ? `${system}|${code}` : code;
652
+ }
641
653
  return undefined;
642
654
  }
655
+ function readCanonicalClaimValue(claims, claimKey) {
656
+ return trimValue(claims[claimKey])
657
+ || findBySuffix(claims, `.${claimKey.toLowerCase()}`);
658
+ }
643
659
  function resolveResourceCodeText(resource) {
644
660
  return trimValue(asRecord(resource?.code).text) || undefined;
645
661
  }
@@ -81,6 +81,7 @@ export * from './jwt-signer';
81
81
  export * from './jwk-thumbprint';
82
82
  export * from './legal-organization-onboarding';
83
83
  export * from './legal-organization-verification-transaction';
84
+ export * from './local-terminology-provider';
84
85
  export * from './license';
85
86
  export * from './license-commercial-search';
86
87
  export * from './license-list-search';
@@ -81,6 +81,7 @@ export * from './jwt-signer.js';
81
81
  export * from './jwk-thumbprint.js';
82
82
  export * from './legal-organization-onboarding.js';
83
83
  export * from './legal-organization-verification-transaction.js';
84
+ export * from './local-terminology-provider.js';
84
85
  export * from './license.js';
85
86
  export * from './license-commercial-search.js';
86
87
  export * from './license-list-search.js';
@@ -0,0 +1,27 @@
1
+ import type { TerminologyCatalogDocument, TerminologyLookupInput, TerminologyProvider, TerminologySearchInput, TerminologySearchResult } from '../models/terminology.js';
2
+ import type { ClinicalTerminologyTranslationInput } from './clinical-resource-view.js';
3
+ /**
4
+ * In-memory terminology provider compatible with the historic JSON
5
+ * catalogs. It performs no network requests and keeps catalog loading under
6
+ * application control.
7
+ */
8
+ export declare class LocalTerminologyProvider implements TerminologyProvider {
9
+ private readonly terms;
10
+ private readonly byIdentity;
11
+ private readonly byScopedIdentity;
12
+ constructor(catalogs: readonly TerminologyCatalogDocument[]);
13
+ /** Returns the best local label for one canonical coding identity. */
14
+ lookup(input: TerminologyLookupInput): string | undefined;
15
+ /**
16
+ * Searches local labels and codes, optionally constrained to coding systems.
17
+ * Results are deterministic and contain at most 100 entries.
18
+ */
19
+ search(input: TerminologySearchInput): readonly TerminologySearchResult[];
20
+ private identityKey;
21
+ private scopedIdentityKey;
22
+ }
23
+ /**
24
+ * Adapts a synchronous local terminology provider to
25
+ * `ClinicalResourceDisplayOptions.translateCode`.
26
+ */
27
+ export declare function createClinicalCodeTranslator(provider: Pick<TerminologyProvider, 'lookup'>): (input: ClinicalTerminologyTranslationInput) => string | undefined;
@@ -0,0 +1,154 @@
1
+ const SNOMED_SYSTEM = 'http://snomed.info/sct';
2
+ const DEFAULT_LANGUAGE = 'en';
3
+ const DEFAULT_LIMIT = 20;
4
+ const MAX_LIMIT = 100;
5
+ function normalizedString(value) {
6
+ return typeof value === 'string' ? value.trim() : '';
7
+ }
8
+ function normalizeSystem(value) {
9
+ const system = normalizedString(value);
10
+ return system.toLowerCase() === 'ips' ? SNOMED_SYSTEM : system;
11
+ }
12
+ function normalizeLanguage(value) {
13
+ return normalizedString(value).replace(/_/g, '-').toLowerCase();
14
+ }
15
+ function normalizeJurisdiction(value) {
16
+ return normalizedString(value).toUpperCase();
17
+ }
18
+ function languageCandidates(language) {
19
+ const normalized = normalizeLanguage(language);
20
+ const base = normalized.split('-')[0];
21
+ return [...new Set([normalized, base, DEFAULT_LANGUAGE].filter(Boolean))];
22
+ }
23
+ function searchableText(value) {
24
+ return value
25
+ .normalize('NFD')
26
+ .replace(/[\u0300-\u036f]/g, '')
27
+ .toLocaleLowerCase();
28
+ }
29
+ function normalizedLimit(value) {
30
+ if (!Number.isFinite(value))
31
+ return DEFAULT_LIMIT;
32
+ return Math.max(1, Math.min(MAX_LIMIT, Math.trunc(value)));
33
+ }
34
+ /**
35
+ * In-memory terminology provider compatible with the historic JSON
36
+ * catalogs. It performs no network requests and keeps catalog loading under
37
+ * application control.
38
+ */
39
+ export class LocalTerminologyProvider {
40
+ terms;
41
+ byIdentity;
42
+ byScopedIdentity;
43
+ constructor(catalogs) {
44
+ const terms = [];
45
+ const byIdentity = new Map();
46
+ const byScopedIdentity = new Map();
47
+ for (const catalog of catalogs) {
48
+ const catalogLanguage = normalizeLanguage(catalog.language) || DEFAULT_LANGUAGE;
49
+ const jurisdiction = normalizeJurisdiction(catalog.jurisdiction) || undefined;
50
+ for (const resource of catalog.data || []) {
51
+ const system = normalizeSystem(resource.id || catalog.system);
52
+ const language = normalizeLanguage(resource.language) || catalogLanguage;
53
+ if (!system || !language || !resource.attributes)
54
+ continue;
55
+ for (const [rawCode, rawDisplay] of Object.entries(resource.attributes)) {
56
+ const code = normalizedString(rawCode);
57
+ const display = normalizedString(rawDisplay);
58
+ if (!code || !display)
59
+ continue;
60
+ const term = { system, code, display, language, jurisdiction };
61
+ terms.push(term);
62
+ byIdentity.set(this.identityKey(language, system, code), display);
63
+ byScopedIdentity.set(this.scopedIdentityKey(language, jurisdiction, system, code), display);
64
+ }
65
+ }
66
+ }
67
+ this.terms = terms;
68
+ this.byIdentity = byIdentity;
69
+ this.byScopedIdentity = byScopedIdentity;
70
+ }
71
+ /** Returns the best local label for one canonical coding identity. */
72
+ lookup(input) {
73
+ const system = normalizeSystem(input.system);
74
+ const code = normalizedString(input.code);
75
+ const jurisdiction = normalizeJurisdiction(input.jurisdiction) || undefined;
76
+ if (!system || !code)
77
+ return undefined;
78
+ for (const language of languageCandidates(input.language)) {
79
+ if (jurisdiction) {
80
+ const scopedDisplay = this.byScopedIdentity.get(this.scopedIdentityKey(language, jurisdiction, system, code)) || this.byScopedIdentity.get(this.scopedIdentityKey(language, undefined, system, code));
81
+ if (scopedDisplay)
82
+ return scopedDisplay;
83
+ continue;
84
+ }
85
+ const display = this.byIdentity.get(this.identityKey(language, system, code));
86
+ if (display)
87
+ return display;
88
+ }
89
+ return undefined;
90
+ }
91
+ /**
92
+ * Searches local labels and codes, optionally constrained to coding systems.
93
+ * Results are deterministic and contain at most 100 entries.
94
+ */
95
+ search(input) {
96
+ const query = searchableText(normalizedString(input.text));
97
+ if (!query)
98
+ return [];
99
+ const allowedSystems = new Set((input.systems || []).map(normalizeSystem).filter(Boolean));
100
+ const jurisdiction = normalizeJurisdiction(input.jurisdiction);
101
+ const languages = languageCandidates(input.language);
102
+ const languageRank = new Map(languages.map((language, index) => [language, index]));
103
+ const selected = new Map();
104
+ for (const term of this.terms) {
105
+ if (allowedSystems.size > 0 && !allowedSystems.has(term.system))
106
+ continue;
107
+ if (jurisdiction && term.jurisdiction && term.jurisdiction !== jurisdiction)
108
+ continue;
109
+ const rank = languageRank.get(term.language);
110
+ if (rank === undefined)
111
+ continue;
112
+ if (!searchableText(term.code).includes(query)
113
+ && !searchableText(term.display).includes(query)) {
114
+ continue;
115
+ }
116
+ const identity = `${term.system}\u0000${term.code}`;
117
+ const current = selected.get(identity);
118
+ if (!current || rank < current.rank)
119
+ selected.set(identity, { term, rank });
120
+ }
121
+ return [...selected.values()]
122
+ .sort((left, right) => (left.rank - right.rank
123
+ || left.term.display.localeCompare(right.term.display)
124
+ || left.term.code.localeCompare(right.term.code)))
125
+ .slice(0, normalizedLimit(input.limit))
126
+ .map(({ term }) => ({
127
+ system: term.system,
128
+ code: term.code,
129
+ display: term.display,
130
+ language: term.language,
131
+ }));
132
+ }
133
+ identityKey(language, system, code) {
134
+ return `${language}\u0000${system}\u0000${code}`;
135
+ }
136
+ scopedIdentityKey(language, jurisdiction, system, code) {
137
+ return `${language}\u0000${jurisdiction || ''}\u0000${system}\u0000${code}`;
138
+ }
139
+ }
140
+ /**
141
+ * Adapts a synchronous local terminology provider to
142
+ * `ClinicalResourceDisplayOptions.translateCode`.
143
+ */
144
+ export function createClinicalCodeTranslator(provider) {
145
+ return (input) => {
146
+ if (!input.system)
147
+ return undefined;
148
+ return provider.lookup({
149
+ system: input.system,
150
+ code: input.code,
151
+ language: input.locale,
152
+ });
153
+ };
154
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdc-common-utils-ts",
3
- "version": "2.3.16",
3
+ "version": "2.3.18",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },