gdc-common-utils-ts 2.3.17 → 2.3.19

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.
@@ -38,6 +38,8 @@ export declare enum ClaimsSoftwareApplicationSchemaorg {
38
38
  * based on Schema.org vocabulary.
39
39
  */
40
40
  export declare enum ClaimsOrganizationSchemaorg {
41
+ /** Schema.org type discriminator for the indexed organization/member profile. */
42
+ additionalType = "org.schema.Organization.additionalType",
41
43
  /** Public aliases used for exact organization or individual resolution. */
42
44
  sameAs = "org.schema.Organization.sameAs",
43
45
  /** ISO 3166-1 alpha-2 (two-letter country code). The jurisdiction could be the country or the region (county, province or state) */
@@ -88,6 +90,16 @@ export declare enum ClaimsOrganizationSchemaorg {
88
90
  ownerIdentifierValue = "org.schema.Organization.owner.identifier.value",
89
91
  /** Individual/family indexed member friendly name used by onboarding draft helpers. */
90
92
  memberAlternateName = "org.schema.Organization.member.alternateName",
93
+ /** Public display name of the indexed member. */
94
+ memberName = "org.schema.Organization.member.name",
95
+ /**
96
+ * Semantic type of the indexed member.
97
+ *
98
+ * Animal onboarding uses the canonical NCBI Taxonomy OBO URI here. This
99
+ * distinguishes an animal without inferring species from a FHIR resource
100
+ * name or from an application-local flag.
101
+ */
102
+ memberAdditionalType = "org.schema.Organization.member.additionalType",
91
103
  memberGivenName = "org.schema.Organization.member.givenName",
92
104
  memberFamilyName = "org.schema.Organization.member.familyName",
93
105
  memberBirthDate = "org.schema.Organization.member.birthDate",
@@ -42,6 +42,8 @@ export var ClaimsSoftwareApplicationSchemaorg;
42
42
  */
43
43
  export var ClaimsOrganizationSchemaorg;
44
44
  (function (ClaimsOrganizationSchemaorg) {
45
+ /** Schema.org type discriminator for the indexed organization/member profile. */
46
+ ClaimsOrganizationSchemaorg["additionalType"] = "org.schema.Organization.additionalType";
45
47
  /** Public aliases used for exact organization or individual resolution. */
46
48
  ClaimsOrganizationSchemaorg["sameAs"] = "org.schema.Organization.sameAs";
47
49
  /** ISO 3166-1 alpha-2 (two-letter country code). The jurisdiction could be the country or the region (county, province or state) */
@@ -92,6 +94,16 @@ export var ClaimsOrganizationSchemaorg;
92
94
  ClaimsOrganizationSchemaorg["ownerIdentifierValue"] = "org.schema.Organization.owner.identifier.value";
93
95
  /** Individual/family indexed member friendly name used by onboarding draft helpers. */
94
96
  ClaimsOrganizationSchemaorg["memberAlternateName"] = "org.schema.Organization.member.alternateName";
97
+ /** Public display name of the indexed member. */
98
+ ClaimsOrganizationSchemaorg["memberName"] = "org.schema.Organization.member.name";
99
+ /**
100
+ * Semantic type of the indexed member.
101
+ *
102
+ * Animal onboarding uses the canonical NCBI Taxonomy OBO URI here. This
103
+ * distinguishes an animal without inferring species from a FHIR resource
104
+ * name or from an application-local flag.
105
+ */
106
+ ClaimsOrganizationSchemaorg["memberAdditionalType"] = "org.schema.Organization.member.additionalType";
95
107
  ClaimsOrganizationSchemaorg["memberGivenName"] = "org.schema.Organization.member.givenName";
96
108
  ClaimsOrganizationSchemaorg["memberFamilyName"] = "org.schema.Organization.member.familyName";
97
109
  ClaimsOrganizationSchemaorg["memberBirthDate"] = "org.schema.Organization.member.birthDate";
@@ -0,0 +1,42 @@
1
+ export declare const AnimalSubjectKinds: Readonly<{
2
+ readonly Animal: "animal";
3
+ }>;
4
+ export declare const NcbiTaxonomy: Readonly<{
5
+ readonly Dog: "9615";
6
+ readonly Cat: "9685";
7
+ readonly Horse: "9796";
8
+ }>;
9
+ export type NcbiTaxonomyId = string;
10
+ export type AnimalOnboardingInput = Readonly<{
11
+ subjectId: string;
12
+ cardDidWeb: string;
13
+ alternateName: string;
14
+ legalName?: string;
15
+ birthDate?: string;
16
+ birthYear?: number;
17
+ gender?: 'female' | 'male' | 'other' | 'unknown';
18
+ ncbiTaxonomyId: NcbiTaxonomyId;
19
+ controllerEmail?: string;
20
+ controllerTelephone?: string;
21
+ sector?: string;
22
+ }>;
23
+ export type AnimalOnboardingClaims = Readonly<Record<string, string>>;
24
+ /**
25
+ * Builds the canonical OBO URI for one NCBI Taxonomy numeric identifier.
26
+ *
27
+ * The URI is public taxonomy metadata, not an animal identifier. Callers must
28
+ * keep microchip, registry and controller identifiers in their separately
29
+ * typed confidential/index contracts.
30
+ */
31
+ export declare function buildNcbiTaxonomyUri(id: NcbiTaxonomyId): string;
32
+ /**
33
+ * Projects one controller-authorized animal card request to the existing
34
+ * schema.org individual-organization claim envelope.
35
+ *
36
+ * The indexed subject is always an animal and its human actor is always a
37
+ * responsible controller. Login identity proves only the application session;
38
+ * GW must still verify the enrollment grant before accepting the request. A
39
+ * card must not be shown as active until the authoritative GW transaction
40
+ * returns success.
41
+ */
42
+ export declare function buildAnimalOnboardingClaims(input: AnimalOnboardingInput): AnimalOnboardingClaims;
@@ -0,0 +1,65 @@
1
+ import { ClaimsOrganizationSchemaorg, ClaimsServiceSchemaorg } from '../constants/schemaorg.js';
2
+ import { DataspaceSectors } from '../constants/sectors.js';
3
+ export const AnimalSubjectKinds = Object.freeze({
4
+ Animal: 'animal',
5
+ });
6
+ export const NcbiTaxonomy = Object.freeze({
7
+ Dog: '9615',
8
+ Cat: '9685',
9
+ Horse: '9796',
10
+ });
11
+ /**
12
+ * Builds the canonical OBO URI for one NCBI Taxonomy numeric identifier.
13
+ *
14
+ * The URI is public taxonomy metadata, not an animal identifier. Callers must
15
+ * keep microchip, registry and controller identifiers in their separately
16
+ * typed confidential/index contracts.
17
+ */
18
+ export function buildNcbiTaxonomyUri(id) {
19
+ const normalized = id.trim();
20
+ if (!/^[1-9]\d*$/.test(normalized)) {
21
+ throw new TypeError('ncbiTaxonomyId must be a positive numeric NCBI Taxonomy identifier.');
22
+ }
23
+ if (normalized === '9606') {
24
+ throw new TypeError('Human NCBI Taxonomy 9606 is not valid for animal onboarding.');
25
+ }
26
+ return `http://purl.obolibrary.org/obo/NCBITaxon_${normalized}`;
27
+ }
28
+ /**
29
+ * Projects one controller-authorized animal card request to the existing
30
+ * schema.org individual-organization claim envelope.
31
+ *
32
+ * The indexed subject is always an animal and its human actor is always a
33
+ * responsible controller. Login identity proves only the application session;
34
+ * GW must still verify the enrollment grant before accepting the request. A
35
+ * card must not be shown as active until the authoritative GW transaction
36
+ * returns success.
37
+ */
38
+ export function buildAnimalOnboardingClaims(input) {
39
+ const subjectId = input.subjectId.trim();
40
+ const cardDidWeb = input.cardDidWeb.trim();
41
+ const alternateName = input.alternateName.trim();
42
+ if (!subjectId || !cardDidWeb || !alternateName) {
43
+ throw new TypeError('subjectId, cardDidWeb and alternateName are required.');
44
+ }
45
+ const birthDate = input.birthDate?.trim()
46
+ || (Number.isInteger(input.birthYear) ? String(input.birthYear) : '');
47
+ const legalName = input.legalName?.trim();
48
+ const gender = input.gender && input.gender !== 'unknown' ? input.gender : '';
49
+ return Object.freeze({
50
+ '@context': 'org.schema',
51
+ [ClaimsOrganizationSchemaorg.identifierValue]: subjectId,
52
+ [ClaimsOrganizationSchemaorg.additionalType]: AnimalSubjectKinds.Animal,
53
+ [ClaimsOrganizationSchemaorg.alternateName]: alternateName,
54
+ ...(legalName ? { [ClaimsOrganizationSchemaorg.legalName]: legalName } : {}),
55
+ [ClaimsOrganizationSchemaorg.sameAs]: cardDidWeb,
56
+ [ClaimsOrganizationSchemaorg.memberName]: alternateName,
57
+ [ClaimsOrganizationSchemaorg.memberAdditionalType]: buildNcbiTaxonomyUri(input.ncbiTaxonomyId),
58
+ [ClaimsOrganizationSchemaorg.memberRole]: 'RESPRSN',
59
+ ...(birthDate ? { [ClaimsOrganizationSchemaorg.memberBirthDate]: birthDate } : {}),
60
+ ...(gender ? { [ClaimsOrganizationSchemaorg.memberGender]: gender } : {}),
61
+ [ClaimsOrganizationSchemaorg.ownerEmail]: input.controllerEmail?.trim() || '',
62
+ [ClaimsOrganizationSchemaorg.ownerTelephone]: input.controllerTelephone?.trim() || '',
63
+ [ClaimsServiceSchemaorg.category]: input.sector?.trim() || DataspaceSectors.AnimalCare,
64
+ });
65
+ }
@@ -26,6 +26,7 @@ export * from './interoperable-claims';
26
26
  export * from './indexing';
27
27
  export * from './identity-bootstrap';
28
28
  export * from './individual-onboarding';
29
+ export * from './animal-onboarding';
29
30
  export * from './inter-tenant-access-contract';
30
31
  export * from './issue';
31
32
  export * from './jsonapi';
@@ -49,6 +50,7 @@ export * from './relationship-access';
49
50
  export * from './response';
50
51
  export * from './subject-identifier-ledger';
51
52
  export * from './subject-identity-binding';
53
+ export * from './terminology';
52
54
  export * from './urlPath';
53
55
  export * from './verifiable-credential';
54
56
  export * from './wallet';
@@ -26,6 +26,7 @@ export * from './interoperable-claims.js';
26
26
  export * from './indexing.js';
27
27
  export * from './identity-bootstrap.js';
28
28
  export * from './individual-onboarding.js';
29
+ export * from './animal-onboarding.js';
29
30
  export * from './inter-tenant-access-contract.js';
30
31
  export * from './issue.js';
31
32
  export * from './jsonapi.js';
@@ -49,6 +50,7 @@ export * from './relationship-access.js';
49
50
  export * from './response.js';
50
51
  export * from './subject-identifier-ledger.js';
51
52
  export * from './subject-identity-binding.js';
53
+ export * from './terminology.js';
52
54
  export * from './urlPath.js';
53
55
  export * from './verifiable-credential.js';
54
56
  export * from './wallet.js';
@@ -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 {};
@@ -45,6 +45,9 @@ export type ClinicalResourceDisplayOptions = Readonly<{
45
45
  *
46
46
  * Return `undefined` when the product has no translation so the shared
47
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.
48
51
  */
49
52
  translateCode?: (input: ClinicalTerminologyTranslationInput) => string | undefined;
50
53
  }>;
@@ -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.17",
3
+ "version": "2.3.19",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },