@claudebernard/node-fhir-mapper 2.1.2 → 2.1.4

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
@@ -211,6 +211,17 @@ back by the other.
211
211
  | `lstPathologiesCIM10` | `Condition` | `http://hl7.org/fhir/sid/icd-10` |
212
212
  | `lstIdComposantAllergie` / `allergies` | `AllergyIntolerance` | `.../CodeSystem/products-ingredients` |
213
213
 
214
+ #### Codifications and mappers
215
+
216
+ Pathologies and allergies already coded in a Claude Bernard code system are read as is, their
217
+ `display` being used as the label : `amm-pathologies` and ICD-10 `Condition` resources, and
218
+ `products-ingredients` `AllergyIntolerance` resources. The optional `allergiesMapper` and
219
+ `snomedPathologiesMapper` are only needed for codings expressed in another system, SNOMED CT
220
+ typically ; an error is reported when such a coding is found and the matching mapper is missing.
221
+
222
+ The wanted coding is looked up by system, not by position, so a `Condition` or an
223
+ `AllergyIntolerance` may carry several codings without the mapping picking the wrong one.
224
+
214
225
  #### Hepatic insufficiency
215
226
 
216
227
  The Child-Pugh `Observation` carries the numeric score in `valueQuantity` and the class as a
@@ -387,7 +398,20 @@ The medication mapper provides utilities for creating and extracting medication
387
398
  function createMedicationsFromBcbCodes(codes: string[]): MappingResponse<Bundle> {}
388
399
  ```
389
400
 
390
- Creates a FHIR Bundle containing Medication resources from BCB codes.
401
+ Creates a FHIR Bundle containing Medication resources from BCB codes. Each Medication is paired with a MedicationRequest that references it. Those MedicationRequest resources are blank : they carry no `dosageInstruction`, only the mandatory `status`, `intent`, `medication` and `subject` fields.
402
+
403
+ Both resources get a UUID v4 as `id`, so their ids differ from one call to the next. Only the Bundle `id` stays deterministic, derived from the codes it contains. Each Bundle entry carries a `fullUrl` built as `<ResourceType>/<id>` :
404
+
405
+ ```json
406
+ {
407
+ "fullUrl": "Medication/d813a438-a3ea-4608-ba50-d19a69e59569",
408
+ "resource": {
409
+ "resourceType": "Medication",
410
+ "id": "d813a438-a3ea-4608-ba50-d19a69e59569",
411
+ "code": { "coding": [{ "system": "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code", "code": "BCB001" }] }
412
+ }
413
+ }
414
+ ```
391
415
 
392
416
  ##### Usage
393
417
 
@@ -398,8 +422,8 @@ const bcbCodes = ['BCB001', 'BCB002', 'BCB003'];
398
422
  const response = medicationMapper.createMedicationsFromBcbCodes(bcbCodes);
399
423
 
400
424
  if (response.result) {
401
- // Bundle with Medication resources
402
- console.log(response.result.entry?.length); // 3 medications
425
+ // Bundle with the Medication resources first, then their MedicationRequest resources
426
+ console.log(response.result.entry?.length); // 6 : 3 medications + 3 medication requests
403
427
  }
404
428
  ```
405
429
 
@@ -410,7 +434,46 @@ function createMedicationsFromCIP13Codes(codes: string[]): MappingResponse<Bundl
410
434
  function createMedicationsFromCISCodes(codes: string[]): MappingResponse<Bundle> {}
411
435
  ```
412
436
 
413
- Similar functions for creating medications from CIP13 and CIS codes respectively.
437
+ Similar functions for creating medications from CIP13 and CIS codes respectively. They also add a blank MedicationRequest per Medication.
438
+
439
+ #### - createMedicationsFromProducts / createMedicationFromProduct
440
+
441
+ ```ts
442
+ function createMedicationsFromProducts(products: Product[]): MappingResponse<Bundle> {}
443
+ function createMedicationFromProduct(product: Product): Medication | undefined {}
444
+ ```
445
+
446
+ Same as `createMedicationsFromBcbCodes`, but starting from the `Product` objects returned by the BCB APIs (`@claudebernard/types`), so the medication labels are kept along with the codes. Each Medication carries one Coding per code the Product holds — `code` for the bcb-code, `properties.codes.cip13` (or `code13`) for the cip13-code, `properties.codes.cis` for the cis-code — each with the product label as `display`, and the same label as the CodeableConcept `text` :
447
+
448
+ ```json
449
+ {
450
+ "resourceType": "Medication",
451
+ "id": "95175bcb-f3cf-4eb8-83be-7956554cd0b8",
452
+ "code": {
453
+ "coding": [
454
+ { "system": "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code", "code": "76461", "display": "JANUMET 50 mg/1000 mg, comprimé pelliculé" },
455
+ { "system": "http://terminology.hl7.org/CodeSystem/cip13-code", "code": "3400957312114", "display": "JANUMET 50 mg/1000 mg, comprimé pelliculé" },
456
+ { "system": "http://terminology.hl7.org/CodeSystem/cis-code", "code": "66010671", "display": "JANUMET 50 mg/1000 mg, comprimé pelliculé" }
457
+ ],
458
+ "text": "JANUMET 50 mg/1000 mg, comprimé pelliculé"
459
+ }
460
+ }
461
+ ```
462
+
463
+ The label is read from `labels.label`, falling back on `labels.longLabel` then `labels.shortLabel`, depending on which fields the API in use fills ; the HTML flavoured fields (`labelHtml` & co) are never used. Unlike the other creation functions, the Bundle `id` is a UUID v4 as well, so it differs from one call to the next. A Product carrying none of the three codes is skipped and reported in the `errors` field of the response ; `createMedicationFromProduct` returns `undefined` for it.
464
+
465
+ ##### Usage
466
+
467
+ ```ts
468
+ import { medicationMapper } from '@claudebernard/fhir-mapper';
469
+
470
+ const response = medicationMapper.createMedicationsFromProducts(products);
471
+
472
+ if (response.result) {
473
+ // Bundle with the Medication resources first, then their MedicationRequest resources
474
+ console.log(response.result.entry?.length);
475
+ }
476
+ ```
414
477
 
415
478
  #### - extractBcbCodesFromMedications / extractCIP13CodesFromMedications / extractCISCodesFromMedications
416
479
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Dosage, Bundle } from 'fhir/r5';
2
- import { CBPatient } from '@claudebernard/types';
1
+ import { Dosage, Bundle, Medication } from 'fhir/r5';
2
+ import { CBPatient, Product } from '@claudebernard/types';
3
3
 
4
4
  interface BCBPosologieStructuree2 {
5
5
  adequationUP?: number | null;
@@ -191,6 +191,9 @@ declare namespace patientMapper_d {
191
191
  };
192
192
  }
193
193
 
194
+ declare const BCB_CODE_SYSTEM = "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code";
195
+ declare const CIP13_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/cip13-code";
196
+ declare const CIS_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/cis-code";
194
197
  /**
195
198
  * Generate a hash-based ID for a medication based on its codes
196
199
  */
@@ -204,64 +207,107 @@ declare const generateMedicationId: (codes: string[], system: string) => string;
204
207
  declare function extractCodesFromMedications(bundle: Bundle, system: string): string[];
205
208
  /**
206
209
  * Extracts a list of bcb-code strings from a Bundle containing Medication resources.
207
- * Only codes with system "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code" are included.
210
+ * Only codes with system BCB_CODE_SYSTEM are included.
208
211
  * @param bundle Bundle containing Medication resources
209
212
  * @returns List of BCB codes
210
213
  */
211
214
  declare function extractBcbCodesFromMedications(bundle: Bundle): string[];
212
215
  /**
213
216
  * Extracts a list of cip13-code strings from a Bundle containing Medication resources.
214
- * Only codes with system "http://terminology.hl7.org/CodeSystem/cip13-code" are included.
217
+ * Only codes with system CIP13_CODE_SYSTEM are included.
215
218
  * @param bundle Bundle containing Medication resources
216
219
  * @returns List of CIP13 codes
217
220
  */
218
221
  declare function extractCIP13CodesFromMedications(bundle: Bundle): string[];
219
222
  /**
220
223
  * Extracts a list of cis-code strings from a Bundle containing Medication resources.
221
- * Only codes with system "http://terminology.hl7.org/CodeSystem/cis-code" are included.
224
+ * Only codes with system CIS_CODE_SYSTEM are included.
222
225
  * @param bundle Bundle containing Medication resources
223
226
  * @returns List of CIS codes
224
227
  */
225
228
  declare function extractCISCodesFromMedications(bundle: Bundle): string[];
226
229
  /**
227
230
  * Creates a Bundle containing Medication resources from a list of bcb-code strings.
228
- * Each Medication will have a CodeableConcept with a Coding for the bcb-code system.
231
+ * Each Medication will have a CodeableConcept with a Coding for the bcb-code system,
232
+ * and is paired with a MedicationRequest referencing it (without dosage instruction).
229
233
  * @param bcbCodes List of BCB codes to convert to Medication resources
230
- * @returns MappingResponse containing a Bundle with Medication resources
234
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
231
235
  */
232
236
  declare function createMedicationsFromBcbCodes(bcbCodes: string[]): MappingResponse<Bundle>;
233
237
  /**
234
238
  * Creates a Bundle containing Medication resources from a list of CIP13 codes.
235
- * Each Medication will have a CodeableConcept with a Coding for the CIP13 system.
239
+ * Each Medication will have a CodeableConcept with a Coding for the CIP13 system,
240
+ * and is paired with a MedicationRequest referencing it (without dosage instruction).
236
241
  * @param cip13Codes List of CIP13 codes to convert to Medication resources
237
- * @returns MappingResponse containing a Bundle with Medication resources
242
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
238
243
  */
239
244
  declare function createMedicationsFromCIP13Codes(cip13Codes: string[]): MappingResponse<Bundle>;
240
245
  /**
241
246
  * Creates a Bundle containing Medication resources from a list of CIS codes.
242
- * Each Medication will have a CodeableConcept with a Coding for the CIS system.
247
+ * Each Medication will have a CodeableConcept with a Coding for the CIS system,
248
+ * and is paired with a MedicationRequest referencing it (without dosage instruction).
243
249
  * @param cisCodes List of CIS codes to convert to Medication resources
244
- * @returns MappingResponse containing a Bundle with Medication resources
250
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
245
251
  */
246
252
  declare function createMedicationsFromCISCodes(cisCodes: string[]): MappingResponse<Bundle>;
253
+ /**
254
+ * Reads the human readable label of a Product, falling back on the other plain text
255
+ * label fields when the preferred one is not filled by the BCB API in use.
256
+ * The HTML flavoured fields (labelHtml and friends) are never used.
257
+ * @param product Product to read the label from
258
+ * @returns The product label, or undefined when the Product carries none
259
+ */
260
+ declare const extractProductLabel: (product: Product) => string | undefined;
261
+ /**
262
+ * Creates a Medication from a Claude Bernard Product, keeping both its codes and its label.
263
+ * The Medication holds one Coding per code the Product carries (bcb-code, cip13-code, cis-code),
264
+ * each with the product label as `display`, and the same label as the CodeableConcept `text`.
265
+ * The Medication id is a UUID v4, so it differs from one call to the next.
266
+ * @param product Product to convert to a Medication resource
267
+ * @returns The Medication resource, or undefined when the Product carries no code at all
268
+ */
269
+ declare function createMedicationFromProduct(product: Product): Medication | undefined;
270
+ /**
271
+ * Creates a Bundle containing a Medication and its associated MedicationRequest for each Product.
272
+ * Each Medication keeps the product codes and label (see createMedicationFromProduct), and each
273
+ * MedicationRequest references its Medication without any dosage instruction.
274
+ * Products carrying no code at all are skipped and reported in the `errors` field.
275
+ * Every id, the Bundle one included, is a UUID v4, so they all differ from one call to
276
+ * the next, and every entry carries a fullUrl built as "<ResourceType>/<id>".
277
+ * @param products List of Products to convert to Medication / MedicationRequest resources
278
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
279
+ */
280
+ declare function createMedicationsFromProducts(products: Product[]): MappingResponse<Bundle>;
247
281
 
282
+ declare const medicationMapper_d_BCB_CODE_SYSTEM: typeof BCB_CODE_SYSTEM;
283
+ declare const medicationMapper_d_CIP13_CODE_SYSTEM: typeof CIP13_CODE_SYSTEM;
284
+ declare const medicationMapper_d_CIS_CODE_SYSTEM: typeof CIS_CODE_SYSTEM;
285
+ declare const medicationMapper_d_createMedicationFromProduct: typeof createMedicationFromProduct;
248
286
  declare const medicationMapper_d_createMedicationsFromBcbCodes: typeof createMedicationsFromBcbCodes;
249
287
  declare const medicationMapper_d_createMedicationsFromCIP13Codes: typeof createMedicationsFromCIP13Codes;
250
288
  declare const medicationMapper_d_createMedicationsFromCISCodes: typeof createMedicationsFromCISCodes;
289
+ declare const medicationMapper_d_createMedicationsFromProducts: typeof createMedicationsFromProducts;
251
290
  declare const medicationMapper_d_extractBcbCodesFromMedications: typeof extractBcbCodesFromMedications;
252
291
  declare const medicationMapper_d_extractCIP13CodesFromMedications: typeof extractCIP13CodesFromMedications;
253
292
  declare const medicationMapper_d_extractCISCodesFromMedications: typeof extractCISCodesFromMedications;
254
293
  declare const medicationMapper_d_extractCodesFromMedications: typeof extractCodesFromMedications;
294
+ declare const medicationMapper_d_extractProductLabel: typeof extractProductLabel;
255
295
  declare const medicationMapper_d_generateMedicationId: typeof generateMedicationId;
256
296
  declare namespace medicationMapper_d {
257
297
  export {
298
+ medicationMapper_d_BCB_CODE_SYSTEM as BCB_CODE_SYSTEM,
299
+ medicationMapper_d_CIP13_CODE_SYSTEM as CIP13_CODE_SYSTEM,
300
+ medicationMapper_d_CIS_CODE_SYSTEM as CIS_CODE_SYSTEM,
301
+ medicationMapper_d_createMedicationFromProduct as createMedicationFromProduct,
258
302
  medicationMapper_d_createMedicationsFromBcbCodes as createMedicationsFromBcbCodes,
259
303
  medicationMapper_d_createMedicationsFromCIP13Codes as createMedicationsFromCIP13Codes,
260
304
  medicationMapper_d_createMedicationsFromCISCodes as createMedicationsFromCISCodes,
305
+ medicationMapper_d_createMedicationsFromProducts as createMedicationsFromProducts,
261
306
  medicationMapper_d_extractBcbCodesFromMedications as extractBcbCodesFromMedications,
262
307
  medicationMapper_d_extractCIP13CodesFromMedications as extractCIP13CodesFromMedications,
263
308
  medicationMapper_d_extractCISCodesFromMedications as extractCISCodesFromMedications,
264
309
  medicationMapper_d_extractCodesFromMedications as extractCodesFromMedications,
310
+ medicationMapper_d_extractProductLabel as extractProductLabel,
265
311
  medicationMapper_d_generateMedicationId as generateMedicationId,
266
312
  };
267
313
  }
@@ -323,5 +369,12 @@ declare const simpleHash: (str: string) => string;
323
369
  */
324
370
  declare const generateHash: (data: any) => string;
325
371
 
326
- export { dosageMapper_d as dosageMapper, generateHash, medicationMapper_d as medicationMapper, medicationRequestMapper_d as medicationRequestMapper, patientMapper_d as patientMapper, simpleHash };
372
+ /**
373
+ * UUID v4 generation compatible with both Node.js and browser environments
374
+ * Like the hash utils, this avoids importing the Node.js crypto module to keep
375
+ * the package usable in a browser bundle
376
+ */
377
+ declare const generateUuidV4: () => string;
378
+
379
+ export { dosageMapper_d as dosageMapper, generateHash, generateUuidV4, medicationMapper_d as medicationMapper, medicationRequestMapper_d as medicationRequestMapper, patientMapper_d as patientMapper, simpleHash };
327
380
  export type { CodificationFunction, Coding, MappingError, MappingResponse, SimpleCodification };
package/dist/index.js CHANGED
@@ -670,6 +670,23 @@ const LEGACY_HEPATIC_ICD10_CODES = Object.keys(hepaticInsufficiencyReverseMap);
670
670
  const isAcceptedLoincCode = (code, acceptedCodes) => !!code && acceptedCodes.includes(code);
671
671
  /** True when any coding of the concept carries one of the accepted codes. */
672
672
  const hasAcceptedCode = (concept, acceptedCodes) => !!concept?.coding?.some(coding => isAcceptedLoincCode(coding.code, acceptedCodes));
673
+ /**
674
+ * Find the coding of a concept belonging to a given system.
675
+ *
676
+ * A concept often carries several codings, so the wanted one is not necessarily the first: reading
677
+ * `coding[0]` blindly would mix up the code and the label of two different code systems.
678
+ */
679
+ const findCodingBySystem = (concept, system) => concept?.coding?.find(coding => coding.system === system);
680
+ /** Read a coding as a codification, the label being carried by its display. */
681
+ const codingToCodification = (coding) => ({
682
+ code: coding.code ?? '',
683
+ label: coding.display ?? ''
684
+ });
685
+ /** Codifications of every condition coded in the given system, labels included. */
686
+ const conditionsToCodifications = (conditions, system) => conditions.flatMap(condition => {
687
+ const coding = findCodingBySystem(condition?.code, system);
688
+ return coding ? [codingToCodification(coding)] : [];
689
+ });
673
690
  /**
674
691
  * Body Surface Area, Du Bois formula.
675
692
  *
@@ -789,6 +806,21 @@ const sortEntries = async (target, entries, allergiesMapper, snomedPathologiesMa
789
806
  let lstIdComposantAllergie = [];
790
807
  let errorField = target === 'bcb' ? 'lstIdComposantAllergie' : 'allergies';
791
808
  for (const allergyEntry of allergyIntoleranceEntries) {
809
+ // An allergy already coded in the BCB ingredients system needs no mapper, its code is the
810
+ // idComposant expected on the BCB / CB side. This mirrors how AMM conditions are read.
811
+ const ingredientCoding = findCodingBySystem(allergyEntry?.code, INGREDIENTS_SYSTEM_URL);
812
+ if (ingredientCoding) {
813
+ if (ingredientCoding.code && !Number.isNaN(Number(ingredientCoding.code))) {
814
+ lstIdComposantAllergie.push(codingToCodification(ingredientCoding));
815
+ }
816
+ else {
817
+ errors.push({
818
+ field: errorField,
819
+ message: `Invalid ingredient code for allergy ${ingredientCoding.code ?? ''}`
820
+ });
821
+ }
822
+ continue;
823
+ }
792
824
  if (allergiesMapper) {
793
825
  const mappingResult = await allergiesMapper(allergyEntry?.code?.coding?.[0]);
794
826
  if (Array.isArray(mappingResult)) {
@@ -821,17 +853,18 @@ const sortEntries = async (target, entries, allergiesMapper, snomedPathologiesMa
821
853
  }
822
854
  // The legacy hepatic Condition is already consumed as the hepatic status, keep it out of the
823
855
  // ICD-10 pathologies so it is not reported twice.
824
- const cim10PathologiesEntries = conditionEntries.filter(entry => entry !== legacyHepaticCondition && entry?.code?.coding?.some(coding => coding.system === ICD10_SYSTEM_URL)).map(pathology => ({ code: pathology?.code?.coding?.[0]?.code || '', label: '' }));
856
+ const cim10PathologiesEntries = conditionsToCodifications(conditionEntries.filter(entry => entry !== legacyHepaticCondition), ICD10_SYSTEM_URL);
825
857
  const snomedPathologiesEntries = conditionEntries.filter(entry => entry?.code?.coding?.some(coding => coding.system === SNOMED_SYSTEM_URL));
826
- const ammPathologiesEntries = conditionEntries.filter(entry => entry?.code?.coding?.some(coding => coding.system === AMM_SYSTEM_URL)).map(pathology => ({ code: pathology?.code?.coding?.[0]?.code || '', label: '' }));
858
+ const ammPathologiesEntries = conditionsToCodifications(conditionEntries, AMM_SYSTEM_URL);
827
859
  let lstPathologies = [];
828
860
  lstPathologies.push(...ammPathologiesEntries);
829
861
  let lstCim10Pathologies = [];
830
862
  lstCim10Pathologies.push(...cim10PathologiesEntries);
831
863
  errorField = target === 'bcb' ? 'lstPathologiesAMM' : 'ammPathologies';
832
864
  for (const pathology of snomedPathologiesEntries) {
865
+ const snomedCoding = findCodingBySystem(pathology?.code, SNOMED_SYSTEM_URL);
833
866
  if (snomedPathologiesMapper) {
834
- const mappingResult = await snomedPathologiesMapper(pathology?.code?.coding?.[0]);
867
+ const mappingResult = await snomedPathologiesMapper(snomedCoding);
835
868
  if (Array.isArray(mappingResult)) {
836
869
  if (mappingResult.length > 0) {
837
870
  lstPathologies.push(...mappingResult);
@@ -839,7 +872,7 @@ const sortEntries = async (target, entries, allergiesMapper, snomedPathologiesMa
839
872
  else {
840
873
  errors.push({
841
874
  field: errorField,
842
- message: `No mapping found for snomed code ${pathology?.code?.coding?.[0]?.code}`
875
+ message: `No mapping found for snomed code ${snomedCoding?.code}`
843
876
  });
844
877
  }
845
878
  }
@@ -849,7 +882,7 @@ const sortEntries = async (target, entries, allergiesMapper, snomedPathologiesMa
849
882
  else {
850
883
  errors.push({
851
884
  field: errorField,
852
- message: `No mapping found for snomed code ${pathology?.code?.coding?.[0]?.code}`
885
+ message: `No mapping found for snomed code ${snomedCoding?.code}`
853
886
  });
854
887
  }
855
888
  }
@@ -1373,6 +1406,42 @@ var patientMapper = /*#__PURE__*/Object.freeze({
1373
1406
  fhirToCb: fhirToCb
1374
1407
  });
1375
1408
 
1409
+ /**
1410
+ * UUID v4 generation compatible with both Node.js and browser environments
1411
+ * Like the hash utils, this avoids importing the Node.js crypto module to keep
1412
+ * the package usable in a browser bundle
1413
+ */
1414
+ const generateUuidV4 = () => {
1415
+ const webCrypto = globalThis.crypto;
1416
+ if (typeof webCrypto?.randomUUID === 'function') {
1417
+ return webCrypto.randomUUID();
1418
+ }
1419
+ const bytes = new Uint8Array(16);
1420
+ if (typeof webCrypto?.getRandomValues === 'function') {
1421
+ // Available in browsers even outside of a secure context, where randomUUID is not
1422
+ webCrypto.getRandomValues(bytes);
1423
+ }
1424
+ else {
1425
+ for (let i = 0; i < bytes.length; i++) {
1426
+ bytes[i] = Math.floor(Math.random() * 256);
1427
+ }
1428
+ }
1429
+ // Force the version (4) and variant (10xx) bits required by RFC 4122
1430
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
1431
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
1432
+ const hex = Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
1433
+ return [
1434
+ hex.substring(0, 8),
1435
+ hex.substring(8, 12),
1436
+ hex.substring(12, 16),
1437
+ hex.substring(16, 20),
1438
+ hex.substring(20)
1439
+ ].join('-');
1440
+ };
1441
+
1442
+ const BCB_CODE_SYSTEM = "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code";
1443
+ const CIP13_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/cip13-code";
1444
+ const CIS_CODE_SYSTEM = "http://terminology.hl7.org/CodeSystem/cis-code";
1376
1445
  /**
1377
1446
  * Generate a hash-based ID for a medication based on its codes
1378
1447
  */
@@ -1419,39 +1488,50 @@ function extractCodesFromMedications(bundle, system) {
1419
1488
  }
1420
1489
  /**
1421
1490
  * Extracts a list of bcb-code strings from a Bundle containing Medication resources.
1422
- * Only codes with system "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code" are included.
1491
+ * Only codes with system BCB_CODE_SYSTEM are included.
1423
1492
  * @param bundle Bundle containing Medication resources
1424
1493
  * @returns List of BCB codes
1425
1494
  */
1426
1495
  function extractBcbCodesFromMedications(bundle) {
1427
- return extractCodesFromMedications(bundle, "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code");
1496
+ return extractCodesFromMedications(bundle, BCB_CODE_SYSTEM);
1428
1497
  }
1429
1498
  /**
1430
1499
  * Extracts a list of cip13-code strings from a Bundle containing Medication resources.
1431
- * Only codes with system "http://terminology.hl7.org/CodeSystem/cip13-code" are included.
1500
+ * Only codes with system CIP13_CODE_SYSTEM are included.
1432
1501
  * @param bundle Bundle containing Medication resources
1433
1502
  * @returns List of CIP13 codes
1434
1503
  */
1435
1504
  function extractCIP13CodesFromMedications(bundle) {
1436
- return extractCodesFromMedications(bundle, "http://terminology.hl7.org/CodeSystem/cip13-code");
1505
+ return extractCodesFromMedications(bundle, CIP13_CODE_SYSTEM);
1437
1506
  }
1438
1507
  /**
1439
1508
  * Extracts a list of cis-code strings from a Bundle containing Medication resources.
1440
- * Only codes with system "http://terminology.hl7.org/CodeSystem/cis-code" are included.
1509
+ * Only codes with system CIS_CODE_SYSTEM are included.
1441
1510
  * @param bundle Bundle containing Medication resources
1442
1511
  * @returns List of CIS codes
1443
1512
  */
1444
1513
  function extractCISCodesFromMedications(bundle) {
1445
- return extractCodesFromMedications(bundle, "http://terminology.hl7.org/CodeSystem/cis-code");
1514
+ return extractCodesFromMedications(bundle, CIS_CODE_SYSTEM);
1446
1515
  }
1447
1516
  /**
1448
- * Creates a Bundle containing Medication resources from a list of bcb-code strings.
1449
- * Each Medication will have a CodeableConcept with a Coding for the bcb-code system.
1450
- * @param bcbCodes List of BCB codes to convert to Medication resources
1451
- * @returns MappingResponse containing a Bundle with Medication resources
1517
+ * Wraps a resource into a Bundle entry, carrying its fullUrl as "<ResourceType>/<id>"
1452
1518
  */
1453
- function createMedicationsFromBcbCodes(bcbCodes) {
1454
- if (!bcbCodes || bcbCodes.length === 0) {
1519
+ const toBundleEntry = (resource) => ({
1520
+ fullUrl: `${resource.resourceType}/${resource.id}`,
1521
+ resource: resource
1522
+ });
1523
+ /**
1524
+ * Creates a Bundle containing a Medication and its associated MedicationRequest for each code.
1525
+ * Each Medication holds a CodeableConcept with a Coding for the given system, and each
1526
+ * MedicationRequest references its Medication without any dosage instruction.
1527
+ * Resource ids are UUID v4, so they differ from one call to the next, and every entry
1528
+ * carries a fullUrl built as "<ResourceType>/<id>".
1529
+ * @param codes List of codes to convert to Medication / MedicationRequest resources
1530
+ * @param system The coding system the codes belong to
1531
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
1532
+ */
1533
+ const createMedicationsFromCodes = (codes, system) => {
1534
+ if (!codes || codes.length === 0) {
1455
1535
  return {
1456
1536
  result: {
1457
1537
  resourceType: "Bundle",
@@ -1462,127 +1542,205 @@ function createMedicationsFromBcbCodes(bcbCodes) {
1462
1542
  errors: []
1463
1543
  };
1464
1544
  }
1465
- const bundleId = generateMedicationId(bcbCodes, "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code");
1466
- const medications = bcbCodes.map((code, index) => {
1467
- const medicationId = `${bundleId}-med-${index}`;
1468
- return {
1469
- resourceType: 'Medication',
1470
- id: medicationId,
1471
- code: {
1472
- coding: [{
1473
- system: "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code",
1474
- code: code
1475
- }]
1545
+ const bundleId = generateMedicationId(codes, system);
1546
+ const medications = codes.map(code => ({
1547
+ resourceType: 'Medication',
1548
+ id: generateUuidV4(),
1549
+ code: {
1550
+ coding: [{
1551
+ system: system,
1552
+ code: code
1553
+ }]
1554
+ }
1555
+ }));
1556
+ const medicationRequests = medications.map(medication => ({
1557
+ resourceType: 'MedicationRequest',
1558
+ id: generateUuidV4(),
1559
+ status: 'active',
1560
+ intent: 'order',
1561
+ medication: {
1562
+ reference: {
1563
+ reference: `Medication/${medication.id}`
1476
1564
  }
1477
- };
1478
- });
1565
+ },
1566
+ subject: {
1567
+ reference: 'Patient/unknown'
1568
+ }
1569
+ }));
1479
1570
  const bundle = {
1480
1571
  resourceType: "Bundle",
1481
1572
  id: `medications-bundle-${bundleId}`,
1482
1573
  type: "collection",
1483
- entry: medications.map(medication => ({ resource: medication }))
1574
+ entry: [
1575
+ ...medications.map(medication => toBundleEntry(medication)),
1576
+ ...medicationRequests.map(medicationRequest => toBundleEntry(medicationRequest))
1577
+ ]
1484
1578
  };
1485
1579
  return {
1486
1580
  result: bundle,
1487
1581
  errors: []
1488
1582
  };
1583
+ };
1584
+ /**
1585
+ * Creates a Bundle containing Medication resources from a list of bcb-code strings.
1586
+ * Each Medication will have a CodeableConcept with a Coding for the bcb-code system,
1587
+ * and is paired with a MedicationRequest referencing it (without dosage instruction).
1588
+ * @param bcbCodes List of BCB codes to convert to Medication resources
1589
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
1590
+ */
1591
+ function createMedicationsFromBcbCodes(bcbCodes) {
1592
+ return createMedicationsFromCodes(bcbCodes, BCB_CODE_SYSTEM);
1489
1593
  }
1490
1594
  /**
1491
1595
  * Creates a Bundle containing Medication resources from a list of CIP13 codes.
1492
- * Each Medication will have a CodeableConcept with a Coding for the CIP13 system.
1596
+ * Each Medication will have a CodeableConcept with a Coding for the CIP13 system,
1597
+ * and is paired with a MedicationRequest referencing it (without dosage instruction).
1493
1598
  * @param cip13Codes List of CIP13 codes to convert to Medication resources
1494
- * @returns MappingResponse containing a Bundle with Medication resources
1599
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
1495
1600
  */
1496
1601
  function createMedicationsFromCIP13Codes(cip13Codes) {
1497
- if (!cip13Codes || cip13Codes.length === 0) {
1498
- return {
1499
- result: {
1500
- resourceType: "Bundle",
1501
- id: "empty-medications-bundle",
1502
- type: "collection",
1503
- entry: []
1504
- },
1505
- errors: []
1506
- };
1507
- }
1508
- const bundleId = generateMedicationId(cip13Codes, "http://terminology.hl7.org/CodeSystem/cip13-code");
1509
- const medications = cip13Codes.map((code, index) => {
1510
- const medicationId = `${bundleId}-med-${index}`;
1511
- return {
1512
- resourceType: 'Medication',
1513
- id: medicationId,
1514
- code: {
1515
- coding: [{
1516
- system: "http://terminology.hl7.org/CodeSystem/cip13-code",
1517
- code: code
1518
- }]
1519
- }
1520
- };
1521
- });
1522
- const bundle = {
1523
- resourceType: "Bundle",
1524
- id: `medications-bundle-${bundleId}`,
1525
- type: "collection",
1526
- entry: medications.map(medication => ({ resource: medication }))
1527
- };
1528
- return {
1529
- result: bundle,
1530
- errors: []
1531
- };
1602
+ return createMedicationsFromCodes(cip13Codes, CIP13_CODE_SYSTEM);
1532
1603
  }
1533
1604
  /**
1534
1605
  * Creates a Bundle containing Medication resources from a list of CIS codes.
1535
- * Each Medication will have a CodeableConcept with a Coding for the CIS system.
1606
+ * Each Medication will have a CodeableConcept with a Coding for the CIS system,
1607
+ * and is paired with a MedicationRequest referencing it (without dosage instruction).
1536
1608
  * @param cisCodes List of CIS codes to convert to Medication resources
1537
- * @returns MappingResponse containing a Bundle with Medication resources
1609
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
1538
1610
  */
1539
1611
  function createMedicationsFromCISCodes(cisCodes) {
1540
- if (!cisCodes || cisCodes.length === 0) {
1612
+ return createMedicationsFromCodes(cisCodes, CIS_CODE_SYSTEM);
1613
+ }
1614
+ /**
1615
+ * Reads the human readable label of a Product, falling back on the other plain text
1616
+ * label fields when the preferred one is not filled by the BCB API in use.
1617
+ * The HTML flavoured fields (labelHtml and friends) are never used.
1618
+ * @param product Product to read the label from
1619
+ * @returns The product label, or undefined when the Product carries none
1620
+ */
1621
+ const extractProductLabel = (product) => {
1622
+ const label = product.labels?.label
1623
+ ?? product.labels?.longLabel
1624
+ ?? product.labels?.shortLabel;
1625
+ return label?.trim() || undefined;
1626
+ };
1627
+ /**
1628
+ * Builds one Coding per code the Product carries (bcb-code, cip13-code, cis-code),
1629
+ * each one carrying the product label as its display.
1630
+ */
1631
+ const buildProductCodings = (product) => {
1632
+ const display = extractProductLabel(product);
1633
+ const codes = [
1634
+ { system: BCB_CODE_SYSTEM, code: product.code },
1635
+ { system: CIP13_CODE_SYSTEM, code: product.properties?.codes?.cip13 ?? product.code13 },
1636
+ { system: CIS_CODE_SYSTEM, code: product.properties?.codes?.cis }
1637
+ ];
1638
+ return codes
1639
+ .filter(({ code }) => !!code)
1640
+ .map(({ system, code }) => ({ system, code, ...(display ? { display } : {}) }));
1641
+ };
1642
+ /**
1643
+ * Creates a Medication from a Claude Bernard Product, keeping both its codes and its label.
1644
+ * The Medication holds one Coding per code the Product carries (bcb-code, cip13-code, cis-code),
1645
+ * each with the product label as `display`, and the same label as the CodeableConcept `text`.
1646
+ * The Medication id is a UUID v4, so it differs from one call to the next.
1647
+ * @param product Product to convert to a Medication resource
1648
+ * @returns The Medication resource, or undefined when the Product carries no code at all
1649
+ */
1650
+ function createMedicationFromProduct(product) {
1651
+ const coding = buildProductCodings(product);
1652
+ if (coding.length === 0) {
1653
+ return undefined;
1654
+ }
1655
+ const label = extractProductLabel(product);
1656
+ return {
1657
+ resourceType: 'Medication',
1658
+ id: generateUuidV4(),
1659
+ code: {
1660
+ coding,
1661
+ ...(label ? { text: label } : {})
1662
+ }
1663
+ };
1664
+ }
1665
+ /**
1666
+ * Creates a Bundle containing a Medication and its associated MedicationRequest for each Product.
1667
+ * Each Medication keeps the product codes and label (see createMedicationFromProduct), and each
1668
+ * MedicationRequest references its Medication without any dosage instruction.
1669
+ * Products carrying no code at all are skipped and reported in the `errors` field.
1670
+ * Every id, the Bundle one included, is a UUID v4, so they all differ from one call to
1671
+ * the next, and every entry carries a fullUrl built as "<ResourceType>/<id>".
1672
+ * @param products List of Products to convert to Medication / MedicationRequest resources
1673
+ * @returns MappingResponse containing a Bundle with Medication and MedicationRequest resources
1674
+ */
1675
+ function createMedicationsFromProducts(products) {
1676
+ if (!products || products.length === 0) {
1541
1677
  return {
1542
1678
  result: {
1543
1679
  resourceType: "Bundle",
1544
- id: "empty-medications-bundle",
1680
+ id: generateUuidV4(),
1545
1681
  type: "collection",
1546
1682
  entry: []
1547
1683
  },
1548
1684
  errors: []
1549
1685
  };
1550
1686
  }
1551
- const bundleId = generateMedicationId(cisCodes, "http://terminology.hl7.org/CodeSystem/cis-code");
1552
- const medications = cisCodes.map((code, index) => {
1553
- const medicationId = `${bundleId}-med-${index}`;
1554
- return {
1555
- resourceType: 'Medication',
1556
- id: medicationId,
1557
- code: {
1558
- coding: [{
1559
- system: "http://terminology.hl7.org/CodeSystem/cis-code",
1560
- code: code
1561
- }]
1562
- }
1563
- };
1687
+ const errors = [];
1688
+ const medications = [];
1689
+ products.forEach((product, index) => {
1690
+ const medication = createMedicationFromProduct(product);
1691
+ if (!medication) {
1692
+ errors.push({
1693
+ field: `products[${index}]`,
1694
+ message: 'Product carries no bcb-code, cip13-code nor cis-code, no Medication created'
1695
+ });
1696
+ return;
1697
+ }
1698
+ medications.push(medication);
1564
1699
  });
1700
+ const medicationRequests = medications.map(medication => ({
1701
+ resourceType: 'MedicationRequest',
1702
+ id: generateUuidV4(),
1703
+ status: 'active',
1704
+ intent: 'order',
1705
+ medication: {
1706
+ reference: {
1707
+ reference: `Medication/${medication.id}`
1708
+ }
1709
+ },
1710
+ subject: {
1711
+ reference: 'Patient/unknown'
1712
+ }
1713
+ }));
1565
1714
  const bundle = {
1566
1715
  resourceType: "Bundle",
1567
- id: `medications-bundle-${bundleId}`,
1716
+ id: generateUuidV4(),
1568
1717
  type: "collection",
1569
- entry: medications.map(medication => ({ resource: medication }))
1718
+ entry: [
1719
+ ...medications.map(medication => toBundleEntry(medication)),
1720
+ ...medicationRequests.map(medicationRequest => toBundleEntry(medicationRequest))
1721
+ ]
1570
1722
  };
1571
1723
  return {
1572
1724
  result: bundle,
1573
- errors: []
1725
+ errors
1574
1726
  };
1575
1727
  }
1576
1728
 
1577
1729
  var medicationMapper = /*#__PURE__*/Object.freeze({
1578
1730
  __proto__: null,
1731
+ BCB_CODE_SYSTEM: BCB_CODE_SYSTEM,
1732
+ CIP13_CODE_SYSTEM: CIP13_CODE_SYSTEM,
1733
+ CIS_CODE_SYSTEM: CIS_CODE_SYSTEM,
1734
+ createMedicationFromProduct: createMedicationFromProduct,
1579
1735
  createMedicationsFromBcbCodes: createMedicationsFromBcbCodes,
1580
1736
  createMedicationsFromCIP13Codes: createMedicationsFromCIP13Codes,
1581
1737
  createMedicationsFromCISCodes: createMedicationsFromCISCodes,
1738
+ createMedicationsFromProducts: createMedicationsFromProducts,
1582
1739
  extractBcbCodesFromMedications: extractBcbCodesFromMedications,
1583
1740
  extractCIP13CodesFromMedications: extractCIP13CodesFromMedications,
1584
1741
  extractCISCodesFromMedications: extractCISCodesFromMedications,
1585
1742
  extractCodesFromMedications: extractCodesFromMedications,
1743
+ extractProductLabel: extractProductLabel,
1586
1744
  generateMedicationId: generateMedicationId
1587
1745
  });
1588
1746
 
@@ -1747,5 +1905,5 @@ var medicationRequestMapper = /*#__PURE__*/Object.freeze({
1747
1905
  generateMedicationRequestId: generateMedicationRequestId
1748
1906
  });
1749
1907
 
1750
- export { dosageMapper, generateHash, medicationMapper, medicationRequestMapper, patientMapper, simpleHash };
1908
+ export { dosageMapper, generateHash, generateUuidV4, medicationMapper, medicationRequestMapper, patientMapper, simpleHash };
1751
1909
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudebernard/node-fhir-mapper",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "description": "A simple FHIR / BCB resource mapper to help stay interoperable while still using the Claude Bernard intelligence",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",