@imranq2/fhirpatientsummary 1.0.25 → 1.0.27

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.
Files changed (3) hide show
  1. package/dist/index.cjs +512 -108
  2. package/dist/index.js +512 -108
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -115,6 +115,8 @@ var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
115
115
  "Diastolic Blood Pressure": "valueRatio.denominator.value",
116
116
  "Default": "valueString"
117
117
  };
118
+ var RESULT_SUMMARY_OBSERVATION_CATEGORIES = ["laboratory", "Lab", "LAB"];
119
+ var RESULT_SUMMARY_OBSERVATION_DATE_FILTER = 2 * 365 * 24 * 60 * 60 * 1e3;
118
120
  var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
119
121
 
120
122
  // src/structures/ips_section_resource_map.ts
@@ -134,7 +136,7 @@ var IPSSectionResourceFilters = {
134
136
  // Includes DeviceUseStatement. Device is needed for linked device details
135
137
  ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
136
138
  // Only include finalized diagnostic reports
137
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => ["DiagnosticReport", "Observation"].includes(resource.resourceType) && resource.status === "final",
139
+ ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
138
140
  // Only include completed procedures
139
141
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
140
142
  // Only include social history Observations
@@ -142,7 +144,7 @@ var IPSSectionResourceFilters = {
142
144
  // Only include pregnancy history Observations
143
145
  ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS).includes(c.code)) || resource.valueCodeableConcept?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME).includes(c.code))),
144
146
  // Only include Conditions or completed ClinicalImpressions
145
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
147
+ ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
146
148
  // Only include resolved medical history Conditions
147
149
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
148
150
  // Only include active care plans
@@ -156,7 +158,7 @@ var IPSSectionSummaryCompositionFilter = {
156
158
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
157
159
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
158
160
  ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
159
- ["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)),
161
+ // [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)),
160
162
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
161
163
  };
162
164
  var IPSSectionResourceHelper = class {
@@ -620,9 +622,18 @@ var TemplateUtilities = class {
620
622
  }
621
623
  return status;
622
624
  }
625
+ formatFloatValue(value) {
626
+ if (typeof value === "number") {
627
+ return value.toFixed(2).replace(/\.?0+$/, "");
628
+ } else if (typeof value === "string" && !isNaN(Number(value))) {
629
+ return parseFloat(value).toFixed(2).replace(/\.?0+$/, "");
630
+ }
631
+ return value;
632
+ }
623
633
  extractObservationSummaryValue(data, timezone) {
624
634
  if (data["valueQuantity.value"] !== void 0) {
625
- const value = data["valueQuantity.value"];
635
+ let value = data["valueQuantity.value"];
636
+ value = this.formatFloatValue(value);
626
637
  const unit = data["valueQuantity.unit"];
627
638
  return unit ? `${value} ${unit}` : `${value}`;
628
639
  }
@@ -639,7 +650,9 @@ var TemplateUtilities = class {
639
650
  return String(data["valueBoolean"]);
640
651
  }
641
652
  if (data["valueInteger"] !== void 0) {
642
- return String(data["valueInteger"]);
653
+ let value = String(data["valueInteger"]);
654
+ value = this.formatFloatValue(value);
655
+ return value;
643
656
  }
644
657
  if (data["valueDateTime"] !== void 0) {
645
658
  return this.renderTime(data["valueDateTime"], timezone);
@@ -659,7 +672,8 @@ var TemplateUtilities = class {
659
672
  return this.renderTime(data["valueTime"], timezone);
660
673
  }
661
674
  if (data["valueSampledData.origin.value"] !== void 0 || data["valueSampledData.origin.unit"] !== void 0) {
662
- const originValue = data["valueSampledData.origin.value"];
675
+ let originValue = data["valueSampledData.origin.value"];
676
+ originValue = this.formatFloatValue(originValue);
663
677
  const originUnit = data["valueSampledData.origin.unit"];
664
678
  let result = "";
665
679
  if (originValue !== void 0 && originUnit !== void 0) {
@@ -669,10 +683,10 @@ var TemplateUtilities = class {
669
683
  } else if (originUnit !== void 0) {
670
684
  result = `${originUnit}`;
671
685
  }
672
- const period = data["valueSampledData.period"];
673
- const factor = data["valueSampledData.factor"];
674
- const lowerLimit = data["valueSampledData.lowerLimit"];
675
- const upperLimit = data["valueSampledData.upperLimit"];
686
+ const period = this.formatFloatValue(data["valueSampledData.period"]);
687
+ const factor = this.formatFloatValue(data["valueSampledData.factor"]);
688
+ const lowerLimit = this.formatFloatValue(data["valueSampledData.lowerLimit"]);
689
+ const upperLimit = this.formatFloatValue(data["valueSampledData.upperLimit"]);
676
690
  const sampledData = data["valueSampledData.data"];
677
691
  const extras = [];
678
692
  if (period !== void 0) extras.push(`period: ${period}`);
@@ -708,14 +722,14 @@ var TemplateUtilities = class {
708
722
  if (data["valueRatio.numerator.value"] !== void 0 || data["valueRatio.denominator.value"] !== void 0) {
709
723
  let ratio = "";
710
724
  if (data["valueRatio.numerator.value"] !== void 0) {
711
- ratio += `${data["valueRatio.numerator.value"]}`;
725
+ ratio += `${this.formatFloatValue(data["valueRatio.numerator.value"])}`;
712
726
  if (data["valueRatio.numerator.unit"] !== void 0) {
713
727
  ratio += ` ${data["valueRatio.numerator.unit"]}`;
714
728
  }
715
729
  }
716
730
  if (data["valueRatio.denominator.value"] !== void 0) {
717
731
  ratio += " / ";
718
- ratio += `${data["valueRatio.denominator.value"]}`;
732
+ ratio += `${this.formatFloatValue(data["valueRatio.denominator.value"])}`;
719
733
  if (data["valueRatio.denominator.unit"] !== void 0) {
720
734
  ratio += ` ${data["valueRatio.denominator.unit"]}`;
721
735
  }
@@ -969,6 +983,313 @@ var TemplateUtilities = class {
969
983
 
970
984
  // src/constants.ts
971
985
  var ADDRESS_SIMILARITY_THRESHOLD = 70;
986
+ var LAB_LOINC_MAP = {
987
+ // METABOLIC PANELS (VERY COMMONLY ORDERED)
988
+ "Basic Metabolic Panel": [
989
+ "24321-2",
990
+ // Basic metabolic 2000 panel - Serum or Plasma
991
+ "51990-0"
992
+ // Basic metabolic 2000 panel - Blood
993
+ ],
994
+ "Comprehensive Metabolic Panel": [
995
+ "24323-8"
996
+ // Comprehensive metabolic 2000 panel - Serum or Plasma
997
+ ],
998
+ // CBC COMPONENTS
999
+ Hemoglobin: [
1000
+ "718-7"
1001
+ // Hemoglobin [Mass/volume] in Blood
1002
+ ],
1003
+ Hematocrit: [
1004
+ "4544-3"
1005
+ // Hematocrit [Volume Fraction] of Blood by Automated count
1006
+ ],
1007
+ "White Blood Cell Count": [
1008
+ "6690-2"
1009
+ // Leukocytes [///volume] in Blood by Automated count
1010
+ ],
1011
+ "Platelet Count": [
1012
+ "777-3"
1013
+ // Platelets [///volume] in Blood by Automated count
1014
+ ],
1015
+ "Complete Blood Count": [
1016
+ "58410-2",
1017
+ // CBC panel - Blood by Automated count
1018
+ "57021-8",
1019
+ // CBC W Auto Differential panel - Blood
1020
+ "69738-3"
1021
+ // CBC W Auto Differential panel - Blood by Automated count
1022
+ ],
1023
+ // CHEMISTRY - GLUCOSE
1024
+ Glucose: [
1025
+ "2345-7",
1026
+ // Glucose [Mass/volume] in Serum or Plasma
1027
+ "1558-6",
1028
+ // Fasting glucose [Mass/volume] in Serum or Plasma
1029
+ "2339-0"
1030
+ // Glucose [Mass/volume] in Blood
1031
+ ],
1032
+ // RENAL FUNCTION
1033
+ Creatinine: [
1034
+ "2160-0"
1035
+ // Creatinine [Mass/volume] in Serum or Plasma
1036
+ ],
1037
+ "Blood Urea Nitrogen": [
1038
+ "3094-0",
1039
+ // Urea nitrogen [Mass/volume] in Serum or Plasma
1040
+ "6299-2"
1041
+ // Urea nitrogen [Mass/volume] in Blood
1042
+ ],
1043
+ // ELECTROLYTES
1044
+ Sodium: [
1045
+ "2951-2"
1046
+ // Sodium [Moles/volume] in Serum or Plasma
1047
+ ],
1048
+ Potassium: [
1049
+ "2823-3"
1050
+ // Potassium [Moles/volume] in Serum or Plasma
1051
+ ],
1052
+ Chloride: [
1053
+ "2075-0"
1054
+ // Chloride [Moles/volume] in Serum or Plasma
1055
+ ],
1056
+ Calcium: [
1057
+ "17861-6",
1058
+ // Calcium [Mass/volume] in Serum or Plasma
1059
+ "1994-3"
1060
+ // Calcium.ionized [Moles/volume] in Serum or Plasma
1061
+ ],
1062
+ Magnesium: [
1063
+ "19123-9"
1064
+ // Magnesium [Mass/volume] in Serum or Plasma
1065
+ ],
1066
+ Phosphate: [
1067
+ "14879-1"
1068
+ // Phosphate [Mass/volume] in Serum or Plasma
1069
+ ],
1070
+ // PROTEINS
1071
+ Albumin: [
1072
+ "1751-7"
1073
+ // Albumin [Mass/volume] in Serum or Plasma
1074
+ ],
1075
+ "Total Protein": [
1076
+ "2885-2"
1077
+ // Protein [Mass/volume] in Serum or Plasma
1078
+ ],
1079
+ // LIVER FUNCTION
1080
+ Bilirubin: [
1081
+ "1975-2",
1082
+ // Bilirubin.total [Mass/volume] in Serum or Plasma
1083
+ "1968-7",
1084
+ // Bilirubin.direct [Mass/volume] in Serum or Plasma
1085
+ "1971-1"
1086
+ // Bilirubin.indirect [Mass/volume] in Serum or Plasma
1087
+ ],
1088
+ "Alkaline Phosphatase": [
1089
+ "6768-6"
1090
+ // Alkaline phosphatase [Enzymatic activity/volume] in Serum or Plasma
1091
+ ],
1092
+ AST: [
1093
+ "1920-8"
1094
+ // Aspartate aminotransferase [Enzymatic activity/volume] in Serum or Plasma
1095
+ ],
1096
+ ALT: [
1097
+ "1742-6"
1098
+ // Alanine aminotransferase [Enzymatic activity/volume] in Serum or Plasma
1099
+ ],
1100
+ GGT: [
1101
+ "2324-2"
1102
+ // Gamma glutamyl transferase [Enzymatic activity/volume] in Serum or Plasma
1103
+ ],
1104
+ // ENDOCRINE
1105
+ TSH: [
1106
+ "3016-3"
1107
+ // Thyrotropin [Units/volume] in Serum or Plasma
1108
+ ],
1109
+ "Free T4": [
1110
+ "3024-7"
1111
+ // Thyroxine (T4) free [Mass/volume] in Serum or Plasma
1112
+ ],
1113
+ "Total T4": [
1114
+ "3026-2"
1115
+ // Thyroxine (T4) [Mass/volume] in Serum or Plasma
1116
+ ],
1117
+ "Free T3": [
1118
+ "3051-0"
1119
+ // Triiodothyronine (T3) free [Mass/volume] in Serum or Plasma
1120
+ ],
1121
+ "Total T3": [
1122
+ "3053-6"
1123
+ // Triiodothyronine (T3) [Mass/volume] in Serum or Plasma
1124
+ ],
1125
+ HbA1c: [
1126
+ "4548-4",
1127
+ // Hemoglobin A1c/Hemoglobin.total in Blood
1128
+ "17856-6"
1129
+ // Hemoglobin A1c/Hemoglobin.total in Blood by HPLC
1130
+ ],
1131
+ // LIPID PANEL
1132
+ "Lipid Panel": [
1133
+ "24331-1",
1134
+ // Lipid 1996 panel - Serum or Plasma
1135
+ "57698-3"
1136
+ // Lipid panel with direct LDL - Serum or Plasma
1137
+ ],
1138
+ "Cholesterol Total": [
1139
+ "2093-3"
1140
+ // Cholesterol [Mass/volume] in Serum or Plasma
1141
+ ],
1142
+ "HDL Cholesterol": [
1143
+ "2085-9"
1144
+ // Cholesterol in HDL [Mass/volume] in Serum or Plasma
1145
+ ],
1146
+ "LDL Cholesterol": [
1147
+ "13457-7",
1148
+ // Cholesterol in LDL [Mass/volume] in Serum or Plasma by calculation
1149
+ "18262-6"
1150
+ // Cholesterol in LDL [Mass/volume] in Serum or Plasma by Direct assay
1151
+ ],
1152
+ Triglycerides: [
1153
+ "2571-8"
1154
+ // Triglyceride [Mass/volume] in Serum or Plasma
1155
+ ],
1156
+ // COAGULATION STUDIES (COMMONLY MISSING!)
1157
+ PT: [
1158
+ "5902-2"
1159
+ // Prothrombin time (PT)
1160
+ ],
1161
+ INR: [
1162
+ "6301-6"
1163
+ // INR in Platelet poor plasma by Coagulation assay
1164
+ ],
1165
+ PTT: [
1166
+ "3173-2",
1167
+ // aPTT in Blood by Coagulation assay
1168
+ "14979-9"
1169
+ // aPTT in Platelet poor plasma by Coagulation assay
1170
+ ],
1171
+ Fibrinogen: [
1172
+ "3255-7"
1173
+ // Fibrinogen [Mass/volume] in Platelet poor plasma by Coagulation assay
1174
+ ],
1175
+ "D-Dimer": [
1176
+ "48065-7",
1177
+ // D-dimer FEU [Mass/volume] in Platelet poor plasma
1178
+ "48066-5"
1179
+ // D-dimer DDU [Mass/volume] in Platelet poor plasma
1180
+ ],
1181
+ // CARDIAC MARKERS (CRITICAL FOR ER!)
1182
+ "Troponin I": [
1183
+ "10839-9",
1184
+ // Troponin I.cardiac [Mass/volume] in Serum or Plasma
1185
+ "42757-5",
1186
+ // Troponin I.cardiac [Mass/volume] in Blood
1187
+ "89579-7"
1188
+ // Troponin I.cardiac [Mass/volume] in Serum or Plasma by High sensitivity method
1189
+ ],
1190
+ "Troponin T": [
1191
+ "6598-7",
1192
+ // Troponin T.cardiac [Mass/volume] in Serum or Plasma
1193
+ "48425-3"
1194
+ // Troponin T.cardiac [Mass/volume] in Serum or Plasma by High sensitivity method
1195
+ ],
1196
+ BNP: [
1197
+ "30934-4"
1198
+ // BNP [Mass/volume] in Serum or Plasma
1199
+ ],
1200
+ "NT-proBNP": [
1201
+ "33762-6"
1202
+ // NT-proBNP [Mass/volume] in Serum or Plasma
1203
+ ],
1204
+ "CK-MB": [
1205
+ "13969-1"
1206
+ // Creatine kinase.MB [Mass/volume] in Serum or Plasma
1207
+ ],
1208
+ // INFLAMMATORY MARKERS
1209
+ CRP: [
1210
+ "1988-5",
1211
+ // C reactive protein [Mass/volume] in Serum or Plasma
1212
+ "30522-7"
1213
+ // C reactive protein [Mass/volume] in Serum or Plasma by High sensitivity method
1214
+ ],
1215
+ ESR: [
1216
+ "30341-2",
1217
+ // Erythrocyte sedimentation rate by Westergren method
1218
+ "4537-7"
1219
+ // Erythrocyte sedimentation rate
1220
+ ],
1221
+ // VITAMINS & MINERALS (VERY HIGH VOLUME!)
1222
+ "Vitamin D": [
1223
+ "1990-1",
1224
+ // Vitamin D [Mass/volume] in Serum or Plasma (obsolete, but still used)
1225
+ "14635-7",
1226
+ // 25-Hydroxyvitamin D3 [Mass/volume] in Serum or Plasma
1227
+ "62292-8"
1228
+ // 25-Hydroxyvitamin D2+D3 [Mass/volume] in Serum or Plasma
1229
+ ],
1230
+ "Vitamin B12": [
1231
+ "2132-9"
1232
+ // Cobalamin (Vitamin B12) [Mass/volume] in Serum or Plasma
1233
+ ],
1234
+ Folate: [
1235
+ "2284-8",
1236
+ // Folate [Mass/volume] in Serum or Plasma
1237
+ "15152-2"
1238
+ // Folate [Mass/volume] in Red Blood Cells
1239
+ ],
1240
+ Iron: [
1241
+ "2498-4"
1242
+ // Iron [Mass/volume] in Serum or Plasma
1243
+ ],
1244
+ Ferritin: [
1245
+ "2276-4"
1246
+ // Ferritin [Mass/volume] in Serum or Plasma
1247
+ ],
1248
+ TIBC: [
1249
+ "2500-7"
1250
+ // Iron binding capacity [Mass/volume] in Serum or Plasma
1251
+ ],
1252
+ // OTHER COMMON TESTS
1253
+ PSA: [
1254
+ "2857-1",
1255
+ // Prostate specific Ag [Mass/volume] in Serum or Plasma
1256
+ "10886-0"
1257
+ // Prostate specific Ag Free [Mass/volume] in Serum or Plasma
1258
+ ],
1259
+ "Uric Acid": [
1260
+ "3084-1"
1261
+ // Urate [Mass/volume] in Serum or Plasma
1262
+ ],
1263
+ LDH: [
1264
+ "2532-0",
1265
+ // Lactate dehydrogenase [Enzymatic activity/volume] in Serum or Plasma
1266
+ "14804-9"
1267
+ // Lactate dehydrogenase [Enzymatic activity/volume] in Serum or Plasma by Lactate to pyruvate reaction
1268
+ ],
1269
+ Amylase: [
1270
+ "1798-8"
1271
+ // Amylase [Enzymatic activity/volume] in Serum or Plasma
1272
+ ],
1273
+ Lipase: [
1274
+ "3040-3"
1275
+ // Lipase [Enzymatic activity/volume] in Serum or Plasma
1276
+ ],
1277
+ hCG: [
1278
+ "21198-7",
1279
+ // Choriogonadotropin.beta subunit [Units/volume] in Serum or Plasma
1280
+ "2118-8",
1281
+ // Choriogonadotropin (pregnancy test) [Presence] in Serum or Plasma
1282
+ "2106-3"
1283
+ // Choriogonadotropin (pregnancy test) [Presence] in Urine
1284
+ ],
1285
+ // URINALYSIS
1286
+ Urinalysis: [
1287
+ "24357-6",
1288
+ // Urinalysis macro (dipstick) panel - Urine
1289
+ "24356-8"
1290
+ // Urinalysis complete panel - Urine
1291
+ ]
1292
+ };
972
1293
 
973
1294
  // src/narratives/templates/typescript/PatientTemplate.ts
974
1295
  var PatientTemplate = class _PatientTemplate {
@@ -1887,8 +2208,8 @@ var ProblemListTemplate = class _ProblemListTemplate {
1887
2208
  let html = ``;
1888
2209
  const activeConditions = resources.map((entry) => entry) || [];
1889
2210
  activeConditions.sort((a, b) => {
1890
- const dateA = a.onsetDateTime ? new Date(a.onsetDateTime).getTime() : 0;
1891
- const dateB = b.onsetDateTime ? new Date(b.onsetDateTime).getTime() : 0;
2211
+ const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
2212
+ const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
1892
2213
  return dateB - dateA;
1893
2214
  });
1894
2215
  html += `
@@ -1901,12 +2222,17 @@ var ProblemListTemplate = class _ProblemListTemplate {
1901
2222
  </tr>
1902
2223
  </thead>
1903
2224
  <tbody>`;
2225
+ const addedConditionCodes = /* @__PURE__ */ new Set();
1904
2226
  for (const cond of activeConditions) {
1905
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
1906
- <td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
1907
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
1908
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
1909
- </tr>`;
2227
+ const conditionCode = templateUtilities.codeableConcept(cond.code);
2228
+ if (!addedConditionCodes.has(conditionCode)) {
2229
+ addedConditionCodes.add(conditionCode);
2230
+ html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2231
+ <td class="Name">${conditionCode}</td>
2232
+ <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2233
+ <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2234
+ </tr>`;
2235
+ }
1910
2236
  }
1911
2237
  html += `</tbody>
1912
2238
  </table>`;
@@ -2088,6 +2414,12 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2088
2414
  };
2089
2415
 
2090
2416
  // src/narratives/templates/typescript/DiagnosticResultsTemplate.ts
2417
+ var loincToLabName = {};
2418
+ for (const [labName, loincCodes] of Object.entries(LAB_LOINC_MAP)) {
2419
+ for (const code of loincCodes) {
2420
+ loincToLabName[code] = labName;
2421
+ }
2422
+ }
2091
2423
  var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2092
2424
  /**
2093
2425
  * Generate HTML narrative for Diagnostic Results
@@ -2390,8 +2722,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2390
2722
  </tr>
2391
2723
  </thead>
2392
2724
  <tbody>`;
2393
- let observationExists = false;
2394
- let diagnosticReportExists = false;
2725
+ const observationAdded = /* @__PURE__ */ new Set();
2726
+ const diagnosticReportAdded = /* @__PURE__ */ new Set();
2395
2727
  for (const resourceItem of resources) {
2396
2728
  for (const rowData of resourceItem.section ?? []) {
2397
2729
  const data = {};
@@ -2431,7 +2763,6 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2431
2763
  }
2432
2764
  }
2433
2765
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2434
- observationExists = true;
2435
2766
  let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
2436
2767
  if (!date && data["effectivePeriodStart"]) {
2437
2768
  date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
@@ -2440,48 +2771,59 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2440
2771
  }
2441
2772
  }
2442
2773
  if (components.length > 0) {
2443
- const groupName = data["code"] ?? "";
2774
+ const groupName = data["code"] ? `${data["code"]} - ` : "";
2444
2775
  for (const component of components) {
2445
- this.formatSummaryObservationData(component);
2446
- observationhtml += `
2447
- <tr>
2448
- <td>${groupName ? groupName + " - " : ""}${component["code"] ?? "-"}</td>
2449
- <td>${component["formattedValue"] ?? "-"}</td>
2450
- <td>${component["referenceRange"]?.trim() ?? "-"}</td>
2451
- <td>${date ?? "-"}</td>
2452
- </tr>`;
2776
+ const componentCode = `${groupName}${component["code"] ?? ""}`;
2777
+ if (componentCode && !observationAdded.has(componentCode)) {
2778
+ observationAdded.add(componentCode);
2779
+ this.formatSummaryObservationData(component);
2780
+ observationhtml += `
2781
+ <tr>
2782
+ <td>${componentCode}</td>
2783
+ <td>${component["formattedValue"] ?? "-"}</td>
2784
+ <td>${component["referenceRange"]?.trim() ?? "-"}</td>
2785
+ <td>${date ?? "-"}</td>
2786
+ </tr>`;
2787
+ }
2453
2788
  }
2454
2789
  } else {
2455
- this.formatSummaryObservationData(data);
2456
- observationhtml += `
2457
- <tr>
2458
- <td>${data["code"] ?? "-"}</td>
2459
- <td>${data["formattedValue"] ?? "-"}</td>
2460
- <td>${data["referenceRange"]?.trim() ?? "-"}</td>
2461
- <td>${date ?? "-"}</td>
2462
- </tr>`;
2790
+ const code = data["code"] ?? "";
2791
+ if (code && !observationAdded.has(code)) {
2792
+ observationAdded.add(code);
2793
+ this.formatSummaryObservationData(data);
2794
+ observationhtml += `
2795
+ <tr>
2796
+ <td>${data["code"] ?? "-"}</td>
2797
+ <td>${data["formattedValue"] ?? "-"}</td>
2798
+ <td>${data["referenceRange"]?.trim() ?? "-"}</td>
2799
+ <td>${date ?? "-"}</td>
2800
+ </tr>`;
2801
+ }
2463
2802
  }
2464
2803
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2465
2804
  if (data["status"] === "final") {
2466
- diagnosticReportExists = true;
2467
- diagnosticReporthtml += `
2468
- <tr>
2469
- <td>${data["report"] ?? "-"}</td>
2470
- <td>${data["performer"] ?? "-"}</td>
2471
- <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
2472
- </tr>`;
2805
+ const reportName = data["report"] ?? "";
2806
+ if (reportName && !diagnosticReportAdded.has(reportName)) {
2807
+ diagnosticReportAdded.add(reportName);
2808
+ diagnosticReporthtml += `
2809
+ <tr>
2810
+ <td>${data["report"] ?? "-"}</td>
2811
+ <td>${data["performer"] ?? "-"}</td>
2812
+ <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
2813
+ </tr>`;
2814
+ }
2473
2815
  }
2474
2816
  }
2475
2817
  }
2476
2818
  }
2477
- if (observationExists) {
2819
+ if (observationAdded.size > 0) {
2478
2820
  html += observationhtml;
2479
2821
  html += `
2480
2822
  </tbody>
2481
2823
  </table>
2482
2824
  </div>`;
2483
2825
  }
2484
- if (diagnosticReportExists) {
2826
+ if (diagnosticReportAdded.size > 0) {
2485
2827
  html += diagnosticReporthtml;
2486
2828
  html += `
2487
2829
  </tbody>
@@ -2490,7 +2832,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2490
2832
  }
2491
2833
  html += `
2492
2834
  </div>`;
2493
- return observationExists || diagnosticReportExists ? html : void 0;
2835
+ return observationAdded.size > 0 || diagnosticReportAdded.size > 0 ? html : void 0;
2494
2836
  }
2495
2837
  /**
2496
2838
  * Internal static implementation that actually generates the narrative
@@ -2504,30 +2846,80 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2504
2846
  const observations = this.getObservations(resources);
2505
2847
  if (observations.length > 0) {
2506
2848
  observations.sort((a, b) => {
2507
- const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
2508
- const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
2509
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2849
+ const dateA = this.getObservationDate(a);
2850
+ const dateB = this.getObservationDate(b);
2851
+ return dateA && dateB ? dateB.getTime() - dateA.getTime() : 0;
2510
2852
  });
2853
+ this.filterObservationForLoincCodes(observations);
2511
2854
  html += this.renderObservations(templateUtilities, observations, timezone);
2512
2855
  }
2513
- const diagnosticReports = this.getDiagnosticReports(resources);
2514
- if (diagnosticReports.length > 0) {
2515
- diagnosticReports.sort((a, b) => {
2516
- const dateA = a.issued;
2517
- const dateB = b.issued;
2518
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2519
- });
2520
- html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
2856
+ if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2857
+ const diagnosticReports = this.getDiagnosticReports(resources);
2858
+ if (diagnosticReports.length > 0) {
2859
+ diagnosticReports.sort((a, b) => {
2860
+ const dateA = a.issued;
2861
+ const dateB = b.issued;
2862
+ return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2863
+ });
2864
+ html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
2865
+ }
2521
2866
  }
2522
2867
  return html;
2523
2868
  }
2869
+ static filterObservationForLoincCodes(observations) {
2870
+ const labsAdded = /* @__PURE__ */ new Set();
2871
+ const filteredObservations = [];
2872
+ for (const obs of observations) {
2873
+ const loincCode = this.getObservationLoincCode(obs);
2874
+ if (loincCode && loincToLabName[loincCode]) {
2875
+ const labName = loincToLabName[loincCode];
2876
+ if (!labsAdded.has(labName)) {
2877
+ labsAdded.add(labName);
2878
+ filteredObservations.push(obs);
2879
+ }
2880
+ }
2881
+ }
2882
+ observations.length = 0;
2883
+ observations.push(...filteredObservations);
2884
+ }
2885
+ static getObservationLoincCode(obs) {
2886
+ if (obs.code && obs.code.coding) {
2887
+ for (const coding of obs.code.coding) {
2888
+ if (coding.system && coding.system.toLowerCase().includes("loinc") && coding.code) {
2889
+ return coding.code;
2890
+ }
2891
+ }
2892
+ }
2893
+ return void 0;
2894
+ }
2895
+ static getObservationDate(obs) {
2896
+ let obsDate = void 0;
2897
+ if (obs.effectiveDateTime) {
2898
+ obsDate = new Date(obs.effectiveDateTime);
2899
+ } else if (obs.effectivePeriod) {
2900
+ if (obs.effectivePeriod.start) {
2901
+ obsDate = new Date(obs.effectivePeriod.start);
2902
+ } else if (obs.effectivePeriod.end) {
2903
+ obsDate = new Date(obs.effectivePeriod.end);
2904
+ }
2905
+ }
2906
+ return obsDate;
2907
+ }
2524
2908
  /**
2525
2909
  * Get all Observation resources from the resource array
2526
2910
  * @param resources - FHIR resources array
2527
2911
  * @returns Array of Observation resources
2528
2912
  */
2529
2913
  static getObservations(resources) {
2530
- return resources.filter((resourceItem) => resourceItem.resourceType === "Observation").map((resourceItem) => resourceItem);
2914
+ return resources.filter((resourceItem) => {
2915
+ if (resourceItem.resourceType === "Observation") {
2916
+ const obsDate = this.getObservationDate(resourceItem);
2917
+ if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
2918
+ return true;
2919
+ }
2920
+ }
2921
+ return false;
2922
+ }).map((resourceItem) => resourceItem);
2531
2923
  }
2532
2924
  /**
2533
2925
  * Get all DiagnosticReport resources from the resource array
@@ -2545,32 +2937,35 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2545
2937
  * @returns HTML string for rendering
2546
2938
  */
2547
2939
  static renderObservations(templateUtilities, observations, timezone) {
2548
- let html = `
2549
- <h3>Observations</h3>
2940
+ let html = "";
2941
+ if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2942
+ html += `
2943
+ <h3>Observations</h3>`;
2944
+ }
2945
+ html += `
2550
2946
  <table>
2551
2947
  <thead>
2552
2948
  <tr>
2553
2949
  <th>Code</th>
2554
2950
  <th>Result</th>
2555
- <th>Unit</th>
2556
- <th>Interpretation</th>
2557
2951
  <th>Reference Range</th>
2558
- <th>Comments</th>
2559
2952
  <th>Date</th>
2560
2953
  </tr>
2561
2954
  </thead>
2562
2955
  <tbody>`;
2956
+ const observationAdded = /* @__PURE__ */ new Set();
2563
2957
  for (const obs of observations) {
2564
- html += `
2565
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
2566
- <td>${templateUtilities.codeableConcept(obs.code)}</td>
2567
- <td>${templateUtilities.extractObservationValue(obs)}</td>
2568
- <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2569
- <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
2570
- <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
2571
- <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
2572
- <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
2573
- </tr>`;
2958
+ const obsCode = templateUtilities.codeableConcept(obs.code);
2959
+ if (!observationAdded.has(obsCode)) {
2960
+ observationAdded.add(obsCode);
2961
+ html += `
2962
+ <tr id="${templateUtilities.narrativeLinkId(obs)}">
2963
+ <td>${obsCode}</td>
2964
+ <td>${templateUtilities.extractObservationValue(obs)}</td>
2965
+ <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
2966
+ <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
2967
+ </tr>`;
2968
+ }
2574
2969
  }
2575
2970
  html += `
2576
2971
  </tbody>
@@ -2597,18 +2992,23 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2597
2992
  </tr>
2598
2993
  </thead>
2599
2994
  <tbody>`;
2995
+ const diagnosticReportAdded = /* @__PURE__ */ new Set();
2600
2996
  for (const report of reports) {
2601
- let resultCount = "";
2602
- if (report.result && Array.isArray(report.result)) {
2603
- resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
2997
+ const reportName = templateUtilities.codeableConcept(report.code);
2998
+ if (!diagnosticReportAdded.has(reportName)) {
2999
+ diagnosticReportAdded.add(reportName);
3000
+ let resultCount = "";
3001
+ if (report.result && Array.isArray(report.result)) {
3002
+ resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
3003
+ }
3004
+ html += `
3005
+ <tr id="${templateUtilities.narrativeLinkId(report)}">
3006
+ <td>${reportName}</td>
3007
+ <td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
3008
+ <td>${resultCount}</td>
3009
+ <td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
3010
+ </tr>`;
2604
3011
  }
2605
- html += `
2606
- <tr id="${templateUtilities.narrativeLinkId(report)}">
2607
- <td>${templateUtilities.codeableConcept(report.code)}</td>
2608
- <td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
2609
- <td>${resultCount}</td>
2610
- <td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
2611
- </tr>`;
2612
3012
  }
2613
3013
  html += `
2614
3014
  </tbody>
@@ -2788,8 +3188,8 @@ var PastHistoryOfIllnessTemplate = class {
2788
3188
  let html = ``;
2789
3189
  const resolvedConditions = resources.map((entry) => entry) || [];
2790
3190
  resolvedConditions.sort((a, b) => {
2791
- const dateA = a.onsetDateTime ? new Date(a.onsetDateTime).getTime() : 0;
2792
- const dateB = b.onsetDateTime ? new Date(b.onsetDateTime).getTime() : 0;
3191
+ const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3192
+ const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
2793
3193
  return dateB - dateA;
2794
3194
  });
2795
3195
  html += `
@@ -2803,13 +3203,18 @@ var PastHistoryOfIllnessTemplate = class {
2803
3203
  </tr>
2804
3204
  </thead>
2805
3205
  <tbody>`;
3206
+ const addedConditionCodes = /* @__PURE__ */ new Set();
2806
3207
  for (const cond of resolvedConditions) {
2807
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2808
- <td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
2809
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2810
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2811
- <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
2812
- </tr>`;
3208
+ const conditionCode = templateUtilities.codeableConcept(cond.code);
3209
+ if (!addedConditionCodes.has(conditionCode)) {
3210
+ addedConditionCodes.add(conditionCode);
3211
+ html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3212
+ <td class="Name">${conditionCode}</td>
3213
+ <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3214
+ <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3215
+ <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
3216
+ </tr>`;
3217
+ }
2813
3218
  }
2814
3219
  html += `</tbody>
2815
3220
  </table>`;
@@ -2934,20 +3339,14 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2934
3339
  const clinicalImpressions = [];
2935
3340
  for (const resourceItem of resources) {
2936
3341
  if (resourceItem.resourceType === "Condition") {
2937
- const cond = resourceItem;
2938
- const isResolved = cond.clinicalStatus?.coding?.some(
2939
- (c) => c.code === "resolved" || c.code === "inactive" || c.display?.toLowerCase().includes("resolved")
2940
- );
2941
- if (!isResolved) {
2942
- activeConditions.push(cond);
2943
- }
3342
+ activeConditions.push(resourceItem);
2944
3343
  } else if (resourceItem.resourceType === "ClinicalImpression") {
2945
3344
  clinicalImpressions.push(resourceItem);
2946
3345
  }
2947
3346
  }
2948
3347
  activeConditions.sort((a, b) => {
2949
- const dateA = a.onsetDateTime ? new Date(a.onsetDateTime).getTime() : 0;
2950
- const dateB = b.onsetDateTime ? new Date(b.onsetDateTime).getTime() : 0;
3348
+ const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3349
+ const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
2951
3350
  return dateB - dateA;
2952
3351
  });
2953
3352
  clinicalImpressions.sort((a, b) => {
@@ -2966,12 +3365,17 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2966
3365
  </tr>
2967
3366
  </thead>
2968
3367
  <tbody>`;
3368
+ const addedConditionCodes = /* @__PURE__ */ new Set();
2969
3369
  for (const cond of activeConditions) {
2970
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2971
- <td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
2972
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2973
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2974
- </tr>`;
3370
+ const conditionCode = templateUtilities.codeableConcept(cond.code);
3371
+ if (!addedConditionCodes.has(conditionCode)) {
3372
+ addedConditionCodes.add(conditionCode);
3373
+ html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3374
+ <td class="Name">${conditionCode}</td>
3375
+ <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3376
+ <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3377
+ </tr>`;
3378
+ }
2975
3379
  }
2976
3380
  html += `</tbody>
2977
3381
  </table>`;