@imranq2/fhirpatientsummary 1.0.33 → 1.0.35

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/README.md CHANGED
@@ -64,6 +64,44 @@ console.log(JSON.stringify(bundle, null, 2));
64
64
  - Use `makeSectionAsync(sectionType, resources, timezone)` to add each IPS section, or use `read_bundle(fhirBundle, timezone)` to extract all supported sections from a FHIR Bundle.
65
65
  - Use `build_bundle` to generate the final FHIR Bundle.
66
66
 
67
+ ## Patient Summary Composition
68
+
69
+ When generating patient summaries from FHIR Bundles, the library can use pre-computed summary compositions instead of raw resources. Summary compositions are specially structured FHIR Composition resources that contain curated, filtered, or aggregated data for specific sections.
70
+
71
+ ### How Summary Compositions Work
72
+
73
+ The `readBundleAsync` method supports a `useSummaryCompositions` parameter. When enabled, the library follows this priority order when processing each IPS section:
74
+
75
+ 1. **IPS-Specific Composition** (highest priority): Compositions with IPS-specific type codes (e.g., `ips_patient_summary_document`, `ips_vital_summary_document`)
76
+ 2. **Summary Composition** (medium priority): Compositions with summary type codes (e.g., `allergy_summary_document`, `condition_summary_document`, `medication_summary_document`)
77
+ 3. **Raw Resources** (fallback): Individual FHIR resources when no composition is available
78
+
79
+ This priority order ensures that the most refined and curated data is used when available, while still supporting raw resource processing as a fallback.
80
+
81
+ ### Environment Variables for enabling Composition Summary
82
+
83
+ The following environment variables can be used to configure the behavior of the patient summary generator:
84
+
85
+ #### SUMMARY_IPS_COMPOSITION_SECTIONS
86
+
87
+ Controls which IPS sections should include IPS-specific composition.
88
+
89
+ - **Default**: Disabled
90
+ - **Format**: Comma-separated list of section names
91
+ - **Example**: `SUMMARY_IPS_COMPOSITION_SECTIONS=Patient,VitalSignsSection`
92
+
93
+ When set to `all`, all supported sections will use IPS composition. To enable only specific sections, provide a comma-separated list of [section names](src/structures/ips_sections.ts).
94
+
95
+ #### SUMMARY_COMPOSITION_SECTIONS
96
+
97
+ Controls which IPS sections should include summary composition.
98
+
99
+ - **Default**: Disabled
100
+ - **Format**: Comma-separated list of section names
101
+ - **Example**: `SUMMARY_COMPOSITION_SECTIONS=AllergyIntoleranceSection,ProblemSection,MedicationSummarySection`
102
+
103
+ When set to `all`, all supported sections will use summary composition. To enable only specific sections, provide a comma-separated list of [section names](src/structures/ips_sections.ts).
104
+
67
105
  ## Running Tests
68
106
 
69
107
  To run the test suite:
package/dist/index.cjs CHANGED
@@ -189,6 +189,8 @@ var IPSSectionResourceFilters = {
189
189
  };
190
190
  var IPSSectionSummaryCompositionFilter = {
191
191
  ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
192
+ ["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "condition_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
193
+ ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "condition_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
192
194
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
193
195
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
194
196
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
@@ -198,7 +200,8 @@ var IPSSectionSummaryCompositionFilter = {
198
200
  };
199
201
  var IPSSectionSummaryIPSCompositionFilter = {
200
202
  ["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_patient_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
201
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
203
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
204
+ ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_social_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
202
205
  };
203
206
  var IPSSectionResourceHelper = class {
204
207
  static getResourceFilterForSection(section) {
@@ -210,10 +213,16 @@ var IPSSectionResourceHelper = class {
210
213
  return resources.filter(filter);
211
214
  }
212
215
  static getSummaryCompositionFilterForSection(section) {
213
- return IPSSectionSummaryCompositionFilter[section];
216
+ const sectionCompositionEnabled = process.env.SUMMARY_COMPOSITION_SECTIONS ? process.env.SUMMARY_COMPOSITION_SECTIONS.split(",").some(
217
+ (s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
218
+ ) : false;
219
+ return sectionCompositionEnabled ? IPSSectionSummaryCompositionFilter[section] : void 0;
214
220
  }
215
221
  static getSummaryIPSCompositionFilterForSection(section) {
216
- return IPSSectionSummaryIPSCompositionFilter[section];
222
+ const sectionIPSCompositionEnabled = process.env.SUMMARY_IPS_COMPOSITION_SECTIONS ? process.env.SUMMARY_IPS_COMPOSITION_SECTIONS.split(",").some(
223
+ (s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
224
+ ) : false;
225
+ return sectionIPSCompositionEnabled ? IPSSectionSummaryIPSCompositionFilter[section] : void 0;
217
226
  }
218
227
  };
219
228
  function codingMatches(coding, code, system) {
@@ -1478,8 +1487,8 @@ var PatientTemplate = class _PatientTemplate {
1478
1487
  <li><strong>Date of Birth:</strong>${data["Date of Birth"] || ""}</li>
1479
1488
  <li><strong>Telecom:</strong>${data["Telecom"] || ""}</li>
1480
1489
  <li><strong>Address(es):</strong>${data["Address"] || ""}</li>
1481
- <li><strong>Marital Status:</strong> ${data["Marital Status"] || ""}</li>
1482
- <li><strong>Deceased:</strong>${data["Deceased"] || ""}</li>
1490
+ ${data["Marital Status"] ? `<li><strong>Marital Status:</strong>${data["Marital Status"]}</li>` : ""}
1491
+ ${data["Deceased"] ? `<li><strong>Deceased:</strong>${data["Deceased"]}</li>` : ""}
1483
1492
  <li><strong>Language(s):</strong>${data["Communication"] || ""}</li>
1484
1493
  </ul>
1485
1494
  </div>`;
@@ -1495,6 +1504,7 @@ var PatientTemplate = class _PatientTemplate {
1495
1504
  static generateStaticNarrative(resources, timezone) {
1496
1505
  const templateUtilities = new TemplateUtilities(resources);
1497
1506
  const combinedPatient = this.combinePatients(resources);
1507
+ const deceasedText = this.renderDeceased(combinedPatient);
1498
1508
  let html = `<p>This section merges all Patient resources into a single combined patient record, preferring non-empty values for each field.</p>`;
1499
1509
  html += `<div>
1500
1510
  <ul>
@@ -1503,8 +1513,8 @@ var PatientTemplate = class _PatientTemplate {
1503
1513
  <li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
1504
1514
  <li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
1505
1515
  <li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
1506
- <li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus?.text || ""}</li>
1507
- <li><strong>Deceased:</strong>${this.renderDeceased(combinedPatient)}</li>
1516
+ ${combinedPatient.maritalStatus?.text ? `<li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus.text}</li>` : ""}
1517
+ ${deceasedText ? `<li><strong>Deceased:</strong>${deceasedText}</li>` : ""}
1508
1518
  <li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
1509
1519
  </ul>
1510
1520
  </div>`;
@@ -2446,6 +2456,59 @@ var ProblemListTemplate = class _ProblemListTemplate {
2446
2456
  generateNarrative(resources, timezone) {
2447
2457
  return _ProblemListTemplate.generateStaticNarrative(resources, timezone);
2448
2458
  }
2459
+ /**
2460
+ * Generate HTML narrative for Condition resources using summary
2461
+ * @param resources - FHIR Composition resources
2462
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2463
+ * @returns HTML string for rendering
2464
+ */
2465
+ generateSummaryNarrative(resources, timezone) {
2466
+ const templateUtilities = new TemplateUtilities(resources);
2467
+ let isSummaryCreated = false;
2468
+ let html = `<p>This list includes patient problems, sorted by recorded date (most recent first)</p>
2469
+ <div>
2470
+ <table>
2471
+ <thead>
2472
+ <tr>
2473
+ <th>Problem</th>
2474
+ <th>Code (System)</th>
2475
+ <th>Is Chronic</th>
2476
+ <th>Onset Date</th>
2477
+ <th>Last Confirmed Date</th>
2478
+ </tr>
2479
+ </thead>
2480
+ <tbody>`;
2481
+ for (const resourceItem of resources) {
2482
+ for (const rowData of resourceItem.section ?? []) {
2483
+ const sectionCodeableConcept = rowData.code;
2484
+ const data = {};
2485
+ for (const columnData of rowData.section ?? []) {
2486
+ if (columnData.title) {
2487
+ data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2488
+ }
2489
+ }
2490
+ if (data["Condition Name"]?.toLowerCase() === "unknown") {
2491
+ continue;
2492
+ }
2493
+ if (data["Status"] === "active") {
2494
+ isSummaryCreated = true;
2495
+ html += `
2496
+ <tr>
2497
+ <td>${templateUtilities.capitalizeFirstLetter(data["Condition Name"] ?? "")}</td>
2498
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
2499
+ <td>${data["Is Chronic"] ?? ""}</td>
2500
+ <td>${templateUtilities.renderTime(data["Onset Date"], timezone) ?? ""}</td>
2501
+ <td>${templateUtilities.renderTime(data["Last Confirmed Date"], timezone) ?? ""}</td>
2502
+ </tr>`;
2503
+ }
2504
+ }
2505
+ }
2506
+ html += `
2507
+ </tbody>
2508
+ </table>
2509
+ </div>`;
2510
+ return isSummaryCreated ? html : void 0;
2511
+ }
2449
2512
  /**
2450
2513
  * Internal static implementation that actually generates the narrative
2451
2514
  * @param resources - FHIR Condition resources
@@ -3608,6 +3671,61 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3608
3671
  generateNarrative(resources, timezone) {
3609
3672
  return _SocialHistoryTemplate.generateStaticNarrative(resources, timezone);
3610
3673
  }
3674
+ /**
3675
+ * Generate HTML narrative for social history using summary
3676
+ * @param resources - FHIR Composition resources
3677
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3678
+ * @returns HTML string for rendering
3679
+ */
3680
+ generateSummaryNarrative(resources, timezone) {
3681
+ const templateUtilities = new TemplateUtilities(resources);
3682
+ let isSummaryCreated = false;
3683
+ let html = `<p>This list includes all information about the patient's social history, sorted by effective date (most recent first).</p>
3684
+ `;
3685
+ html += `
3686
+ <div>
3687
+ <table>
3688
+ <thead>
3689
+ <tr>
3690
+ <th>Name</th>
3691
+ <th>Code (System)</th>
3692
+ <th>Result</th>
3693
+ <th>Date</th>
3694
+ <th>Comments</th>
3695
+ </tr>
3696
+ </thead>
3697
+ <tbody>`;
3698
+ for (const resourceItem of resources) {
3699
+ for (const rowData of resourceItem.section ?? []) {
3700
+ const sectionCodeableConcept = rowData.code;
3701
+ const data = {};
3702
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
3703
+ for (const columnData of rowData.section ?? []) {
3704
+ const columnTitle = columnData.title;
3705
+ if (columnTitle) {
3706
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3707
+ }
3708
+ }
3709
+ if (data["Social History Name"]?.toLowerCase() === "unknown") {
3710
+ continue;
3711
+ }
3712
+ isSummaryCreated = true;
3713
+ html += `
3714
+ <tr>
3715
+ <td>${templateUtilities.capitalizeFirstLetter(data["Social History Name"] ?? "")}</td>
3716
+ <td>${data["codeSystem"] ?? ""}</td>
3717
+ <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? ""}</td>
3718
+ <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
3719
+ <td>${data["Notes"] ?? ""}</td>
3720
+ </tr>`;
3721
+ }
3722
+ }
3723
+ html += `
3724
+ </tbody>
3725
+ </table>
3726
+ </div>`;
3727
+ return isSummaryCreated ? html : void 0;
3728
+ }
3611
3729
  /**
3612
3730
  * Internal static implementation that actually generates the narrative
3613
3731
  * @param resources - FHIR Observation resources
@@ -3731,6 +3849,73 @@ var PastHistoryOfIllnessTemplate = class {
3731
3849
  }
3732
3850
  return html;
3733
3851
  }
3852
+ /**
3853
+ * Generate HTML narrative for Condition resources using summary
3854
+ * @param resources - FHIR Composition resources
3855
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3856
+ * @returns HTML string for rendering
3857
+ */
3858
+ generateSummaryNarrative(resources, timezone, now) {
3859
+ const templateUtilities = new TemplateUtilities(resources);
3860
+ let isSummaryCreated = false;
3861
+ const currentDate = now || /* @__PURE__ */ new Date();
3862
+ const fiveYearsAgo = new Date(currentDate);
3863
+ fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
3864
+ let skippedConditions = 0;
3865
+ let html = `<p>This list includes past problems for the patient with a recorded date within the last 5 years, sorted by recorded date (most recent first).</p>
3866
+ <div>
3867
+ <table>
3868
+ <thead>
3869
+ <tr>
3870
+ <th>Problem</th>
3871
+ <th>Code (System)</th>
3872
+ <th>Is Chronic</th>
3873
+ <th>Onset Date</th>
3874
+ <th>Last Confirmed Date</th>
3875
+ <th>Resolved Date</th>
3876
+ </tr>
3877
+ </thead>
3878
+ <tbody>`;
3879
+ for (const resourceItem of resources) {
3880
+ for (const rowData of resourceItem.section ?? []) {
3881
+ const sectionCodeableConcept = rowData.code;
3882
+ const data = {};
3883
+ for (const columnData of rowData.section ?? []) {
3884
+ if (columnData.title) {
3885
+ data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3886
+ }
3887
+ }
3888
+ if (data["Condition Name"]?.toLowerCase() === "unknown") {
3889
+ continue;
3890
+ }
3891
+ if (data["Status"] === "inactive") {
3892
+ if (data["Last Confirmed Date"] && new Date(data["Last Confirmed Date"]) < fiveYearsAgo) {
3893
+ skippedConditions++;
3894
+ continue;
3895
+ }
3896
+ isSummaryCreated = true;
3897
+ html += `
3898
+ <tr>
3899
+ <td>${templateUtilities.capitalizeFirstLetter(data["Condition Name"] ?? "")}</td>
3900
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
3901
+ <td>${data["Is Chronic"] ?? ""}</td>
3902
+ <td>${templateUtilities.renderTime(data["Onset Date"], timezone) ?? ""}</td>
3903
+ <td>${templateUtilities.renderTime(data["Last Confirmed Date"], timezone) ?? ""}</td>
3904
+ <td>${templateUtilities.renderTime(data["Resolved Date"], timezone) ?? ""}</td>
3905
+ </tr>`;
3906
+ }
3907
+ }
3908
+ }
3909
+ html += `
3910
+ </tbody>
3911
+ </table>`;
3912
+ if (skippedConditions > 0) {
3913
+ html += `
3914
+ <p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
3915
+ }
3916
+ html += `</div>`;
3917
+ return isSummaryCreated ? html : void 0;
3918
+ }
3734
3919
  };
3735
3920
 
3736
3921
  // src/narratives/templates/typescript/PlanOfCareTemplate.ts
package/dist/index.js CHANGED
@@ -161,6 +161,8 @@ var IPSSectionResourceFilters = {
161
161
  };
162
162
  var IPSSectionSummaryCompositionFilter = {
163
163
  ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
164
+ ["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "condition_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
165
+ ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "condition_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
164
166
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
165
167
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
166
168
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
@@ -170,7 +172,8 @@ var IPSSectionSummaryCompositionFilter = {
170
172
  };
171
173
  var IPSSectionSummaryIPSCompositionFilter = {
172
174
  ["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_patient_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
173
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
175
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
176
+ ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_social_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
174
177
  };
175
178
  var IPSSectionResourceHelper = class {
176
179
  static getResourceFilterForSection(section) {
@@ -182,10 +185,16 @@ var IPSSectionResourceHelper = class {
182
185
  return resources.filter(filter);
183
186
  }
184
187
  static getSummaryCompositionFilterForSection(section) {
185
- return IPSSectionSummaryCompositionFilter[section];
188
+ const sectionCompositionEnabled = process.env.SUMMARY_COMPOSITION_SECTIONS ? process.env.SUMMARY_COMPOSITION_SECTIONS.split(",").some(
189
+ (s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
190
+ ) : false;
191
+ return sectionCompositionEnabled ? IPSSectionSummaryCompositionFilter[section] : void 0;
186
192
  }
187
193
  static getSummaryIPSCompositionFilterForSection(section) {
188
- return IPSSectionSummaryIPSCompositionFilter[section];
194
+ const sectionIPSCompositionEnabled = process.env.SUMMARY_IPS_COMPOSITION_SECTIONS ? process.env.SUMMARY_IPS_COMPOSITION_SECTIONS.split(",").some(
195
+ (s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
196
+ ) : false;
197
+ return sectionIPSCompositionEnabled ? IPSSectionSummaryIPSCompositionFilter[section] : void 0;
189
198
  }
190
199
  };
191
200
  function codingMatches(coding, code, system) {
@@ -1450,8 +1459,8 @@ var PatientTemplate = class _PatientTemplate {
1450
1459
  <li><strong>Date of Birth:</strong>${data["Date of Birth"] || ""}</li>
1451
1460
  <li><strong>Telecom:</strong>${data["Telecom"] || ""}</li>
1452
1461
  <li><strong>Address(es):</strong>${data["Address"] || ""}</li>
1453
- <li><strong>Marital Status:</strong> ${data["Marital Status"] || ""}</li>
1454
- <li><strong>Deceased:</strong>${data["Deceased"] || ""}</li>
1462
+ ${data["Marital Status"] ? `<li><strong>Marital Status:</strong>${data["Marital Status"]}</li>` : ""}
1463
+ ${data["Deceased"] ? `<li><strong>Deceased:</strong>${data["Deceased"]}</li>` : ""}
1455
1464
  <li><strong>Language(s):</strong>${data["Communication"] || ""}</li>
1456
1465
  </ul>
1457
1466
  </div>`;
@@ -1467,6 +1476,7 @@ var PatientTemplate = class _PatientTemplate {
1467
1476
  static generateStaticNarrative(resources, timezone) {
1468
1477
  const templateUtilities = new TemplateUtilities(resources);
1469
1478
  const combinedPatient = this.combinePatients(resources);
1479
+ const deceasedText = this.renderDeceased(combinedPatient);
1470
1480
  let html = `<p>This section merges all Patient resources into a single combined patient record, preferring non-empty values for each field.</p>`;
1471
1481
  html += `<div>
1472
1482
  <ul>
@@ -1475,8 +1485,8 @@ var PatientTemplate = class _PatientTemplate {
1475
1485
  <li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
1476
1486
  <li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
1477
1487
  <li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
1478
- <li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus?.text || ""}</li>
1479
- <li><strong>Deceased:</strong>${this.renderDeceased(combinedPatient)}</li>
1488
+ ${combinedPatient.maritalStatus?.text ? `<li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus.text}</li>` : ""}
1489
+ ${deceasedText ? `<li><strong>Deceased:</strong>${deceasedText}</li>` : ""}
1480
1490
  <li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
1481
1491
  </ul>
1482
1492
  </div>`;
@@ -2418,6 +2428,59 @@ var ProblemListTemplate = class _ProblemListTemplate {
2418
2428
  generateNarrative(resources, timezone) {
2419
2429
  return _ProblemListTemplate.generateStaticNarrative(resources, timezone);
2420
2430
  }
2431
+ /**
2432
+ * Generate HTML narrative for Condition resources using summary
2433
+ * @param resources - FHIR Composition resources
2434
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2435
+ * @returns HTML string for rendering
2436
+ */
2437
+ generateSummaryNarrative(resources, timezone) {
2438
+ const templateUtilities = new TemplateUtilities(resources);
2439
+ let isSummaryCreated = false;
2440
+ let html = `<p>This list includes patient problems, sorted by recorded date (most recent first)</p>
2441
+ <div>
2442
+ <table>
2443
+ <thead>
2444
+ <tr>
2445
+ <th>Problem</th>
2446
+ <th>Code (System)</th>
2447
+ <th>Is Chronic</th>
2448
+ <th>Onset Date</th>
2449
+ <th>Last Confirmed Date</th>
2450
+ </tr>
2451
+ </thead>
2452
+ <tbody>`;
2453
+ for (const resourceItem of resources) {
2454
+ for (const rowData of resourceItem.section ?? []) {
2455
+ const sectionCodeableConcept = rowData.code;
2456
+ const data = {};
2457
+ for (const columnData of rowData.section ?? []) {
2458
+ if (columnData.title) {
2459
+ data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2460
+ }
2461
+ }
2462
+ if (data["Condition Name"]?.toLowerCase() === "unknown") {
2463
+ continue;
2464
+ }
2465
+ if (data["Status"] === "active") {
2466
+ isSummaryCreated = true;
2467
+ html += `
2468
+ <tr>
2469
+ <td>${templateUtilities.capitalizeFirstLetter(data["Condition Name"] ?? "")}</td>
2470
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
2471
+ <td>${data["Is Chronic"] ?? ""}</td>
2472
+ <td>${templateUtilities.renderTime(data["Onset Date"], timezone) ?? ""}</td>
2473
+ <td>${templateUtilities.renderTime(data["Last Confirmed Date"], timezone) ?? ""}</td>
2474
+ </tr>`;
2475
+ }
2476
+ }
2477
+ }
2478
+ html += `
2479
+ </tbody>
2480
+ </table>
2481
+ </div>`;
2482
+ return isSummaryCreated ? html : void 0;
2483
+ }
2421
2484
  /**
2422
2485
  * Internal static implementation that actually generates the narrative
2423
2486
  * @param resources - FHIR Condition resources
@@ -3580,6 +3643,61 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3580
3643
  generateNarrative(resources, timezone) {
3581
3644
  return _SocialHistoryTemplate.generateStaticNarrative(resources, timezone);
3582
3645
  }
3646
+ /**
3647
+ * Generate HTML narrative for social history using summary
3648
+ * @param resources - FHIR Composition resources
3649
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3650
+ * @returns HTML string for rendering
3651
+ */
3652
+ generateSummaryNarrative(resources, timezone) {
3653
+ const templateUtilities = new TemplateUtilities(resources);
3654
+ let isSummaryCreated = false;
3655
+ let html = `<p>This list includes all information about the patient's social history, sorted by effective date (most recent first).</p>
3656
+ `;
3657
+ html += `
3658
+ <div>
3659
+ <table>
3660
+ <thead>
3661
+ <tr>
3662
+ <th>Name</th>
3663
+ <th>Code (System)</th>
3664
+ <th>Result</th>
3665
+ <th>Date</th>
3666
+ <th>Comments</th>
3667
+ </tr>
3668
+ </thead>
3669
+ <tbody>`;
3670
+ for (const resourceItem of resources) {
3671
+ for (const rowData of resourceItem.section ?? []) {
3672
+ const sectionCodeableConcept = rowData.code;
3673
+ const data = {};
3674
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
3675
+ for (const columnData of rowData.section ?? []) {
3676
+ const columnTitle = columnData.title;
3677
+ if (columnTitle) {
3678
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3679
+ }
3680
+ }
3681
+ if (data["Social History Name"]?.toLowerCase() === "unknown") {
3682
+ continue;
3683
+ }
3684
+ isSummaryCreated = true;
3685
+ html += `
3686
+ <tr>
3687
+ <td>${templateUtilities.capitalizeFirstLetter(data["Social History Name"] ?? "")}</td>
3688
+ <td>${data["codeSystem"] ?? ""}</td>
3689
+ <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? ""}</td>
3690
+ <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
3691
+ <td>${data["Notes"] ?? ""}</td>
3692
+ </tr>`;
3693
+ }
3694
+ }
3695
+ html += `
3696
+ </tbody>
3697
+ </table>
3698
+ </div>`;
3699
+ return isSummaryCreated ? html : void 0;
3700
+ }
3583
3701
  /**
3584
3702
  * Internal static implementation that actually generates the narrative
3585
3703
  * @param resources - FHIR Observation resources
@@ -3703,6 +3821,73 @@ var PastHistoryOfIllnessTemplate = class {
3703
3821
  }
3704
3822
  return html;
3705
3823
  }
3824
+ /**
3825
+ * Generate HTML narrative for Condition resources using summary
3826
+ * @param resources - FHIR Composition resources
3827
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3828
+ * @returns HTML string for rendering
3829
+ */
3830
+ generateSummaryNarrative(resources, timezone, now) {
3831
+ const templateUtilities = new TemplateUtilities(resources);
3832
+ let isSummaryCreated = false;
3833
+ const currentDate = now || /* @__PURE__ */ new Date();
3834
+ const fiveYearsAgo = new Date(currentDate);
3835
+ fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
3836
+ let skippedConditions = 0;
3837
+ let html = `<p>This list includes past problems for the patient with a recorded date within the last 5 years, sorted by recorded date (most recent first).</p>
3838
+ <div>
3839
+ <table>
3840
+ <thead>
3841
+ <tr>
3842
+ <th>Problem</th>
3843
+ <th>Code (System)</th>
3844
+ <th>Is Chronic</th>
3845
+ <th>Onset Date</th>
3846
+ <th>Last Confirmed Date</th>
3847
+ <th>Resolved Date</th>
3848
+ </tr>
3849
+ </thead>
3850
+ <tbody>`;
3851
+ for (const resourceItem of resources) {
3852
+ for (const rowData of resourceItem.section ?? []) {
3853
+ const sectionCodeableConcept = rowData.code;
3854
+ const data = {};
3855
+ for (const columnData of rowData.section ?? []) {
3856
+ if (columnData.title) {
3857
+ data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3858
+ }
3859
+ }
3860
+ if (data["Condition Name"]?.toLowerCase() === "unknown") {
3861
+ continue;
3862
+ }
3863
+ if (data["Status"] === "inactive") {
3864
+ if (data["Last Confirmed Date"] && new Date(data["Last Confirmed Date"]) < fiveYearsAgo) {
3865
+ skippedConditions++;
3866
+ continue;
3867
+ }
3868
+ isSummaryCreated = true;
3869
+ html += `
3870
+ <tr>
3871
+ <td>${templateUtilities.capitalizeFirstLetter(data["Condition Name"] ?? "")}</td>
3872
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
3873
+ <td>${data["Is Chronic"] ?? ""}</td>
3874
+ <td>${templateUtilities.renderTime(data["Onset Date"], timezone) ?? ""}</td>
3875
+ <td>${templateUtilities.renderTime(data["Last Confirmed Date"], timezone) ?? ""}</td>
3876
+ <td>${templateUtilities.renderTime(data["Resolved Date"], timezone) ?? ""}</td>
3877
+ </tr>`;
3878
+ }
3879
+ }
3880
+ }
3881
+ html += `
3882
+ </tbody>
3883
+ </table>`;
3884
+ if (skippedConditions > 0) {
3885
+ html += `
3886
+ <p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
3887
+ }
3888
+ html += `</div>`;
3889
+ return isSummaryCreated ? html : void 0;
3890
+ }
3706
3891
  };
3707
3892
 
3708
3893
  // src/narratives/templates/typescript/PlanOfCareTemplate.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
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",