@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 +38 -0
- package/dist/index.cjs +192 -7
- package/dist/index.js +192 -7
- 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)),
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1482
|
-
|
|
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
|
-
|
|
1507
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1454
|
-
|
|
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
|
-
|
|
1479
|
-
|
|
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
|