vet-data-utils-ts 0.5.31 → 0.5.32
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/dist/immunization-draft.d.ts +43 -0
- package/dist/immunization-draft.js +94 -0
- package/dist/immunization-flat-claims.d.ts +55 -0
- package/dist/immunization-flat-claims.js +224 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +11 -2
|
@@ -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,94 @@
|
|
|
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
|
+
performerReference: required(context.performerReference, 'veterinary_immunization_performer_required'),
|
|
50
|
+
administrationDate: draft.administrationDate,
|
|
51
|
+
vaccineCode: draft.vaccineCode,
|
|
52
|
+
targetDiseases: draft.targetDiseases,
|
|
53
|
+
...(draft.routeCode ? { route: { system: 'http://terminology.hl7.org/CodeSystem/v3-RouteOfAdministration', code: draft.routeCode } } : {}),
|
|
54
|
+
...(draft.siteCode ? { site: { system: 'http://terminology.hl7.org/CodeSystem/v3-ActSite', code: draft.siteCode } } : {}),
|
|
55
|
+
...(draft.doseValue ? { dose: { value: draft.doseValue, ...(draft.doseUnit ? { unit: draft.doseUnit } : {}) } } : {}),
|
|
56
|
+
...(draft.nextDoseDate ? { nextDoseDate: draft.nextDoseDate } : {}),
|
|
57
|
+
...(hasMedication ? { medication: {
|
|
58
|
+
entryId: `${context.entryId}-medication`,
|
|
59
|
+
productName: draft.productName || draft.vaccineCode.display || `${draft.vaccineCode.system}|${draft.vaccineCode.code}`,
|
|
60
|
+
...(draft.productCode ? { productCode: draft.productCode } : {}),
|
|
61
|
+
identifiers: draft.productIdentifiers,
|
|
62
|
+
...(draft.manufacturer ? { manufacturer: draft.manufacturer } : {}),
|
|
63
|
+
...(draft.lotNumber ? { lotNumber: draft.lotNumber } : {}),
|
|
64
|
+
...(draft.productExpirationDate ? { expirationDate: draft.productExpirationDate } : {}),
|
|
65
|
+
...(draft.barcode ? { barcode: draft.barcode } : {}),
|
|
66
|
+
} } : {}),
|
|
67
|
+
attachments,
|
|
68
|
+
attachmentEntryIds: attachments.map(({ entryId }) => entryId),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function coding(value, error) {
|
|
72
|
+
if (!value || typeof value !== 'object')
|
|
73
|
+
throw new TypeError(error);
|
|
74
|
+
const candidate = value;
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
system: required(candidate.system, error), code: required(candidate.code, error),
|
|
77
|
+
...(text(candidate.display) ? { display: text(candidate.display) } : {}),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function date(value, error) {
|
|
81
|
+
const normalized = required(value, error);
|
|
82
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(normalized) || Number.isNaN(Date.parse(normalized)))
|
|
83
|
+
throw new TypeError(error);
|
|
84
|
+
return normalized;
|
|
85
|
+
}
|
|
86
|
+
function required(value, error) {
|
|
87
|
+
const normalized = text(value);
|
|
88
|
+
if (!normalized)
|
|
89
|
+
throw new TypeError(error);
|
|
90
|
+
return normalized;
|
|
91
|
+
}
|
|
92
|
+
function text(value) {
|
|
93
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
94
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type FlatClaimResourceEntry } from 'sos-data-utils-ts/flat-claim-resource-graph';
|
|
2
|
+
export type VeterinaryImmunizationCoding = Readonly<{
|
|
3
|
+
system: string;
|
|
4
|
+
code: string;
|
|
5
|
+
display?: string;
|
|
6
|
+
}>;
|
|
7
|
+
export type VeterinaryImmunizationFlatInput = Readonly<{
|
|
8
|
+
entryId: string;
|
|
9
|
+
identifier?: string;
|
|
10
|
+
animalReference: string;
|
|
11
|
+
administrationDate: string;
|
|
12
|
+
vaccineCode: VeterinaryImmunizationCoding;
|
|
13
|
+
performerReference: string;
|
|
14
|
+
targetDiseases?: readonly VeterinaryImmunizationCoding[];
|
|
15
|
+
route?: VeterinaryImmunizationCoding;
|
|
16
|
+
site?: VeterinaryImmunizationCoding;
|
|
17
|
+
dose?: Readonly<{
|
|
18
|
+
value: string;
|
|
19
|
+
unit?: string;
|
|
20
|
+
}>;
|
|
21
|
+
nextDoseDate?: string;
|
|
22
|
+
medication?: Readonly<{
|
|
23
|
+
entryId: string;
|
|
24
|
+
productName: string;
|
|
25
|
+
productCode?: VeterinaryImmunizationCoding;
|
|
26
|
+
identifiers?: readonly Readonly<{
|
|
27
|
+
system: string;
|
|
28
|
+
value: string;
|
|
29
|
+
}>[];
|
|
30
|
+
manufacturer?: string;
|
|
31
|
+
lotNumber?: string;
|
|
32
|
+
expirationDate?: string;
|
|
33
|
+
barcode?: string;
|
|
34
|
+
}>;
|
|
35
|
+
attachments?: readonly FlatClaimResourceEntry[];
|
|
36
|
+
attachmentEntryIds?: readonly string[];
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Creates a version-neutral flat graph. SearchParameter names are preferred
|
|
40
|
+
* (`Immunization.date`, `Medication.expiration-date`) and product evidence is
|
|
41
|
+
* kept in Medication instead of overloading credential validity.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildVeterinaryImmunizationFlatGraph(input: VeterinaryImmunizationFlatInput): readonly FlatClaimResourceEntry[];
|
|
44
|
+
export type FhirExportRelease = 'R4' | 'R5' | 'R6';
|
|
45
|
+
/** Explicit adapter boundary from canonical flat claims to a selected FHIR release. */
|
|
46
|
+
export declare function exportVeterinaryImmunizationFlatGraph(graph: readonly FlatClaimResourceEntry[], release: FhirExportRelease): Readonly<{
|
|
47
|
+
resourceType: 'Bundle';
|
|
48
|
+
type: 'collection';
|
|
49
|
+
entry: readonly Readonly<{
|
|
50
|
+
fullUrl: string;
|
|
51
|
+
resource: Record<string, unknown>;
|
|
52
|
+
}>[];
|
|
53
|
+
}>;
|
|
54
|
+
/** De-identified three-event fixture based on the supplied Canadian certificate; no owner contact data is retained. */
|
|
55
|
+
export declare const NALA_VACCINATION_FLAT_GRAPH_TEST_DATA: readonly FlatClaimResourceEntry[];
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { buildDocumentReferenceFlatEntry, buildFlatClaimResourceEntry, linkFlatClaimResourceEntries, } from 'sos-data-utils-ts/flat-claim-resource-graph';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a version-neutral flat graph. SearchParameter names are preferred
|
|
4
|
+
* (`Immunization.date`, `Medication.expiration-date`) and product evidence is
|
|
5
|
+
* kept in Medication instead of overloading credential validity.
|
|
6
|
+
*/
|
|
7
|
+
export function buildVeterinaryImmunizationFlatGraph(input) {
|
|
8
|
+
const vaccineCode = codingToken(input.vaccineCode);
|
|
9
|
+
const claims = {
|
|
10
|
+
'Immunization.status': 'completed',
|
|
11
|
+
'Immunization.patient': required(input.animalReference, 'veterinary_immunization_animal_reference_required'),
|
|
12
|
+
'Immunization.date': isoDate(input.administrationDate, 'veterinary_immunization_date_invalid'),
|
|
13
|
+
'Immunization.vaccine-code': vaccineCode,
|
|
14
|
+
'Immunization.performer': required(input.performerReference, 'veterinary_immunization_performer_required'),
|
|
15
|
+
};
|
|
16
|
+
if (input.identifier)
|
|
17
|
+
claims['Immunization.identifier'] = required(input.identifier, 'veterinary_immunization_identifier_invalid');
|
|
18
|
+
if (input.vaccineCode.display)
|
|
19
|
+
claims['Immunization.vaccine-code-display'] = input.vaccineCode.display.trim();
|
|
20
|
+
if (input.targetDiseases?.length)
|
|
21
|
+
claims['Immunization.target-disease'] = input.targetDiseases.map(codingToken).join(',');
|
|
22
|
+
if (input.route)
|
|
23
|
+
claims['Immunization.route'] = codingToken(input.route);
|
|
24
|
+
if (input.site)
|
|
25
|
+
claims['Immunization.site'] = codingToken(input.site);
|
|
26
|
+
if (input.dose) {
|
|
27
|
+
claims['Immunization.dose-quantity-value'] = required(input.dose.value, 'veterinary_immunization_dose_invalid');
|
|
28
|
+
if (input.dose.unit)
|
|
29
|
+
claims['Immunization.dose-quantity-unit'] = input.dose.unit.trim();
|
|
30
|
+
}
|
|
31
|
+
const immunization = buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'Immunization', claims });
|
|
32
|
+
const children = [];
|
|
33
|
+
if (input.medication)
|
|
34
|
+
children.push(buildMedicationEntry(input.medication));
|
|
35
|
+
if (input.nextDoseDate) {
|
|
36
|
+
children.push(buildFlatClaimResourceEntry({
|
|
37
|
+
entryId: `${input.entryId}-recommendation`,
|
|
38
|
+
resourceType: 'ImmunizationRecommendation',
|
|
39
|
+
claims: {
|
|
40
|
+
'ImmunizationRecommendation.patient': input.animalReference,
|
|
41
|
+
'ImmunizationRecommendation.date': isoDate(input.nextDoseDate, 'veterinary_immunization_next_dose_date_invalid'),
|
|
42
|
+
'ImmunizationRecommendation.vaccine-code': vaccineCode,
|
|
43
|
+
},
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
for (const attachment of input.attachments ?? []) {
|
|
47
|
+
if (attachment.resourceType !== 'DocumentReference')
|
|
48
|
+
throw new TypeError('veterinary_immunization_attachment_invalid');
|
|
49
|
+
children.push(attachment);
|
|
50
|
+
}
|
|
51
|
+
const graph = uniqueEntries([immunization, ...children]);
|
|
52
|
+
const childEntryIds = [
|
|
53
|
+
...(input.medication ? [input.medication.entryId] : []),
|
|
54
|
+
...(input.nextDoseDate ? [`${input.entryId}-recommendation`] : []),
|
|
55
|
+
...(input.attachmentEntryIds ?? []),
|
|
56
|
+
];
|
|
57
|
+
return childEntryIds.length
|
|
58
|
+
? linkFlatClaimResourceEntries(graph, { parentEntryId: input.entryId, childEntryIds })
|
|
59
|
+
: graph;
|
|
60
|
+
}
|
|
61
|
+
function buildMedicationEntry(input) {
|
|
62
|
+
const claims = {
|
|
63
|
+
'Medication.code': input.productCode ? codingToken(input.productCode) : required(input.productName, 'veterinary_medication_product_required'),
|
|
64
|
+
'Medication.code-text': required(input.productName, 'veterinary_medication_product_required'),
|
|
65
|
+
};
|
|
66
|
+
if (input.identifiers?.length) {
|
|
67
|
+
claims['Medication.identifier'] = input.identifiers.map(({ system, value }) => (`${required(system, 'veterinary_medication_identifier_invalid')}|${required(value, 'veterinary_medication_identifier_invalid')}`)).join(',');
|
|
68
|
+
}
|
|
69
|
+
if (input.manufacturer)
|
|
70
|
+
claims['Medication.manufacturer'] = input.manufacturer.trim();
|
|
71
|
+
if (input.lotNumber)
|
|
72
|
+
claims['Medication.lot-number'] = input.lotNumber.trim();
|
|
73
|
+
if (input.expirationDate)
|
|
74
|
+
claims['Medication.expiration-date'] = isoDate(input.expirationDate, 'veterinary_medication_expiration_date_invalid');
|
|
75
|
+
if (input.barcode)
|
|
76
|
+
claims['Medication.serial-number'] = input.barcode.trim();
|
|
77
|
+
return buildFlatClaimResourceEntry({ entryId: input.entryId, resourceType: 'Medication', claims });
|
|
78
|
+
}
|
|
79
|
+
/** Explicit adapter boundary from canonical flat claims to a selected FHIR release. */
|
|
80
|
+
export function exportVeterinaryImmunizationFlatGraph(graph, release) {
|
|
81
|
+
const byReference = new Map(graph.map((entry) => [entry.reference, entry]));
|
|
82
|
+
return Object.freeze({
|
|
83
|
+
resourceType: 'Bundle',
|
|
84
|
+
type: 'collection',
|
|
85
|
+
entry: Object.freeze(graph.map((entry) => Object.freeze({
|
|
86
|
+
fullUrl: entry.reference,
|
|
87
|
+
resource: exportEntry(entry, release, byReference),
|
|
88
|
+
}))),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function exportEntry(entry, release, byReference) {
|
|
92
|
+
const claims = entry.claims;
|
|
93
|
+
if (entry.resourceType === 'Immunization') {
|
|
94
|
+
const vaccineCode = codeableConcept(claims['Immunization.vaccine-code'], claims['Immunization.vaccine-code-display']);
|
|
95
|
+
const children = splitCsv(claims['Immunization.contained-reference-list']).map((reference) => byReference.get(reference)).filter(Boolean);
|
|
96
|
+
const medication = children.find((child) => child?.resourceType === 'Medication');
|
|
97
|
+
const resource = {
|
|
98
|
+
resourceType: 'Immunization', id: entry.entryId, status: claims['Immunization.status'],
|
|
99
|
+
vaccineCode, patient: { reference: claims['Immunization.patient'] },
|
|
100
|
+
occurrenceDateTime: claims['Immunization.date'],
|
|
101
|
+
performer: [{ actor: { reference: claims['Immunization.performer'] } }],
|
|
102
|
+
};
|
|
103
|
+
if (claims['Immunization.identifier'])
|
|
104
|
+
resource.identifier = [{ value: claims['Immunization.identifier'] }];
|
|
105
|
+
if (claims['Immunization.route'])
|
|
106
|
+
resource.route = codeableConcept(claims['Immunization.route']);
|
|
107
|
+
if (claims['Immunization.site'])
|
|
108
|
+
resource.site = codeableConcept(claims['Immunization.site']);
|
|
109
|
+
if (claims['Immunization.target-disease'])
|
|
110
|
+
resource.protocolApplied = [{ targetDisease: splitCsv(claims['Immunization.target-disease']).map((code) => codeableConcept(code)) }];
|
|
111
|
+
if (medication && release !== 'R4')
|
|
112
|
+
resource.administeredProduct = { reference: { reference: medication.reference } };
|
|
113
|
+
return resource;
|
|
114
|
+
}
|
|
115
|
+
if (entry.resourceType === 'Medication') {
|
|
116
|
+
const lotNumber = claims['Medication.lot-number'];
|
|
117
|
+
const expirationDate = claims['Medication.expiration-date'];
|
|
118
|
+
const resource = {
|
|
119
|
+
resourceType: 'Medication', id: entry.entryId,
|
|
120
|
+
code: codeableConcept(claims['Medication.code'], claims['Medication.code-text']),
|
|
121
|
+
};
|
|
122
|
+
if (claims['Medication.identifier'])
|
|
123
|
+
resource.identifier = splitCsv(claims['Medication.identifier']).map(identifierFromToken);
|
|
124
|
+
if (lotNumber || expirationDate)
|
|
125
|
+
resource[release === 'R6' ? 'instance' : 'batch'] = {
|
|
126
|
+
...(lotNumber ? { lotNumber } : {}), ...(expirationDate ? { expirationDate } : {}),
|
|
127
|
+
};
|
|
128
|
+
return resource;
|
|
129
|
+
}
|
|
130
|
+
if (entry.resourceType === 'ImmunizationRecommendation')
|
|
131
|
+
return {
|
|
132
|
+
resourceType: 'ImmunizationRecommendation', id: entry.entryId,
|
|
133
|
+
patient: { reference: claims['ImmunizationRecommendation.patient'] },
|
|
134
|
+
date: claims['ImmunizationRecommendation.date'],
|
|
135
|
+
recommendation: [{ vaccineCode: [codeableConcept(claims['ImmunizationRecommendation.vaccine-code'])] }],
|
|
136
|
+
};
|
|
137
|
+
if (entry.resourceType === 'DocumentReference')
|
|
138
|
+
return {
|
|
139
|
+
resourceType: 'DocumentReference', id: entry.entryId, status: 'current',
|
|
140
|
+
identifier: [{ value: claims['DocumentReference.identifier'] }],
|
|
141
|
+
content: [{ attachment: {
|
|
142
|
+
contentType: claims['DocumentReference.content-type'],
|
|
143
|
+
...(claims['DocumentReference.url'] ? { url: claims['DocumentReference.url'] } : {}),
|
|
144
|
+
...(claims['DocumentReference.title'] ? { title: claims['DocumentReference.title'] } : {}),
|
|
145
|
+
} }],
|
|
146
|
+
};
|
|
147
|
+
return { resourceType: entry.resourceType, id: entry.entryId, meta: { claims: { ...claims } } };
|
|
148
|
+
}
|
|
149
|
+
function codeableConcept(token, display) {
|
|
150
|
+
const value = required(token, 'veterinary_coding_required');
|
|
151
|
+
const separator = value.lastIndexOf('|');
|
|
152
|
+
if (separator <= 0 || separator === value.length - 1)
|
|
153
|
+
return { text: display || value };
|
|
154
|
+
return { coding: [{ system: value.slice(0, separator), code: value.slice(separator + 1), ...(display ? { display } : {}) }] };
|
|
155
|
+
}
|
|
156
|
+
function identifierFromToken(token) {
|
|
157
|
+
const separator = token.lastIndexOf('|');
|
|
158
|
+
return separator > 0 ? { system: token.slice(0, separator), value: token.slice(separator + 1) } : { value: token };
|
|
159
|
+
}
|
|
160
|
+
function codingToken(coding) {
|
|
161
|
+
return `${required(coding.system, 'veterinary_coding_system_required')}|${required(coding.code, 'veterinary_coding_code_required')}`;
|
|
162
|
+
}
|
|
163
|
+
function isoDate(value, error) {
|
|
164
|
+
const normalized = required(value, error);
|
|
165
|
+
if (!/^\d{4}-\d{2}-\d{2}(?:T.*)?$/.test(normalized) || Number.isNaN(Date.parse(normalized)))
|
|
166
|
+
throw new TypeError(error);
|
|
167
|
+
return normalized;
|
|
168
|
+
}
|
|
169
|
+
function required(value, error) {
|
|
170
|
+
if (typeof value !== 'string' || !value.trim())
|
|
171
|
+
throw new TypeError(error);
|
|
172
|
+
return value.trim();
|
|
173
|
+
}
|
|
174
|
+
function splitCsv(value) {
|
|
175
|
+
return value?.split(',').map((item) => item.trim()).filter(Boolean) ?? [];
|
|
176
|
+
}
|
|
177
|
+
function uniqueEntries(entries) {
|
|
178
|
+
const byId = new Map();
|
|
179
|
+
for (const entry of entries) {
|
|
180
|
+
const existing = byId.get(entry.entryId);
|
|
181
|
+
if (existing && existing.reference !== entry.reference)
|
|
182
|
+
throw new TypeError('flat_claim_entry_id_collision');
|
|
183
|
+
byId.set(entry.entryId, existing ?? entry);
|
|
184
|
+
}
|
|
185
|
+
return Object.freeze([...byId.values()]);
|
|
186
|
+
}
|
|
187
|
+
const atcVet = 'http://www.whocc.no/atcvet';
|
|
188
|
+
const certificate = buildDocumentReferenceFlatEntry({
|
|
189
|
+
entryId: 'nala-vaccination-certificate', identifier: 'urn:uuid:nala-vaccination-certificate',
|
|
190
|
+
contentType: 'image/jpeg', contentHash: 'sha256:test-only-nala-certificate-digest', title: 'Vaccination certificate',
|
|
191
|
+
});
|
|
192
|
+
/** De-identified three-event fixture based on the supplied Canadian certificate; no owner contact data is retained. */
|
|
193
|
+
export const NALA_VACCINATION_FLAT_GRAPH_TEST_DATA = uniqueEntries([
|
|
194
|
+
...buildVeterinaryImmunizationFlatGraph({
|
|
195
|
+
entryId: 'nala-rabies', animalReference: 'Patient/nala', administrationDate: '2024-05-23',
|
|
196
|
+
performerReference: 'PractitionerRole/certificate-veterinarian',
|
|
197
|
+
vaccineCode: { system: atcVet, code: 'QI07AA02', display: 'Inactivated rabies virus vaccines' },
|
|
198
|
+
targetDiseases: [{ system: 'http://snomed.info/sct', code: '14168008', display: 'Rabies' }],
|
|
199
|
+
nextDoseDate: '2027-05-23',
|
|
200
|
+
medication: {
|
|
201
|
+
entryId: 'nala-rabies-product', productName: 'Imrab 3 TF',
|
|
202
|
+
productCode: { system: atcVet, code: 'QI07AA02' }, manufacturer: 'Boehringer Ingelheim',
|
|
203
|
+
identifiers: [
|
|
204
|
+
{ system: 'https://inspection.canada.ca/ccvb', value: '820VV/R1.14/R2.1' },
|
|
205
|
+
{ system: 'https://www.aphis.usda.gov/cvb/product-code', value: '1905.26' },
|
|
206
|
+
],
|
|
207
|
+
lotNumber: '18595', expirationDate: '2025-06-28',
|
|
208
|
+
},
|
|
209
|
+
route: { system: 'http://terminology.hl7.org/CodeSystem/v3-RouteOfAdministration', code: 'SQ', display: 'Subcutaneous' },
|
|
210
|
+
attachments: [certificate], attachmentEntryIds: [certificate.entryId],
|
|
211
|
+
}),
|
|
212
|
+
...buildVeterinaryImmunizationFlatGraph({
|
|
213
|
+
entryId: 'nala-da2plp', animalReference: 'Patient/nala', administrationDate: '2024-05-23',
|
|
214
|
+
performerReference: 'PractitionerRole/certificate-veterinarian',
|
|
215
|
+
vaccineCode: { system: atcVet, code: 'QI07AI02', display: 'Canine viral and bacterial combination vaccines' },
|
|
216
|
+
nextDoseDate: '2025-05-23', attachments: [certificate], attachmentEntryIds: [certificate.entryId],
|
|
217
|
+
}),
|
|
218
|
+
...buildVeterinaryImmunizationFlatGraph({
|
|
219
|
+
entryId: 'nala-lyme', animalReference: 'Patient/nala', administrationDate: '2024-05-23',
|
|
220
|
+
performerReference: 'PractitionerRole/certificate-veterinarian',
|
|
221
|
+
vaccineCode: { system: atcVet, code: 'QI07AB04', display: 'Borrelia vaccines' },
|
|
222
|
+
nextDoseDate: '2025-05-23', attachments: [certificate], attachmentEntryIds: [certificate.entryId],
|
|
223
|
+
}),
|
|
224
|
+
]);
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ 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-draft.js';
|
|
13
15
|
export * from './international-health-card.js';
|
|
14
16
|
export * from './index-projection-tags.js';
|
|
15
17
|
export * from './insurance-consent.js';
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,8 @@ 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-draft.js';
|
|
13
15
|
export * from './international-health-card.js';
|
|
14
16
|
export * from './index-projection-tags.js';
|
|
15
17
|
export * from './insurance-consent.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.32",
|
|
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,14 @@
|
|
|
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-draft": {
|
|
64
|
+
"types": "./dist/immunization-draft.d.ts",
|
|
65
|
+
"default": "./dist/immunization-draft.js"
|
|
66
|
+
},
|
|
59
67
|
"./international-health-card": {
|
|
60
68
|
"types": "./dist/international-health-card.d.ts",
|
|
61
69
|
"default": "./dist/international-health-card.js"
|
|
@@ -142,7 +150,8 @@
|
|
|
142
150
|
},
|
|
143
151
|
"dependencies": {
|
|
144
152
|
"gdc-common-utils-ts": "2.9.12",
|
|
145
|
-
"pako": "^2.2.0"
|
|
153
|
+
"pako": "^2.2.0",
|
|
154
|
+
"sos-data-utils-ts": "0.3.1"
|
|
146
155
|
},
|
|
147
156
|
"engines": {
|
|
148
157
|
"node": ">=20"
|