@imranq2/fhirpatientsummary 1.0.23 → 1.0.24

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) {
@@ -2088,6 +2090,400 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2088
2090
  generateNarrative(resources, timezone) {
2089
2091
  return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2090
2092
  }
2093
+ /**
2094
+ * Helper function to format observation data fields
2095
+ * @param obsData - Record containing observation data fields
2096
+ */
2097
+ formatSummaryObservationData(obsData) {
2098
+ const valueType = obsData["valueType"];
2099
+ switch (valueType) {
2100
+ case "valueQuantity":
2101
+ if (obsData["value"] && obsData["unit"]) {
2102
+ obsData["formattedValue"] = `${obsData["value"]} ${obsData["unit"]}`;
2103
+ } else if (obsData["value"]) {
2104
+ obsData["formattedValue"] = obsData["value"];
2105
+ }
2106
+ break;
2107
+ case "valueCodeableConcept":
2108
+ case "valueString":
2109
+ case "valueBoolean":
2110
+ case "valueInteger":
2111
+ case "valueDateTime":
2112
+ case "valueTime":
2113
+ obsData["formattedValue"] = obsData["value"] ?? "";
2114
+ break;
2115
+ case "valuePeriod":
2116
+ if (obsData["valuePeriodStart"] && obsData["valuePeriodEnd"]) {
2117
+ obsData["formattedValue"] = `${obsData["valuePeriodStart"]} - ${obsData["valuePeriodEnd"]}`;
2118
+ } else if (obsData["valuePeriodStart"]) {
2119
+ obsData["formattedValue"] = `From ${obsData["valuePeriodStart"]}`;
2120
+ } else if (obsData["valuePeriodEnd"]) {
2121
+ obsData["formattedValue"] = `Until ${obsData["valuePeriodEnd"]}`;
2122
+ }
2123
+ break;
2124
+ case "valueSampledData": {
2125
+ const sampledParts = [];
2126
+ if (obsData["sampledDataOriginValue"]) {
2127
+ sampledParts.push(`Origin: ${obsData["sampledDataOriginValue"]}${obsData["sampledDataOriginUnit"] ? " " + obsData["sampledDataOriginUnit"] : ""}`);
2128
+ }
2129
+ if (obsData["sampledDataPeriod"]) {
2130
+ sampledParts.push(`Period: ${obsData["sampledDataPeriod"]}`);
2131
+ }
2132
+ if (obsData["sampledDataFactor"]) {
2133
+ sampledParts.push(`Factor: ${obsData["sampledDataFactor"]}`);
2134
+ }
2135
+ if (obsData["sampledDataLowerLimit"]) {
2136
+ sampledParts.push(`Lower: ${obsData["sampledDataLowerLimit"]}`);
2137
+ }
2138
+ if (obsData["sampledDataUpperLimit"]) {
2139
+ sampledParts.push(`Upper: ${obsData["sampledDataUpperLimit"]}`);
2140
+ }
2141
+ if (obsData["sampledDataData"]) {
2142
+ sampledParts.push(`Data: ${obsData["sampledDataData"]}`);
2143
+ }
2144
+ obsData["formattedValue"] = sampledParts.join(", ");
2145
+ break;
2146
+ }
2147
+ case "valueRange": {
2148
+ const rangeParts = [];
2149
+ if (obsData["valueRangeLowValue"]) {
2150
+ rangeParts.push(`${obsData["valueRangeLowValue"]}${obsData["valueRangeLowUnit"] ? " " + obsData["valueRangeLowUnit"] : ""}`);
2151
+ }
2152
+ if (obsData["valueRangeHighValue"]) {
2153
+ rangeParts.push(`${obsData["valueRangeHighValue"]}${obsData["valueRangeHighUnit"] ? " " + obsData["valueRangeHighUnit"] : ""}`);
2154
+ }
2155
+ obsData["formattedValue"] = rangeParts.join(" - ");
2156
+ break;
2157
+ }
2158
+ case "valueRatio": {
2159
+ const numerator = obsData["valueRatioNumeratorValue"] ? `${obsData["valueRatioNumeratorValue"]}${obsData["valueRatioNumeratorUnit"] ? " " + obsData["valueRatioNumeratorUnit"] : ""}` : "";
2160
+ const denominator = obsData["valueRatioDenominatorValue"] ? `${obsData["valueRatioDenominatorValue"]}${obsData["valueRatioDenominatorUnit"] ? " " + obsData["valueRatioDenominatorUnit"] : ""}` : "";
2161
+ if (numerator && denominator) {
2162
+ obsData["formattedValue"] = `${numerator} / ${denominator}`;
2163
+ } else if (numerator) {
2164
+ obsData["formattedValue"] = numerator;
2165
+ }
2166
+ break;
2167
+ }
2168
+ default:
2169
+ obsData["formattedValue"] = obsData["value"] ?? "";
2170
+ break;
2171
+ }
2172
+ if (obsData["referenceRangeLow"]) {
2173
+ obsData["referenceRange"] = obsData["referenceRangeLow"] + " " + obsData["referenceRangeLowUnit"];
2174
+ }
2175
+ if (obsData["referenceRangeHigh"]) {
2176
+ if (obsData["referenceRange"]) {
2177
+ obsData["referenceRange"] += " - ";
2178
+ } else {
2179
+ obsData["referenceRange"] = "";
2180
+ }
2181
+ obsData["referenceRange"] += obsData["referenceRangeHigh"] + " " + obsData["referenceRangeHighUnit"];
2182
+ }
2183
+ if (obsData["referenceRangeAgeLowValue"] || obsData["referenceRangeAgeHighValue"]) {
2184
+ const ageParts = [];
2185
+ if (obsData["referenceRangeAgeLowValue"]) {
2186
+ ageParts.push(`${obsData["referenceRangeAgeLowValue"]}${obsData["referenceRangeAgeLowUnit"] ? " " + obsData["referenceRangeAgeLowUnit"] : ""}`);
2187
+ }
2188
+ if (obsData["referenceRangeAgeHighValue"]) {
2189
+ ageParts.push(`${obsData["referenceRangeAgeHighValue"]}${obsData["referenceRangeAgeHighUnit"] ? " " + obsData["referenceRangeAgeHighUnit"] : ""}`);
2190
+ }
2191
+ if (obsData["referenceRange"]) {
2192
+ obsData["referenceRange"] += ` (Age: ${ageParts.join(" - ")})`;
2193
+ } else {
2194
+ obsData["referenceRange"] = `Age: ${ageParts.join(" - ")}`;
2195
+ }
2196
+ }
2197
+ }
2198
+ /**
2199
+ * Helper function to extract observation field data
2200
+ * @param column - Column data from the summary
2201
+ * @param targetData - Record to populate with extracted data
2202
+ */
2203
+ extractSummaryObservationFields(column, targetData) {
2204
+ switch (column.title) {
2205
+ case "Labs Name":
2206
+ targetData["code"] = column.text?.div ?? "";
2207
+ break;
2208
+ case "effectiveDateTime":
2209
+ targetData["effectiveDateTime"] = column.text?.div ?? "";
2210
+ break;
2211
+ case "effectivePeriod.start":
2212
+ targetData["effectivePeriodStart"] = column.text?.div ?? "";
2213
+ break;
2214
+ case "effectivePeriod.end":
2215
+ targetData["effectivePeriodEnd"] = column.text?.div ?? "";
2216
+ break;
2217
+ // valueQuantity
2218
+ case "valueQuantity.value":
2219
+ targetData["value"] = column.text?.div ?? "";
2220
+ targetData["valueType"] = "valueQuantity";
2221
+ break;
2222
+ case "valueQuantity.unit":
2223
+ targetData["unit"] = column.text?.div ?? "";
2224
+ break;
2225
+ // valueCodeableConcept
2226
+ case "valueCodeableConcept.text":
2227
+ targetData["value"] = column.text?.div ?? "";
2228
+ targetData["valueType"] = "valueCodeableConcept";
2229
+ break;
2230
+ case "valueCodeableConcept.coding.display":
2231
+ if (!targetData["value"]) {
2232
+ targetData["value"] = column.text?.div ?? "";
2233
+ targetData["valueType"] = "valueCodeableConcept";
2234
+ }
2235
+ break;
2236
+ // valueString
2237
+ case "valueString":
2238
+ targetData["value"] = column.text?.div ?? "";
2239
+ targetData["valueType"] = "valueString";
2240
+ break;
2241
+ // valueBoolean
2242
+ case "valueBoolean":
2243
+ targetData["value"] = column.text?.div ?? "";
2244
+ targetData["valueType"] = "valueBoolean";
2245
+ break;
2246
+ // valueInteger
2247
+ case "valueInteger":
2248
+ targetData["value"] = column.text?.div ?? "";
2249
+ targetData["valueType"] = "valueInteger";
2250
+ break;
2251
+ // valueDateTime
2252
+ case "valueDateTime":
2253
+ targetData["value"] = column.text?.div ?? "";
2254
+ targetData["valueType"] = "valueDateTime";
2255
+ break;
2256
+ // valuePeriod
2257
+ case "valuePeriod.start":
2258
+ targetData["valuePeriodStart"] = column.text?.div ?? "";
2259
+ targetData["valueType"] = "valuePeriod";
2260
+ break;
2261
+ case "valuePeriod.end":
2262
+ targetData["valuePeriodEnd"] = column.text?.div ?? "";
2263
+ targetData["valueType"] = "valuePeriod";
2264
+ break;
2265
+ // valueTime
2266
+ case "valueTime":
2267
+ targetData["value"] = column.text?.div ?? "";
2268
+ targetData["valueType"] = "valueTime";
2269
+ break;
2270
+ // valueSampledData
2271
+ case "valueSampledData.origin.value":
2272
+ targetData["sampledDataOriginValue"] = column.text?.div ?? "";
2273
+ targetData["valueType"] = "valueSampledData";
2274
+ break;
2275
+ case "valueSampledData.origin.unit":
2276
+ targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
2277
+ break;
2278
+ case "valueSampledData.period":
2279
+ targetData["sampledDataPeriod"] = column.text?.div ?? "";
2280
+ break;
2281
+ case "valueSampledData.factor":
2282
+ targetData["sampledDataFactor"] = column.text?.div ?? "";
2283
+ break;
2284
+ case "valueSampledData.lowerLimit":
2285
+ targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
2286
+ break;
2287
+ case "valueSampledData.upperLimit":
2288
+ targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
2289
+ break;
2290
+ case "valueSampledData.data":
2291
+ targetData["sampledDataData"] = column.text?.div ?? "";
2292
+ break;
2293
+ // valueRange
2294
+ case "valueRange.low.value":
2295
+ targetData["valueRangeLowValue"] = column.text?.div ?? "";
2296
+ targetData["valueType"] = "valueRange";
2297
+ break;
2298
+ case "valueRange.low.unit":
2299
+ targetData["valueRangeLowUnit"] = column.text?.div ?? "";
2300
+ break;
2301
+ case "valueRange.high.value":
2302
+ targetData["valueRangeHighValue"] = column.text?.div ?? "";
2303
+ break;
2304
+ case "valueRange.high.unit":
2305
+ targetData["valueRangeHighUnit"] = column.text?.div ?? "";
2306
+ break;
2307
+ // valueRatio
2308
+ case "valueRatio.numerator.value":
2309
+ targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
2310
+ targetData["valueType"] = "valueRatio";
2311
+ break;
2312
+ case "valueRatio.numerator.unit":
2313
+ targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
2314
+ break;
2315
+ case "valueRatio.denominator.value":
2316
+ targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
2317
+ break;
2318
+ case "valueRatio.denominator.unit":
2319
+ targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
2320
+ break;
2321
+ // referenceRange
2322
+ case "referenceRange.low.value":
2323
+ targetData["referenceRangeLow"] = column.text?.div ?? "";
2324
+ break;
2325
+ case "referenceRange.low.unit":
2326
+ targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
2327
+ break;
2328
+ case "referenceRange.high.value":
2329
+ targetData["referenceRangeHigh"] = column.text?.div ?? "";
2330
+ break;
2331
+ case "referenceRange.high.unit":
2332
+ targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
2333
+ break;
2334
+ case "referenceRange.age.low.value":
2335
+ targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
2336
+ break;
2337
+ case "referenceRange.age.low.unit":
2338
+ targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
2339
+ break;
2340
+ case "referenceRange.age.high.value":
2341
+ targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
2342
+ break;
2343
+ case "referenceRange.age.high.unit":
2344
+ targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
2345
+ break;
2346
+ default:
2347
+ break;
2348
+ }
2349
+ }
2350
+ /**
2351
+ * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2352
+ * @param resources - FHIR Composition resources
2353
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2354
+ * @returns HTML string for rendering
2355
+ */
2356
+ generateSummaryNarrative(resources, timezone) {
2357
+ const templateUtilities = new TemplateUtilities(resources);
2358
+ let html = `
2359
+ <div>`;
2360
+ let observationhtml = `
2361
+ <div>
2362
+ <h3>Observations</h3>
2363
+ <table>
2364
+ <thead>
2365
+ <tr>
2366
+ <th>Code</th>
2367
+ <th>Result</th>
2368
+ <th>Reference Range</th>
2369
+ <th>Date</th>
2370
+ </tr>
2371
+ </thead>
2372
+ <tbody>`;
2373
+ let diagnosticReporthtml = `
2374
+ <div>
2375
+ <h3>Diagnostic Reports</h3>
2376
+ <table>
2377
+ <thead>
2378
+ <tr>
2379
+ <th>Report</th>
2380
+ <th>Performer</th>
2381
+ <th>Issued</th>
2382
+ </tr>
2383
+ </thead>
2384
+ <tbody>`;
2385
+ let observationExists = false;
2386
+ let diagnosticReportExists = false;
2387
+ for (const resourceItem of resources) {
2388
+ for (const rowData of resourceItem.section ?? []) {
2389
+ const data = {};
2390
+ const components = [];
2391
+ for (const columnData of rowData.section ?? []) {
2392
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2393
+ if (columnData.text?.div === "Observation.component" && columnData.section) {
2394
+ for (const componentSection of columnData.section) {
2395
+ const componentData = {};
2396
+ for (const nestedColumn of componentSection.section ?? []) {
2397
+ this.extractSummaryObservationFields(nestedColumn, componentData);
2398
+ }
2399
+ if (Object.keys(componentData).length > 0) {
2400
+ components.push(componentData);
2401
+ }
2402
+ }
2403
+ } else {
2404
+ this.extractSummaryObservationFields(columnData, data);
2405
+ }
2406
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2407
+ switch (columnData.title) {
2408
+ case "Diagnostic Report Name":
2409
+ data["report"] = columnData.text?.div ?? "";
2410
+ break;
2411
+ case "Performer":
2412
+ data["performer"] = columnData.text?.div ?? "";
2413
+ break;
2414
+ case "Issued Date":
2415
+ data["issued"] = columnData.text?.div ?? "";
2416
+ break;
2417
+ case "Status":
2418
+ data["status"] = columnData.text?.div ?? "";
2419
+ break;
2420
+ default:
2421
+ break;
2422
+ }
2423
+ }
2424
+ }
2425
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2426
+ observationExists = true;
2427
+ let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
2428
+ if (!date && data["effectivePeriodStart"]) {
2429
+ date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2430
+ if (data["effectivePeriodEnd"]) {
2431
+ date += " - " + templateUtilities.renderTime(data["effectivePeriodEnd"], timezone);
2432
+ }
2433
+ }
2434
+ if (components.length > 0) {
2435
+ const groupName = data["code"] ?? "";
2436
+ for (const component of components) {
2437
+ this.formatSummaryObservationData(component);
2438
+ observationhtml += `
2439
+ <tr>
2440
+ <td>${groupName ? groupName + " - " : ""}${component["code"] ?? "-"}</td>
2441
+ <td>${component["formattedValue"] ?? "-"}</td>
2442
+ <td>${component["referenceRange"]?.trim() ?? "-"}</td>
2443
+ <td>${date ?? "-"}</td>
2444
+ </tr>`;
2445
+ }
2446
+ } else {
2447
+ this.formatSummaryObservationData(data);
2448
+ observationhtml += `
2449
+ <tr>
2450
+ <td>${data["code"] ?? "-"}</td>
2451
+ <td>${data["formattedValue"] ?? "-"}</td>
2452
+ <td>${data["referenceRange"]?.trim() ?? "-"}</td>
2453
+ <td>${date ?? "-"}</td>
2454
+ </tr>`;
2455
+ }
2456
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2457
+ if (data["status"] === "final") {
2458
+ diagnosticReportExists = true;
2459
+ diagnosticReporthtml += `
2460
+ <tr>
2461
+ <td>${data["report"] ?? "-"}</td>
2462
+ <td>${data["performer"] ?? "-"}</td>
2463
+ <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
2464
+ </tr>`;
2465
+ }
2466
+ }
2467
+ }
2468
+ }
2469
+ if (observationExists) {
2470
+ html += observationhtml;
2471
+ html += `
2472
+ </tbody>
2473
+ </table>
2474
+ </div>`;
2475
+ }
2476
+ if (diagnosticReportExists) {
2477
+ html += diagnosticReporthtml;
2478
+ html += `
2479
+ </tbody>
2480
+ </table>
2481
+ </div>`;
2482
+ }
2483
+ html += `
2484
+ </div>`;
2485
+ return html;
2486
+ }
2091
2487
  /**
2092
2488
  * Internal static implementation that actually generates the narrative
2093
2489
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
@@ -2229,6 +2625,57 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
2229
2625
  });
2230
2626
  return _HistoryOfProceduresTemplate.generateStaticNarrative(resources, timezone);
2231
2627
  }
2628
+ /**
2629
+ * Generate HTML narrative for Procedure resources using summary
2630
+ * @param resources - FHIR Composition resources
2631
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2632
+ * @returns HTML string for rendering
2633
+ */
2634
+ generateSummaryNarrative(resources, timezone) {
2635
+ const templateUtilities = new TemplateUtilities(resources);
2636
+ let html = `
2637
+ <div>
2638
+ <table>
2639
+ <thead>
2640
+ <tr>
2641
+ <th>Procedure</th>
2642
+ <th>Performer</th>
2643
+ <th>Date</th>
2644
+ </tr>
2645
+ </thead>
2646
+ <tbody>`;
2647
+ for (const resourceItem of resources) {
2648
+ for (const rowData of resourceItem.section ?? []) {
2649
+ const data = {};
2650
+ for (const columnData of rowData.section ?? []) {
2651
+ switch (columnData.title) {
2652
+ case "Procedure Name":
2653
+ data["procedure"] = columnData.text?.div ?? "";
2654
+ break;
2655
+ case "Performer":
2656
+ data["performer"] = columnData.text?.div ?? "";
2657
+ break;
2658
+ case "Performed Date":
2659
+ data["date"] = columnData.text?.div ?? "";
2660
+ break;
2661
+ default:
2662
+ break;
2663
+ }
2664
+ }
2665
+ html += `
2666
+ <tr>
2667
+ <td>${data["procedure"] ?? "-"}</td>
2668
+ <td>${data["performer"] ?? "-"}</td>
2669
+ <td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
2670
+ </tr>`;
2671
+ }
2672
+ }
2673
+ html += `
2674
+ </tbody>
2675
+ </table>
2676
+ </div>`;
2677
+ return html;
2678
+ }
2232
2679
  /**
2233
2680
  * Internal static implementation that actually generates the narrative
2234
2681
  * @param resources - FHIR Procedure resources
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) {
@@ -2060,6 +2062,400 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2060
2062
  generateNarrative(resources, timezone) {
2061
2063
  return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2062
2064
  }
2065
+ /**
2066
+ * Helper function to format observation data fields
2067
+ * @param obsData - Record containing observation data fields
2068
+ */
2069
+ formatSummaryObservationData(obsData) {
2070
+ const valueType = obsData["valueType"];
2071
+ switch (valueType) {
2072
+ case "valueQuantity":
2073
+ if (obsData["value"] && obsData["unit"]) {
2074
+ obsData["formattedValue"] = `${obsData["value"]} ${obsData["unit"]}`;
2075
+ } else if (obsData["value"]) {
2076
+ obsData["formattedValue"] = obsData["value"];
2077
+ }
2078
+ break;
2079
+ case "valueCodeableConcept":
2080
+ case "valueString":
2081
+ case "valueBoolean":
2082
+ case "valueInteger":
2083
+ case "valueDateTime":
2084
+ case "valueTime":
2085
+ obsData["formattedValue"] = obsData["value"] ?? "";
2086
+ break;
2087
+ case "valuePeriod":
2088
+ if (obsData["valuePeriodStart"] && obsData["valuePeriodEnd"]) {
2089
+ obsData["formattedValue"] = `${obsData["valuePeriodStart"]} - ${obsData["valuePeriodEnd"]}`;
2090
+ } else if (obsData["valuePeriodStart"]) {
2091
+ obsData["formattedValue"] = `From ${obsData["valuePeriodStart"]}`;
2092
+ } else if (obsData["valuePeriodEnd"]) {
2093
+ obsData["formattedValue"] = `Until ${obsData["valuePeriodEnd"]}`;
2094
+ }
2095
+ break;
2096
+ case "valueSampledData": {
2097
+ const sampledParts = [];
2098
+ if (obsData["sampledDataOriginValue"]) {
2099
+ sampledParts.push(`Origin: ${obsData["sampledDataOriginValue"]}${obsData["sampledDataOriginUnit"] ? " " + obsData["sampledDataOriginUnit"] : ""}`);
2100
+ }
2101
+ if (obsData["sampledDataPeriod"]) {
2102
+ sampledParts.push(`Period: ${obsData["sampledDataPeriod"]}`);
2103
+ }
2104
+ if (obsData["sampledDataFactor"]) {
2105
+ sampledParts.push(`Factor: ${obsData["sampledDataFactor"]}`);
2106
+ }
2107
+ if (obsData["sampledDataLowerLimit"]) {
2108
+ sampledParts.push(`Lower: ${obsData["sampledDataLowerLimit"]}`);
2109
+ }
2110
+ if (obsData["sampledDataUpperLimit"]) {
2111
+ sampledParts.push(`Upper: ${obsData["sampledDataUpperLimit"]}`);
2112
+ }
2113
+ if (obsData["sampledDataData"]) {
2114
+ sampledParts.push(`Data: ${obsData["sampledDataData"]}`);
2115
+ }
2116
+ obsData["formattedValue"] = sampledParts.join(", ");
2117
+ break;
2118
+ }
2119
+ case "valueRange": {
2120
+ const rangeParts = [];
2121
+ if (obsData["valueRangeLowValue"]) {
2122
+ rangeParts.push(`${obsData["valueRangeLowValue"]}${obsData["valueRangeLowUnit"] ? " " + obsData["valueRangeLowUnit"] : ""}`);
2123
+ }
2124
+ if (obsData["valueRangeHighValue"]) {
2125
+ rangeParts.push(`${obsData["valueRangeHighValue"]}${obsData["valueRangeHighUnit"] ? " " + obsData["valueRangeHighUnit"] : ""}`);
2126
+ }
2127
+ obsData["formattedValue"] = rangeParts.join(" - ");
2128
+ break;
2129
+ }
2130
+ case "valueRatio": {
2131
+ const numerator = obsData["valueRatioNumeratorValue"] ? `${obsData["valueRatioNumeratorValue"]}${obsData["valueRatioNumeratorUnit"] ? " " + obsData["valueRatioNumeratorUnit"] : ""}` : "";
2132
+ const denominator = obsData["valueRatioDenominatorValue"] ? `${obsData["valueRatioDenominatorValue"]}${obsData["valueRatioDenominatorUnit"] ? " " + obsData["valueRatioDenominatorUnit"] : ""}` : "";
2133
+ if (numerator && denominator) {
2134
+ obsData["formattedValue"] = `${numerator} / ${denominator}`;
2135
+ } else if (numerator) {
2136
+ obsData["formattedValue"] = numerator;
2137
+ }
2138
+ break;
2139
+ }
2140
+ default:
2141
+ obsData["formattedValue"] = obsData["value"] ?? "";
2142
+ break;
2143
+ }
2144
+ if (obsData["referenceRangeLow"]) {
2145
+ obsData["referenceRange"] = obsData["referenceRangeLow"] + " " + obsData["referenceRangeLowUnit"];
2146
+ }
2147
+ if (obsData["referenceRangeHigh"]) {
2148
+ if (obsData["referenceRange"]) {
2149
+ obsData["referenceRange"] += " - ";
2150
+ } else {
2151
+ obsData["referenceRange"] = "";
2152
+ }
2153
+ obsData["referenceRange"] += obsData["referenceRangeHigh"] + " " + obsData["referenceRangeHighUnit"];
2154
+ }
2155
+ if (obsData["referenceRangeAgeLowValue"] || obsData["referenceRangeAgeHighValue"]) {
2156
+ const ageParts = [];
2157
+ if (obsData["referenceRangeAgeLowValue"]) {
2158
+ ageParts.push(`${obsData["referenceRangeAgeLowValue"]}${obsData["referenceRangeAgeLowUnit"] ? " " + obsData["referenceRangeAgeLowUnit"] : ""}`);
2159
+ }
2160
+ if (obsData["referenceRangeAgeHighValue"]) {
2161
+ ageParts.push(`${obsData["referenceRangeAgeHighValue"]}${obsData["referenceRangeAgeHighUnit"] ? " " + obsData["referenceRangeAgeHighUnit"] : ""}`);
2162
+ }
2163
+ if (obsData["referenceRange"]) {
2164
+ obsData["referenceRange"] += ` (Age: ${ageParts.join(" - ")})`;
2165
+ } else {
2166
+ obsData["referenceRange"] = `Age: ${ageParts.join(" - ")}`;
2167
+ }
2168
+ }
2169
+ }
2170
+ /**
2171
+ * Helper function to extract observation field data
2172
+ * @param column - Column data from the summary
2173
+ * @param targetData - Record to populate with extracted data
2174
+ */
2175
+ extractSummaryObservationFields(column, targetData) {
2176
+ switch (column.title) {
2177
+ case "Labs Name":
2178
+ targetData["code"] = column.text?.div ?? "";
2179
+ break;
2180
+ case "effectiveDateTime":
2181
+ targetData["effectiveDateTime"] = column.text?.div ?? "";
2182
+ break;
2183
+ case "effectivePeriod.start":
2184
+ targetData["effectivePeriodStart"] = column.text?.div ?? "";
2185
+ break;
2186
+ case "effectivePeriod.end":
2187
+ targetData["effectivePeriodEnd"] = column.text?.div ?? "";
2188
+ break;
2189
+ // valueQuantity
2190
+ case "valueQuantity.value":
2191
+ targetData["value"] = column.text?.div ?? "";
2192
+ targetData["valueType"] = "valueQuantity";
2193
+ break;
2194
+ case "valueQuantity.unit":
2195
+ targetData["unit"] = column.text?.div ?? "";
2196
+ break;
2197
+ // valueCodeableConcept
2198
+ case "valueCodeableConcept.text":
2199
+ targetData["value"] = column.text?.div ?? "";
2200
+ targetData["valueType"] = "valueCodeableConcept";
2201
+ break;
2202
+ case "valueCodeableConcept.coding.display":
2203
+ if (!targetData["value"]) {
2204
+ targetData["value"] = column.text?.div ?? "";
2205
+ targetData["valueType"] = "valueCodeableConcept";
2206
+ }
2207
+ break;
2208
+ // valueString
2209
+ case "valueString":
2210
+ targetData["value"] = column.text?.div ?? "";
2211
+ targetData["valueType"] = "valueString";
2212
+ break;
2213
+ // valueBoolean
2214
+ case "valueBoolean":
2215
+ targetData["value"] = column.text?.div ?? "";
2216
+ targetData["valueType"] = "valueBoolean";
2217
+ break;
2218
+ // valueInteger
2219
+ case "valueInteger":
2220
+ targetData["value"] = column.text?.div ?? "";
2221
+ targetData["valueType"] = "valueInteger";
2222
+ break;
2223
+ // valueDateTime
2224
+ case "valueDateTime":
2225
+ targetData["value"] = column.text?.div ?? "";
2226
+ targetData["valueType"] = "valueDateTime";
2227
+ break;
2228
+ // valuePeriod
2229
+ case "valuePeriod.start":
2230
+ targetData["valuePeriodStart"] = column.text?.div ?? "";
2231
+ targetData["valueType"] = "valuePeriod";
2232
+ break;
2233
+ case "valuePeriod.end":
2234
+ targetData["valuePeriodEnd"] = column.text?.div ?? "";
2235
+ targetData["valueType"] = "valuePeriod";
2236
+ break;
2237
+ // valueTime
2238
+ case "valueTime":
2239
+ targetData["value"] = column.text?.div ?? "";
2240
+ targetData["valueType"] = "valueTime";
2241
+ break;
2242
+ // valueSampledData
2243
+ case "valueSampledData.origin.value":
2244
+ targetData["sampledDataOriginValue"] = column.text?.div ?? "";
2245
+ targetData["valueType"] = "valueSampledData";
2246
+ break;
2247
+ case "valueSampledData.origin.unit":
2248
+ targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
2249
+ break;
2250
+ case "valueSampledData.period":
2251
+ targetData["sampledDataPeriod"] = column.text?.div ?? "";
2252
+ break;
2253
+ case "valueSampledData.factor":
2254
+ targetData["sampledDataFactor"] = column.text?.div ?? "";
2255
+ break;
2256
+ case "valueSampledData.lowerLimit":
2257
+ targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
2258
+ break;
2259
+ case "valueSampledData.upperLimit":
2260
+ targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
2261
+ break;
2262
+ case "valueSampledData.data":
2263
+ targetData["sampledDataData"] = column.text?.div ?? "";
2264
+ break;
2265
+ // valueRange
2266
+ case "valueRange.low.value":
2267
+ targetData["valueRangeLowValue"] = column.text?.div ?? "";
2268
+ targetData["valueType"] = "valueRange";
2269
+ break;
2270
+ case "valueRange.low.unit":
2271
+ targetData["valueRangeLowUnit"] = column.text?.div ?? "";
2272
+ break;
2273
+ case "valueRange.high.value":
2274
+ targetData["valueRangeHighValue"] = column.text?.div ?? "";
2275
+ break;
2276
+ case "valueRange.high.unit":
2277
+ targetData["valueRangeHighUnit"] = column.text?.div ?? "";
2278
+ break;
2279
+ // valueRatio
2280
+ case "valueRatio.numerator.value":
2281
+ targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
2282
+ targetData["valueType"] = "valueRatio";
2283
+ break;
2284
+ case "valueRatio.numerator.unit":
2285
+ targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
2286
+ break;
2287
+ case "valueRatio.denominator.value":
2288
+ targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
2289
+ break;
2290
+ case "valueRatio.denominator.unit":
2291
+ targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
2292
+ break;
2293
+ // referenceRange
2294
+ case "referenceRange.low.value":
2295
+ targetData["referenceRangeLow"] = column.text?.div ?? "";
2296
+ break;
2297
+ case "referenceRange.low.unit":
2298
+ targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
2299
+ break;
2300
+ case "referenceRange.high.value":
2301
+ targetData["referenceRangeHigh"] = column.text?.div ?? "";
2302
+ break;
2303
+ case "referenceRange.high.unit":
2304
+ targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
2305
+ break;
2306
+ case "referenceRange.age.low.value":
2307
+ targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
2308
+ break;
2309
+ case "referenceRange.age.low.unit":
2310
+ targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
2311
+ break;
2312
+ case "referenceRange.age.high.value":
2313
+ targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
2314
+ break;
2315
+ case "referenceRange.age.high.unit":
2316
+ targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
2317
+ break;
2318
+ default:
2319
+ break;
2320
+ }
2321
+ }
2322
+ /**
2323
+ * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2324
+ * @param resources - FHIR Composition resources
2325
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2326
+ * @returns HTML string for rendering
2327
+ */
2328
+ generateSummaryNarrative(resources, timezone) {
2329
+ const templateUtilities = new TemplateUtilities(resources);
2330
+ let html = `
2331
+ <div>`;
2332
+ let observationhtml = `
2333
+ <div>
2334
+ <h3>Observations</h3>
2335
+ <table>
2336
+ <thead>
2337
+ <tr>
2338
+ <th>Code</th>
2339
+ <th>Result</th>
2340
+ <th>Reference Range</th>
2341
+ <th>Date</th>
2342
+ </tr>
2343
+ </thead>
2344
+ <tbody>`;
2345
+ let diagnosticReporthtml = `
2346
+ <div>
2347
+ <h3>Diagnostic Reports</h3>
2348
+ <table>
2349
+ <thead>
2350
+ <tr>
2351
+ <th>Report</th>
2352
+ <th>Performer</th>
2353
+ <th>Issued</th>
2354
+ </tr>
2355
+ </thead>
2356
+ <tbody>`;
2357
+ let observationExists = false;
2358
+ let diagnosticReportExists = false;
2359
+ for (const resourceItem of resources) {
2360
+ for (const rowData of resourceItem.section ?? []) {
2361
+ const data = {};
2362
+ const components = [];
2363
+ for (const columnData of rowData.section ?? []) {
2364
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2365
+ if (columnData.text?.div === "Observation.component" && columnData.section) {
2366
+ for (const componentSection of columnData.section) {
2367
+ const componentData = {};
2368
+ for (const nestedColumn of componentSection.section ?? []) {
2369
+ this.extractSummaryObservationFields(nestedColumn, componentData);
2370
+ }
2371
+ if (Object.keys(componentData).length > 0) {
2372
+ components.push(componentData);
2373
+ }
2374
+ }
2375
+ } else {
2376
+ this.extractSummaryObservationFields(columnData, data);
2377
+ }
2378
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2379
+ switch (columnData.title) {
2380
+ case "Diagnostic Report Name":
2381
+ data["report"] = columnData.text?.div ?? "";
2382
+ break;
2383
+ case "Performer":
2384
+ data["performer"] = columnData.text?.div ?? "";
2385
+ break;
2386
+ case "Issued Date":
2387
+ data["issued"] = columnData.text?.div ?? "";
2388
+ break;
2389
+ case "Status":
2390
+ data["status"] = columnData.text?.div ?? "";
2391
+ break;
2392
+ default:
2393
+ break;
2394
+ }
2395
+ }
2396
+ }
2397
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2398
+ observationExists = true;
2399
+ let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
2400
+ if (!date && data["effectivePeriodStart"]) {
2401
+ date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2402
+ if (data["effectivePeriodEnd"]) {
2403
+ date += " - " + templateUtilities.renderTime(data["effectivePeriodEnd"], timezone);
2404
+ }
2405
+ }
2406
+ if (components.length > 0) {
2407
+ const groupName = data["code"] ?? "";
2408
+ for (const component of components) {
2409
+ this.formatSummaryObservationData(component);
2410
+ observationhtml += `
2411
+ <tr>
2412
+ <td>${groupName ? groupName + " - " : ""}${component["code"] ?? "-"}</td>
2413
+ <td>${component["formattedValue"] ?? "-"}</td>
2414
+ <td>${component["referenceRange"]?.trim() ?? "-"}</td>
2415
+ <td>${date ?? "-"}</td>
2416
+ </tr>`;
2417
+ }
2418
+ } else {
2419
+ this.formatSummaryObservationData(data);
2420
+ observationhtml += `
2421
+ <tr>
2422
+ <td>${data["code"] ?? "-"}</td>
2423
+ <td>${data["formattedValue"] ?? "-"}</td>
2424
+ <td>${data["referenceRange"]?.trim() ?? "-"}</td>
2425
+ <td>${date ?? "-"}</td>
2426
+ </tr>`;
2427
+ }
2428
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2429
+ if (data["status"] === "final") {
2430
+ diagnosticReportExists = true;
2431
+ diagnosticReporthtml += `
2432
+ <tr>
2433
+ <td>${data["report"] ?? "-"}</td>
2434
+ <td>${data["performer"] ?? "-"}</td>
2435
+ <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
2436
+ </tr>`;
2437
+ }
2438
+ }
2439
+ }
2440
+ }
2441
+ if (observationExists) {
2442
+ html += observationhtml;
2443
+ html += `
2444
+ </tbody>
2445
+ </table>
2446
+ </div>`;
2447
+ }
2448
+ if (diagnosticReportExists) {
2449
+ html += diagnosticReporthtml;
2450
+ html += `
2451
+ </tbody>
2452
+ </table>
2453
+ </div>`;
2454
+ }
2455
+ html += `
2456
+ </div>`;
2457
+ return html;
2458
+ }
2063
2459
  /**
2064
2460
  * Internal static implementation that actually generates the narrative
2065
2461
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
@@ -2201,6 +2597,57 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
2201
2597
  });
2202
2598
  return _HistoryOfProceduresTemplate.generateStaticNarrative(resources, timezone);
2203
2599
  }
2600
+ /**
2601
+ * Generate HTML narrative for Procedure resources using summary
2602
+ * @param resources - FHIR Composition resources
2603
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2604
+ * @returns HTML string for rendering
2605
+ */
2606
+ generateSummaryNarrative(resources, timezone) {
2607
+ const templateUtilities = new TemplateUtilities(resources);
2608
+ let html = `
2609
+ <div>
2610
+ <table>
2611
+ <thead>
2612
+ <tr>
2613
+ <th>Procedure</th>
2614
+ <th>Performer</th>
2615
+ <th>Date</th>
2616
+ </tr>
2617
+ </thead>
2618
+ <tbody>`;
2619
+ for (const resourceItem of resources) {
2620
+ for (const rowData of resourceItem.section ?? []) {
2621
+ const data = {};
2622
+ for (const columnData of rowData.section ?? []) {
2623
+ switch (columnData.title) {
2624
+ case "Procedure Name":
2625
+ data["procedure"] = columnData.text?.div ?? "";
2626
+ break;
2627
+ case "Performer":
2628
+ data["performer"] = columnData.text?.div ?? "";
2629
+ break;
2630
+ case "Performed Date":
2631
+ data["date"] = columnData.text?.div ?? "";
2632
+ break;
2633
+ default:
2634
+ break;
2635
+ }
2636
+ }
2637
+ html += `
2638
+ <tr>
2639
+ <td>${data["procedure"] ?? "-"}</td>
2640
+ <td>${data["performer"] ?? "-"}</td>
2641
+ <td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
2642
+ </tr>`;
2643
+ }
2644
+ }
2645
+ html += `
2646
+ </tbody>
2647
+ </table>
2648
+ </div>`;
2649
+ return html;
2650
+ }
2204
2651
  /**
2205
2652
  * Internal static implementation that actually generates the narrative
2206
2653
  * @param resources - FHIR Procedure resources
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.23",
3
+ "version": "1.0.24",
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",