vet-data-utils-ts 0.5.28 → 0.5.30

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
@@ -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
@@ -146,6 +159,15 @@ future PATCH. The matching `normalize*FlatClaimsResource()` functions reject
146
159
  nested FHIR persistence fields and unknown claims. Native FHIR JSON remains an
147
160
  explicit import, projection or export boundary.
148
161
 
162
+ Animal-insurance eligibility Consent uses the same boundary through
163
+ `vet-data-utils-ts/insurance-consent`: FHIR-equivalent authorization semantics
164
+ live in canonical flat claims, the complete query-shaped ODRL Agreement lives
165
+ in an `application/odrl+json` attachment, semantic divergence fails closed,
166
+ and native R4/R5 appears only when explicitly requested. Aggregations such as
167
+ "latest date" and "count" remain ODRL-only because FHIR Consent has no native
168
+ field for them. See
169
+ [`docs/insurance-consent-odrl.md`](docs/insurance-consent-odrl.md).
170
+
149
171
  Reusable Communication screens receive immutable workflow presets from
150
172
  `vet-data-utils-ts/communication`. The research-agreement screen is fixed to
151
173
  FHIR `notification` plus HL7 v3 ActReason `HRESCH` and does not expose a topic
@@ -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';
@@ -11,7 +12,9 @@ export * from './health-dcat.js';
11
12
  export * from './immunization.js';
12
13
  export * from './international-health-card.js';
13
14
  export * from './index-projection-tags.js';
15
+ export * from './insurance-consent.js';
14
16
  export * from './iso-jurisdictions.js';
17
+ export * from './lost-animal-access.js';
15
18
  export * from './organization-application.js';
16
19
  export * from './payment.js';
17
20
  export * from './place-service-directory.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';
@@ -11,7 +12,9 @@ export * from './health-dcat.js';
11
12
  export * from './immunization.js';
12
13
  export * from './international-health-card.js';
13
14
  export * from './index-projection-tags.js';
15
+ export * from './insurance-consent.js';
14
16
  export * from './iso-jurisdictions.js';
17
+ export * from './lost-animal-access.js';
15
18
  export * from './organization-application.js';
16
19
  export * from './payment.js';
17
20
  export * from './place-service-directory.js';
@@ -0,0 +1,126 @@
1
+ import { ResourceTypesFhirR4 } from 'gdc-common-utils-ts/constants/fhir-resource-types';
2
+ /** W3C ODRL and VetChain profile identifiers used by insurance eligibility consent. */
3
+ export declare const AnimalInsuranceConsentVocabulary: Readonly<{
4
+ readonly OdrlContext: "http://www.w3.org/ns/odrl.jsonld";
5
+ readonly Agreement: "Agreement";
6
+ readonly Read: "read";
7
+ readonly Equal: "eq";
8
+ readonly GreaterThanOrEqual: "gteq";
9
+ readonly LessThanOrEqual: "lteq";
10
+ readonly MediaType: "application/odrl+json";
11
+ readonly Profile: "https://vetchain.app/ns/odrl/animal-insurance-eligibility/v1";
12
+ readonly EligibilitySummaryTarget: "https://vetchain.app/ns/asset/animal-insurance-eligibility-summary";
13
+ readonly FactKindOperand: "https://vetchain.app/ns/odrl/operand/eligibility-fact-kind";
14
+ readonly ClinicalCodeOperand: "https://vetchain.app/ns/odrl/operand/clinical-code";
15
+ readonly DataPeriodStartOperand: "https://vetchain.app/ns/odrl/operand/data-period-start";
16
+ readonly DataPeriodEndOperand: "https://vetchain.app/ns/odrl/operand/data-period-end";
17
+ }>;
18
+ export declare const AnimalInsuranceEligibilityFactKinds: Readonly<{
19
+ readonly LatestEventDate: "latest-event-date";
20
+ readonly EventCount: "event-count";
21
+ }>;
22
+ export declare const AnimalInsuranceConsentErrors: Readonly<{
23
+ readonly InvalidOdrl: "insurance_consent_odrl_invalid";
24
+ readonly OdrlClaimsDiverge: "insurance_consent_odrl_claims_diverge";
25
+ readonly ResearchForbidden: "insurance_consent_research_forbidden";
26
+ readonly FactsRequired: "insurance_consent_facts_required";
27
+ }>;
28
+ /** Reusable values for package, GW and portal contract tests. */
29
+ export declare const ANIMAL_INSURANCE_CONSENT_TEST_DATA: Readonly<{
30
+ readonly consentId: "animal-insurance-consent-test-01";
31
+ readonly consentIdentifier: "urn:uuid:54b4d158-ed78-48b4-b006-84ba5a5d85b4";
32
+ readonly subjectReference: "Patient/animal-beneficiary-test-01";
33
+ readonly controllerReference: "RelatedPerson/controller-test-01";
34
+ readonly insurerReference: "Organization/animal-insurer-test-01";
35
+ readonly managerReference: "Organization/vetchain-consent-manager-test-01";
36
+ readonly enforcerReference: "Organization/vetchain-consent-enforcer-test-01";
37
+ readonly createdAt: "2026-09-08";
38
+ readonly consentPeriodStart: "2026-09-08T00:00:00Z";
39
+ readonly consentPeriodEnd: "2026-10-08T00:00:00Z";
40
+ readonly dataPeriodStart: "2025-09-08T00:00:00Z";
41
+ readonly dataPeriodEnd: "2026-09-08T00:00:00Z";
42
+ readonly actorRole: "http://terminology.hl7.org/CodeSystem/v3-RoleCode|PAYOR";
43
+ readonly action: "http://terminology.hl7.org/CodeSystem/consentaction|access";
44
+ readonly purpose: "http://terminology.hl7.org/CodeSystem/v3-ActReason|HOPERAT";
45
+ readonly scope: "http://terminology.hl7.org/CodeSystem/consentscope|patient-privacy";
46
+ readonly category: "http://loinc.org|59284-0";
47
+ readonly resourceType: "http://hl7.org/fhir/fhir-types|Observation";
48
+ readonly clinicalCode: "http://loinc.org|85353-1";
49
+ readonly secondClinicalCode: "http://loinc.org|8310-5";
50
+ readonly clinicalCodeList: "http://loinc.org|85353-1,http://loinc.org|8310-5";
51
+ readonly status: "active";
52
+ readonly decision: "permit";
53
+ readonly factKind: "latest-event-date";
54
+ readonly forbiddenPurpose: "http://terminology.hl7.org/CodeSystem/v3-ActReason|HRESCH";
55
+ readonly divergentInsurerReference: "Organization/different-animal-insurer-test-01";
56
+ readonly jurisdiction: "CA";
57
+ readonly fhirConsentResourceType: "Consent";
58
+ }>;
59
+ export type AnimalInsuranceEligibilityFact = Readonly<{
60
+ kind: typeof AnimalInsuranceEligibilityFactKinds[keyof typeof AnimalInsuranceEligibilityFactKinds];
61
+ clinicalCode: string;
62
+ }>;
63
+ export type AnimalInsuranceConsentInput = Readonly<{
64
+ id: string;
65
+ identifier: string;
66
+ subjectReference: string;
67
+ grantorReference: string;
68
+ granteeReference: string;
69
+ managerReference: string;
70
+ controllerReference: string;
71
+ createdAt: string;
72
+ consentPeriod: Readonly<{
73
+ start: string;
74
+ end: string;
75
+ }>;
76
+ dataPeriod: Readonly<{
77
+ start: string;
78
+ end: string;
79
+ }>;
80
+ actorRole: string;
81
+ action: string;
82
+ purpose: string;
83
+ scope: string;
84
+ category: string;
85
+ resourceType: string;
86
+ facts: readonly AnimalInsuranceEligibilityFact[];
87
+ }>;
88
+ /** Complete reusable builder input used by downstream GW, SDK and portal tests. */
89
+ export declare const ANIMAL_INSURANCE_CONSENT_TEST_INPUT: AnimalInsuranceConsentInput;
90
+ export type ClaimsFirstAnimalInsuranceConsent = Readonly<{
91
+ resourceType: typeof ResourceTypesFhirR4.Consent;
92
+ id: string;
93
+ meta: Readonly<{
94
+ claims: Readonly<Record<string, string>>;
95
+ }>;
96
+ }>;
97
+ export type AnimalInsuranceOdrlConstraint = Readonly<{
98
+ leftOperand: string;
99
+ operator: string;
100
+ rightOperand: string;
101
+ }>;
102
+ export type AnimalInsuranceOdrlPolicy = Readonly<{
103
+ '@context': typeof AnimalInsuranceConsentVocabulary.OdrlContext;
104
+ '@type': typeof AnimalInsuranceConsentVocabulary.Agreement;
105
+ uid: string;
106
+ profile: typeof AnimalInsuranceConsentVocabulary.Profile;
107
+ assigner: string;
108
+ assignee: string;
109
+ permission: readonly Readonly<{
110
+ target: typeof AnimalInsuranceConsentVocabulary.EligibilitySummaryTarget;
111
+ action: typeof AnimalInsuranceConsentVocabulary.Read;
112
+ constraint: readonly AnimalInsuranceOdrlConstraint[];
113
+ }>[];
114
+ }>;
115
+ /**
116
+ * Builds an atomic claims-first FHIR Consent for one insurer eligibility query.
117
+ * Standard authorization semantics are mirrored in flat Consent claims; the
118
+ * complete query-shaping policy remains an ODRL JSON attachment.
119
+ */
120
+ export declare function buildAnimalInsuranceEligibilityConsent(input: AnimalInsuranceConsentInput): ClaimsFirstAnimalInsuranceConsent;
121
+ /** Parses the attached ODRL and fails closed unless its authorization semantics match the claims. */
122
+ export declare function validateAnimalInsuranceConsentOdrlParity(resource: ClaimsFirstAnimalInsuranceConsent): AnimalInsuranceOdrlPolicy;
123
+ /** Validates parity, then exports a native FHIR R4 Consent without ODRL-only fields. */
124
+ export declare function projectAnimalInsuranceConsentToFhirR4(resource: ClaimsFirstAnimalInsuranceConsent): Record<string, unknown>;
125
+ /** Validates parity, then exports a native FHIR R5 Consent without ODRL-only fields. */
126
+ export declare function projectAnimalInsuranceConsentToFhirR5(resource: ClaimsFirstAnimalInsuranceConsent): Record<string, unknown>;
@@ -0,0 +1,228 @@
1
+ import { ClaimConsent, ConsentDecisions, ConsentStatuses, } from 'gdc-common-utils-ts/models/consent-rule';
2
+ import { HealthcareConsentPurposes, } from 'gdc-common-utils-ts/constants/healthcare';
3
+ import { ResourceTypesFhirR4 } from 'gdc-common-utils-ts/constants/fhir-resource-types';
4
+ import { consentFlatToFhirR4, consentFlatToFhirR5, } from 'gdc-common-utils-ts/convert/convert-consent';
5
+ /** W3C ODRL and VetChain profile identifiers used by insurance eligibility consent. */
6
+ export const AnimalInsuranceConsentVocabulary = Object.freeze({
7
+ OdrlContext: 'http://www.w3.org/ns/odrl.jsonld',
8
+ Agreement: 'Agreement',
9
+ Read: 'read',
10
+ Equal: 'eq',
11
+ GreaterThanOrEqual: 'gteq',
12
+ LessThanOrEqual: 'lteq',
13
+ MediaType: 'application/odrl+json',
14
+ Profile: 'https://vetchain.app/ns/odrl/animal-insurance-eligibility/v1',
15
+ EligibilitySummaryTarget: 'https://vetchain.app/ns/asset/animal-insurance-eligibility-summary',
16
+ FactKindOperand: 'https://vetchain.app/ns/odrl/operand/eligibility-fact-kind',
17
+ ClinicalCodeOperand: 'https://vetchain.app/ns/odrl/operand/clinical-code',
18
+ DataPeriodStartOperand: 'https://vetchain.app/ns/odrl/operand/data-period-start',
19
+ DataPeriodEndOperand: 'https://vetchain.app/ns/odrl/operand/data-period-end',
20
+ });
21
+ export const AnimalInsuranceEligibilityFactKinds = Object.freeze({
22
+ LatestEventDate: 'latest-event-date',
23
+ EventCount: 'event-count',
24
+ });
25
+ export const AnimalInsuranceConsentErrors = Object.freeze({
26
+ InvalidOdrl: 'insurance_consent_odrl_invalid',
27
+ OdrlClaimsDiverge: 'insurance_consent_odrl_claims_diverge',
28
+ ResearchForbidden: 'insurance_consent_research_forbidden',
29
+ FactsRequired: 'insurance_consent_facts_required',
30
+ });
31
+ /** Reusable values for package, GW and portal contract tests. */
32
+ export const ANIMAL_INSURANCE_CONSENT_TEST_DATA = Object.freeze({
33
+ consentId: 'animal-insurance-consent-test-01',
34
+ consentIdentifier: 'urn:uuid:54b4d158-ed78-48b4-b006-84ba5a5d85b4',
35
+ subjectReference: 'Patient/animal-beneficiary-test-01',
36
+ controllerReference: 'RelatedPerson/controller-test-01',
37
+ insurerReference: 'Organization/animal-insurer-test-01',
38
+ managerReference: 'Organization/vetchain-consent-manager-test-01',
39
+ enforcerReference: 'Organization/vetchain-consent-enforcer-test-01',
40
+ createdAt: '2026-09-08',
41
+ consentPeriodStart: '2026-09-08T00:00:00Z',
42
+ consentPeriodEnd: '2026-10-08T00:00:00Z',
43
+ dataPeriodStart: '2025-09-08T00:00:00Z',
44
+ dataPeriodEnd: '2026-09-08T00:00:00Z',
45
+ actorRole: 'http://terminology.hl7.org/CodeSystem/v3-RoleCode|PAYOR',
46
+ action: 'http://terminology.hl7.org/CodeSystem/consentaction|access',
47
+ purpose: `http://terminology.hl7.org/CodeSystem/v3-ActReason|${HealthcareConsentPurposes.Operations}`,
48
+ scope: 'http://terminology.hl7.org/CodeSystem/consentscope|patient-privacy',
49
+ category: 'http://loinc.org|59284-0',
50
+ resourceType: 'http://hl7.org/fhir/fhir-types|Observation',
51
+ clinicalCode: 'http://loinc.org|85353-1',
52
+ secondClinicalCode: 'http://loinc.org|8310-5',
53
+ clinicalCodeList: 'http://loinc.org|85353-1,http://loinc.org|8310-5',
54
+ status: ConsentStatuses.Active,
55
+ decision: ConsentDecisions.Permit,
56
+ factKind: AnimalInsuranceEligibilityFactKinds.LatestEventDate,
57
+ forbiddenPurpose: `http://terminology.hl7.org/CodeSystem/v3-ActReason|${HealthcareConsentPurposes.Research}`,
58
+ divergentInsurerReference: 'Organization/different-animal-insurer-test-01',
59
+ jurisdiction: 'CA',
60
+ fhirConsentResourceType: ResourceTypesFhirR4.Consent,
61
+ });
62
+ /** Complete reusable builder input used by downstream GW, SDK and portal tests. */
63
+ export const ANIMAL_INSURANCE_CONSENT_TEST_INPUT = Object.freeze({
64
+ id: ANIMAL_INSURANCE_CONSENT_TEST_DATA.consentId,
65
+ identifier: ANIMAL_INSURANCE_CONSENT_TEST_DATA.consentIdentifier,
66
+ subjectReference: ANIMAL_INSURANCE_CONSENT_TEST_DATA.subjectReference,
67
+ grantorReference: ANIMAL_INSURANCE_CONSENT_TEST_DATA.controllerReference,
68
+ granteeReference: ANIMAL_INSURANCE_CONSENT_TEST_DATA.insurerReference,
69
+ managerReference: ANIMAL_INSURANCE_CONSENT_TEST_DATA.managerReference,
70
+ controllerReference: ANIMAL_INSURANCE_CONSENT_TEST_DATA.enforcerReference,
71
+ createdAt: ANIMAL_INSURANCE_CONSENT_TEST_DATA.createdAt,
72
+ consentPeriod: Object.freeze({
73
+ start: ANIMAL_INSURANCE_CONSENT_TEST_DATA.consentPeriodStart,
74
+ end: ANIMAL_INSURANCE_CONSENT_TEST_DATA.consentPeriodEnd,
75
+ }),
76
+ dataPeriod: Object.freeze({
77
+ start: ANIMAL_INSURANCE_CONSENT_TEST_DATA.dataPeriodStart,
78
+ end: ANIMAL_INSURANCE_CONSENT_TEST_DATA.dataPeriodEnd,
79
+ }),
80
+ actorRole: ANIMAL_INSURANCE_CONSENT_TEST_DATA.actorRole,
81
+ action: ANIMAL_INSURANCE_CONSENT_TEST_DATA.action,
82
+ purpose: ANIMAL_INSURANCE_CONSENT_TEST_DATA.purpose,
83
+ scope: ANIMAL_INSURANCE_CONSENT_TEST_DATA.scope,
84
+ category: ANIMAL_INSURANCE_CONSENT_TEST_DATA.category,
85
+ resourceType: ANIMAL_INSURANCE_CONSENT_TEST_DATA.resourceType,
86
+ facts: Object.freeze([{
87
+ kind: ANIMAL_INSURANCE_CONSENT_TEST_DATA.factKind,
88
+ clinicalCode: ANIMAL_INSURANCE_CONSENT_TEST_DATA.clinicalCode,
89
+ }, {
90
+ kind: AnimalInsuranceEligibilityFactKinds.EventCount,
91
+ clinicalCode: ANIMAL_INSURANCE_CONSENT_TEST_DATA.secondClinicalCode,
92
+ }]),
93
+ });
94
+ function encodeBase64Utf8(value) {
95
+ const bytes = new TextEncoder().encode(value);
96
+ let binary = '';
97
+ bytes.forEach((byte) => { binary += String.fromCharCode(byte); });
98
+ return btoa(binary);
99
+ }
100
+ function decodeBase64Utf8(value) {
101
+ const binary = atob(value);
102
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
103
+ return new TextDecoder().decode(bytes);
104
+ }
105
+ function buildPolicy(input) {
106
+ return {
107
+ '@context': AnimalInsuranceConsentVocabulary.OdrlContext,
108
+ '@type': AnimalInsuranceConsentVocabulary.Agreement,
109
+ uid: input.identifier,
110
+ profile: AnimalInsuranceConsentVocabulary.Profile,
111
+ assigner: input.grantorReference,
112
+ assignee: input.granteeReference,
113
+ permission: input.facts.map((fact) => ({
114
+ target: AnimalInsuranceConsentVocabulary.EligibilitySummaryTarget,
115
+ action: AnimalInsuranceConsentVocabulary.Read,
116
+ constraint: [
117
+ { leftOperand: AnimalInsuranceConsentVocabulary.FactKindOperand, operator: AnimalInsuranceConsentVocabulary.Equal, rightOperand: fact.kind },
118
+ { leftOperand: AnimalInsuranceConsentVocabulary.ClinicalCodeOperand, operator: AnimalInsuranceConsentVocabulary.Equal, rightOperand: fact.clinicalCode },
119
+ { leftOperand: AnimalInsuranceConsentVocabulary.DataPeriodStartOperand, operator: AnimalInsuranceConsentVocabulary.GreaterThanOrEqual, rightOperand: input.dataPeriod.start },
120
+ { leftOperand: AnimalInsuranceConsentVocabulary.DataPeriodEndOperand, operator: AnimalInsuranceConsentVocabulary.LessThanOrEqual, rightOperand: input.dataPeriod.end },
121
+ ],
122
+ })),
123
+ };
124
+ }
125
+ function rejectResearchSemantics(input) {
126
+ if (input.purpose.split('|').at(-1) === HealthcareConsentPurposes.Research
127
+ || input.resourceType.split('|').at(-1) === ResourceTypesFhirR4.ResearchStudy) {
128
+ throw new TypeError(AnimalInsuranceConsentErrors.ResearchForbidden);
129
+ }
130
+ }
131
+ /**
132
+ * Builds an atomic claims-first FHIR Consent for one insurer eligibility query.
133
+ * Standard authorization semantics are mirrored in flat Consent claims; the
134
+ * complete query-shaping policy remains an ODRL JSON attachment.
135
+ */
136
+ export function buildAnimalInsuranceEligibilityConsent(input) {
137
+ rejectResearchSemantics(input);
138
+ if (!input.facts.length)
139
+ throw new TypeError(AnimalInsuranceConsentErrors.FactsRequired);
140
+ if (input.facts.some((fact) => !Object.values(AnimalInsuranceEligibilityFactKinds).includes(fact.kind))) {
141
+ throw new TypeError(AnimalInsuranceConsentErrors.InvalidOdrl);
142
+ }
143
+ const policy = buildPolicy(input);
144
+ return {
145
+ resourceType: ResourceTypesFhirR4.Consent,
146
+ id: input.id,
147
+ meta: {
148
+ claims: {
149
+ '@context': 'org.hl7.fhir.api',
150
+ [ClaimConsent.identifier]: input.identifier,
151
+ [ClaimConsent.status]: ConsentStatuses.Active,
152
+ [ClaimConsent.subject]: input.subjectReference,
153
+ [ClaimConsent.date]: input.createdAt,
154
+ [ClaimConsent.decision]: ConsentDecisions.Permit,
155
+ [ClaimConsent.periodStart]: input.consentPeriod.start,
156
+ [ClaimConsent.periodEnd]: input.consentPeriod.end,
157
+ [ClaimConsent.dataPeriodStart]: input.dataPeriod.start,
158
+ [ClaimConsent.dataPeriodEnd]: input.dataPeriod.end,
159
+ [ClaimConsent.grantor]: input.grantorReference,
160
+ [ClaimConsent.grantee]: input.granteeReference,
161
+ [ClaimConsent.manager]: input.managerReference,
162
+ [ClaimConsent.controller]: input.controllerReference,
163
+ [ClaimConsent.actorIdentifier]: input.granteeReference,
164
+ [ClaimConsent.actorRole]: input.actorRole,
165
+ [ClaimConsent.action]: input.action,
166
+ [ClaimConsent.purpose]: input.purpose,
167
+ [ClaimConsent.scope]: input.scope,
168
+ [ClaimConsent.category]: input.category,
169
+ [ClaimConsent.resourceType]: input.resourceType,
170
+ [ClaimConsent.provisionCode]: input.facts.map((fact) => fact.clinicalCode).join(','),
171
+ [ClaimConsent.attachmentContentType]: AnimalInsuranceConsentVocabulary.MediaType,
172
+ [ClaimConsent.attachmentData]: encodeBase64Utf8(JSON.stringify(policy)),
173
+ },
174
+ },
175
+ };
176
+ }
177
+ function constraintValue(permission, operand) {
178
+ return permission.constraint.find((constraint) => constraint.leftOperand === operand)?.rightOperand;
179
+ }
180
+ /** Parses the attached ODRL and fails closed unless its authorization semantics match the claims. */
181
+ export function validateAnimalInsuranceConsentOdrlParity(resource) {
182
+ const claims = resource?.meta?.claims;
183
+ if (resource?.resourceType !== ResourceTypesFhirR4.Consent
184
+ || claims?.[ClaimConsent.attachmentContentType] !== AnimalInsuranceConsentVocabulary.MediaType) {
185
+ throw new TypeError(AnimalInsuranceConsentErrors.InvalidOdrl);
186
+ }
187
+ let policy;
188
+ try {
189
+ policy = JSON.parse(decodeBase64Utf8(claims[ClaimConsent.attachmentData]));
190
+ }
191
+ catch {
192
+ throw new TypeError(AnimalInsuranceConsentErrors.InvalidOdrl);
193
+ }
194
+ const permissions = Array.isArray(policy.permission) ? policy.permission : [];
195
+ const codes = permissions.map((permission) => constraintValue(permission, AnimalInsuranceConsentVocabulary.ClinicalCodeOperand)).filter(Boolean);
196
+ const periodsMatch = permissions.every((permission) => constraintValue(permission, AnimalInsuranceConsentVocabulary.DataPeriodStartOperand) === claims[ClaimConsent.dataPeriodStart]
197
+ && constraintValue(permission, AnimalInsuranceConsentVocabulary.DataPeriodEndOperand) === claims[ClaimConsent.dataPeriodEnd]);
198
+ const coreMatches = policy['@context'] === AnimalInsuranceConsentVocabulary.OdrlContext
199
+ && policy['@type'] === AnimalInsuranceConsentVocabulary.Agreement
200
+ && policy.profile === AnimalInsuranceConsentVocabulary.Profile
201
+ && policy.uid === claims[ClaimConsent.identifier]
202
+ && policy.assigner === claims[ClaimConsent.grantor]
203
+ && policy.assignee === claims[ClaimConsent.grantee]
204
+ && claims[ClaimConsent.actorIdentifier] === claims[ClaimConsent.grantee]
205
+ && claims[ClaimConsent.decision] === ConsentDecisions.Permit
206
+ && permissions.length > 0
207
+ && permissions.every((permission) => permission.target === AnimalInsuranceConsentVocabulary.EligibilitySummaryTarget
208
+ && permission.action === AnimalInsuranceConsentVocabulary.Read)
209
+ && codes.join(',') === claims[ClaimConsent.provisionCode]
210
+ && periodsMatch;
211
+ if (!coreMatches)
212
+ throw new TypeError(AnimalInsuranceConsentErrors.OdrlClaimsDiverge);
213
+ rejectResearchSemantics({
214
+ purpose: claims[ClaimConsent.purpose],
215
+ resourceType: claims[ClaimConsent.resourceType],
216
+ });
217
+ return policy;
218
+ }
219
+ /** Validates parity, then exports a native FHIR R4 Consent without ODRL-only fields. */
220
+ export function projectAnimalInsuranceConsentToFhirR4(resource) {
221
+ validateAnimalInsuranceConsentOdrlParity(resource);
222
+ return consentFlatToFhirR4(resource.meta.claims);
223
+ }
224
+ /** Validates parity, then exports a native FHIR R5 Consent without ODRL-only fields. */
225
+ export function projectAnimalInsuranceConsentToFhirR5(resource) {
226
+ validateAnimalInsuranceConsentOdrlParity(resource);
227
+ return consentFlatToFhirR5(resource.meta.claims);
228
+ }
@@ -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
+ });
@@ -38,7 +38,7 @@ export declare const SchedulingSearchParameterCatalog: Readonly<{
38
38
  readonly AppointmentResponse: readonly string[];
39
39
  }>;
40
40
  /** Indexed scheduling claims: FHIR SearchParameters plus the governed generic user-selected extension. */
41
- export declare const SchedulingFlatClaimCatalog: Readonly<Record<"Location" | "Schedule" | "Appointment" | "AppointmentResponse" | "Slot", readonly string[]>>;
41
+ export declare const SchedulingFlatClaimCatalog: Readonly<Record<"Appointment" | "AppointmentResponse" | "Location" | "Schedule" | "Slot", readonly string[]>>;
42
42
  export type SchedulingResourceType = keyof typeof SchedulingFlatClaimCatalog;
43
43
  export type SchedulingFlatClaimsResource = Readonly<Record<string, unknown> & {
44
44
  resourceType: SchedulingResourceType;
@@ -163,7 +163,7 @@ export declare function buildAppointmentNotificationResource(input: Readonly<{
163
163
  /** @deprecated Use {@link SlotSearchParameters}. */
164
164
  export declare const VeterinarySlotSearchParameters: readonly ["appointment-type", "identifier", "schedule", "service-category", "service-type", "service-type-reference", "specialty", "start", "status"];
165
165
  /** @deprecated Use {@link SchedulingFlatClaimCatalog}. */
166
- export declare const VeterinarySchedulingFlatClaimCatalog: Readonly<Record<"Location" | "Schedule" | "Appointment" | "AppointmentResponse" | "Slot", readonly string[]>>;
166
+ export declare const VeterinarySchedulingFlatClaimCatalog: Readonly<Record<"Appointment" | "AppointmentResponse" | "Location" | "Schedule" | "Slot", readonly string[]>>;
167
167
  /** @deprecated Use {@link SchedulingResourceType}. */
168
168
  export type VeterinarySchedulingResourceType = SchedulingResourceType;
169
169
  /** @deprecated Use {@link SchedulingFlatClaimsResource}. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vet-data-utils-ts",
3
- "version": "0.5.28",
3
+ "version": "0.5.30",
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"
@@ -60,10 +64,18 @@
60
64
  "types": "./dist/index-projection-tags.d.ts",
61
65
  "default": "./dist/index-projection-tags.js"
62
66
  },
67
+ "./insurance-consent": {
68
+ "types": "./dist/insurance-consent.d.ts",
69
+ "default": "./dist/insurance-consent.js"
70
+ },
63
71
  "./iso-jurisdictions": {
64
72
  "types": "./dist/iso-jurisdictions.d.ts",
65
73
  "default": "./dist/iso-jurisdictions.js"
66
74
  },
75
+ "./lost-animal-access": {
76
+ "types": "./dist/lost-animal-access.d.ts",
77
+ "default": "./dist/lost-animal-access.js"
78
+ },
67
79
  "./organization-application": {
68
80
  "types": "./dist/organization-application.d.ts",
69
81
  "default": "./dist/organization-application.js"
@@ -125,7 +137,7 @@
125
137
  "typescript": "^5.5.4"
126
138
  },
127
139
  "dependencies": {
128
- "gdc-common-utils-ts": "2.9.10",
140
+ "gdc-common-utils-ts": "2.9.12",
129
141
  "pako": "^2.2.0"
130
142
  },
131
143
  "engines": {