vet-data-utils-ts 0.5.8 → 0.5.9

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
@@ -85,6 +85,16 @@ for immunologicals); ordinary human ATC is not substituted for it. Veterinary
85
85
  allergy product coding may also use ATCvet, while manifestations use an allowed
86
86
  SNOMED source.
87
87
 
88
+ The shared IPS reader boundary is `projectClinicalBundleForReading(...)` from
89
+ `vet-data-utils-ts/clinical-bundle-reader`. It accepts a native FHIR Bundle
90
+ (including R4/R5 resources supported by the common normalizer) or resources
91
+ already carrying canonical `meta.claims`, creates a presentation-only copy,
92
+ and fills the governed primary `*-text`/`*-display` claims through an injected
93
+ exact terminology resolver. The resolver is called with `system`, `code` and
94
+ locale; the original verified Bundle is never mutated. Local UI labels render
95
+ as `text (English display)` outside English, English renders only `display`,
96
+ and missing labels fall back to the canonical `system|code` token.
97
+
88
98
  The deferred spreadsheet shape for member and employee role assignments is
89
99
  documented in [`docs/member-import-contract.md`](docs/member-import-contract.md).
90
100
  It is not an implemented importer API.
@@ -0,0 +1,28 @@
1
+ export type ClinicalTerminologyLookupRequest = Readonly<{
2
+ resourceType: string;
3
+ field: string;
4
+ system: string;
5
+ code: string;
6
+ version?: string;
7
+ language: string;
8
+ }>;
9
+ export type ClinicalTerminologyLabels = Readonly<{
10
+ text?: string;
11
+ display?: string;
12
+ }>;
13
+ export type ClinicalBundleReaderOptions = Readonly<{
14
+ language: string;
15
+ resolveTerminology(request: ClinicalTerminologyLookupRequest): Promise<ClinicalTerminologyLabels>;
16
+ }>;
17
+ /**
18
+ * Creates a presentation-only Bundle for the shared IPS reader. Native FHIR
19
+ * and already-normalized `resource.meta.claims` converge on the same flat
20
+ * claims without changing the verified/signed input.
21
+ */
22
+ export declare function projectClinicalBundleForReading<T extends Record<string, unknown>>(bundle: T, options: ClinicalBundleReaderOptions): Promise<T>;
23
+ export declare function formatLocalizedTerminologyLabel(input: Readonly<{
24
+ code: string;
25
+ text?: string;
26
+ display?: string;
27
+ language: string;
28
+ }>): string;
@@ -0,0 +1,100 @@
1
+ import { normalizeClaimsFromFhirResource } from 'gdc-common-utils-ts/utils/interoperable-resource-operation';
2
+ const PRIMARY_CODE_PROJECTIONS = Object.freeze({
3
+ Condition: { field: 'Condition.code', code: 'Condition.code', text: 'Condition.code-text', display: 'Condition.code-display' },
4
+ Procedure: { field: 'Procedure.code', code: 'Procedure.code', text: 'Procedure.code-text', display: 'Procedure.code-display' },
5
+ DiagnosticReport: { field: 'DiagnosticReport.code', code: 'DiagnosticReport.code', text: 'DiagnosticReport.code-text', display: 'DiagnosticReport.code-display' },
6
+ Observation: { field: 'Observation.code', code: 'Observation.code', text: 'Observation.code-text', display: 'Observation.code-display' },
7
+ AllergyIntolerance: { field: 'AllergyIntolerance.code', code: 'AllergyIntolerance.code', text: 'AllergyIntolerance.code-text', display: 'AllergyIntolerance.code-display' },
8
+ Immunization: { field: 'Immunization.vaccineCode', code: 'Immunization.vaccine-code', text: 'Immunization.vaccine-code-text', display: 'Immunization.vaccine-code-display' },
9
+ });
10
+ /**
11
+ * Creates a presentation-only Bundle for the shared IPS reader. Native FHIR
12
+ * and already-normalized `resource.meta.claims` converge on the same flat
13
+ * claims without changing the verified/signed input.
14
+ */
15
+ export async function projectClinicalBundleForReading(bundle, options) {
16
+ if (bundle.resourceType !== 'Bundle' || !Array.isArray(bundle.entry)) {
17
+ throw new TypeError('clinical_bundle_required');
18
+ }
19
+ const projected = structuredClone(bundle);
20
+ const entries = projected.entry;
21
+ for (const entry of entries) {
22
+ const resource = asRecord(entry.resource);
23
+ if (!resource.resourceType)
24
+ continue;
25
+ entry.resource = await projectResource(resource, options);
26
+ }
27
+ return projected;
28
+ }
29
+ export function formatLocalizedTerminologyLabel(input) {
30
+ const text = clean(input.text);
31
+ const display = clean(input.display);
32
+ const language = input.language.trim().toLowerCase().split(/[-_]/)[0];
33
+ if (language === 'en')
34
+ return display || text || input.code;
35
+ if (text && display && text.localeCompare(display, undefined, { sensitivity: 'accent' }) !== 0) {
36
+ return `${text} (${display})`;
37
+ }
38
+ return text || display || input.code;
39
+ }
40
+ async function projectResource(resource, options) {
41
+ const resourceType = clean(resource.resourceType);
42
+ const existingMeta = asRecord(resource.meta);
43
+ const existingClaims = asRecord(existingMeta.claims);
44
+ const claims = Object.keys(existingClaims).length
45
+ ? { ...existingClaims }
46
+ : normalizeClaimsFromFhirResource(resource, {});
47
+ const projection = PRIMARY_CODE_PROJECTIONS[resourceType];
48
+ if (projection) {
49
+ const token = firstToken(claims[projection.code]);
50
+ const parsed = parseCodingToken(token);
51
+ const needsText = !clean(claims[projection.text]);
52
+ const needsDisplay = !clean(claims[projection.display]);
53
+ if (parsed && (needsText || needsDisplay)) {
54
+ const labels = await options.resolveTerminology({
55
+ resourceType,
56
+ field: projection.field,
57
+ ...parsed,
58
+ language: options.language,
59
+ });
60
+ if (needsText && clean(labels.text))
61
+ claims[projection.text] = clean(labels.text);
62
+ if (needsDisplay && clean(labels.display))
63
+ claims[projection.display] = clean(labels.display);
64
+ }
65
+ }
66
+ return {
67
+ ...resource,
68
+ language: clean(resource.language) || options.language,
69
+ meta: { ...existingMeta, claims },
70
+ };
71
+ }
72
+ function parseCodingToken(value) {
73
+ const separator = value.lastIndexOf('|');
74
+ if (separator <= 0 || separator === value.length - 1)
75
+ return undefined;
76
+ const systemAndVersion = value.slice(0, separator);
77
+ const code = value.slice(separator + 1);
78
+ const versionSeparator = systemAndVersion.indexOf('|');
79
+ if (versionSeparator > 0) {
80
+ return {
81
+ system: systemAndVersion.slice(0, versionSeparator),
82
+ version: systemAndVersion.slice(versionSeparator + 1),
83
+ code,
84
+ };
85
+ }
86
+ return { system: systemAndVersion, code };
87
+ }
88
+ function firstToken(value) {
89
+ if (Array.isArray(value))
90
+ return clean(value[0]);
91
+ return clean(value).split(',')[0]?.trim() ?? '';
92
+ }
93
+ function asRecord(value) {
94
+ return value && typeof value === 'object' && !Array.isArray(value)
95
+ ? value
96
+ : {};
97
+ }
98
+ function clean(value) {
99
+ return typeof value === 'string' ? value.trim() : '';
100
+ }
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from './animal-card.js';
2
2
  export * from './assistant.js';
3
3
  export * from './communication.js';
4
4
  export * from './clinical-terminology.js';
5
+ export * from './clinical-bundle-reader.js';
5
6
  export * from './digital-twin.js';
6
7
  export * from './emergency.js';
7
8
  export * from './financial.js';
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from './animal-card.js';
2
2
  export * from './assistant.js';
3
3
  export * from './communication.js';
4
4
  export * from './clinical-terminology.js';
5
+ export * from './clinical-bundle-reader.js';
5
6
  export * from './digital-twin.js';
6
7
  export * from './emergency.js';
7
8
  export * from './financial.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vet-data-utils-ts",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Browser-safe governed VetChain data contracts",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Connecting Solution & Applications Ltd",
@@ -24,6 +24,10 @@
24
24
  "types": "./dist/clinical-terminology.d.ts",
25
25
  "default": "./dist/clinical-terminology.js"
26
26
  },
27
+ "./clinical-bundle-reader": {
28
+ "types": "./dist/clinical-bundle-reader.d.ts",
29
+ "default": "./dist/clinical-bundle-reader.js"
30
+ },
27
31
  "./digital-twin": {
28
32
  "types": "./dist/digital-twin.d.ts",
29
33
  "default": "./dist/digital-twin.js"