vet-data-utils-ts 0.5.30 → 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 CHANGED
@@ -189,6 +189,20 @@ for immunologicals); ordinary human ATC is not substituted for it. Veterinary
189
189
  allergy product coding may also use ATCvet, while manifestations use an allowed
190
190
  SNOMED source.
191
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
+
192
206
  The shared IPS reader boundary is `projectClinicalBundleForReading(...)` from
193
207
  `vet-data-utils-ts/clinical-bundle-reader`. It accepts a native FHIR Bundle
194
208
  (including R4/R5 resources supported by the common normalizer) or resources
package/dist/index.d.ts CHANGED
@@ -23,4 +23,5 @@ export * from './research-study.js';
23
23
  export * from './sectors.js';
24
24
  export * from './shc.js';
25
25
  export * from './veterinary-sections.js';
26
+ export * from './veterinary-biologic-product.js';
26
27
  export * from './scheduling.js';
package/dist/index.js CHANGED
@@ -23,4 +23,5 @@ export * from './research-study.js';
23
23
  export * from './sectors.js';
24
24
  export * from './shc.js';
25
25
  export * from './veterinary-sections.js';
26
+ export * from './veterinary-biologic-product.js';
26
27
  export * from './scheduling.js';
@@ -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.30",
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",
@@ -115,6 +115,10 @@
115
115
  "./scheduling": {
116
116
  "types": "./dist/scheduling.d.ts",
117
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"
118
122
  }
119
123
  },
120
124
  "files": [