vet-data-utils-ts 0.5.29 → 0.5.31
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 +27 -0
- package/dist/animal-behavior.d.ts +18 -0
- package/dist/animal-behavior.js +44 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lost-animal-access.d.ts +23 -0
- package/dist/lost-animal-access.js +45 -0
- package/dist/veterinary-biologic-product.d.ts +184 -0
- package/dist/veterinary-biologic-product.js +171 -0
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# vet-data-utils-ts
|
|
2
2
|
|
|
3
|
+
## Lost-animal access and behavior
|
|
4
|
+
|
|
5
|
+
`normalizeIso3166Jurisdiction(...)` and
|
|
6
|
+
`buildLostAnimalEmergencyJurisdictionActors(...)` create exact country or
|
|
7
|
+
subdivision Consent actors such as `urn:iso:3166-2:CA-BC`. The
|
|
8
|
+
`LostAnimalEmergencyAccess` profile limits standing access to licensed
|
|
9
|
+
veterinarians, emergency treatment and a minimal veterinary summary. GW still
|
|
10
|
+
verifies the credential, active licence, route jurisdiction and exact animal.
|
|
11
|
+
|
|
12
|
+
`buildAnimalBehaviorObservation(...)` records controller-reported temperament
|
|
13
|
+
and fear context as a claims-first social-history Observation. It deliberately
|
|
14
|
+
uses narrative text rather than inventing a veterinary diagnosis code.
|
|
15
|
+
|
|
3
16
|
The product-local `place-service-directory` export models persistent public
|
|
4
17
|
sites as claims-only Schema.org `Place` resources and their offerings as
|
|
5
18
|
claims-only `Service` or `Product` resources. A care organization publishes
|
|
@@ -176,6 +189,20 @@ for immunologicals); ordinary human ATC is not substituted for it. Veterinary
|
|
|
176
189
|
allergy product coding may also use ATCvet, while manifestations use an allowed
|
|
177
190
|
SNOMED source.
|
|
178
191
|
|
|
192
|
+
`vet-data-utils-ts/veterinary-biologic-product` keeps three independent axes:
|
|
193
|
+
|
|
194
|
+
- WHO ATCvet QI is the international medicine classification;
|
|
195
|
+
- the authorized commercial product and its identifier are regional, sourced
|
|
196
|
+
from [EMA UPD](https://medicines.health.europa.eu/veterinary/),
|
|
197
|
+
[USDA APHIS CVB](https://www.aphis.usda.gov/veterinary-biologics/licensed-products)
|
|
198
|
+
or [CFIA CCVB](https://active.inspection.gc.ca/netapp/veterinarybio-bioveterinaire/vetbioe.aspx);
|
|
199
|
+
- `targetDiseases` contains the separately coded diseases/antigens used by
|
|
200
|
+
FHIR `Immunization.protocolApplied.targetDisease`.
|
|
201
|
+
|
|
202
|
+
Source adapters preserve the authority's original product/true/trade names,
|
|
203
|
+
manufacturer, agents and identifiers. Filtering uses reviewed exact NCBI taxa;
|
|
204
|
+
broad words such as “poultry” are source labels, not invented species IDs.
|
|
205
|
+
|
|
179
206
|
The shared IPS reader boundary is `projectClinicalBundleForReading(...)` from
|
|
180
207
|
`vet-data-utils-ts/clinical-bundle-reader`. It accepts a native FHIR Bundle
|
|
181
208
|
(including R4/R5 resources supported by the common normalizer) or resources
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type AnimalBehaviorObservationInput = Readonly<{
|
|
2
|
+
id: string;
|
|
3
|
+
subject: string;
|
|
4
|
+
observedAt: string;
|
|
5
|
+
language: string;
|
|
6
|
+
summary: string;
|
|
7
|
+
note?: string;
|
|
8
|
+
localLabel?: string;
|
|
9
|
+
}>;
|
|
10
|
+
export type AnimalBehaviorObservation = Readonly<{
|
|
11
|
+
resourceType: 'Observation';
|
|
12
|
+
id: string;
|
|
13
|
+
meta: Readonly<{
|
|
14
|
+
claims: Readonly<Record<string, string>>;
|
|
15
|
+
}>;
|
|
16
|
+
}>;
|
|
17
|
+
/** Builds one claims-first, controller-reported behavior Observation without inventing a diagnosis code. */
|
|
18
|
+
export declare function buildAnimalBehaviorObservation(input: AnimalBehaviorObservationInput): AnimalBehaviorObservation;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ObservationCategoryCodes } from 'gdc-common-utils-ts/constants/observation-category';
|
|
2
|
+
import { ObservationClaim } from 'gdc-common-utils-ts/models/interoperable-claims/observation-claims';
|
|
3
|
+
import { isVetChainAnimalCardDid } from './animal-card.js';
|
|
4
|
+
const ENGLISH_DISPLAY = 'Animal temperament and fear response';
|
|
5
|
+
function boundedText(value, name, max) {
|
|
6
|
+
const normalized = String(value || '').trim();
|
|
7
|
+
if (!normalized || normalized.length > max)
|
|
8
|
+
throw new TypeError(`animal_behavior_${name}_invalid`);
|
|
9
|
+
return normalized;
|
|
10
|
+
}
|
|
11
|
+
/** Builds one claims-first, controller-reported behavior Observation without inventing a diagnosis code. */
|
|
12
|
+
export function buildAnimalBehaviorObservation(input) {
|
|
13
|
+
const id = String(input.id || '').trim();
|
|
14
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id))
|
|
15
|
+
throw new TypeError('animal_behavior_id_invalid');
|
|
16
|
+
const subject = String(input.subject || '').trim();
|
|
17
|
+
if (!isVetChainAnimalCardDid(subject))
|
|
18
|
+
throw new TypeError('animal_behavior_animal_card_did_required');
|
|
19
|
+
const observedAt = String(input.observedAt || '').trim();
|
|
20
|
+
if (!observedAt || Number.isNaN(Date.parse(observedAt)))
|
|
21
|
+
throw new TypeError('animal_behavior_observedAt_invalid');
|
|
22
|
+
const language = String(input.language || '').trim();
|
|
23
|
+
if (!/^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/.test(language))
|
|
24
|
+
throw new TypeError('animal_behavior_language_invalid');
|
|
25
|
+
const summary = boundedText(input.summary, 'summary', 1000);
|
|
26
|
+
const note = input.note === undefined ? undefined : boundedText(input.note, 'note', 1000);
|
|
27
|
+
const localLabel = input.localLabel === undefined ? ENGLISH_DISPLAY : boundedText(input.localLabel, 'local_label', 120);
|
|
28
|
+
const claims = {
|
|
29
|
+
'@context': 'org.hl7.fhir.api',
|
|
30
|
+
[ObservationClaim.Identifier]: id,
|
|
31
|
+
[ObservationClaim.Subject]: subject,
|
|
32
|
+
[ObservationClaim.Status]: 'final',
|
|
33
|
+
[ObservationClaim.Category]: ObservationCategoryCodes.SocialHistory.claim,
|
|
34
|
+
[ObservationClaim.CodeText]: localLabel,
|
|
35
|
+
[ObservationClaim.CodeDisplay]: ENGLISH_DISPLAY,
|
|
36
|
+
[ObservationClaim.ValueString]: summary,
|
|
37
|
+
[ObservationClaim.Date]: observedAt,
|
|
38
|
+
[ObservationClaim.EffectiveDateTime]: observedAt,
|
|
39
|
+
[ObservationClaim.Language]: language,
|
|
40
|
+
};
|
|
41
|
+
if (note)
|
|
42
|
+
claims[ObservationClaim.Note] = note;
|
|
43
|
+
return Object.freeze({ resourceType: 'Observation', id, meta: Object.freeze({ claims: Object.freeze(claims) }) });
|
|
44
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './animal-card.js';
|
|
2
|
+
export * from './animal-behavior.js';
|
|
2
3
|
export * from './assistant.js';
|
|
3
4
|
export * from './communication.js';
|
|
4
5
|
export * from './clinical-terminology.js';
|
|
@@ -13,6 +14,7 @@ export * from './international-health-card.js';
|
|
|
13
14
|
export * from './index-projection-tags.js';
|
|
14
15
|
export * from './insurance-consent.js';
|
|
15
16
|
export * from './iso-jurisdictions.js';
|
|
17
|
+
export * from './lost-animal-access.js';
|
|
16
18
|
export * from './organization-application.js';
|
|
17
19
|
export * from './payment.js';
|
|
18
20
|
export * from './place-service-directory.js';
|
|
@@ -21,4 +23,5 @@ export * from './research-study.js';
|
|
|
21
23
|
export * from './sectors.js';
|
|
22
24
|
export * from './shc.js';
|
|
23
25
|
export * from './veterinary-sections.js';
|
|
26
|
+
export * from './veterinary-biologic-product.js';
|
|
24
27
|
export * from './scheduling.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './animal-card.js';
|
|
2
|
+
export * from './animal-behavior.js';
|
|
2
3
|
export * from './assistant.js';
|
|
3
4
|
export * from './communication.js';
|
|
4
5
|
export * from './clinical-terminology.js';
|
|
@@ -13,6 +14,7 @@ export * from './international-health-card.js';
|
|
|
13
14
|
export * from './index-projection-tags.js';
|
|
14
15
|
export * from './insurance-consent.js';
|
|
15
16
|
export * from './iso-jurisdictions.js';
|
|
17
|
+
export * from './lost-animal-access.js';
|
|
16
18
|
export * from './organization-application.js';
|
|
17
19
|
export * from './payment.js';
|
|
18
20
|
export * from './place-service-directory.js';
|
|
@@ -21,4 +23,5 @@ export * from './research-study.js';
|
|
|
21
23
|
export * from './sectors.js';
|
|
22
24
|
export * from './shc.js';
|
|
23
25
|
export * from './veterinary-sections.js';
|
|
26
|
+
export * from './veterinary-biologic-product.js';
|
|
24
27
|
export * from './scheduling.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type Iso3166Jurisdiction = Readonly<{
|
|
2
|
+
code: string;
|
|
3
|
+
countryCode: string;
|
|
4
|
+
kind: 'country' | 'subdivision';
|
|
5
|
+
actorIdentifier: string;
|
|
6
|
+
}>;
|
|
7
|
+
/** Normalizes an ISO 3166 country or subdivision into its canonical Consent actor. */
|
|
8
|
+
export declare function normalizeIso3166Jurisdiction(input: unknown): Iso3166Jurisdiction;
|
|
9
|
+
/** Builds a deterministic, duplicate-free actor list for controller-authored regional Consent rules. */
|
|
10
|
+
export declare function buildLostAnimalEmergencyJurisdictionActors(input: readonly unknown[]): readonly string[];
|
|
11
|
+
/**
|
|
12
|
+
* Least-privilege standing access profile for a lost animal.
|
|
13
|
+
*
|
|
14
|
+
* A matching rule is not proof that the requester is a veterinarian: GW must
|
|
15
|
+
* independently verify the professional credential, active licence, route
|
|
16
|
+
* jurisdiction and exact animal subject before issuing a short-lived token.
|
|
17
|
+
*/
|
|
18
|
+
export declare const LostAnimalEmergencyAccess: Readonly<{
|
|
19
|
+
actorRole: "ISCO-08|2250";
|
|
20
|
+
purpose: "ETREAT";
|
|
21
|
+
resourceTypes: readonly ["Composition"];
|
|
22
|
+
sections: readonly `LOINC|${string}`[];
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { HealthcareActorRoles, HealthcareConsentPurposes } from 'gdc-common-utils-ts/constants/healthcare';
|
|
2
|
+
import { isIso3166CountryCode, isIso3166SubdivisionForCountry } from './iso-jurisdictions.js';
|
|
3
|
+
import { VeterinarySummarySections } from './veterinary-sections.js';
|
|
4
|
+
const countryActor = /^urn:iso:3166:([a-z]{2})$/i;
|
|
5
|
+
const subdivisionActor = /^urn:iso:3166-2:([a-z]{2}-[a-z0-9]{1,3})$/i;
|
|
6
|
+
/** Normalizes an ISO 3166 country or subdivision into its canonical Consent actor. */
|
|
7
|
+
export function normalizeIso3166Jurisdiction(input) {
|
|
8
|
+
const raw = String(input || '').trim();
|
|
9
|
+
const unwrapped = countryActor.exec(raw)?.[1] || subdivisionActor.exec(raw)?.[1] || raw;
|
|
10
|
+
const code = unwrapped.toUpperCase();
|
|
11
|
+
const countryCode = code.slice(0, 2);
|
|
12
|
+
if (code.length === 2 && isIso3166CountryCode(code)) {
|
|
13
|
+
return Object.freeze({ code, countryCode, kind: 'country', actorIdentifier: `urn:iso:3166:${code}` });
|
|
14
|
+
}
|
|
15
|
+
if (isIso3166CountryCode(countryCode) && isIso3166SubdivisionForCountry(code, countryCode)) {
|
|
16
|
+
return Object.freeze({ code, countryCode, kind: 'subdivision', actorIdentifier: `urn:iso:3166-2:${code}` });
|
|
17
|
+
}
|
|
18
|
+
throw new TypeError('lost_animal_access_iso_3166_jurisdiction_invalid');
|
|
19
|
+
}
|
|
20
|
+
/** Builds a deterministic, duplicate-free actor list for controller-authored regional Consent rules. */
|
|
21
|
+
export function buildLostAnimalEmergencyJurisdictionActors(input) {
|
|
22
|
+
if (!Array.isArray(input) || input.length === 0)
|
|
23
|
+
throw new TypeError('lost_animal_access_jurisdiction_required');
|
|
24
|
+
return Object.freeze([...new Set(input.map(value => normalizeIso3166Jurisdiction(value).actorIdentifier))]);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Least-privilege standing access profile for a lost animal.
|
|
28
|
+
*
|
|
29
|
+
* A matching rule is not proof that the requester is a veterinarian: GW must
|
|
30
|
+
* independently verify the professional credential, active licence, route
|
|
31
|
+
* jurisdiction and exact animal subject before issuing a short-lived token.
|
|
32
|
+
*/
|
|
33
|
+
export const LostAnimalEmergencyAccess = Object.freeze({
|
|
34
|
+
actorRole: HealthcareActorRoles.Veterinarian,
|
|
35
|
+
purpose: HealthcareConsentPurposes.EmergencyTreatment,
|
|
36
|
+
resourceTypes: Object.freeze(['Composition']),
|
|
37
|
+
sections: Object.freeze([
|
|
38
|
+
VeterinarySummarySections.Alerts.value,
|
|
39
|
+
VeterinarySummarySections.Allergies.value,
|
|
40
|
+
VeterinarySummarySections.Medications.value,
|
|
41
|
+
VeterinarySummarySections.Immunizations.value,
|
|
42
|
+
VeterinarySummarySections.Problems.value,
|
|
43
|
+
VeterinarySummarySections.EnvironmentAndLifestyle.value,
|
|
44
|
+
]),
|
|
45
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
export declare const NcbiTaxonomySystem: "https://www.ncbi.nlm.nih.gov/taxonomy";
|
|
2
|
+
export type VeterinarySpeciesTaxon = Readonly<{
|
|
3
|
+
system: typeof NcbiTaxonomySystem;
|
|
4
|
+
code: string;
|
|
5
|
+
display: string;
|
|
6
|
+
}>;
|
|
7
|
+
/** Exact taxa used by shared animal-product filters; broad groups are not identities. */
|
|
8
|
+
export declare const VeterinarySpeciesTaxonomy: Readonly<{
|
|
9
|
+
readonly Dog: Readonly<{
|
|
10
|
+
system: typeof NcbiTaxonomySystem;
|
|
11
|
+
code: string;
|
|
12
|
+
display: string;
|
|
13
|
+
}>;
|
|
14
|
+
readonly Cat: Readonly<{
|
|
15
|
+
system: typeof NcbiTaxonomySystem;
|
|
16
|
+
code: string;
|
|
17
|
+
display: string;
|
|
18
|
+
}>;
|
|
19
|
+
readonly Horse: Readonly<{
|
|
20
|
+
system: typeof NcbiTaxonomySystem;
|
|
21
|
+
code: string;
|
|
22
|
+
display: string;
|
|
23
|
+
}>;
|
|
24
|
+
readonly Cattle: Readonly<{
|
|
25
|
+
system: typeof NcbiTaxonomySystem;
|
|
26
|
+
code: string;
|
|
27
|
+
display: string;
|
|
28
|
+
}>;
|
|
29
|
+
readonly Pig: Readonly<{
|
|
30
|
+
system: typeof NcbiTaxonomySystem;
|
|
31
|
+
code: string;
|
|
32
|
+
display: string;
|
|
33
|
+
}>;
|
|
34
|
+
readonly Sheep: Readonly<{
|
|
35
|
+
system: typeof NcbiTaxonomySystem;
|
|
36
|
+
code: string;
|
|
37
|
+
display: string;
|
|
38
|
+
}>;
|
|
39
|
+
readonly Goat: Readonly<{
|
|
40
|
+
system: typeof NcbiTaxonomySystem;
|
|
41
|
+
code: string;
|
|
42
|
+
display: string;
|
|
43
|
+
}>;
|
|
44
|
+
readonly Chicken: Readonly<{
|
|
45
|
+
system: typeof NcbiTaxonomySystem;
|
|
46
|
+
code: string;
|
|
47
|
+
display: string;
|
|
48
|
+
}>;
|
|
49
|
+
}>;
|
|
50
|
+
export declare const VeterinaryBiologicAuthorizationRegions: Readonly<{
|
|
51
|
+
readonly EuEea: "EU-EEA";
|
|
52
|
+
readonly Us: "US";
|
|
53
|
+
readonly Ca: "CA";
|
|
54
|
+
}>;
|
|
55
|
+
export type VeterinaryBiologicAuthorizationRegion = typeof VeterinaryBiologicAuthorizationRegions[keyof typeof VeterinaryBiologicAuthorizationRegions];
|
|
56
|
+
export declare const VeterinaryBiologicProductSources: Readonly<{
|
|
57
|
+
readonly EmaUpd: "ema-upd";
|
|
58
|
+
readonly UsdaCvb: "usda-cvb";
|
|
59
|
+
readonly CfiaCcvb: "cfia-ccvb";
|
|
60
|
+
}>;
|
|
61
|
+
export declare const VeterinaryBiologicIdentifierKinds: Readonly<{
|
|
62
|
+
readonly EmaUpdProductId: "eu-upd-product-id";
|
|
63
|
+
readonly UsdaCvbPcn: "usda-cvb-pcn";
|
|
64
|
+
readonly CfiaCcvbNumber: "cfia-ccvb-number";
|
|
65
|
+
readonly UsdaCode: "usda-code";
|
|
66
|
+
}>;
|
|
67
|
+
export type VeterinaryBiologicIdentifierKind = typeof VeterinaryBiologicIdentifierKinds[keyof typeof VeterinaryBiologicIdentifierKinds];
|
|
68
|
+
export type VeterinaryBiologicCoding = Readonly<{
|
|
69
|
+
system: string;
|
|
70
|
+
code: string;
|
|
71
|
+
display?: string;
|
|
72
|
+
}>;
|
|
73
|
+
export type VeterinaryBiologicProduct = Readonly<{
|
|
74
|
+
source: typeof VeterinaryBiologicProductSources[keyof typeof VeterinaryBiologicProductSources];
|
|
75
|
+
authorization: Readonly<{
|
|
76
|
+
region: VeterinaryBiologicAuthorizationRegion;
|
|
77
|
+
identifiers: readonly Readonly<{
|
|
78
|
+
kind: VeterinaryBiologicIdentifierKind;
|
|
79
|
+
value: string;
|
|
80
|
+
}>[];
|
|
81
|
+
}>;
|
|
82
|
+
productName: string;
|
|
83
|
+
trueName?: string;
|
|
84
|
+
tradeNames: readonly string[];
|
|
85
|
+
manufacturer: string;
|
|
86
|
+
species: readonly VeterinarySpeciesTaxon[];
|
|
87
|
+
targetDiseases: readonly VeterinaryBiologicCoding[];
|
|
88
|
+
/** WHO ATCvet QI classifications; never a regional product authorization id. */
|
|
89
|
+
atcVetCodes: readonly string[];
|
|
90
|
+
agents: readonly string[];
|
|
91
|
+
}>;
|
|
92
|
+
/** Validates one authority record without conflating its identifiers with terminology. */
|
|
93
|
+
export declare function parseVeterinaryBiologicProduct(value: unknown): VeterinaryBiologicProduct;
|
|
94
|
+
/** Pure catalog filtering after source adapters have produced governed records. */
|
|
95
|
+
export declare function filterVeterinaryBiologicProducts(products: readonly VeterinaryBiologicProduct[], filter: Readonly<{
|
|
96
|
+
region: VeterinaryBiologicAuthorizationRegion;
|
|
97
|
+
speciesTaxonomyCode: string;
|
|
98
|
+
targetDisease?: Readonly<{
|
|
99
|
+
system: string;
|
|
100
|
+
code: string;
|
|
101
|
+
}>;
|
|
102
|
+
text?: string;
|
|
103
|
+
}>): readonly VeterinaryBiologicProduct[];
|
|
104
|
+
/** Shared synthetic fixtures; identifiers are deliberately test-only, while systems/taxa are canonical. */
|
|
105
|
+
export declare const VETERINARY_BIOLOGIC_PRODUCT_TEST_DATA: Readonly<{
|
|
106
|
+
readonly euDogRabies: Readonly<{
|
|
107
|
+
source: "ema-upd";
|
|
108
|
+
authorization: Readonly<{
|
|
109
|
+
region: "EU-EEA";
|
|
110
|
+
identifiers: readonly Readonly<{
|
|
111
|
+
kind: "eu-upd-product-id";
|
|
112
|
+
value: "test-eu-product-1";
|
|
113
|
+
}>[];
|
|
114
|
+
}>;
|
|
115
|
+
productName: "Test EU canine rabies vaccine";
|
|
116
|
+
tradeNames: readonly string[];
|
|
117
|
+
manufacturer: "Test manufacturer EU";
|
|
118
|
+
species: readonly Readonly<{
|
|
119
|
+
system: typeof NcbiTaxonomySystem;
|
|
120
|
+
code: string;
|
|
121
|
+
display: string;
|
|
122
|
+
}>[];
|
|
123
|
+
targetDiseases: readonly Readonly<{
|
|
124
|
+
system: "http://snomed.info/sct";
|
|
125
|
+
code: "14168008";
|
|
126
|
+
display: "Rabies";
|
|
127
|
+
}>[];
|
|
128
|
+
atcVetCodes: readonly string[];
|
|
129
|
+
agents: readonly string[];
|
|
130
|
+
}>;
|
|
131
|
+
readonly usCattleProduct: Readonly<{
|
|
132
|
+
source: "usda-cvb";
|
|
133
|
+
authorization: Readonly<{
|
|
134
|
+
region: "US";
|
|
135
|
+
identifiers: readonly Readonly<{
|
|
136
|
+
kind: "usda-cvb-pcn";
|
|
137
|
+
value: "test-us-pcn-1";
|
|
138
|
+
}>[];
|
|
139
|
+
}>;
|
|
140
|
+
productName: "Test US cattle biologic";
|
|
141
|
+
trueName: "Test cattle agent vaccine";
|
|
142
|
+
tradeNames: readonly never[];
|
|
143
|
+
manufacturer: "Test manufacturer US";
|
|
144
|
+
species: readonly Readonly<{
|
|
145
|
+
system: typeof NcbiTaxonomySystem;
|
|
146
|
+
code: string;
|
|
147
|
+
display: string;
|
|
148
|
+
}>[];
|
|
149
|
+
targetDiseases: readonly {
|
|
150
|
+
system: string;
|
|
151
|
+
code: string;
|
|
152
|
+
}[];
|
|
153
|
+
atcVetCodes: readonly never[];
|
|
154
|
+
agents: readonly string[];
|
|
155
|
+
}>;
|
|
156
|
+
readonly caDogRabies: Readonly<{
|
|
157
|
+
source: "cfia-ccvb";
|
|
158
|
+
authorization: Readonly<{
|
|
159
|
+
region: "CA";
|
|
160
|
+
identifiers: readonly (Readonly<{
|
|
161
|
+
kind: "cfia-ccvb-number";
|
|
162
|
+
value: "test-ca-ccvb-1";
|
|
163
|
+
}> | Readonly<{
|
|
164
|
+
kind: "usda-code";
|
|
165
|
+
value: "test-usda-code-1";
|
|
166
|
+
}>)[];
|
|
167
|
+
}>;
|
|
168
|
+
productName: "Test Canadian canine rabies vaccine";
|
|
169
|
+
tradeNames: readonly string[];
|
|
170
|
+
manufacturer: "Test manufacturer CA";
|
|
171
|
+
species: readonly Readonly<{
|
|
172
|
+
system: typeof NcbiTaxonomySystem;
|
|
173
|
+
code: string;
|
|
174
|
+
display: string;
|
|
175
|
+
}>[];
|
|
176
|
+
targetDiseases: readonly Readonly<{
|
|
177
|
+
system: "http://snomed.info/sct";
|
|
178
|
+
code: "14168008";
|
|
179
|
+
display: "Rabies";
|
|
180
|
+
}>[];
|
|
181
|
+
atcVetCodes: readonly string[];
|
|
182
|
+
agents: readonly string[];
|
|
183
|
+
}>;
|
|
184
|
+
}>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
export const NcbiTaxonomySystem = 'https://www.ncbi.nlm.nih.gov/taxonomy';
|
|
2
|
+
function taxon(code, display) {
|
|
3
|
+
return Object.freeze({ system: NcbiTaxonomySystem, code, display });
|
|
4
|
+
}
|
|
5
|
+
/** Exact taxa used by shared animal-product filters; broad groups are not identities. */
|
|
6
|
+
export const VeterinarySpeciesTaxonomy = Object.freeze({
|
|
7
|
+
Dog: taxon('9615', 'Canis lupus familiaris'),
|
|
8
|
+
Cat: taxon('9685', 'Felis catus'),
|
|
9
|
+
Horse: taxon('9796', 'Equus caballus'),
|
|
10
|
+
Cattle: taxon('9913', 'Bos taurus'),
|
|
11
|
+
Pig: taxon('9823', 'Sus scrofa'),
|
|
12
|
+
Sheep: taxon('9940', 'Ovis aries'),
|
|
13
|
+
Goat: taxon('9925', 'Capra hircus'),
|
|
14
|
+
Chicken: taxon('9031', 'Gallus gallus'),
|
|
15
|
+
});
|
|
16
|
+
export const VeterinaryBiologicAuthorizationRegions = Object.freeze({
|
|
17
|
+
EuEea: 'EU-EEA',
|
|
18
|
+
Us: 'US',
|
|
19
|
+
Ca: 'CA',
|
|
20
|
+
});
|
|
21
|
+
export const VeterinaryBiologicProductSources = Object.freeze({
|
|
22
|
+
EmaUpd: 'ema-upd',
|
|
23
|
+
UsdaCvb: 'usda-cvb',
|
|
24
|
+
CfiaCcvb: 'cfia-ccvb',
|
|
25
|
+
});
|
|
26
|
+
export const VeterinaryBiologicIdentifierKinds = Object.freeze({
|
|
27
|
+
EmaUpdProductId: 'eu-upd-product-id',
|
|
28
|
+
UsdaCvbPcn: 'usda-cvb-pcn',
|
|
29
|
+
CfiaCcvbNumber: 'cfia-ccvb-number',
|
|
30
|
+
UsdaCode: 'usda-code',
|
|
31
|
+
});
|
|
32
|
+
const SOURCE_REGION = Object.freeze({
|
|
33
|
+
[VeterinaryBiologicProductSources.EmaUpd]: VeterinaryBiologicAuthorizationRegions.EuEea,
|
|
34
|
+
[VeterinaryBiologicProductSources.UsdaCvb]: VeterinaryBiologicAuthorizationRegions.Us,
|
|
35
|
+
[VeterinaryBiologicProductSources.CfiaCcvb]: VeterinaryBiologicAuthorizationRegions.Ca,
|
|
36
|
+
});
|
|
37
|
+
const PRIMARY_IDENTIFIER = Object.freeze({
|
|
38
|
+
[VeterinaryBiologicAuthorizationRegions.EuEea]: VeterinaryBiologicIdentifierKinds.EmaUpdProductId,
|
|
39
|
+
[VeterinaryBiologicAuthorizationRegions.Us]: VeterinaryBiologicIdentifierKinds.UsdaCvbPcn,
|
|
40
|
+
[VeterinaryBiologicAuthorizationRegions.Ca]: VeterinaryBiologicIdentifierKinds.CfiaCcvbNumber,
|
|
41
|
+
});
|
|
42
|
+
function required(value, error) {
|
|
43
|
+
if (typeof value !== 'string' || !value.trim())
|
|
44
|
+
throw new TypeError(error);
|
|
45
|
+
return value.trim();
|
|
46
|
+
}
|
|
47
|
+
function strings(value, error) {
|
|
48
|
+
if (!Array.isArray(value))
|
|
49
|
+
throw new TypeError(error);
|
|
50
|
+
const normalized = value.map((item) => required(item, error));
|
|
51
|
+
return Object.freeze([...new Set(normalized)]);
|
|
52
|
+
}
|
|
53
|
+
/** Validates one authority record without conflating its identifiers with terminology. */
|
|
54
|
+
export function parseVeterinaryBiologicProduct(value) {
|
|
55
|
+
if (!value || typeof value !== 'object')
|
|
56
|
+
throw new TypeError('veterinary_biologic_product_invalid');
|
|
57
|
+
const input = value;
|
|
58
|
+
const source = required(input.source, 'veterinary_biologic_source_invalid');
|
|
59
|
+
if (!(source in SOURCE_REGION))
|
|
60
|
+
throw new TypeError('veterinary_biologic_source_invalid');
|
|
61
|
+
const authorizationInput = input.authorization;
|
|
62
|
+
const region = required(authorizationInput?.region, 'veterinary_biologic_authorization_region_invalid');
|
|
63
|
+
if (SOURCE_REGION[source] !== region)
|
|
64
|
+
throw new TypeError('veterinary_biologic_authorization_region_invalid');
|
|
65
|
+
if (!Array.isArray(authorizationInput?.identifiers) || authorizationInput.identifiers.length === 0) {
|
|
66
|
+
throw new TypeError('veterinary_biologic_authorization_identifier_invalid');
|
|
67
|
+
}
|
|
68
|
+
const identifiers = authorizationInput.identifiers.map((entry) => {
|
|
69
|
+
if (!entry || typeof entry !== 'object')
|
|
70
|
+
throw new TypeError('veterinary_biologic_authorization_identifier_invalid');
|
|
71
|
+
const identifier = entry;
|
|
72
|
+
const kind = required(identifier.kind, 'veterinary_biologic_authorization_identifier_invalid');
|
|
73
|
+
if (!Object.values(VeterinaryBiologicIdentifierKinds).includes(kind)) {
|
|
74
|
+
throw new TypeError('veterinary_biologic_authorization_identifier_invalid');
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze({ kind, value: required(identifier.value, 'veterinary_biologic_authorization_identifier_invalid') });
|
|
77
|
+
});
|
|
78
|
+
if (identifiers[0]?.kind !== PRIMARY_IDENTIFIER[region]) {
|
|
79
|
+
throw new TypeError('veterinary_biologic_authorization_identifier_invalid');
|
|
80
|
+
}
|
|
81
|
+
if (identifiers.slice(1).some(({ kind }) => region !== VeterinaryBiologicAuthorizationRegions.Ca
|
|
82
|
+
|| kind !== VeterinaryBiologicIdentifierKinds.UsdaCode)) {
|
|
83
|
+
throw new TypeError('veterinary_biologic_authorization_identifier_invalid');
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(input.species) || input.species.length === 0)
|
|
86
|
+
throw new TypeError('veterinary_biologic_species_invalid');
|
|
87
|
+
const species = input.species.map((entry) => {
|
|
88
|
+
if (!entry || typeof entry !== 'object')
|
|
89
|
+
throw new TypeError('veterinary_biologic_species_invalid');
|
|
90
|
+
const candidate = entry;
|
|
91
|
+
if (candidate.system !== NcbiTaxonomySystem)
|
|
92
|
+
throw new TypeError('veterinary_biologic_species_invalid');
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
system: NcbiTaxonomySystem,
|
|
95
|
+
code: required(candidate.code, 'veterinary_biologic_species_invalid'),
|
|
96
|
+
display: required(candidate.display, 'veterinary_biologic_species_invalid'),
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
if (!Array.isArray(input.targetDiseases) || input.targetDiseases.length === 0) {
|
|
100
|
+
throw new TypeError('veterinary_biologic_target_disease_invalid');
|
|
101
|
+
}
|
|
102
|
+
const targetDiseases = input.targetDiseases.map((entry) => {
|
|
103
|
+
if (!entry || typeof entry !== 'object')
|
|
104
|
+
throw new TypeError('veterinary_biologic_target_disease_invalid');
|
|
105
|
+
const candidate = entry;
|
|
106
|
+
const display = typeof candidate.display === 'string' && candidate.display.trim() ? candidate.display.trim() : undefined;
|
|
107
|
+
return Object.freeze({
|
|
108
|
+
system: required(candidate.system, 'veterinary_biologic_target_disease_invalid'),
|
|
109
|
+
code: required(candidate.code, 'veterinary_biologic_target_disease_invalid'),
|
|
110
|
+
...(display ? { display } : {}),
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
const atcVetCodes = strings(input.atcVetCodes ?? [], 'veterinary_biologic_atcvet_invalid');
|
|
114
|
+
if (atcVetCodes.some((code) => !/^QI[A-Z0-9]{0,8}$/.test(code)))
|
|
115
|
+
throw new TypeError('veterinary_biologic_atcvet_invalid');
|
|
116
|
+
return Object.freeze({
|
|
117
|
+
source,
|
|
118
|
+
authorization: Object.freeze({ region, identifiers: Object.freeze(identifiers) }),
|
|
119
|
+
productName: required(input.productName, 'veterinary_biologic_product_name_required'),
|
|
120
|
+
...(typeof input.trueName === 'string' && input.trueName.trim() ? { trueName: input.trueName.trim() } : {}),
|
|
121
|
+
tradeNames: strings(input.tradeNames ?? [], 'veterinary_biologic_trade_name_invalid'),
|
|
122
|
+
manufacturer: required(input.manufacturer, 'veterinary_biologic_manufacturer_required'),
|
|
123
|
+
species: Object.freeze(species),
|
|
124
|
+
targetDiseases: Object.freeze(targetDiseases),
|
|
125
|
+
atcVetCodes,
|
|
126
|
+
agents: strings(input.agents ?? [], 'veterinary_biologic_agent_invalid'),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/** Pure catalog filtering after source adapters have produced governed records. */
|
|
130
|
+
export function filterVeterinaryBiologicProducts(products, filter) {
|
|
131
|
+
const speciesCode = required(filter.speciesTaxonomyCode, 'veterinary_biologic_species_filter_required');
|
|
132
|
+
const text = String(filter.text || '').trim().toLocaleLowerCase('en');
|
|
133
|
+
return products.filter((product) => product.authorization.region === filter.region)
|
|
134
|
+
.filter((product) => product.species.some(({ code }) => code === speciesCode))
|
|
135
|
+
.filter((product) => !filter.targetDisease || product.targetDiseases.some(({ system, code }) => (system === filter.targetDisease?.system && code === filter.targetDisease.code)))
|
|
136
|
+
.filter((product) => !text || [product.productName, product.trueName, ...product.tradeNames, ...product.agents]
|
|
137
|
+
.filter(Boolean).some((candidate) => String(candidate).toLocaleLowerCase('en').includes(text)));
|
|
138
|
+
}
|
|
139
|
+
const rabies = Object.freeze({ system: 'http://snomed.info/sct', code: '14168008', display: 'Rabies' });
|
|
140
|
+
/** Shared synthetic fixtures; identifiers are deliberately test-only, while systems/taxa are canonical. */
|
|
141
|
+
export const VETERINARY_BIOLOGIC_PRODUCT_TEST_DATA = Object.freeze({
|
|
142
|
+
euDogRabies: Object.freeze({
|
|
143
|
+
source: VeterinaryBiologicProductSources.EmaUpd,
|
|
144
|
+
authorization: Object.freeze({ region: VeterinaryBiologicAuthorizationRegions.EuEea, identifiers: Object.freeze([
|
|
145
|
+
Object.freeze({ kind: VeterinaryBiologicIdentifierKinds.EmaUpdProductId, value: 'test-eu-product-1' }),
|
|
146
|
+
]) }),
|
|
147
|
+
productName: 'Test EU canine rabies vaccine', tradeNames: Object.freeze(['Test EU Rabies']),
|
|
148
|
+
manufacturer: 'Test manufacturer EU', species: Object.freeze([VeterinarySpeciesTaxonomy.Dog]),
|
|
149
|
+
targetDiseases: Object.freeze([rabies]), atcVetCodes: Object.freeze(['QI07AA90']), agents: Object.freeze(['Rabies virus']),
|
|
150
|
+
}),
|
|
151
|
+
usCattleProduct: Object.freeze({
|
|
152
|
+
source: VeterinaryBiologicProductSources.UsdaCvb,
|
|
153
|
+
authorization: Object.freeze({ region: VeterinaryBiologicAuthorizationRegions.Us, identifiers: Object.freeze([
|
|
154
|
+
Object.freeze({ kind: VeterinaryBiologicIdentifierKinds.UsdaCvbPcn, value: 'test-us-pcn-1' }),
|
|
155
|
+
]) }),
|
|
156
|
+
productName: 'Test US cattle biologic', trueName: 'Test cattle agent vaccine', tradeNames: Object.freeze([]),
|
|
157
|
+
manufacturer: 'Test manufacturer US', species: Object.freeze([VeterinarySpeciesTaxonomy.Cattle]),
|
|
158
|
+
targetDiseases: Object.freeze([{ system: 'https://example.test/disease', code: 'test-cattle-disease' }]),
|
|
159
|
+
atcVetCodes: Object.freeze([]), agents: Object.freeze(['Test cattle agent']),
|
|
160
|
+
}),
|
|
161
|
+
caDogRabies: Object.freeze({
|
|
162
|
+
source: VeterinaryBiologicProductSources.CfiaCcvb,
|
|
163
|
+
authorization: Object.freeze({ region: VeterinaryBiologicAuthorizationRegions.Ca, identifiers: Object.freeze([
|
|
164
|
+
Object.freeze({ kind: VeterinaryBiologicIdentifierKinds.CfiaCcvbNumber, value: 'test-ca-ccvb-1' }),
|
|
165
|
+
Object.freeze({ kind: VeterinaryBiologicIdentifierKinds.UsdaCode, value: 'test-usda-code-1' }),
|
|
166
|
+
]) }),
|
|
167
|
+
productName: 'Test Canadian canine rabies vaccine', tradeNames: Object.freeze(['Test CA Rabies']),
|
|
168
|
+
manufacturer: 'Test manufacturer CA', species: Object.freeze([VeterinarySpeciesTaxonomy.Dog]),
|
|
169
|
+
targetDiseases: Object.freeze([rabies]), atcVetCodes: Object.freeze(['QI07AA90']), agents: Object.freeze(['Rabies virus']),
|
|
170
|
+
}),
|
|
171
|
+
});
|
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.31",
|
|
4
4
|
"description": "Browser-safe governed VetChain data contracts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Connecting Solution & Applications Ltd",
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"types": "./dist/animal-card.d.ts",
|
|
17
17
|
"default": "./dist/animal-card.js"
|
|
18
18
|
},
|
|
19
|
+
"./animal-behavior": {
|
|
20
|
+
"types": "./dist/animal-behavior.d.ts",
|
|
21
|
+
"default": "./dist/animal-behavior.js"
|
|
22
|
+
},
|
|
19
23
|
"./communication": {
|
|
20
24
|
"types": "./dist/communication.d.ts",
|
|
21
25
|
"default": "./dist/communication.js"
|
|
@@ -68,6 +72,10 @@
|
|
|
68
72
|
"types": "./dist/iso-jurisdictions.d.ts",
|
|
69
73
|
"default": "./dist/iso-jurisdictions.js"
|
|
70
74
|
},
|
|
75
|
+
"./lost-animal-access": {
|
|
76
|
+
"types": "./dist/lost-animal-access.d.ts",
|
|
77
|
+
"default": "./dist/lost-animal-access.js"
|
|
78
|
+
},
|
|
71
79
|
"./organization-application": {
|
|
72
80
|
"types": "./dist/organization-application.d.ts",
|
|
73
81
|
"default": "./dist/organization-application.js"
|
|
@@ -107,6 +115,10 @@
|
|
|
107
115
|
"./scheduling": {
|
|
108
116
|
"types": "./dist/scheduling.d.ts",
|
|
109
117
|
"default": "./dist/scheduling.js"
|
|
118
|
+
},
|
|
119
|
+
"./veterinary-biologic-product": {
|
|
120
|
+
"types": "./dist/veterinary-biologic-product.d.ts",
|
|
121
|
+
"default": "./dist/veterinary-biologic-product.js"
|
|
110
122
|
}
|
|
111
123
|
},
|
|
112
124
|
"files": [
|