vet-data-utils-ts 0.5.31 → 0.5.36
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 +19 -0
- package/dist/clinical-bundle-reader.js +20 -1
- package/dist/clinical-terminology.d.ts +30 -2
- package/dist/clinical-terminology.js +7 -31
- package/dist/immunization-coding-review.d.ts +51 -0
- package/dist/immunization-coding-review.js +59 -0
- package/dist/immunization-draft.d.ts +43 -0
- package/dist/immunization-draft.js +100 -0
- package/dist/immunization-flat-claims.d.ts +70 -0
- package/dist/immunization-flat-claims.js +313 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/professional-access-policy.d.ts +2 -0
- package/dist/professional-access-policy.js +2 -0
- package/package.json +20 -2
package/README.md
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
# vet-data-utils-ts
|
|
2
|
+
## Blocking local-first SDK release
|
|
3
|
+
|
|
4
|
+
The canonical contract is [`SDK_LAYERING.md`](https://github.com/Fundacion-UNID/sos-data-utils-ts/blob/main/docs/SDK_LAYERING.md). Its
|
|
5
|
+
indivisible order is **local packages -> full local test matrix -> merge every
|
|
6
|
+
validated branch to `main` -> publish bottom-up -> install exact registry
|
|
7
|
+
versions and commit lockfiles on `main` -> staging**. Never publish to discover
|
|
8
|
+
deterministic failures and never commit a local package reference. Staging
|
|
9
|
+
begins only after exact registry packages are installed and verified.
|
|
10
|
+
|
|
2
11
|
|
|
3
12
|
## Lost-animal access and behavior
|
|
4
13
|
|
|
@@ -189,6 +198,16 @@ for immunologicals); ordinary human ATC is not substituted for it. Veterinary
|
|
|
189
198
|
allergy product coding may also use ATCvet, while manifestations use an allowed
|
|
190
199
|
SNOMED source.
|
|
191
200
|
|
|
201
|
+
Terminology-assisted entry uses the neutral coding-review contract from
|
|
202
|
+
`fhir-data-utils-ts/coding-review-flat-claims`. Spoken, typed or imported text
|
|
203
|
+
is `Immunization.vaccine-code-text`; an unconfirmed candidate stays in
|
|
204
|
+
`meta.codingProposals[]` beside the Immunization's `meta.claims`, targets
|
|
205
|
+
`Immunization.vaccine-code`, and is never copied to `ResearchSubject.meta`.
|
|
206
|
+
Confirmation adds the selected `system|code` and official `-display` without
|
|
207
|
+
overwriting local `-text`. Vet policy supplies ATCvet/species constraints; it
|
|
208
|
+
does not redefine the shared shape. Consumers reuse exported fixtures and
|
|
209
|
+
builders rather than repeating governed literals.
|
|
210
|
+
|
|
192
211
|
`vet-data-utils-ts/veterinary-biologic-product` keeps three independent axes:
|
|
193
212
|
|
|
194
213
|
- WHO ATCvet QI is the international medicine classification;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeClaimsFromFhirResource } from 'gdc-common-utils-ts/utils/interoperable-resource-operation';
|
|
2
|
+
import { normalizeFhirApiClaimKey } from 'gdc-common-utils-ts/utils/fhir-api-claim-helpers';
|
|
2
3
|
const PRIMARY_CODE_PROJECTIONS = Object.freeze({
|
|
3
4
|
Condition: { field: 'Condition.code', code: 'Condition.code', text: 'Condition.code-text', display: 'Condition.code-display' },
|
|
4
5
|
Procedure: { field: 'Procedure.code', code: 'Procedure.code', text: 'Procedure.code-text', display: 'Procedure.code-display' },
|
|
@@ -42,7 +43,7 @@ async function projectResource(resource, options) {
|
|
|
42
43
|
const existingMeta = asRecord(resource.meta);
|
|
43
44
|
const existingClaims = asRecord(existingMeta.claims);
|
|
44
45
|
const claims = Object.keys(existingClaims).length
|
|
45
|
-
?
|
|
46
|
+
? presentationClaims(existingClaims)
|
|
46
47
|
: normalizeClaimsFromFhirResource(resource, {});
|
|
47
48
|
const projection = PRIMARY_CODE_PROJECTIONS[resourceType];
|
|
48
49
|
if (projection) {
|
|
@@ -69,6 +70,24 @@ async function projectResource(resource, options) {
|
|
|
69
70
|
meta: { ...existingMeta, claims },
|
|
70
71
|
};
|
|
71
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Keeps structural FHIR metadata on the resource while preventing derived
|
|
75
|
+
* paths such as `Immunization.meta.versionId` from becoming editable/search
|
|
76
|
+
* claims in the presentation copy.
|
|
77
|
+
*/
|
|
78
|
+
function presentationClaims(source) {
|
|
79
|
+
return Object.fromEntries(Object.entries(source).filter(([claim]) => {
|
|
80
|
+
if (claim.startsWith('@'))
|
|
81
|
+
return true;
|
|
82
|
+
try {
|
|
83
|
+
normalizeFhirApiClaimKey(claim);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
72
91
|
function parseCodingToken(value) {
|
|
73
92
|
const separator = value.lastIndexOf('|');
|
|
74
93
|
if (separator <= 0 || separator === value.length - 1)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
/** @deprecated Import `ClinicalTerminologySystems` from sos-data-utils-ts. */
|
|
1
2
|
export declare const VeterinaryClinicalTerminologySystems: Readonly<{
|
|
2
3
|
readonly Loinc: "http://loinc.org";
|
|
3
4
|
readonly SnomedCt: "http://snomed.info/sct";
|
|
4
5
|
readonly Icd10: "http://hl7.org/fhir/sid/icd-10";
|
|
5
|
-
|
|
6
|
+
readonly Atc: "http://www.whocc.no/atc";
|
|
6
7
|
readonly AtcVet: "http://www.whocc.no/atcvet";
|
|
7
8
|
}>;
|
|
8
9
|
/**
|
|
@@ -11,15 +12,19 @@ export declare const VeterinaryClinicalTerminologySystems: Readonly<{
|
|
|
11
12
|
* canonical SNOMED system URI.
|
|
12
13
|
* https://www.snomed.org/gps
|
|
13
14
|
*/
|
|
15
|
+
/** @deprecated Import `ClinicalTerminologySourceScopes` from sos-data-utils-ts. */
|
|
14
16
|
export declare const VeterinaryTerminologySourceScopes: Readonly<{
|
|
15
17
|
readonly Loinc: "LNC";
|
|
16
18
|
readonly SnomedGps: "SNOMED_GPS";
|
|
19
|
+
readonly SnomedIps: "SNOMED_IPS";
|
|
17
20
|
readonly SnomedCt: "SNOMED_CT";
|
|
18
21
|
readonly SnomedVeterinary: "SNOMEDCT_VET";
|
|
19
22
|
readonly Icd10: "ICD10";
|
|
23
|
+
readonly Atc: "ATC";
|
|
20
24
|
readonly AtcVet: "ATCVET";
|
|
21
25
|
}>;
|
|
22
26
|
/** Deterministic standards-based data for the isolated local terminology upstream. */
|
|
27
|
+
/** @deprecated Import `CLINICAL_TERMINOLOGY_TEST_DATA` from sos-data-utils-ts. */
|
|
23
28
|
export declare const VET_TERMINOLOGY_TEST_DATA: Readonly<{
|
|
24
29
|
readonly localApiKey: "local-playwright-umls-key";
|
|
25
30
|
readonly bodyTemperature: Readonly<{
|
|
@@ -35,8 +40,31 @@ export declare const VET_TERMINOLOGY_TEST_DATA: Readonly<{
|
|
|
35
40
|
code: "3135009";
|
|
36
41
|
display: "otitis externa";
|
|
37
42
|
}>;
|
|
43
|
+
readonly canineRabiesVaccine: Readonly<{
|
|
44
|
+
scope: "ATCVET";
|
|
45
|
+
system: "http://www.whocc.no/atcvet";
|
|
46
|
+
code: "QI07AA02";
|
|
47
|
+
text: "virus de la rabia";
|
|
48
|
+
display: "rabies virus";
|
|
49
|
+
ncbiTaxonomyId: "9615";
|
|
50
|
+
}>;
|
|
51
|
+
readonly humanInactivatedInfluenzaVaccine: Readonly<{
|
|
52
|
+
scope: "ATC";
|
|
53
|
+
system: "http://www.whocc.no/atc";
|
|
54
|
+
code: "J07BB02";
|
|
55
|
+
text: "vacuna contra la gripe inactivada de virus fraccionados o ant\u00EDgeno de superficie";
|
|
56
|
+
display: "influenza, inactivated, split virus or surface antigen";
|
|
57
|
+
ncbiTaxonomyId: "9606";
|
|
58
|
+
}>;
|
|
59
|
+
readonly rabiesDisease: Readonly<{
|
|
60
|
+
scope: "SNOMED_GPS";
|
|
61
|
+
umlsSource: "SNOMEDCT_US";
|
|
62
|
+
system: "http://snomed.info/sct";
|
|
63
|
+
code: "14168008";
|
|
64
|
+
display: "Rabies";
|
|
65
|
+
}>;
|
|
38
66
|
}>;
|
|
39
|
-
export type VeterinaryTerminologySourceScope = typeof VeterinaryTerminologySourceScopes[keyof typeof VeterinaryTerminologySourceScopes]
|
|
67
|
+
export type VeterinaryTerminologySourceScope = Exclude<typeof VeterinaryTerminologySourceScopes[keyof typeof VeterinaryTerminologySourceScopes], 'ATC' | 'SNOMED_IPS'>;
|
|
40
68
|
export type VeterinaryTerminologyCapability = Readonly<{
|
|
41
69
|
type: 'terminology-capability';
|
|
42
70
|
id: string;
|
|
@@ -1,41 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
Icd10: 'http://hl7.org/fhir/sid/icd-10',
|
|
5
|
-
/** WHO Collaborating Centre ATCvet classification, distinct from human ATC. */
|
|
6
|
-
AtcVet: 'http://www.whocc.no/atcvet',
|
|
7
|
-
});
|
|
1
|
+
import { CLINICAL_TERMINOLOGY_TEST_DATA, ClinicalTerminologySourceScopes, ClinicalTerminologySystems, } from 'sos-data-utils-ts/clinical-terminology';
|
|
2
|
+
/** @deprecated Import `ClinicalTerminologySystems` from sos-data-utils-ts. */
|
|
3
|
+
export const VeterinaryClinicalTerminologySystems = ClinicalTerminologySystems;
|
|
8
4
|
/**
|
|
9
5
|
* Source scopes are authorization/content boundaries, not Coding.system URIs.
|
|
10
6
|
* GPS, full SNOMED CT and the veterinary extension intentionally share the
|
|
11
7
|
* canonical SNOMED system URI.
|
|
12
8
|
* https://www.snomed.org/gps
|
|
13
9
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
SnomedGps: 'SNOMED_GPS',
|
|
17
|
-
SnomedCt: 'SNOMED_CT',
|
|
18
|
-
SnomedVeterinary: 'SNOMEDCT_VET',
|
|
19
|
-
Icd10: 'ICD10',
|
|
20
|
-
AtcVet: 'ATCVET',
|
|
21
|
-
});
|
|
10
|
+
/** @deprecated Import `ClinicalTerminologySourceScopes` from sos-data-utils-ts. */
|
|
11
|
+
export const VeterinaryTerminologySourceScopes = ClinicalTerminologySourceScopes;
|
|
22
12
|
/** Deterministic standards-based data for the isolated local terminology upstream. */
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
bodyTemperature: Object.freeze({
|
|
26
|
-
source: VeterinaryTerminologySourceScopes.Loinc,
|
|
27
|
-
system: VeterinaryClinicalTerminologySystems.Loinc,
|
|
28
|
-
code: '8310-5',
|
|
29
|
-
display: 'Body temperature:Temp:Pt:^Patient:Qn',
|
|
30
|
-
}),
|
|
31
|
-
otitisExternaSpanish: Object.freeze({
|
|
32
|
-
scope: VeterinaryTerminologySourceScopes.SnomedCt,
|
|
33
|
-
umlsSource: 'SCTSPA',
|
|
34
|
-
system: VeterinaryClinicalTerminologySystems.SnomedCt,
|
|
35
|
-
code: '3135009',
|
|
36
|
-
display: 'otitis externa',
|
|
37
|
-
}),
|
|
38
|
-
});
|
|
13
|
+
/** @deprecated Import `CLINICAL_TERMINOLOGY_TEST_DATA` from sos-data-utils-ts. */
|
|
14
|
+
export const VET_TERMINOLOGY_TEST_DATA = CLINICAL_TERMINOLOGY_TEST_DATA;
|
|
39
15
|
/**
|
|
40
16
|
* ISO 3166-1 alpha-2 territories enumerated by the official SNOMED
|
|
41
17
|
* International member page on 2026-09-05.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type FlatClaimCodingCandidate, type FlatClaimCodingReviewResource } from 'fhir-data-utils-ts/coding-review-flat-claims';
|
|
2
|
+
/** Governed claim coordinates reused by Vet gateways, portals and assistants. */
|
|
3
|
+
export declare const VeterinaryImmunizationCodingReviewClaims: Readonly<{
|
|
4
|
+
code: "Immunization.vaccine-code";
|
|
5
|
+
display: string;
|
|
6
|
+
localText: string;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Builds an unconfirmed veterinary Immunization coding review from text spoken,
|
|
10
|
+
* typed or imported by a channel. The neutral helper owns proposal placement;
|
|
11
|
+
* this wrapper contributes veterinary resource coordinates and ATCvet policy.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildVeterinaryImmunizationCodingReview(input: Readonly<{
|
|
14
|
+
entryId: string;
|
|
15
|
+
animalReference: string;
|
|
16
|
+
administrationDate: string;
|
|
17
|
+
localText: string;
|
|
18
|
+
language: string;
|
|
19
|
+
proposalId: string;
|
|
20
|
+
candidates: readonly FlatClaimCodingCandidate[];
|
|
21
|
+
}>): FlatClaimCodingReviewResource;
|
|
22
|
+
/** Package-owned synthetic example for assistants, portals and downstream tests. */
|
|
23
|
+
export declare const VETERINARY_IMMUNIZATION_CODING_REVIEW_EXAMPLE: Readonly<{
|
|
24
|
+
input: Readonly<{
|
|
25
|
+
entryId: "00000000-0000-4000-8000-000000000201";
|
|
26
|
+
animalReference: "Patient/00000000-0000-4000-8000-000000000202";
|
|
27
|
+
administrationDate: "2024-05-23";
|
|
28
|
+
localText: "vacuna contra la rabia";
|
|
29
|
+
language: "es-ES";
|
|
30
|
+
proposalId: "veterinary-immunization-proposal-example";
|
|
31
|
+
candidates: readonly Readonly<{
|
|
32
|
+
id: "veterinary-immunization-candidate-example";
|
|
33
|
+
system: "http://www.whocc.no/atcvet";
|
|
34
|
+
code: "QI07AA02";
|
|
35
|
+
display: "Inactivated rabies virus vaccines";
|
|
36
|
+
}>[];
|
|
37
|
+
}>;
|
|
38
|
+
claims: Readonly<{
|
|
39
|
+
code: "Immunization.vaccine-code";
|
|
40
|
+
display: string;
|
|
41
|
+
localText: string;
|
|
42
|
+
}>;
|
|
43
|
+
expected: Readonly<{
|
|
44
|
+
resourceType: string;
|
|
45
|
+
id: string;
|
|
46
|
+
meta: Readonly<{
|
|
47
|
+
claims: Readonly<Record<string, string>>;
|
|
48
|
+
codingProposals?: readonly import("fhir-data-utils-ts/coding-review-flat-claims").FlatClaimCodingProposal[];
|
|
49
|
+
}>;
|
|
50
|
+
}>;
|
|
51
|
+
}>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { buildFlatClaimCodingReviewResource, deriveCodingDisplayClaim, deriveCodingLocalTextClaim, } from 'fhir-data-utils-ts/coding-review-flat-claims';
|
|
2
|
+
import { VeterinaryClinicalTerminologySystems } from './clinical-terminology.js';
|
|
3
|
+
const IMMUNIZATION_RESOURCE_TYPE = 'Immunization';
|
|
4
|
+
const IMMUNIZATION_VACCINE_CODE_CLAIM = `${IMMUNIZATION_RESOURCE_TYPE}.vaccine-code`;
|
|
5
|
+
/** Governed claim coordinates reused by Vet gateways, portals and assistants. */
|
|
6
|
+
export const VeterinaryImmunizationCodingReviewClaims = Object.freeze({
|
|
7
|
+
code: IMMUNIZATION_VACCINE_CODE_CLAIM,
|
|
8
|
+
display: deriveCodingDisplayClaim(IMMUNIZATION_VACCINE_CODE_CLAIM),
|
|
9
|
+
localText: deriveCodingLocalTextClaim(IMMUNIZATION_VACCINE_CODE_CLAIM),
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Builds an unconfirmed veterinary Immunization coding review from text spoken,
|
|
13
|
+
* typed or imported by a channel. The neutral helper owns proposal placement;
|
|
14
|
+
* this wrapper contributes veterinary resource coordinates and ATCvet policy.
|
|
15
|
+
*/
|
|
16
|
+
export function buildVeterinaryImmunizationCodingReview(input) {
|
|
17
|
+
const candidates = input.candidates.map((candidate) => {
|
|
18
|
+
if (candidate.system !== VeterinaryClinicalTerminologySystems.AtcVet) {
|
|
19
|
+
throw new TypeError('veterinary_immunization_candidate_system_invalid');
|
|
20
|
+
}
|
|
21
|
+
return candidate;
|
|
22
|
+
});
|
|
23
|
+
return buildFlatClaimCodingReviewResource({
|
|
24
|
+
resourceType: IMMUNIZATION_RESOURCE_TYPE,
|
|
25
|
+
resourceId: input.entryId,
|
|
26
|
+
targetClaim: VeterinaryImmunizationCodingReviewClaims.code,
|
|
27
|
+
localText: input.localText,
|
|
28
|
+
language: input.language,
|
|
29
|
+
baseClaims: {
|
|
30
|
+
'Immunization.status': 'completed',
|
|
31
|
+
'Immunization.patient': input.animalReference,
|
|
32
|
+
'Immunization.date': input.administrationDate,
|
|
33
|
+
},
|
|
34
|
+
proposalId: input.proposalId,
|
|
35
|
+
candidates,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const EXAMPLE_ENTRY_ID = '00000000-0000-4000-8000-000000000201';
|
|
39
|
+
const EXAMPLE_ANIMAL_ID = '00000000-0000-4000-8000-000000000202';
|
|
40
|
+
const EXAMPLE_INPUT = Object.freeze({
|
|
41
|
+
entryId: EXAMPLE_ENTRY_ID,
|
|
42
|
+
animalReference: `Patient/${EXAMPLE_ANIMAL_ID}`,
|
|
43
|
+
administrationDate: '2024-05-23',
|
|
44
|
+
localText: 'vacuna contra la rabia',
|
|
45
|
+
language: 'es-ES',
|
|
46
|
+
proposalId: 'veterinary-immunization-proposal-example',
|
|
47
|
+
candidates: Object.freeze([Object.freeze({
|
|
48
|
+
id: 'veterinary-immunization-candidate-example',
|
|
49
|
+
system: VeterinaryClinicalTerminologySystems.AtcVet,
|
|
50
|
+
code: 'QI07AA02',
|
|
51
|
+
display: 'Inactivated rabies virus vaccines',
|
|
52
|
+
})]),
|
|
53
|
+
});
|
|
54
|
+
/** Package-owned synthetic example for assistants, portals and downstream tests. */
|
|
55
|
+
export const VETERINARY_IMMUNIZATION_CODING_REVIEW_EXAMPLE = Object.freeze({
|
|
56
|
+
input: EXAMPLE_INPUT,
|
|
57
|
+
claims: VeterinaryImmunizationCodingReviewClaims,
|
|
58
|
+
expected: buildVeterinaryImmunizationCodingReview(EXAMPLE_INPUT),
|
|
59
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type FlatClaimResourceEntry } from 'sos-data-utils-ts/flat-claim-resource-graph';
|
|
2
|
+
import { type VeterinaryImmunizationCoding } from './immunization-flat-claims.js';
|
|
3
|
+
export type VeterinaryImmunizationDraftAttachment = Readonly<{
|
|
4
|
+
entryId: string;
|
|
5
|
+
identifier: string;
|
|
6
|
+
contentType: 'application/pdf' | 'image/jpeg' | 'image/png';
|
|
7
|
+
contentHash: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
dataBase64?: string;
|
|
10
|
+
}>;
|
|
11
|
+
export type VeterinaryImmunizationDraft = Readonly<{
|
|
12
|
+
status: 'preparation' | 'confirmed';
|
|
13
|
+
administrationDate: string;
|
|
14
|
+
vaccineCode: VeterinaryImmunizationCoding;
|
|
15
|
+
targetDiseases: readonly VeterinaryImmunizationCoding[];
|
|
16
|
+
productName?: string;
|
|
17
|
+
productCode?: VeterinaryImmunizationCoding;
|
|
18
|
+
productIdentifiers: readonly Readonly<{
|
|
19
|
+
system: string;
|
|
20
|
+
value: string;
|
|
21
|
+
}>[];
|
|
22
|
+
manufacturer?: string;
|
|
23
|
+
lotNumber?: string;
|
|
24
|
+
productExpirationDate?: string;
|
|
25
|
+
barcode?: string;
|
|
26
|
+
routeCode?: string;
|
|
27
|
+
siteCode?: string;
|
|
28
|
+
doseValue?: string;
|
|
29
|
+
doseUnit?: string;
|
|
30
|
+
nextDoseDate?: string;
|
|
31
|
+
attachments: readonly VeterinaryImmunizationDraftAttachment[];
|
|
32
|
+
confirmedAt?: string;
|
|
33
|
+
}>;
|
|
34
|
+
/** Normalizes the same reviewable draft for web, chat, WhatsApp and telephone adapters. */
|
|
35
|
+
export declare function prepareVeterinaryImmunizationDraft(input: Partial<Omit<VeterinaryImmunizationDraft, 'status' | 'confirmedAt'>>): VeterinaryImmunizationDraft;
|
|
36
|
+
/** Records the channel's explicit human readback/preview confirmation. */
|
|
37
|
+
export declare function confirmVeterinaryImmunizationDraft(draft: VeterinaryImmunizationDraft, confirmedAt: string): VeterinaryImmunizationDraft;
|
|
38
|
+
/** Completes a confirmed channel draft with server-owned subject and performer coordinates. */
|
|
39
|
+
export declare function materializeVeterinaryImmunizationDraft(draft: VeterinaryImmunizationDraft, context: Readonly<{
|
|
40
|
+
entryId: string;
|
|
41
|
+
animalReference: string;
|
|
42
|
+
performerReference?: string;
|
|
43
|
+
}>): readonly FlatClaimResourceEntry[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { buildDocumentReferenceFlatEntry } from 'sos-data-utils-ts/flat-claim-resource-graph';
|
|
2
|
+
import { buildVeterinaryImmunizationFlatGraph, } from './immunization-flat-claims.js';
|
|
3
|
+
/** Normalizes the same reviewable draft for web, chat, WhatsApp and telephone adapters. */
|
|
4
|
+
export function prepareVeterinaryImmunizationDraft(input) {
|
|
5
|
+
const administrationDate = date(input.administrationDate, 'veterinary_immunization_date_invalid');
|
|
6
|
+
const vaccineCode = coding(input.vaccineCode, 'veterinary_immunization_vaccine_code_required');
|
|
7
|
+
const productExpirationDate = input.productExpirationDate ? date(input.productExpirationDate, 'veterinary_medication_expiration_date_invalid') : undefined;
|
|
8
|
+
const nextDoseDate = input.nextDoseDate ? date(input.nextDoseDate, 'veterinary_immunization_next_dose_date_invalid') : undefined;
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
status: 'preparation', administrationDate, vaccineCode,
|
|
11
|
+
targetDiseases: Object.freeze((input.targetDiseases ?? []).map((value) => coding(value, 'veterinary_immunization_target_disease_invalid'))),
|
|
12
|
+
...(text(input.productName) ? { productName: text(input.productName) } : {}),
|
|
13
|
+
...(input.productCode ? { productCode: coding(input.productCode, 'veterinary_immunization_product_code_invalid') } : {}),
|
|
14
|
+
productIdentifiers: Object.freeze((input.productIdentifiers ?? []).map(({ system, value }) => Object.freeze({
|
|
15
|
+
system: required(system, 'veterinary_immunization_product_identifier_invalid'),
|
|
16
|
+
value: required(value, 'veterinary_immunization_product_identifier_invalid'),
|
|
17
|
+
}))),
|
|
18
|
+
...(text(input.manufacturer) ? { manufacturer: text(input.manufacturer) } : {}),
|
|
19
|
+
...(text(input.lotNumber) ? { lotNumber: text(input.lotNumber) } : {}),
|
|
20
|
+
...(productExpirationDate ? { productExpirationDate } : {}),
|
|
21
|
+
...(text(input.barcode) ? { barcode: text(input.barcode) } : {}),
|
|
22
|
+
...(text(input.routeCode) ? { routeCode: text(input.routeCode) } : {}),
|
|
23
|
+
...(text(input.siteCode) ? { siteCode: text(input.siteCode) } : {}),
|
|
24
|
+
...(text(input.doseValue) ? { doseValue: text(input.doseValue) } : {}),
|
|
25
|
+
...(text(input.doseUnit) ? { doseUnit: text(input.doseUnit) } : {}),
|
|
26
|
+
...(nextDoseDate ? { nextDoseDate } : {}),
|
|
27
|
+
attachments: Object.freeze([...(input.attachments ?? [])]),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Records the channel's explicit human readback/preview confirmation. */
|
|
31
|
+
export function confirmVeterinaryImmunizationDraft(draft, confirmedAt) {
|
|
32
|
+
if (draft.status !== 'preparation')
|
|
33
|
+
throw new TypeError('veterinary_immunization_draft_already_confirmed');
|
|
34
|
+
const instant = required(confirmedAt, 'veterinary_immunization_confirmation_time_required');
|
|
35
|
+
if (Number.isNaN(Date.parse(instant)))
|
|
36
|
+
throw new TypeError('veterinary_immunization_confirmation_time_invalid');
|
|
37
|
+
return Object.freeze({ ...draft, status: 'confirmed', confirmedAt: instant });
|
|
38
|
+
}
|
|
39
|
+
/** Completes a confirmed channel draft with server-owned subject and performer coordinates. */
|
|
40
|
+
export function materializeVeterinaryImmunizationDraft(draft, context) {
|
|
41
|
+
if (draft.status !== 'confirmed')
|
|
42
|
+
throw new TypeError('veterinary_immunization_draft_confirmation_required');
|
|
43
|
+
const attachments = draft.attachments.map(attachment => buildDocumentReferenceFlatEntry(attachment));
|
|
44
|
+
const hasMedication = Boolean(draft.productName || draft.productCode || draft.productIdentifiers.length
|
|
45
|
+
|| draft.manufacturer || draft.lotNumber || draft.productExpirationDate || draft.barcode);
|
|
46
|
+
return buildVeterinaryImmunizationFlatGraph({
|
|
47
|
+
entryId: required(context.entryId, 'veterinary_immunization_entry_id_required'),
|
|
48
|
+
animalReference: required(context.animalReference, 'veterinary_immunization_animal_reference_required'),
|
|
49
|
+
...(context.performerReference ? {
|
|
50
|
+
performerReference: required(context.performerReference, 'veterinary_immunization_performer_invalid'),
|
|
51
|
+
} : {}),
|
|
52
|
+
administrationDate: draft.administrationDate,
|
|
53
|
+
vaccineCode: draft.vaccineCode,
|
|
54
|
+
targetDiseases: draft.targetDiseases,
|
|
55
|
+
...(draft.routeCode ? { route: { system: 'http://terminology.hl7.org/CodeSystem/v3-RouteOfAdministration', code: draft.routeCode } } : {}),
|
|
56
|
+
...(draft.siteCode ? { site: { system: 'http://terminology.hl7.org/CodeSystem/v3-ActSite', code: draft.siteCode } } : {}),
|
|
57
|
+
...(draft.doseValue ? { dose: { value: draft.doseValue, ...(draft.doseUnit ? { unit: draft.doseUnit } : {}) } } : {}),
|
|
58
|
+
...(draft.nextDoseDate ? {
|
|
59
|
+
nextDoseDate: draft.nextDoseDate,
|
|
60
|
+
recommendationDate: required(draft.confirmedAt, 'veterinary_immunization_confirmation_time_required'),
|
|
61
|
+
} : {}),
|
|
62
|
+
...(hasMedication ? { medication: {
|
|
63
|
+
entryId: `${context.entryId}-medication`,
|
|
64
|
+
productName: draft.productName || draft.vaccineCode.display || `${draft.vaccineCode.system}|${draft.vaccineCode.code}`,
|
|
65
|
+
...(draft.productCode ? { productCode: draft.productCode } : {}),
|
|
66
|
+
identifiers: draft.productIdentifiers,
|
|
67
|
+
...(draft.manufacturer ? { manufacturer: draft.manufacturer } : {}),
|
|
68
|
+
...(draft.lotNumber ? { lotNumber: draft.lotNumber } : {}),
|
|
69
|
+
...(draft.productExpirationDate ? { expirationDate: draft.productExpirationDate } : {}),
|
|
70
|
+
...(draft.barcode ? { barcode: draft.barcode } : {}),
|
|
71
|
+
} } : {}),
|
|
72
|
+
attachments,
|
|
73
|
+
attachmentEntryIds: attachments.map(({ entryId }) => entryId),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function coding(value, error) {
|
|
77
|
+
if (!value || typeof value !== 'object')
|
|
78
|
+
throw new TypeError(error);
|
|
79
|
+
const candidate = value;
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
system: required(candidate.system, error), code: required(candidate.code, error),
|
|
82
|
+
...(text(candidate.display) ? { display: text(candidate.display) } : {}),
|
|
83
|
+
...(text(candidate.text) ? { text: text(candidate.text) } : {}),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function date(value, error) {
|
|
87
|
+
const normalized = required(value, error);
|
|
88
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(normalized) || Number.isNaN(Date.parse(normalized)))
|
|
89
|
+
throw new TypeError(error);
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
92
|
+
function required(value, error) {
|
|
93
|
+
const normalized = text(value);
|
|
94
|
+
if (!normalized)
|
|
95
|
+
throw new TypeError(error);
|
|
96
|
+
return normalized;
|
|
97
|
+
}
|
|
98
|
+
function text(value) {
|
|
99
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
100
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type FlatClaimResourceEntry } from 'sos-data-utils-ts/flat-claim-resource-graph';
|
|
2
|
+
import { type ImmunizationReminderChannel } from 'sos-data-utils-ts/immunization-reminder-plan';
|
|
3
|
+
export type VeterinaryImmunizationCoding = Readonly<{
|
|
4
|
+
system: string;
|
|
5
|
+
code: string;
|
|
6
|
+
display?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Canonical requested-language term entered or selected by the channel.
|
|
9
|
+
* A neutral coding proposal targets `Immunization.vaccine-code`; accepting
|
|
10
|
+
* it must preserve this value as `Immunization.vaccine-code-text`.
|
|
11
|
+
*/
|
|
12
|
+
text?: string;
|
|
13
|
+
}>;
|
|
14
|
+
export type VeterinaryImmunizationFlatInput = Readonly<{
|
|
15
|
+
entryId: string;
|
|
16
|
+
identifier?: string;
|
|
17
|
+
animalReference: string;
|
|
18
|
+
administrationDate: string;
|
|
19
|
+
vaccineCode: VeterinaryImmunizationCoding;
|
|
20
|
+
/** Present only when the administering professional is known. */
|
|
21
|
+
performerReference?: string;
|
|
22
|
+
targetDiseases?: readonly VeterinaryImmunizationCoding[];
|
|
23
|
+
route?: VeterinaryImmunizationCoding;
|
|
24
|
+
site?: VeterinaryImmunizationCoding;
|
|
25
|
+
dose?: Readonly<{
|
|
26
|
+
value: string;
|
|
27
|
+
unit?: string;
|
|
28
|
+
}>;
|
|
29
|
+
nextDoseDate?: string;
|
|
30
|
+
/** FHIR dateTime when the recommendation was created; required with nextDoseDate. */
|
|
31
|
+
recommendationDate?: string;
|
|
32
|
+
/** Ordered candidates only; dispatch rechecks current preference and Consent. */
|
|
33
|
+
reminderChannels?: readonly ImmunizationReminderChannel[];
|
|
34
|
+
medication?: Readonly<{
|
|
35
|
+
entryId: string;
|
|
36
|
+
productName: string;
|
|
37
|
+
productCode?: VeterinaryImmunizationCoding;
|
|
38
|
+
identifiers?: readonly Readonly<{
|
|
39
|
+
system: string;
|
|
40
|
+
value: string;
|
|
41
|
+
}>[];
|
|
42
|
+
manufacturer?: string;
|
|
43
|
+
lotNumber?: string;
|
|
44
|
+
expirationDate?: string;
|
|
45
|
+
barcode?: string;
|
|
46
|
+
}>;
|
|
47
|
+
attachments?: readonly FlatClaimResourceEntry[];
|
|
48
|
+
attachmentEntryIds?: readonly string[];
|
|
49
|
+
}>;
|
|
50
|
+
/**
|
|
51
|
+
* Creates a version-neutral flat graph after coding review is confirmed.
|
|
52
|
+
* Unconfirmed `meta.codingProposals[]` use the neutral `fhir-data-utils-ts`
|
|
53
|
+
* contract and remain on the same resource, outside `meta.claims`.
|
|
54
|
+
* SearchParameter names are preferred
|
|
55
|
+
* (`Immunization.date`, `Medication.expiration-date`) and product evidence is
|
|
56
|
+
* kept in Medication instead of overloading credential validity.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildVeterinaryImmunizationFlatGraph(input: VeterinaryImmunizationFlatInput): readonly FlatClaimResourceEntry[];
|
|
59
|
+
export type FhirExportRelease = 'R4' | 'R5' | 'R6';
|
|
60
|
+
/** Explicit adapter boundary from canonical flat claims to a selected FHIR release. */
|
|
61
|
+
export declare function exportVeterinaryImmunizationFlatGraph(graph: readonly FlatClaimResourceEntry[], release: FhirExportRelease): Readonly<{
|
|
62
|
+
resourceType: 'Bundle';
|
|
63
|
+
type: 'collection';
|
|
64
|
+
entry: readonly Readonly<{
|
|
65
|
+
fullUrl: string;
|
|
66
|
+
resource: Record<string, unknown>;
|
|
67
|
+
}>[];
|
|
68
|
+
}>;
|
|
69
|
+
/** De-identified three-event fixture based on the supplied Canadian certificate; no owner contact data is retained. */
|
|
70
|
+
export declare const NALA_VACCINATION_FLAT_GRAPH_TEST_DATA: readonly FlatClaimResourceEntry[];
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { buildDocumentReferenceFlatEntry, buildFlatClaimResourceEntry, linkFlatClaimResourceEntries, } from 'sos-data-utils-ts/flat-claim-resource-graph';
|
|
2
|
+
import { ImmunizationReminderChannels, buildImmunizationReminderTasks, } from 'sos-data-utils-ts/immunization-reminder-plan';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a version-neutral flat graph after coding review is confirmed.
|
|
5
|
+
* Unconfirmed `meta.codingProposals[]` use the neutral `fhir-data-utils-ts`
|
|
6
|
+
* contract and remain on the same resource, outside `meta.claims`.
|
|
7
|
+
* SearchParameter names are preferred
|
|
8
|
+
* (`Immunization.date`, `Medication.expiration-date`) and product evidence is
|
|
9
|
+
* kept in Medication instead of overloading credential validity.
|
|
10
|
+
*/
|
|
11
|
+
export function buildVeterinaryImmunizationFlatGraph(input) {
|
|
12
|
+
const vaccineCode = codingToken(input.vaccineCode);
|
|
13
|
+
const claims = {
|
|
14
|
+
'Immunization.status': 'completed',
|
|
15
|
+
'Immunization.patient': required(input.animalReference, 'veterinary_immunization_animal_reference_required'),
|
|
16
|
+
'Immunization.date': isoDate(input.administrationDate, 'veterinary_immunization_date_invalid'),
|
|
17
|
+
'Immunization.vaccine-code': vaccineCode,
|
|
18
|
+
};
|
|
19
|
+
if (input.identifier)
|
|
20
|
+
claims['Immunization.identifier'] = required(input.identifier, 'veterinary_immunization_identifier_invalid');
|
|
21
|
+
if (input.vaccineCode.display)
|
|
22
|
+
claims['Immunization.vaccine-code-display'] = input.vaccineCode.display.trim();
|
|
23
|
+
if (input.vaccineCode.text)
|
|
24
|
+
claims['Immunization.vaccine-code-text'] = input.vaccineCode.text.trim();
|
|
25
|
+
if (input.performerReference)
|
|
26
|
+
claims['Immunization.performer'] = required(input.performerReference, 'veterinary_immunization_performer_invalid');
|
|
27
|
+
if (input.targetDiseases?.length) {
|
|
28
|
+
claims['Immunization.target-disease'] = input.targetDiseases.map(codingToken).join(',');
|
|
29
|
+
if (input.targetDiseases.some(({ display }) => display?.trim())) {
|
|
30
|
+
claims['Immunization.target-disease-display'] = joinCsv(input.targetDiseases.map(({ display }) => display?.trim() ?? ''));
|
|
31
|
+
}
|
|
32
|
+
if (input.targetDiseases.some(({ text }) => text?.trim())) {
|
|
33
|
+
claims['Immunization.target-disease-text'] = joinCsv(input.targetDiseases.map(({ text }) => text?.trim() ?? ''));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (input.route)
|
|
37
|
+
claims['Immunization.route'] = codingToken(input.route);
|
|
38
|
+
if (input.site)
|
|
39
|
+
claims['Immunization.site'] = codingToken(input.site);
|
|
40
|
+
if (input.dose) {
|
|
41
|
+
claims['Immunization.dose-quantity-value'] = required(input.dose.value, 'veterinary_immunization_dose_invalid');
|
|
42
|
+
if (input.dose.unit)
|
|
43
|
+
claims['Immunization.dose-quantity-unit'] = input.dose.unit.trim();
|
|
44
|
+
}
|
|
45
|
+
const immunization = buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'Immunization', claims });
|
|
46
|
+
const children = [];
|
|
47
|
+
let recommendationId;
|
|
48
|
+
let reminderTasks = [];
|
|
49
|
+
if (input.medication)
|
|
50
|
+
children.push(buildMedicationEntry(input.medication));
|
|
51
|
+
if (input.nextDoseDate) {
|
|
52
|
+
recommendationId = `${input.entryId}-recommendation`;
|
|
53
|
+
children.push(buildFlatClaimResourceEntry({
|
|
54
|
+
entryId: recommendationId,
|
|
55
|
+
resourceType: 'ImmunizationRecommendation',
|
|
56
|
+
claims: {
|
|
57
|
+
'ImmunizationRecommendation.patient': input.animalReference,
|
|
58
|
+
// FHIR date is when the forecast was created, not when coverage ends.
|
|
59
|
+
'ImmunizationRecommendation.date': isoDate(required(input.recommendationDate, 'veterinary_immunization_recommendation_date_required'), 'veterinary_immunization_recommendation_date_invalid'),
|
|
60
|
+
'ImmunizationRecommendation.vaccine-code': vaccineCode,
|
|
61
|
+
'ImmunizationRecommendation.forecast-status': 'http://terminology.hl7.org/CodeSystem/immunization-recommendation-status|due',
|
|
62
|
+
'ImmunizationRecommendation.date-criterion-code': 'http://loinc.org|30980-7',
|
|
63
|
+
'ImmunizationRecommendation.date-criterion-value': isoDate(input.nextDoseDate, 'veterinary_immunization_next_dose_date_invalid'),
|
|
64
|
+
'ImmunizationRecommendation.supporting-immunization': `Immunization/${input.entryId}`,
|
|
65
|
+
},
|
|
66
|
+
}));
|
|
67
|
+
reminderTasks = buildImmunizationReminderTasks({
|
|
68
|
+
entryIdPrefix: recommendationId,
|
|
69
|
+
recommendationReference: `ImmunizationRecommendation/${recommendationId}`,
|
|
70
|
+
subjectReference: input.animalReference,
|
|
71
|
+
coverageEndDate: input.nextDoseDate,
|
|
72
|
+
channelCandidates: input.reminderChannels ?? ImmunizationReminderChannels,
|
|
73
|
+
});
|
|
74
|
+
children.push(...reminderTasks);
|
|
75
|
+
}
|
|
76
|
+
for (const attachment of input.attachments ?? []) {
|
|
77
|
+
if (attachment.resourceType !== 'DocumentReference')
|
|
78
|
+
throw new TypeError('veterinary_immunization_attachment_invalid');
|
|
79
|
+
children.push(attachment);
|
|
80
|
+
}
|
|
81
|
+
const graph = uniqueEntries([immunization, ...children]);
|
|
82
|
+
const childEntryIds = [
|
|
83
|
+
...(input.medication ? [input.medication.entryId] : []),
|
|
84
|
+
...(input.nextDoseDate ? [`${input.entryId}-recommendation`] : []),
|
|
85
|
+
...(input.attachments ?? []).map(({ entryId }) => entryId),
|
|
86
|
+
...(input.attachmentEntryIds ?? []),
|
|
87
|
+
];
|
|
88
|
+
const linkedImmunization = childEntryIds.length
|
|
89
|
+
? linkFlatClaimResourceEntries(graph, { parentEntryId: input.entryId, childEntryIds })
|
|
90
|
+
: graph;
|
|
91
|
+
return recommendationId && reminderTasks.length
|
|
92
|
+
? linkFlatClaimResourceEntries(linkedImmunization, {
|
|
93
|
+
parentEntryId: recommendationId,
|
|
94
|
+
childEntryIds: reminderTasks.map(({ entryId }) => entryId),
|
|
95
|
+
})
|
|
96
|
+
: linkedImmunization;
|
|
97
|
+
}
|
|
98
|
+
function buildMedicationEntry(input) {
|
|
99
|
+
const claims = {
|
|
100
|
+
'Medication.code': input.productCode ? codingToken(input.productCode) : required(input.productName, 'veterinary_medication_product_required'),
|
|
101
|
+
'Medication.code-text': required(input.productName, 'veterinary_medication_product_required'),
|
|
102
|
+
};
|
|
103
|
+
if (input.identifiers?.length) {
|
|
104
|
+
claims['Medication.identifier'] = input.identifiers.map(({ system, value }) => (`${required(system, 'veterinary_medication_identifier_invalid')}|${required(value, 'veterinary_medication_identifier_invalid')}`)).join(',');
|
|
105
|
+
}
|
|
106
|
+
if (input.manufacturer)
|
|
107
|
+
claims['Medication.manufacturer'] = input.manufacturer.trim();
|
|
108
|
+
if (input.lotNumber)
|
|
109
|
+
claims['Medication.lot-number'] = input.lotNumber.trim();
|
|
110
|
+
if (input.expirationDate)
|
|
111
|
+
claims['Medication.expiration-date'] = isoDate(input.expirationDate, 'veterinary_medication_expiration_date_invalid');
|
|
112
|
+
if (input.barcode)
|
|
113
|
+
claims['Medication.serial-number'] = input.barcode.trim();
|
|
114
|
+
return buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'Medication', claims });
|
|
115
|
+
}
|
|
116
|
+
/** Explicit adapter boundary from canonical flat claims to a selected FHIR release. */
|
|
117
|
+
export function exportVeterinaryImmunizationFlatGraph(graph, release) {
|
|
118
|
+
const byReference = new Map(graph.map((entry) => [entry.reference, entry]));
|
|
119
|
+
return Object.freeze({
|
|
120
|
+
resourceType: 'Bundle',
|
|
121
|
+
type: 'collection',
|
|
122
|
+
entry: Object.freeze(graph.map((entry) => Object.freeze({
|
|
123
|
+
fullUrl: entry.reference,
|
|
124
|
+
resource: exportEntry(entry, release, byReference),
|
|
125
|
+
}))),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function exportEntry(entry, release, byReference) {
|
|
129
|
+
const claims = entry.claims;
|
|
130
|
+
if (entry.resourceType === 'Immunization') {
|
|
131
|
+
const vaccineCode = codeableConcept(claims['Immunization.vaccine-code'], claims['Immunization.vaccine-code-display'], claims['Immunization.vaccine-code-text']);
|
|
132
|
+
const children = splitCsv(claims['Immunization.contained-reference-list']).map((reference) => byReference.get(reference)).filter(Boolean);
|
|
133
|
+
const medication = children.find((child) => child?.resourceType === 'Medication');
|
|
134
|
+
const resource = {
|
|
135
|
+
resourceType: 'Immunization', id: entry.entryId, status: claims['Immunization.status'],
|
|
136
|
+
vaccineCode, patient: { reference: claims['Immunization.patient'] },
|
|
137
|
+
occurrenceDateTime: claims['Immunization.date'],
|
|
138
|
+
};
|
|
139
|
+
if (claims['Immunization.performer'])
|
|
140
|
+
resource.performer = [{ actor: { reference: claims['Immunization.performer'] } }];
|
|
141
|
+
if (claims['Immunization.identifier'])
|
|
142
|
+
resource.identifier = [{ value: claims['Immunization.identifier'] }];
|
|
143
|
+
if (claims['Immunization.route'])
|
|
144
|
+
resource.route = codeableConcept(claims['Immunization.route']);
|
|
145
|
+
if (claims['Immunization.site'])
|
|
146
|
+
resource.site = codeableConcept(claims['Immunization.site']);
|
|
147
|
+
if (claims['Immunization.target-disease']) {
|
|
148
|
+
const displays = splitCsvKeepingEmpty(claims['Immunization.target-disease-display']);
|
|
149
|
+
const texts = splitCsvKeepingEmpty(claims['Immunization.target-disease-text']);
|
|
150
|
+
resource.protocolApplied = [{
|
|
151
|
+
targetDisease: splitCsv(claims['Immunization.target-disease']).map((code, index) => (codeableConcept(code, displays[index], texts[index]))),
|
|
152
|
+
}];
|
|
153
|
+
}
|
|
154
|
+
if (medication && release !== 'R4')
|
|
155
|
+
resource.administeredProduct = { reference: { reference: medication.reference } };
|
|
156
|
+
return resource;
|
|
157
|
+
}
|
|
158
|
+
if (entry.resourceType === 'Medication') {
|
|
159
|
+
const lotNumber = claims['Medication.lot-number'];
|
|
160
|
+
const expirationDate = claims['Medication.expiration-date'];
|
|
161
|
+
const resource = {
|
|
162
|
+
resourceType: 'Medication', id: entry.entryId,
|
|
163
|
+
code: codeableConcept(claims['Medication.code'], undefined, claims['Medication.code-text']),
|
|
164
|
+
};
|
|
165
|
+
if (claims['Medication.identifier'])
|
|
166
|
+
resource.identifier = splitCsv(claims['Medication.identifier']).map(identifierFromToken);
|
|
167
|
+
if (lotNumber || expirationDate)
|
|
168
|
+
resource[release === 'R6' ? 'instance' : 'batch'] = {
|
|
169
|
+
...(lotNumber ? { lotNumber } : {}), ...(expirationDate ? { expirationDate } : {}),
|
|
170
|
+
};
|
|
171
|
+
return resource;
|
|
172
|
+
}
|
|
173
|
+
if (entry.resourceType === 'ImmunizationRecommendation')
|
|
174
|
+
return {
|
|
175
|
+
resourceType: 'ImmunizationRecommendation', id: entry.entryId,
|
|
176
|
+
patient: { reference: claims['ImmunizationRecommendation.patient'] },
|
|
177
|
+
date: claims['ImmunizationRecommendation.date'],
|
|
178
|
+
recommendation: [{
|
|
179
|
+
vaccineCode: [codeableConcept(claims['ImmunizationRecommendation.vaccine-code'])],
|
|
180
|
+
forecastStatus: codeableConcept(claims['ImmunizationRecommendation.forecast-status']),
|
|
181
|
+
dateCriterion: [{
|
|
182
|
+
code: codeableConcept(claims['ImmunizationRecommendation.date-criterion-code']),
|
|
183
|
+
value: claims['ImmunizationRecommendation.date-criterion-value'],
|
|
184
|
+
}],
|
|
185
|
+
supportingImmunization: [{ reference: claims['ImmunizationRecommendation.supporting-immunization'] }],
|
|
186
|
+
}],
|
|
187
|
+
};
|
|
188
|
+
if (entry.resourceType === 'DocumentReference')
|
|
189
|
+
return {
|
|
190
|
+
resourceType: 'DocumentReference', id: entry.entryId, status: 'current',
|
|
191
|
+
identifier: [{ value: claims['DocumentReference.identifier'] }],
|
|
192
|
+
content: [{ attachment: {
|
|
193
|
+
contentType: claims['DocumentReference.content-type'],
|
|
194
|
+
...(claims['DocumentReference.url'] ? { url: claims['DocumentReference.url'] } : {}),
|
|
195
|
+
...(claims['DocumentReference.title'] ? { title: claims['DocumentReference.title'] } : {}),
|
|
196
|
+
} }],
|
|
197
|
+
};
|
|
198
|
+
return { resourceType: entry.resourceType, id: entry.entryId, meta: { claims: { ...claims } } };
|
|
199
|
+
}
|
|
200
|
+
function codeableConcept(token, display, text) {
|
|
201
|
+
const value = required(token, 'veterinary_coding_required');
|
|
202
|
+
const separator = value.lastIndexOf('|');
|
|
203
|
+
if (separator <= 0 || separator === value.length - 1)
|
|
204
|
+
return { text: text || display || value };
|
|
205
|
+
return {
|
|
206
|
+
coding: [{ system: value.slice(0, separator), code: value.slice(separator + 1), ...(display ? { display } : {}) }],
|
|
207
|
+
...(text ? { text } : {}),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function identifierFromToken(token) {
|
|
211
|
+
const separator = token.lastIndexOf('|');
|
|
212
|
+
return separator > 0 ? { system: token.slice(0, separator), value: token.slice(separator + 1) } : { value: token };
|
|
213
|
+
}
|
|
214
|
+
function codingToken(coding) {
|
|
215
|
+
return `${required(coding.system, 'veterinary_coding_system_required')}|${required(coding.code, 'veterinary_coding_code_required')}`;
|
|
216
|
+
}
|
|
217
|
+
function isoDate(value, error) {
|
|
218
|
+
const normalized = required(value, error);
|
|
219
|
+
if (!/^\d{4}-\d{2}-\d{2}(?:T.*)?$/.test(normalized) || Number.isNaN(Date.parse(normalized)))
|
|
220
|
+
throw new TypeError(error);
|
|
221
|
+
return normalized;
|
|
222
|
+
}
|
|
223
|
+
function required(value, error) {
|
|
224
|
+
if (typeof value !== 'string' || !value.trim())
|
|
225
|
+
throw new TypeError(error);
|
|
226
|
+
return value.trim();
|
|
227
|
+
}
|
|
228
|
+
function splitCsv(value) {
|
|
229
|
+
return splitCsvKeepingEmpty(value).filter(Boolean);
|
|
230
|
+
}
|
|
231
|
+
function splitCsvKeepingEmpty(value) {
|
|
232
|
+
if (value === undefined)
|
|
233
|
+
return [];
|
|
234
|
+
const values = [];
|
|
235
|
+
let current = '';
|
|
236
|
+
let quoted = false;
|
|
237
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
238
|
+
const character = value[index];
|
|
239
|
+
if (character === '"' && quoted && value[index + 1] === '"') {
|
|
240
|
+
current += '"';
|
|
241
|
+
index += 1;
|
|
242
|
+
}
|
|
243
|
+
else if (character === '"')
|
|
244
|
+
quoted = !quoted;
|
|
245
|
+
else if (character === ',' && !quoted) {
|
|
246
|
+
values.push(current.trim());
|
|
247
|
+
current = '';
|
|
248
|
+
}
|
|
249
|
+
else
|
|
250
|
+
current += character;
|
|
251
|
+
}
|
|
252
|
+
values.push(current.trim());
|
|
253
|
+
return values;
|
|
254
|
+
}
|
|
255
|
+
function joinCsv(values) {
|
|
256
|
+
return values.map((value) => value.includes(',') || value.includes('"')
|
|
257
|
+
? `"${value.replaceAll('"', '""')}"`
|
|
258
|
+
: value).join(',');
|
|
259
|
+
}
|
|
260
|
+
function uniqueEntries(entries) {
|
|
261
|
+
const byId = new Map();
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
const existing = byId.get(entry.entryId);
|
|
264
|
+
if (existing && existing.reference !== entry.reference)
|
|
265
|
+
throw new TypeError('flat_claim_entry_id_collision');
|
|
266
|
+
byId.set(entry.entryId, existing ?? entry);
|
|
267
|
+
}
|
|
268
|
+
return Object.freeze([...byId.values()]);
|
|
269
|
+
}
|
|
270
|
+
const atcVet = 'http://www.whocc.no/atcvet';
|
|
271
|
+
const certificate = buildDocumentReferenceFlatEntry({
|
|
272
|
+
entryId: 'nala-vaccination-certificate', identifier: 'urn:uuid:nala-vaccination-certificate',
|
|
273
|
+
contentType: 'image/jpeg', contentHash: 'sha256:test-only-nala-certificate-digest', title: 'Vaccination certificate',
|
|
274
|
+
});
|
|
275
|
+
/** De-identified three-event fixture based on the supplied Canadian certificate; no owner contact data is retained. */
|
|
276
|
+
export const NALA_VACCINATION_FLAT_GRAPH_TEST_DATA = uniqueEntries([
|
|
277
|
+
...buildVeterinaryImmunizationFlatGraph({
|
|
278
|
+
entryId: 'nala-rabies', animalReference: 'Patient/nala', administrationDate: '2024-05-23',
|
|
279
|
+
performerReference: 'PractitionerRole/certificate-veterinarian',
|
|
280
|
+
vaccineCode: {
|
|
281
|
+
system: atcVet, code: 'QI07AA02', display: 'Inactivated rabies virus vaccines',
|
|
282
|
+
text: 'Vacuna contra el virus de la rabia',
|
|
283
|
+
},
|
|
284
|
+
targetDiseases: [{ system: 'http://snomed.info/sct', code: '14168008', display: 'Rabies', text: 'Rabia' }],
|
|
285
|
+
nextDoseDate: '2027-05-23',
|
|
286
|
+
recommendationDate: '2026-09-20T12:00:00.000Z',
|
|
287
|
+
medication: {
|
|
288
|
+
entryId: 'nala-rabies-product', productName: 'Imrab 3 TF',
|
|
289
|
+
productCode: { system: atcVet, code: 'QI07AA02' }, manufacturer: 'Boehringer Ingelheim',
|
|
290
|
+
identifiers: [
|
|
291
|
+
{ system: 'https://inspection.canada.ca/ccvb', value: '820VV/R1.14/R2.1' },
|
|
292
|
+
{ system: 'https://www.aphis.usda.gov/cvb/product-code', value: '1905.26' },
|
|
293
|
+
],
|
|
294
|
+
lotNumber: '18595', expirationDate: '2025-06-28',
|
|
295
|
+
},
|
|
296
|
+
route: { system: 'http://terminology.hl7.org/CodeSystem/v3-RouteOfAdministration', code: 'SQ', display: 'Subcutaneous' },
|
|
297
|
+
attachments: [certificate], attachmentEntryIds: [certificate.entryId],
|
|
298
|
+
}),
|
|
299
|
+
...buildVeterinaryImmunizationFlatGraph({
|
|
300
|
+
entryId: 'nala-da2plp', animalReference: 'Patient/nala', administrationDate: '2024-05-23',
|
|
301
|
+
performerReference: 'PractitionerRole/certificate-veterinarian',
|
|
302
|
+
vaccineCode: { system: atcVet, code: 'QI07AI02', display: 'Canine viral and bacterial combination vaccines' },
|
|
303
|
+
nextDoseDate: '2025-05-23', recommendationDate: '2026-09-20T12:00:00.000Z',
|
|
304
|
+
attachments: [certificate], attachmentEntryIds: [certificate.entryId],
|
|
305
|
+
}),
|
|
306
|
+
...buildVeterinaryImmunizationFlatGraph({
|
|
307
|
+
entryId: 'nala-lyme', animalReference: 'Patient/nala', administrationDate: '2024-05-23',
|
|
308
|
+
performerReference: 'PractitionerRole/certificate-veterinarian',
|
|
309
|
+
vaccineCode: { system: atcVet, code: 'QI07AB04', display: 'Borrelia vaccines' },
|
|
310
|
+
nextDoseDate: '2025-05-23', recommendationDate: '2026-09-20T12:00:00.000Z',
|
|
311
|
+
attachments: [certificate], attachmentEntryIds: [certificate.entryId],
|
|
312
|
+
}),
|
|
313
|
+
]);
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,9 @@ export * from './financial.js';
|
|
|
10
10
|
export * from './group.js';
|
|
11
11
|
export * from './health-dcat.js';
|
|
12
12
|
export * from './immunization.js';
|
|
13
|
+
export * from './immunization-flat-claims.js';
|
|
14
|
+
export * from './immunization-coding-review.js';
|
|
15
|
+
export * from './immunization-draft.js';
|
|
13
16
|
export * from './international-health-card.js';
|
|
14
17
|
export * from './index-projection-tags.js';
|
|
15
18
|
export * from './insurance-consent.js';
|
|
@@ -18,6 +21,7 @@ export * from './lost-animal-access.js';
|
|
|
18
21
|
export * from './organization-application.js';
|
|
19
22
|
export * from './payment.js';
|
|
20
23
|
export * from './place-service-directory.js';
|
|
24
|
+
export * from './professional-access-policy.js';
|
|
21
25
|
export * from './provenance.js';
|
|
22
26
|
export * from './research-study.js';
|
|
23
27
|
export * from './sectors.js';
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,9 @@ export * from './financial.js';
|
|
|
10
10
|
export * from './group.js';
|
|
11
11
|
export * from './health-dcat.js';
|
|
12
12
|
export * from './immunization.js';
|
|
13
|
+
export * from './immunization-flat-claims.js';
|
|
14
|
+
export * from './immunization-coding-review.js';
|
|
15
|
+
export * from './immunization-draft.js';
|
|
13
16
|
export * from './international-health-card.js';
|
|
14
17
|
export * from './index-projection-tags.js';
|
|
15
18
|
export * from './insurance-consent.js';
|
|
@@ -18,6 +21,7 @@ export * from './lost-animal-access.js';
|
|
|
18
21
|
export * from './organization-application.js';
|
|
19
22
|
export * from './payment.js';
|
|
20
23
|
export * from './place-service-directory.js';
|
|
24
|
+
export * from './professional-access-policy.js';
|
|
21
25
|
export * from './provenance.js';
|
|
22
26
|
export * from './research-study.js';
|
|
23
27
|
export * from './sectors.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.36",
|
|
4
4
|
"description": "Browser-safe governed VetChain data contracts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -56,6 +56,18 @@
|
|
|
56
56
|
"types": "./dist/immunization.d.ts",
|
|
57
57
|
"default": "./dist/immunization.js"
|
|
58
58
|
},
|
|
59
|
+
"./immunization-flat-claims": {
|
|
60
|
+
"types": "./dist/immunization-flat-claims.d.ts",
|
|
61
|
+
"default": "./dist/immunization-flat-claims.js"
|
|
62
|
+
},
|
|
63
|
+
"./immunization-coding-review": {
|
|
64
|
+
"types": "./dist/immunization-coding-review.d.ts",
|
|
65
|
+
"default": "./dist/immunization-coding-review.js"
|
|
66
|
+
},
|
|
67
|
+
"./immunization-draft": {
|
|
68
|
+
"types": "./dist/immunization-draft.d.ts",
|
|
69
|
+
"default": "./dist/immunization-draft.js"
|
|
70
|
+
},
|
|
59
71
|
"./international-health-card": {
|
|
60
72
|
"types": "./dist/international-health-card.d.ts",
|
|
61
73
|
"default": "./dist/international-health-card.js"
|
|
@@ -92,6 +104,10 @@
|
|
|
92
104
|
"types": "./dist/place-service-directory.d.ts",
|
|
93
105
|
"default": "./dist/place-service-directory.js"
|
|
94
106
|
},
|
|
107
|
+
"./professional-access-policy": {
|
|
108
|
+
"types": "./dist/professional-access-policy.d.ts",
|
|
109
|
+
"default": "./dist/professional-access-policy.js"
|
|
110
|
+
},
|
|
95
111
|
"./research-study": {
|
|
96
112
|
"types": "./dist/research-study.d.ts",
|
|
97
113
|
"default": "./dist/research-study.js"
|
|
@@ -141,8 +157,10 @@
|
|
|
141
157
|
"typescript": "^5.5.4"
|
|
142
158
|
},
|
|
143
159
|
"dependencies": {
|
|
160
|
+
"fhir-data-utils-ts": "0.2.4",
|
|
144
161
|
"gdc-common-utils-ts": "2.9.12",
|
|
145
|
-
"pako": "^2.2.0"
|
|
162
|
+
"pako": "^2.2.0",
|
|
163
|
+
"sos-data-utils-ts": "0.3.6"
|
|
146
164
|
},
|
|
147
165
|
"engines": {
|
|
148
166
|
"node": ">=20"
|