@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.cjs CHANGED
@@ -248,7 +248,7 @@ var TemplateUtilities = class {
248
248
  renderOrganization(orgRef) {
249
249
  const organization = orgRef && this.resolveReference(orgRef);
250
250
  if (organization && organization.resourceType === "Organization" && organization.name) {
251
- return organization.name;
251
+ return this.renderTextAsHtml(organization.name);
252
252
  }
253
253
  return "";
254
254
  }
@@ -260,7 +260,7 @@ var TemplateUtilities = class {
260
260
  renderVaccineManufacturer(immunization) {
261
261
  const organization = immunization.manufacturer && this.resolveReference(immunization.manufacturer);
262
262
  if (organization && organization.resourceType === "Organization" && organization.name) {
263
- return organization.name;
263
+ return this.renderTextAsHtml(organization.name);
264
264
  }
265
265
  return "";
266
266
  }
@@ -299,7 +299,7 @@ var TemplateUtilities = class {
299
299
  */
300
300
  renderMedicationCode(medication) {
301
301
  if (medication && medication.code) {
302
- return this.codeableConcept(medication.code, "display");
302
+ return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
303
303
  }
304
304
  return "";
305
305
  }
@@ -310,7 +310,7 @@ var TemplateUtilities = class {
310
310
  */
311
311
  renderDoseNumber(doseNumber) {
312
312
  if (doseNumber && doseNumber.value !== void 0) {
313
- return doseNumber.value.toString();
313
+ return this.renderTextAsHtml(doseNumber.value.toString());
314
314
  }
315
315
  return "";
316
316
  }
@@ -321,7 +321,7 @@ var TemplateUtilities = class {
321
321
  */
322
322
  renderValueUnit(value) {
323
323
  if (value && value.constructor?.name === "Quantity" && value.unit) {
324
- return value.unit;
324
+ return this.renderTextAsHtml(value.unit);
325
325
  }
326
326
  return "";
327
327
  }
@@ -387,7 +387,7 @@ var TemplateUtilities = class {
387
387
  * @returns Comma-separated string of items
388
388
  */
389
389
  safeConcat(list, attr) {
390
- return this.concat(list || [], attr);
390
+ return this.renderTextAsHtml(this.concat(list || [], attr));
391
391
  }
392
392
  /**
393
393
  * Concatenates text from a list of CodeableConcept objects
@@ -404,7 +404,7 @@ var TemplateUtilities = class {
404
404
  items.push(item.text);
405
405
  }
406
406
  }
407
- return items.join(", ");
407
+ return this.renderTextAsHtml(items.join(", "));
408
408
  }
409
409
  /**
410
410
  * Concatenates reaction manifestations
@@ -425,7 +425,7 @@ var TemplateUtilities = class {
425
425
  }
426
426
  }
427
427
  }
428
- return texts.join(", ");
428
+ return this.renderTextAsHtml(texts.join(", "));
429
429
  }
430
430
  /**
431
431
  * Concatenates dose numbers
@@ -442,7 +442,7 @@ var TemplateUtilities = class {
442
442
  doseNumbers.push(item.doseNumberPositiveInt.toString());
443
443
  }
444
444
  }
445
- return doseNumbers.join(", ");
445
+ return this.renderTextAsHtml(doseNumbers.join(", "));
446
446
  }
447
447
  /**
448
448
  * Concatenates dosage routes
@@ -459,7 +459,7 @@ var TemplateUtilities = class {
459
459
  routes.push(item.route.text);
460
460
  }
461
461
  }
462
- return routes.join(", ");
462
+ return this.renderTextAsHtml(routes.join(", "));
463
463
  }
464
464
  /**
465
465
  * Returns the first item from a list of CodeableConcept objects
@@ -468,7 +468,7 @@ var TemplateUtilities = class {
468
468
  */
469
469
  firstFromCodeableConceptList(list) {
470
470
  if (list && Array.isArray(list) && list[0]) {
471
- return this.codeableConcept(list[0], "display");
471
+ return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
472
472
  }
473
473
  return "";
474
474
  }
@@ -487,7 +487,7 @@ var TemplateUtilities = class {
487
487
  texts.push(item.text);
488
488
  }
489
489
  }
490
- return texts.join(", ");
490
+ return this.renderTextAsHtml(texts.join(", "));
491
491
  }
492
492
  /**
493
493
  * Renders component codes
@@ -819,9 +819,11 @@ var TemplateUtilities = class {
819
819
  return "";
820
820
  }
821
821
  /**
822
- * Renders text as HTML, escaping special characters and replacing newlines with <br />
823
- * @param text - The text to render
824
- * @private
822
+ * Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
823
+ * This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
824
+ * and to preserve formatting. Use this in templates or UI components that need to safely display multi-line or arbitrary text.
825
+ * @param text - The text to render as HTML
826
+ * @returns The HTML-safe string with newlines converted to <br />
825
827
  */
826
828
  renderTextAsHtml(text) {
827
829
  if (!text || text.trim() === "") {
@@ -885,7 +887,7 @@ var TemplateUtilities = class {
885
887
  dateTime = import_luxon.DateTime.fromISO(String(dateValue));
886
888
  }
887
889
  if (!dateTime.isValid) {
888
- return String(dateValue);
890
+ return this.renderTextAsHtml(String(dateValue));
889
891
  }
890
892
  if (dateOnly) {
891
893
  dateTime = dateTime.toUTC();
@@ -901,9 +903,9 @@ var TemplateUtilities = class {
901
903
  hour12: true,
902
904
  timeZoneName: "short"
903
905
  };
904
- return dateTime.toLocaleString(formatOptions);
906
+ return this.renderTextAsHtml(dateTime.toLocaleString(formatOptions));
905
907
  } catch {
906
- return String(dateValue);
908
+ return this.renderTextAsHtml(String(dateValue));
907
909
  }
908
910
  }
909
911
  /**
@@ -1607,7 +1609,7 @@ var PatientTemplate = class _PatientTemplate {
1607
1609
  const uniqueLanguages = /* @__PURE__ */ new Set();
1608
1610
  const preferredLanguages = /* @__PURE__ */ new Set();
1609
1611
  patient.communication.forEach((comm) => {
1610
- const language = templateUtilities.codeableConcept(comm.language);
1612
+ const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
1611
1613
  if (language) {
1612
1614
  if (comm.preferred) {
1613
1615
  preferredLanguages.add(language);
@@ -1665,13 +1667,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1665
1667
  for (const columnData of rowData.section ?? []) {
1666
1668
  switch (columnData.title) {
1667
1669
  case "Allergen Name":
1668
- data["allergen"] = columnData.text?.div ?? "";
1670
+ data["allergen"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1669
1671
  break;
1670
1672
  case "Criticality":
1671
- data["criticality"] = columnData.text?.div ?? "";
1673
+ data["criticality"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1672
1674
  break;
1673
1675
  case "Recorded Date":
1674
- data["recordedDate"] = columnData.text?.div ?? "";
1676
+ data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1675
1677
  break;
1676
1678
  default:
1677
1679
  break;
@@ -1792,11 +1794,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1792
1794
  for (const allergy of allergies) {
1793
1795
  html += `
1794
1796
  <tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
1795
- <td class="Name"><span class="AllergenName">${templateUtilities.codeableConcept(allergy.code)}</span></td>
1796
- <td class="Status">${templateUtilities.codeableConcept(allergy.clinicalStatus) || "-"}</td>
1797
- <td class="Category">${templateUtilities.safeConcat(allergy.category) || "-"}</td>
1798
- <td class="Reaction">${templateUtilities.concatReactionManifestation(allergy.reaction) || "-"}</td>
1799
- <td class="OnsetDate">${templateUtilities.renderTime(allergy.onsetDateTime, timezone) || "-"}</td>
1797
+ <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
1798
+ <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
1799
+ <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
1800
+ <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
1801
+ <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
1800
1802
  <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
1801
1803
  if (includeResolved) {
1802
1804
  let endDate = "-";
@@ -1809,7 +1811,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1809
1811
  }
1810
1812
  }
1811
1813
  html += `
1812
- <td class="ResolvedDate">${endDate}</td>`;
1814
+ <td class="ResolvedDate">${templateUtilities.renderTextAsHtml(endDate)}</td>`;
1813
1815
  }
1814
1816
  html += `</tr>`;
1815
1817
  }
@@ -1832,17 +1834,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1832
1834
  * Generate HTML narrative for Medication resources using summary
1833
1835
  * @param resources - FHIR Composition resources
1834
1836
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1837
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1835
1838
  * @returns HTML string for rendering
1836
1839
  */
1837
- generateSummaryNarrative(resources, timezone) {
1840
+ generateSummaryNarrative(resources, timezone, now) {
1838
1841
  const templateUtilities = new TemplateUtilities(resources);
1839
1842
  let isSummaryCreated = false;
1843
+ const currentDate = now || /* @__PURE__ */ new Date();
1844
+ const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
1840
1845
  let html = `
1841
1846
  <div>
1842
1847
  <table>
1843
1848
  <thead>
1844
1849
  <tr>
1845
1850
  <th>Medication</th>
1851
+ <th>Status</th>
1846
1852
  <th>Sig</th>
1847
1853
  <th>Days of Supply</th>
1848
1854
  <th>Refills</th>
@@ -1856,40 +1862,48 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1856
1862
  for (const columnData of rowData.section ?? []) {
1857
1863
  switch (columnData.title) {
1858
1864
  case "Medication Name":
1859
- data["medication"] = columnData.text?.div ?? "";
1865
+ data["medication"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1860
1866
  break;
1861
1867
  case "Status":
1862
- data["status"] = columnData.text?.div ?? "";
1868
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1863
1869
  break;
1864
1870
  case "Prescriber Instruction":
1865
- data["sig-prescriber"] = columnData.text?.div ?? "";
1871
+ data["sig-prescriber"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1866
1872
  break;
1867
1873
  case "Pharmacy Instruction":
1868
- data["sig-pharmacy"] = columnData.text?.div ?? "";
1874
+ data["sig-pharmacy"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1869
1875
  break;
1870
1876
  case "Days Of Supply":
1871
- data["daysOfSupply"] = columnData.text?.div ?? "";
1877
+ data["daysOfSupply"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1872
1878
  break;
1873
1879
  case "Refills Remaining":
1874
- data["refills"] = columnData.text?.div ?? "";
1880
+ data["refills"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1875
1881
  break;
1876
1882
  case "Authored On Date":
1877
- data["startDate"] = columnData.text?.div ?? "";
1883
+ data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1878
1884
  break;
1879
1885
  default:
1880
1886
  break;
1881
1887
  }
1882
1888
  }
1883
- if (data["status"] === "active") {
1889
+ let startDateObj;
1890
+ if (data["startDate"]) {
1891
+ startDateObj = new Date(data["startDate"]);
1892
+ if (isNaN(startDateObj.getTime())) {
1893
+ startDateObj = void 0;
1894
+ }
1895
+ }
1896
+ if (data["status"] === "active" || startDateObj && startDateObj >= twelveMonthsAgo) {
1884
1897
  isSummaryCreated = true;
1885
1898
  html += `
1886
- <tr>
1887
- <td>${data["medication"]}</td>
1888
- <td>${data["sig-prescriber"] || data["sig-pharmacy"]}</td>
1889
- <td>${data["daysOfSupply"]}</td>
1890
- <td>${data["refills"]}</td>
1891
- <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
1892
- </tr>`;
1899
+ <tr>
1900
+ <td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
1901
+ <td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
1902
+ <td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
1903
+ <td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
1904
+ <td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
1905
+ <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
1906
+ </tr>`;
1893
1907
  }
1894
1908
  }
1895
1909
  }
@@ -2111,13 +2125,13 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2111
2125
  for (const columnData of rowData.section ?? []) {
2112
2126
  switch (columnData.title) {
2113
2127
  case "Immunization Name":
2114
- data["immunization"] = columnData.text?.div ?? "";
2128
+ data["immunization"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2115
2129
  break;
2116
2130
  case "Status":
2117
- data["status"] = columnData.text?.div ?? "";
2131
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2118
2132
  break;
2119
2133
  case "occurrenceDateTime":
2120
- data["occurrenceDateTime"] = columnData.text?.div ?? "";
2134
+ data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2121
2135
  break;
2122
2136
  default:
2123
2137
  break;
@@ -2168,7 +2182,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2168
2182
  const imm = resourceItem;
2169
2183
  html += `
2170
2184
  <tr id="${templateUtilities.narrativeLinkId(imm)}">
2171
- <td>${templateUtilities.codeableConcept(imm.vaccineCode)}</td>
2185
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
2172
2186
  <td>${imm.status || ""}</td>
2173
2187
  <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
2174
2188
  <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
@@ -2224,7 +2238,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
2224
2238
  <tbody>`;
2225
2239
  const addedConditionCodes = /* @__PURE__ */ new Set();
2226
2240
  for (const cond of activeConditions) {
2227
- const conditionCode = templateUtilities.codeableConcept(cond.code);
2241
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2228
2242
  if (!addedConditionCodes.has(conditionCode)) {
2229
2243
  addedConditionCodes.add(conditionCode);
2230
2244
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -2284,7 +2298,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2284
2298
  const vitalData = {};
2285
2299
  for (const component of columnData.section?.[0]?.section ?? []) {
2286
2300
  if (component.title) {
2287
- vitalData[component.title] = component.text?.div ?? "";
2301
+ vitalData[component.title] = templateUtilities.renderTextAsHtml(component.text?.div ?? "");
2288
2302
  }
2289
2303
  }
2290
2304
  const vitalValue = templateUtilities.extractObservationSummaryValue(
@@ -2296,7 +2310,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2296
2310
  data[dataKey] = vitalValue;
2297
2311
  }
2298
2312
  }
2299
- data[columnTitle] = columnData.text?.div ?? "";
2313
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2300
2314
  }
2301
2315
  }
2302
2316
  isSummaryCreated = true;
@@ -2346,7 +2360,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2346
2360
  for (const obs of observations) {
2347
2361
  html += `
2348
2362
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2349
- <td>${templateUtilities.codeableConcept(obs.code, "display")}</td>
2363
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
2350
2364
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2351
2365
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2352
2366
  <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
@@ -2400,10 +2414,10 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2400
2414
  for (const dus of deviceStatements) {
2401
2415
  html += `
2402
2416
  <tr id="${templateUtilities.narrativeLinkId(dus)}">
2403
- <td>${templateUtilities.renderDevice(dus.device)}</td>
2404
- <td>${dus.status || ""}</td>
2417
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
2418
+ <td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
2405
2419
  <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
2406
- <td>${templateUtilities.renderRecorded(dus.recordedOn, timezone)}</td>
2420
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderRecorded(dus.recordedOn, timezone))}</td>
2407
2421
  </tr>`;
2408
2422
  }
2409
2423
  html += `
@@ -2539,149 +2553,150 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2539
2553
  * Helper function to extract observation field data
2540
2554
  * @param column - Column data from the summary
2541
2555
  * @param targetData - Record to populate with extracted data
2556
+ * @param templateUtilities - Instance of TemplateUtilities for utility functions
2542
2557
  */
2543
- extractSummaryObservationFields(column, targetData) {
2558
+ extractSummaryObservationFields(column, targetData, templateUtilities) {
2544
2559
  switch (column.title) {
2545
2560
  case "Labs Name":
2546
- targetData["code"] = column.text?.div ?? "";
2561
+ targetData["code"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2547
2562
  break;
2548
2563
  case "effectiveDateTime":
2549
- targetData["effectiveDateTime"] = column.text?.div ?? "";
2564
+ targetData["effectiveDateTime"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2550
2565
  break;
2551
2566
  case "effectivePeriod.start":
2552
- targetData["effectivePeriodStart"] = column.text?.div ?? "";
2567
+ targetData["effectivePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2553
2568
  break;
2554
2569
  case "effectivePeriod.end":
2555
- targetData["effectivePeriodEnd"] = column.text?.div ?? "";
2570
+ targetData["effectivePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2556
2571
  break;
2557
2572
  // valueQuantity
2558
2573
  case "valueQuantity.value":
2559
- targetData["value"] = column.text?.div ?? "";
2574
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2560
2575
  targetData["valueType"] = "valueQuantity";
2561
2576
  break;
2562
2577
  case "valueQuantity.unit":
2563
- targetData["unit"] = column.text?.div ?? "";
2578
+ targetData["unit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2564
2579
  break;
2565
2580
  // valueCodeableConcept
2566
2581
  case "valueCodeableConcept.text":
2567
- targetData["value"] = column.text?.div ?? "";
2582
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2568
2583
  targetData["valueType"] = "valueCodeableConcept";
2569
2584
  break;
2570
2585
  case "valueCodeableConcept.coding.display":
2571
2586
  if (!targetData["value"]) {
2572
- targetData["value"] = column.text?.div ?? "";
2587
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2573
2588
  targetData["valueType"] = "valueCodeableConcept";
2574
2589
  }
2575
2590
  break;
2576
2591
  // valueString
2577
2592
  case "valueString":
2578
- targetData["value"] = column.text?.div ?? "";
2593
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2579
2594
  targetData["valueType"] = "valueString";
2580
2595
  break;
2581
2596
  // valueBoolean
2582
2597
  case "valueBoolean":
2583
- targetData["value"] = column.text?.div ?? "";
2598
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2584
2599
  targetData["valueType"] = "valueBoolean";
2585
2600
  break;
2586
2601
  // valueInteger
2587
2602
  case "valueInteger":
2588
- targetData["value"] = column.text?.div ?? "";
2603
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2589
2604
  targetData["valueType"] = "valueInteger";
2590
2605
  break;
2591
2606
  // valueDateTime
2592
2607
  case "valueDateTime":
2593
- targetData["value"] = column.text?.div ?? "";
2608
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2594
2609
  targetData["valueType"] = "valueDateTime";
2595
2610
  break;
2596
2611
  // valuePeriod
2597
2612
  case "valuePeriod.start":
2598
- targetData["valuePeriodStart"] = column.text?.div ?? "";
2613
+ targetData["valuePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2599
2614
  targetData["valueType"] = "valuePeriod";
2600
2615
  break;
2601
2616
  case "valuePeriod.end":
2602
- targetData["valuePeriodEnd"] = column.text?.div ?? "";
2617
+ targetData["valuePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2603
2618
  targetData["valueType"] = "valuePeriod";
2604
2619
  break;
2605
2620
  // valueTime
2606
2621
  case "valueTime":
2607
- targetData["value"] = column.text?.div ?? "";
2622
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2608
2623
  targetData["valueType"] = "valueTime";
2609
2624
  break;
2610
2625
  // valueSampledData
2611
2626
  case "valueSampledData.origin.value":
2612
- targetData["sampledDataOriginValue"] = column.text?.div ?? "";
2627
+ targetData["sampledDataOriginValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2613
2628
  targetData["valueType"] = "valueSampledData";
2614
2629
  break;
2615
2630
  case "valueSampledData.origin.unit":
2616
- targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
2631
+ targetData["sampledDataOriginUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2617
2632
  break;
2618
2633
  case "valueSampledData.period":
2619
- targetData["sampledDataPeriod"] = column.text?.div ?? "";
2634
+ targetData["sampledDataPeriod"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2620
2635
  break;
2621
2636
  case "valueSampledData.factor":
2622
- targetData["sampledDataFactor"] = column.text?.div ?? "";
2637
+ targetData["sampledDataFactor"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2623
2638
  break;
2624
2639
  case "valueSampledData.lowerLimit":
2625
- targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
2640
+ targetData["sampledDataLowerLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2626
2641
  break;
2627
2642
  case "valueSampledData.upperLimit":
2628
- targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
2643
+ targetData["sampledDataUpperLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2629
2644
  break;
2630
2645
  case "valueSampledData.data":
2631
- targetData["sampledDataData"] = column.text?.div ?? "";
2646
+ targetData["sampledDataData"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2632
2647
  break;
2633
2648
  // valueRange
2634
2649
  case "valueRange.low.value":
2635
- targetData["valueRangeLowValue"] = column.text?.div ?? "";
2650
+ targetData["valueRangeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2636
2651
  targetData["valueType"] = "valueRange";
2637
2652
  break;
2638
2653
  case "valueRange.low.unit":
2639
- targetData["valueRangeLowUnit"] = column.text?.div ?? "";
2654
+ targetData["valueRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2640
2655
  break;
2641
2656
  case "valueRange.high.value":
2642
- targetData["valueRangeHighValue"] = column.text?.div ?? "";
2657
+ targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2643
2658
  break;
2644
2659
  case "valueRange.high.unit":
2645
- targetData["valueRangeHighUnit"] = column.text?.div ?? "";
2660
+ targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2646
2661
  break;
2647
2662
  // valueRatio
2648
2663
  case "valueRatio.numerator.value":
2649
- targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
2664
+ targetData["valueRatioNumeratorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2650
2665
  targetData["valueType"] = "valueRatio";
2651
2666
  break;
2652
2667
  case "valueRatio.numerator.unit":
2653
- targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
2668
+ targetData["valueRatioNumeratorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2654
2669
  break;
2655
2670
  case "valueRatio.denominator.value":
2656
- targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
2671
+ targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2657
2672
  break;
2658
2673
  case "valueRatio.denominator.unit":
2659
- targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
2674
+ targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2660
2675
  break;
2661
2676
  // referenceRange
2662
2677
  case "referenceRange.low.value":
2663
- targetData["referenceRangeLow"] = column.text?.div ?? "";
2678
+ targetData["referenceRangeLow"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2664
2679
  break;
2665
2680
  case "referenceRange.low.unit":
2666
- targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
2681
+ targetData["referenceRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2667
2682
  break;
2668
2683
  case "referenceRange.high.value":
2669
- targetData["referenceRangeHigh"] = column.text?.div ?? "";
2684
+ targetData["referenceRangeHigh"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2670
2685
  break;
2671
2686
  case "referenceRange.high.unit":
2672
- targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
2687
+ targetData["referenceRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2673
2688
  break;
2674
2689
  case "referenceRange.age.low.value":
2675
- targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
2690
+ targetData["referenceRangeAgeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2676
2691
  break;
2677
2692
  case "referenceRange.age.low.unit":
2678
- targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
2693
+ targetData["referenceRangeAgeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2679
2694
  break;
2680
2695
  case "referenceRange.age.high.value":
2681
- targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
2696
+ targetData["referenceRangeAgeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2682
2697
  break;
2683
2698
  case "referenceRange.age.high.unit":
2684
- targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
2699
+ targetData["referenceRangeAgeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2685
2700
  break;
2686
2701
  default:
2687
2702
  break;
@@ -2734,28 +2749,28 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2734
2749
  for (const componentSection of columnData.section) {
2735
2750
  const componentData = {};
2736
2751
  for (const nestedColumn of componentSection.section ?? []) {
2737
- this.extractSummaryObservationFields(nestedColumn, componentData);
2752
+ this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2738
2753
  }
2739
2754
  if (Object.keys(componentData).length > 0) {
2740
2755
  components.push(componentData);
2741
2756
  }
2742
2757
  }
2743
2758
  } else {
2744
- this.extractSummaryObservationFields(columnData, data);
2759
+ this.extractSummaryObservationFields(columnData, data, templateUtilities);
2745
2760
  }
2746
2761
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2747
2762
  switch (columnData.title) {
2748
2763
  case "Diagnostic Report Name":
2749
- data["report"] = columnData.text?.div ?? "";
2764
+ data["report"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2750
2765
  break;
2751
2766
  case "Performer":
2752
- data["performer"] = columnData.text?.div ?? "";
2767
+ data["performer"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2753
2768
  break;
2754
2769
  case "Issued Date":
2755
- data["issued"] = columnData.text?.div ?? "";
2770
+ data["issued"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2756
2771
  break;
2757
2772
  case "Status":
2758
- data["status"] = columnData.text?.div ?? "";
2773
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2759
2774
  break;
2760
2775
  default:
2761
2776
  break;
@@ -2780,8 +2795,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2780
2795
  observationhtml += `
2781
2796
  <tr>
2782
2797
  <td>${componentCode}</td>
2783
- <td>${component["formattedValue"] ?? "-"}</td>
2784
- <td>${component["referenceRange"]?.trim() ?? "-"}</td>
2798
+ <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
2799
+ <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
2785
2800
  <td>${date ?? "-"}</td>
2786
2801
  </tr>`;
2787
2802
  }
@@ -2794,8 +2809,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2794
2809
  observationhtml += `
2795
2810
  <tr>
2796
2811
  <td>${data["code"] ?? "-"}</td>
2797
- <td>${data["formattedValue"] ?? "-"}</td>
2798
- <td>${data["referenceRange"]?.trim() ?? "-"}</td>
2812
+ <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
2813
+ <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
2799
2814
  <td>${date ?? "-"}</td>
2800
2815
  </tr>`;
2801
2816
  }
@@ -2955,7 +2970,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2955
2970
  <tbody>`;
2956
2971
  const observationAdded = /* @__PURE__ */ new Set();
2957
2972
  for (const obs of observations) {
2958
- const obsCode = templateUtilities.codeableConcept(obs.code);
2973
+ const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
2959
2974
  if (!observationAdded.has(obsCode)) {
2960
2975
  observationAdded.add(obsCode);
2961
2976
  html += `
@@ -2994,7 +3009,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2994
3009
  <tbody>`;
2995
3010
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2996
3011
  for (const report of reports) {
2997
- const reportName = templateUtilities.codeableConcept(report.code);
3012
+ const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
2998
3013
  if (!diagnosticReportAdded.has(reportName)) {
2999
3014
  diagnosticReportAdded.add(reportName);
3000
3015
  let resultCount = "";
@@ -3108,7 +3123,7 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3108
3123
  const proc = resourceItem;
3109
3124
  html += `
3110
3125
  <tr id="${templateUtilities.narrativeLinkId(proc)}">
3111
- <td>${templateUtilities.codeableConcept(proc.code, "display")}</td>
3126
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
3112
3127
  <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
3113
3128
  <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
3114
3129
  </tr>`;
@@ -3160,7 +3175,7 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3160
3175
  for (const obs of observations) {
3161
3176
  html += `
3162
3177
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
3163
- <td>${templateUtilities.codeableConcept(obs.code)}</td>
3178
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
3164
3179
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3165
3180
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
3166
3181
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
@@ -3205,7 +3220,7 @@ var PastHistoryOfIllnessTemplate = class {
3205
3220
  <tbody>`;
3206
3221
  const addedConditionCodes = /* @__PURE__ */ new Set();
3207
3222
  for (const cond of resolvedConditions) {
3208
- const conditionCode = templateUtilities.codeableConcept(cond.code);
3223
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3209
3224
  if (!addedConditionCodes.has(conditionCode)) {
3210
3225
  addedConditionCodes.add(conditionCode);
3211
3226
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -3291,7 +3306,7 @@ var PlanOfCareTemplate = class {
3291
3306
  const data = {};
3292
3307
  for (const columnData of rowData.section ?? []) {
3293
3308
  if (columnData.title) {
3294
- data[columnData.title] = columnData.text?.div ?? "";
3309
+ data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3295
3310
  }
3296
3311
  }
3297
3312
  if (data["status"] !== "active") {
@@ -3367,7 +3382,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3367
3382
  <tbody>`;
3368
3383
  const addedConditionCodes = /* @__PURE__ */ new Set();
3369
3384
  for (const cond of activeConditions) {
3370
- const conditionCode = templateUtilities.codeableConcept(cond.code);
3385
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3371
3386
  if (!addedConditionCodes.has(conditionCode)) {
3372
3387
  addedConditionCodes.add(conditionCode);
3373
3388
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -3412,7 +3427,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3412
3427
  if (impression.finding && impression.finding.length > 0) {
3413
3428
  findingsHtml = "<ul>";
3414
3429
  for (const finding of impression.finding) {
3415
- const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConcept(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3430
+ const findingText = finding.itemCodeableConcept ? templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(finding.itemCodeableConcept)) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3416
3431
  const cause = finding.basis || "";
3417
3432
  findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
3418
3433
  }
@@ -3473,9 +3488,9 @@ var PregnancyTemplate = class _PregnancyTemplate {
3473
3488
  const obs = resource;
3474
3489
  html += `
3475
3490
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
3476
- <td>${templateUtilities.extractPregnancyStatus(obs)}</td>
3491
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
3477
3492
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3478
- <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3493
+ <td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
3479
3494
  </tr>`;
3480
3495
  }
3481
3496
  html += `
@@ -3525,7 +3540,7 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3525
3540
  const consent = resourceItem;
3526
3541
  html += `
3527
3542
  <tr id="${templateUtilities.narrativeLinkId(consent)}">
3528
- <td>${templateUtilities.codeableConcept(consent.scope, "display")}</td>
3543
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
3529
3544
  <td>${consent.status || ""}</td>
3530
3545
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
3531
3546
  <td>${consent.dateTime || ""}</td>
@@ -3546,16 +3561,18 @@ var TypeScriptTemplateMapper = class {
3546
3561
  * @param resources - FHIR resources
3547
3562
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3548
3563
  * @param useSectionSummary - Whether to use the section summary for narrative generation
3564
+ * @param now - Optional current date to use for generating relative dates in the narrative
3549
3565
  * @returns HTML string for rendering
3550
3566
  */
3551
- static generateNarrative(section, resources, timezone, useSectionSummary = false) {
3567
+ static generateNarrative(section, resources, timezone, useSectionSummary = false, now) {
3552
3568
  const templateClass = this.sectionToTemplate[section];
3553
3569
  if (!templateClass) {
3554
3570
  throw new Error(`No template found for section: ${section}`);
3555
3571
  }
3556
3572
  return useSectionSummary ? templateClass.generateSummaryNarrative(
3557
3573
  resources,
3558
- timezone
3574
+ timezone,
3575
+ now
3559
3576
  ) : templateClass.generateNarrative(resources, timezone);
3560
3577
  }
3561
3578
  };
@@ -3613,14 +3630,15 @@ var NarrativeGenerator = class {
3613
3630
  * @param resources - Array of domain resources
3614
3631
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3615
3632
  * @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
3633
+ * @param now - Optional date parameter
3616
3634
  * @returns Generated HTML content or undefined if no resources
3617
3635
  */
3618
- static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false) {
3636
+ static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false, now) {
3619
3637
  if (!resources || resources.length === 0) {
3620
3638
  return void 0;
3621
3639
  }
3622
3640
  try {
3623
- const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary);
3641
+ const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary, now);
3624
3642
  if (!content) {
3625
3643
  return void 0;
3626
3644
  }
@@ -3642,8 +3660,8 @@ var NarrativeGenerator = class {
3642
3660
  const options = aggressive ? AGGRESSIVE_MINIFY_OPTIONS : DEFAULT_MINIFY_OPTIONS;
3643
3661
  return await (0, import_html_minifier_terser.minify)(html, options);
3644
3662
  } catch (error) {
3645
- console.warn("HTML minification failed", error);
3646
- return html;
3663
+ console.warn("HTML minification failed", error, html);
3664
+ return `${error instanceof Error ? error.message : String(error)}`;
3647
3665
  }
3648
3666
  }
3649
3667
  /**
@@ -3672,10 +3690,11 @@ var NarrativeGenerator = class {
3672
3690
  * @param timezone - Optional timezone to use for date formatting
3673
3691
  * @param minify - Whether to minify the HTML content (default: true)
3674
3692
  * @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
3693
+ * @param now - Optional date parameter
3675
3694
  * @returns Promise that resolves to a FHIR Narrative object or undefined if no resources
3676
3695
  */
3677
- static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false) {
3678
- const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary);
3696
+ static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false, now) {
3697
+ const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary, now);
3679
3698
  if (!content) {
3680
3699
  return void 0;
3681
3700
  }
@@ -3852,8 +3871,9 @@ var ComprehensiveIPSCompositionBuilder = class {
3852
3871
  * @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
3853
3872
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3854
3873
  * @param patientId - Optional patient ID to use as primary patient for composition reference
3874
+ * @param now - Optional current date to use for composition date (defaults to new Date())
3855
3875
  */
3856
- async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId) {
3876
+ async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
3857
3877
  if (baseUrl.endsWith("/")) {
3858
3878
  baseUrl = baseUrl.slice(0, -1);
3859
3879
  }
@@ -3880,20 +3900,22 @@ var ComprehensiveIPSCompositionBuilder = class {
3880
3900
  // Assuming patient is also a practitioner for simplicity
3881
3901
  display: authorOrganizationName
3882
3902
  }],
3883
- date: (/* @__PURE__ */ new Date()).toISOString(),
3903
+ date: (now || /* @__PURE__ */ new Date()).toISOString(),
3884
3904
  title: "International Patient Summary",
3885
3905
  section: this.sections,
3886
3906
  text: await NarrativeGenerator.generateNarrativeAsync(
3887
3907
  "Patient" /* PATIENT */,
3888
3908
  this.patients,
3889
3909
  timezone,
3890
- true
3910
+ true,
3911
+ false,
3912
+ now
3891
3913
  )
3892
3914
  };
3893
3915
  const bundle = {
3894
3916
  resourceType: "Bundle",
3895
3917
  type: "document",
3896
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3918
+ timestamp: (now || /* @__PURE__ */ new Date()).toISOString(),
3897
3919
  identifier: {
3898
3920
  "system": "urn:ietf:rfc:3986",
3899
3921
  "value": "urn:uuid:4dcfd353-49fd-4ab0-b521-c8d57ced74d6"