vet-data-utils-ts 0.5.21 → 0.5.23

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
@@ -2,12 +2,24 @@
2
2
 
3
3
  The product-local `place-service-directory` export models persistent public
4
4
  sites as claims-only Schema.org `Place` resources and their offerings as
5
- claims-only `Service` resources. Photos, accessibility, coordinates, address
6
- and opening-hours specifications remain neutral indexed claims. Native FHIR R5
7
- `Location` and `HealthcareService` are produced only by explicit projection
8
- helpers; the shared `gdc-*` claim catalog is unchanged while this profile is
5
+ claims-only `Service` or `Product` resources. A care organization publishes
6
+ services; an insurance organization publishes products in a Schema.org
7
+ `OfferCatalog`. Photos, accessibility, coordinates, address and opening-hours
8
+ specifications remain neutral indexed claims. Native FHIR R5 `Location`,
9
+ `HealthcareService` and `InsurancePlan` are produced only by explicit
10
+ projection helpers. Member-specific FHIR `Coverage` is never a public catalog
11
+ entry. The shared `gdc-*` claim catalog remains unchanged while this profile is
9
12
  validated in VetChain.
10
13
 
14
+ The customer-facing Schema.org `OfferCatalog` is distinct from the Eclipse
15
+ Dataspace Protocol catalog. `buildDataspaceCatalogAdvertisement(...)` exposes a
16
+ transferable catalog snapshot as a DSP 2025-1 Dataset with an ODRL use Offer;
17
+ `buildDataspaceDataAddress(...)` describes the endpoint only in the negotiated
18
+ transfer flow. The package deliberately has no third-party DSP runtime
19
+ dependency: the Eclipse project publishes the specification and Java TCK/EDC
20
+ implementation, while currently available npm implementations are independent
21
+ projects and are not adopted implicitly.
22
+
11
23
  ## Veterinary immunization credentials
12
24
 
13
25
  `buildVeterinaryImmunization(...)` creates a FHIR R4 `Immunization` with the
@@ -33,10 +33,31 @@ export declare enum ClaimsDirectoryServiceSchemaorg {
33
33
  areaServed = "org.schema.Service.areaServed",
34
34
  userSelected = "Service.user-selected"
35
35
  }
36
- /** Product-local allowlist for the experimental Place and Service directory profile. */
36
+ /** Schema.org Product claims for an organization's public commercial offering. */
37
+ export declare enum ClaimsProductSchemaorg {
38
+ identifier = "org.schema.Product.identifier",
39
+ additionalType = "org.schema.Product.additionalType",
40
+ name = "org.schema.Product.name",
41
+ description = "org.schema.Product.description",
42
+ providerIdentifier = "org.schema.Product.provider.identifier",
43
+ category = "org.schema.Product.category",
44
+ areaServed = "org.schema.Product.areaServed",
45
+ url = "org.schema.Product.url",
46
+ imageContentUrl = "org.schema.Product.image.contentUrl",
47
+ availabilityStarts = "org.schema.Product.offers.availabilityStarts",
48
+ availabilityEnds = "org.schema.Product.offers.availabilityEnds",
49
+ price = "org.schema.Product.offers.price",
50
+ priceCurrency = "org.schema.Product.offers.priceCurrency",
51
+ insuranceStatus = "org.schema.Product.additionalProperty.insuranceStatus",
52
+ insuranceCoverageType = "org.schema.Product.additionalProperty.insuranceCoverageType",
53
+ insuranceNetwork = "org.schema.Product.additionalProperty.insuranceNetwork",
54
+ userSelected = "Product.user-selected"
55
+ }
56
+ /** Product-local allowlist for the experimental Place, Service and Product directory profile. */
37
57
  export declare const DirectoryFlatClaimCatalog: Readonly<{
38
58
  readonly Place: readonly ClaimsPlaceSchemaorg[];
39
59
  readonly Service: readonly ClaimsDirectoryServiceSchemaorg[];
60
+ readonly Product: readonly ClaimsProductSchemaorg[];
40
61
  }>;
41
62
  export type DirectoryResourceType = keyof typeof DirectoryFlatClaimCatalog;
42
63
  export type DirectoryClaimValue = string | number | boolean | readonly string[];
@@ -105,11 +126,52 @@ export type ServiceDirectoryInput = Readonly<{
105
126
  areaServed?: readonly string[];
106
127
  userSelected?: boolean;
107
128
  }>;
129
+ export type ProductDirectoryInput = Readonly<{
130
+ id: string;
131
+ organizationIdentifier: string;
132
+ name: string;
133
+ description?: string;
134
+ productType: string;
135
+ categories?: readonly string[];
136
+ areaServed?: readonly string[];
137
+ url?: string;
138
+ imageUrl?: string;
139
+ availableFrom?: string;
140
+ availableThrough?: string;
141
+ price?: number;
142
+ priceCurrency?: string;
143
+ insurance?: Readonly<{
144
+ status: 'draft' | 'active' | 'retired' | 'unknown';
145
+ coverageTypes: readonly string[];
146
+ networks?: readonly string[];
147
+ }>;
148
+ userSelected?: boolean;
149
+ }>;
150
+ /** Sector-owned offering kind. It drives presentation only and grants no authority. */
151
+ export declare const OrganizationOfferingKinds: Readonly<{
152
+ readonly 'animal-care': "Service";
153
+ readonly 'animal-insurance': "Product";
154
+ }>;
155
+ /** Complete VetChain organization-sector presentation allowlist. */
156
+ export declare const OrganizationOfferingKindsBySector: Readonly<{
157
+ readonly 'animal-care': readonly ["Service"];
158
+ readonly 'animal-insurance': readonly ["Product"];
159
+ readonly 'animal-tech': readonly ["Service", "Product"];
160
+ readonly 'animal-research': readonly ["Service"];
161
+ }>;
162
+ /** Stable Eclipse Dataspace Protocol 2025-1 identifiers, including current editorial errata. */
163
+ export declare const DataspaceProtocol2025: Readonly<{
164
+ readonly release: "2025-1-err1";
165
+ readonly context: "https://w3id.org/dspace/2025/1/context.jsonld";
166
+ readonly httpEndpointType: "https://w3id.org/idsa/v4.1/HTTP";
167
+ }>;
108
168
  /** Builds the claims-only public Place authored by an organization. */
109
169
  export declare function buildPlaceDirectoryResource(input: PlaceDirectoryInput): DirectoryResource;
110
170
  /** Builds a claims-only Schema.org Service joined to its provider and public Places. */
111
171
  export declare function buildServiceDirectoryResource(input: ServiceDirectoryInput): DirectoryResource;
112
- /** Rejects native fields and converts a Place or Service into deterministic array-valued claims. */
172
+ /** Builds a claims-only Schema.org Product; insurance details remain a public plan definition, not personal Coverage. */
173
+ export declare function buildProductDirectoryResource(input: ProductDirectoryInput): DirectoryResource;
174
+ /** Rejects native fields and converts a directory resource into deterministic array-valued claims. */
113
175
  export declare function normalizeDirectoryFlatClaimsResource(input: unknown): DirectoryFlatClaimsResource;
114
176
  /** Explicitly projects one neutral Place to native FHIR R5 Location for export or interop. */
115
177
  export declare function projectPlaceToFhirR5Location(place: DirectoryResource | DirectoryFlatClaimsResource): Readonly<{
@@ -204,6 +266,140 @@ export declare function projectServiceToFhirR5HealthcareService(service: Directo
204
266
  reference: string;
205
267
  }[];
206
268
  }>;
269
+ /** Explicitly projects a public insurance Product definition to FHIR R5 InsurancePlan. */
270
+ export declare function projectProductToFhirR5InsurancePlan(product: DirectoryResource | DirectoryFlatClaimsResource): Readonly<{
271
+ ownedBy: {
272
+ reference: string;
273
+ };
274
+ coverageArea: {
275
+ reference: string;
276
+ }[];
277
+ network: {
278
+ reference: string;
279
+ }[];
280
+ coverage: {
281
+ type: {
282
+ coding: {
283
+ code: string;
284
+ }[];
285
+ };
286
+ network: {
287
+ reference: string;
288
+ }[];
289
+ benefit: {
290
+ type: {
291
+ coding: {
292
+ code: string;
293
+ }[];
294
+ };
295
+ }[];
296
+ }[];
297
+ meta: {
298
+ claims: {
299
+ 'InsurancePlan.identifier': string;
300
+ 'InsurancePlan.status': string;
301
+ 'InsurancePlan.name': string;
302
+ 'InsurancePlan.owned-by': string;
303
+ 'InsurancePlan.type': string;
304
+ };
305
+ };
306
+ period?: {
307
+ start: string;
308
+ end: string;
309
+ } | undefined;
310
+ resourceType: "InsurancePlan";
311
+ id: string;
312
+ identifier: {
313
+ value: string;
314
+ }[];
315
+ status: string;
316
+ type: {
317
+ coding: {
318
+ code: string;
319
+ }[];
320
+ }[];
321
+ name: string;
322
+ }>;
323
+ /** Builds the Schema.org JSON-LD OfferCatalog presented by one organization. */
324
+ export declare function buildOrganizationOfferCatalog(input: Readonly<{
325
+ id: string;
326
+ name: string;
327
+ organizationIdentifier: string;
328
+ sector: keyof typeof OrganizationOfferingKindsBySector;
329
+ offerings: readonly (DirectoryResource | DirectoryFlatClaimsResource)[];
330
+ }>): Readonly<{
331
+ '@context': "https://schema.org";
332
+ '@type': "OfferCatalog";
333
+ identifier: string;
334
+ name: string;
335
+ offeredBy: Readonly<{
336
+ '@type': "Organization";
337
+ identifier: string;
338
+ }>;
339
+ numberOfItems: number;
340
+ itemListElement: readonly Readonly<{
341
+ '@type': "Offer";
342
+ itemOffered: Readonly<{
343
+ '@type': "Place" | "Service" | "Product";
344
+ identifier: string;
345
+ name: string;
346
+ }>;
347
+ }>[];
348
+ }>;
349
+ /** Advertises a transferable catalog snapshot as a DSP Dataset with an ODRL use Offer. */
350
+ export declare function buildDataspaceCatalogAdvertisement(input: Readonly<{
351
+ catalogId: string;
352
+ participantId: string;
353
+ dataServiceId: string;
354
+ endpointUrl: string;
355
+ datasetId: string;
356
+ policyId: string;
357
+ distributionFormat: string;
358
+ }>): Readonly<{
359
+ '@context': readonly "https://w3id.org/dspace/2025/1/context.jsonld"[];
360
+ '@id': string;
361
+ '@type': "Catalog";
362
+ participantId: string;
363
+ service: readonly Readonly<{
364
+ '@id': string;
365
+ '@type': "DataService";
366
+ endpointURL: string;
367
+ }>[];
368
+ dataset: readonly Readonly<{
369
+ '@id': string;
370
+ '@type': "Dataset";
371
+ hasPolicy: readonly Readonly<{
372
+ '@id': string;
373
+ '@type': "Offer";
374
+ permission: readonly Readonly<{
375
+ action: "use";
376
+ }>[];
377
+ }>[];
378
+ distribution: readonly Readonly<{
379
+ '@type': "Distribution";
380
+ format: string;
381
+ accessService: string;
382
+ }>[];
383
+ }>[];
384
+ }>;
385
+ /** Builds the DSP DataAddress exchanged only after agreement negotiation starts a transfer. */
386
+ export declare function buildDataspaceDataAddress(input: Readonly<{
387
+ endpointType: string;
388
+ endpoint?: string;
389
+ endpointProperties?: readonly Readonly<{
390
+ name: string;
391
+ value: string;
392
+ }>[];
393
+ }>): Readonly<{
394
+ endpointProperties?: readonly Readonly<{
395
+ '@type': "EndpointProperty";
396
+ name: string;
397
+ value: string;
398
+ }>[] | undefined;
399
+ endpoint?: string | undefined;
400
+ '@type': "DataAddress";
401
+ endpointType: string;
402
+ }>;
207
403
  export type ClinicMarketplaceResult = Readonly<{
208
404
  place: DirectoryResource | DirectoryFlatClaimsResource;
209
405
  services: readonly (DirectoryResource | DirectoryFlatClaimsResource)[];
@@ -36,10 +36,50 @@ export var ClaimsDirectoryServiceSchemaorg;
36
36
  ClaimsDirectoryServiceSchemaorg["areaServed"] = "org.schema.Service.areaServed";
37
37
  ClaimsDirectoryServiceSchemaorg["userSelected"] = "Service.user-selected";
38
38
  })(ClaimsDirectoryServiceSchemaorg || (ClaimsDirectoryServiceSchemaorg = {}));
39
- /** Product-local allowlist for the experimental Place and Service directory profile. */
39
+ /** Schema.org Product claims for an organization's public commercial offering. */
40
+ export var ClaimsProductSchemaorg;
41
+ (function (ClaimsProductSchemaorg) {
42
+ ClaimsProductSchemaorg["identifier"] = "org.schema.Product.identifier";
43
+ ClaimsProductSchemaorg["additionalType"] = "org.schema.Product.additionalType";
44
+ ClaimsProductSchemaorg["name"] = "org.schema.Product.name";
45
+ ClaimsProductSchemaorg["description"] = "org.schema.Product.description";
46
+ ClaimsProductSchemaorg["providerIdentifier"] = "org.schema.Product.provider.identifier";
47
+ ClaimsProductSchemaorg["category"] = "org.schema.Product.category";
48
+ ClaimsProductSchemaorg["areaServed"] = "org.schema.Product.areaServed";
49
+ ClaimsProductSchemaorg["url"] = "org.schema.Product.url";
50
+ ClaimsProductSchemaorg["imageContentUrl"] = "org.schema.Product.image.contentUrl";
51
+ ClaimsProductSchemaorg["availabilityStarts"] = "org.schema.Product.offers.availabilityStarts";
52
+ ClaimsProductSchemaorg["availabilityEnds"] = "org.schema.Product.offers.availabilityEnds";
53
+ ClaimsProductSchemaorg["price"] = "org.schema.Product.offers.price";
54
+ ClaimsProductSchemaorg["priceCurrency"] = "org.schema.Product.offers.priceCurrency";
55
+ ClaimsProductSchemaorg["insuranceStatus"] = "org.schema.Product.additionalProperty.insuranceStatus";
56
+ ClaimsProductSchemaorg["insuranceCoverageType"] = "org.schema.Product.additionalProperty.insuranceCoverageType";
57
+ ClaimsProductSchemaorg["insuranceNetwork"] = "org.schema.Product.additionalProperty.insuranceNetwork";
58
+ ClaimsProductSchemaorg["userSelected"] = "Product.user-selected";
59
+ })(ClaimsProductSchemaorg || (ClaimsProductSchemaorg = {}));
60
+ /** Product-local allowlist for the experimental Place, Service and Product directory profile. */
40
61
  export const DirectoryFlatClaimCatalog = Object.freeze({
41
62
  Place: Object.freeze(Object.values(ClaimsPlaceSchemaorg)),
42
63
  Service: Object.freeze(Object.values(ClaimsDirectoryServiceSchemaorg)),
64
+ Product: Object.freeze(Object.values(ClaimsProductSchemaorg)),
65
+ });
66
+ /** Sector-owned offering kind. It drives presentation only and grants no authority. */
67
+ export const OrganizationOfferingKinds = Object.freeze({
68
+ 'animal-care': 'Service',
69
+ 'animal-insurance': 'Product',
70
+ });
71
+ /** Complete VetChain organization-sector presentation allowlist. */
72
+ export const OrganizationOfferingKindsBySector = Object.freeze({
73
+ 'animal-care': Object.freeze(['Service']),
74
+ 'animal-insurance': Object.freeze(['Product']),
75
+ 'animal-tech': Object.freeze(['Service', 'Product']),
76
+ 'animal-research': Object.freeze(['Service']),
77
+ });
78
+ /** Stable Eclipse Dataspace Protocol 2025-1 identifiers, including current editorial errata. */
79
+ export const DataspaceProtocol2025 = Object.freeze({
80
+ release: '2025-1-err1',
81
+ context: 'https://w3id.org/dspace/2025/1/context.jsonld',
82
+ httpEndpointType: 'https://w3id.org/idsa/v4.1/HTTP',
43
83
  });
44
84
  const RESOURCE_ID_PATTERN = /^[A-Za-z0-9\-.]{1,64}$/;
45
85
  const TIME_PATTERN = /^(?:[01]\d|2[0-3]):[0-5]\d$/;
@@ -55,6 +95,20 @@ function optional(value) {
55
95
  const normalized = String(value ?? '').trim();
56
96
  return normalized || undefined;
57
97
  }
98
+ function optionalHttpsUrl(value, code) {
99
+ const normalized = optional(value);
100
+ if (!normalized)
101
+ return undefined;
102
+ try {
103
+ const parsed = new URL(normalized);
104
+ if (parsed.protocol !== 'https:')
105
+ throw new Error();
106
+ return parsed.toString();
107
+ }
108
+ catch {
109
+ throw new TypeError(code);
110
+ }
111
+ }
58
112
  function unique(values, code) {
59
113
  const result = [...new Set((values ?? []).map(value => required(value, code)))];
60
114
  return Object.freeze(result);
@@ -184,12 +238,71 @@ export function buildServiceDirectoryResource(input) {
184
238
  claims[ClaimsDirectoryServiceSchemaorg.areaServed] = areaServed;
185
239
  return Object.freeze({ resourceType: 'Service', id, meta: Object.freeze({ claims: Object.freeze(claims) }) });
186
240
  }
187
- /** Rejects native fields and converts a Place or Service into deterministic array-valued claims. */
241
+ /** Builds a claims-only Schema.org Product; insurance details remain a public plan definition, not personal Coverage. */
242
+ export function buildProductDirectoryResource(input) {
243
+ const id = required(input.id, 'product_id_required');
244
+ if (!RESOURCE_ID_PATTERN.test(id))
245
+ throw new TypeError('product_id_invalid');
246
+ const productType = required(input.productType, 'product_type_required');
247
+ const categories = unique(input.categories, 'product_category_invalid');
248
+ const areaServed = unique(input.areaServed, 'product_area_invalid');
249
+ const url = optionalHttpsUrl(input.url, 'product_url_invalid');
250
+ const imageUrl = optionalHttpsUrl(input.imageUrl, 'product_image_url_invalid');
251
+ const availableFrom = input.availableFrom ? assertDate(input.availableFrom, 'product_period_invalid') : undefined;
252
+ const availableThrough = input.availableThrough ? assertDate(input.availableThrough, 'product_period_invalid') : undefined;
253
+ if (Boolean(availableFrom) !== Boolean(availableThrough) || (availableFrom && availableThrough && availableFrom > availableThrough))
254
+ throw new TypeError('product_period_invalid');
255
+ if (input.price !== undefined && (!Number.isFinite(input.price) || input.price < 0))
256
+ throw new TypeError('product_price_invalid');
257
+ const priceCurrency = optional(input.priceCurrency)?.toUpperCase();
258
+ if ((input.price === undefined) !== (priceCurrency === undefined) || (priceCurrency && !/^[A-Z]{3}$/.test(priceCurrency)))
259
+ throw new TypeError('product_price_invalid');
260
+ if (productType === 'insurance-plan' && !input.insurance)
261
+ throw new TypeError('insurance_product_profile_required');
262
+ const coverageTypes = unique(input.insurance?.coverageTypes, 'insurance_coverage_type_invalid');
263
+ if (input.insurance && !coverageTypes.length)
264
+ throw new TypeError('insurance_coverage_type_invalid');
265
+ const networks = unique(input.insurance?.networks, 'insurance_network_invalid');
266
+ const claims = {
267
+ [ClaimsProductSchemaorg.identifier]: id,
268
+ [ClaimsProductSchemaorg.providerIdentifier]: required(input.organizationIdentifier, 'product_organization_required'),
269
+ [ClaimsProductSchemaorg.name]: required(input.name, 'product_name_required'),
270
+ [ClaimsProductSchemaorg.additionalType]: productType,
271
+ [ClaimsProductSchemaorg.userSelected]: input.userSelected ?? false,
272
+ };
273
+ const description = optional(input.description);
274
+ if (description)
275
+ claims[ClaimsProductSchemaorg.description] = description;
276
+ if (categories.length)
277
+ claims[ClaimsProductSchemaorg.category] = categories;
278
+ if (areaServed.length)
279
+ claims[ClaimsProductSchemaorg.areaServed] = areaServed;
280
+ if (url)
281
+ claims[ClaimsProductSchemaorg.url] = url;
282
+ if (imageUrl)
283
+ claims[ClaimsProductSchemaorg.imageContentUrl] = imageUrl;
284
+ if (availableFrom)
285
+ claims[ClaimsProductSchemaorg.availabilityStarts] = availableFrom;
286
+ if (availableThrough)
287
+ claims[ClaimsProductSchemaorg.availabilityEnds] = availableThrough;
288
+ if (input.price !== undefined)
289
+ claims[ClaimsProductSchemaorg.price] = input.price;
290
+ if (priceCurrency)
291
+ claims[ClaimsProductSchemaorg.priceCurrency] = priceCurrency;
292
+ if (input.insurance)
293
+ claims[ClaimsProductSchemaorg.insuranceStatus] = input.insurance.status;
294
+ if (coverageTypes.length)
295
+ claims[ClaimsProductSchemaorg.insuranceCoverageType] = coverageTypes;
296
+ if (networks.length)
297
+ claims[ClaimsProductSchemaorg.insuranceNetwork] = networks;
298
+ return Object.freeze({ resourceType: 'Product', id, meta: Object.freeze({ claims: Object.freeze(claims) }) });
299
+ }
300
+ /** Rejects native fields and converts a directory resource into deterministic array-valued claims. */
188
301
  export function normalizeDirectoryFlatClaimsResource(input) {
189
302
  if (!input || typeof input !== 'object' || Array.isArray(input))
190
303
  throw new TypeError('directory_flat_resource_invalid');
191
304
  const value = input;
192
- if ((value.resourceType !== 'Place' && value.resourceType !== 'Service') || Object.keys(value).some(key => !['resourceType', 'id', 'meta'].includes(key)))
305
+ if ((value.resourceType !== 'Place' && value.resourceType !== 'Service' && value.resourceType !== 'Product') || Object.keys(value).some(key => !['resourceType', 'id', 'meta'].includes(key)))
193
306
  throw new TypeError('directory_flat_resource_invalid');
194
307
  const resourceType = value.resourceType;
195
308
  const id = required(value.id, 'directory_flat_resource_invalid');
@@ -292,6 +405,118 @@ export function projectServiceToFhirR5HealthcareService(service, input) {
292
405
  } },
293
406
  });
294
407
  }
408
+ /** Explicitly projects a public insurance Product definition to FHIR R5 InsurancePlan. */
409
+ export function projectProductToFhirR5InsurancePlan(product) {
410
+ if (product.resourceType !== 'Product')
411
+ throw new TypeError('product_resource_required');
412
+ if (first(product, ClaimsProductSchemaorg.additionalType) !== 'insurance-plan')
413
+ throw new TypeError('insurance_product_required');
414
+ const organization = required(first(product, ClaimsProductSchemaorg.providerIdentifier), 'product_organization_required');
415
+ const status = required(first(product, ClaimsProductSchemaorg.insuranceStatus), 'insurance_product_profile_required');
416
+ const coverageTypes = values(product, ClaimsProductSchemaorg.insuranceCoverageType);
417
+ if (!coverageTypes.length)
418
+ throw new TypeError('insurance_coverage_type_invalid');
419
+ const networks = values(product, ClaimsProductSchemaorg.insuranceNetwork);
420
+ const areaServed = values(product, ClaimsProductSchemaorg.areaServed);
421
+ const name = required(first(product, ClaimsProductSchemaorg.name), 'product_name_required');
422
+ const start = first(product, ClaimsProductSchemaorg.availabilityStarts);
423
+ const end = first(product, ClaimsProductSchemaorg.availabilityEnds);
424
+ const concept = (code) => ({ coding: [{ code }] });
425
+ return Object.freeze({
426
+ resourceType: 'InsurancePlan', id: product.id,
427
+ identifier: [{ value: product.id }], status, type: [concept('insurance-plan')], name,
428
+ ...(start && end ? { period: { start, end } } : {}),
429
+ ownedBy: { reference: organization },
430
+ coverageArea: areaServed.map(reference => ({ reference })),
431
+ network: networks.map(reference => ({ reference })),
432
+ coverage: coverageTypes.map(type => ({ type: concept(type), network: networks.map(reference => ({ reference })), benefit: [{ type: concept(type) }] })),
433
+ meta: { claims: {
434
+ 'InsurancePlan.identifier': product.id,
435
+ 'InsurancePlan.status': status,
436
+ 'InsurancePlan.name': name,
437
+ 'InsurancePlan.owned-by': organization,
438
+ 'InsurancePlan.type': 'insurance-plan',
439
+ } },
440
+ });
441
+ }
442
+ /** Builds the Schema.org JSON-LD OfferCatalog presented by one organization. */
443
+ export function buildOrganizationOfferCatalog(input) {
444
+ const allowedKinds = OrganizationOfferingKindsBySector[input.sector];
445
+ const organizationIdentifier = required(input.organizationIdentifier, 'organization_identifier_required');
446
+ const itemListElement = input.offerings.map(offering => {
447
+ if (!allowedKinds.includes(offering.resourceType))
448
+ throw new TypeError('organization_offering_kind_invalid');
449
+ const isService = offering.resourceType === 'Service';
450
+ const nameClaim = isService ? ClaimsDirectoryServiceSchemaorg.name : ClaimsProductSchemaorg.name;
451
+ const providerClaim = isService ? ClaimsDirectoryServiceSchemaorg.providerIdentifier : ClaimsProductSchemaorg.providerIdentifier;
452
+ if (first(offering, providerClaim) !== organizationIdentifier)
453
+ throw new TypeError('organization_offering_owner_invalid');
454
+ return Object.freeze({
455
+ '@type': 'Offer',
456
+ itemOffered: Object.freeze({
457
+ '@type': offering.resourceType,
458
+ identifier: offering.id,
459
+ name: required(first(offering, nameClaim), 'organization_offering_name_required'),
460
+ }),
461
+ });
462
+ });
463
+ return Object.freeze({
464
+ '@context': 'https://schema.org', '@type': 'OfferCatalog',
465
+ identifier: required(input.id, 'organization_catalog_id_required'),
466
+ name: required(input.name, 'organization_catalog_name_required'),
467
+ offeredBy: Object.freeze({ '@type': 'Organization', identifier: organizationIdentifier }),
468
+ numberOfItems: itemListElement.length,
469
+ itemListElement: Object.freeze(itemListElement),
470
+ });
471
+ }
472
+ /** Advertises a transferable catalog snapshot as a DSP Dataset with an ODRL use Offer. */
473
+ export function buildDataspaceCatalogAdvertisement(input) {
474
+ const endpointURL = optionalHttpsUrl(input.endpointUrl, 'dataspace_endpoint_url_invalid');
475
+ if (!endpointURL)
476
+ throw new TypeError('dataspace_endpoint_url_invalid');
477
+ const dataServiceId = required(input.dataServiceId, 'dataspace_data_service_id_required');
478
+ const datasetId = required(input.datasetId, 'dataspace_dataset_id_required');
479
+ const service = Object.freeze({ '@id': dataServiceId, '@type': 'DataService', endpointURL });
480
+ const dataset = Object.freeze({
481
+ '@id': datasetId,
482
+ '@type': 'Dataset',
483
+ hasPolicy: Object.freeze([Object.freeze({
484
+ '@id': required(input.policyId, 'dataspace_policy_id_required'),
485
+ '@type': 'Offer',
486
+ permission: Object.freeze([Object.freeze({ action: 'use' })]),
487
+ })]),
488
+ distribution: Object.freeze([Object.freeze({
489
+ '@type': 'Distribution',
490
+ format: required(input.distributionFormat, 'dataspace_distribution_format_required'),
491
+ accessService: dataServiceId,
492
+ })]),
493
+ });
494
+ return Object.freeze({
495
+ '@context': Object.freeze([DataspaceProtocol2025.context]),
496
+ '@id': required(input.catalogId, 'dataspace_catalog_id_required'),
497
+ '@type': 'Catalog',
498
+ participantId: required(input.participantId, 'dataspace_participant_id_required'),
499
+ service: Object.freeze([service]),
500
+ dataset: Object.freeze([dataset]),
501
+ });
502
+ }
503
+ /** Builds the DSP DataAddress exchanged only after agreement negotiation starts a transfer. */
504
+ export function buildDataspaceDataAddress(input) {
505
+ const endpointType = required(input.endpointType, 'dataspace_data_address_invalid');
506
+ if (!endpointType.startsWith('https://'))
507
+ throw new TypeError('dataspace_data_address_invalid');
508
+ const endpoint = optionalHttpsUrl(input.endpoint, 'dataspace_data_address_invalid');
509
+ const endpointProperties = (input.endpointProperties ?? []).map(property => Object.freeze({
510
+ '@type': 'EndpointProperty',
511
+ name: required(property.name, 'dataspace_data_address_invalid'),
512
+ value: required(property.value, 'dataspace_data_address_invalid'),
513
+ }));
514
+ return Object.freeze({
515
+ '@type': 'DataAddress', endpointType,
516
+ ...(endpoint ? { endpoint } : {}),
517
+ ...(endpointProperties.length ? { endpointProperties: Object.freeze(endpointProperties) } : {}),
518
+ });
519
+ }
295
520
  function normalizedPostal(value) { return value.replace(/[^A-Za-z0-9]/g, '').toUpperCase(); }
296
521
  function distanceKm(left, right) {
297
522
  const radians = (value) => value * Math.PI / 180;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vet-data-utils-ts",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "Browser-safe governed VetChain data contracts",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Connecting Solution & Applications Ltd",