vet-sdk-core-ts 0.4.39 → 0.4.41
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 +21 -3
- package/dist/animal-onboarding.d.ts +47 -0
- package/dist/animal-onboarding.js +52 -0
- package/dist/species.d.ts +45 -0
- package/dist/species.js +69 -0
- package/docs/101-ANIMAL-INDIVIDUAL-ONBOARDING.md +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,13 +7,15 @@ An animal is the first member of its own Individual Organization, so
|
|
|
7
7
|
human that creates it is `Organization.owner`, the controller by default;
|
|
8
8
|
`RESPRSN` must never be copied into the animal member.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
demographics.
|
|
10
|
+
Usual name, birth date, exact NCBI species and optional breed are animal
|
|
11
|
+
demographics. The usual name may be supplied by the controller or generated as
|
|
12
|
+
a pronounceable species ordinal such as `Dog 1`; it is not identity evidence.
|
|
13
|
+
Breed remains separate descriptive text. Controller-authored
|
|
12
14
|
demographics remain editable until a veterinarian attests them; attestation
|
|
13
15
|
does not rewrite the original author, and later correction creates a new
|
|
14
16
|
veterinarian-authored version.
|
|
15
17
|
|
|
16
|
-
Use `buildVetChainAnimalDraftClaims(...)` while the name, exact NCBI species or
|
|
18
|
+
Use `buildVetChainAnimalDraftClaims(...)` while the generated/supplied usual name, exact NCBI species or
|
|
17
19
|
public Card is missing. Only a draft for which
|
|
18
20
|
`readVetChainAnimalOnboardingReadiness(...)` returns `ready: true` may be
|
|
19
21
|
activated and issued a Card. See
|
|
@@ -228,6 +230,16 @@ The catalogue is a governed starter set, not a closed biological universe.
|
|
|
228
230
|
Callers may retain another verified positive non-human NCBI Taxonomy identifier.
|
|
229
231
|
This matters because animal keeping varies by jurisdiction and taxonomy evolves.
|
|
230
232
|
|
|
233
|
+
Species-language resolution is channel-neutral. Voice, WhatsApp, portal chat
|
|
234
|
+
and portal speech call `resolveVetChainSpeciesAlias` with the same localized
|
|
235
|
+
lexicon. It performs exact normalized matching only. If that fast path misses,
|
|
236
|
+
a server adapter implements `VetChainSpeciesTerminologyResolver` and sends text
|
|
237
|
+
plus locale to the authenticated terminology service. The service returns a
|
|
238
|
+
closed NCBI candidate set, which must pass
|
|
239
|
+
`validateVetChainSpeciesTerminologyCandidates`; a model such as Gemma may rank
|
|
240
|
+
that set but cannot add codes. `selectVetChainSpeciesTerminologyCandidate`
|
|
241
|
+
accepts only a code actually returned for user review and confirmation.
|
|
242
|
+
|
|
231
243
|
Current cards are issued with `issueVetChainAnimalCard`: five jurisdiction
|
|
232
244
|
digits plus `animalNumericId15 + checkDigit1`, and the matching
|
|
233
245
|
`did:web:{host}:card:vetchain:{jurisdiction5}:{animal16}`. Species is separate
|
|
@@ -238,10 +250,16 @@ being repeated in the 21 printed digits.
|
|
|
238
250
|
import {
|
|
239
251
|
VetChainDomesticAnimalSpecies,
|
|
240
252
|
findVetChainSpeciesByTaxonomyId,
|
|
253
|
+
resolveVetChainSpeciesAlias,
|
|
241
254
|
} from "vet-sdk-core-ts/species";
|
|
242
255
|
|
|
243
256
|
VetChainDomesticAnimalSpecies.DomesticFerret.ncbiTaxonomyId; // "9669"
|
|
244
257
|
findVetChainSpeciesByTaxonomyId("9685")?.key; // "Cat"
|
|
258
|
+
resolveVetChainSpeciesAlias({
|
|
259
|
+
text: "Dog.",
|
|
260
|
+
locale: "en-CA",
|
|
261
|
+
lexicon: [{ ncbiTaxonomyId: "9615", acceptedTexts: ["dog", "it is a dog"] }],
|
|
262
|
+
})?.ncbiTaxonomyId; // "9615"
|
|
245
263
|
```
|
|
246
264
|
|
|
247
265
|
## Digital-twin search
|
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel-neutral form contract for animal demographics.
|
|
3
|
+
*
|
|
4
|
+
* Voice, chat and portal navigation may render this schema differently, but
|
|
5
|
+
* they must not redefine required fields or post-activation mutability.
|
|
6
|
+
*/
|
|
7
|
+
export declare const VETCHAIN_ANIMAL_DEMOGRAPHICS_SCHEMA: Readonly<{
|
|
8
|
+
readonly $schema: "https://json-schema.org/draft/2020-12/schema";
|
|
9
|
+
readonly $id: "https://vetchain.org/schemas/animal-demographics/v1";
|
|
10
|
+
readonly type: "object";
|
|
11
|
+
readonly additionalProperties: false;
|
|
12
|
+
readonly properties: Readonly<{
|
|
13
|
+
usualName: Readonly<{
|
|
14
|
+
type: "string";
|
|
15
|
+
maxLength: 200;
|
|
16
|
+
'x-vetchain-claim': "org.schema.Organization.alternateName";
|
|
17
|
+
'x-vetchain-auto-generated': true;
|
|
18
|
+
'x-vetchain-mutable-until-attested': true;
|
|
19
|
+
}>;
|
|
20
|
+
birthDate: Readonly<{
|
|
21
|
+
type: "string";
|
|
22
|
+
pattern: "^\\d{4}(?:-(?:0[1-9]|1[0-2])(?:-(?:0[1-9]|[12]\\d|3[01]))?)?$";
|
|
23
|
+
'x-vetchain-claim': "org.schema.Organization.member.birthDate";
|
|
24
|
+
'x-vetchain-mutable-until-attested': true;
|
|
25
|
+
}>;
|
|
26
|
+
species: Readonly<{
|
|
27
|
+
type: "string";
|
|
28
|
+
pattern: "^[1-9]\\d*$";
|
|
29
|
+
not: Readonly<{
|
|
30
|
+
const: "9606";
|
|
31
|
+
}>;
|
|
32
|
+
'x-vetchain-claim': "org.schema.Organization.member.additionalType";
|
|
33
|
+
'x-vetchain-immutable-after-activation': true;
|
|
34
|
+
}>;
|
|
35
|
+
}>;
|
|
36
|
+
readonly required: readonly string[];
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the controller-supplied usual name or a stable, pronounceable
|
|
40
|
+
* English fallback such as `Dog 1`. The ordinal is allocated durably by the
|
|
41
|
+
* owning BFF; this pure helper never counts records or exposes a private UUID.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveVetChainAnimalUsualName(input: Readonly<{
|
|
44
|
+
usualName?: string;
|
|
45
|
+
ncbiTaxonomyId: string;
|
|
46
|
+
ordinal: number;
|
|
47
|
+
}>): string;
|
|
1
48
|
/** Canonical member relationship for the animal SELF subject. */
|
|
2
49
|
export declare const VetChainAnimalMemberRoles: Readonly<{
|
|
3
50
|
readonly Self: "ONESELF";
|
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
import { parseVetChainAnimalCardDid } from 'vet-data-utils-ts';
|
|
2
2
|
import { buildAnimalIdentifier16, buildSpeciesId7 } from './animal-identity.js';
|
|
3
3
|
import { normalizeVetChainNcbiTaxonomyId } from './card-issuance.js';
|
|
4
|
+
import { findVetChainSpeciesByTaxonomyId } from './species.js';
|
|
5
|
+
/**
|
|
6
|
+
* Channel-neutral form contract for animal demographics.
|
|
7
|
+
*
|
|
8
|
+
* Voice, chat and portal navigation may render this schema differently, but
|
|
9
|
+
* they must not redefine required fields or post-activation mutability.
|
|
10
|
+
*/
|
|
11
|
+
export const VETCHAIN_ANIMAL_DEMOGRAPHICS_SCHEMA = Object.freeze({
|
|
12
|
+
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
|
13
|
+
$id: 'https://vetchain.org/schemas/animal-demographics/v1',
|
|
14
|
+
type: 'object',
|
|
15
|
+
additionalProperties: false,
|
|
16
|
+
properties: Object.freeze({
|
|
17
|
+
usualName: Object.freeze({
|
|
18
|
+
type: 'string', maxLength: 200,
|
|
19
|
+
'x-vetchain-claim': 'org.schema.Organization.alternateName',
|
|
20
|
+
'x-vetchain-auto-generated': true,
|
|
21
|
+
'x-vetchain-mutable-until-attested': true,
|
|
22
|
+
}),
|
|
23
|
+
birthDate: Object.freeze({
|
|
24
|
+
type: 'string', pattern: '^\\d{4}(?:-(?:0[1-9]|1[0-2])(?:-(?:0[1-9]|[12]\\d|3[01]))?)?$',
|
|
25
|
+
'x-vetchain-claim': 'org.schema.Organization.member.birthDate',
|
|
26
|
+
'x-vetchain-mutable-until-attested': true,
|
|
27
|
+
}),
|
|
28
|
+
species: Object.freeze({
|
|
29
|
+
type: 'string', pattern: '^[1-9]\\d*$', not: Object.freeze({ const: '9606' }),
|
|
30
|
+
'x-vetchain-claim': 'org.schema.Organization.member.additionalType',
|
|
31
|
+
'x-vetchain-immutable-after-activation': true,
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
required: Object.freeze(['species']),
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Returns the controller-supplied usual name or a stable, pronounceable
|
|
38
|
+
* English fallback such as `Dog 1`. The ordinal is allocated durably by the
|
|
39
|
+
* owning BFF; this pure helper never counts records or exposes a private UUID.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveVetChainAnimalUsualName(input) {
|
|
42
|
+
const supplied = String(input.usualName || '').trim();
|
|
43
|
+
if (supplied)
|
|
44
|
+
return supplied;
|
|
45
|
+
if (!Number.isSafeInteger(input.ordinal) || input.ordinal < 1) {
|
|
46
|
+
throw new TypeError('vet_animal_usual_name_ordinal_invalid');
|
|
47
|
+
}
|
|
48
|
+
const taxonomy = normalizeVetChainNcbiTaxonomyId(input.ncbiTaxonomyId);
|
|
49
|
+
const species = findVetChainSpeciesByTaxonomyId(taxonomy);
|
|
50
|
+
const label = species ? humanizeSpeciesKey(species.key) : 'Animal';
|
|
51
|
+
return `${label} ${input.ordinal}`;
|
|
52
|
+
}
|
|
4
53
|
/** Canonical member relationship for the animal SELF subject. */
|
|
5
54
|
export const VetChainAnimalMemberRoles = Object.freeze({ Self: 'ONESELF' });
|
|
6
55
|
/** Canonical schema.org claim names for the VetChain animal registration. */
|
|
@@ -136,3 +185,6 @@ function required(value, error) {
|
|
|
136
185
|
throw new TypeError(error);
|
|
137
186
|
return normalized;
|
|
138
187
|
}
|
|
188
|
+
function humanizeSpeciesKey(value) {
|
|
189
|
+
return value.replace(/([a-z0-9])([A-Z])/g, '$1 $2').trim();
|
|
190
|
+
}
|
package/dist/species.d.ts
CHANGED
|
@@ -9,6 +9,27 @@ export type VetChainSpeciesDefinition = Readonly<{
|
|
|
9
9
|
scientificName: string;
|
|
10
10
|
group: VetChainAnimalGroup;
|
|
11
11
|
}>;
|
|
12
|
+
export declare const VETCHAIN_NCBI_TAXONOMY_CODE_SYSTEM: "https://www.ncbi.nlm.nih.gov/Taxonomy";
|
|
13
|
+
/** Localized exact texts supplied by a VetChain product, independently of its channel adapter. */
|
|
14
|
+
export type VetChainSpeciesLexiconEntry = Readonly<{
|
|
15
|
+
ncbiTaxonomyId: NcbiTaxonomyId;
|
|
16
|
+
acceptedTexts: readonly string[];
|
|
17
|
+
}>;
|
|
18
|
+
/** One reviewable result returned by the authenticated terminology service. */
|
|
19
|
+
export type VetChainSpeciesTerminologyCandidate = Readonly<{
|
|
20
|
+
codeSystem: typeof VETCHAIN_NCBI_TAXONOMY_CODE_SYSTEM;
|
|
21
|
+
codeValue: NcbiTaxonomyId;
|
|
22
|
+
display: string;
|
|
23
|
+
score?: number;
|
|
24
|
+
}>;
|
|
25
|
+
/** Channel-neutral boundary implemented by a server-side VetChain terminology adapter. */
|
|
26
|
+
export interface VetChainSpeciesTerminologyResolver {
|
|
27
|
+
search(input: Readonly<{
|
|
28
|
+
text: string;
|
|
29
|
+
locale: string;
|
|
30
|
+
maxCandidates: number;
|
|
31
|
+
}>): Promise<readonly VetChainSpeciesTerminologyCandidate[]>;
|
|
32
|
+
}
|
|
12
33
|
/**
|
|
13
34
|
* Governed starter catalogue for domesticated and commonly kept animals.
|
|
14
35
|
*
|
|
@@ -482,3 +503,27 @@ export declare const VetChainDomesticAnimalSpeciesList: readonly Readonly<{
|
|
|
482
503
|
export declare function findVetChainSpeciesByTaxonomyId(value: string): VetChainSpeciesDefinition | undefined;
|
|
483
504
|
/** The current printed card layout can embed only NCBI Taxonomy IDs of at most five digits. */
|
|
484
505
|
export declare function isVetChainSpeciesCode5Compatible(value: string): boolean;
|
|
506
|
+
/** Normalizes human text consistently before an exact localized alias lookup. */
|
|
507
|
+
export declare function normalizeVetChainSpeciesText(value: string, locale?: string): string;
|
|
508
|
+
/**
|
|
509
|
+
* Resolves only an exact normalized text configured by the product locale.
|
|
510
|
+
*
|
|
511
|
+
* Voice, WhatsApp, portal chat and portal speech must call this same helper.
|
|
512
|
+
* Unmatched free text belongs at the terminology boundary, not in an LLM-only
|
|
513
|
+
* inference path.
|
|
514
|
+
*/
|
|
515
|
+
export declare function resolveVetChainSpeciesAlias(input: Readonly<{
|
|
516
|
+
text: string;
|
|
517
|
+
locale: string;
|
|
518
|
+
lexicon: readonly VetChainSpeciesLexiconEntry[];
|
|
519
|
+
}>): Readonly<{
|
|
520
|
+
ncbiTaxonomyId: NcbiTaxonomyId;
|
|
521
|
+
matchedText: string;
|
|
522
|
+
}> | undefined;
|
|
523
|
+
/** Validates the closed candidate set before it can be presented or ranked. */
|
|
524
|
+
export declare function validateVetChainSpeciesTerminologyCandidates(values: readonly VetChainSpeciesTerminologyCandidate[]): readonly VetChainSpeciesTerminologyCandidate[];
|
|
525
|
+
/**
|
|
526
|
+
* Selects only a code in the terminology service's reviewed candidate set.
|
|
527
|
+
* A Gemma/ranking service may reorder this set but cannot add a code.
|
|
528
|
+
*/
|
|
529
|
+
export declare function selectVetChainSpeciesTerminologyCandidate(candidates: readonly VetChainSpeciesTerminologyCandidate[], selectedCodeValue: string): VetChainSpeciesTerminologyCandidate;
|
package/dist/species.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Copyright 2026 Connecting Solution & Applications Ltd under the Apache License, Version 2.0.
|
|
2
|
+
export const VETCHAIN_NCBI_TAXONOMY_CODE_SYSTEM = "https://www.ncbi.nlm.nih.gov/Taxonomy";
|
|
2
3
|
function species(key, ncbiTaxonomyId, scientificName, group) {
|
|
3
4
|
if (!/^[1-9]\d*$/.test(ncbiTaxonomyId) || ncbiTaxonomyId === "9606") {
|
|
4
5
|
throw new TypeError("A species requires a positive, non-human NCBI Taxonomy identifier.");
|
|
@@ -74,3 +75,71 @@ export function findVetChainSpeciesByTaxonomyId(value) {
|
|
|
74
75
|
export function isVetChainSpeciesCode5Compatible(value) {
|
|
75
76
|
return /^[1-9]\d{0,4}$/.test(value) && value !== "9606";
|
|
76
77
|
}
|
|
78
|
+
/** Normalizes human text consistently before an exact localized alias lookup. */
|
|
79
|
+
export function normalizeVetChainSpeciesText(value, locale = "en") {
|
|
80
|
+
const normalizedLocale = String(locale || "en").trim() || "en";
|
|
81
|
+
return String(value || "")
|
|
82
|
+
.normalize("NFD")
|
|
83
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
84
|
+
.toLocaleLowerCase(normalizedLocale)
|
|
85
|
+
.replace(/[^\p{L}\p{N}\s]/gu, " ")
|
|
86
|
+
.replace(/\s+/g, " ")
|
|
87
|
+
.trim();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolves only an exact normalized text configured by the product locale.
|
|
91
|
+
*
|
|
92
|
+
* Voice, WhatsApp, portal chat and portal speech must call this same helper.
|
|
93
|
+
* Unmatched free text belongs at the terminology boundary, not in an LLM-only
|
|
94
|
+
* inference path.
|
|
95
|
+
*/
|
|
96
|
+
export function resolveVetChainSpeciesAlias(input) {
|
|
97
|
+
const normalizedInput = normalizeVetChainSpeciesText(input.text, input.locale);
|
|
98
|
+
if (!normalizedInput)
|
|
99
|
+
return undefined;
|
|
100
|
+
for (const entry of input.lexicon) {
|
|
101
|
+
assertNonHumanTaxonomyId(entry.ncbiTaxonomyId);
|
|
102
|
+
if (entry.acceptedTexts.some((text) => normalizeVetChainSpeciesText(text, input.locale) === normalizedInput)) {
|
|
103
|
+
return Object.freeze({ ncbiTaxonomyId: entry.ncbiTaxonomyId, matchedText: normalizedInput });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
/** Validates the closed candidate set before it can be presented or ranked. */
|
|
109
|
+
export function validateVetChainSpeciesTerminologyCandidates(values) {
|
|
110
|
+
if (!Array.isArray(values) || values.length === 0 || values.length > 20) {
|
|
111
|
+
throw new TypeError("Species terminology must return between one and twenty candidates.");
|
|
112
|
+
}
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
return Object.freeze(values.map((candidate) => {
|
|
115
|
+
if (candidate.codeSystem !== VETCHAIN_NCBI_TAXONOMY_CODE_SYSTEM) {
|
|
116
|
+
throw new TypeError("Species terminology candidates must use the canonical NCBI Taxonomy code system.");
|
|
117
|
+
}
|
|
118
|
+
assertNonHumanTaxonomyId(candidate.codeValue);
|
|
119
|
+
if (!candidate.display.trim())
|
|
120
|
+
throw new TypeError("A species terminology candidate requires a display label.");
|
|
121
|
+
if (seen.has(candidate.codeValue))
|
|
122
|
+
throw new TypeError("Species terminology candidates must be unique.");
|
|
123
|
+
if (candidate.score !== undefined && (!Number.isFinite(candidate.score) || candidate.score < 0 || candidate.score > 1)) {
|
|
124
|
+
throw new TypeError("A species terminology candidate score must be between zero and one.");
|
|
125
|
+
}
|
|
126
|
+
seen.add(candidate.codeValue);
|
|
127
|
+
return Object.freeze({ ...candidate, display: candidate.display.trim() });
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Selects only a code in the terminology service's reviewed candidate set.
|
|
132
|
+
* A Gemma/ranking service may reorder this set but cannot add a code.
|
|
133
|
+
*/
|
|
134
|
+
export function selectVetChainSpeciesTerminologyCandidate(candidates, selectedCodeValue) {
|
|
135
|
+
const validated = validateVetChainSpeciesTerminologyCandidates(candidates);
|
|
136
|
+
const selected = validated.find((candidate) => candidate.codeValue === selectedCodeValue.trim());
|
|
137
|
+
if (!selected)
|
|
138
|
+
throw new TypeError("The selected species was not returned by the terminology service.");
|
|
139
|
+
return selected;
|
|
140
|
+
}
|
|
141
|
+
function assertNonHumanTaxonomyId(value) {
|
|
142
|
+
if (!/^[1-9]\d*$/.test(value) || value === "9606") {
|
|
143
|
+
throw new TypeError("A species requires a positive, non-human NCBI Taxonomy identifier.");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -40,7 +40,8 @@ readVetChainAnimalOnboardingReadiness(draft)
|
|
|
40
40
|
// { ready: false, missing: ['alternateName', 'species', 'card'] }
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
The BFF later rebuilds the same subject draft with its name
|
|
43
|
+
The BFF later rebuilds the same subject draft with its supplied usual name or a
|
|
44
|
+
durably numbered species fallback such as `Dog 1`, plus its exact NCBI
|
|
44
45
|
taxonomy, issues the public Card, and resubmits it under the same private
|
|
45
46
|
`subjectId`. GW VET alone performs the `pending -> active` transition. A draft
|
|
46
47
|
does not need a clinical `Composition`; the active clinical index is a
|
package/package.json
CHANGED