@imranq2/fhirpatientsummary 1.0.33 → 1.0.34
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 +38 -0
- package/dist/index.cjs +135 -6
- package/dist/index.js +135 -6
- package/package.json +1 -1
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)),
|
|
@@ -210,10 +212,16 @@ var IPSSectionResourceHelper = class {
|
|
|
210
212
|
return resources.filter(filter);
|
|
211
213
|
}
|
|
212
214
|
static getSummaryCompositionFilterForSection(section) {
|
|
213
|
-
|
|
215
|
+
const sectionCompositionEnabled = process.env.SUMMARY_COMPOSITION_SECTIONS ? process.env.SUMMARY_COMPOSITION_SECTIONS.split(",").some(
|
|
216
|
+
(s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
|
|
217
|
+
) : false;
|
|
218
|
+
return sectionCompositionEnabled ? IPSSectionSummaryCompositionFilter[section] : void 0;
|
|
214
219
|
}
|
|
215
220
|
static getSummaryIPSCompositionFilterForSection(section) {
|
|
216
|
-
|
|
221
|
+
const sectionIPSCompositionEnabled = process.env.SUMMARY_IPS_COMPOSITION_SECTIONS ? process.env.SUMMARY_IPS_COMPOSITION_SECTIONS.split(",").some(
|
|
222
|
+
(s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
|
|
223
|
+
) : false;
|
|
224
|
+
return sectionIPSCompositionEnabled ? IPSSectionSummaryIPSCompositionFilter[section] : void 0;
|
|
217
225
|
}
|
|
218
226
|
};
|
|
219
227
|
function codingMatches(coding, code, system) {
|
|
@@ -1478,8 +1486,8 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1478
1486
|
<li><strong>Date of Birth:</strong>${data["Date of Birth"] || ""}</li>
|
|
1479
1487
|
<li><strong>Telecom:</strong>${data["Telecom"] || ""}</li>
|
|
1480
1488
|
<li><strong>Address(es):</strong>${data["Address"] || ""}</li>
|
|
1481
|
-
|
|
1482
|
-
|
|
1489
|
+
${data["Marital Status"] ? `<li><strong>Marital Status:</strong>${data["Marital Status"]}</li>` : ""}
|
|
1490
|
+
${data["Deceased"] ? `<li><strong>Deceased:</strong>${data["Deceased"]}</li>` : ""}
|
|
1483
1491
|
<li><strong>Language(s):</strong>${data["Communication"] || ""}</li>
|
|
1484
1492
|
</ul>
|
|
1485
1493
|
</div>`;
|
|
@@ -1495,6 +1503,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1495
1503
|
static generateStaticNarrative(resources, timezone) {
|
|
1496
1504
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1497
1505
|
const combinedPatient = this.combinePatients(resources);
|
|
1506
|
+
const deceasedText = this.renderDeceased(combinedPatient);
|
|
1498
1507
|
let html = `<p>This section merges all Patient resources into a single combined patient record, preferring non-empty values for each field.</p>`;
|
|
1499
1508
|
html += `<div>
|
|
1500
1509
|
<ul>
|
|
@@ -1503,8 +1512,8 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1503
1512
|
<li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
|
|
1504
1513
|
<li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
|
|
1505
1514
|
<li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
|
|
1506
|
-
|
|
1507
|
-
|
|
1515
|
+
${combinedPatient.maritalStatus?.text ? `<li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus.text}</li>` : ""}
|
|
1516
|
+
${deceasedText ? `<li><strong>Deceased:</strong>${deceasedText}</li>` : ""}
|
|
1508
1517
|
<li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
|
|
1509
1518
|
</ul>
|
|
1510
1519
|
</div>`;
|
|
@@ -2446,6 +2455,59 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
2446
2455
|
generateNarrative(resources, timezone) {
|
|
2447
2456
|
return _ProblemListTemplate.generateStaticNarrative(resources, timezone);
|
|
2448
2457
|
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Generate HTML narrative for Condition resources using summary
|
|
2460
|
+
* @param resources - FHIR Composition resources
|
|
2461
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2462
|
+
* @returns HTML string for rendering
|
|
2463
|
+
*/
|
|
2464
|
+
generateSummaryNarrative(resources, timezone) {
|
|
2465
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2466
|
+
let isSummaryCreated = false;
|
|
2467
|
+
let html = `<p>This list includes patient problems, sorted by recorded date (most recent first)</p>
|
|
2468
|
+
<div>
|
|
2469
|
+
<table>
|
|
2470
|
+
<thead>
|
|
2471
|
+
<tr>
|
|
2472
|
+
<th>Problem</th>
|
|
2473
|
+
<th>Code (System)</th>
|
|
2474
|
+
<th>Is Chronic</th>
|
|
2475
|
+
<th>Onset Date</th>
|
|
2476
|
+
<th>Last Confirmed Date</th>
|
|
2477
|
+
</tr>
|
|
2478
|
+
</thead>
|
|
2479
|
+
<tbody>`;
|
|
2480
|
+
for (const resourceItem of resources) {
|
|
2481
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2482
|
+
const sectionCodeableConcept = rowData.code;
|
|
2483
|
+
const data = {};
|
|
2484
|
+
for (const columnData of rowData.section ?? []) {
|
|
2485
|
+
if (columnData.title) {
|
|
2486
|
+
data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
if (data["Condition Name"]?.toLowerCase() === "unknown") {
|
|
2490
|
+
continue;
|
|
2491
|
+
}
|
|
2492
|
+
if (data["Status"] === "active") {
|
|
2493
|
+
isSummaryCreated = true;
|
|
2494
|
+
html += `
|
|
2495
|
+
<tr>
|
|
2496
|
+
<td>${templateUtilities.capitalizeFirstLetter(data["Condition Name"] ?? "")}</td>
|
|
2497
|
+
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
2498
|
+
<td>${data["Is Chronic"] ?? ""}</td>
|
|
2499
|
+
<td>${templateUtilities.renderTime(data["Onset Date"], timezone) ?? ""}</td>
|
|
2500
|
+
<td>${templateUtilities.renderTime(data["Last Confirmed Date"], timezone) ?? ""}</td>
|
|
2501
|
+
</tr>`;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
html += `
|
|
2506
|
+
</tbody>
|
|
2507
|
+
</table>
|
|
2508
|
+
</div>`;
|
|
2509
|
+
return isSummaryCreated ? html : void 0;
|
|
2510
|
+
}
|
|
2449
2511
|
/**
|
|
2450
2512
|
* Internal static implementation that actually generates the narrative
|
|
2451
2513
|
* @param resources - FHIR Condition resources
|
|
@@ -3731,6 +3793,73 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
3731
3793
|
}
|
|
3732
3794
|
return html;
|
|
3733
3795
|
}
|
|
3796
|
+
/**
|
|
3797
|
+
* Generate HTML narrative for Condition resources using summary
|
|
3798
|
+
* @param resources - FHIR Composition resources
|
|
3799
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3800
|
+
* @returns HTML string for rendering
|
|
3801
|
+
*/
|
|
3802
|
+
generateSummaryNarrative(resources, timezone, now) {
|
|
3803
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
3804
|
+
let isSummaryCreated = false;
|
|
3805
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
3806
|
+
const fiveYearsAgo = new Date(currentDate);
|
|
3807
|
+
fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
|
|
3808
|
+
let skippedConditions = 0;
|
|
3809
|
+
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>
|
|
3810
|
+
<div>
|
|
3811
|
+
<table>
|
|
3812
|
+
<thead>
|
|
3813
|
+
<tr>
|
|
3814
|
+
<th>Problem</th>
|
|
3815
|
+
<th>Code (System)</th>
|
|
3816
|
+
<th>Is Chronic</th>
|
|
3817
|
+
<th>Onset Date</th>
|
|
3818
|
+
<th>Last Confirmed Date</th>
|
|
3819
|
+
<th>Resolved Date</th>
|
|
3820
|
+
</tr>
|
|
3821
|
+
</thead>
|
|
3822
|
+
<tbody>`;
|
|
3823
|
+
for (const resourceItem of resources) {
|
|
3824
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
3825
|
+
const sectionCodeableConcept = rowData.code;
|
|
3826
|
+
const data = {};
|
|
3827
|
+
for (const columnData of rowData.section ?? []) {
|
|
3828
|
+
if (columnData.title) {
|
|
3829
|
+
data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
if (data["Condition Name"]?.toLowerCase() === "unknown") {
|
|
3833
|
+
continue;
|
|
3834
|
+
}
|
|
3835
|
+
if (data["Status"] === "inactive") {
|
|
3836
|
+
if (data["Last Confirmed Date"] && new Date(data["Last Confirmed Date"]) < fiveYearsAgo) {
|
|
3837
|
+
skippedConditions++;
|
|
3838
|
+
continue;
|
|
3839
|
+
}
|
|
3840
|
+
isSummaryCreated = true;
|
|
3841
|
+
html += `
|
|
3842
|
+
<tr>
|
|
3843
|
+
<td>${templateUtilities.capitalizeFirstLetter(data["Condition Name"] ?? "")}</td>
|
|
3844
|
+
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
3845
|
+
<td>${data["Is Chronic"] ?? ""}</td>
|
|
3846
|
+
<td>${templateUtilities.renderTime(data["Onset Date"], timezone) ?? ""}</td>
|
|
3847
|
+
<td>${templateUtilities.renderTime(data["Last Confirmed Date"], timezone) ?? ""}</td>
|
|
3848
|
+
<td>${templateUtilities.renderTime(data["Resolved Date"], timezone) ?? ""}</td>
|
|
3849
|
+
</tr>`;
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
html += `
|
|
3854
|
+
</tbody>
|
|
3855
|
+
</table>`;
|
|
3856
|
+
if (skippedConditions > 0) {
|
|
3857
|
+
html += `
|
|
3858
|
+
<p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
|
|
3859
|
+
}
|
|
3860
|
+
html += `</div>`;
|
|
3861
|
+
return isSummaryCreated ? html : void 0;
|
|
3862
|
+
}
|
|
3734
3863
|
};
|
|
3735
3864
|
|
|
3736
3865
|
// 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)),
|
|
@@ -182,10 +184,16 @@ var IPSSectionResourceHelper = class {
|
|
|
182
184
|
return resources.filter(filter);
|
|
183
185
|
}
|
|
184
186
|
static getSummaryCompositionFilterForSection(section) {
|
|
185
|
-
|
|
187
|
+
const sectionCompositionEnabled = process.env.SUMMARY_COMPOSITION_SECTIONS ? process.env.SUMMARY_COMPOSITION_SECTIONS.split(",").some(
|
|
188
|
+
(s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
|
|
189
|
+
) : false;
|
|
190
|
+
return sectionCompositionEnabled ? IPSSectionSummaryCompositionFilter[section] : void 0;
|
|
186
191
|
}
|
|
187
192
|
static getSummaryIPSCompositionFilterForSection(section) {
|
|
188
|
-
|
|
193
|
+
const sectionIPSCompositionEnabled = process.env.SUMMARY_IPS_COMPOSITION_SECTIONS ? process.env.SUMMARY_IPS_COMPOSITION_SECTIONS.split(",").some(
|
|
194
|
+
(s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
|
|
195
|
+
) : false;
|
|
196
|
+
return sectionIPSCompositionEnabled ? IPSSectionSummaryIPSCompositionFilter[section] : void 0;
|
|
189
197
|
}
|
|
190
198
|
};
|
|
191
199
|
function codingMatches(coding, code, system) {
|
|
@@ -1450,8 +1458,8 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1450
1458
|
<li><strong>Date of Birth:</strong>${data["Date of Birth"] || ""}</li>
|
|
1451
1459
|
<li><strong>Telecom:</strong>${data["Telecom"] || ""}</li>
|
|
1452
1460
|
<li><strong>Address(es):</strong>${data["Address"] || ""}</li>
|
|
1453
|
-
|
|
1454
|
-
|
|
1461
|
+
${data["Marital Status"] ? `<li><strong>Marital Status:</strong>${data["Marital Status"]}</li>` : ""}
|
|
1462
|
+
${data["Deceased"] ? `<li><strong>Deceased:</strong>${data["Deceased"]}</li>` : ""}
|
|
1455
1463
|
<li><strong>Language(s):</strong>${data["Communication"] || ""}</li>
|
|
1456
1464
|
</ul>
|
|
1457
1465
|
</div>`;
|
|
@@ -1467,6 +1475,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1467
1475
|
static generateStaticNarrative(resources, timezone) {
|
|
1468
1476
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1469
1477
|
const combinedPatient = this.combinePatients(resources);
|
|
1478
|
+
const deceasedText = this.renderDeceased(combinedPatient);
|
|
1470
1479
|
let html = `<p>This section merges all Patient resources into a single combined patient record, preferring non-empty values for each field.</p>`;
|
|
1471
1480
|
html += `<div>
|
|
1472
1481
|
<ul>
|
|
@@ -1475,8 +1484,8 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1475
1484
|
<li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
|
|
1476
1485
|
<li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
|
|
1477
1486
|
<li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
|
|
1478
|
-
|
|
1479
|
-
|
|
1487
|
+
${combinedPatient.maritalStatus?.text ? `<li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus.text}</li>` : ""}
|
|
1488
|
+
${deceasedText ? `<li><strong>Deceased:</strong>${deceasedText}</li>` : ""}
|
|
1480
1489
|
<li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
|
|
1481
1490
|
</ul>
|
|
1482
1491
|
</div>`;
|
|
@@ -2418,6 +2427,59 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
2418
2427
|
generateNarrative(resources, timezone) {
|
|
2419
2428
|
return _ProblemListTemplate.generateStaticNarrative(resources, timezone);
|
|
2420
2429
|
}
|
|
2430
|
+
/**
|
|
2431
|
+
* Generate HTML narrative for Condition resources using summary
|
|
2432
|
+
* @param resources - FHIR Composition resources
|
|
2433
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
2434
|
+
* @returns HTML string for rendering
|
|
2435
|
+
*/
|
|
2436
|
+
generateSummaryNarrative(resources, timezone) {
|
|
2437
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
2438
|
+
let isSummaryCreated = false;
|
|
2439
|
+
let html = `<p>This list includes patient problems, sorted by recorded date (most recent first)</p>
|
|
2440
|
+
<div>
|
|
2441
|
+
<table>
|
|
2442
|
+
<thead>
|
|
2443
|
+
<tr>
|
|
2444
|
+
<th>Problem</th>
|
|
2445
|
+
<th>Code (System)</th>
|
|
2446
|
+
<th>Is Chronic</th>
|
|
2447
|
+
<th>Onset Date</th>
|
|
2448
|
+
<th>Last Confirmed Date</th>
|
|
2449
|
+
</tr>
|
|
2450
|
+
</thead>
|
|
2451
|
+
<tbody>`;
|
|
2452
|
+
for (const resourceItem of resources) {
|
|
2453
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
2454
|
+
const sectionCodeableConcept = rowData.code;
|
|
2455
|
+
const data = {};
|
|
2456
|
+
for (const columnData of rowData.section ?? []) {
|
|
2457
|
+
if (columnData.title) {
|
|
2458
|
+
data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
if (data["Condition Name"]?.toLowerCase() === "unknown") {
|
|
2462
|
+
continue;
|
|
2463
|
+
}
|
|
2464
|
+
if (data["Status"] === "active") {
|
|
2465
|
+
isSummaryCreated = true;
|
|
2466
|
+
html += `
|
|
2467
|
+
<tr>
|
|
2468
|
+
<td>${templateUtilities.capitalizeFirstLetter(data["Condition Name"] ?? "")}</td>
|
|
2469
|
+
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
2470
|
+
<td>${data["Is Chronic"] ?? ""}</td>
|
|
2471
|
+
<td>${templateUtilities.renderTime(data["Onset Date"], timezone) ?? ""}</td>
|
|
2472
|
+
<td>${templateUtilities.renderTime(data["Last Confirmed Date"], timezone) ?? ""}</td>
|
|
2473
|
+
</tr>`;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
html += `
|
|
2478
|
+
</tbody>
|
|
2479
|
+
</table>
|
|
2480
|
+
</div>`;
|
|
2481
|
+
return isSummaryCreated ? html : void 0;
|
|
2482
|
+
}
|
|
2421
2483
|
/**
|
|
2422
2484
|
* Internal static implementation that actually generates the narrative
|
|
2423
2485
|
* @param resources - FHIR Condition resources
|
|
@@ -3703,6 +3765,73 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
3703
3765
|
}
|
|
3704
3766
|
return html;
|
|
3705
3767
|
}
|
|
3768
|
+
/**
|
|
3769
|
+
* Generate HTML narrative for Condition resources using summary
|
|
3770
|
+
* @param resources - FHIR Composition resources
|
|
3771
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3772
|
+
* @returns HTML string for rendering
|
|
3773
|
+
*/
|
|
3774
|
+
generateSummaryNarrative(resources, timezone, now) {
|
|
3775
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
3776
|
+
let isSummaryCreated = false;
|
|
3777
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
3778
|
+
const fiveYearsAgo = new Date(currentDate);
|
|
3779
|
+
fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
|
|
3780
|
+
let skippedConditions = 0;
|
|
3781
|
+
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>
|
|
3782
|
+
<div>
|
|
3783
|
+
<table>
|
|
3784
|
+
<thead>
|
|
3785
|
+
<tr>
|
|
3786
|
+
<th>Problem</th>
|
|
3787
|
+
<th>Code (System)</th>
|
|
3788
|
+
<th>Is Chronic</th>
|
|
3789
|
+
<th>Onset Date</th>
|
|
3790
|
+
<th>Last Confirmed Date</th>
|
|
3791
|
+
<th>Resolved Date</th>
|
|
3792
|
+
</tr>
|
|
3793
|
+
</thead>
|
|
3794
|
+
<tbody>`;
|
|
3795
|
+
for (const resourceItem of resources) {
|
|
3796
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
3797
|
+
const sectionCodeableConcept = rowData.code;
|
|
3798
|
+
const data = {};
|
|
3799
|
+
for (const columnData of rowData.section ?? []) {
|
|
3800
|
+
if (columnData.title) {
|
|
3801
|
+
data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
if (data["Condition Name"]?.toLowerCase() === "unknown") {
|
|
3805
|
+
continue;
|
|
3806
|
+
}
|
|
3807
|
+
if (data["Status"] === "inactive") {
|
|
3808
|
+
if (data["Last Confirmed Date"] && new Date(data["Last Confirmed Date"]) < fiveYearsAgo) {
|
|
3809
|
+
skippedConditions++;
|
|
3810
|
+
continue;
|
|
3811
|
+
}
|
|
3812
|
+
isSummaryCreated = true;
|
|
3813
|
+
html += `
|
|
3814
|
+
<tr>
|
|
3815
|
+
<td>${templateUtilities.capitalizeFirstLetter(data["Condition Name"] ?? "")}</td>
|
|
3816
|
+
<td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
|
|
3817
|
+
<td>${data["Is Chronic"] ?? ""}</td>
|
|
3818
|
+
<td>${templateUtilities.renderTime(data["Onset Date"], timezone) ?? ""}</td>
|
|
3819
|
+
<td>${templateUtilities.renderTime(data["Last Confirmed Date"], timezone) ?? ""}</td>
|
|
3820
|
+
<td>${templateUtilities.renderTime(data["Resolved Date"], timezone) ?? ""}</td>
|
|
3821
|
+
</tr>`;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
html += `
|
|
3826
|
+
</tbody>
|
|
3827
|
+
</table>`;
|
|
3828
|
+
if (skippedConditions > 0) {
|
|
3829
|
+
html += `
|
|
3830
|
+
<p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
|
|
3831
|
+
}
|
|
3832
|
+
html += `</div>`;
|
|
3833
|
+
return isSummaryCreated ? html : void 0;
|
|
3834
|
+
}
|
|
3706
3835
|
};
|
|
3707
3836
|
|
|
3708
3837
|
// src/narratives/templates/typescript/PlanOfCareTemplate.ts
|