@imranq2/fhirpatientsummary 1.0.27 → 1.0.28

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 CHANGED
@@ -220,7 +220,7 @@ var TemplateUtilities = class {
220
220
  renderOrganization(orgRef) {
221
221
  const organization = orgRef && this.resolveReference(orgRef);
222
222
  if (organization && organization.resourceType === "Organization" && organization.name) {
223
- return organization.name;
223
+ return this.renderTextAsHtml(organization.name);
224
224
  }
225
225
  return "";
226
226
  }
@@ -232,7 +232,7 @@ var TemplateUtilities = class {
232
232
  renderVaccineManufacturer(immunization) {
233
233
  const organization = immunization.manufacturer && this.resolveReference(immunization.manufacturer);
234
234
  if (organization && organization.resourceType === "Organization" && organization.name) {
235
- return organization.name;
235
+ return this.renderTextAsHtml(organization.name);
236
236
  }
237
237
  return "";
238
238
  }
@@ -271,7 +271,7 @@ var TemplateUtilities = class {
271
271
  */
272
272
  renderMedicationCode(medication) {
273
273
  if (medication && medication.code) {
274
- return this.codeableConcept(medication.code, "display");
274
+ return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
275
275
  }
276
276
  return "";
277
277
  }
@@ -282,7 +282,7 @@ var TemplateUtilities = class {
282
282
  */
283
283
  renderDoseNumber(doseNumber) {
284
284
  if (doseNumber && doseNumber.value !== void 0) {
285
- return doseNumber.value.toString();
285
+ return this.renderTextAsHtml(doseNumber.value.toString());
286
286
  }
287
287
  return "";
288
288
  }
@@ -293,7 +293,7 @@ var TemplateUtilities = class {
293
293
  */
294
294
  renderValueUnit(value) {
295
295
  if (value && value.constructor?.name === "Quantity" && value.unit) {
296
- return value.unit;
296
+ return this.renderTextAsHtml(value.unit);
297
297
  }
298
298
  return "";
299
299
  }
@@ -359,7 +359,7 @@ var TemplateUtilities = class {
359
359
  * @returns Comma-separated string of items
360
360
  */
361
361
  safeConcat(list, attr) {
362
- return this.concat(list || [], attr);
362
+ return this.renderTextAsHtml(this.concat(list || [], attr));
363
363
  }
364
364
  /**
365
365
  * Concatenates text from a list of CodeableConcept objects
@@ -376,7 +376,7 @@ var TemplateUtilities = class {
376
376
  items.push(item.text);
377
377
  }
378
378
  }
379
- return items.join(", ");
379
+ return this.renderTextAsHtml(items.join(", "));
380
380
  }
381
381
  /**
382
382
  * Concatenates reaction manifestations
@@ -397,7 +397,7 @@ var TemplateUtilities = class {
397
397
  }
398
398
  }
399
399
  }
400
- return texts.join(", ");
400
+ return this.renderTextAsHtml(texts.join(", "));
401
401
  }
402
402
  /**
403
403
  * Concatenates dose numbers
@@ -414,7 +414,7 @@ var TemplateUtilities = class {
414
414
  doseNumbers.push(item.doseNumberPositiveInt.toString());
415
415
  }
416
416
  }
417
- return doseNumbers.join(", ");
417
+ return this.renderTextAsHtml(doseNumbers.join(", "));
418
418
  }
419
419
  /**
420
420
  * Concatenates dosage routes
@@ -431,7 +431,7 @@ var TemplateUtilities = class {
431
431
  routes.push(item.route.text);
432
432
  }
433
433
  }
434
- return routes.join(", ");
434
+ return this.renderTextAsHtml(routes.join(", "));
435
435
  }
436
436
  /**
437
437
  * Returns the first item from a list of CodeableConcept objects
@@ -440,7 +440,7 @@ var TemplateUtilities = class {
440
440
  */
441
441
  firstFromCodeableConceptList(list) {
442
442
  if (list && Array.isArray(list) && list[0]) {
443
- return this.codeableConcept(list[0], "display");
443
+ return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
444
444
  }
445
445
  return "";
446
446
  }
@@ -459,7 +459,7 @@ var TemplateUtilities = class {
459
459
  texts.push(item.text);
460
460
  }
461
461
  }
462
- return texts.join(", ");
462
+ return this.renderTextAsHtml(texts.join(", "));
463
463
  }
464
464
  /**
465
465
  * Renders component codes
@@ -791,9 +791,11 @@ var TemplateUtilities = class {
791
791
  return "";
792
792
  }
793
793
  /**
794
- * Renders text as HTML, escaping special characters and replacing newlines with <br />
795
- * @param text - The text to render
796
- * @private
794
+ * Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
795
+ * This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
796
+ * and to preserve formatting. Use this in templates or UI components that need to safely display multi-line or arbitrary text.
797
+ * @param text - The text to render as HTML
798
+ * @returns The HTML-safe string with newlines converted to <br />
797
799
  */
798
800
  renderTextAsHtml(text) {
799
801
  if (!text || text.trim() === "") {
@@ -857,7 +859,7 @@ var TemplateUtilities = class {
857
859
  dateTime = DateTime.fromISO(String(dateValue));
858
860
  }
859
861
  if (!dateTime.isValid) {
860
- return String(dateValue);
862
+ return this.renderTextAsHtml(String(dateValue));
861
863
  }
862
864
  if (dateOnly) {
863
865
  dateTime = dateTime.toUTC();
@@ -873,9 +875,9 @@ var TemplateUtilities = class {
873
875
  hour12: true,
874
876
  timeZoneName: "short"
875
877
  };
876
- return dateTime.toLocaleString(formatOptions);
878
+ return this.renderTextAsHtml(dateTime.toLocaleString(formatOptions));
877
879
  } catch {
878
- return String(dateValue);
880
+ return this.renderTextAsHtml(String(dateValue));
879
881
  }
880
882
  }
881
883
  /**
@@ -1579,7 +1581,7 @@ var PatientTemplate = class _PatientTemplate {
1579
1581
  const uniqueLanguages = /* @__PURE__ */ new Set();
1580
1582
  const preferredLanguages = /* @__PURE__ */ new Set();
1581
1583
  patient.communication.forEach((comm) => {
1582
- const language = templateUtilities.codeableConcept(comm.language);
1584
+ const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
1583
1585
  if (language) {
1584
1586
  if (comm.preferred) {
1585
1587
  preferredLanguages.add(language);
@@ -1637,13 +1639,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1637
1639
  for (const columnData of rowData.section ?? []) {
1638
1640
  switch (columnData.title) {
1639
1641
  case "Allergen Name":
1640
- data["allergen"] = columnData.text?.div ?? "";
1642
+ data["allergen"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1641
1643
  break;
1642
1644
  case "Criticality":
1643
- data["criticality"] = columnData.text?.div ?? "";
1645
+ data["criticality"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1644
1646
  break;
1645
1647
  case "Recorded Date":
1646
- data["recordedDate"] = columnData.text?.div ?? "";
1648
+ data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1647
1649
  break;
1648
1650
  default:
1649
1651
  break;
@@ -1764,11 +1766,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1764
1766
  for (const allergy of allergies) {
1765
1767
  html += `
1766
1768
  <tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
1767
- <td class="Name"><span class="AllergenName">${templateUtilities.codeableConcept(allergy.code)}</span></td>
1768
- <td class="Status">${templateUtilities.codeableConcept(allergy.clinicalStatus) || "-"}</td>
1769
- <td class="Category">${templateUtilities.safeConcat(allergy.category) || "-"}</td>
1770
- <td class="Reaction">${templateUtilities.concatReactionManifestation(allergy.reaction) || "-"}</td>
1771
- <td class="OnsetDate">${templateUtilities.renderTime(allergy.onsetDateTime, timezone) || "-"}</td>
1769
+ <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
1770
+ <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
1771
+ <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
1772
+ <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
1773
+ <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
1772
1774
  <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
1773
1775
  if (includeResolved) {
1774
1776
  let endDate = "-";
@@ -1781,7 +1783,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1781
1783
  }
1782
1784
  }
1783
1785
  html += `
1784
- <td class="ResolvedDate">${endDate}</td>`;
1786
+ <td class="ResolvedDate">${templateUtilities.renderTextAsHtml(endDate)}</td>`;
1785
1787
  }
1786
1788
  html += `</tr>`;
1787
1789
  }
@@ -1804,17 +1806,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1804
1806
  * Generate HTML narrative for Medication resources using summary
1805
1807
  * @param resources - FHIR Composition resources
1806
1808
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1809
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1807
1810
  * @returns HTML string for rendering
1808
1811
  */
1809
- generateSummaryNarrative(resources, timezone) {
1812
+ generateSummaryNarrative(resources, timezone, now) {
1810
1813
  const templateUtilities = new TemplateUtilities(resources);
1811
1814
  let isSummaryCreated = false;
1815
+ const currentDate = now || /* @__PURE__ */ new Date();
1816
+ const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
1812
1817
  let html = `
1813
1818
  <div>
1814
1819
  <table>
1815
1820
  <thead>
1816
1821
  <tr>
1817
1822
  <th>Medication</th>
1823
+ <th>Status</th>
1818
1824
  <th>Sig</th>
1819
1825
  <th>Days of Supply</th>
1820
1826
  <th>Refills</th>
@@ -1828,40 +1834,48 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1828
1834
  for (const columnData of rowData.section ?? []) {
1829
1835
  switch (columnData.title) {
1830
1836
  case "Medication Name":
1831
- data["medication"] = columnData.text?.div ?? "";
1837
+ data["medication"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1832
1838
  break;
1833
1839
  case "Status":
1834
- data["status"] = columnData.text?.div ?? "";
1840
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1835
1841
  break;
1836
1842
  case "Prescriber Instruction":
1837
- data["sig-prescriber"] = columnData.text?.div ?? "";
1843
+ data["sig-prescriber"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1838
1844
  break;
1839
1845
  case "Pharmacy Instruction":
1840
- data["sig-pharmacy"] = columnData.text?.div ?? "";
1846
+ data["sig-pharmacy"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1841
1847
  break;
1842
1848
  case "Days Of Supply":
1843
- data["daysOfSupply"] = columnData.text?.div ?? "";
1849
+ data["daysOfSupply"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1844
1850
  break;
1845
1851
  case "Refills Remaining":
1846
- data["refills"] = columnData.text?.div ?? "";
1852
+ data["refills"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1847
1853
  break;
1848
1854
  case "Authored On Date":
1849
- data["startDate"] = columnData.text?.div ?? "";
1855
+ data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1850
1856
  break;
1851
1857
  default:
1852
1858
  break;
1853
1859
  }
1854
1860
  }
1855
- if (data["status"] === "active") {
1861
+ let startDateObj;
1862
+ if (data["startDate"]) {
1863
+ startDateObj = new Date(data["startDate"]);
1864
+ if (isNaN(startDateObj.getTime())) {
1865
+ startDateObj = void 0;
1866
+ }
1867
+ }
1868
+ if (data["status"] === "active" || startDateObj && startDateObj >= twelveMonthsAgo) {
1856
1869
  isSummaryCreated = true;
1857
1870
  html += `
1858
- <tr>
1859
- <td>${data["medication"]}</td>
1860
- <td>${data["sig-prescriber"] || data["sig-pharmacy"]}</td>
1861
- <td>${data["daysOfSupply"]}</td>
1862
- <td>${data["refills"]}</td>
1863
- <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
1864
- </tr>`;
1871
+ <tr>
1872
+ <td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
1873
+ <td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
1874
+ <td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
1875
+ <td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
1876
+ <td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
1877
+ <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
1878
+ </tr>`;
1865
1879
  }
1866
1880
  }
1867
1881
  }
@@ -2083,13 +2097,13 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2083
2097
  for (const columnData of rowData.section ?? []) {
2084
2098
  switch (columnData.title) {
2085
2099
  case "Immunization Name":
2086
- data["immunization"] = columnData.text?.div ?? "";
2100
+ data["immunization"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2087
2101
  break;
2088
2102
  case "Status":
2089
- data["status"] = columnData.text?.div ?? "";
2103
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2090
2104
  break;
2091
2105
  case "occurrenceDateTime":
2092
- data["occurrenceDateTime"] = columnData.text?.div ?? "";
2106
+ data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2093
2107
  break;
2094
2108
  default:
2095
2109
  break;
@@ -2140,7 +2154,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2140
2154
  const imm = resourceItem;
2141
2155
  html += `
2142
2156
  <tr id="${templateUtilities.narrativeLinkId(imm)}">
2143
- <td>${templateUtilities.codeableConcept(imm.vaccineCode)}</td>
2157
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
2144
2158
  <td>${imm.status || ""}</td>
2145
2159
  <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
2146
2160
  <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
@@ -2196,7 +2210,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
2196
2210
  <tbody>`;
2197
2211
  const addedConditionCodes = /* @__PURE__ */ new Set();
2198
2212
  for (const cond of activeConditions) {
2199
- const conditionCode = templateUtilities.codeableConcept(cond.code);
2213
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2200
2214
  if (!addedConditionCodes.has(conditionCode)) {
2201
2215
  addedConditionCodes.add(conditionCode);
2202
2216
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -2256,7 +2270,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2256
2270
  const vitalData = {};
2257
2271
  for (const component of columnData.section?.[0]?.section ?? []) {
2258
2272
  if (component.title) {
2259
- vitalData[component.title] = component.text?.div ?? "";
2273
+ vitalData[component.title] = templateUtilities.renderTextAsHtml(component.text?.div ?? "");
2260
2274
  }
2261
2275
  }
2262
2276
  const vitalValue = templateUtilities.extractObservationSummaryValue(
@@ -2268,7 +2282,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2268
2282
  data[dataKey] = vitalValue;
2269
2283
  }
2270
2284
  }
2271
- data[columnTitle] = columnData.text?.div ?? "";
2285
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2272
2286
  }
2273
2287
  }
2274
2288
  isSummaryCreated = true;
@@ -2318,7 +2332,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2318
2332
  for (const obs of observations) {
2319
2333
  html += `
2320
2334
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2321
- <td>${templateUtilities.codeableConcept(obs.code, "display")}</td>
2335
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
2322
2336
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2323
2337
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2324
2338
  <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
@@ -2372,10 +2386,10 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2372
2386
  for (const dus of deviceStatements) {
2373
2387
  html += `
2374
2388
  <tr id="${templateUtilities.narrativeLinkId(dus)}">
2375
- <td>${templateUtilities.renderDevice(dus.device)}</td>
2376
- <td>${dus.status || ""}</td>
2389
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
2390
+ <td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
2377
2391
  <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
2378
- <td>${templateUtilities.renderRecorded(dus.recordedOn, timezone)}</td>
2392
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderRecorded(dus.recordedOn, timezone))}</td>
2379
2393
  </tr>`;
2380
2394
  }
2381
2395
  html += `
@@ -2511,149 +2525,150 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2511
2525
  * Helper function to extract observation field data
2512
2526
  * @param column - Column data from the summary
2513
2527
  * @param targetData - Record to populate with extracted data
2528
+ * @param templateUtilities - Instance of TemplateUtilities for utility functions
2514
2529
  */
2515
- extractSummaryObservationFields(column, targetData) {
2530
+ extractSummaryObservationFields(column, targetData, templateUtilities) {
2516
2531
  switch (column.title) {
2517
2532
  case "Labs Name":
2518
- targetData["code"] = column.text?.div ?? "";
2533
+ targetData["code"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2519
2534
  break;
2520
2535
  case "effectiveDateTime":
2521
- targetData["effectiveDateTime"] = column.text?.div ?? "";
2536
+ targetData["effectiveDateTime"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2522
2537
  break;
2523
2538
  case "effectivePeriod.start":
2524
- targetData["effectivePeriodStart"] = column.text?.div ?? "";
2539
+ targetData["effectivePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2525
2540
  break;
2526
2541
  case "effectivePeriod.end":
2527
- targetData["effectivePeriodEnd"] = column.text?.div ?? "";
2542
+ targetData["effectivePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2528
2543
  break;
2529
2544
  // valueQuantity
2530
2545
  case "valueQuantity.value":
2531
- targetData["value"] = column.text?.div ?? "";
2546
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2532
2547
  targetData["valueType"] = "valueQuantity";
2533
2548
  break;
2534
2549
  case "valueQuantity.unit":
2535
- targetData["unit"] = column.text?.div ?? "";
2550
+ targetData["unit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2536
2551
  break;
2537
2552
  // valueCodeableConcept
2538
2553
  case "valueCodeableConcept.text":
2539
- targetData["value"] = column.text?.div ?? "";
2554
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2540
2555
  targetData["valueType"] = "valueCodeableConcept";
2541
2556
  break;
2542
2557
  case "valueCodeableConcept.coding.display":
2543
2558
  if (!targetData["value"]) {
2544
- targetData["value"] = column.text?.div ?? "";
2559
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2545
2560
  targetData["valueType"] = "valueCodeableConcept";
2546
2561
  }
2547
2562
  break;
2548
2563
  // valueString
2549
2564
  case "valueString":
2550
- targetData["value"] = column.text?.div ?? "";
2565
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2551
2566
  targetData["valueType"] = "valueString";
2552
2567
  break;
2553
2568
  // valueBoolean
2554
2569
  case "valueBoolean":
2555
- targetData["value"] = column.text?.div ?? "";
2570
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2556
2571
  targetData["valueType"] = "valueBoolean";
2557
2572
  break;
2558
2573
  // valueInteger
2559
2574
  case "valueInteger":
2560
- targetData["value"] = column.text?.div ?? "";
2575
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2561
2576
  targetData["valueType"] = "valueInteger";
2562
2577
  break;
2563
2578
  // valueDateTime
2564
2579
  case "valueDateTime":
2565
- targetData["value"] = column.text?.div ?? "";
2580
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2566
2581
  targetData["valueType"] = "valueDateTime";
2567
2582
  break;
2568
2583
  // valuePeriod
2569
2584
  case "valuePeriod.start":
2570
- targetData["valuePeriodStart"] = column.text?.div ?? "";
2585
+ targetData["valuePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2571
2586
  targetData["valueType"] = "valuePeriod";
2572
2587
  break;
2573
2588
  case "valuePeriod.end":
2574
- targetData["valuePeriodEnd"] = column.text?.div ?? "";
2589
+ targetData["valuePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2575
2590
  targetData["valueType"] = "valuePeriod";
2576
2591
  break;
2577
2592
  // valueTime
2578
2593
  case "valueTime":
2579
- targetData["value"] = column.text?.div ?? "";
2594
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2580
2595
  targetData["valueType"] = "valueTime";
2581
2596
  break;
2582
2597
  // valueSampledData
2583
2598
  case "valueSampledData.origin.value":
2584
- targetData["sampledDataOriginValue"] = column.text?.div ?? "";
2599
+ targetData["sampledDataOriginValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2585
2600
  targetData["valueType"] = "valueSampledData";
2586
2601
  break;
2587
2602
  case "valueSampledData.origin.unit":
2588
- targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
2603
+ targetData["sampledDataOriginUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2589
2604
  break;
2590
2605
  case "valueSampledData.period":
2591
- targetData["sampledDataPeriod"] = column.text?.div ?? "";
2606
+ targetData["sampledDataPeriod"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2592
2607
  break;
2593
2608
  case "valueSampledData.factor":
2594
- targetData["sampledDataFactor"] = column.text?.div ?? "";
2609
+ targetData["sampledDataFactor"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2595
2610
  break;
2596
2611
  case "valueSampledData.lowerLimit":
2597
- targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
2612
+ targetData["sampledDataLowerLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2598
2613
  break;
2599
2614
  case "valueSampledData.upperLimit":
2600
- targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
2615
+ targetData["sampledDataUpperLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2601
2616
  break;
2602
2617
  case "valueSampledData.data":
2603
- targetData["sampledDataData"] = column.text?.div ?? "";
2618
+ targetData["sampledDataData"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2604
2619
  break;
2605
2620
  // valueRange
2606
2621
  case "valueRange.low.value":
2607
- targetData["valueRangeLowValue"] = column.text?.div ?? "";
2622
+ targetData["valueRangeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2608
2623
  targetData["valueType"] = "valueRange";
2609
2624
  break;
2610
2625
  case "valueRange.low.unit":
2611
- targetData["valueRangeLowUnit"] = column.text?.div ?? "";
2626
+ targetData["valueRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2612
2627
  break;
2613
2628
  case "valueRange.high.value":
2614
- targetData["valueRangeHighValue"] = column.text?.div ?? "";
2629
+ targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2615
2630
  break;
2616
2631
  case "valueRange.high.unit":
2617
- targetData["valueRangeHighUnit"] = column.text?.div ?? "";
2632
+ targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2618
2633
  break;
2619
2634
  // valueRatio
2620
2635
  case "valueRatio.numerator.value":
2621
- targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
2636
+ targetData["valueRatioNumeratorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2622
2637
  targetData["valueType"] = "valueRatio";
2623
2638
  break;
2624
2639
  case "valueRatio.numerator.unit":
2625
- targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
2640
+ targetData["valueRatioNumeratorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2626
2641
  break;
2627
2642
  case "valueRatio.denominator.value":
2628
- targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
2643
+ targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2629
2644
  break;
2630
2645
  case "valueRatio.denominator.unit":
2631
- targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
2646
+ targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2632
2647
  break;
2633
2648
  // referenceRange
2634
2649
  case "referenceRange.low.value":
2635
- targetData["referenceRangeLow"] = column.text?.div ?? "";
2650
+ targetData["referenceRangeLow"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2636
2651
  break;
2637
2652
  case "referenceRange.low.unit":
2638
- targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
2653
+ targetData["referenceRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2639
2654
  break;
2640
2655
  case "referenceRange.high.value":
2641
- targetData["referenceRangeHigh"] = column.text?.div ?? "";
2656
+ targetData["referenceRangeHigh"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2642
2657
  break;
2643
2658
  case "referenceRange.high.unit":
2644
- targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
2659
+ targetData["referenceRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2645
2660
  break;
2646
2661
  case "referenceRange.age.low.value":
2647
- targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
2662
+ targetData["referenceRangeAgeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2648
2663
  break;
2649
2664
  case "referenceRange.age.low.unit":
2650
- targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
2665
+ targetData["referenceRangeAgeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2651
2666
  break;
2652
2667
  case "referenceRange.age.high.value":
2653
- targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
2668
+ targetData["referenceRangeAgeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2654
2669
  break;
2655
2670
  case "referenceRange.age.high.unit":
2656
- targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
2671
+ targetData["referenceRangeAgeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2657
2672
  break;
2658
2673
  default:
2659
2674
  break;
@@ -2706,28 +2721,28 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2706
2721
  for (const componentSection of columnData.section) {
2707
2722
  const componentData = {};
2708
2723
  for (const nestedColumn of componentSection.section ?? []) {
2709
- this.extractSummaryObservationFields(nestedColumn, componentData);
2724
+ this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2710
2725
  }
2711
2726
  if (Object.keys(componentData).length > 0) {
2712
2727
  components.push(componentData);
2713
2728
  }
2714
2729
  }
2715
2730
  } else {
2716
- this.extractSummaryObservationFields(columnData, data);
2731
+ this.extractSummaryObservationFields(columnData, data, templateUtilities);
2717
2732
  }
2718
2733
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2719
2734
  switch (columnData.title) {
2720
2735
  case "Diagnostic Report Name":
2721
- data["report"] = columnData.text?.div ?? "";
2736
+ data["report"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2722
2737
  break;
2723
2738
  case "Performer":
2724
- data["performer"] = columnData.text?.div ?? "";
2739
+ data["performer"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2725
2740
  break;
2726
2741
  case "Issued Date":
2727
- data["issued"] = columnData.text?.div ?? "";
2742
+ data["issued"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2728
2743
  break;
2729
2744
  case "Status":
2730
- data["status"] = columnData.text?.div ?? "";
2745
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2731
2746
  break;
2732
2747
  default:
2733
2748
  break;
@@ -2752,8 +2767,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2752
2767
  observationhtml += `
2753
2768
  <tr>
2754
2769
  <td>${componentCode}</td>
2755
- <td>${component["formattedValue"] ?? "-"}</td>
2756
- <td>${component["referenceRange"]?.trim() ?? "-"}</td>
2770
+ <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
2771
+ <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
2757
2772
  <td>${date ?? "-"}</td>
2758
2773
  </tr>`;
2759
2774
  }
@@ -2766,8 +2781,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2766
2781
  observationhtml += `
2767
2782
  <tr>
2768
2783
  <td>${data["code"] ?? "-"}</td>
2769
- <td>${data["formattedValue"] ?? "-"}</td>
2770
- <td>${data["referenceRange"]?.trim() ?? "-"}</td>
2784
+ <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
2785
+ <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
2771
2786
  <td>${date ?? "-"}</td>
2772
2787
  </tr>`;
2773
2788
  }
@@ -2927,7 +2942,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2927
2942
  <tbody>`;
2928
2943
  const observationAdded = /* @__PURE__ */ new Set();
2929
2944
  for (const obs of observations) {
2930
- const obsCode = templateUtilities.codeableConcept(obs.code);
2945
+ const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
2931
2946
  if (!observationAdded.has(obsCode)) {
2932
2947
  observationAdded.add(obsCode);
2933
2948
  html += `
@@ -2966,7 +2981,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2966
2981
  <tbody>`;
2967
2982
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2968
2983
  for (const report of reports) {
2969
- const reportName = templateUtilities.codeableConcept(report.code);
2984
+ const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
2970
2985
  if (!diagnosticReportAdded.has(reportName)) {
2971
2986
  diagnosticReportAdded.add(reportName);
2972
2987
  let resultCount = "";
@@ -3080,7 +3095,7 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3080
3095
  const proc = resourceItem;
3081
3096
  html += `
3082
3097
  <tr id="${templateUtilities.narrativeLinkId(proc)}">
3083
- <td>${templateUtilities.codeableConcept(proc.code, "display")}</td>
3098
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
3084
3099
  <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
3085
3100
  <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
3086
3101
  </tr>`;
@@ -3132,7 +3147,7 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3132
3147
  for (const obs of observations) {
3133
3148
  html += `
3134
3149
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
3135
- <td>${templateUtilities.codeableConcept(obs.code)}</td>
3150
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
3136
3151
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3137
3152
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
3138
3153
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
@@ -3177,7 +3192,7 @@ var PastHistoryOfIllnessTemplate = class {
3177
3192
  <tbody>`;
3178
3193
  const addedConditionCodes = /* @__PURE__ */ new Set();
3179
3194
  for (const cond of resolvedConditions) {
3180
- const conditionCode = templateUtilities.codeableConcept(cond.code);
3195
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3181
3196
  if (!addedConditionCodes.has(conditionCode)) {
3182
3197
  addedConditionCodes.add(conditionCode);
3183
3198
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -3263,7 +3278,7 @@ var PlanOfCareTemplate = class {
3263
3278
  const data = {};
3264
3279
  for (const columnData of rowData.section ?? []) {
3265
3280
  if (columnData.title) {
3266
- data[columnData.title] = columnData.text?.div ?? "";
3281
+ data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3267
3282
  }
3268
3283
  }
3269
3284
  if (data["status"] !== "active") {
@@ -3339,7 +3354,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3339
3354
  <tbody>`;
3340
3355
  const addedConditionCodes = /* @__PURE__ */ new Set();
3341
3356
  for (const cond of activeConditions) {
3342
- const conditionCode = templateUtilities.codeableConcept(cond.code);
3357
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3343
3358
  if (!addedConditionCodes.has(conditionCode)) {
3344
3359
  addedConditionCodes.add(conditionCode);
3345
3360
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -3384,7 +3399,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3384
3399
  if (impression.finding && impression.finding.length > 0) {
3385
3400
  findingsHtml = "<ul>";
3386
3401
  for (const finding of impression.finding) {
3387
- const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConcept(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3402
+ const findingText = finding.itemCodeableConcept ? templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(finding.itemCodeableConcept)) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3388
3403
  const cause = finding.basis || "";
3389
3404
  findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
3390
3405
  }
@@ -3445,9 +3460,9 @@ var PregnancyTemplate = class _PregnancyTemplate {
3445
3460
  const obs = resource;
3446
3461
  html += `
3447
3462
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
3448
- <td>${templateUtilities.extractPregnancyStatus(obs)}</td>
3463
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
3449
3464
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3450
- <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3465
+ <td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
3451
3466
  </tr>`;
3452
3467
  }
3453
3468
  html += `
@@ -3497,7 +3512,7 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3497
3512
  const consent = resourceItem;
3498
3513
  html += `
3499
3514
  <tr id="${templateUtilities.narrativeLinkId(consent)}">
3500
- <td>${templateUtilities.codeableConcept(consent.scope, "display")}</td>
3515
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
3501
3516
  <td>${consent.status || ""}</td>
3502
3517
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
3503
3518
  <td>${consent.dateTime || ""}</td>
@@ -3518,16 +3533,18 @@ var TypeScriptTemplateMapper = class {
3518
3533
  * @param resources - FHIR resources
3519
3534
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3520
3535
  * @param useSectionSummary - Whether to use the section summary for narrative generation
3536
+ * @param now - Optional current date to use for generating relative dates in the narrative
3521
3537
  * @returns HTML string for rendering
3522
3538
  */
3523
- static generateNarrative(section, resources, timezone, useSectionSummary = false) {
3539
+ static generateNarrative(section, resources, timezone, useSectionSummary = false, now) {
3524
3540
  const templateClass = this.sectionToTemplate[section];
3525
3541
  if (!templateClass) {
3526
3542
  throw new Error(`No template found for section: ${section}`);
3527
3543
  }
3528
3544
  return useSectionSummary ? templateClass.generateSummaryNarrative(
3529
3545
  resources,
3530
- timezone
3546
+ timezone,
3547
+ now
3531
3548
  ) : templateClass.generateNarrative(resources, timezone);
3532
3549
  }
3533
3550
  };
@@ -3585,14 +3602,15 @@ var NarrativeGenerator = class {
3585
3602
  * @param resources - Array of domain resources
3586
3603
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3587
3604
  * @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
3605
+ * @param now - Optional date parameter
3588
3606
  * @returns Generated HTML content or undefined if no resources
3589
3607
  */
3590
- static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false) {
3608
+ static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false, now) {
3591
3609
  if (!resources || resources.length === 0) {
3592
3610
  return void 0;
3593
3611
  }
3594
3612
  try {
3595
- const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary);
3613
+ const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary, now);
3596
3614
  if (!content) {
3597
3615
  return void 0;
3598
3616
  }
@@ -3614,8 +3632,8 @@ var NarrativeGenerator = class {
3614
3632
  const options = aggressive ? AGGRESSIVE_MINIFY_OPTIONS : DEFAULT_MINIFY_OPTIONS;
3615
3633
  return await htmlMinify(html, options);
3616
3634
  } catch (error) {
3617
- console.warn("HTML minification failed", error);
3618
- return html;
3635
+ console.warn("HTML minification failed", error, html);
3636
+ return `${error instanceof Error ? error.message : String(error)}`;
3619
3637
  }
3620
3638
  }
3621
3639
  /**
@@ -3644,10 +3662,11 @@ var NarrativeGenerator = class {
3644
3662
  * @param timezone - Optional timezone to use for date formatting
3645
3663
  * @param minify - Whether to minify the HTML content (default: true)
3646
3664
  * @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
3665
+ * @param now - Optional date parameter
3647
3666
  * @returns Promise that resolves to a FHIR Narrative object or undefined if no resources
3648
3667
  */
3649
- static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false) {
3650
- const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary);
3668
+ static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false, now) {
3669
+ const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary, now);
3651
3670
  if (!content) {
3652
3671
  return void 0;
3653
3672
  }
@@ -3824,8 +3843,9 @@ var ComprehensiveIPSCompositionBuilder = class {
3824
3843
  * @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
3825
3844
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3826
3845
  * @param patientId - Optional patient ID to use as primary patient for composition reference
3846
+ * @param now - Optional current date to use for composition date (defaults to new Date())
3827
3847
  */
3828
- async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId) {
3848
+ async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
3829
3849
  if (baseUrl.endsWith("/")) {
3830
3850
  baseUrl = baseUrl.slice(0, -1);
3831
3851
  }
@@ -3852,20 +3872,22 @@ var ComprehensiveIPSCompositionBuilder = class {
3852
3872
  // Assuming patient is also a practitioner for simplicity
3853
3873
  display: authorOrganizationName
3854
3874
  }],
3855
- date: (/* @__PURE__ */ new Date()).toISOString(),
3875
+ date: (now || /* @__PURE__ */ new Date()).toISOString(),
3856
3876
  title: "International Patient Summary",
3857
3877
  section: this.sections,
3858
3878
  text: await NarrativeGenerator.generateNarrativeAsync(
3859
3879
  "Patient" /* PATIENT */,
3860
3880
  this.patients,
3861
3881
  timezone,
3862
- true
3882
+ true,
3883
+ false,
3884
+ now
3863
3885
  )
3864
3886
  };
3865
3887
  const bundle = {
3866
3888
  resourceType: "Bundle",
3867
3889
  type: "document",
3868
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3890
+ timestamp: (now || /* @__PURE__ */ new Date()).toISOString(),
3869
3891
  identifier: {
3870
3892
  "system": "urn:ietf:rfc:3986",
3871
3893
  "value": "urn:uuid:4dcfd353-49fd-4ab0-b521-c8d57ced74d6"