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 +10 -0
- package/dist/clinical-bundle-reader.d.ts +28 -0
- package/dist/clinical-bundle-reader.js +100 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +5 -1
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.
|
|
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"
|