@imranq2/fhirpatientsummary 1.0.26 → 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
@@ -87,6 +87,8 @@ var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
87
87
  "Diastolic Blood Pressure": "valueRatio.denominator.value",
88
88
  "Default": "valueString"
89
89
  };
90
+ var RESULT_SUMMARY_OBSERVATION_CATEGORIES = ["laboratory", "Lab", "LAB"];
91
+ var RESULT_SUMMARY_OBSERVATION_DATE_FILTER = 2 * 365 * 24 * 60 * 60 * 1e3;
90
92
  var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
91
93
 
92
94
  // src/structures/ips_section_resource_map.ts
@@ -106,7 +108,7 @@ var IPSSectionResourceFilters = {
106
108
  // Includes DeviceUseStatement. Device is needed for linked device details
107
109
  ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
108
110
  // Only include finalized diagnostic reports
109
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => ["DiagnosticReport", "Observation"].includes(resource.resourceType) && resource.status === "final",
111
+ ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
110
112
  // Only include completed procedures
111
113
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
112
114
  // Only include social history Observations
@@ -128,7 +130,7 @@ var IPSSectionSummaryCompositionFilter = {
128
130
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
129
131
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
130
132
  ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
131
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && ["lab_summary_document", "diagnosticreportlab_summary_document"].includes(c.code)),
133
+ // [IPSSections.DIAGNOSTIC_REPORTS]: (resource) => resource.resourceType === 'Composition' && resource.type?.coding?.some((c: any) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && ["lab_summary_document", "diagnosticreportlab_summary_document"].includes(c.code)),
132
134
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
133
135
  };
134
136
  var IPSSectionResourceHelper = class {
@@ -218,7 +220,7 @@ var TemplateUtilities = class {
218
220
  renderOrganization(orgRef) {
219
221
  const organization = orgRef && this.resolveReference(orgRef);
220
222
  if (organization && organization.resourceType === "Organization" && organization.name) {
221
- return organization.name;
223
+ return this.renderTextAsHtml(organization.name);
222
224
  }
223
225
  return "";
224
226
  }
@@ -230,7 +232,7 @@ var TemplateUtilities = class {
230
232
  renderVaccineManufacturer(immunization) {
231
233
  const organization = immunization.manufacturer && this.resolveReference(immunization.manufacturer);
232
234
  if (organization && organization.resourceType === "Organization" && organization.name) {
233
- return organization.name;
235
+ return this.renderTextAsHtml(organization.name);
234
236
  }
235
237
  return "";
236
238
  }
@@ -269,7 +271,7 @@ var TemplateUtilities = class {
269
271
  */
270
272
  renderMedicationCode(medication) {
271
273
  if (medication && medication.code) {
272
- return this.codeableConcept(medication.code, "display");
274
+ return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
273
275
  }
274
276
  return "";
275
277
  }
@@ -280,7 +282,7 @@ var TemplateUtilities = class {
280
282
  */
281
283
  renderDoseNumber(doseNumber) {
282
284
  if (doseNumber && doseNumber.value !== void 0) {
283
- return doseNumber.value.toString();
285
+ return this.renderTextAsHtml(doseNumber.value.toString());
284
286
  }
285
287
  return "";
286
288
  }
@@ -291,7 +293,7 @@ var TemplateUtilities = class {
291
293
  */
292
294
  renderValueUnit(value) {
293
295
  if (value && value.constructor?.name === "Quantity" && value.unit) {
294
- return value.unit;
296
+ return this.renderTextAsHtml(value.unit);
295
297
  }
296
298
  return "";
297
299
  }
@@ -357,7 +359,7 @@ var TemplateUtilities = class {
357
359
  * @returns Comma-separated string of items
358
360
  */
359
361
  safeConcat(list, attr) {
360
- return this.concat(list || [], attr);
362
+ return this.renderTextAsHtml(this.concat(list || [], attr));
361
363
  }
362
364
  /**
363
365
  * Concatenates text from a list of CodeableConcept objects
@@ -374,7 +376,7 @@ var TemplateUtilities = class {
374
376
  items.push(item.text);
375
377
  }
376
378
  }
377
- return items.join(", ");
379
+ return this.renderTextAsHtml(items.join(", "));
378
380
  }
379
381
  /**
380
382
  * Concatenates reaction manifestations
@@ -395,7 +397,7 @@ var TemplateUtilities = class {
395
397
  }
396
398
  }
397
399
  }
398
- return texts.join(", ");
400
+ return this.renderTextAsHtml(texts.join(", "));
399
401
  }
400
402
  /**
401
403
  * Concatenates dose numbers
@@ -412,7 +414,7 @@ var TemplateUtilities = class {
412
414
  doseNumbers.push(item.doseNumberPositiveInt.toString());
413
415
  }
414
416
  }
415
- return doseNumbers.join(", ");
417
+ return this.renderTextAsHtml(doseNumbers.join(", "));
416
418
  }
417
419
  /**
418
420
  * Concatenates dosage routes
@@ -429,7 +431,7 @@ var TemplateUtilities = class {
429
431
  routes.push(item.route.text);
430
432
  }
431
433
  }
432
- return routes.join(", ");
434
+ return this.renderTextAsHtml(routes.join(", "));
433
435
  }
434
436
  /**
435
437
  * Returns the first item from a list of CodeableConcept objects
@@ -438,7 +440,7 @@ var TemplateUtilities = class {
438
440
  */
439
441
  firstFromCodeableConceptList(list) {
440
442
  if (list && Array.isArray(list) && list[0]) {
441
- return this.codeableConcept(list[0], "display");
443
+ return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
442
444
  }
443
445
  return "";
444
446
  }
@@ -457,7 +459,7 @@ var TemplateUtilities = class {
457
459
  texts.push(item.text);
458
460
  }
459
461
  }
460
- return texts.join(", ");
462
+ return this.renderTextAsHtml(texts.join(", "));
461
463
  }
462
464
  /**
463
465
  * Renders component codes
@@ -592,9 +594,18 @@ var TemplateUtilities = class {
592
594
  }
593
595
  return status;
594
596
  }
597
+ formatFloatValue(value) {
598
+ if (typeof value === "number") {
599
+ return value.toFixed(2).replace(/\.?0+$/, "");
600
+ } else if (typeof value === "string" && !isNaN(Number(value))) {
601
+ return parseFloat(value).toFixed(2).replace(/\.?0+$/, "");
602
+ }
603
+ return value;
604
+ }
595
605
  extractObservationSummaryValue(data, timezone) {
596
606
  if (data["valueQuantity.value"] !== void 0) {
597
- const value = data["valueQuantity.value"];
607
+ let value = data["valueQuantity.value"];
608
+ value = this.formatFloatValue(value);
598
609
  const unit = data["valueQuantity.unit"];
599
610
  return unit ? `${value} ${unit}` : `${value}`;
600
611
  }
@@ -611,7 +622,9 @@ var TemplateUtilities = class {
611
622
  return String(data["valueBoolean"]);
612
623
  }
613
624
  if (data["valueInteger"] !== void 0) {
614
- return String(data["valueInteger"]);
625
+ let value = String(data["valueInteger"]);
626
+ value = this.formatFloatValue(value);
627
+ return value;
615
628
  }
616
629
  if (data["valueDateTime"] !== void 0) {
617
630
  return this.renderTime(data["valueDateTime"], timezone);
@@ -631,7 +644,8 @@ var TemplateUtilities = class {
631
644
  return this.renderTime(data["valueTime"], timezone);
632
645
  }
633
646
  if (data["valueSampledData.origin.value"] !== void 0 || data["valueSampledData.origin.unit"] !== void 0) {
634
- const originValue = data["valueSampledData.origin.value"];
647
+ let originValue = data["valueSampledData.origin.value"];
648
+ originValue = this.formatFloatValue(originValue);
635
649
  const originUnit = data["valueSampledData.origin.unit"];
636
650
  let result = "";
637
651
  if (originValue !== void 0 && originUnit !== void 0) {
@@ -641,10 +655,10 @@ var TemplateUtilities = class {
641
655
  } else if (originUnit !== void 0) {
642
656
  result = `${originUnit}`;
643
657
  }
644
- const period = data["valueSampledData.period"];
645
- const factor = data["valueSampledData.factor"];
646
- const lowerLimit = data["valueSampledData.lowerLimit"];
647
- const upperLimit = data["valueSampledData.upperLimit"];
658
+ const period = this.formatFloatValue(data["valueSampledData.period"]);
659
+ const factor = this.formatFloatValue(data["valueSampledData.factor"]);
660
+ const lowerLimit = this.formatFloatValue(data["valueSampledData.lowerLimit"]);
661
+ const upperLimit = this.formatFloatValue(data["valueSampledData.upperLimit"]);
648
662
  const sampledData = data["valueSampledData.data"];
649
663
  const extras = [];
650
664
  if (period !== void 0) extras.push(`period: ${period}`);
@@ -680,14 +694,14 @@ var TemplateUtilities = class {
680
694
  if (data["valueRatio.numerator.value"] !== void 0 || data["valueRatio.denominator.value"] !== void 0) {
681
695
  let ratio = "";
682
696
  if (data["valueRatio.numerator.value"] !== void 0) {
683
- ratio += `${data["valueRatio.numerator.value"]}`;
697
+ ratio += `${this.formatFloatValue(data["valueRatio.numerator.value"])}`;
684
698
  if (data["valueRatio.numerator.unit"] !== void 0) {
685
699
  ratio += ` ${data["valueRatio.numerator.unit"]}`;
686
700
  }
687
701
  }
688
702
  if (data["valueRatio.denominator.value"] !== void 0) {
689
703
  ratio += " / ";
690
- ratio += `${data["valueRatio.denominator.value"]}`;
704
+ ratio += `${this.formatFloatValue(data["valueRatio.denominator.value"])}`;
691
705
  if (data["valueRatio.denominator.unit"] !== void 0) {
692
706
  ratio += ` ${data["valueRatio.denominator.unit"]}`;
693
707
  }
@@ -777,9 +791,11 @@ var TemplateUtilities = class {
777
791
  return "";
778
792
  }
779
793
  /**
780
- * Renders text as HTML, escaping special characters and replacing newlines with <br />
781
- * @param text - The text to render
782
- * @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 />
783
799
  */
784
800
  renderTextAsHtml(text) {
785
801
  if (!text || text.trim() === "") {
@@ -843,7 +859,7 @@ var TemplateUtilities = class {
843
859
  dateTime = DateTime.fromISO(String(dateValue));
844
860
  }
845
861
  if (!dateTime.isValid) {
846
- return String(dateValue);
862
+ return this.renderTextAsHtml(String(dateValue));
847
863
  }
848
864
  if (dateOnly) {
849
865
  dateTime = dateTime.toUTC();
@@ -859,9 +875,9 @@ var TemplateUtilities = class {
859
875
  hour12: true,
860
876
  timeZoneName: "short"
861
877
  };
862
- return dateTime.toLocaleString(formatOptions);
878
+ return this.renderTextAsHtml(dateTime.toLocaleString(formatOptions));
863
879
  } catch {
864
- return String(dateValue);
880
+ return this.renderTextAsHtml(String(dateValue));
865
881
  }
866
882
  }
867
883
  /**
@@ -941,6 +957,313 @@ var TemplateUtilities = class {
941
957
 
942
958
  // src/constants.ts
943
959
  var ADDRESS_SIMILARITY_THRESHOLD = 70;
960
+ var LAB_LOINC_MAP = {
961
+ // METABOLIC PANELS (VERY COMMONLY ORDERED)
962
+ "Basic Metabolic Panel": [
963
+ "24321-2",
964
+ // Basic metabolic 2000 panel - Serum or Plasma
965
+ "51990-0"
966
+ // Basic metabolic 2000 panel - Blood
967
+ ],
968
+ "Comprehensive Metabolic Panel": [
969
+ "24323-8"
970
+ // Comprehensive metabolic 2000 panel - Serum or Plasma
971
+ ],
972
+ // CBC COMPONENTS
973
+ Hemoglobin: [
974
+ "718-7"
975
+ // Hemoglobin [Mass/volume] in Blood
976
+ ],
977
+ Hematocrit: [
978
+ "4544-3"
979
+ // Hematocrit [Volume Fraction] of Blood by Automated count
980
+ ],
981
+ "White Blood Cell Count": [
982
+ "6690-2"
983
+ // Leukocytes [///volume] in Blood by Automated count
984
+ ],
985
+ "Platelet Count": [
986
+ "777-3"
987
+ // Platelets [///volume] in Blood by Automated count
988
+ ],
989
+ "Complete Blood Count": [
990
+ "58410-2",
991
+ // CBC panel - Blood by Automated count
992
+ "57021-8",
993
+ // CBC W Auto Differential panel - Blood
994
+ "69738-3"
995
+ // CBC W Auto Differential panel - Blood by Automated count
996
+ ],
997
+ // CHEMISTRY - GLUCOSE
998
+ Glucose: [
999
+ "2345-7",
1000
+ // Glucose [Mass/volume] in Serum or Plasma
1001
+ "1558-6",
1002
+ // Fasting glucose [Mass/volume] in Serum or Plasma
1003
+ "2339-0"
1004
+ // Glucose [Mass/volume] in Blood
1005
+ ],
1006
+ // RENAL FUNCTION
1007
+ Creatinine: [
1008
+ "2160-0"
1009
+ // Creatinine [Mass/volume] in Serum or Plasma
1010
+ ],
1011
+ "Blood Urea Nitrogen": [
1012
+ "3094-0",
1013
+ // Urea nitrogen [Mass/volume] in Serum or Plasma
1014
+ "6299-2"
1015
+ // Urea nitrogen [Mass/volume] in Blood
1016
+ ],
1017
+ // ELECTROLYTES
1018
+ Sodium: [
1019
+ "2951-2"
1020
+ // Sodium [Moles/volume] in Serum or Plasma
1021
+ ],
1022
+ Potassium: [
1023
+ "2823-3"
1024
+ // Potassium [Moles/volume] in Serum or Plasma
1025
+ ],
1026
+ Chloride: [
1027
+ "2075-0"
1028
+ // Chloride [Moles/volume] in Serum or Plasma
1029
+ ],
1030
+ Calcium: [
1031
+ "17861-6",
1032
+ // Calcium [Mass/volume] in Serum or Plasma
1033
+ "1994-3"
1034
+ // Calcium.ionized [Moles/volume] in Serum or Plasma
1035
+ ],
1036
+ Magnesium: [
1037
+ "19123-9"
1038
+ // Magnesium [Mass/volume] in Serum or Plasma
1039
+ ],
1040
+ Phosphate: [
1041
+ "14879-1"
1042
+ // Phosphate [Mass/volume] in Serum or Plasma
1043
+ ],
1044
+ // PROTEINS
1045
+ Albumin: [
1046
+ "1751-7"
1047
+ // Albumin [Mass/volume] in Serum or Plasma
1048
+ ],
1049
+ "Total Protein": [
1050
+ "2885-2"
1051
+ // Protein [Mass/volume] in Serum or Plasma
1052
+ ],
1053
+ // LIVER FUNCTION
1054
+ Bilirubin: [
1055
+ "1975-2",
1056
+ // Bilirubin.total [Mass/volume] in Serum or Plasma
1057
+ "1968-7",
1058
+ // Bilirubin.direct [Mass/volume] in Serum or Plasma
1059
+ "1971-1"
1060
+ // Bilirubin.indirect [Mass/volume] in Serum or Plasma
1061
+ ],
1062
+ "Alkaline Phosphatase": [
1063
+ "6768-6"
1064
+ // Alkaline phosphatase [Enzymatic activity/volume] in Serum or Plasma
1065
+ ],
1066
+ AST: [
1067
+ "1920-8"
1068
+ // Aspartate aminotransferase [Enzymatic activity/volume] in Serum or Plasma
1069
+ ],
1070
+ ALT: [
1071
+ "1742-6"
1072
+ // Alanine aminotransferase [Enzymatic activity/volume] in Serum or Plasma
1073
+ ],
1074
+ GGT: [
1075
+ "2324-2"
1076
+ // Gamma glutamyl transferase [Enzymatic activity/volume] in Serum or Plasma
1077
+ ],
1078
+ // ENDOCRINE
1079
+ TSH: [
1080
+ "3016-3"
1081
+ // Thyrotropin [Units/volume] in Serum or Plasma
1082
+ ],
1083
+ "Free T4": [
1084
+ "3024-7"
1085
+ // Thyroxine (T4) free [Mass/volume] in Serum or Plasma
1086
+ ],
1087
+ "Total T4": [
1088
+ "3026-2"
1089
+ // Thyroxine (T4) [Mass/volume] in Serum or Plasma
1090
+ ],
1091
+ "Free T3": [
1092
+ "3051-0"
1093
+ // Triiodothyronine (T3) free [Mass/volume] in Serum or Plasma
1094
+ ],
1095
+ "Total T3": [
1096
+ "3053-6"
1097
+ // Triiodothyronine (T3) [Mass/volume] in Serum or Plasma
1098
+ ],
1099
+ HbA1c: [
1100
+ "4548-4",
1101
+ // Hemoglobin A1c/Hemoglobin.total in Blood
1102
+ "17856-6"
1103
+ // Hemoglobin A1c/Hemoglobin.total in Blood by HPLC
1104
+ ],
1105
+ // LIPID PANEL
1106
+ "Lipid Panel": [
1107
+ "24331-1",
1108
+ // Lipid 1996 panel - Serum or Plasma
1109
+ "57698-3"
1110
+ // Lipid panel with direct LDL - Serum or Plasma
1111
+ ],
1112
+ "Cholesterol Total": [
1113
+ "2093-3"
1114
+ // Cholesterol [Mass/volume] in Serum or Plasma
1115
+ ],
1116
+ "HDL Cholesterol": [
1117
+ "2085-9"
1118
+ // Cholesterol in HDL [Mass/volume] in Serum or Plasma
1119
+ ],
1120
+ "LDL Cholesterol": [
1121
+ "13457-7",
1122
+ // Cholesterol in LDL [Mass/volume] in Serum or Plasma by calculation
1123
+ "18262-6"
1124
+ // Cholesterol in LDL [Mass/volume] in Serum or Plasma by Direct assay
1125
+ ],
1126
+ Triglycerides: [
1127
+ "2571-8"
1128
+ // Triglyceride [Mass/volume] in Serum or Plasma
1129
+ ],
1130
+ // COAGULATION STUDIES (COMMONLY MISSING!)
1131
+ PT: [
1132
+ "5902-2"
1133
+ // Prothrombin time (PT)
1134
+ ],
1135
+ INR: [
1136
+ "6301-6"
1137
+ // INR in Platelet poor plasma by Coagulation assay
1138
+ ],
1139
+ PTT: [
1140
+ "3173-2",
1141
+ // aPTT in Blood by Coagulation assay
1142
+ "14979-9"
1143
+ // aPTT in Platelet poor plasma by Coagulation assay
1144
+ ],
1145
+ Fibrinogen: [
1146
+ "3255-7"
1147
+ // Fibrinogen [Mass/volume] in Platelet poor plasma by Coagulation assay
1148
+ ],
1149
+ "D-Dimer": [
1150
+ "48065-7",
1151
+ // D-dimer FEU [Mass/volume] in Platelet poor plasma
1152
+ "48066-5"
1153
+ // D-dimer DDU [Mass/volume] in Platelet poor plasma
1154
+ ],
1155
+ // CARDIAC MARKERS (CRITICAL FOR ER!)
1156
+ "Troponin I": [
1157
+ "10839-9",
1158
+ // Troponin I.cardiac [Mass/volume] in Serum or Plasma
1159
+ "42757-5",
1160
+ // Troponin I.cardiac [Mass/volume] in Blood
1161
+ "89579-7"
1162
+ // Troponin I.cardiac [Mass/volume] in Serum or Plasma by High sensitivity method
1163
+ ],
1164
+ "Troponin T": [
1165
+ "6598-7",
1166
+ // Troponin T.cardiac [Mass/volume] in Serum or Plasma
1167
+ "48425-3"
1168
+ // Troponin T.cardiac [Mass/volume] in Serum or Plasma by High sensitivity method
1169
+ ],
1170
+ BNP: [
1171
+ "30934-4"
1172
+ // BNP [Mass/volume] in Serum or Plasma
1173
+ ],
1174
+ "NT-proBNP": [
1175
+ "33762-6"
1176
+ // NT-proBNP [Mass/volume] in Serum or Plasma
1177
+ ],
1178
+ "CK-MB": [
1179
+ "13969-1"
1180
+ // Creatine kinase.MB [Mass/volume] in Serum or Plasma
1181
+ ],
1182
+ // INFLAMMATORY MARKERS
1183
+ CRP: [
1184
+ "1988-5",
1185
+ // C reactive protein [Mass/volume] in Serum or Plasma
1186
+ "30522-7"
1187
+ // C reactive protein [Mass/volume] in Serum or Plasma by High sensitivity method
1188
+ ],
1189
+ ESR: [
1190
+ "30341-2",
1191
+ // Erythrocyte sedimentation rate by Westergren method
1192
+ "4537-7"
1193
+ // Erythrocyte sedimentation rate
1194
+ ],
1195
+ // VITAMINS & MINERALS (VERY HIGH VOLUME!)
1196
+ "Vitamin D": [
1197
+ "1990-1",
1198
+ // Vitamin D [Mass/volume] in Serum or Plasma (obsolete, but still used)
1199
+ "14635-7",
1200
+ // 25-Hydroxyvitamin D3 [Mass/volume] in Serum or Plasma
1201
+ "62292-8"
1202
+ // 25-Hydroxyvitamin D2+D3 [Mass/volume] in Serum or Plasma
1203
+ ],
1204
+ "Vitamin B12": [
1205
+ "2132-9"
1206
+ // Cobalamin (Vitamin B12) [Mass/volume] in Serum or Plasma
1207
+ ],
1208
+ Folate: [
1209
+ "2284-8",
1210
+ // Folate [Mass/volume] in Serum or Plasma
1211
+ "15152-2"
1212
+ // Folate [Mass/volume] in Red Blood Cells
1213
+ ],
1214
+ Iron: [
1215
+ "2498-4"
1216
+ // Iron [Mass/volume] in Serum or Plasma
1217
+ ],
1218
+ Ferritin: [
1219
+ "2276-4"
1220
+ // Ferritin [Mass/volume] in Serum or Plasma
1221
+ ],
1222
+ TIBC: [
1223
+ "2500-7"
1224
+ // Iron binding capacity [Mass/volume] in Serum or Plasma
1225
+ ],
1226
+ // OTHER COMMON TESTS
1227
+ PSA: [
1228
+ "2857-1",
1229
+ // Prostate specific Ag [Mass/volume] in Serum or Plasma
1230
+ "10886-0"
1231
+ // Prostate specific Ag Free [Mass/volume] in Serum or Plasma
1232
+ ],
1233
+ "Uric Acid": [
1234
+ "3084-1"
1235
+ // Urate [Mass/volume] in Serum or Plasma
1236
+ ],
1237
+ LDH: [
1238
+ "2532-0",
1239
+ // Lactate dehydrogenase [Enzymatic activity/volume] in Serum or Plasma
1240
+ "14804-9"
1241
+ // Lactate dehydrogenase [Enzymatic activity/volume] in Serum or Plasma by Lactate to pyruvate reaction
1242
+ ],
1243
+ Amylase: [
1244
+ "1798-8"
1245
+ // Amylase [Enzymatic activity/volume] in Serum or Plasma
1246
+ ],
1247
+ Lipase: [
1248
+ "3040-3"
1249
+ // Lipase [Enzymatic activity/volume] in Serum or Plasma
1250
+ ],
1251
+ hCG: [
1252
+ "21198-7",
1253
+ // Choriogonadotropin.beta subunit [Units/volume] in Serum or Plasma
1254
+ "2118-8",
1255
+ // Choriogonadotropin (pregnancy test) [Presence] in Serum or Plasma
1256
+ "2106-3"
1257
+ // Choriogonadotropin (pregnancy test) [Presence] in Urine
1258
+ ],
1259
+ // URINALYSIS
1260
+ Urinalysis: [
1261
+ "24357-6",
1262
+ // Urinalysis macro (dipstick) panel - Urine
1263
+ "24356-8"
1264
+ // Urinalysis complete panel - Urine
1265
+ ]
1266
+ };
944
1267
 
945
1268
  // src/narratives/templates/typescript/PatientTemplate.ts
946
1269
  var PatientTemplate = class _PatientTemplate {
@@ -1258,7 +1581,7 @@ var PatientTemplate = class _PatientTemplate {
1258
1581
  const uniqueLanguages = /* @__PURE__ */ new Set();
1259
1582
  const preferredLanguages = /* @__PURE__ */ new Set();
1260
1583
  patient.communication.forEach((comm) => {
1261
- const language = templateUtilities.codeableConcept(comm.language);
1584
+ const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
1262
1585
  if (language) {
1263
1586
  if (comm.preferred) {
1264
1587
  preferredLanguages.add(language);
@@ -1316,13 +1639,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1316
1639
  for (const columnData of rowData.section ?? []) {
1317
1640
  switch (columnData.title) {
1318
1641
  case "Allergen Name":
1319
- data["allergen"] = columnData.text?.div ?? "";
1642
+ data["allergen"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1320
1643
  break;
1321
1644
  case "Criticality":
1322
- data["criticality"] = columnData.text?.div ?? "";
1645
+ data["criticality"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1323
1646
  break;
1324
1647
  case "Recorded Date":
1325
- data["recordedDate"] = columnData.text?.div ?? "";
1648
+ data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1326
1649
  break;
1327
1650
  default:
1328
1651
  break;
@@ -1443,11 +1766,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1443
1766
  for (const allergy of allergies) {
1444
1767
  html += `
1445
1768
  <tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
1446
- <td class="Name"><span class="AllergenName">${templateUtilities.codeableConcept(allergy.code)}</span></td>
1447
- <td class="Status">${templateUtilities.codeableConcept(allergy.clinicalStatus) || "-"}</td>
1448
- <td class="Category">${templateUtilities.safeConcat(allergy.category) || "-"}</td>
1449
- <td class="Reaction">${templateUtilities.concatReactionManifestation(allergy.reaction) || "-"}</td>
1450
- <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>
1451
1774
  <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
1452
1775
  if (includeResolved) {
1453
1776
  let endDate = "-";
@@ -1460,7 +1783,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1460
1783
  }
1461
1784
  }
1462
1785
  html += `
1463
- <td class="ResolvedDate">${endDate}</td>`;
1786
+ <td class="ResolvedDate">${templateUtilities.renderTextAsHtml(endDate)}</td>`;
1464
1787
  }
1465
1788
  html += `</tr>`;
1466
1789
  }
@@ -1483,17 +1806,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1483
1806
  * Generate HTML narrative for Medication resources using summary
1484
1807
  * @param resources - FHIR Composition resources
1485
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())
1486
1810
  * @returns HTML string for rendering
1487
1811
  */
1488
- generateSummaryNarrative(resources, timezone) {
1812
+ generateSummaryNarrative(resources, timezone, now) {
1489
1813
  const templateUtilities = new TemplateUtilities(resources);
1490
1814
  let isSummaryCreated = false;
1815
+ const currentDate = now || /* @__PURE__ */ new Date();
1816
+ const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
1491
1817
  let html = `
1492
1818
  <div>
1493
1819
  <table>
1494
1820
  <thead>
1495
1821
  <tr>
1496
1822
  <th>Medication</th>
1823
+ <th>Status</th>
1497
1824
  <th>Sig</th>
1498
1825
  <th>Days of Supply</th>
1499
1826
  <th>Refills</th>
@@ -1507,40 +1834,48 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1507
1834
  for (const columnData of rowData.section ?? []) {
1508
1835
  switch (columnData.title) {
1509
1836
  case "Medication Name":
1510
- data["medication"] = columnData.text?.div ?? "";
1837
+ data["medication"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1511
1838
  break;
1512
1839
  case "Status":
1513
- data["status"] = columnData.text?.div ?? "";
1840
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1514
1841
  break;
1515
1842
  case "Prescriber Instruction":
1516
- data["sig-prescriber"] = columnData.text?.div ?? "";
1843
+ data["sig-prescriber"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1517
1844
  break;
1518
1845
  case "Pharmacy Instruction":
1519
- data["sig-pharmacy"] = columnData.text?.div ?? "";
1846
+ data["sig-pharmacy"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1520
1847
  break;
1521
1848
  case "Days Of Supply":
1522
- data["daysOfSupply"] = columnData.text?.div ?? "";
1849
+ data["daysOfSupply"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1523
1850
  break;
1524
1851
  case "Refills Remaining":
1525
- data["refills"] = columnData.text?.div ?? "";
1852
+ data["refills"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1526
1853
  break;
1527
1854
  case "Authored On Date":
1528
- data["startDate"] = columnData.text?.div ?? "";
1855
+ data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1529
1856
  break;
1530
1857
  default:
1531
1858
  break;
1532
1859
  }
1533
1860
  }
1534
- 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) {
1535
1869
  isSummaryCreated = true;
1536
1870
  html += `
1537
- <tr>
1538
- <td>${data["medication"]}</td>
1539
- <td>${data["sig-prescriber"] || data["sig-pharmacy"]}</td>
1540
- <td>${data["daysOfSupply"]}</td>
1541
- <td>${data["refills"]}</td>
1542
- <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
1543
- </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>`;
1544
1879
  }
1545
1880
  }
1546
1881
  }
@@ -1762,13 +2097,13 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1762
2097
  for (const columnData of rowData.section ?? []) {
1763
2098
  switch (columnData.title) {
1764
2099
  case "Immunization Name":
1765
- data["immunization"] = columnData.text?.div ?? "";
2100
+ data["immunization"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1766
2101
  break;
1767
2102
  case "Status":
1768
- data["status"] = columnData.text?.div ?? "";
2103
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1769
2104
  break;
1770
2105
  case "occurrenceDateTime":
1771
- data["occurrenceDateTime"] = columnData.text?.div ?? "";
2106
+ data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1772
2107
  break;
1773
2108
  default:
1774
2109
  break;
@@ -1819,7 +2154,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1819
2154
  const imm = resourceItem;
1820
2155
  html += `
1821
2156
  <tr id="${templateUtilities.narrativeLinkId(imm)}">
1822
- <td>${templateUtilities.codeableConcept(imm.vaccineCode)}</td>
2157
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
1823
2158
  <td>${imm.status || ""}</td>
1824
2159
  <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
1825
2160
  <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
@@ -1875,7 +2210,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
1875
2210
  <tbody>`;
1876
2211
  const addedConditionCodes = /* @__PURE__ */ new Set();
1877
2212
  for (const cond of activeConditions) {
1878
- const conditionCode = templateUtilities.codeableConcept(cond.code);
2213
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
1879
2214
  if (!addedConditionCodes.has(conditionCode)) {
1880
2215
  addedConditionCodes.add(conditionCode);
1881
2216
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -1935,7 +2270,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1935
2270
  const vitalData = {};
1936
2271
  for (const component of columnData.section?.[0]?.section ?? []) {
1937
2272
  if (component.title) {
1938
- vitalData[component.title] = component.text?.div ?? "";
2273
+ vitalData[component.title] = templateUtilities.renderTextAsHtml(component.text?.div ?? "");
1939
2274
  }
1940
2275
  }
1941
2276
  const vitalValue = templateUtilities.extractObservationSummaryValue(
@@ -1947,7 +2282,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1947
2282
  data[dataKey] = vitalValue;
1948
2283
  }
1949
2284
  }
1950
- data[columnTitle] = columnData.text?.div ?? "";
2285
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1951
2286
  }
1952
2287
  }
1953
2288
  isSummaryCreated = true;
@@ -1997,7 +2332,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1997
2332
  for (const obs of observations) {
1998
2333
  html += `
1999
2334
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2000
- <td>${templateUtilities.codeableConcept(obs.code, "display")}</td>
2335
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
2001
2336
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2002
2337
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2003
2338
  <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
@@ -2051,10 +2386,10 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2051
2386
  for (const dus of deviceStatements) {
2052
2387
  html += `
2053
2388
  <tr id="${templateUtilities.narrativeLinkId(dus)}">
2054
- <td>${templateUtilities.renderDevice(dus.device)}</td>
2055
- <td>${dus.status || ""}</td>
2389
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
2390
+ <td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
2056
2391
  <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
2057
- <td>${templateUtilities.renderRecorded(dus.recordedOn, timezone)}</td>
2392
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderRecorded(dus.recordedOn, timezone))}</td>
2058
2393
  </tr>`;
2059
2394
  }
2060
2395
  html += `
@@ -2065,6 +2400,12 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2065
2400
  };
2066
2401
 
2067
2402
  // src/narratives/templates/typescript/DiagnosticResultsTemplate.ts
2403
+ var loincToLabName = {};
2404
+ for (const [labName, loincCodes] of Object.entries(LAB_LOINC_MAP)) {
2405
+ for (const code of loincCodes) {
2406
+ loincToLabName[code] = labName;
2407
+ }
2408
+ }
2068
2409
  var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2069
2410
  /**
2070
2411
  * Generate HTML narrative for Diagnostic Results
@@ -2184,149 +2525,150 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2184
2525
  * Helper function to extract observation field data
2185
2526
  * @param column - Column data from the summary
2186
2527
  * @param targetData - Record to populate with extracted data
2528
+ * @param templateUtilities - Instance of TemplateUtilities for utility functions
2187
2529
  */
2188
- extractSummaryObservationFields(column, targetData) {
2530
+ extractSummaryObservationFields(column, targetData, templateUtilities) {
2189
2531
  switch (column.title) {
2190
2532
  case "Labs Name":
2191
- targetData["code"] = column.text?.div ?? "";
2533
+ targetData["code"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2192
2534
  break;
2193
2535
  case "effectiveDateTime":
2194
- targetData["effectiveDateTime"] = column.text?.div ?? "";
2536
+ targetData["effectiveDateTime"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2195
2537
  break;
2196
2538
  case "effectivePeriod.start":
2197
- targetData["effectivePeriodStart"] = column.text?.div ?? "";
2539
+ targetData["effectivePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2198
2540
  break;
2199
2541
  case "effectivePeriod.end":
2200
- targetData["effectivePeriodEnd"] = column.text?.div ?? "";
2542
+ targetData["effectivePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2201
2543
  break;
2202
2544
  // valueQuantity
2203
2545
  case "valueQuantity.value":
2204
- targetData["value"] = column.text?.div ?? "";
2546
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2205
2547
  targetData["valueType"] = "valueQuantity";
2206
2548
  break;
2207
2549
  case "valueQuantity.unit":
2208
- targetData["unit"] = column.text?.div ?? "";
2550
+ targetData["unit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2209
2551
  break;
2210
2552
  // valueCodeableConcept
2211
2553
  case "valueCodeableConcept.text":
2212
- targetData["value"] = column.text?.div ?? "";
2554
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2213
2555
  targetData["valueType"] = "valueCodeableConcept";
2214
2556
  break;
2215
2557
  case "valueCodeableConcept.coding.display":
2216
2558
  if (!targetData["value"]) {
2217
- targetData["value"] = column.text?.div ?? "";
2559
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2218
2560
  targetData["valueType"] = "valueCodeableConcept";
2219
2561
  }
2220
2562
  break;
2221
2563
  // valueString
2222
2564
  case "valueString":
2223
- targetData["value"] = column.text?.div ?? "";
2565
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2224
2566
  targetData["valueType"] = "valueString";
2225
2567
  break;
2226
2568
  // valueBoolean
2227
2569
  case "valueBoolean":
2228
- targetData["value"] = column.text?.div ?? "";
2570
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2229
2571
  targetData["valueType"] = "valueBoolean";
2230
2572
  break;
2231
2573
  // valueInteger
2232
2574
  case "valueInteger":
2233
- targetData["value"] = column.text?.div ?? "";
2575
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2234
2576
  targetData["valueType"] = "valueInteger";
2235
2577
  break;
2236
2578
  // valueDateTime
2237
2579
  case "valueDateTime":
2238
- targetData["value"] = column.text?.div ?? "";
2580
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2239
2581
  targetData["valueType"] = "valueDateTime";
2240
2582
  break;
2241
2583
  // valuePeriod
2242
2584
  case "valuePeriod.start":
2243
- targetData["valuePeriodStart"] = column.text?.div ?? "";
2585
+ targetData["valuePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2244
2586
  targetData["valueType"] = "valuePeriod";
2245
2587
  break;
2246
2588
  case "valuePeriod.end":
2247
- targetData["valuePeriodEnd"] = column.text?.div ?? "";
2589
+ targetData["valuePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2248
2590
  targetData["valueType"] = "valuePeriod";
2249
2591
  break;
2250
2592
  // valueTime
2251
2593
  case "valueTime":
2252
- targetData["value"] = column.text?.div ?? "";
2594
+ targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2253
2595
  targetData["valueType"] = "valueTime";
2254
2596
  break;
2255
2597
  // valueSampledData
2256
2598
  case "valueSampledData.origin.value":
2257
- targetData["sampledDataOriginValue"] = column.text?.div ?? "";
2599
+ targetData["sampledDataOriginValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2258
2600
  targetData["valueType"] = "valueSampledData";
2259
2601
  break;
2260
2602
  case "valueSampledData.origin.unit":
2261
- targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
2603
+ targetData["sampledDataOriginUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2262
2604
  break;
2263
2605
  case "valueSampledData.period":
2264
- targetData["sampledDataPeriod"] = column.text?.div ?? "";
2606
+ targetData["sampledDataPeriod"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2265
2607
  break;
2266
2608
  case "valueSampledData.factor":
2267
- targetData["sampledDataFactor"] = column.text?.div ?? "";
2609
+ targetData["sampledDataFactor"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2268
2610
  break;
2269
2611
  case "valueSampledData.lowerLimit":
2270
- targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
2612
+ targetData["sampledDataLowerLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2271
2613
  break;
2272
2614
  case "valueSampledData.upperLimit":
2273
- targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
2615
+ targetData["sampledDataUpperLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2274
2616
  break;
2275
2617
  case "valueSampledData.data":
2276
- targetData["sampledDataData"] = column.text?.div ?? "";
2618
+ targetData["sampledDataData"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2277
2619
  break;
2278
2620
  // valueRange
2279
2621
  case "valueRange.low.value":
2280
- targetData["valueRangeLowValue"] = column.text?.div ?? "";
2622
+ targetData["valueRangeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2281
2623
  targetData["valueType"] = "valueRange";
2282
2624
  break;
2283
2625
  case "valueRange.low.unit":
2284
- targetData["valueRangeLowUnit"] = column.text?.div ?? "";
2626
+ targetData["valueRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2285
2627
  break;
2286
2628
  case "valueRange.high.value":
2287
- targetData["valueRangeHighValue"] = column.text?.div ?? "";
2629
+ targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2288
2630
  break;
2289
2631
  case "valueRange.high.unit":
2290
- targetData["valueRangeHighUnit"] = column.text?.div ?? "";
2632
+ targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2291
2633
  break;
2292
2634
  // valueRatio
2293
2635
  case "valueRatio.numerator.value":
2294
- targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
2636
+ targetData["valueRatioNumeratorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2295
2637
  targetData["valueType"] = "valueRatio";
2296
2638
  break;
2297
2639
  case "valueRatio.numerator.unit":
2298
- targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
2640
+ targetData["valueRatioNumeratorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2299
2641
  break;
2300
2642
  case "valueRatio.denominator.value":
2301
- targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
2643
+ targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2302
2644
  break;
2303
2645
  case "valueRatio.denominator.unit":
2304
- targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
2646
+ targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2305
2647
  break;
2306
2648
  // referenceRange
2307
2649
  case "referenceRange.low.value":
2308
- targetData["referenceRangeLow"] = column.text?.div ?? "";
2650
+ targetData["referenceRangeLow"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2309
2651
  break;
2310
2652
  case "referenceRange.low.unit":
2311
- targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
2653
+ targetData["referenceRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2312
2654
  break;
2313
2655
  case "referenceRange.high.value":
2314
- targetData["referenceRangeHigh"] = column.text?.div ?? "";
2656
+ targetData["referenceRangeHigh"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2315
2657
  break;
2316
2658
  case "referenceRange.high.unit":
2317
- targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
2659
+ targetData["referenceRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2318
2660
  break;
2319
2661
  case "referenceRange.age.low.value":
2320
- targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
2662
+ targetData["referenceRangeAgeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2321
2663
  break;
2322
2664
  case "referenceRange.age.low.unit":
2323
- targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
2665
+ targetData["referenceRangeAgeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2324
2666
  break;
2325
2667
  case "referenceRange.age.high.value":
2326
- targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
2668
+ targetData["referenceRangeAgeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2327
2669
  break;
2328
2670
  case "referenceRange.age.high.unit":
2329
- targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
2671
+ targetData["referenceRangeAgeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2330
2672
  break;
2331
2673
  default:
2332
2674
  break;
@@ -2379,28 +2721,28 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2379
2721
  for (const componentSection of columnData.section) {
2380
2722
  const componentData = {};
2381
2723
  for (const nestedColumn of componentSection.section ?? []) {
2382
- this.extractSummaryObservationFields(nestedColumn, componentData);
2724
+ this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2383
2725
  }
2384
2726
  if (Object.keys(componentData).length > 0) {
2385
2727
  components.push(componentData);
2386
2728
  }
2387
2729
  }
2388
2730
  } else {
2389
- this.extractSummaryObservationFields(columnData, data);
2731
+ this.extractSummaryObservationFields(columnData, data, templateUtilities);
2390
2732
  }
2391
2733
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2392
2734
  switch (columnData.title) {
2393
2735
  case "Diagnostic Report Name":
2394
- data["report"] = columnData.text?.div ?? "";
2736
+ data["report"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2395
2737
  break;
2396
2738
  case "Performer":
2397
- data["performer"] = columnData.text?.div ?? "";
2739
+ data["performer"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2398
2740
  break;
2399
2741
  case "Issued Date":
2400
- data["issued"] = columnData.text?.div ?? "";
2742
+ data["issued"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2401
2743
  break;
2402
2744
  case "Status":
2403
- data["status"] = columnData.text?.div ?? "";
2745
+ data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2404
2746
  break;
2405
2747
  default:
2406
2748
  break;
@@ -2425,8 +2767,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2425
2767
  observationhtml += `
2426
2768
  <tr>
2427
2769
  <td>${componentCode}</td>
2428
- <td>${component["formattedValue"] ?? "-"}</td>
2429
- <td>${component["referenceRange"]?.trim() ?? "-"}</td>
2770
+ <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
2771
+ <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
2430
2772
  <td>${date ?? "-"}</td>
2431
2773
  </tr>`;
2432
2774
  }
@@ -2439,8 +2781,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2439
2781
  observationhtml += `
2440
2782
  <tr>
2441
2783
  <td>${data["code"] ?? "-"}</td>
2442
- <td>${data["formattedValue"] ?? "-"}</td>
2443
- <td>${data["referenceRange"]?.trim() ?? "-"}</td>
2784
+ <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
2785
+ <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
2444
2786
  <td>${date ?? "-"}</td>
2445
2787
  </tr>`;
2446
2788
  }
@@ -2491,30 +2833,80 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2491
2833
  const observations = this.getObservations(resources);
2492
2834
  if (observations.length > 0) {
2493
2835
  observations.sort((a, b) => {
2494
- const dateA = a.effectiveDateTime || a.effectivePeriod?.start || a.effectivePeriod?.end;
2495
- const dateB = b.effectiveDateTime || b.effectivePeriod?.start || b.effectivePeriod?.end;
2496
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2836
+ const dateA = this.getObservationDate(a);
2837
+ const dateB = this.getObservationDate(b);
2838
+ return dateA && dateB ? dateB.getTime() - dateA.getTime() : 0;
2497
2839
  });
2840
+ this.filterObservationForLoincCodes(observations);
2498
2841
  html += this.renderObservations(templateUtilities, observations, timezone);
2499
2842
  }
2500
- const diagnosticReports = this.getDiagnosticReports(resources);
2501
- if (diagnosticReports.length > 0) {
2502
- diagnosticReports.sort((a, b) => {
2503
- const dateA = a.issued;
2504
- const dateB = b.issued;
2505
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2506
- });
2507
- html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
2843
+ if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2844
+ const diagnosticReports = this.getDiagnosticReports(resources);
2845
+ if (diagnosticReports.length > 0) {
2846
+ diagnosticReports.sort((a, b) => {
2847
+ const dateA = a.issued;
2848
+ const dateB = b.issued;
2849
+ return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2850
+ });
2851
+ html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
2852
+ }
2508
2853
  }
2509
2854
  return html;
2510
2855
  }
2856
+ static filterObservationForLoincCodes(observations) {
2857
+ const labsAdded = /* @__PURE__ */ new Set();
2858
+ const filteredObservations = [];
2859
+ for (const obs of observations) {
2860
+ const loincCode = this.getObservationLoincCode(obs);
2861
+ if (loincCode && loincToLabName[loincCode]) {
2862
+ const labName = loincToLabName[loincCode];
2863
+ if (!labsAdded.has(labName)) {
2864
+ labsAdded.add(labName);
2865
+ filteredObservations.push(obs);
2866
+ }
2867
+ }
2868
+ }
2869
+ observations.length = 0;
2870
+ observations.push(...filteredObservations);
2871
+ }
2872
+ static getObservationLoincCode(obs) {
2873
+ if (obs.code && obs.code.coding) {
2874
+ for (const coding of obs.code.coding) {
2875
+ if (coding.system && coding.system.toLowerCase().includes("loinc") && coding.code) {
2876
+ return coding.code;
2877
+ }
2878
+ }
2879
+ }
2880
+ return void 0;
2881
+ }
2882
+ static getObservationDate(obs) {
2883
+ let obsDate = void 0;
2884
+ if (obs.effectiveDateTime) {
2885
+ obsDate = new Date(obs.effectiveDateTime);
2886
+ } else if (obs.effectivePeriod) {
2887
+ if (obs.effectivePeriod.start) {
2888
+ obsDate = new Date(obs.effectivePeriod.start);
2889
+ } else if (obs.effectivePeriod.end) {
2890
+ obsDate = new Date(obs.effectivePeriod.end);
2891
+ }
2892
+ }
2893
+ return obsDate;
2894
+ }
2511
2895
  /**
2512
2896
  * Get all Observation resources from the resource array
2513
2897
  * @param resources - FHIR resources array
2514
2898
  * @returns Array of Observation resources
2515
2899
  */
2516
2900
  static getObservations(resources) {
2517
- return resources.filter((resourceItem) => resourceItem.resourceType === "Observation").map((resourceItem) => resourceItem);
2901
+ return resources.filter((resourceItem) => {
2902
+ if (resourceItem.resourceType === "Observation") {
2903
+ const obsDate = this.getObservationDate(resourceItem);
2904
+ if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
2905
+ return true;
2906
+ }
2907
+ }
2908
+ return false;
2909
+ }).map((resourceItem) => resourceItem);
2518
2910
  }
2519
2911
  /**
2520
2912
  * Get all DiagnosticReport resources from the resource array
@@ -2532,34 +2924,32 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2532
2924
  * @returns HTML string for rendering
2533
2925
  */
2534
2926
  static renderObservations(templateUtilities, observations, timezone) {
2535
- let html = `
2536
- <h3>Observations</h3>
2927
+ let html = "";
2928
+ if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2929
+ html += `
2930
+ <h3>Observations</h3>`;
2931
+ }
2932
+ html += `
2537
2933
  <table>
2538
2934
  <thead>
2539
2935
  <tr>
2540
2936
  <th>Code</th>
2541
2937
  <th>Result</th>
2542
- <th>Unit</th>
2543
- <th>Interpretation</th>
2544
2938
  <th>Reference Range</th>
2545
- <th>Comments</th>
2546
2939
  <th>Date</th>
2547
2940
  </tr>
2548
2941
  </thead>
2549
2942
  <tbody>`;
2550
2943
  const observationAdded = /* @__PURE__ */ new Set();
2551
2944
  for (const obs of observations) {
2552
- const obsCode = templateUtilities.codeableConcept(obs.code);
2945
+ const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
2553
2946
  if (!observationAdded.has(obsCode)) {
2554
2947
  observationAdded.add(obsCode);
2555
2948
  html += `
2556
2949
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2557
2950
  <td>${obsCode}</td>
2558
2951
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2559
- <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2560
- <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
2561
2952
  <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
2562
- <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
2563
2953
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
2564
2954
  </tr>`;
2565
2955
  }
@@ -2591,7 +2981,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2591
2981
  <tbody>`;
2592
2982
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2593
2983
  for (const report of reports) {
2594
- const reportName = templateUtilities.codeableConcept(report.code);
2984
+ const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
2595
2985
  if (!diagnosticReportAdded.has(reportName)) {
2596
2986
  diagnosticReportAdded.add(reportName);
2597
2987
  let resultCount = "";
@@ -2705,7 +3095,7 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
2705
3095
  const proc = resourceItem;
2706
3096
  html += `
2707
3097
  <tr id="${templateUtilities.narrativeLinkId(proc)}">
2708
- <td>${templateUtilities.codeableConcept(proc.code, "display")}</td>
3098
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
2709
3099
  <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
2710
3100
  <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
2711
3101
  </tr>`;
@@ -2757,7 +3147,7 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
2757
3147
  for (const obs of observations) {
2758
3148
  html += `
2759
3149
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2760
- <td>${templateUtilities.codeableConcept(obs.code)}</td>
3150
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
2761
3151
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2762
3152
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2763
3153
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
@@ -2802,7 +3192,7 @@ var PastHistoryOfIllnessTemplate = class {
2802
3192
  <tbody>`;
2803
3193
  const addedConditionCodes = /* @__PURE__ */ new Set();
2804
3194
  for (const cond of resolvedConditions) {
2805
- const conditionCode = templateUtilities.codeableConcept(cond.code);
3195
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2806
3196
  if (!addedConditionCodes.has(conditionCode)) {
2807
3197
  addedConditionCodes.add(conditionCode);
2808
3198
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -2888,7 +3278,7 @@ var PlanOfCareTemplate = class {
2888
3278
  const data = {};
2889
3279
  for (const columnData of rowData.section ?? []) {
2890
3280
  if (columnData.title) {
2891
- data[columnData.title] = columnData.text?.div ?? "";
3281
+ data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2892
3282
  }
2893
3283
  }
2894
3284
  if (data["status"] !== "active") {
@@ -2964,7 +3354,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2964
3354
  <tbody>`;
2965
3355
  const addedConditionCodes = /* @__PURE__ */ new Set();
2966
3356
  for (const cond of activeConditions) {
2967
- const conditionCode = templateUtilities.codeableConcept(cond.code);
3357
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2968
3358
  if (!addedConditionCodes.has(conditionCode)) {
2969
3359
  addedConditionCodes.add(conditionCode);
2970
3360
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
@@ -3009,7 +3399,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3009
3399
  if (impression.finding && impression.finding.length > 0) {
3010
3400
  findingsHtml = "<ul>";
3011
3401
  for (const finding of impression.finding) {
3012
- 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) : "";
3013
3403
  const cause = finding.basis || "";
3014
3404
  findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
3015
3405
  }
@@ -3070,9 +3460,9 @@ var PregnancyTemplate = class _PregnancyTemplate {
3070
3460
  const obs = resource;
3071
3461
  html += `
3072
3462
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
3073
- <td>${templateUtilities.extractPregnancyStatus(obs)}</td>
3463
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
3074
3464
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3075
- <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>
3076
3466
  </tr>`;
3077
3467
  }
3078
3468
  html += `
@@ -3122,7 +3512,7 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3122
3512
  const consent = resourceItem;
3123
3513
  html += `
3124
3514
  <tr id="${templateUtilities.narrativeLinkId(consent)}">
3125
- <td>${templateUtilities.codeableConcept(consent.scope, "display")}</td>
3515
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
3126
3516
  <td>${consent.status || ""}</td>
3127
3517
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
3128
3518
  <td>${consent.dateTime || ""}</td>
@@ -3143,16 +3533,18 @@ var TypeScriptTemplateMapper = class {
3143
3533
  * @param resources - FHIR resources
3144
3534
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3145
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
3146
3537
  * @returns HTML string for rendering
3147
3538
  */
3148
- static generateNarrative(section, resources, timezone, useSectionSummary = false) {
3539
+ static generateNarrative(section, resources, timezone, useSectionSummary = false, now) {
3149
3540
  const templateClass = this.sectionToTemplate[section];
3150
3541
  if (!templateClass) {
3151
3542
  throw new Error(`No template found for section: ${section}`);
3152
3543
  }
3153
3544
  return useSectionSummary ? templateClass.generateSummaryNarrative(
3154
3545
  resources,
3155
- timezone
3546
+ timezone,
3547
+ now
3156
3548
  ) : templateClass.generateNarrative(resources, timezone);
3157
3549
  }
3158
3550
  };
@@ -3210,14 +3602,15 @@ var NarrativeGenerator = class {
3210
3602
  * @param resources - Array of domain resources
3211
3603
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3212
3604
  * @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
3605
+ * @param now - Optional date parameter
3213
3606
  * @returns Generated HTML content or undefined if no resources
3214
3607
  */
3215
- static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false) {
3608
+ static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false, now) {
3216
3609
  if (!resources || resources.length === 0) {
3217
3610
  return void 0;
3218
3611
  }
3219
3612
  try {
3220
- const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary);
3613
+ const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary, now);
3221
3614
  if (!content) {
3222
3615
  return void 0;
3223
3616
  }
@@ -3239,8 +3632,8 @@ var NarrativeGenerator = class {
3239
3632
  const options = aggressive ? AGGRESSIVE_MINIFY_OPTIONS : DEFAULT_MINIFY_OPTIONS;
3240
3633
  return await htmlMinify(html, options);
3241
3634
  } catch (error) {
3242
- console.warn("HTML minification failed", error);
3243
- return html;
3635
+ console.warn("HTML minification failed", error, html);
3636
+ return `${error instanceof Error ? error.message : String(error)}`;
3244
3637
  }
3245
3638
  }
3246
3639
  /**
@@ -3269,10 +3662,11 @@ var NarrativeGenerator = class {
3269
3662
  * @param timezone - Optional timezone to use for date formatting
3270
3663
  * @param minify - Whether to minify the HTML content (default: true)
3271
3664
  * @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
3665
+ * @param now - Optional date parameter
3272
3666
  * @returns Promise that resolves to a FHIR Narrative object or undefined if no resources
3273
3667
  */
3274
- static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false) {
3275
- 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);
3276
3670
  if (!content) {
3277
3671
  return void 0;
3278
3672
  }
@@ -3449,8 +3843,9 @@ var ComprehensiveIPSCompositionBuilder = class {
3449
3843
  * @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
3450
3844
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3451
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())
3452
3847
  */
3453
- async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId) {
3848
+ async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
3454
3849
  if (baseUrl.endsWith("/")) {
3455
3850
  baseUrl = baseUrl.slice(0, -1);
3456
3851
  }
@@ -3477,20 +3872,22 @@ var ComprehensiveIPSCompositionBuilder = class {
3477
3872
  // Assuming patient is also a practitioner for simplicity
3478
3873
  display: authorOrganizationName
3479
3874
  }],
3480
- date: (/* @__PURE__ */ new Date()).toISOString(),
3875
+ date: (now || /* @__PURE__ */ new Date()).toISOString(),
3481
3876
  title: "International Patient Summary",
3482
3877
  section: this.sections,
3483
3878
  text: await NarrativeGenerator.generateNarrativeAsync(
3484
3879
  "Patient" /* PATIENT */,
3485
3880
  this.patients,
3486
3881
  timezone,
3487
- true
3882
+ true,
3883
+ false,
3884
+ now
3488
3885
  )
3489
3886
  };
3490
3887
  const bundle = {
3491
3888
  resourceType: "Bundle",
3492
3889
  type: "document",
3493
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3890
+ timestamp: (now || /* @__PURE__ */ new Date()).toISOString(),
3494
3891
  identifier: {
3495
3892
  "system": "urn:ietf:rfc:3986",
3496
3893
  "value": "urn:uuid:4dcfd353-49fd-4ab0-b521-c8d57ced74d6"