@claudebernard/node-fhir-mapper 2.1.2

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/dist/index.js ADDED
@@ -0,0 +1,1751 @@
1
+ const unitsOfTime = {
2
+ 's': { value: 0, label: 'seconde' },
3
+ 'min': { value: 10, label: 'minute' },
4
+ 'h': { value: 20, label: 'heure' },
5
+ 'd': { value: 40, label: 'jour' },
6
+ 'wk': { value: 50, label: 'semaine' },
7
+ 'mo': { value: 70, label: 'mois' },
8
+ 'a': { value: 100, label: 'an' }
9
+ };
10
+
11
+ /**
12
+ * Converts a CB Posology Bean to BCB Posologie Structuree2 format
13
+ * Based on the Java constructor BCBPosologieStructuree2(PosologyBean poso)
14
+ * Maps all fields from the Java constructor
15
+ */
16
+ function convertCBToBCB(poso) {
17
+ return {
18
+ // Direct mappings from the Java constructor - exact field mapping
19
+ idProduit: poso.productId,
20
+ codeTerrain: poso.code,
21
+ codeIndication: poso.indicationCode,
22
+ codeNature: poso.typeCode,
23
+ codeVoie: poso.routeCode,
24
+ codeProfil: poso.profilCode,
25
+ quantite1: poso.quantity1,
26
+ quantite2: poso.quantity2,
27
+ codeUnitePrise: poso.intakeUnitCode,
28
+ parKilo: poso.perKilo,
29
+ adequationUP: poso.adequacy,
30
+ codePar: poso.byCode,
31
+ combien1: poso.howMuch1,
32
+ combien2: poso.howMuch2,
33
+ tousLes: poso.every,
34
+ codeDuree1: poso.duration1Code,
35
+ codeDuree2: poso.duration2Code,
36
+ codeDuree3: poso.duration3Code,
37
+ codeMoment: poso.momentCode,
38
+ pendant1: poso.during1,
39
+ pendant2: poso.during2,
40
+ maximum: poso.maximum,
41
+ maximumPoids: poso.weightMaximum,
42
+ codeSpecifPrise1: poso.intakeSpecification1Code,
43
+ codeSpecifPrise2: poso.intakeSpecification2Code,
44
+ nbUnites: poso.numberOfUnits,
45
+ coeffMoment: poso.momentCoeff,
46
+ implicite: poso.implicit,
47
+ // Additional label fields for completeness (not in Java constructor but useful)
48
+ noPosologie: poso.patientPosology,
49
+ libellePosologie: poso.posologyLabel,
50
+ libIndication: poso.indicationLabel,
51
+ libVoie: poso.routeLabel,
52
+ libUnitePrise: poso.intakeUnitLabel,
53
+ libDuree1: poso.duration1Label,
54
+ libDuree2: poso.duration2Label,
55
+ libDuree3: poso.duration3Label,
56
+ libSpecifPrise1: poso.intakeSpecification1Label
57
+ };
58
+ }
59
+ /**
60
+ * Converts BCB Posologie Structuree2 back to CB Posology Bean format.
61
+ * This is the reverse of the convertCBToBCB function.
62
+ * @param bcbPosology BCB Posologie Structuree2 object
63
+ * @returns CB Posology Bean object
64
+ */
65
+ function convertBCBToCB(bcbPosology) {
66
+ return {
67
+ // Reverse mapping from BCB to CB (Java constructor mapping in reverse)
68
+ productId: bcbPosology.idProduit ?? undefined,
69
+ code: bcbPosology.codeTerrain ?? undefined,
70
+ indicationCode: bcbPosology.codeIndication ?? undefined,
71
+ typeCode: bcbPosology.codeNature ?? undefined,
72
+ routeCode: bcbPosology.codeVoie ?? undefined,
73
+ profilCode: bcbPosology.codeProfil ?? undefined,
74
+ quantity1: bcbPosology.quantite1 ?? undefined,
75
+ quantity2: bcbPosology.quantite2 ?? undefined,
76
+ intakeUnitCode: bcbPosology.codeUnitePrise ?? undefined,
77
+ perKilo: bcbPosology.parKilo ?? undefined,
78
+ adequacy: bcbPosology.adequationUP ?? undefined,
79
+ byCode: bcbPosology.codePar ?? undefined,
80
+ howMuch1: bcbPosology.combien1 ?? undefined,
81
+ howMuch2: bcbPosology.combien2 ?? undefined,
82
+ every: bcbPosology.tousLes ?? undefined,
83
+ duration1Code: bcbPosology.codeDuree1 ?? undefined,
84
+ duration2Code: bcbPosology.codeDuree2 ?? undefined,
85
+ duration3Code: bcbPosology.codeDuree3 ?? undefined,
86
+ momentCode: bcbPosology.codeMoment ?? undefined,
87
+ during1: bcbPosology.pendant1 ?? undefined,
88
+ during2: bcbPosology.pendant2 ?? undefined,
89
+ maximum: bcbPosology.maximum ?? undefined,
90
+ weightMaximum: bcbPosology.maximumPoids ?? undefined,
91
+ intakeSpecification1Code: bcbPosology.codeSpecifPrise1 ?? undefined,
92
+ intakeSpecification2Code: bcbPosology.codeSpecifPrise2 ?? undefined,
93
+ numberOfUnits: bcbPosology.nbUnites ?? undefined,
94
+ momentCoeff: bcbPosology.coeffMoment ?? undefined,
95
+ implicit: bcbPosology.implicite ?? undefined,
96
+ // Label fields (reverse mapping)
97
+ patientPosology: bcbPosology.noPosologie ?? undefined,
98
+ posologyLabel: bcbPosology.libellePosologie ?? undefined,
99
+ indicationLabel: bcbPosology.libIndication ?? undefined,
100
+ routeLabel: bcbPosology.libVoie ?? undefined,
101
+ intakeUnitLabel: bcbPosology.libUnitePrise ?? undefined,
102
+ duration1Label: bcbPosology.libDuree1 ?? undefined,
103
+ duration2Label: bcbPosology.libDuree2 ?? undefined,
104
+ duration3Label: bcbPosology.libDuree3 ?? undefined,
105
+ intakeSpecification1Label: bcbPosology.libSpecifPrise1 ?? undefined
106
+ };
107
+ }
108
+
109
+ function buildDoseAndRate(dosage) {
110
+ const coding = {
111
+ system: dosage.codeUnitePrise ? 'https://platform.claudebernard.fr/fhir/CodeSystem/dosage-intake-units' : undefined,
112
+ code: dosage.codeUnitePrise ?? undefined,
113
+ unit: dosage.codeUnitePrise ? dosage.libUnitePrise : undefined
114
+ };
115
+ const doseAndRate = dosage.quantite2 ? {
116
+ doseRange: {
117
+ low: {
118
+ value: dosage.quantite1,
119
+ ...coding
120
+ },
121
+ high: {
122
+ value: dosage.quantite2,
123
+ ...coding
124
+ }
125
+ }
126
+ } : {
127
+ doseQuantity: {
128
+ value: dosage.quantite1,
129
+ ...coding
130
+ }
131
+ };
132
+ return [doseAndRate];
133
+ }
134
+ // The formula used here is count = dose * frequency * (duration / period)
135
+ function calculateCount(dose, frequency, period, periodUnit, duration, durationUnit) {
136
+ // Provide default values for null or undefined parameters
137
+ const actualDose = dose ?? 0;
138
+ const actualFrequency = frequency ?? 0;
139
+ const actualPeriod = period ?? 1;
140
+ const actualDuration = duration ?? 0;
141
+ const actualPeriodUnit = periodUnit || 'd'; // Default to 'd' (days) if not provided
142
+ const actualDurationUnit = durationUnit || 'd'; // Default to 'd' (days) if not provided
143
+ // Convert duration to the same unit as period
144
+ let convertedDuration = actualDuration;
145
+ if (actualDurationUnit !== actualPeriodUnit && actualDuration && actualDurationUnit && actualPeriodUnit) {
146
+ convertedDuration = convertTime(actualDuration, actualDurationUnit, actualPeriodUnit);
147
+ }
148
+ // Check if dose, frequency, convertedDuration, or period is null or 0 and compute count accordingly
149
+ if (!actualDose) {
150
+ return actualFrequency * (convertedDuration / actualPeriod);
151
+ }
152
+ else if (!actualFrequency) {
153
+ return actualDose * (convertedDuration / actualPeriod);
154
+ }
155
+ else if (!convertedDuration) {
156
+ return actualDose * actualFrequency * actualPeriod;
157
+ }
158
+ else if (!actualPeriod) {
159
+ return actualDose * actualFrequency * convertedDuration;
160
+ }
161
+ else {
162
+ return actualDose * actualFrequency * (convertedDuration / actualPeriod);
163
+ }
164
+ }
165
+ function convertTime(value, fromUnit, toUnit) {
166
+ const conversionFactors = {
167
+ 's': 1,
168
+ 'min': 60,
169
+ 'h': 3600,
170
+ 'd': 86400,
171
+ 'wk': 604800,
172
+ 'mo': 2628000,
173
+ 'a': 31536000
174
+ };
175
+ if (!conversionFactors[fromUnit]) {
176
+ throw new Error(`Invalid fromUnit: ${fromUnit}`);
177
+ }
178
+ if (!conversionFactors[toUnit]) {
179
+ throw new Error(`Invalid toUnit: ${toUnit}`);
180
+ }
181
+ // Convert the input value to seconds
182
+ const valueInSeconds = value * conversionFactors[fromUnit];
183
+ // Convert from seconds to the desired unit
184
+ const convertedValue = valueInSeconds / conversionFactors[toUnit];
185
+ return convertedValue;
186
+ }
187
+ async function handleCodifications(dosage, indicationMapper, routeMapper, intakeMapper) {
188
+ let conditionCode;
189
+ let conditionLabel;
190
+ let intakeCode;
191
+ let intakeLabel;
192
+ let routeCode;
193
+ let routeLabel;
194
+ let errors = [];
195
+ if (dosage.asNeededFor && dosage.asNeededFor[0].coding && dosage.asNeededFor[0].coding.length > 0) {
196
+ if (dosage.asNeededFor[0]?.coding?.[0]?.system === 'https://platform.claudebernard.fr/fhir/CodeSystem/amm-pathologies') {
197
+ conditionCode = dosage.asNeededFor?.[0]?.coding?.[0]?.code;
198
+ conditionLabel = dosage.asNeededFor?.[0]?.coding?.[0]?.display;
199
+ }
200
+ else if (indicationMapper) {
201
+ const result = await indicationMapper(dosage?.asNeededFor?.[0]?.coding?.[0]);
202
+ let indication = undefined;
203
+ if (Array.isArray(result)) {
204
+ if (result.length > 0)
205
+ indication = result[0];
206
+ }
207
+ else if (result) {
208
+ indication = result;
209
+ }
210
+ if (indication) {
211
+ conditionCode = indication.code;
212
+ conditionLabel = indication.label;
213
+ }
214
+ else {
215
+ conditionCode = undefined;
216
+ conditionLabel = undefined;
217
+ errors.push({
218
+ field: 'codeIndication',
219
+ message: 'No mapping found for indication code'
220
+ });
221
+ }
222
+ }
223
+ else {
224
+ errors.push({
225
+ field: 'codeIndication',
226
+ message: 'The system used in the asNeededFor coding field is not supported and no custom mapper was provided'
227
+ });
228
+ }
229
+ }
230
+ if (dosage.route && dosage.route.coding && dosage.route.coding.length > 0) {
231
+ if (dosage.route?.coding?.[0]?.system === 'https://platform.claudebernard.fr/fhir/CodeSystem/dosage-routes') {
232
+ routeCode = parseInt(dosage?.route?.coding?.[0]?.code ?? '');
233
+ routeLabel = dosage.route?.coding?.[0]?.display;
234
+ }
235
+ else if (dosage.route && routeMapper) {
236
+ const result = await routeMapper(dosage.route?.coding?.[0]);
237
+ let route = undefined;
238
+ if (Array.isArray(result)) {
239
+ if (result.length > 0)
240
+ route = result[0];
241
+ }
242
+ else if (result) {
243
+ route = result;
244
+ }
245
+ if (route) {
246
+ routeCode = parseInt(route.code);
247
+ routeLabel = route.label;
248
+ }
249
+ else {
250
+ routeCode = undefined;
251
+ routeLabel = undefined;
252
+ errors.push({
253
+ field: 'codeVoie',
254
+ message: 'No mapping found for route code'
255
+ });
256
+ }
257
+ }
258
+ else {
259
+ errors.push({
260
+ field: 'codeVoie',
261
+ message: 'The system used in the route coding field is not supported and no custom mapper was provided'
262
+ });
263
+ }
264
+ }
265
+ if (dosage.doseAndRate && dosage.doseAndRate.length > 0) {
266
+ if (dosage.doseAndRate?.[0]?.doseQuantity?.system === 'https://platform.claudebernard.fr/fhir/CodeSystem/dosage-intake-units') {
267
+ intakeCode = parseInt(dosage.doseAndRate?.[0]?.doseQuantity.code ?? '');
268
+ intakeLabel = dosage.doseAndRate?.[0]?.doseQuantity.unit;
269
+ }
270
+ else if (dosage.doseAndRate && intakeMapper) {
271
+ const result = await intakeMapper(dosage.doseAndRate?.[0]?.doseQuantity);
272
+ let intake = undefined;
273
+ if (Array.isArray(result)) {
274
+ if (result.length > 0)
275
+ intake = result[0];
276
+ }
277
+ else if (result) {
278
+ intake = result;
279
+ }
280
+ if (intake) {
281
+ intakeCode = parseInt(intake.code);
282
+ intakeLabel = intake.label;
283
+ }
284
+ else {
285
+ intakeCode = undefined;
286
+ intakeLabel = undefined;
287
+ errors.push({
288
+ field: 'codeUnitePrise',
289
+ message: 'No mapping found for intake code'
290
+ });
291
+ }
292
+ }
293
+ else {
294
+ errors.push({
295
+ field: 'codeUnitePrise',
296
+ message: 'The system used in the doseQuantity coding field is not supported and no custom mapper was provided'
297
+ });
298
+ }
299
+ }
300
+ return {
301
+ conditionCode,
302
+ conditionLabel,
303
+ intakeCode,
304
+ intakeLabel,
305
+ routeCode,
306
+ routeLabel,
307
+ codificationErrors: errors
308
+ };
309
+ }
310
+
311
+ // adding this function to support new naming convention without breaking changes
312
+ async function fhirToBcb$2(dosageInstructions, indicationMapper, routeMapper, intakeMapper) {
313
+ return await mapToBcb(dosageInstructions, indicationMapper, routeMapper, intakeMapper);
314
+ }
315
+ async function mapToBcb(dosageInstructions, indicationMapper, routeMapper, intakeMapper) {
316
+ if (!dosageInstructions || dosageInstructions.length === 0) {
317
+ return [
318
+ {
319
+ result: undefined,
320
+ errors: [{
321
+ field: 'root',
322
+ message: 'Invalid Dosage array'
323
+ }]
324
+ }
325
+ ];
326
+ }
327
+ const safeIndicationMapper = wrapMaybeAsync$1(indicationMapper);
328
+ const safeRouteMapper = wrapMaybeAsync$1(routeMapper);
329
+ const safeIntakeMapper = wrapMaybeAsync$1(intakeMapper);
330
+ const results = [];
331
+ for (const dosage of dosageInstructions) {
332
+ let errors = [];
333
+ const periodUnit = dosage.timing?.repeat?.periodUnit;
334
+ const quantite1 = dosage?.doseAndRate?.[0]?.doseQuantity?.value ?? dosage?.doseAndRate?.[0]?.doseRange?.low?.value;
335
+ const quantite2 = dosage?.doseAndRate?.[0]?.doseRange?.high?.value ?? null;
336
+ let maximum = null;
337
+ let codeDuree3 = null;
338
+ let maxDosePerDay = null;
339
+ if (dosage.maxDosePerPeriod) {
340
+ maxDosePerDay = dosage.maxDosePerPeriod.find(period => period?.denominator?.unit === 'd' && period?.denominator?.value === 1);
341
+ if (maxDosePerDay) {
342
+ maximum = maxDosePerDay?.numerator?.value;
343
+ codeDuree3 = maxDosePerDay?.denominator?.unit;
344
+ }
345
+ }
346
+ let libSpecifPrise1 = dosage.asNeeded ? 'selon besoin' : null;
347
+ const doseSpacing = dosage.maxDosePerPeriod?.find(period => period?.denominator?.unit === 'h');
348
+ if (doseSpacing) {
349
+ const spacingText = `en espaçant les prises de ${doseSpacing?.denominator?.value}h minimum`;
350
+ libSpecifPrise1 = libSpecifPrise1 ? `${libSpecifPrise1}, ${spacingText}` : spacingText;
351
+ }
352
+ // handleCodifications must be updated to support async mappers
353
+ const { conditionCode, conditionLabel, intakeCode, intakeLabel, routeCode, routeLabel, codificationErrors } = await handleCodifications(dosage, safeIndicationMapper, safeRouteMapper, safeIntakeMapper);
354
+ const codeDuree2 = dosage?.timing?.repeat?.boundsDuration?.code;
355
+ const bcbDosage = {
356
+ noPosologie: dosage.sequence ?? undefined,
357
+ libellePosologie: dosage.text ?? undefined,
358
+ codeIndication: conditionCode,
359
+ libIndication: conditionLabel,
360
+ codeDuree1: periodUnit ? unitsOfTime[periodUnit]?.value : undefined,
361
+ libDuree1: periodUnit ? unitsOfTime[periodUnit]?.label : undefined,
362
+ quantite1: quantite1 ?? undefined,
363
+ quantite2: quantite2 ?? undefined,
364
+ tousLes: dosage.timing?.repeat?.period ?? undefined,
365
+ combien1: dosage.timing?.repeat?.frequency ?? undefined,
366
+ combien2: dosage.timing?.repeat?.frequencyMax ?? undefined,
367
+ pendant1: dosage.timing?.repeat?.boundsDuration?.value ?? undefined,
368
+ codeUnitePrise: intakeCode,
369
+ libUnitePrise: intakeLabel,
370
+ codeVoie: routeCode,
371
+ libVoie: routeLabel,
372
+ codeDuree2: codeDuree2 ? unitsOfTime[codeDuree2]?.value : undefined,
373
+ libDuree2: codeDuree2 ? unitsOfTime[codeDuree2]?.label : undefined,
374
+ maximum: maximum ?? undefined,
375
+ codeDuree3: codeDuree3 ? unitsOfTime[codeDuree3]?.value : undefined,
376
+ libDuree3: codeDuree3 ? unitsOfTime[codeDuree3]?.label : undefined,
377
+ libSpecifPrise1: libSpecifPrise1 ?? undefined
378
+ };
379
+ results.push({
380
+ result: bcbDosage,
381
+ errors: codificationErrors.concat(errors)
382
+ });
383
+ }
384
+ return results;
385
+ }
386
+ // adding this function to support new naming convention without breaking changes
387
+ function bcbToFhir$2(bcbDosages) {
388
+ return mapToFhir(bcbDosages);
389
+ }
390
+ function mapToFhir(bcbDosages) {
391
+ if (!bcbDosages || bcbDosages.length === 0) {
392
+ throw new Error('Invalid BCBPosologieStructuree2 array');
393
+ }
394
+ return bcbDosages.map((dosage) => {
395
+ const keys = Object.keys(unitsOfTime);
396
+ const periodUnit = keys.find(key => unitsOfTime[key].value === dosage.codeDuree1) ?? undefined;
397
+ const durationUnit = keys.find(key => unitsOfTime[key].value === dosage.codeDuree2) ?? undefined;
398
+ const maximumUnit = keys.find(key => unitsOfTime[key].value === dosage.codeDuree3) ?? undefined;
399
+ const route = dosage.codeVoie ? {
400
+ coding: [{
401
+ system: 'https://platform.claudebernard.fr/fhir/CodeSystem/dosage-routes',
402
+ code: dosage.codeVoie.toString(),
403
+ display: dosage.libVoie
404
+ }]
405
+ } : undefined;
406
+ const intake = dosage.codeUnitePrise ? {
407
+ coding: [{
408
+ system: 'https://platform.claudebernard.fr/fhir/CodeSystem/dosage-intake-units',
409
+ code: dosage.codeUnitePrise.toString(),
410
+ display: dosage.libUnitePrise
411
+ }]
412
+ } : undefined;
413
+ const indication = dosage.codeIndication ? [{
414
+ coding: [{
415
+ system: 'https://platform.claudebernard.fr/fhir/CodeSystem/amm-pathologies',
416
+ code: dosage.codeIndication,
417
+ display: dosage.libIndication
418
+ }]
419
+ }] : undefined;
420
+ const maxDosePerPeriod = dosage.maximum ? [{
421
+ numerator: {
422
+ value: dosage.maximum,
423
+ system: 'https://platform.claudebernard.fr/fhir/CodeSystem/dosage-intake-units',
424
+ code: intake?.coding?.[0]?.code,
425
+ unit: intake?.coding?.[0]?.display
426
+ },
427
+ denominator: {
428
+ value: 1,
429
+ unit: maximumUnit
430
+ }
431
+ }] : undefined;
432
+ const doseAndRate = buildDoseAndRate(dosage);
433
+ const boundsDuration = dosage.pendant1 ? { value: dosage.pendant1, unit: durationUnit } : undefined;
434
+ const asNeeded = dosage.libSpecifPrise1?.includes('besoin');
435
+ return {
436
+ result: {
437
+ text: dosage.libellePosologie,
438
+ sequence: dosage.noPosologie,
439
+ doseAndRate: doseAndRate,
440
+ maxDosePerPeriod: dosage.maximum ? maxDosePerPeriod : undefined,
441
+ route: route,
442
+ asNeeded: asNeeded,
443
+ asNeededFor: indication,
444
+ timing: {
445
+ repeat: {
446
+ frequency: dosage.combien1,
447
+ frequencyMax: dosage.combien2,
448
+ period: dosage.tousLes,
449
+ periodUnit: periodUnit,
450
+ boundsDuration: boundsDuration,
451
+ count: calculateCount(dosage.quantite1, dosage.combien1, dosage.tousLes, periodUnit, dosage.pendant1, durationUnit),
452
+ countMax: calculateCount(dosage.quantite2 ?? dosage.quantite1, dosage.combien2 ?? dosage.combien1, dosage.tousLes, periodUnit, dosage.pendant2 ?? dosage.pendant1, durationUnit)
453
+ }
454
+ }
455
+ },
456
+ errors: []
457
+ };
458
+ });
459
+ }
460
+ // Utility to wrap a possibly sync or async function so it always returns a Promise
461
+ function wrapMaybeAsync$1(fn) {
462
+ if (!fn)
463
+ return undefined;
464
+ return ((...args) => Promise.resolve(fn(...args)));
465
+ }
466
+ /**
467
+ * Converts FHIR Dosage instructions to CB Posology Bean format.
468
+ * Uses the existing fhirToBcb function and then converts BCB to CB format.
469
+ * @param dosageInstructions Array of FHIR Dosage resources
470
+ * @param indicationMapper Optional mapper function for indication codes
471
+ * @param routeMapper Optional mapper function for route codes
472
+ * @param intakeMapper Optional mapper function for intake codes
473
+ * @returns Promise of array of CB Posology Beans with mapping responses
474
+ */
475
+ async function fhirToCb$1(dosageInstructions, indicationMapper, routeMapper, intakeMapper) {
476
+ // First convert FHIR to BCB using existing function
477
+ const bcbResults = await fhirToBcb$2(dosageInstructions, indicationMapper, routeMapper, intakeMapper);
478
+ // Convert each BCB result to CB format
479
+ const cbResults = [];
480
+ for (const bcbResult of bcbResults) {
481
+ if (bcbResult.result) {
482
+ // Convert BCB to CB format (reverse conversion)
483
+ const cbPosology = convertBCBToCB(bcbResult.result);
484
+ cbResults.push({
485
+ result: cbPosology,
486
+ errors: bcbResult.errors || []
487
+ });
488
+ }
489
+ else {
490
+ // If BCB conversion failed, pass through the errors
491
+ cbResults.push({
492
+ result: undefined,
493
+ errors: bcbResult.errors || [{
494
+ field: 'conversion',
495
+ message: 'Failed to convert FHIR to BCB format'
496
+ }]
497
+ });
498
+ }
499
+ }
500
+ return cbResults;
501
+ }
502
+ /**
503
+ * Converts CB Posology Bean format to FHIR Dosage instructions.
504
+ * Converts CB to BCB format first, then uses existing bcbToFhir function.
505
+ * @param cbPosologies Array of CB Posology Beans
506
+ * @returns Array of FHIR Dosage resources with mapping responses
507
+ */
508
+ function cbToFhir$1(cbPosologies) {
509
+ if (!cbPosologies || cbPosologies.length === 0) {
510
+ return [{
511
+ result: undefined,
512
+ errors: [{
513
+ field: 'root',
514
+ message: 'Invalid CB Posology array'
515
+ }]
516
+ }];
517
+ }
518
+ // Convert each CB to BCB format using the converter
519
+ const bcbPosologies = [];
520
+ const conversionErrors = [];
521
+ for (const [index, cbPosology] of cbPosologies.entries()) {
522
+ try {
523
+ const bcbPosology = convertCBToBCB(cbPosology);
524
+ bcbPosologies.push(bcbPosology);
525
+ }
526
+ catch (error) {
527
+ conversionErrors.push({
528
+ field: `cbPosology[${index}]`,
529
+ message: `Failed to convert CB to BCB format: ${error}`
530
+ });
531
+ }
532
+ }
533
+ // If there were conversion errors, return them
534
+ if (conversionErrors.length > 0) {
535
+ return [{
536
+ result: undefined,
537
+ errors: conversionErrors
538
+ }];
539
+ }
540
+ // Use existing BCB to FHIR conversion
541
+ return bcbToFhir$2(bcbPosologies);
542
+ }
543
+
544
+ var dosageMapper = /*#__PURE__*/Object.freeze({
545
+ __proto__: null,
546
+ bcbToFhir: bcbToFhir$2,
547
+ cbToFhir: cbToFhir$1,
548
+ fhirToBcb: fhirToBcb$2,
549
+ fhirToCb: fhirToCb$1,
550
+ mapToBcb: mapToBcb,
551
+ mapToFhir: mapToFhir
552
+ });
553
+
554
+ /**
555
+ * Simple hash function compatible with both Node.js and browser environments
556
+ * This replaces the Node.js crypto module to avoid browser compatibility issues
557
+ */
558
+ const simpleHash = (str) => {
559
+ let hash = 0;
560
+ if (str.length === 0)
561
+ return hash.toString(16).padStart(8, '0');
562
+ for (let i = 0; i < str.length; i++) {
563
+ const char = str.charCodeAt(i);
564
+ hash = ((hash << 5) - hash) + char;
565
+ hash = hash & hash; // Convert to 32-bit integer
566
+ }
567
+ return Math.abs(hash).toString(16).padStart(8, '0').substring(0, 8);
568
+ };
569
+ /**
570
+ * Generate a deterministic 8-character hash from input data
571
+ * Compatible with browser and Node.js environments
572
+ */
573
+ const generateHash = (data) => {
574
+ const dataString = typeof data === 'string' ? data : JSON.stringify(data);
575
+ return simpleHash(dataString);
576
+ };
577
+
578
+ /* ================== System URLs ================== */
579
+ const LOINC_SYSTEM_URL = 'http://loinc.org';
580
+ const SNOMED_SYSTEM_URL = 'http://snomed.info/sct';
581
+ const ICD10_SYSTEM_URL = 'http://hl7.org/fhir/sid/icd-10';
582
+ const AMM_SYSTEM_URL = 'https://platform.claudebernard.fr/fhir/CodeSystem/amm-pathologies';
583
+ const INGREDIENTS_SYSTEM_URL = 'https://platform.claudebernard.fr/fhir/CodeSystem/products-ingredients';
584
+ const UCUM_SYSTEM_URL = 'http://unitsofmeasure.org';
585
+ /* ================== LOINC codes ================== */
586
+ const WEIGHT_LOINC_CODE = '29463-7'; // Body weight
587
+ const HEIGHT_LOINC_CODE = '8302-2'; // Body height
588
+ const PREGNANCY_LOINC_CODE = '82810-3'; // Pregnancy status
589
+ const BREASTFEEDING_LOINC_CODE = '63895-7'; // Breastfeeding
590
+ const AMENORRHEA_LOINC_CODE = '18185-9'; // Gestational age
591
+ const CHILD_PUGH_LOINC_CODE = '98152-2'; // Child-Pugh score
592
+ const CREATININEMIE_MOL_LOINC_CODE = '14682-9'; // Creatinine [Moles/volume] in Serum or Plasma
593
+ const CREATININEMIE_MG_LOINC_CODE = '2160-0'; // Creatinine [Mass/volume] in Serum or Plasma
594
+ const GFR_LOINC_CODE = '69405-9'; // Glomerular filtration rate (absolute, used when writing)
595
+ // LOINC codes for renal function - kept in sync with mvn-fhir-mapper (PatientMapperUtils)
596
+ const ACCEPTED_LOINC_CODES_FOR_RENAL_CLEARANCE = [
597
+ '2163-4', // Creatinine renal clearance
598
+ '2164-2', // Creatinine clearance
599
+ '13441-1', // Creatinine renal clearance/1.73 sq M.predicted
600
+ '13442-9', // Creatinine renal clearance/BSA
601
+ '13443-7', // Creatinine renal clearance/1.73 sq M.predicted by Cockcroft-Gault formula
602
+ '13446-0', // Creatinine renal clearance by 24 hour urine collection
603
+ '13447-8', // Creatinine renal clearance/1.73 sq M.predicted by 24 hour urine collection
604
+ '13449-4', // Creatinine renal clearance/BSA by 24 hour urine collection
605
+ '13450-2', // Creatinine renal clearance by Cockcroft-Gault formula
606
+ '26752-6', // Creatinine renal clearance by MDRD equation
607
+ '35591-7', // Creatinine renal clearance/1.73 sq M.predicted by MDRD equation
608
+ '35594-1', // Creatinine renal clearance/BSA by MDRD equation
609
+ '50380-5', // Creatinine renal clearance/1.73 sq M.predicted by CKD-EPI creatinine equation
610
+ '50381-3', // Creatinine renal clearance/BSA by CKD-EPI creatinine equation
611
+ '104805-7' // Creatinine renal clearance by CKD-EPI creatinine-cystatin C equation
612
+ ];
613
+ const ACCEPTED_LOINC_CODES_FOR_RENAL_GFR = [
614
+ '69405-9', // Glomerular filtration rate
615
+ '70969-1' // Glomerular filtration rate by creatinine-based formula
616
+ ];
617
+ const ACCEPTED_LOINC_CODES_FOR_RENAL_GFR_PER_BSA = [
618
+ '48642-3', // Glomerular filtration rate/1.73 sq M.predicted by CKD-EPI creatinine equation
619
+ '48643-1', // Glomerular filtration rate/1.73 sq M.predicted by MDRD equation
620
+ '50044-7', // Glomerular filtration rate/1.73 sq M.predicted
621
+ '50210-4', // Glomerular filtration rate/1.73 sq M.predicted by CKD-EPI creatinine-cystatin C equation
622
+ '50384-7', // Glomerular filtration rate/BSA predicted
623
+ '62238-1', // Glomerular filtration rate/1.73 sq M.predicted by Schwartz equation
624
+ '77147-7', // Glomerular filtration rate/1.73 sq M.predicted by CKD-EPI cystatin C equation
625
+ '78006-4', // Glomerular filtration rate/BSA by CKD-EPI creatinine equation
626
+ '88293-6', // Glomerular filtration rate/1.73 sq M.predicted by CKD-EPI creatinine-cystatin C equation
627
+ '88294-4', // Glomerular filtration rate/1.73 sq M.predicted by Full Age Spectrum creatinine equation
628
+ '94677-2', // Glomerular filtration rate/1.73 sq M.predicted by Berlin Initiative Study equation
629
+ '96591-3', // Glomerular filtration rate/1.73 sq M.predicted by European Kidney Function Consortium creatinine equation
630
+ '96592-1', // Glomerular filtration rate/1.73 sq M.predicted by European Kidney Function Consortium creatinine-cystatin C equation
631
+ '98979-8', // Glomerular filtration rate/1.73 sq M.predicted by creatinine-based formula
632
+ '98980-6', // Glomerular filtration rate/BSA by creatinine-based formula
633
+ '102097-3' // Glomerular filtration rate/1.73 sq M.predicted by CKD-EPI creatinine equation (2021)
634
+ ];
635
+ /* ================== Child-Pugh ================== */
636
+ const CHILD_PUGH_STAGE_A_CODE = '710065009'; // Child-Pugh score class A
637
+ const CHILD_PUGH_STAGE_B_CODE = '710066005'; // Child-Pugh score class B
638
+ const CHILD_PUGH_STAGE_C_CODE = '710067001'; // Child-Pugh score class C
639
+ /**
640
+ * Child-Pugh stage details, keyed by BCB stage letter.
641
+ * Scores are the representative value of each class (A: < 7, B: 7-9, C: >= 10).
642
+ */
643
+ const childPughStageMap = {
644
+ 'A': { score: 6, snomedCode: CHILD_PUGH_STAGE_A_CODE, snomedDisplay: 'Child-Pugh score class A', stageText: 'Stade A' },
645
+ 'B': { score: 8, snomedCode: CHILD_PUGH_STAGE_B_CODE, snomedDisplay: 'Child-Pugh score class B', stageText: 'Stade B' },
646
+ 'C': { score: 11, snomedCode: CHILD_PUGH_STAGE_C_CODE, snomedDisplay: 'Child-Pugh score class C', stageText: 'Stade C' }
647
+ };
648
+ const childPughSnomedReverseMap = {
649
+ [CHILD_PUGH_STAGE_A_CODE]: 'A',
650
+ [CHILD_PUGH_STAGE_B_CODE]: 'B',
651
+ [CHILD_PUGH_STAGE_C_CODE]: 'C'
652
+ };
653
+ const genderMap = {
654
+ male: 'M',
655
+ female: 'F'
656
+ };
657
+ const fhirGenderMap = {
658
+ M: 'male',
659
+ F: 'female'
660
+ };
661
+ /** @deprecated See {@link hepaticInsufficiencyMap}. */
662
+ const hepaticInsufficiencyReverseMap = {
663
+ 'K72.90': 'A',
664
+ 'K72.91': 'B',
665
+ 'K72.92': 'C'
666
+ };
667
+ const LEGACY_HEPATIC_ICD10_CODES = Object.keys(hepaticInsufficiencyReverseMap);
668
+ /* ================== Clinical helpers ================== */
669
+ /** Check whether a code belongs to a list of accepted LOINC codes. */
670
+ const isAcceptedLoincCode = (code, acceptedCodes) => !!code && acceptedCodes.includes(code);
671
+ /** True when any coding of the concept carries one of the accepted codes. */
672
+ const hasAcceptedCode = (concept, acceptedCodes) => !!concept?.coding?.some(coding => isAcceptedLoincCode(coding.code, acceptedCodes));
673
+ /**
674
+ * Body Surface Area, Du Bois formula.
675
+ *
676
+ * @param height Height in centimeters
677
+ * @param weight Weight in kilograms
678
+ * @returns BSA in square meters
679
+ */
680
+ const calculateBSA = (height, weight) => Math.sqrt((height * weight) / 3600);
681
+ /**
682
+ * Convert a GFR normalized to 1.73 m² into an absolute GFR.
683
+ *
684
+ * @param normalizedGFR GFR normalized to 1.73 m²
685
+ * @param bsa Body surface area in m²
686
+ */
687
+ const normalizedGFRtoAbsolute = (normalizedGFR, bsa) => Math.round((normalizedGFR * (bsa / 1.73)) * 100) / 100;
688
+ /**
689
+ * Convert a Child-Pugh score into its stage letter.
690
+ *
691
+ * @param score Child-Pugh total score
692
+ * @returns 'A' (mild), 'B' (moderate) or 'C' (severe)
693
+ */
694
+ const childPughScoreToStage = (score) => {
695
+ if (score < 7) {
696
+ return 'A';
697
+ }
698
+ if (score < 10) {
699
+ return 'B';
700
+ }
701
+ return 'C';
702
+ };
703
+ /** Convert a SNOMED CT Child-Pugh class code into its stage letter, or '' when unknown. */
704
+ const snomedCodeToChildPughStage = (snomedCode) => snomedCode ? childPughSnomedReverseMap[snomedCode] ?? '' : '';
705
+ const calculateAgeInMonths = (birthDate) => {
706
+ const birth = new Date(birthDate);
707
+ const today = new Date();
708
+ const yearsDifference = today.getFullYear() - birth.getFullYear();
709
+ const monthsDifference = today.getMonth() - birth.getMonth();
710
+ const daysDifference = today.getDate() - birth.getDate();
711
+ let ageInMonths = yearsDifference * 12 + monthsDifference;
712
+ if (daysDifference < 0) {
713
+ ageInMonths--;
714
+ }
715
+ return ageInMonths;
716
+ };
717
+ const calculateBirthDateFromAgeInMonths = (ageInMonths) => {
718
+ const today = new Date();
719
+ const birthDate = new Date(today.setMonth(today.getMonth() - ageInMonths));
720
+ const year = birthDate.getFullYear();
721
+ const month = (birthDate.getMonth() + 1).toString().padStart(2, '0');
722
+ const day = birthDate.getDate().toString().padStart(2, '0');
723
+ return `${year}-${month}-${day}`;
724
+ };
725
+ const generatePatientId = (patientData) => {
726
+ // Create a consistent hash based on patient data
727
+ const dataToHash = {
728
+ age: patientData.age,
729
+ gender: 'sexe' in patientData ? patientData.sexe : patientData.gender,
730
+ weight: 'poids' in patientData ? patientData.poids : patientData.weight,
731
+ height: 'taille' in patientData ? patientData.taille : patientData.height,
732
+ pregnancy: 'grossesse' in patientData ? patientData.grossesse : patientData.pregnancy,
733
+ breastfeeding: 'allaitement' in patientData ? patientData.allaitement : patientData.breastfeeding
734
+ };
735
+ return generateHash(dataToHash);
736
+ };
737
+ const convertPatient = (bcbPatient) => {
738
+ return {
739
+ firstName: '',
740
+ lastName: '',
741
+ age: bcbPatient.age,
742
+ gender: bcbPatient.sexe,
743
+ weight: bcbPatient.poids,
744
+ pregnancy: bcbPatient.grossesse === 1,
745
+ breastfeeding: bcbPatient.allaitement === 1,
746
+ ammPathologies: bcbPatient.lstPathologiesAMM.map(code => ({
747
+ type: 'AMM',
748
+ code: code,
749
+ label: ''
750
+ })),
751
+ allergies: bcbPatient.lstIdComposantAllergie.map(code => ({
752
+ code: code,
753
+ label: ''
754
+ })),
755
+ hepaticStage: bcbPatient.insuffisanceHepatique,
756
+ height: bcbPatient.taille,
757
+ weeksOfPregnancy: 0,
758
+ creatinineClearance: bcbPatient.clairanceCreatinine,
759
+ molCreatinine: bcbPatient.creatininemieMol,
760
+ mglCreatinine: bcbPatient.creatininemieMg,
761
+ gfr: 0,
762
+ medicalTeam: {
763
+ firstNameDoctor: "",
764
+ lastNameDoctor: "",
765
+ firstNameSpecialist: "",
766
+ lastNameSpecialist: "",
767
+ firstNamePharmacist: "",
768
+ lastNamePharmacist: "",
769
+ firstNameNurse: "",
770
+ lastNameNurse: ""
771
+ }
772
+ };
773
+ };
774
+ const sortEntries = async (target, entries, allergiesMapper, snomedPathologiesMapper, errors = []) => {
775
+ const patientEntry = entries.find(entry => entry?.resource?.resourceType === "Patient")?.resource;
776
+ const observationEntries = entries.filter(entry => entry?.resource?.resourceType === "Observation").map(entry => entry?.resource);
777
+ const conditionEntries = entries.filter(entry => entry?.resource?.resourceType === "Condition").map(entry => entry?.resource);
778
+ const allergyIntoleranceEntries = entries.filter(entry => entry?.resource?.resourceType === "AllergyIntolerance").map(entry => entry?.resource);
779
+ const weightObservation = observationEntries.find(entry => entry?.code?.coding?.[0]?.code === WEIGHT_LOINC_CODE);
780
+ const heightObservation = observationEntries.find(entry => entry?.code?.coding?.[0]?.code === HEIGHT_LOINC_CODE);
781
+ const pregnancyObservation = observationEntries.find(entry => entry?.code?.coding?.some(coding => coding.code === PREGNANCY_LOINC_CODE));
782
+ const breastfeedingObservation = observationEntries.find(entry => entry?.code?.coding?.some(coding => coding.code === BREASTFEEDING_LOINC_CODE));
783
+ const amenorrheaObservation = observationEntries.find(entry => entry?.code?.coding?.some(coding => coding.code === AMENORRHEA_LOINC_CODE));
784
+ const childPughObservation = observationEntries.find(entry => entry?.code?.coding?.some(coding => coding.code === CHILD_PUGH_LOINC_CODE));
785
+ // Hepatic insufficiency used to be carried by an ICD-10 Condition; still read it as a fallback
786
+ // so that bundles produced by previous versions of the library keep working.
787
+ const legacyHepaticCondition = conditionEntries.find(entry => hasAcceptedCode(entry?.code, LEGACY_HEPATIC_ICD10_CODES));
788
+ const insuffisanceHepatique = resolveHepaticStage(childPughObservation, legacyHepaticCondition);
789
+ let lstIdComposantAllergie = [];
790
+ let errorField = target === 'bcb' ? 'lstIdComposantAllergie' : 'allergies';
791
+ for (const allergyEntry of allergyIntoleranceEntries) {
792
+ if (allergiesMapper) {
793
+ const mappingResult = await allergiesMapper(allergyEntry?.code?.coding?.[0]);
794
+ if (Array.isArray(mappingResult)) {
795
+ if (mappingResult.length > 0) {
796
+ lstIdComposantAllergie.push(...mappingResult);
797
+ }
798
+ else {
799
+ errors.push({
800
+ field: errorField,
801
+ message: `No mapping found for allergy code ${allergyEntry?.code?.coding?.[0]?.code}`
802
+ });
803
+ }
804
+ }
805
+ else if (mappingResult) {
806
+ lstIdComposantAllergie.push(mappingResult);
807
+ }
808
+ else {
809
+ errors.push({
810
+ field: errorField,
811
+ message: `No mapping found for allergy code ${allergyEntry?.code?.coding?.[0]?.code}`
812
+ });
813
+ }
814
+ }
815
+ else {
816
+ errors.push({
817
+ field: errorField,
818
+ message: 'Allergy Intolerance values were found but no allergies mapper was provided, the output field is thus left empty.'
819
+ });
820
+ }
821
+ }
822
+ // The legacy hepatic Condition is already consumed as the hepatic status, keep it out of the
823
+ // 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: '' }));
825
+ 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: '' }));
827
+ let lstPathologies = [];
828
+ lstPathologies.push(...ammPathologiesEntries);
829
+ let lstCim10Pathologies = [];
830
+ lstCim10Pathologies.push(...cim10PathologiesEntries);
831
+ errorField = target === 'bcb' ? 'lstPathologiesAMM' : 'ammPathologies';
832
+ for (const pathology of snomedPathologiesEntries) {
833
+ if (snomedPathologiesMapper) {
834
+ const mappingResult = await snomedPathologiesMapper(pathology?.code?.coding?.[0]);
835
+ if (Array.isArray(mappingResult)) {
836
+ if (mappingResult.length > 0) {
837
+ lstPathologies.push(...mappingResult);
838
+ }
839
+ else {
840
+ errors.push({
841
+ field: errorField,
842
+ message: `No mapping found for snomed code ${pathology?.code?.coding?.[0]?.code}`
843
+ });
844
+ }
845
+ }
846
+ else if (mappingResult) {
847
+ lstPathologies.push(mappingResult);
848
+ }
849
+ else {
850
+ errors.push({
851
+ field: errorField,
852
+ message: `No mapping found for snomed code ${pathology?.code?.coding?.[0]?.code}`
853
+ });
854
+ }
855
+ }
856
+ else {
857
+ errors.push({
858
+ field: errorField,
859
+ message: "Snomed pathologies were found but no mapper was provided, the output field is thus left empty."
860
+ });
861
+ }
862
+ }
863
+ const clairanceCreatinineObservation = observationEntries.find(entry => hasAcceptedCode(entry?.code, ACCEPTED_LOINC_CODES_FOR_RENAL_CLEARANCE));
864
+ const gfrObservation = observationEntries.find(entry => hasAcceptedCode(entry?.code, ACCEPTED_LOINC_CODES_FOR_RENAL_GFR));
865
+ const gfrPerBsaObservation = observationEntries.find(entry => hasAcceptedCode(entry?.code, ACCEPTED_LOINC_CODES_FOR_RENAL_GFR_PER_BSA));
866
+ const creatininemieMolObservation = observationEntries.find(entry => entry?.code?.coding?.some(coding => coding.code === CREATININEMIE_MOL_LOINC_CODE));
867
+ const creatininemieMgObservation = observationEntries.find(entry => entry?.code?.coding?.some(coding => coding.code === CREATININEMIE_MG_LOINC_CODE));
868
+ const renalStatus = getPatientRenalStatus({
869
+ weightObservation,
870
+ heightObservation,
871
+ clairanceCreatinineObservation,
872
+ gfrObservation,
873
+ gfrPerBsaObservation
874
+ });
875
+ return {
876
+ patientEntry,
877
+ weightObservation,
878
+ heightObservation,
879
+ pregnancyObservation,
880
+ breastfeedingObservation,
881
+ amenorrheaObservation,
882
+ childPughObservation,
883
+ legacyHepaticCondition,
884
+ insuffisanceHepatique,
885
+ lstIdComposantAllergie,
886
+ lstCim10Pathologies,
887
+ lstPathologies,
888
+ clairanceCreatinineObservation,
889
+ gfrObservation,
890
+ gfrPerBsaObservation,
891
+ renalStatus,
892
+ creatininemieMolObservation,
893
+ creatininemieMgObservation
894
+ };
895
+ };
896
+ /**
897
+ * Resolve the BCB hepatic insufficiency stage from a bundle.
898
+ *
899
+ * The Child-Pugh `Observation` is authoritative and is inspected in the same order as
900
+ * mvn-fhir-mapper: a component holding a SNOMED class, then a direct `valueCodeableConcept`,
901
+ * then the numeric score. The legacy ICD-10 `Condition` is only used when none of those yield
902
+ * a stage.
903
+ *
904
+ * @returns 'A', 'B', 'C' or '' when the bundle carries no hepatic information
905
+ */
906
+ const resolveHepaticStage = (childPughObservation, legacyHepaticCondition) => {
907
+ if (childPughObservation) {
908
+ // First priority: a component carrying the stage as a codeable concept
909
+ for (const component of childPughObservation.component ?? []) {
910
+ const stage = snomedCodeToChildPughStage(component.valueCodeableConcept?.coding?.[0]?.code);
911
+ if (stage) {
912
+ return stage;
913
+ }
914
+ }
915
+ // Second priority: the stage as a direct valueCodeableConcept
916
+ const directStage = snomedCodeToChildPughStage(childPughObservation.valueCodeableConcept?.coding?.[0]?.code);
917
+ if (directStage) {
918
+ return directStage;
919
+ }
920
+ // Third priority: derive the stage from the numeric score
921
+ const score = childPughObservation.valueQuantity?.value;
922
+ if (score !== undefined && score !== null) {
923
+ return childPughScoreToStage(score);
924
+ }
925
+ }
926
+ const legacyCode = legacyHepaticCondition?.code?.coding?.find(coding => hepaticInsufficiencyReverseMap[coding.code ?? ''])?.code;
927
+ return legacyCode ? hepaticInsufficiencyReverseMap[legacyCode] : '';
928
+ };
929
+ /**
930
+ * Compute the advanced renal status from the renal observations of a bundle.
931
+ *
932
+ * An absolute GFR observation wins. Otherwise a GFR normalized to 1.73 m² is converted to an
933
+ * absolute value, which requires both weight and height — without them the GFR stays undefined.
934
+ */
935
+ const getPatientRenalStatus = (entries) => {
936
+ const clearance = entries.clairanceCreatinineObservation?.valueQuantity;
937
+ if (entries.gfrObservation?.valueQuantity) {
938
+ return { clearance, gfr: entries.gfrObservation.valueQuantity };
939
+ }
940
+ const normalizedGfrValue = entries.gfrPerBsaObservation?.valueQuantity?.value;
941
+ if (normalizedGfrValue !== undefined && normalizedGfrValue !== null) {
942
+ const weight = entries.weightObservation?.valueQuantity?.value ?? 0;
943
+ const height = entries.heightObservation?.valueQuantity?.value ?? 0;
944
+ if (weight > 0 && height > 0) {
945
+ const bsa = calculateBSA(height, weight);
946
+ return {
947
+ clearance,
948
+ gfr: {
949
+ value: normalizedGFRtoAbsolute(normalizedGfrValue, bsa),
950
+ unit: 'mL/min',
951
+ system: UCUM_SYSTEM_URL,
952
+ code: 'mL/min'
953
+ }
954
+ };
955
+ }
956
+ }
957
+ return { clearance, gfr: undefined };
958
+ };
959
+ const patientToFhir = (patientData) => {
960
+ if (!patientData) {
961
+ return {
962
+ result: undefined,
963
+ errors: [{
964
+ field: 'root',
965
+ message: 'Invalid patient object'
966
+ }]
967
+ };
968
+ }
969
+ let errors = [];
970
+ const bundleEntries = [];
971
+ // Generate a consistent patient ID based on patient data
972
+ const patientId = generatePatientId(patientData);
973
+ const patientReference = `Patient/${patientId}`;
974
+ const patient = {
975
+ resourceType: 'Patient',
976
+ id: patientId,
977
+ birthDate: calculateBirthDateFromAgeInMonths(patientData.age),
978
+ gender: fhirGenderMap[patientData.gender] || 'unknown'
979
+ };
980
+ const weightObservation = {
981
+ resourceType: 'Observation',
982
+ id: `${patientId}-weight`,
983
+ code: {
984
+ coding: [{
985
+ code: '29463-7',
986
+ display: 'Body Weight'
987
+ }]
988
+ },
989
+ status: 'final',
990
+ valueQuantity: {
991
+ value: patientData.weight,
992
+ unit: 'kg'
993
+ },
994
+ subject: {
995
+ reference: patientReference
996
+ }
997
+ };
998
+ const heightObservation = {
999
+ resourceType: 'Observation',
1000
+ id: `${patientId}-height`,
1001
+ code: {
1002
+ coding: [{
1003
+ code: '8302-2',
1004
+ display: 'Body Height'
1005
+ }]
1006
+ },
1007
+ status: 'final',
1008
+ valueQuantity: {
1009
+ value: patientData.height,
1010
+ unit: 'cm'
1011
+ },
1012
+ subject: {
1013
+ reference: patientReference
1014
+ }
1015
+ };
1016
+ let pregnancyObservation = undefined;
1017
+ if (patientData.pregnancy) {
1018
+ pregnancyObservation = {
1019
+ resourceType: 'Observation',
1020
+ id: `${patientId}-pregnancy`,
1021
+ code: {
1022
+ coding: [{
1023
+ code: '82810-3',
1024
+ display: 'Pregnancy Status'
1025
+ }]
1026
+ },
1027
+ status: 'final',
1028
+ subject: {
1029
+ reference: patientReference
1030
+ }
1031
+ };
1032
+ }
1033
+ if (pregnancyObservation && patientData.weeksOfPregnancy > 0) {
1034
+ pregnancyObservation.valueQuantity = {
1035
+ value: patientData.weeksOfPregnancy,
1036
+ unit: 'weeks',
1037
+ system: 'http://unitsofmeasure.org',
1038
+ code: 'wk'
1039
+ };
1040
+ }
1041
+ let breastfeedingObservation = undefined;
1042
+ if (patientData.breastfeeding) {
1043
+ breastfeedingObservation = {
1044
+ resourceType: 'Observation',
1045
+ id: `${patientId}-breastfeeding`,
1046
+ code: {
1047
+ coding: [{
1048
+ code: '63895-7',
1049
+ display: 'Breastfeeding Status'
1050
+ }]
1051
+ },
1052
+ status: 'final',
1053
+ subject: {
1054
+ reference: patientReference
1055
+ }
1056
+ };
1057
+ }
1058
+ let creatinineClearanceObservation;
1059
+ if (patientData.creatinineClearance > 0) {
1060
+ creatinineClearanceObservation = {
1061
+ resourceType: 'Observation',
1062
+ id: `${patientId}-creatinine-clearance`,
1063
+ code: {
1064
+ coding: [{
1065
+ system: 'http://loinc.org',
1066
+ code: '2164-2',
1067
+ display: 'Creatinine clearance'
1068
+ }]
1069
+ },
1070
+ status: 'final',
1071
+ valueQuantity: {
1072
+ value: patientData.creatinineClearance,
1073
+ unit: 'mL/min'
1074
+ },
1075
+ subject: {
1076
+ reference: patientReference
1077
+ }
1078
+ };
1079
+ }
1080
+ let gfrObservation;
1081
+ if (patientData.gfr > 0) {
1082
+ gfrObservation = {
1083
+ resourceType: 'Observation',
1084
+ id: `${patientId}-gfr`,
1085
+ code: {
1086
+ coding: [{
1087
+ system: LOINC_SYSTEM_URL,
1088
+ code: GFR_LOINC_CODE,
1089
+ display: 'Glomerular filtration rate'
1090
+ }]
1091
+ },
1092
+ status: 'final',
1093
+ valueQuantity: {
1094
+ value: patientData.gfr,
1095
+ unit: 'mL/min',
1096
+ system: UCUM_SYSTEM_URL,
1097
+ code: 'mL/min'
1098
+ },
1099
+ subject: {
1100
+ reference: patientReference
1101
+ }
1102
+ };
1103
+ }
1104
+ let creatinineMolObservation;
1105
+ if (patientData.molCreatinine > 0) {
1106
+ creatinineMolObservation = {
1107
+ resourceType: 'Observation',
1108
+ id: `${patientId}-creatinine-mol`,
1109
+ code: {
1110
+ coding: [{
1111
+ system: 'http://loinc.org',
1112
+ code: '14682-9',
1113
+ display: 'Creatinine [Moles/volume] in Serum or Plasma'
1114
+ }]
1115
+ },
1116
+ status: 'final',
1117
+ valueQuantity: {
1118
+ value: patientData.molCreatinine,
1119
+ unit: 'mmol/L'
1120
+ },
1121
+ subject: {
1122
+ reference: patientReference
1123
+ }
1124
+ };
1125
+ }
1126
+ let creatinineMgObservation;
1127
+ if (patientData.mglCreatinine > 0) {
1128
+ creatinineMgObservation = {
1129
+ resourceType: 'Observation',
1130
+ id: `${patientId}-creatinine-mg`,
1131
+ code: {
1132
+ coding: [{
1133
+ system: 'http://loinc.org',
1134
+ code: '2160-0',
1135
+ display: 'Creatinine [Mass/volume] in Serum or Plasma'
1136
+ }]
1137
+ },
1138
+ status: 'final',
1139
+ valueQuantity: {
1140
+ value: patientData.mglCreatinine,
1141
+ unit: 'mg/dL'
1142
+ },
1143
+ subject: {
1144
+ reference: patientReference
1145
+ }
1146
+ };
1147
+ }
1148
+ let conditions = [];
1149
+ if (patientData.ammPathologies) {
1150
+ conditions = patientData.ammPathologies.map((pathology, index) => {
1151
+ return {
1152
+ resourceType: 'Condition',
1153
+ id: `${patientId}-condition-${index}`,
1154
+ code: {
1155
+ coding: [{
1156
+ system: pathology.type === 'AMM' ? AMM_SYSTEM_URL : ICD10_SYSTEM_URL,
1157
+ code: pathology.code,
1158
+ display: pathology.label || pathology.code
1159
+ }]
1160
+ },
1161
+ clinicalStatus: {
1162
+ coding: [{
1163
+ system: 'http://terminology.hl7.org/CodeSystem/condition-clinical',
1164
+ code: 'active',
1165
+ display: 'Active'
1166
+ }]
1167
+ },
1168
+ subject: {
1169
+ reference: patientReference
1170
+ }
1171
+ };
1172
+ });
1173
+ }
1174
+ // Hepatic insufficiency is expressed as a Child-Pugh observation: the numeric score in
1175
+ // valueQuantity, the class as a SNOMED coding in a component.
1176
+ let childPughObservation;
1177
+ if (patientData.hepaticStage && childPughStageMap[patientData.hepaticStage]) {
1178
+ const stageInfo = childPughStageMap[patientData.hepaticStage];
1179
+ childPughObservation = {
1180
+ resourceType: 'Observation',
1181
+ id: `${patientId}-child-pugh-score`,
1182
+ status: 'final',
1183
+ code: {
1184
+ coding: [{
1185
+ system: LOINC_SYSTEM_URL,
1186
+ code: CHILD_PUGH_LOINC_CODE,
1187
+ display: 'Total score [Child-Pugh]'
1188
+ }],
1189
+ text: 'Score total Child-Pugh'
1190
+ },
1191
+ subject: {
1192
+ reference: patientReference
1193
+ },
1194
+ valueQuantity: {
1195
+ value: stageInfo.score,
1196
+ unit: 'score',
1197
+ system: UCUM_SYSTEM_URL,
1198
+ code: '{score}'
1199
+ },
1200
+ component: [{
1201
+ code: {
1202
+ coding: [{
1203
+ system: LOINC_SYSTEM_URL,
1204
+ code: CHILD_PUGH_LOINC_CODE,
1205
+ display: 'Child-Pugh class'
1206
+ }],
1207
+ text: 'Classe Child-Pugh'
1208
+ },
1209
+ valueCodeableConcept: {
1210
+ coding: [{
1211
+ system: SNOMED_SYSTEM_URL,
1212
+ code: stageInfo.snomedCode,
1213
+ display: stageInfo.snomedDisplay
1214
+ }],
1215
+ text: stageInfo.stageText
1216
+ }
1217
+ }]
1218
+ };
1219
+ }
1220
+ let allergies = [];
1221
+ if (patientData.allergies) {
1222
+ allergies = patientData.allergies.map((allergy, index) => {
1223
+ return {
1224
+ resourceType: 'AllergyIntolerance',
1225
+ id: `${patientId}-allergy-${index}`,
1226
+ patient: {
1227
+ reference: patientReference
1228
+ },
1229
+ code: {
1230
+ coding: [{
1231
+ system: INGREDIENTS_SYSTEM_URL,
1232
+ code: allergy.code.toString(),
1233
+ display: allergy.label?.toString() || allergy.code.toString()
1234
+ }]
1235
+ },
1236
+ clinicalStatus: {
1237
+ coding: [{
1238
+ system: 'http://terminology.hl7.org/CodeSystem/condition-clinical',
1239
+ code: 'active',
1240
+ display: 'Active'
1241
+ }]
1242
+ },
1243
+ };
1244
+ });
1245
+ }
1246
+ bundleEntries.push(...[
1247
+ { resource: patient },
1248
+ ...(weightObservation ? [{ resource: weightObservation }] : []),
1249
+ ...(heightObservation ? [{ resource: heightObservation }] : []),
1250
+ ...(pregnancyObservation ? [{ resource: pregnancyObservation }] : []),
1251
+ ...(breastfeedingObservation ? [{ resource: breastfeedingObservation }] : []),
1252
+ ...(creatinineClearanceObservation ? [{ resource: creatinineClearanceObservation }] : []),
1253
+ ...(gfrObservation ? [{ resource: gfrObservation }] : []),
1254
+ ...(creatinineMolObservation ? [{ resource: creatinineMolObservation }] : []),
1255
+ ...(creatinineMgObservation ? [{ resource: creatinineMgObservation }] : []),
1256
+ ...(childPughObservation ? [{ resource: childPughObservation }] : []),
1257
+ ...conditions.map(condition => ({ resource: condition })),
1258
+ ...allergies.map(allergy => ({ resource: allergy })),
1259
+ ]);
1260
+ const bundle = {
1261
+ resourceType: "Bundle",
1262
+ id: "patient-bundle",
1263
+ type: "collection",
1264
+ entry: bundleEntries
1265
+ };
1266
+ return {
1267
+ result: bundle,
1268
+ errors: errors
1269
+ };
1270
+ };
1271
+
1272
+ /* ================== FHIR <=> BCB ================== */
1273
+ function bcbToFhir$1(bcbPatient) {
1274
+ return patientToFhir(convertPatient(bcbPatient));
1275
+ }
1276
+ async function fhirToBcb$1(fhirBundle, allergiesMapper, snomedPathologiesMapper) {
1277
+ if (!fhirBundle || !fhirBundle.entry || fhirBundle.entry.length === 0) {
1278
+ return {
1279
+ result: undefined,
1280
+ errors: [{
1281
+ field: 'root',
1282
+ message: 'Invalid Bundle or empty entries'
1283
+ }]
1284
+ };
1285
+ }
1286
+ let errors = [];
1287
+ const { patientEntry, weightObservation, heightObservation, pregnancyObservation, breastfeedingObservation, insuffisanceHepatique, lstIdComposantAllergie, lstCim10Pathologies, lstPathologies, renalStatus, creatininemieMolObservation, creatininemieMgObservation } = await sortEntries('bcb', fhirBundle.entry, wrapMaybeAsync(allergiesMapper), wrapMaybeAsync(snomedPathologiesMapper), errors);
1288
+ const bcbPatient = {
1289
+ lstIdComposantAllergie: lstIdComposantAllergie.map(codification => Number(codification.code)),
1290
+ lstPathologiesCIM10: lstCim10Pathologies.map(codification => codification.code),
1291
+ lstPathologiesAMM: lstPathologies.map(codification => codification.code),
1292
+ age: patientEntry && patientEntry.birthDate ? calculateAgeInMonths(patientEntry.birthDate) : 0,
1293
+ poids: weightObservation && weightObservation?.valueQuantity?.value ? weightObservation?.valueQuantity?.value : 0,
1294
+ taille: heightObservation && heightObservation?.valueQuantity?.value ? heightObservation?.valueQuantity?.value : 0,
1295
+ grossesse: pregnancyObservation ? 1 : 0,
1296
+ allaitement: breastfeedingObservation ? 1 : 0,
1297
+ sexe: patientEntry?.gender ? genderMap[patientEntry.gender] || '' : '',
1298
+ clairanceCreatinine: renalStatus.clearance?.value ?? 0,
1299
+ creatininemieMol: creatininemieMolObservation && creatininemieMolObservation?.valueQuantity?.value ? creatininemieMolObservation?.valueQuantity?.value : 0,
1300
+ creatininemieMg: creatininemieMgObservation && creatininemieMgObservation?.valueQuantity?.value ? creatininemieMgObservation?.valueQuantity?.value : 0,
1301
+ insuffisanceHepatique,
1302
+ };
1303
+ return {
1304
+ result: bcbPatient,
1305
+ errors: errors
1306
+ };
1307
+ }
1308
+ /* ================== FHIR <=> CB ================== */
1309
+ function cbToFhir(cbPatient) {
1310
+ return patientToFhir(cbPatient);
1311
+ }
1312
+ async function fhirToCb(fhirBundle, allergiesMapper, snomedPathologiesMapper) {
1313
+ if (!fhirBundle?.entry?.length) {
1314
+ return {
1315
+ result: undefined,
1316
+ errors: [{
1317
+ field: 'root',
1318
+ message: 'Invalid Bundle or empty entries'
1319
+ }]
1320
+ };
1321
+ }
1322
+ let errors = [];
1323
+ const { patientEntry, weightObservation, heightObservation, pregnancyObservation, breastfeedingObservation, amenorrheaObservation, insuffisanceHepatique, lstIdComposantAllergie, lstCim10Pathologies, lstPathologies, renalStatus, creatininemieMolObservation, creatininemieMgObservation } = await sortEntries('cb', fhirBundle.entry, wrapMaybeAsync(allergiesMapper), wrapMaybeAsync(snomedPathologiesMapper), errors);
1324
+ const pathologies = [
1325
+ ...lstPathologies.map(codification => ({ type: 'AMM', code: codification.code, label: codification.label })),
1326
+ ...lstCim10Pathologies.map(codification => ({ type: 'CIM10', code: codification.code, label: codification.label }))
1327
+ ];
1328
+ const patientName = patientEntry?.name?.[0];
1329
+ return {
1330
+ result: {
1331
+ firstName: patientName?.given?.join(' ') ?? '',
1332
+ lastName: patientName?.family ?? '',
1333
+ age: patientEntry && patientEntry.birthDate ? calculateAgeInMonths(patientEntry.birthDate) : 0,
1334
+ gender: patientEntry?.gender ? genderMap[patientEntry.gender] || '' : '',
1335
+ weight: weightObservation && weightObservation?.valueQuantity?.value ? weightObservation?.valueQuantity?.value : 0,
1336
+ pregnancy: pregnancyObservation ? true : false,
1337
+ breastfeeding: breastfeedingObservation ? true : false,
1338
+ ammPathologies: pathologies,
1339
+ allergies: lstIdComposantAllergie.map(codification => ({ code: Number(codification.code), label: codification.label })),
1340
+ hepaticStage: insuffisanceHepatique,
1341
+ height: heightObservation && heightObservation?.valueQuantity?.value ? heightObservation?.valueQuantity?.value : 0,
1342
+ weeksOfPregnancy: pregnancyObservation?.valueQuantity?.value ?? amenorrheaObservation?.valueQuantity?.value ?? 0,
1343
+ creatinineClearance: renalStatus.clearance?.value ?? 0,
1344
+ molCreatinine: creatininemieMolObservation && creatininemieMolObservation?.valueQuantity?.value ? creatininemieMolObservation?.valueQuantity?.value : 0,
1345
+ mglCreatinine: creatininemieMgObservation && creatininemieMgObservation?.valueQuantity?.value ? creatininemieMgObservation?.valueQuantity?.value : 0,
1346
+ gfr: renalStatus.gfr?.value ?? 0,
1347
+ medicalTeam: {
1348
+ firstNameDoctor: "",
1349
+ lastNameDoctor: "",
1350
+ firstNameSpecialist: "",
1351
+ lastNameSpecialist: "",
1352
+ firstNamePharmacist: "",
1353
+ lastNamePharmacist: "",
1354
+ firstNameNurse: "",
1355
+ lastNameNurse: ""
1356
+ }
1357
+ },
1358
+ errors: errors
1359
+ };
1360
+ }
1361
+ // Utility to wrap a possibly sync or async function so it always returns a Promise
1362
+ function wrapMaybeAsync(fn) {
1363
+ if (!fn)
1364
+ return undefined;
1365
+ return ((...args) => Promise.resolve(fn(...args)));
1366
+ }
1367
+
1368
+ var patientMapper = /*#__PURE__*/Object.freeze({
1369
+ __proto__: null,
1370
+ bcbToFhir: bcbToFhir$1,
1371
+ cbToFhir: cbToFhir,
1372
+ fhirToBcb: fhirToBcb$1,
1373
+ fhirToCb: fhirToCb
1374
+ });
1375
+
1376
+ /**
1377
+ * Generate a hash-based ID for a medication based on its codes
1378
+ */
1379
+ const generateMedicationId = (codes, system) => {
1380
+ const sortedCodes = [...codes].sort((a, b) => a.localeCompare(b));
1381
+ const dataToHash = {
1382
+ codes: sortedCodes,
1383
+ system
1384
+ };
1385
+ return generateHash(dataToHash);
1386
+ };
1387
+ /**
1388
+ * Helper function to extract codes from a single medication
1389
+ */
1390
+ const extractCodesFromMedication = (medication, system) => {
1391
+ const codes = [];
1392
+ if (medication.code?.coding) {
1393
+ for (const coding of medication.code.coding) {
1394
+ if (coding.system === system && coding.code) {
1395
+ codes.push(coding.code);
1396
+ }
1397
+ }
1398
+ }
1399
+ return codes;
1400
+ };
1401
+ /**
1402
+ * Extracts a list of codes from a Bundle containing Medication resources based on the specified system.
1403
+ * @param bundle Bundle containing Medication resources to extract codes from
1404
+ * @param system The coding system to filter by
1405
+ * @returns List of codes that match the specified system
1406
+ */
1407
+ function extractCodesFromMedications(bundle, system) {
1408
+ const codes = [];
1409
+ if (!bundle.entry) {
1410
+ return codes;
1411
+ }
1412
+ for (const entry of bundle.entry) {
1413
+ if (entry.resource?.resourceType === 'Medication') {
1414
+ const medication = entry.resource;
1415
+ codes.push(...extractCodesFromMedication(medication, system));
1416
+ }
1417
+ }
1418
+ return codes;
1419
+ }
1420
+ /**
1421
+ * 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.
1423
+ * @param bundle Bundle containing Medication resources
1424
+ * @returns List of BCB codes
1425
+ */
1426
+ function extractBcbCodesFromMedications(bundle) {
1427
+ return extractCodesFromMedications(bundle, "https://platform.claudebernard.fr/fhir/CodeSystem/bcb-code");
1428
+ }
1429
+ /**
1430
+ * 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.
1432
+ * @param bundle Bundle containing Medication resources
1433
+ * @returns List of CIP13 codes
1434
+ */
1435
+ function extractCIP13CodesFromMedications(bundle) {
1436
+ return extractCodesFromMedications(bundle, "http://terminology.hl7.org/CodeSystem/cip13-code");
1437
+ }
1438
+ /**
1439
+ * 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.
1441
+ * @param bundle Bundle containing Medication resources
1442
+ * @returns List of CIS codes
1443
+ */
1444
+ function extractCISCodesFromMedications(bundle) {
1445
+ return extractCodesFromMedications(bundle, "http://terminology.hl7.org/CodeSystem/cis-code");
1446
+ }
1447
+ /**
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
1452
+ */
1453
+ function createMedicationsFromBcbCodes(bcbCodes) {
1454
+ if (!bcbCodes || bcbCodes.length === 0) {
1455
+ return {
1456
+ result: {
1457
+ resourceType: "Bundle",
1458
+ id: "empty-medications-bundle",
1459
+ type: "collection",
1460
+ entry: []
1461
+ },
1462
+ errors: []
1463
+ };
1464
+ }
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
+ }]
1476
+ }
1477
+ };
1478
+ });
1479
+ const bundle = {
1480
+ resourceType: "Bundle",
1481
+ id: `medications-bundle-${bundleId}`,
1482
+ type: "collection",
1483
+ entry: medications.map(medication => ({ resource: medication }))
1484
+ };
1485
+ return {
1486
+ result: bundle,
1487
+ errors: []
1488
+ };
1489
+ }
1490
+ /**
1491
+ * 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.
1493
+ * @param cip13Codes List of CIP13 codes to convert to Medication resources
1494
+ * @returns MappingResponse containing a Bundle with Medication resources
1495
+ */
1496
+ 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
+ };
1532
+ }
1533
+ /**
1534
+ * 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.
1536
+ * @param cisCodes List of CIS codes to convert to Medication resources
1537
+ * @returns MappingResponse containing a Bundle with Medication resources
1538
+ */
1539
+ function createMedicationsFromCISCodes(cisCodes) {
1540
+ if (!cisCodes || cisCodes.length === 0) {
1541
+ return {
1542
+ result: {
1543
+ resourceType: "Bundle",
1544
+ id: "empty-medications-bundle",
1545
+ type: "collection",
1546
+ entry: []
1547
+ },
1548
+ errors: []
1549
+ };
1550
+ }
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
+ };
1564
+ });
1565
+ const bundle = {
1566
+ resourceType: "Bundle",
1567
+ id: `medications-bundle-${bundleId}`,
1568
+ type: "collection",
1569
+ entry: medications.map(medication => ({ resource: medication }))
1570
+ };
1571
+ return {
1572
+ result: bundle,
1573
+ errors: []
1574
+ };
1575
+ }
1576
+
1577
+ var medicationMapper = /*#__PURE__*/Object.freeze({
1578
+ __proto__: null,
1579
+ createMedicationsFromBcbCodes: createMedicationsFromBcbCodes,
1580
+ createMedicationsFromCIP13Codes: createMedicationsFromCIP13Codes,
1581
+ createMedicationsFromCISCodes: createMedicationsFromCISCodes,
1582
+ extractBcbCodesFromMedications: extractBcbCodesFromMedications,
1583
+ extractCIP13CodesFromMedications: extractCIP13CodesFromMedications,
1584
+ extractCISCodesFromMedications: extractCISCodesFromMedications,
1585
+ extractCodesFromMedications: extractCodesFromMedications,
1586
+ generateMedicationId: generateMedicationId
1587
+ });
1588
+
1589
+ /**
1590
+ * Generate a hash-based ID for a medication request based on its key properties
1591
+ */
1592
+ const generateMedicationRequestId = (medicationCode, patientId) => {
1593
+ const dataToHash = {
1594
+ medicationCode: medicationCode || 'unknown',
1595
+ patientId: patientId || 'unknown'
1596
+ };
1597
+ return generateHash(dataToHash);
1598
+ };
1599
+ /**
1600
+ * Converts dosage instructions from MedicationRequest resources in a Bundle to BCB format.
1601
+ * Uses the dosage-mapper for dosageInstruction field conversion.
1602
+ * @param bundle Bundle containing MedicationRequest resources
1603
+ * @returns Array of BCB dosage instructions from all MedicationRequest resources
1604
+ */
1605
+ async function fhirToBcb(bundle) {
1606
+ const results = [];
1607
+ if (!bundle.entry) {
1608
+ return [{
1609
+ result: undefined,
1610
+ errors: [{ field: 'bundle.entry', message: 'Bundle has no entries' }]
1611
+ }];
1612
+ }
1613
+ for (const entry of bundle.entry) {
1614
+ if (entry.resource?.resourceType === 'MedicationRequest') {
1615
+ const medicationRequest = entry.resource;
1616
+ // Convert dosage instructions using the dosage-mapper
1617
+ if (medicationRequest.dosageInstruction && medicationRequest.dosageInstruction.length > 0) {
1618
+ try {
1619
+ const dosageResults = await fhirToBcb$2(medicationRequest.dosageInstruction);
1620
+ results.push(...dosageResults);
1621
+ }
1622
+ catch (error) {
1623
+ results.push({
1624
+ result: undefined,
1625
+ errors: [{
1626
+ field: 'dosageInstruction',
1627
+ message: `Error converting dosage instructions: ${error}`
1628
+ }]
1629
+ });
1630
+ }
1631
+ }
1632
+ }
1633
+ }
1634
+ return results;
1635
+ }
1636
+ /**
1637
+ * Creates a Bundle containing MedicationRequest resources from BCB dosage data.
1638
+ * Uses the dosage-mapper for converting BCB dosage to FHIR dosage instructions.
1639
+ * @param bcbDosages Array of BCB dosage data
1640
+ * @returns MappingResponse containing a Bundle with MedicationRequest resources
1641
+ */
1642
+ function bcbToFhir(bcbDosages) {
1643
+ if (!bcbDosages || bcbDosages.length === 0) {
1644
+ return {
1645
+ result: {
1646
+ resourceType: "Bundle",
1647
+ id: "empty-medication-requests-bundle",
1648
+ type: "collection",
1649
+ entry: []
1650
+ },
1651
+ errors: []
1652
+ };
1653
+ }
1654
+ const bundleId = generateMedicationRequestId();
1655
+ const medicationRequests = [];
1656
+ const allErrors = [];
1657
+ // Convert BCB dosages to FHIR dosages using dosage-mapper
1658
+ const dosageResponses = bcbToFhir$2(bcbDosages);
1659
+ const dosageInstructions = [];
1660
+ for (const response of dosageResponses) {
1661
+ if (response.result) {
1662
+ dosageInstructions.push(response.result);
1663
+ }
1664
+ if (response.errors && response.errors.length > 0) {
1665
+ allErrors.push(...response.errors);
1666
+ }
1667
+ }
1668
+ // Create a single MedicationRequest with all converted dosage instructions
1669
+ const medicationRequest = {
1670
+ resourceType: 'MedicationRequest',
1671
+ id: `medication-request-${bundleId}`,
1672
+ status: 'active',
1673
+ intent: 'order',
1674
+ medication: {
1675
+ concept: {
1676
+ coding: [{
1677
+ code: 'CONVERTED_FROM_BCB'
1678
+ }]
1679
+ }
1680
+ },
1681
+ subject: {
1682
+ reference: 'Patient/unknown'
1683
+ },
1684
+ dosageInstruction: dosageInstructions
1685
+ };
1686
+ medicationRequests.push(medicationRequest);
1687
+ const bundle = {
1688
+ resourceType: "Bundle",
1689
+ id: `medication-requests-bundle-${bundleId}`,
1690
+ type: "collection",
1691
+ entry: medicationRequests.map(medicationRequest => ({ resource: medicationRequest }))
1692
+ };
1693
+ return {
1694
+ result: bundle,
1695
+ errors: allErrors
1696
+ };
1697
+ }
1698
+ /**
1699
+ * Extracts medication codes from MedicationRequest resources in a Bundle.
1700
+ * @param bundle Bundle containing MedicationRequest resources
1701
+ * @returns Array of medication codes
1702
+ */
1703
+ function extractMedicationCodes(bundle) {
1704
+ const codes = [];
1705
+ if (!bundle.entry) {
1706
+ return codes;
1707
+ }
1708
+ for (const entry of bundle.entry) {
1709
+ if (entry.resource?.resourceType === 'MedicationRequest') {
1710
+ const medicationRequest = entry.resource;
1711
+ const medicationCode = medicationRequest.medication?.concept?.coding?.[0]?.code;
1712
+ if (medicationCode) {
1713
+ codes.push(medicationCode);
1714
+ }
1715
+ }
1716
+ }
1717
+ return codes;
1718
+ }
1719
+ /**
1720
+ * Extracts patient references from MedicationRequest resources in a Bundle.
1721
+ * @param bundle Bundle containing MedicationRequest resources
1722
+ * @returns Array of patient references
1723
+ */
1724
+ function extractPatientReferences(bundle) {
1725
+ const references = [];
1726
+ if (!bundle.entry) {
1727
+ return references;
1728
+ }
1729
+ for (const entry of bundle.entry) {
1730
+ if (entry.resource?.resourceType === 'MedicationRequest') {
1731
+ const medicationRequest = entry.resource;
1732
+ const patientReference = medicationRequest.subject?.reference;
1733
+ if (patientReference) {
1734
+ references.push(patientReference);
1735
+ }
1736
+ }
1737
+ }
1738
+ return references;
1739
+ }
1740
+
1741
+ var medicationRequestMapper = /*#__PURE__*/Object.freeze({
1742
+ __proto__: null,
1743
+ bcbToFhir: bcbToFhir,
1744
+ extractMedicationCodes: extractMedicationCodes,
1745
+ extractPatientReferences: extractPatientReferences,
1746
+ fhirToBcb: fhirToBcb,
1747
+ generateMedicationRequestId: generateMedicationRequestId
1748
+ });
1749
+
1750
+ export { dosageMapper, generateHash, medicationMapper, medicationRequestMapper, patientMapper, simpleHash };
1751
+ //# sourceMappingURL=index.js.map