vet-data-utils-ts 0.5.37 → 0.6.0

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
@@ -23,15 +23,19 @@ and fear context as a claims-first social-history Observation. It deliberately
23
23
  uses narrative text rather than inventing a veterinary diagnosis code.
24
24
 
25
25
  The product-local `place-service-directory` export models persistent public
26
- sites as claims-only Schema.org `Place` resources and their offerings as
27
- claims-only `Service` or `Product` resources. A care organization publishes
28
- services; an insurance organization publishes products in a Schema.org
29
- `OfferCatalog`. Photos, accessibility, coordinates, address and opening-hours
30
- specifications remain neutral indexed claims. Native FHIR R5 `Location`,
31
- `HealthcareService` and `InsurancePlan` are produced only by explicit
32
- projection helpers. Member-specific FHIR `Coverage` is never a public catalog
33
- entry. The shared `gdc-*` claim catalog remains unchanged while this profile is
34
- validated in VetChain.
26
+ departments as claims-only Schema.org `Organization` resources, their sites as
27
+ `Place`, and their offerings as `Service` or `Product`. A department has one
28
+ parent legal organization, one or more Places and several offered Services.
29
+ `projectDepartmentToFhirR5HealthcareService(...)` aggregates those Services
30
+ into exactly one stable FHIR `HealthcareService` per department; an offered
31
+ Service is not a second department or communication sender. A care organization
32
+ publishes services; an insurance organization publishes products in a
33
+ Schema.org `OfferCatalog`. Logos, images, accessibility, coordinates, address
34
+ and opening-hours specifications remain neutral indexed claims. Native FHIR R5
35
+ `Organization`, `Location`, `HealthcareService` and `InsurancePlan` are
36
+ produced only by explicit projection helpers. Member-specific FHIR `Coverage`
37
+ is never a public catalog entry. The shared `gdc-*` claim catalog remains
38
+ unchanged while this profile is validated in VetChain.
35
39
 
36
40
  The customer-facing Schema.org `OfferCatalog` is distinct from the Eclipse
37
41
  Dataspace Protocol catalog. `buildDataspaceCatalogAdvertisement(...)` exposes a
@@ -21,6 +21,19 @@ export declare enum ClaimsPlaceSchemaorg {
21
21
  specialOpeningHoursSpecification = "org.schema.Place.specialOpeningHoursSpecification",
22
22
  userSelected = "Place.user-selected"
23
23
  }
24
+ /** Schema.org flat claims for a public department/subOrganization. */
25
+ export declare enum ClaimsDirectoryOrganizationSchemaorg {
26
+ identifier = "org.schema.Organization.identifier",
27
+ name = "org.schema.Organization.name",
28
+ description = "org.schema.Organization.description",
29
+ parentOrganizationIdentifier = "org.schema.Organization.parentOrganization.identifier",
30
+ additionalType = "org.schema.Organization.additionalType",
31
+ sameAs = "org.schema.Organization.sameAs",
32
+ locationIdentifier = "org.schema.Organization.location.identifier",
33
+ logoContentUrl = "org.schema.Organization.logo.contentUrl",
34
+ imageContentUrl = "org.schema.Organization.image.contentUrl",
35
+ userSelected = "Organization.user-selected"
36
+ }
24
37
  /** Schema.org Service claims needed to join a public service to one or more Places. */
25
38
  export declare enum ClaimsDirectoryServiceSchemaorg {
26
39
  identifier = "org.schema.Service.identifier",
@@ -55,6 +68,7 @@ export declare enum ClaimsProductSchemaorg {
55
68
  }
56
69
  /** Product-local allowlist for the experimental Place, Service and Product directory profile. */
57
70
  export declare const DirectoryFlatClaimCatalog: Readonly<{
71
+ readonly Organization: readonly ClaimsDirectoryOrganizationSchemaorg[];
58
72
  readonly Place: readonly ClaimsPlaceSchemaorg[];
59
73
  readonly Service: readonly ClaimsDirectoryServiceSchemaorg[];
60
74
  readonly Product: readonly ClaimsProductSchemaorg[];
@@ -89,6 +103,21 @@ export type SpecialOpeningHoursInput = Readonly<{
89
103
  opens?: string;
90
104
  closes?: string;
91
105
  }>;
106
+ export type DepartmentDirectoryInput = Readonly<{
107
+ id: string;
108
+ legalOrganizationIdentifier: string;
109
+ name: string;
110
+ description?: string;
111
+ specialty?: Readonly<{
112
+ system: 'http://loinc.org';
113
+ code: string;
114
+ display: string;
115
+ }>;
116
+ placeIdentifiers: readonly string[];
117
+ logoUrl?: string;
118
+ imageUrls?: readonly string[];
119
+ userSelected?: boolean;
120
+ }>;
92
121
  export type PlaceDirectoryInput = Readonly<{
93
122
  id: string;
94
123
  organizationIdentifier: string;
@@ -165,6 +194,14 @@ export declare const DataspaceProtocol2025: Readonly<{
165
194
  readonly context: "https://w3id.org/dspace/2025/1/context.jsonld";
166
195
  readonly httpEndpointType: "https://w3id.org/idsa/v4.1/HTTP";
167
196
  }>;
197
+ /**
198
+ * Builds the claims-only public department Organization.
199
+ *
200
+ * One department is one Schema.org/FHIR Organization and owns one stable
201
+ * HealthcareService projection whose id is the same department id. Offered
202
+ * Services remain separate directory records linked through providerIdentifier.
203
+ */
204
+ export declare function buildDepartmentDirectoryResource(input: DepartmentDirectoryInput): DirectoryResource;
168
205
  /** Builds the claims-only public Place authored by an organization. */
169
206
  export declare function buildPlaceDirectoryResource(input: PlaceDirectoryInput): DirectoryResource;
170
207
  /** Builds a claims-only Schema.org Service joined to its provider and public Places. */
@@ -173,6 +210,78 @@ export declare function buildServiceDirectoryResource(input: ServiceDirectoryInp
173
210
  export declare function buildProductDirectoryResource(input: ProductDirectoryInput): DirectoryResource;
174
211
  /** Rejects native fields and converts a directory resource into deterministic array-valued claims. */
175
212
  export declare function normalizeDirectoryFlatClaimsResource(input: unknown): DirectoryFlatClaimsResource;
213
+ /** Explicitly projects one public department to a native FHIR R5 Organization. */
214
+ export declare function projectDepartmentToFhirR5Organization(department: DirectoryResource | DirectoryFlatClaimsResource): Readonly<{
215
+ meta: {
216
+ claims: {
217
+ 'Organization.identifier': string;
218
+ 'Organization.active': string;
219
+ 'Organization.name': string | undefined;
220
+ 'Organization.part-of': string;
221
+ };
222
+ };
223
+ type?: {
224
+ coding: {
225
+ system: string;
226
+ code: string;
227
+ }[];
228
+ }[] | undefined;
229
+ partOf: {
230
+ reference: string;
231
+ };
232
+ description?: string | undefined;
233
+ resourceType: "Organization";
234
+ id: string;
235
+ active: true;
236
+ identifier: {
237
+ value: string;
238
+ }[];
239
+ name: string;
240
+ }>;
241
+ /**
242
+ * Projects all offered Services of one department into its single stable
243
+ * HealthcareService. The Services supply types; the department supplies the
244
+ * provider identity and allowed Locations.
245
+ */
246
+ export declare function projectDepartmentToFhirR5HealthcareService(department: DirectoryResource | DirectoryFlatClaimsResource, input: Readonly<{
247
+ services: readonly (DirectoryResource | DirectoryFlatClaimsResource)[];
248
+ places: readonly (DirectoryResource | DirectoryFlatClaimsResource)[];
249
+ }>): Readonly<{
250
+ meta: {
251
+ claims: {
252
+ 'HealthcareService.service-category'?: string[] | undefined;
253
+ 'HealthcareService.identifier': string;
254
+ 'HealthcareService.organization': string;
255
+ 'HealthcareService.name': string | undefined;
256
+ 'HealthcareService.location': string[];
257
+ 'HealthcareService.service-type': string[];
258
+ };
259
+ };
260
+ photo?: {
261
+ url: string;
262
+ } | undefined;
263
+ resourceType: "HealthcareService";
264
+ id: string;
265
+ active: true;
266
+ providedBy: {
267
+ reference: string;
268
+ };
269
+ name: string | undefined;
270
+ comment: string | undefined;
271
+ category: {
272
+ coding: {
273
+ code: string;
274
+ }[];
275
+ }[];
276
+ type: {
277
+ coding: {
278
+ code: string;
279
+ }[];
280
+ }[];
281
+ location: {
282
+ reference: string;
283
+ }[];
284
+ }>;
176
285
  /** Explicitly projects one neutral Place to native FHIR R5 Location for export or interop. */
177
286
  export declare function projectPlaceToFhirR5Location(place: DirectoryResource | DirectoryFlatClaimsResource): Readonly<{
178
287
  resourceType: "Location";
@@ -340,7 +449,7 @@ export declare function buildOrganizationOfferCatalog(input: Readonly<{
340
449
  itemListElement: readonly Readonly<{
341
450
  '@type': "Offer";
342
451
  itemOffered: Readonly<{
343
- '@type': "Place" | "Service" | "Product";
452
+ '@type': "Organization" | "Place" | "Service" | "Product";
344
453
  identifier: string;
345
454
  name: string;
346
455
  }>;
@@ -23,6 +23,20 @@ export var ClaimsPlaceSchemaorg;
23
23
  ClaimsPlaceSchemaorg["specialOpeningHoursSpecification"] = "org.schema.Place.specialOpeningHoursSpecification";
24
24
  ClaimsPlaceSchemaorg["userSelected"] = "Place.user-selected";
25
25
  })(ClaimsPlaceSchemaorg || (ClaimsPlaceSchemaorg = {}));
26
+ /** Schema.org flat claims for a public department/subOrganization. */
27
+ export var ClaimsDirectoryOrganizationSchemaorg;
28
+ (function (ClaimsDirectoryOrganizationSchemaorg) {
29
+ ClaimsDirectoryOrganizationSchemaorg["identifier"] = "org.schema.Organization.identifier";
30
+ ClaimsDirectoryOrganizationSchemaorg["name"] = "org.schema.Organization.name";
31
+ ClaimsDirectoryOrganizationSchemaorg["description"] = "org.schema.Organization.description";
32
+ ClaimsDirectoryOrganizationSchemaorg["parentOrganizationIdentifier"] = "org.schema.Organization.parentOrganization.identifier";
33
+ ClaimsDirectoryOrganizationSchemaorg["additionalType"] = "org.schema.Organization.additionalType";
34
+ ClaimsDirectoryOrganizationSchemaorg["sameAs"] = "org.schema.Organization.sameAs";
35
+ ClaimsDirectoryOrganizationSchemaorg["locationIdentifier"] = "org.schema.Organization.location.identifier";
36
+ ClaimsDirectoryOrganizationSchemaorg["logoContentUrl"] = "org.schema.Organization.logo.contentUrl";
37
+ ClaimsDirectoryOrganizationSchemaorg["imageContentUrl"] = "org.schema.Organization.image.contentUrl";
38
+ ClaimsDirectoryOrganizationSchemaorg["userSelected"] = "Organization.user-selected";
39
+ })(ClaimsDirectoryOrganizationSchemaorg || (ClaimsDirectoryOrganizationSchemaorg = {}));
26
40
  /** Schema.org Service claims needed to join a public service to one or more Places. */
27
41
  export var ClaimsDirectoryServiceSchemaorg;
28
42
  (function (ClaimsDirectoryServiceSchemaorg) {
@@ -59,6 +73,7 @@ export var ClaimsProductSchemaorg;
59
73
  })(ClaimsProductSchemaorg || (ClaimsProductSchemaorg = {}));
60
74
  /** Product-local allowlist for the experimental Place, Service and Product directory profile. */
61
75
  export const DirectoryFlatClaimCatalog = Object.freeze({
76
+ Organization: Object.freeze(Object.values(ClaimsDirectoryOrganizationSchemaorg)),
62
77
  Place: Object.freeze(Object.values(ClaimsPlaceSchemaorg)),
63
78
  Service: Object.freeze(Object.values(ClaimsDirectoryServiceSchemaorg)),
64
79
  Product: Object.freeze(Object.values(ClaimsProductSchemaorg)),
@@ -151,6 +166,51 @@ function encodeSpecialOpeningHours(value) {
151
166
  throw new TypeError('place_special_opening_hours_invalid');
152
167
  return JSON.stringify({ validFrom, validThrough, opens: value.opens, closes: value.closes });
153
168
  }
169
+ /**
170
+ * Builds the claims-only public department Organization.
171
+ *
172
+ * One department is one Schema.org/FHIR Organization and owns one stable
173
+ * HealthcareService projection whose id is the same department id. Offered
174
+ * Services remain separate directory records linked through providerIdentifier.
175
+ */
176
+ export function buildDepartmentDirectoryResource(input) {
177
+ const id = required(input.id, 'department_id_required');
178
+ if (!RESOURCE_ID_PATTERN.test(id))
179
+ throw new TypeError('department_id_invalid');
180
+ const placeIdentifiers = unique(input.placeIdentifiers, 'department_place_invalid');
181
+ if (!placeIdentifiers.length)
182
+ throw new TypeError('department_place_required');
183
+ const logoUrl = optionalHttpsUrl(input.logoUrl, 'department_logo_url_invalid');
184
+ const imageUrls = unique(input.imageUrls, 'department_image_url_invalid').map(value => {
185
+ const imageUrl = optionalHttpsUrl(value, 'department_image_url_invalid');
186
+ if (!imageUrl)
187
+ throw new TypeError('department_image_url_invalid');
188
+ return imageUrl;
189
+ });
190
+ const claims = {
191
+ [ClaimsDirectoryOrganizationSchemaorg.identifier]: id,
192
+ [ClaimsDirectoryOrganizationSchemaorg.name]: required(input.name, 'department_name_required'),
193
+ [ClaimsDirectoryOrganizationSchemaorg.parentOrganizationIdentifier]: required(input.legalOrganizationIdentifier, 'department_parent_organization_required'),
194
+ [ClaimsDirectoryOrganizationSchemaorg.sameAs]: `Organization/${id}`,
195
+ [ClaimsDirectoryOrganizationSchemaorg.locationIdentifier]: placeIdentifiers,
196
+ [ClaimsDirectoryOrganizationSchemaorg.userSelected]: input.userSelected ?? false,
197
+ };
198
+ const description = optional(input.description);
199
+ if (description)
200
+ claims[ClaimsDirectoryOrganizationSchemaorg.description] = description;
201
+ if (input.specialty) {
202
+ if (input.specialty.system !== 'http://loinc.org'
203
+ || !/^L(?:A|P)\d+(?:-\d+)?$/.test(input.specialty.code)
204
+ || !optional(input.specialty.display))
205
+ throw new TypeError('department_specialty_invalid');
206
+ claims[ClaimsDirectoryOrganizationSchemaorg.additionalType] = `${input.specialty.system}/${input.specialty.code}`;
207
+ }
208
+ if (logoUrl)
209
+ claims[ClaimsDirectoryOrganizationSchemaorg.logoContentUrl] = logoUrl;
210
+ if (imageUrls.length)
211
+ claims[ClaimsDirectoryOrganizationSchemaorg.imageContentUrl] = Object.freeze(imageUrls);
212
+ return Object.freeze({ resourceType: 'Organization', id, meta: Object.freeze({ claims: Object.freeze(claims) }) });
213
+ }
154
214
  /** Builds the claims-only public Place authored by an organization. */
155
215
  export function buildPlaceDirectoryResource(input) {
156
216
  const id = required(input.id, 'place_id_required');
@@ -302,7 +362,7 @@ export function normalizeDirectoryFlatClaimsResource(input) {
302
362
  if (!input || typeof input !== 'object' || Array.isArray(input))
303
363
  throw new TypeError('directory_flat_resource_invalid');
304
364
  const value = input;
305
- if ((value.resourceType !== 'Place' && value.resourceType !== 'Service' && value.resourceType !== 'Product') || Object.keys(value).some(key => !['resourceType', 'id', 'meta'].includes(key)))
365
+ if ((value.resourceType !== 'Organization' && value.resourceType !== 'Place' && value.resourceType !== 'Service' && value.resourceType !== 'Product') || Object.keys(value).some(key => !['resourceType', 'id', 'meta'].includes(key)))
306
366
  throw new TypeError('directory_flat_resource_invalid');
307
367
  const resourceType = value.resourceType;
308
368
  const id = required(value.id, 'directory_flat_resource_invalid');
@@ -338,6 +398,78 @@ function parseJson(value, code) {
338
398
  throw new TypeError(code);
339
399
  }
340
400
  }
401
+ /** Explicitly projects one public department to a native FHIR R5 Organization. */
402
+ export function projectDepartmentToFhirR5Organization(department) {
403
+ if (department.resourceType !== 'Organization')
404
+ throw new TypeError('department_resource_required');
405
+ const parent = required(first(department, ClaimsDirectoryOrganizationSchemaorg.parentOrganizationIdentifier), 'department_parent_organization_required');
406
+ const additionalType = first(department, ClaimsDirectoryOrganizationSchemaorg.additionalType);
407
+ return Object.freeze({
408
+ resourceType: 'Organization', id: department.id, active: true,
409
+ identifier: [{ value: department.id }],
410
+ name: required(first(department, ClaimsDirectoryOrganizationSchemaorg.name), 'department_name_required'),
411
+ ...(first(department, ClaimsDirectoryOrganizationSchemaorg.description)
412
+ ? { description: first(department, ClaimsDirectoryOrganizationSchemaorg.description) } : {}),
413
+ partOf: { reference: parent },
414
+ ...(additionalType ? { type: [{ coding: [{ system: 'http://loinc.org', code: additionalType.replace('http://loinc.org/', '') }] }] } : {}),
415
+ meta: { claims: {
416
+ 'Organization.identifier': department.id,
417
+ 'Organization.active': 'true',
418
+ 'Organization.name': first(department, ClaimsDirectoryOrganizationSchemaorg.name),
419
+ 'Organization.part-of': parent,
420
+ } },
421
+ });
422
+ }
423
+ /**
424
+ * Projects all offered Services of one department into its single stable
425
+ * HealthcareService. The Services supply types; the department supplies the
426
+ * provider identity and allowed Locations.
427
+ */
428
+ export function projectDepartmentToFhirR5HealthcareService(department, input) {
429
+ if (department.resourceType !== 'Organization')
430
+ throw new TypeError('department_resource_required');
431
+ const departmentLocations = values(department, ClaimsDirectoryOrganizationSchemaorg.locationIdentifier);
432
+ const places = departmentLocations.map(identifier => {
433
+ const place = input.places.find(candidate => candidate.resourceType === 'Place' && candidate.id === identifier);
434
+ if (!place)
435
+ throw new TypeError('department_place_not_found');
436
+ return place;
437
+ });
438
+ const services = input.services.filter(service => {
439
+ if (service.resourceType !== 'Service')
440
+ throw new TypeError('service_resource_required');
441
+ return first(service, ClaimsDirectoryServiceSchemaorg.providerIdentifier) === department.id;
442
+ });
443
+ if (!services.length)
444
+ throw new TypeError('department_service_required');
445
+ for (const service of services) {
446
+ if (values(service, ClaimsDirectoryServiceSchemaorg.placeIdentifier).some(identifier => !departmentLocations.includes(identifier))) {
447
+ throw new TypeError('department_service_place_invalid');
448
+ }
449
+ }
450
+ const serviceTypes = [...new Set(services.flatMap(service => values(service, ClaimsDirectoryServiceSchemaorg.serviceType)))];
451
+ const categories = [...new Set(services.flatMap(service => values(service, ClaimsDirectoryServiceSchemaorg.category)))];
452
+ const concept = (code) => ({ coding: [{ code }] });
453
+ const photoUrl = first(department, ClaimsDirectoryOrganizationSchemaorg.logoContentUrl)
454
+ || places.map(place => first(place, ClaimsPlaceSchemaorg.photoContentUrl)).find(Boolean);
455
+ return Object.freeze({
456
+ resourceType: 'HealthcareService', id: department.id, active: true,
457
+ providedBy: { reference: `Organization/${department.id}` },
458
+ name: first(department, ClaimsDirectoryOrganizationSchemaorg.name),
459
+ comment: first(department, ClaimsDirectoryOrganizationSchemaorg.description),
460
+ category: categories.map(concept), type: serviceTypes.map(concept),
461
+ location: places.map(place => ({ reference: `Location/${place.id}` })),
462
+ ...(photoUrl ? { photo: { url: photoUrl } } : {}),
463
+ meta: { claims: {
464
+ 'HealthcareService.identifier': department.id,
465
+ 'HealthcareService.organization': `Organization/${department.id}`,
466
+ 'HealthcareService.name': first(department, ClaimsDirectoryOrganizationSchemaorg.name),
467
+ 'HealthcareService.location': places.map(place => `Location/${place.id}`),
468
+ 'HealthcareService.service-type': serviceTypes,
469
+ ...(categories.length ? { 'HealthcareService.service-category': categories } : {}),
470
+ } },
471
+ });
472
+ }
341
473
  /** Explicitly projects one neutral Place to native FHIR R5 Location for export or interop. */
342
474
  export function projectPlaceToFhirR5Location(place) {
343
475
  if (place.resourceType !== 'Place')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vet-data-utils-ts",
3
- "version": "0.5.37",
3
+ "version": "0.6.0",
4
4
  "description": "Browser-safe governed VetChain data contracts",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Connecting Solution & Applications Ltd",