@imranq2/fhirpatientsummary 1.0.23 → 1.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -155,7 +155,9 @@ var IPSSectionSummaryCompositionFilter = {
155
155
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
156
156
  ["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
157
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
158
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document")
158
+ ["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)),
160
+ ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
159
161
  };
160
162
  var IPSSectionResourceHelper = class {
161
163
  static getResourceFilterForSection(section) {
@@ -1324,6 +1326,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1324
1326
  */
1325
1327
  generateSummaryNarrative(resources, timezone) {
1326
1328
  const templateUtilities = new TemplateUtilities(resources);
1329
+ let isSummaryCreated = false;
1327
1330
  let html = `
1328
1331
  <div>
1329
1332
  <table>
@@ -1353,6 +1356,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1353
1356
  break;
1354
1357
  }
1355
1358
  }
1359
+ isSummaryCreated = true;
1356
1360
  html += `
1357
1361
  <tr>
1358
1362
  <td>${data["allergen"] ?? "-"}</td>
@@ -1365,7 +1369,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1365
1369
  </tbody>
1366
1370
  </table>
1367
1371
  </div>`;
1368
- return html;
1372
+ return isSummaryCreated ? html : void 0;
1369
1373
  }
1370
1374
  /**
1371
1375
  * Internal static implementation that actually generates the narrative
@@ -1511,6 +1515,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1511
1515
  */
1512
1516
  generateSummaryNarrative(resources, timezone) {
1513
1517
  const templateUtilities = new TemplateUtilities(resources);
1518
+ let isSummaryCreated = false;
1514
1519
  let html = `
1515
1520
  <div>
1516
1521
  <table>
@@ -1555,6 +1560,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1555
1560
  }
1556
1561
  }
1557
1562
  if (data["status"] === "active") {
1563
+ isSummaryCreated = true;
1558
1564
  html += `
1559
1565
  <tr>
1560
1566
  <td>${data["medication"]}</td>
@@ -1570,7 +1576,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1570
1576
  </tbody>
1571
1577
  </table>
1572
1578
  </div>`;
1573
- return html;
1579
+ return isSummaryCreated ? html : void 0;
1574
1580
  }
1575
1581
  /**
1576
1582
  * Safely parse a date string and return a valid Date object or null
@@ -1766,6 +1772,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1766
1772
  */
1767
1773
  generateSummaryNarrative(resources, timezone) {
1768
1774
  const templateUtilities = new TemplateUtilities(resources);
1775
+ let isSummaryCreated = false;
1769
1776
  let html = `
1770
1777
  <div>
1771
1778
  <table>
@@ -1796,6 +1803,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1796
1803
  }
1797
1804
  }
1798
1805
  if (data["status"] === "completed") {
1806
+ isSummaryCreated = true;
1799
1807
  html += `
1800
1808
  <tr>
1801
1809
  <td>${data["immunization"] ?? "-"}</td>
@@ -1809,7 +1817,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1809
1817
  </tbody>
1810
1818
  </table>
1811
1819
  </div>`;
1812
- return html;
1820
+ return isSummaryCreated ? html : void 0;
1813
1821
  }
1814
1822
  /**
1815
1823
  * Internal static implementation that actually generates the narrative
@@ -1925,6 +1933,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1925
1933
  */
1926
1934
  generateSummaryNarrative(resources, timezone) {
1927
1935
  const templateUtilities = new TemplateUtilities(resources);
1936
+ let isSummaryCreated = false;
1928
1937
  let html = `
1929
1938
  <div>
1930
1939
  <table>
@@ -1964,6 +1973,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1964
1973
  data[columnTitle] = columnData.text?.div ?? "";
1965
1974
  }
1966
1975
  }
1976
+ isSummaryCreated = true;
1967
1977
  html += `
1968
1978
  <tr>
1969
1979
  <td>${data["Vital Name"] ?? "-"}</td>
@@ -1977,7 +1987,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1977
1987
  </tbody>
1978
1988
  </table>
1979
1989
  </div>`;
1980
- return html;
1990
+ return isSummaryCreated ? html : void 0;
1981
1991
  }
1982
1992
  /**
1983
1993
  * Internal static implementation that actually generates the narrative
@@ -2088,6 +2098,400 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2088
2098
  generateNarrative(resources, timezone) {
2089
2099
  return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2090
2100
  }
2101
+ /**
2102
+ * Helper function to format observation data fields
2103
+ * @param obsData - Record containing observation data fields
2104
+ */
2105
+ formatSummaryObservationData(obsData) {
2106
+ const valueType = obsData["valueType"];
2107
+ switch (valueType) {
2108
+ case "valueQuantity":
2109
+ if (obsData["value"] && obsData["unit"]) {
2110
+ obsData["formattedValue"] = `${obsData["value"]} ${obsData["unit"]}`;
2111
+ } else if (obsData["value"]) {
2112
+ obsData["formattedValue"] = obsData["value"];
2113
+ }
2114
+ break;
2115
+ case "valueCodeableConcept":
2116
+ case "valueString":
2117
+ case "valueBoolean":
2118
+ case "valueInteger":
2119
+ case "valueDateTime":
2120
+ case "valueTime":
2121
+ obsData["formattedValue"] = obsData["value"] ?? "";
2122
+ break;
2123
+ case "valuePeriod":
2124
+ if (obsData["valuePeriodStart"] && obsData["valuePeriodEnd"]) {
2125
+ obsData["formattedValue"] = `${obsData["valuePeriodStart"]} - ${obsData["valuePeriodEnd"]}`;
2126
+ } else if (obsData["valuePeriodStart"]) {
2127
+ obsData["formattedValue"] = `From ${obsData["valuePeriodStart"]}`;
2128
+ } else if (obsData["valuePeriodEnd"]) {
2129
+ obsData["formattedValue"] = `Until ${obsData["valuePeriodEnd"]}`;
2130
+ }
2131
+ break;
2132
+ case "valueSampledData": {
2133
+ const sampledParts = [];
2134
+ if (obsData["sampledDataOriginValue"]) {
2135
+ sampledParts.push(`Origin: ${obsData["sampledDataOriginValue"]}${obsData["sampledDataOriginUnit"] ? " " + obsData["sampledDataOriginUnit"] : ""}`);
2136
+ }
2137
+ if (obsData["sampledDataPeriod"]) {
2138
+ sampledParts.push(`Period: ${obsData["sampledDataPeriod"]}`);
2139
+ }
2140
+ if (obsData["sampledDataFactor"]) {
2141
+ sampledParts.push(`Factor: ${obsData["sampledDataFactor"]}`);
2142
+ }
2143
+ if (obsData["sampledDataLowerLimit"]) {
2144
+ sampledParts.push(`Lower: ${obsData["sampledDataLowerLimit"]}`);
2145
+ }
2146
+ if (obsData["sampledDataUpperLimit"]) {
2147
+ sampledParts.push(`Upper: ${obsData["sampledDataUpperLimit"]}`);
2148
+ }
2149
+ if (obsData["sampledDataData"]) {
2150
+ sampledParts.push(`Data: ${obsData["sampledDataData"]}`);
2151
+ }
2152
+ obsData["formattedValue"] = sampledParts.join(", ");
2153
+ break;
2154
+ }
2155
+ case "valueRange": {
2156
+ const rangeParts = [];
2157
+ if (obsData["valueRangeLowValue"]) {
2158
+ rangeParts.push(`${obsData["valueRangeLowValue"]}${obsData["valueRangeLowUnit"] ? " " + obsData["valueRangeLowUnit"] : ""}`);
2159
+ }
2160
+ if (obsData["valueRangeHighValue"]) {
2161
+ rangeParts.push(`${obsData["valueRangeHighValue"]}${obsData["valueRangeHighUnit"] ? " " + obsData["valueRangeHighUnit"] : ""}`);
2162
+ }
2163
+ obsData["formattedValue"] = rangeParts.join(" - ");
2164
+ break;
2165
+ }
2166
+ case "valueRatio": {
2167
+ const numerator = obsData["valueRatioNumeratorValue"] ? `${obsData["valueRatioNumeratorValue"]}${obsData["valueRatioNumeratorUnit"] ? " " + obsData["valueRatioNumeratorUnit"] : ""}` : "";
2168
+ const denominator = obsData["valueRatioDenominatorValue"] ? `${obsData["valueRatioDenominatorValue"]}${obsData["valueRatioDenominatorUnit"] ? " " + obsData["valueRatioDenominatorUnit"] : ""}` : "";
2169
+ if (numerator && denominator) {
2170
+ obsData["formattedValue"] = `${numerator} / ${denominator}`;
2171
+ } else if (numerator) {
2172
+ obsData["formattedValue"] = numerator;
2173
+ }
2174
+ break;
2175
+ }
2176
+ default:
2177
+ obsData["formattedValue"] = obsData["value"] ?? "";
2178
+ break;
2179
+ }
2180
+ if (obsData["referenceRangeLow"]) {
2181
+ obsData["referenceRange"] = obsData["referenceRangeLow"] + " " + obsData["referenceRangeLowUnit"];
2182
+ }
2183
+ if (obsData["referenceRangeHigh"]) {
2184
+ if (obsData["referenceRange"]) {
2185
+ obsData["referenceRange"] += " - ";
2186
+ } else {
2187
+ obsData["referenceRange"] = "";
2188
+ }
2189
+ obsData["referenceRange"] += obsData["referenceRangeHigh"] + " " + obsData["referenceRangeHighUnit"];
2190
+ }
2191
+ if (obsData["referenceRangeAgeLowValue"] || obsData["referenceRangeAgeHighValue"]) {
2192
+ const ageParts = [];
2193
+ if (obsData["referenceRangeAgeLowValue"]) {
2194
+ ageParts.push(`${obsData["referenceRangeAgeLowValue"]}${obsData["referenceRangeAgeLowUnit"] ? " " + obsData["referenceRangeAgeLowUnit"] : ""}`);
2195
+ }
2196
+ if (obsData["referenceRangeAgeHighValue"]) {
2197
+ ageParts.push(`${obsData["referenceRangeAgeHighValue"]}${obsData["referenceRangeAgeHighUnit"] ? " " + obsData["referenceRangeAgeHighUnit"] : ""}`);
2198
+ }
2199
+ if (obsData["referenceRange"]) {
2200
+ obsData["referenceRange"] += ` (Age: ${ageParts.join(" - ")})`;
2201
+ } else {
2202
+ obsData["referenceRange"] = `Age: ${ageParts.join(" - ")}`;
2203
+ }
2204
+ }
2205
+ }
2206
+ /**
2207
+ * Helper function to extract observation field data
2208
+ * @param column - Column data from the summary
2209
+ * @param targetData - Record to populate with extracted data
2210
+ */
2211
+ extractSummaryObservationFields(column, targetData) {
2212
+ switch (column.title) {
2213
+ case "Labs Name":
2214
+ targetData["code"] = column.text?.div ?? "";
2215
+ break;
2216
+ case "effectiveDateTime":
2217
+ targetData["effectiveDateTime"] = column.text?.div ?? "";
2218
+ break;
2219
+ case "effectivePeriod.start":
2220
+ targetData["effectivePeriodStart"] = column.text?.div ?? "";
2221
+ break;
2222
+ case "effectivePeriod.end":
2223
+ targetData["effectivePeriodEnd"] = column.text?.div ?? "";
2224
+ break;
2225
+ // valueQuantity
2226
+ case "valueQuantity.value":
2227
+ targetData["value"] = column.text?.div ?? "";
2228
+ targetData["valueType"] = "valueQuantity";
2229
+ break;
2230
+ case "valueQuantity.unit":
2231
+ targetData["unit"] = column.text?.div ?? "";
2232
+ break;
2233
+ // valueCodeableConcept
2234
+ case "valueCodeableConcept.text":
2235
+ targetData["value"] = column.text?.div ?? "";
2236
+ targetData["valueType"] = "valueCodeableConcept";
2237
+ break;
2238
+ case "valueCodeableConcept.coding.display":
2239
+ if (!targetData["value"]) {
2240
+ targetData["value"] = column.text?.div ?? "";
2241
+ targetData["valueType"] = "valueCodeableConcept";
2242
+ }
2243
+ break;
2244
+ // valueString
2245
+ case "valueString":
2246
+ targetData["value"] = column.text?.div ?? "";
2247
+ targetData["valueType"] = "valueString";
2248
+ break;
2249
+ // valueBoolean
2250
+ case "valueBoolean":
2251
+ targetData["value"] = column.text?.div ?? "";
2252
+ targetData["valueType"] = "valueBoolean";
2253
+ break;
2254
+ // valueInteger
2255
+ case "valueInteger":
2256
+ targetData["value"] = column.text?.div ?? "";
2257
+ targetData["valueType"] = "valueInteger";
2258
+ break;
2259
+ // valueDateTime
2260
+ case "valueDateTime":
2261
+ targetData["value"] = column.text?.div ?? "";
2262
+ targetData["valueType"] = "valueDateTime";
2263
+ break;
2264
+ // valuePeriod
2265
+ case "valuePeriod.start":
2266
+ targetData["valuePeriodStart"] = column.text?.div ?? "";
2267
+ targetData["valueType"] = "valuePeriod";
2268
+ break;
2269
+ case "valuePeriod.end":
2270
+ targetData["valuePeriodEnd"] = column.text?.div ?? "";
2271
+ targetData["valueType"] = "valuePeriod";
2272
+ break;
2273
+ // valueTime
2274
+ case "valueTime":
2275
+ targetData["value"] = column.text?.div ?? "";
2276
+ targetData["valueType"] = "valueTime";
2277
+ break;
2278
+ // valueSampledData
2279
+ case "valueSampledData.origin.value":
2280
+ targetData["sampledDataOriginValue"] = column.text?.div ?? "";
2281
+ targetData["valueType"] = "valueSampledData";
2282
+ break;
2283
+ case "valueSampledData.origin.unit":
2284
+ targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
2285
+ break;
2286
+ case "valueSampledData.period":
2287
+ targetData["sampledDataPeriod"] = column.text?.div ?? "";
2288
+ break;
2289
+ case "valueSampledData.factor":
2290
+ targetData["sampledDataFactor"] = column.text?.div ?? "";
2291
+ break;
2292
+ case "valueSampledData.lowerLimit":
2293
+ targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
2294
+ break;
2295
+ case "valueSampledData.upperLimit":
2296
+ targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
2297
+ break;
2298
+ case "valueSampledData.data":
2299
+ targetData["sampledDataData"] = column.text?.div ?? "";
2300
+ break;
2301
+ // valueRange
2302
+ case "valueRange.low.value":
2303
+ targetData["valueRangeLowValue"] = column.text?.div ?? "";
2304
+ targetData["valueType"] = "valueRange";
2305
+ break;
2306
+ case "valueRange.low.unit":
2307
+ targetData["valueRangeLowUnit"] = column.text?.div ?? "";
2308
+ break;
2309
+ case "valueRange.high.value":
2310
+ targetData["valueRangeHighValue"] = column.text?.div ?? "";
2311
+ break;
2312
+ case "valueRange.high.unit":
2313
+ targetData["valueRangeHighUnit"] = column.text?.div ?? "";
2314
+ break;
2315
+ // valueRatio
2316
+ case "valueRatio.numerator.value":
2317
+ targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
2318
+ targetData["valueType"] = "valueRatio";
2319
+ break;
2320
+ case "valueRatio.numerator.unit":
2321
+ targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
2322
+ break;
2323
+ case "valueRatio.denominator.value":
2324
+ targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
2325
+ break;
2326
+ case "valueRatio.denominator.unit":
2327
+ targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
2328
+ break;
2329
+ // referenceRange
2330
+ case "referenceRange.low.value":
2331
+ targetData["referenceRangeLow"] = column.text?.div ?? "";
2332
+ break;
2333
+ case "referenceRange.low.unit":
2334
+ targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
2335
+ break;
2336
+ case "referenceRange.high.value":
2337
+ targetData["referenceRangeHigh"] = column.text?.div ?? "";
2338
+ break;
2339
+ case "referenceRange.high.unit":
2340
+ targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
2341
+ break;
2342
+ case "referenceRange.age.low.value":
2343
+ targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
2344
+ break;
2345
+ case "referenceRange.age.low.unit":
2346
+ targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
2347
+ break;
2348
+ case "referenceRange.age.high.value":
2349
+ targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
2350
+ break;
2351
+ case "referenceRange.age.high.unit":
2352
+ targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
2353
+ break;
2354
+ default:
2355
+ break;
2356
+ }
2357
+ }
2358
+ /**
2359
+ * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2360
+ * @param resources - FHIR Composition resources
2361
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2362
+ * @returns HTML string for rendering
2363
+ */
2364
+ generateSummaryNarrative(resources, timezone) {
2365
+ const templateUtilities = new TemplateUtilities(resources);
2366
+ let html = `
2367
+ <div>`;
2368
+ let observationhtml = `
2369
+ <div>
2370
+ <h3>Observations</h3>
2371
+ <table>
2372
+ <thead>
2373
+ <tr>
2374
+ <th>Code</th>
2375
+ <th>Result</th>
2376
+ <th>Reference Range</th>
2377
+ <th>Date</th>
2378
+ </tr>
2379
+ </thead>
2380
+ <tbody>`;
2381
+ let diagnosticReporthtml = `
2382
+ <div>
2383
+ <h3>Diagnostic Reports</h3>
2384
+ <table>
2385
+ <thead>
2386
+ <tr>
2387
+ <th>Report</th>
2388
+ <th>Performer</th>
2389
+ <th>Issued</th>
2390
+ </tr>
2391
+ </thead>
2392
+ <tbody>`;
2393
+ let observationExists = false;
2394
+ let diagnosticReportExists = false;
2395
+ for (const resourceItem of resources) {
2396
+ for (const rowData of resourceItem.section ?? []) {
2397
+ const data = {};
2398
+ const components = [];
2399
+ for (const columnData of rowData.section ?? []) {
2400
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2401
+ if (columnData.text?.div === "Observation.component" && columnData.section) {
2402
+ for (const componentSection of columnData.section) {
2403
+ const componentData = {};
2404
+ for (const nestedColumn of componentSection.section ?? []) {
2405
+ this.extractSummaryObservationFields(nestedColumn, componentData);
2406
+ }
2407
+ if (Object.keys(componentData).length > 0) {
2408
+ components.push(componentData);
2409
+ }
2410
+ }
2411
+ } else {
2412
+ this.extractSummaryObservationFields(columnData, data);
2413
+ }
2414
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2415
+ switch (columnData.title) {
2416
+ case "Diagnostic Report Name":
2417
+ data["report"] = columnData.text?.div ?? "";
2418
+ break;
2419
+ case "Performer":
2420
+ data["performer"] = columnData.text?.div ?? "";
2421
+ break;
2422
+ case "Issued Date":
2423
+ data["issued"] = columnData.text?.div ?? "";
2424
+ break;
2425
+ case "Status":
2426
+ data["status"] = columnData.text?.div ?? "";
2427
+ break;
2428
+ default:
2429
+ break;
2430
+ }
2431
+ }
2432
+ }
2433
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2434
+ observationExists = true;
2435
+ let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
2436
+ if (!date && data["effectivePeriodStart"]) {
2437
+ date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2438
+ if (data["effectivePeriodEnd"]) {
2439
+ date += " - " + templateUtilities.renderTime(data["effectivePeriodEnd"], timezone);
2440
+ }
2441
+ }
2442
+ if (components.length > 0) {
2443
+ const groupName = data["code"] ?? "";
2444
+ 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>`;
2453
+ }
2454
+ } 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>`;
2463
+ }
2464
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2465
+ 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>`;
2473
+ }
2474
+ }
2475
+ }
2476
+ }
2477
+ if (observationExists) {
2478
+ html += observationhtml;
2479
+ html += `
2480
+ </tbody>
2481
+ </table>
2482
+ </div>`;
2483
+ }
2484
+ if (diagnosticReportExists) {
2485
+ html += diagnosticReporthtml;
2486
+ html += `
2487
+ </tbody>
2488
+ </table>
2489
+ </div>`;
2490
+ }
2491
+ html += `
2492
+ </div>`;
2493
+ return observationExists || diagnosticReportExists ? html : void 0;
2494
+ }
2091
2495
  /**
2092
2496
  * Internal static implementation that actually generates the narrative
2093
2497
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
@@ -2229,6 +2633,59 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
2229
2633
  });
2230
2634
  return _HistoryOfProceduresTemplate.generateStaticNarrative(resources, timezone);
2231
2635
  }
2636
+ /**
2637
+ * Generate HTML narrative for Procedure resources using summary
2638
+ * @param resources - FHIR Composition resources
2639
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2640
+ * @returns HTML string for rendering
2641
+ */
2642
+ generateSummaryNarrative(resources, timezone) {
2643
+ const templateUtilities = new TemplateUtilities(resources);
2644
+ let isSummaryCreated = false;
2645
+ let html = `
2646
+ <div>
2647
+ <table>
2648
+ <thead>
2649
+ <tr>
2650
+ <th>Procedure</th>
2651
+ <th>Performer</th>
2652
+ <th>Date</th>
2653
+ </tr>
2654
+ </thead>
2655
+ <tbody>`;
2656
+ for (const resourceItem of resources) {
2657
+ for (const rowData of resourceItem.section ?? []) {
2658
+ const data = {};
2659
+ for (const columnData of rowData.section ?? []) {
2660
+ switch (columnData.title) {
2661
+ case "Procedure Name":
2662
+ data["procedure"] = columnData.text?.div ?? "";
2663
+ break;
2664
+ case "Performer":
2665
+ data["performer"] = columnData.text?.div ?? "";
2666
+ break;
2667
+ case "Performed Date":
2668
+ data["date"] = columnData.text?.div ?? "";
2669
+ break;
2670
+ default:
2671
+ break;
2672
+ }
2673
+ }
2674
+ isSummaryCreated = true;
2675
+ html += `
2676
+ <tr>
2677
+ <td>${data["procedure"] ?? "-"}</td>
2678
+ <td>${data["performer"] ?? "-"}</td>
2679
+ <td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
2680
+ </tr>`;
2681
+ }
2682
+ }
2683
+ html += `
2684
+ </tbody>
2685
+ </table>
2686
+ </div>`;
2687
+ return isSummaryCreated ? html : void 0;
2688
+ }
2232
2689
  /**
2233
2690
  * Internal static implementation that actually generates the narrative
2234
2691
  * @param resources - FHIR Procedure resources
@@ -2411,6 +2868,7 @@ var PlanOfCareTemplate = class {
2411
2868
  */
2412
2869
  generateSummaryNarrative(resources, timezone) {
2413
2870
  const templateUtilities = new TemplateUtilities(resources);
2871
+ let isSummaryCreated = false;
2414
2872
  let html = `
2415
2873
  <div>
2416
2874
  <table>
@@ -2434,6 +2892,7 @@ var PlanOfCareTemplate = class {
2434
2892
  if (data["status"] !== "active") {
2435
2893
  continue;
2436
2894
  }
2895
+ isSummaryCreated = true;
2437
2896
  html += `
2438
2897
  <tr>
2439
2898
  <td>${data["CarePlan Name"] ?? "-"}</td>
@@ -2447,7 +2906,7 @@ var PlanOfCareTemplate = class {
2447
2906
  </tbody>
2448
2907
  </table>
2449
2908
  </div>`;
2450
- return html;
2909
+ return isSummaryCreated ? html : void 0;
2451
2910
  }
2452
2911
  };
2453
2912
 
@@ -2900,11 +3359,13 @@ var ComprehensiveIPSCompositionBuilder = class {
2900
3359
  timezone,
2901
3360
  true
2902
3361
  );
2903
- } else if (sectionType in IPSMandatorySections) {
3362
+ }
3363
+ if (!narrative && sectionType in IPSMandatorySections) {
2904
3364
  narrative = await NarrativeGenerator.createNarrativeAsync(
2905
3365
  IPSMissingMandatorySectionContent[sectionType]
2906
3366
  );
2907
- } else {
3367
+ }
3368
+ if (!narrative) {
2908
3369
  return this;
2909
3370
  }
2910
3371
  this.addSectionAsync(narrative, sectionType, validResources);
@@ -2922,13 +3383,21 @@ var ComprehensiveIPSCompositionBuilder = class {
2922
3383
  }
2923
3384
  });
2924
3385
  }
2925
- const narrative = await NarrativeGenerator.generateNarrativeAsync(
3386
+ let narrative = await NarrativeGenerator.generateNarrativeAsync(
2926
3387
  sectionType,
2927
3388
  summaryCompositions,
2928
3389
  timezone,
2929
3390
  true,
2930
3391
  true
2931
3392
  );
3393
+ if (!narrative && sectionType in IPSMandatorySections) {
3394
+ narrative = await NarrativeGenerator.createNarrativeAsync(
3395
+ IPSMissingMandatorySectionContent[sectionType]
3396
+ );
3397
+ }
3398
+ if (!narrative) {
3399
+ return this;
3400
+ }
2932
3401
  this.addSectionAsync(narrative, sectionType, sectionResources);
2933
3402
  return this;
2934
3403
  }
package/dist/index.js CHANGED
@@ -127,7 +127,9 @@ var IPSSectionSummaryCompositionFilter = {
127
127
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
128
128
  ["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
129
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
130
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document")
130
+ ["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)),
132
+ ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
131
133
  };
132
134
  var IPSSectionResourceHelper = class {
133
135
  static getResourceFilterForSection(section) {
@@ -1296,6 +1298,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1296
1298
  */
1297
1299
  generateSummaryNarrative(resources, timezone) {
1298
1300
  const templateUtilities = new TemplateUtilities(resources);
1301
+ let isSummaryCreated = false;
1299
1302
  let html = `
1300
1303
  <div>
1301
1304
  <table>
@@ -1325,6 +1328,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1325
1328
  break;
1326
1329
  }
1327
1330
  }
1331
+ isSummaryCreated = true;
1328
1332
  html += `
1329
1333
  <tr>
1330
1334
  <td>${data["allergen"] ?? "-"}</td>
@@ -1337,7 +1341,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1337
1341
  </tbody>
1338
1342
  </table>
1339
1343
  </div>`;
1340
- return html;
1344
+ return isSummaryCreated ? html : void 0;
1341
1345
  }
1342
1346
  /**
1343
1347
  * Internal static implementation that actually generates the narrative
@@ -1483,6 +1487,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1483
1487
  */
1484
1488
  generateSummaryNarrative(resources, timezone) {
1485
1489
  const templateUtilities = new TemplateUtilities(resources);
1490
+ let isSummaryCreated = false;
1486
1491
  let html = `
1487
1492
  <div>
1488
1493
  <table>
@@ -1527,6 +1532,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1527
1532
  }
1528
1533
  }
1529
1534
  if (data["status"] === "active") {
1535
+ isSummaryCreated = true;
1530
1536
  html += `
1531
1537
  <tr>
1532
1538
  <td>${data["medication"]}</td>
@@ -1542,7 +1548,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1542
1548
  </tbody>
1543
1549
  </table>
1544
1550
  </div>`;
1545
- return html;
1551
+ return isSummaryCreated ? html : void 0;
1546
1552
  }
1547
1553
  /**
1548
1554
  * Safely parse a date string and return a valid Date object or null
@@ -1738,6 +1744,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1738
1744
  */
1739
1745
  generateSummaryNarrative(resources, timezone) {
1740
1746
  const templateUtilities = new TemplateUtilities(resources);
1747
+ let isSummaryCreated = false;
1741
1748
  let html = `
1742
1749
  <div>
1743
1750
  <table>
@@ -1768,6 +1775,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1768
1775
  }
1769
1776
  }
1770
1777
  if (data["status"] === "completed") {
1778
+ isSummaryCreated = true;
1771
1779
  html += `
1772
1780
  <tr>
1773
1781
  <td>${data["immunization"] ?? "-"}</td>
@@ -1781,7 +1789,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
1781
1789
  </tbody>
1782
1790
  </table>
1783
1791
  </div>`;
1784
- return html;
1792
+ return isSummaryCreated ? html : void 0;
1785
1793
  }
1786
1794
  /**
1787
1795
  * Internal static implementation that actually generates the narrative
@@ -1897,6 +1905,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1897
1905
  */
1898
1906
  generateSummaryNarrative(resources, timezone) {
1899
1907
  const templateUtilities = new TemplateUtilities(resources);
1908
+ let isSummaryCreated = false;
1900
1909
  let html = `
1901
1910
  <div>
1902
1911
  <table>
@@ -1936,6 +1945,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1936
1945
  data[columnTitle] = columnData.text?.div ?? "";
1937
1946
  }
1938
1947
  }
1948
+ isSummaryCreated = true;
1939
1949
  html += `
1940
1950
  <tr>
1941
1951
  <td>${data["Vital Name"] ?? "-"}</td>
@@ -1949,7 +1959,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
1949
1959
  </tbody>
1950
1960
  </table>
1951
1961
  </div>`;
1952
- return html;
1962
+ return isSummaryCreated ? html : void 0;
1953
1963
  }
1954
1964
  /**
1955
1965
  * Internal static implementation that actually generates the narrative
@@ -2060,6 +2070,400 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2060
2070
  generateNarrative(resources, timezone) {
2061
2071
  return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2062
2072
  }
2073
+ /**
2074
+ * Helper function to format observation data fields
2075
+ * @param obsData - Record containing observation data fields
2076
+ */
2077
+ formatSummaryObservationData(obsData) {
2078
+ const valueType = obsData["valueType"];
2079
+ switch (valueType) {
2080
+ case "valueQuantity":
2081
+ if (obsData["value"] && obsData["unit"]) {
2082
+ obsData["formattedValue"] = `${obsData["value"]} ${obsData["unit"]}`;
2083
+ } else if (obsData["value"]) {
2084
+ obsData["formattedValue"] = obsData["value"];
2085
+ }
2086
+ break;
2087
+ case "valueCodeableConcept":
2088
+ case "valueString":
2089
+ case "valueBoolean":
2090
+ case "valueInteger":
2091
+ case "valueDateTime":
2092
+ case "valueTime":
2093
+ obsData["formattedValue"] = obsData["value"] ?? "";
2094
+ break;
2095
+ case "valuePeriod":
2096
+ if (obsData["valuePeriodStart"] && obsData["valuePeriodEnd"]) {
2097
+ obsData["formattedValue"] = `${obsData["valuePeriodStart"]} - ${obsData["valuePeriodEnd"]}`;
2098
+ } else if (obsData["valuePeriodStart"]) {
2099
+ obsData["formattedValue"] = `From ${obsData["valuePeriodStart"]}`;
2100
+ } else if (obsData["valuePeriodEnd"]) {
2101
+ obsData["formattedValue"] = `Until ${obsData["valuePeriodEnd"]}`;
2102
+ }
2103
+ break;
2104
+ case "valueSampledData": {
2105
+ const sampledParts = [];
2106
+ if (obsData["sampledDataOriginValue"]) {
2107
+ sampledParts.push(`Origin: ${obsData["sampledDataOriginValue"]}${obsData["sampledDataOriginUnit"] ? " " + obsData["sampledDataOriginUnit"] : ""}`);
2108
+ }
2109
+ if (obsData["sampledDataPeriod"]) {
2110
+ sampledParts.push(`Period: ${obsData["sampledDataPeriod"]}`);
2111
+ }
2112
+ if (obsData["sampledDataFactor"]) {
2113
+ sampledParts.push(`Factor: ${obsData["sampledDataFactor"]}`);
2114
+ }
2115
+ if (obsData["sampledDataLowerLimit"]) {
2116
+ sampledParts.push(`Lower: ${obsData["sampledDataLowerLimit"]}`);
2117
+ }
2118
+ if (obsData["sampledDataUpperLimit"]) {
2119
+ sampledParts.push(`Upper: ${obsData["sampledDataUpperLimit"]}`);
2120
+ }
2121
+ if (obsData["sampledDataData"]) {
2122
+ sampledParts.push(`Data: ${obsData["sampledDataData"]}`);
2123
+ }
2124
+ obsData["formattedValue"] = sampledParts.join(", ");
2125
+ break;
2126
+ }
2127
+ case "valueRange": {
2128
+ const rangeParts = [];
2129
+ if (obsData["valueRangeLowValue"]) {
2130
+ rangeParts.push(`${obsData["valueRangeLowValue"]}${obsData["valueRangeLowUnit"] ? " " + obsData["valueRangeLowUnit"] : ""}`);
2131
+ }
2132
+ if (obsData["valueRangeHighValue"]) {
2133
+ rangeParts.push(`${obsData["valueRangeHighValue"]}${obsData["valueRangeHighUnit"] ? " " + obsData["valueRangeHighUnit"] : ""}`);
2134
+ }
2135
+ obsData["formattedValue"] = rangeParts.join(" - ");
2136
+ break;
2137
+ }
2138
+ case "valueRatio": {
2139
+ const numerator = obsData["valueRatioNumeratorValue"] ? `${obsData["valueRatioNumeratorValue"]}${obsData["valueRatioNumeratorUnit"] ? " " + obsData["valueRatioNumeratorUnit"] : ""}` : "";
2140
+ const denominator = obsData["valueRatioDenominatorValue"] ? `${obsData["valueRatioDenominatorValue"]}${obsData["valueRatioDenominatorUnit"] ? " " + obsData["valueRatioDenominatorUnit"] : ""}` : "";
2141
+ if (numerator && denominator) {
2142
+ obsData["formattedValue"] = `${numerator} / ${denominator}`;
2143
+ } else if (numerator) {
2144
+ obsData["formattedValue"] = numerator;
2145
+ }
2146
+ break;
2147
+ }
2148
+ default:
2149
+ obsData["formattedValue"] = obsData["value"] ?? "";
2150
+ break;
2151
+ }
2152
+ if (obsData["referenceRangeLow"]) {
2153
+ obsData["referenceRange"] = obsData["referenceRangeLow"] + " " + obsData["referenceRangeLowUnit"];
2154
+ }
2155
+ if (obsData["referenceRangeHigh"]) {
2156
+ if (obsData["referenceRange"]) {
2157
+ obsData["referenceRange"] += " - ";
2158
+ } else {
2159
+ obsData["referenceRange"] = "";
2160
+ }
2161
+ obsData["referenceRange"] += obsData["referenceRangeHigh"] + " " + obsData["referenceRangeHighUnit"];
2162
+ }
2163
+ if (obsData["referenceRangeAgeLowValue"] || obsData["referenceRangeAgeHighValue"]) {
2164
+ const ageParts = [];
2165
+ if (obsData["referenceRangeAgeLowValue"]) {
2166
+ ageParts.push(`${obsData["referenceRangeAgeLowValue"]}${obsData["referenceRangeAgeLowUnit"] ? " " + obsData["referenceRangeAgeLowUnit"] : ""}`);
2167
+ }
2168
+ if (obsData["referenceRangeAgeHighValue"]) {
2169
+ ageParts.push(`${obsData["referenceRangeAgeHighValue"]}${obsData["referenceRangeAgeHighUnit"] ? " " + obsData["referenceRangeAgeHighUnit"] : ""}`);
2170
+ }
2171
+ if (obsData["referenceRange"]) {
2172
+ obsData["referenceRange"] += ` (Age: ${ageParts.join(" - ")})`;
2173
+ } else {
2174
+ obsData["referenceRange"] = `Age: ${ageParts.join(" - ")}`;
2175
+ }
2176
+ }
2177
+ }
2178
+ /**
2179
+ * Helper function to extract observation field data
2180
+ * @param column - Column data from the summary
2181
+ * @param targetData - Record to populate with extracted data
2182
+ */
2183
+ extractSummaryObservationFields(column, targetData) {
2184
+ switch (column.title) {
2185
+ case "Labs Name":
2186
+ targetData["code"] = column.text?.div ?? "";
2187
+ break;
2188
+ case "effectiveDateTime":
2189
+ targetData["effectiveDateTime"] = column.text?.div ?? "";
2190
+ break;
2191
+ case "effectivePeriod.start":
2192
+ targetData["effectivePeriodStart"] = column.text?.div ?? "";
2193
+ break;
2194
+ case "effectivePeriod.end":
2195
+ targetData["effectivePeriodEnd"] = column.text?.div ?? "";
2196
+ break;
2197
+ // valueQuantity
2198
+ case "valueQuantity.value":
2199
+ targetData["value"] = column.text?.div ?? "";
2200
+ targetData["valueType"] = "valueQuantity";
2201
+ break;
2202
+ case "valueQuantity.unit":
2203
+ targetData["unit"] = column.text?.div ?? "";
2204
+ break;
2205
+ // valueCodeableConcept
2206
+ case "valueCodeableConcept.text":
2207
+ targetData["value"] = column.text?.div ?? "";
2208
+ targetData["valueType"] = "valueCodeableConcept";
2209
+ break;
2210
+ case "valueCodeableConcept.coding.display":
2211
+ if (!targetData["value"]) {
2212
+ targetData["value"] = column.text?.div ?? "";
2213
+ targetData["valueType"] = "valueCodeableConcept";
2214
+ }
2215
+ break;
2216
+ // valueString
2217
+ case "valueString":
2218
+ targetData["value"] = column.text?.div ?? "";
2219
+ targetData["valueType"] = "valueString";
2220
+ break;
2221
+ // valueBoolean
2222
+ case "valueBoolean":
2223
+ targetData["value"] = column.text?.div ?? "";
2224
+ targetData["valueType"] = "valueBoolean";
2225
+ break;
2226
+ // valueInteger
2227
+ case "valueInteger":
2228
+ targetData["value"] = column.text?.div ?? "";
2229
+ targetData["valueType"] = "valueInteger";
2230
+ break;
2231
+ // valueDateTime
2232
+ case "valueDateTime":
2233
+ targetData["value"] = column.text?.div ?? "";
2234
+ targetData["valueType"] = "valueDateTime";
2235
+ break;
2236
+ // valuePeriod
2237
+ case "valuePeriod.start":
2238
+ targetData["valuePeriodStart"] = column.text?.div ?? "";
2239
+ targetData["valueType"] = "valuePeriod";
2240
+ break;
2241
+ case "valuePeriod.end":
2242
+ targetData["valuePeriodEnd"] = column.text?.div ?? "";
2243
+ targetData["valueType"] = "valuePeriod";
2244
+ break;
2245
+ // valueTime
2246
+ case "valueTime":
2247
+ targetData["value"] = column.text?.div ?? "";
2248
+ targetData["valueType"] = "valueTime";
2249
+ break;
2250
+ // valueSampledData
2251
+ case "valueSampledData.origin.value":
2252
+ targetData["sampledDataOriginValue"] = column.text?.div ?? "";
2253
+ targetData["valueType"] = "valueSampledData";
2254
+ break;
2255
+ case "valueSampledData.origin.unit":
2256
+ targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
2257
+ break;
2258
+ case "valueSampledData.period":
2259
+ targetData["sampledDataPeriod"] = column.text?.div ?? "";
2260
+ break;
2261
+ case "valueSampledData.factor":
2262
+ targetData["sampledDataFactor"] = column.text?.div ?? "";
2263
+ break;
2264
+ case "valueSampledData.lowerLimit":
2265
+ targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
2266
+ break;
2267
+ case "valueSampledData.upperLimit":
2268
+ targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
2269
+ break;
2270
+ case "valueSampledData.data":
2271
+ targetData["sampledDataData"] = column.text?.div ?? "";
2272
+ break;
2273
+ // valueRange
2274
+ case "valueRange.low.value":
2275
+ targetData["valueRangeLowValue"] = column.text?.div ?? "";
2276
+ targetData["valueType"] = "valueRange";
2277
+ break;
2278
+ case "valueRange.low.unit":
2279
+ targetData["valueRangeLowUnit"] = column.text?.div ?? "";
2280
+ break;
2281
+ case "valueRange.high.value":
2282
+ targetData["valueRangeHighValue"] = column.text?.div ?? "";
2283
+ break;
2284
+ case "valueRange.high.unit":
2285
+ targetData["valueRangeHighUnit"] = column.text?.div ?? "";
2286
+ break;
2287
+ // valueRatio
2288
+ case "valueRatio.numerator.value":
2289
+ targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
2290
+ targetData["valueType"] = "valueRatio";
2291
+ break;
2292
+ case "valueRatio.numerator.unit":
2293
+ targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
2294
+ break;
2295
+ case "valueRatio.denominator.value":
2296
+ targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
2297
+ break;
2298
+ case "valueRatio.denominator.unit":
2299
+ targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
2300
+ break;
2301
+ // referenceRange
2302
+ case "referenceRange.low.value":
2303
+ targetData["referenceRangeLow"] = column.text?.div ?? "";
2304
+ break;
2305
+ case "referenceRange.low.unit":
2306
+ targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
2307
+ break;
2308
+ case "referenceRange.high.value":
2309
+ targetData["referenceRangeHigh"] = column.text?.div ?? "";
2310
+ break;
2311
+ case "referenceRange.high.unit":
2312
+ targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
2313
+ break;
2314
+ case "referenceRange.age.low.value":
2315
+ targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
2316
+ break;
2317
+ case "referenceRange.age.low.unit":
2318
+ targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
2319
+ break;
2320
+ case "referenceRange.age.high.value":
2321
+ targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
2322
+ break;
2323
+ case "referenceRange.age.high.unit":
2324
+ targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
2325
+ break;
2326
+ default:
2327
+ break;
2328
+ }
2329
+ }
2330
+ /**
2331
+ * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2332
+ * @param resources - FHIR Composition resources
2333
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2334
+ * @returns HTML string for rendering
2335
+ */
2336
+ generateSummaryNarrative(resources, timezone) {
2337
+ const templateUtilities = new TemplateUtilities(resources);
2338
+ let html = `
2339
+ <div>`;
2340
+ let observationhtml = `
2341
+ <div>
2342
+ <h3>Observations</h3>
2343
+ <table>
2344
+ <thead>
2345
+ <tr>
2346
+ <th>Code</th>
2347
+ <th>Result</th>
2348
+ <th>Reference Range</th>
2349
+ <th>Date</th>
2350
+ </tr>
2351
+ </thead>
2352
+ <tbody>`;
2353
+ let diagnosticReporthtml = `
2354
+ <div>
2355
+ <h3>Diagnostic Reports</h3>
2356
+ <table>
2357
+ <thead>
2358
+ <tr>
2359
+ <th>Report</th>
2360
+ <th>Performer</th>
2361
+ <th>Issued</th>
2362
+ </tr>
2363
+ </thead>
2364
+ <tbody>`;
2365
+ let observationExists = false;
2366
+ let diagnosticReportExists = false;
2367
+ for (const resourceItem of resources) {
2368
+ for (const rowData of resourceItem.section ?? []) {
2369
+ const data = {};
2370
+ const components = [];
2371
+ for (const columnData of rowData.section ?? []) {
2372
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2373
+ if (columnData.text?.div === "Observation.component" && columnData.section) {
2374
+ for (const componentSection of columnData.section) {
2375
+ const componentData = {};
2376
+ for (const nestedColumn of componentSection.section ?? []) {
2377
+ this.extractSummaryObservationFields(nestedColumn, componentData);
2378
+ }
2379
+ if (Object.keys(componentData).length > 0) {
2380
+ components.push(componentData);
2381
+ }
2382
+ }
2383
+ } else {
2384
+ this.extractSummaryObservationFields(columnData, data);
2385
+ }
2386
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2387
+ switch (columnData.title) {
2388
+ case "Diagnostic Report Name":
2389
+ data["report"] = columnData.text?.div ?? "";
2390
+ break;
2391
+ case "Performer":
2392
+ data["performer"] = columnData.text?.div ?? "";
2393
+ break;
2394
+ case "Issued Date":
2395
+ data["issued"] = columnData.text?.div ?? "";
2396
+ break;
2397
+ case "Status":
2398
+ data["status"] = columnData.text?.div ?? "";
2399
+ break;
2400
+ default:
2401
+ break;
2402
+ }
2403
+ }
2404
+ }
2405
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2406
+ observationExists = true;
2407
+ let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
2408
+ if (!date && data["effectivePeriodStart"]) {
2409
+ date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2410
+ if (data["effectivePeriodEnd"]) {
2411
+ date += " - " + templateUtilities.renderTime(data["effectivePeriodEnd"], timezone);
2412
+ }
2413
+ }
2414
+ if (components.length > 0) {
2415
+ const groupName = data["code"] ?? "";
2416
+ for (const component of components) {
2417
+ this.formatSummaryObservationData(component);
2418
+ observationhtml += `
2419
+ <tr>
2420
+ <td>${groupName ? groupName + " - " : ""}${component["code"] ?? "-"}</td>
2421
+ <td>${component["formattedValue"] ?? "-"}</td>
2422
+ <td>${component["referenceRange"]?.trim() ?? "-"}</td>
2423
+ <td>${date ?? "-"}</td>
2424
+ </tr>`;
2425
+ }
2426
+ } else {
2427
+ this.formatSummaryObservationData(data);
2428
+ observationhtml += `
2429
+ <tr>
2430
+ <td>${data["code"] ?? "-"}</td>
2431
+ <td>${data["formattedValue"] ?? "-"}</td>
2432
+ <td>${data["referenceRange"]?.trim() ?? "-"}</td>
2433
+ <td>${date ?? "-"}</td>
2434
+ </tr>`;
2435
+ }
2436
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2437
+ if (data["status"] === "final") {
2438
+ diagnosticReportExists = true;
2439
+ diagnosticReporthtml += `
2440
+ <tr>
2441
+ <td>${data["report"] ?? "-"}</td>
2442
+ <td>${data["performer"] ?? "-"}</td>
2443
+ <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
2444
+ </tr>`;
2445
+ }
2446
+ }
2447
+ }
2448
+ }
2449
+ if (observationExists) {
2450
+ html += observationhtml;
2451
+ html += `
2452
+ </tbody>
2453
+ </table>
2454
+ </div>`;
2455
+ }
2456
+ if (diagnosticReportExists) {
2457
+ html += diagnosticReporthtml;
2458
+ html += `
2459
+ </tbody>
2460
+ </table>
2461
+ </div>`;
2462
+ }
2463
+ html += `
2464
+ </div>`;
2465
+ return observationExists || diagnosticReportExists ? html : void 0;
2466
+ }
2063
2467
  /**
2064
2468
  * Internal static implementation that actually generates the narrative
2065
2469
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
@@ -2201,6 +2605,59 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
2201
2605
  });
2202
2606
  return _HistoryOfProceduresTemplate.generateStaticNarrative(resources, timezone);
2203
2607
  }
2608
+ /**
2609
+ * Generate HTML narrative for Procedure resources using summary
2610
+ * @param resources - FHIR Composition resources
2611
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2612
+ * @returns HTML string for rendering
2613
+ */
2614
+ generateSummaryNarrative(resources, timezone) {
2615
+ const templateUtilities = new TemplateUtilities(resources);
2616
+ let isSummaryCreated = false;
2617
+ let html = `
2618
+ <div>
2619
+ <table>
2620
+ <thead>
2621
+ <tr>
2622
+ <th>Procedure</th>
2623
+ <th>Performer</th>
2624
+ <th>Date</th>
2625
+ </tr>
2626
+ </thead>
2627
+ <tbody>`;
2628
+ for (const resourceItem of resources) {
2629
+ for (const rowData of resourceItem.section ?? []) {
2630
+ const data = {};
2631
+ for (const columnData of rowData.section ?? []) {
2632
+ switch (columnData.title) {
2633
+ case "Procedure Name":
2634
+ data["procedure"] = columnData.text?.div ?? "";
2635
+ break;
2636
+ case "Performer":
2637
+ data["performer"] = columnData.text?.div ?? "";
2638
+ break;
2639
+ case "Performed Date":
2640
+ data["date"] = columnData.text?.div ?? "";
2641
+ break;
2642
+ default:
2643
+ break;
2644
+ }
2645
+ }
2646
+ isSummaryCreated = true;
2647
+ html += `
2648
+ <tr>
2649
+ <td>${data["procedure"] ?? "-"}</td>
2650
+ <td>${data["performer"] ?? "-"}</td>
2651
+ <td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
2652
+ </tr>`;
2653
+ }
2654
+ }
2655
+ html += `
2656
+ </tbody>
2657
+ </table>
2658
+ </div>`;
2659
+ return isSummaryCreated ? html : void 0;
2660
+ }
2204
2661
  /**
2205
2662
  * Internal static implementation that actually generates the narrative
2206
2663
  * @param resources - FHIR Procedure resources
@@ -2383,6 +2840,7 @@ var PlanOfCareTemplate = class {
2383
2840
  */
2384
2841
  generateSummaryNarrative(resources, timezone) {
2385
2842
  const templateUtilities = new TemplateUtilities(resources);
2843
+ let isSummaryCreated = false;
2386
2844
  let html = `
2387
2845
  <div>
2388
2846
  <table>
@@ -2406,6 +2864,7 @@ var PlanOfCareTemplate = class {
2406
2864
  if (data["status"] !== "active") {
2407
2865
  continue;
2408
2866
  }
2867
+ isSummaryCreated = true;
2409
2868
  html += `
2410
2869
  <tr>
2411
2870
  <td>${data["CarePlan Name"] ?? "-"}</td>
@@ -2419,7 +2878,7 @@ var PlanOfCareTemplate = class {
2419
2878
  </tbody>
2420
2879
  </table>
2421
2880
  </div>`;
2422
- return html;
2881
+ return isSummaryCreated ? html : void 0;
2423
2882
  }
2424
2883
  };
2425
2884
 
@@ -2872,11 +3331,13 @@ var ComprehensiveIPSCompositionBuilder = class {
2872
3331
  timezone,
2873
3332
  true
2874
3333
  );
2875
- } else if (sectionType in IPSMandatorySections) {
3334
+ }
3335
+ if (!narrative && sectionType in IPSMandatorySections) {
2876
3336
  narrative = await NarrativeGenerator.createNarrativeAsync(
2877
3337
  IPSMissingMandatorySectionContent[sectionType]
2878
3338
  );
2879
- } else {
3339
+ }
3340
+ if (!narrative) {
2880
3341
  return this;
2881
3342
  }
2882
3343
  this.addSectionAsync(narrative, sectionType, validResources);
@@ -2894,13 +3355,21 @@ var ComprehensiveIPSCompositionBuilder = class {
2894
3355
  }
2895
3356
  });
2896
3357
  }
2897
- const narrative = await NarrativeGenerator.generateNarrativeAsync(
3358
+ let narrative = await NarrativeGenerator.generateNarrativeAsync(
2898
3359
  sectionType,
2899
3360
  summaryCompositions,
2900
3361
  timezone,
2901
3362
  true,
2902
3363
  true
2903
3364
  );
3365
+ if (!narrative && sectionType in IPSMandatorySections) {
3366
+ narrative = await NarrativeGenerator.createNarrativeAsync(
3367
+ IPSMissingMandatorySectionContent[sectionType]
3368
+ );
3369
+ }
3370
+ if (!narrative) {
3371
+ return this;
3372
+ }
2904
3373
  this.addSectionAsync(narrative, sectionType, sectionResources);
2905
3374
  return this;
2906
3375
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "A template for creating npm packages using TypeScript and VSCode",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",