@imranq2/fhirpatientsummary 1.0.39 → 1.0.40
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 +176 -50
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +176 -50
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -229,6 +229,23 @@ var RESULT_SUMMARY_OBSERVATION_DATE_FILTER = 2 * 365 * 24 * 60 * 60 * 1e3;
|
|
|
229
229
|
var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
|
|
230
230
|
|
|
231
231
|
// src/structures/ips_section_resource_map.ts
|
|
232
|
+
var IPSSectionResourcesMap = {
|
|
233
|
+
["Patient" /* PATIENT */]: ["Patient"],
|
|
234
|
+
["AllergyIntoleranceSection" /* ALLERGIES */]: ["AllergyIntolerance"],
|
|
235
|
+
["MedicationSummarySection" /* MEDICATIONS */]: ["MedicationRequest", "MedicationStatement", "Medication"],
|
|
236
|
+
["ProblemSection" /* PROBLEMS */]: ["Condition"],
|
|
237
|
+
["ImmunizationSection" /* IMMUNIZATIONS */]: ["Immunization", "Organization"],
|
|
238
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: ["Observation"],
|
|
239
|
+
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: ["DeviceUseStatement", "Device"],
|
|
240
|
+
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: ["DiagnosticReport", "Observation"],
|
|
241
|
+
["HistoryOfProceduresSection" /* PROCEDURES */]: ["Procedure"],
|
|
242
|
+
["SocialHistorySection" /* SOCIAL_HISTORY */]: ["Observation"],
|
|
243
|
+
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: ["Observation", "Patient"],
|
|
244
|
+
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: ["Condition", "ClinicalImpression"],
|
|
245
|
+
["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: ["Condition"],
|
|
246
|
+
["PlanOfCareSection" /* CARE_PLAN */]: ["CarePlan"],
|
|
247
|
+
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
|
|
248
|
+
};
|
|
232
249
|
var IPSSectionResourceFilters = {
|
|
233
250
|
// Patient section: only Patient resource
|
|
234
251
|
["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Patient",
|
|
@@ -251,7 +268,7 @@ var IPSSectionResourceFilters = {
|
|
|
251
268
|
// Only include social history Observations
|
|
252
269
|
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
|
|
253
270
|
// Only include pregnancy history Observations or relevant Conditions
|
|
254
|
-
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "
|
|
271
|
+
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "Patient" && resource.gender !== "male",
|
|
255
272
|
// Only include Condition with Functional Status LOINC and SNOMED codes, category code 'problem-list-item', and completed ClinicalImpressions
|
|
256
273
|
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && ((codeableConceptMatches(resource.code, Object.keys(FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(FUNCTIONAL_STATUS_SNOMED_CODES), "http://snomed.info/sct")) && resource.clinicalStatus?.coding?.some((c) => c.code === "active") && resource.category?.some(
|
|
257
274
|
(cat) => cat.coding?.some((c) => c.code === "problem-list-item")
|
|
@@ -280,7 +297,8 @@ var IPSSectionSummaryIPSCompositionFilter = {
|
|
|
280
297
|
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_advanced_directives_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
281
298
|
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_social_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
282
299
|
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, ["ips_functional_status_condition_summary_document", "ips_functional_status_clinical_impression_summary_document"], IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
283
|
-
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_medical_device_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
|
|
300
|
+
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_medical_device_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
301
|
+
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_pregnancy_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
|
|
284
302
|
};
|
|
285
303
|
var IPSSectionResourceHelper = class {
|
|
286
304
|
static getResourceFilterForSection(section) {
|
|
@@ -291,6 +309,9 @@ var IPSSectionResourceHelper = class {
|
|
|
291
309
|
if (!filter) return [];
|
|
292
310
|
return resources.filter(filter);
|
|
293
311
|
}
|
|
312
|
+
static getResourceTypesForSection(section) {
|
|
313
|
+
return IPSSectionResourcesMap[section] || [];
|
|
314
|
+
}
|
|
294
315
|
static getSummaryCompositionFilterForSection(section) {
|
|
295
316
|
const sectionCompositionEnabled = process.env.SUMMARY_COMPOSITION_SECTIONS ? process.env.SUMMARY_COMPOSITION_SECTIONS.split(",").some(
|
|
296
317
|
(s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
|
|
@@ -4555,6 +4576,67 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4555
4576
|
generateNarrative(resources, timezone) {
|
|
4556
4577
|
return _PregnancyTemplate.generateStaticNarrative(resources, timezone);
|
|
4557
4578
|
}
|
|
4579
|
+
/**
|
|
4580
|
+
* Generate HTML narrative for pregnancy using summary
|
|
4581
|
+
* @param resources - FHIR Composition resources
|
|
4582
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
4583
|
+
* @returns HTML string for rendering
|
|
4584
|
+
*/
|
|
4585
|
+
generateSummaryNarrative(resources, timezone) {
|
|
4586
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
4587
|
+
let isSummaryCreated = false;
|
|
4588
|
+
let html = `<p>This list includes all information about the patient's pregnancy history, sorted by effective date (most recent first).</p>
|
|
4589
|
+
`;
|
|
4590
|
+
html += `
|
|
4591
|
+
<div>
|
|
4592
|
+
<table>
|
|
4593
|
+
<thead>
|
|
4594
|
+
<tr>
|
|
4595
|
+
<th>Result</th>
|
|
4596
|
+
<th>Code (System)</th>
|
|
4597
|
+
<th>Comments</th>
|
|
4598
|
+
<th>Date</th>
|
|
4599
|
+
<th>Source</th>
|
|
4600
|
+
</tr>
|
|
4601
|
+
</thead>
|
|
4602
|
+
<tbody>`;
|
|
4603
|
+
for (const resourceItem of resources) {
|
|
4604
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
4605
|
+
const sectionCodeableConcept = rowData.code;
|
|
4606
|
+
const rowTitle = rowData.title;
|
|
4607
|
+
const data = {};
|
|
4608
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
4609
|
+
for (const columnData of rowData.section ?? []) {
|
|
4610
|
+
const columnTitle = columnData.title;
|
|
4611
|
+
if (columnTitle) {
|
|
4612
|
+
data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
if (rowTitle === "Expected Delivery Date") {
|
|
4616
|
+
data["Result"] = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(data, timezone));
|
|
4617
|
+
} else {
|
|
4618
|
+
data["Result"] = data["Pregnancy Result"];
|
|
4619
|
+
}
|
|
4620
|
+
if (data["Result"]?.toLowerCase() === "unknown") {
|
|
4621
|
+
continue;
|
|
4622
|
+
}
|
|
4623
|
+
isSummaryCreated = true;
|
|
4624
|
+
html += `
|
|
4625
|
+
<tr>
|
|
4626
|
+
<td class="Result">${data["Result"] ? templateUtilities.capitalizeFirstLetter(data["Result"]) : ""}</td>
|
|
4627
|
+
<td class="CodeSystem">${data["codeSystem"]}</td>
|
|
4628
|
+
<td class="Comments">${data["Comments"] ?? ""}</td>
|
|
4629
|
+
<td class="Date">${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
|
|
4630
|
+
<td class="Source">${data["Source"] ?? ""}</td>
|
|
4631
|
+
</tr>`;
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
html += `
|
|
4635
|
+
</tbody>
|
|
4636
|
+
</table>
|
|
4637
|
+
</div>`;
|
|
4638
|
+
return isSummaryCreated ? html : void 0;
|
|
4639
|
+
}
|
|
4558
4640
|
/**
|
|
4559
4641
|
* Internal static implementation that actually generates the narrative
|
|
4560
4642
|
* @param resources - FHIR Observation resources
|
|
@@ -4563,10 +4645,13 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4563
4645
|
*/
|
|
4564
4646
|
static generateStaticNarrative(resources, timezone) {
|
|
4565
4647
|
const templateUtilities = new TemplateUtilities(resources);
|
|
4648
|
+
const patients = resources.filter((r) => r.resourceType === "Patient");
|
|
4649
|
+
if (patients.length === 0) {
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
4566
4652
|
const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
|
|
4567
4653
|
const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
|
|
4568
4654
|
const observations = filteredResources.filter((r) => r.resourceType === "Observation");
|
|
4569
|
-
const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
|
|
4570
4655
|
const EDD_LOINC = "11778-8";
|
|
4571
4656
|
const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
|
|
4572
4657
|
const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
|
|
@@ -4587,10 +4672,10 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4587
4672
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
4588
4673
|
return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
4589
4674
|
});
|
|
4590
|
-
if (!pregnancyStatusObs && !eddObs && historyObs.length === 0
|
|
4591
|
-
return
|
|
4675
|
+
if (!pregnancyStatusObs && !eddObs && historyObs.length === 0) {
|
|
4676
|
+
return;
|
|
4592
4677
|
}
|
|
4593
|
-
let html = `<p>This list includes Observation
|
|
4678
|
+
let html = `<p>This list includes Observation resources relevant to pregnancy, sorted by date (most recent first).</p>`;
|
|
4594
4679
|
html += `
|
|
4595
4680
|
<table>
|
|
4596
4681
|
<thead>
|
|
@@ -4629,11 +4714,6 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4629
4714
|
const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
|
|
4630
4715
|
rowResources.push({ resource: obs, date, type: "history" });
|
|
4631
4716
|
}
|
|
4632
|
-
for (const cond of conditions) {
|
|
4633
|
-
const condition = cond;
|
|
4634
|
-
const date = condition.onsetDateTime || condition.onsetPeriod?.start;
|
|
4635
|
-
rowResources.push({ resource: condition, date, type: "condition" });
|
|
4636
|
-
}
|
|
4637
4717
|
rowResources.sort((a, b) => {
|
|
4638
4718
|
if (!a.date && !b.date) return 0;
|
|
4639
4719
|
if (!a.date) return 1;
|
|
@@ -4649,7 +4729,7 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4649
4729
|
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4650
4730
|
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
4651
4731
|
} else if (type === "edd") {
|
|
4652
|
-
result = "Estimated Delivery Date: " +
|
|
4732
|
+
result = "Estimated Delivery Date: " + (templateUtilities.extractObservationValue(resource) ?? "");
|
|
4653
4733
|
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
4654
4734
|
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4655
4735
|
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
@@ -4658,11 +4738,6 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4658
4738
|
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
4659
4739
|
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4660
4740
|
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
4661
|
-
} else if (type === "condition") {
|
|
4662
|
-
result = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(resource.code));
|
|
4663
|
-
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
4664
|
-
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4665
|
-
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
4666
4741
|
}
|
|
4667
4742
|
const rowKey = `${result}|${codeSystem}`;
|
|
4668
4743
|
if (!addedRows.has(rowKey)) {
|
|
@@ -5075,16 +5150,33 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5075
5150
|
this.addSectionAsync(narrative, sectionType, validResources);
|
|
5076
5151
|
return this;
|
|
5077
5152
|
}
|
|
5078
|
-
async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone) {
|
|
5153
|
+
async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone, includeSummaryCompositionOnly = false) {
|
|
5079
5154
|
const sectionResources = [];
|
|
5080
5155
|
for (const summaryComposition of summaryCompositions) {
|
|
5081
5156
|
const resourceEntries = summaryComposition?.section?.flatMap((sec) => sec.entry || []) ?? [];
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5157
|
+
if (includeSummaryCompositionOnly) {
|
|
5158
|
+
const addedEntries = /* @__PURE__ */ new Set();
|
|
5159
|
+
resourceEntries.forEach((entry) => {
|
|
5160
|
+
if (entry.reference && !addedEntries.has(entry.reference)) {
|
|
5161
|
+
const reference = entry.reference.split("/");
|
|
5162
|
+
if (reference.length === 2) {
|
|
5163
|
+
const resource = {
|
|
5164
|
+
id: reference[1],
|
|
5165
|
+
resourceType: reference[0]
|
|
5166
|
+
};
|
|
5167
|
+
sectionResources.push(resource);
|
|
5168
|
+
addedEntries.add(entry.reference);
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
});
|
|
5172
|
+
} else {
|
|
5173
|
+
resources.forEach((resource) => {
|
|
5174
|
+
if (resourceEntries?.some((entry) => entry.reference === `${resource.resourceType}/${resource.id}`)) {
|
|
5175
|
+
this.resources.add(resource);
|
|
5176
|
+
sectionResources.push(resource);
|
|
5177
|
+
}
|
|
5178
|
+
});
|
|
5179
|
+
}
|
|
5088
5180
|
}
|
|
5089
5181
|
let narrative = await NarrativeGenerator.generateNarrativeAsync(
|
|
5090
5182
|
sectionType,
|
|
@@ -5109,8 +5201,10 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5109
5201
|
* @param bundle - FHIR Bundle containing resources
|
|
5110
5202
|
* @param timezone - Optional timezone to use for date formatting
|
|
5111
5203
|
* @param useSummaryCompositions - Whether to use summary compositions (default: false)
|
|
5204
|
+
* @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
|
|
5205
|
+
* @param consoleLogger - Optional console logger for logging (default: console)
|
|
5112
5206
|
*/
|
|
5113
|
-
async readBundleAsync(bundle, timezone, useSummaryCompositions = false, consoleLogger = console) {
|
|
5207
|
+
async readBundleAsync(bundle, timezone, useSummaryCompositions = false, includeSummaryCompositionOnly = false, consoleLogger = console) {
|
|
5114
5208
|
if (!bundle.entry) {
|
|
5115
5209
|
return this;
|
|
5116
5210
|
}
|
|
@@ -5134,14 +5228,14 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5134
5228
|
const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
|
|
5135
5229
|
if (sectionIPSSummary.length > 0) {
|
|
5136
5230
|
consoleLogger.info(`Using IPS summary composition for section: ${sectionType}`);
|
|
5137
|
-
await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone);
|
|
5231
|
+
await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone, includeSummaryCompositionOnly);
|
|
5138
5232
|
continue;
|
|
5139
5233
|
}
|
|
5140
5234
|
const summaryCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType) : void 0;
|
|
5141
5235
|
const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
|
|
5142
5236
|
if (sectionSummary.length > 0) {
|
|
5143
5237
|
consoleLogger.info(`Using summary composition for section: ${sectionType}`);
|
|
5144
|
-
await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone);
|
|
5238
|
+
await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone, includeSummaryCompositionOnly);
|
|
5145
5239
|
} else {
|
|
5146
5240
|
consoleLogger.info(`Using individual resources for section: ${sectionType}`);
|
|
5147
5241
|
const sectionFilter = IPSSectionResourceHelper.getResourceFilterForSection(sectionType);
|
|
@@ -5157,10 +5251,11 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5157
5251
|
* @param authorOrganizationName - Name of the authoring organization
|
|
5158
5252
|
* @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
|
|
5159
5253
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
5254
|
+
* @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
|
|
5160
5255
|
* @param patientId - Optional patient ID to use as primary patient for composition reference
|
|
5161
5256
|
* @param now - Optional current date to use for composition date (defaults to new Date())
|
|
5162
5257
|
*/
|
|
5163
|
-
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
|
|
5258
|
+
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, includeSummaryCompositionOnly = false, patientId, now) {
|
|
5164
5259
|
if (baseUrl.endsWith("/")) {
|
|
5165
5260
|
baseUrl = baseUrl.slice(0, -1);
|
|
5166
5261
|
}
|
|
@@ -5209,30 +5304,32 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5209
5304
|
fullUrl: `${baseUrl}/Composition/${composition.id}`,
|
|
5210
5305
|
resource: composition
|
|
5211
5306
|
});
|
|
5212
|
-
|
|
5307
|
+
if (!includeSummaryCompositionOnly) {
|
|
5308
|
+
this.patients.forEach((patient) => {
|
|
5309
|
+
bundle.entry?.push({
|
|
5310
|
+
fullUrl: `${baseUrl}/Patient/${patient.id}`,
|
|
5311
|
+
resource: patient
|
|
5312
|
+
});
|
|
5313
|
+
});
|
|
5314
|
+
this.resources.forEach((resource) => {
|
|
5315
|
+
if (resource.resourceType !== "Patient") {
|
|
5316
|
+
bundle.entry?.push(
|
|
5317
|
+
{
|
|
5318
|
+
fullUrl: `${baseUrl}/${resource.resourceType}/${resource.id}`,
|
|
5319
|
+
resource
|
|
5320
|
+
}
|
|
5321
|
+
);
|
|
5322
|
+
}
|
|
5323
|
+
});
|
|
5213
5324
|
bundle.entry?.push({
|
|
5214
|
-
fullUrl: `${baseUrl}/
|
|
5215
|
-
resource:
|
|
5325
|
+
fullUrl: `${baseUrl}/Organization/${authorOrganizationId}`,
|
|
5326
|
+
resource: {
|
|
5327
|
+
resourceType: "Organization",
|
|
5328
|
+
id: authorOrganizationId,
|
|
5329
|
+
name: authorOrganizationName
|
|
5330
|
+
}
|
|
5216
5331
|
});
|
|
5217
|
-
}
|
|
5218
|
-
this.resources.forEach((resource) => {
|
|
5219
|
-
if (resource.resourceType !== "Patient") {
|
|
5220
|
-
bundle.entry?.push(
|
|
5221
|
-
{
|
|
5222
|
-
fullUrl: `${baseUrl}/${resource.resourceType}/${resource.id}`,
|
|
5223
|
-
resource
|
|
5224
|
-
}
|
|
5225
|
-
);
|
|
5226
|
-
}
|
|
5227
|
-
});
|
|
5228
|
-
bundle.entry?.push({
|
|
5229
|
-
fullUrl: `${baseUrl}/Organization/${authorOrganizationId}`,
|
|
5230
|
-
resource: {
|
|
5231
|
-
resourceType: "Organization",
|
|
5232
|
-
id: authorOrganizationId,
|
|
5233
|
-
name: authorOrganizationName
|
|
5234
|
-
}
|
|
5235
|
-
});
|
|
5332
|
+
}
|
|
5236
5333
|
return bundle;
|
|
5237
5334
|
}
|
|
5238
5335
|
/**
|
|
@@ -5242,6 +5339,35 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5242
5339
|
getSections() {
|
|
5243
5340
|
return this.sections;
|
|
5244
5341
|
}
|
|
5342
|
+
/**
|
|
5343
|
+
* Identifies remaining resource types that are missing from the composition bundle.
|
|
5344
|
+
* @param bundle - FHIR Bundle containing resources
|
|
5345
|
+
* @returns Array of missing resource type strings
|
|
5346
|
+
*/
|
|
5347
|
+
getRemainingResourcesFromCompositionBundle(bundle) {
|
|
5348
|
+
const resources = [];
|
|
5349
|
+
bundle.entry?.forEach((e) => {
|
|
5350
|
+
if (e.resource) {
|
|
5351
|
+
resources.push(e.resource);
|
|
5352
|
+
}
|
|
5353
|
+
});
|
|
5354
|
+
const remainingResources = /* @__PURE__ */ new Set();
|
|
5355
|
+
for (const sectionType of Object.values(IPSSections)) {
|
|
5356
|
+
const summaryIPSCompositionFilter = IPSSectionResourceHelper.getSummaryIPSCompositionFilterForSection(sectionType);
|
|
5357
|
+
const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
|
|
5358
|
+
const summaryCompositionFilter = IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType);
|
|
5359
|
+
const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
|
|
5360
|
+
if (sectionSummary.length === 0 && sectionIPSSummary.length === 0) {
|
|
5361
|
+
const resourcesForSection = IPSSectionResourceHelper.getResourceTypesForSection(sectionType);
|
|
5362
|
+
resourcesForSection.forEach((resourceType) => {
|
|
5363
|
+
if (!remainingResources.has(resourceType) && !resources.some((r) => r.resourceType === resourceType)) {
|
|
5364
|
+
remainingResources.add(resourceType);
|
|
5365
|
+
}
|
|
5366
|
+
});
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
return Array.from(remainingResources);
|
|
5370
|
+
}
|
|
5245
5371
|
};
|
|
5246
5372
|
|
|
5247
5373
|
// src/index.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -709,29 +709,38 @@ declare class ComprehensiveIPSCompositionBuilder {
|
|
|
709
709
|
* @param timezone - Optional timezone to use for date formatting
|
|
710
710
|
*/
|
|
711
711
|
makeSectionAsync<T extends TDomainResource>(sectionType: IPSSections, validResources: T[], timezone: string | undefined): Promise<this>;
|
|
712
|
-
makeSectionFromSummaryAsync(sectionType: IPSSections, summaryCompositions: TComposition[], resources: TDomainResource[], timezone: string | undefined): Promise<this>;
|
|
712
|
+
makeSectionFromSummaryAsync(sectionType: IPSSections, summaryCompositions: TComposition[], resources: TDomainResource[], timezone: string | undefined, includeSummaryCompositionOnly?: boolean): Promise<this>;
|
|
713
713
|
/**
|
|
714
714
|
* Reads a FHIR Bundle and extracts resources for each section defined in IPSSections.
|
|
715
715
|
* @param bundle - FHIR Bundle containing resources
|
|
716
716
|
* @param timezone - Optional timezone to use for date formatting
|
|
717
717
|
* @param useSummaryCompositions - Whether to use summary compositions (default: false)
|
|
718
|
+
* @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
|
|
719
|
+
* @param consoleLogger - Optional console logger for logging (default: console)
|
|
718
720
|
*/
|
|
719
|
-
readBundleAsync(bundle: TBundle, timezone: string | undefined, useSummaryCompositions?: boolean, consoleLogger?: Console): Promise<this>;
|
|
721
|
+
readBundleAsync(bundle: TBundle, timezone: string | undefined, useSummaryCompositions?: boolean, includeSummaryCompositionOnly?: boolean, consoleLogger?: Console): Promise<this>;
|
|
720
722
|
/**
|
|
721
723
|
* Builds a complete FHIR Bundle containing the Composition and all resources.
|
|
722
724
|
* @param authorOrganizationId - ID of the authoring organization (e.g., hospital or clinic)
|
|
723
725
|
* @param authorOrganizationName - Name of the authoring organization
|
|
724
726
|
* @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
|
|
725
727
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
728
|
+
* @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
|
|
726
729
|
* @param patientId - Optional patient ID to use as primary patient for composition reference
|
|
727
730
|
* @param now - Optional current date to use for composition date (defaults to new Date())
|
|
728
731
|
*/
|
|
729
|
-
buildBundleAsync(authorOrganizationId: string, authorOrganizationName: string, baseUrl: string, timezone: string | undefined, patientId?: string, now?: Date): Promise<TBundle>;
|
|
732
|
+
buildBundleAsync(authorOrganizationId: string, authorOrganizationName: string, baseUrl: string, timezone: string | undefined, includeSummaryCompositionOnly?: boolean, patientId?: string, now?: Date): Promise<TBundle>;
|
|
730
733
|
/**
|
|
731
734
|
* Returns the Composition sections without creating a full bundle.
|
|
732
735
|
* @returns Array of TCompositionSection
|
|
733
736
|
*/
|
|
734
737
|
getSections(): TCompositionSection[];
|
|
738
|
+
/**
|
|
739
|
+
* Identifies remaining resource types that are missing from the composition bundle.
|
|
740
|
+
* @param bundle - FHIR Bundle containing resources
|
|
741
|
+
* @returns Array of missing resource type strings
|
|
742
|
+
*/
|
|
743
|
+
getRemainingResourcesFromCompositionBundle(bundle: TBundle): string[];
|
|
735
744
|
}
|
|
736
745
|
|
|
737
746
|
interface Narrative {
|
package/dist/index.d.ts
CHANGED
|
@@ -709,29 +709,38 @@ declare class ComprehensiveIPSCompositionBuilder {
|
|
|
709
709
|
* @param timezone - Optional timezone to use for date formatting
|
|
710
710
|
*/
|
|
711
711
|
makeSectionAsync<T extends TDomainResource>(sectionType: IPSSections, validResources: T[], timezone: string | undefined): Promise<this>;
|
|
712
|
-
makeSectionFromSummaryAsync(sectionType: IPSSections, summaryCompositions: TComposition[], resources: TDomainResource[], timezone: string | undefined): Promise<this>;
|
|
712
|
+
makeSectionFromSummaryAsync(sectionType: IPSSections, summaryCompositions: TComposition[], resources: TDomainResource[], timezone: string | undefined, includeSummaryCompositionOnly?: boolean): Promise<this>;
|
|
713
713
|
/**
|
|
714
714
|
* Reads a FHIR Bundle and extracts resources for each section defined in IPSSections.
|
|
715
715
|
* @param bundle - FHIR Bundle containing resources
|
|
716
716
|
* @param timezone - Optional timezone to use for date formatting
|
|
717
717
|
* @param useSummaryCompositions - Whether to use summary compositions (default: false)
|
|
718
|
+
* @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
|
|
719
|
+
* @param consoleLogger - Optional console logger for logging (default: console)
|
|
718
720
|
*/
|
|
719
|
-
readBundleAsync(bundle: TBundle, timezone: string | undefined, useSummaryCompositions?: boolean, consoleLogger?: Console): Promise<this>;
|
|
721
|
+
readBundleAsync(bundle: TBundle, timezone: string | undefined, useSummaryCompositions?: boolean, includeSummaryCompositionOnly?: boolean, consoleLogger?: Console): Promise<this>;
|
|
720
722
|
/**
|
|
721
723
|
* Builds a complete FHIR Bundle containing the Composition and all resources.
|
|
722
724
|
* @param authorOrganizationId - ID of the authoring organization (e.g., hospital or clinic)
|
|
723
725
|
* @param authorOrganizationName - Name of the authoring organization
|
|
724
726
|
* @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
|
|
725
727
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
728
|
+
* @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
|
|
726
729
|
* @param patientId - Optional patient ID to use as primary patient for composition reference
|
|
727
730
|
* @param now - Optional current date to use for composition date (defaults to new Date())
|
|
728
731
|
*/
|
|
729
|
-
buildBundleAsync(authorOrganizationId: string, authorOrganizationName: string, baseUrl: string, timezone: string | undefined, patientId?: string, now?: Date): Promise<TBundle>;
|
|
732
|
+
buildBundleAsync(authorOrganizationId: string, authorOrganizationName: string, baseUrl: string, timezone: string | undefined, includeSummaryCompositionOnly?: boolean, patientId?: string, now?: Date): Promise<TBundle>;
|
|
730
733
|
/**
|
|
731
734
|
* Returns the Composition sections without creating a full bundle.
|
|
732
735
|
* @returns Array of TCompositionSection
|
|
733
736
|
*/
|
|
734
737
|
getSections(): TCompositionSection[];
|
|
738
|
+
/**
|
|
739
|
+
* Identifies remaining resource types that are missing from the composition bundle.
|
|
740
|
+
* @param bundle - FHIR Bundle containing resources
|
|
741
|
+
* @returns Array of missing resource type strings
|
|
742
|
+
*/
|
|
743
|
+
getRemainingResourcesFromCompositionBundle(bundle: TBundle): string[];
|
|
735
744
|
}
|
|
736
745
|
|
|
737
746
|
interface Narrative {
|
package/dist/index.js
CHANGED
|
@@ -201,6 +201,23 @@ var RESULT_SUMMARY_OBSERVATION_DATE_FILTER = 2 * 365 * 24 * 60 * 60 * 1e3;
|
|
|
201
201
|
var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
|
|
202
202
|
|
|
203
203
|
// src/structures/ips_section_resource_map.ts
|
|
204
|
+
var IPSSectionResourcesMap = {
|
|
205
|
+
["Patient" /* PATIENT */]: ["Patient"],
|
|
206
|
+
["AllergyIntoleranceSection" /* ALLERGIES */]: ["AllergyIntolerance"],
|
|
207
|
+
["MedicationSummarySection" /* MEDICATIONS */]: ["MedicationRequest", "MedicationStatement", "Medication"],
|
|
208
|
+
["ProblemSection" /* PROBLEMS */]: ["Condition"],
|
|
209
|
+
["ImmunizationSection" /* IMMUNIZATIONS */]: ["Immunization", "Organization"],
|
|
210
|
+
["VitalSignsSection" /* VITAL_SIGNS */]: ["Observation"],
|
|
211
|
+
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: ["DeviceUseStatement", "Device"],
|
|
212
|
+
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: ["DiagnosticReport", "Observation"],
|
|
213
|
+
["HistoryOfProceduresSection" /* PROCEDURES */]: ["Procedure"],
|
|
214
|
+
["SocialHistorySection" /* SOCIAL_HISTORY */]: ["Observation"],
|
|
215
|
+
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: ["Observation", "Patient"],
|
|
216
|
+
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: ["Condition", "ClinicalImpression"],
|
|
217
|
+
["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: ["Condition"],
|
|
218
|
+
["PlanOfCareSection" /* CARE_PLAN */]: ["CarePlan"],
|
|
219
|
+
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: ["Consent"]
|
|
220
|
+
};
|
|
204
221
|
var IPSSectionResourceFilters = {
|
|
205
222
|
// Patient section: only Patient resource
|
|
206
223
|
["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Patient",
|
|
@@ -223,7 +240,7 @@ var IPSSectionResourceFilters = {
|
|
|
223
240
|
// Only include social history Observations
|
|
224
241
|
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
|
|
225
242
|
// Only include pregnancy history Observations or relevant Conditions
|
|
226
|
-
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "
|
|
243
|
+
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "Patient" && resource.gender !== "male",
|
|
227
244
|
// Only include Condition with Functional Status LOINC and SNOMED codes, category code 'problem-list-item', and completed ClinicalImpressions
|
|
228
245
|
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && ((codeableConceptMatches(resource.code, Object.keys(FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(FUNCTIONAL_STATUS_SNOMED_CODES), "http://snomed.info/sct")) && resource.clinicalStatus?.coding?.some((c) => c.code === "active") && resource.category?.some(
|
|
229
246
|
(cat) => cat.coding?.some((c) => c.code === "problem-list-item")
|
|
@@ -252,7 +269,8 @@ var IPSSectionSummaryIPSCompositionFilter = {
|
|
|
252
269
|
["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_advanced_directives_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
253
270
|
["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_social_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
254
271
|
["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, ["ips_functional_status_condition_summary_document", "ips_functional_status_clinical_impression_summary_document"], IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
255
|
-
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_medical_device_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
|
|
272
|
+
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_medical_device_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
|
|
273
|
+
["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_pregnancy_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
|
|
256
274
|
};
|
|
257
275
|
var IPSSectionResourceHelper = class {
|
|
258
276
|
static getResourceFilterForSection(section) {
|
|
@@ -263,6 +281,9 @@ var IPSSectionResourceHelper = class {
|
|
|
263
281
|
if (!filter) return [];
|
|
264
282
|
return resources.filter(filter);
|
|
265
283
|
}
|
|
284
|
+
static getResourceTypesForSection(section) {
|
|
285
|
+
return IPSSectionResourcesMap[section] || [];
|
|
286
|
+
}
|
|
266
287
|
static getSummaryCompositionFilterForSection(section) {
|
|
267
288
|
const sectionCompositionEnabled = process.env.SUMMARY_COMPOSITION_SECTIONS ? process.env.SUMMARY_COMPOSITION_SECTIONS.split(",").some(
|
|
268
289
|
(s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
|
|
@@ -4527,6 +4548,67 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4527
4548
|
generateNarrative(resources, timezone) {
|
|
4528
4549
|
return _PregnancyTemplate.generateStaticNarrative(resources, timezone);
|
|
4529
4550
|
}
|
|
4551
|
+
/**
|
|
4552
|
+
* Generate HTML narrative for pregnancy using summary
|
|
4553
|
+
* @param resources - FHIR Composition resources
|
|
4554
|
+
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
4555
|
+
* @returns HTML string for rendering
|
|
4556
|
+
*/
|
|
4557
|
+
generateSummaryNarrative(resources, timezone) {
|
|
4558
|
+
const templateUtilities = new TemplateUtilities(resources);
|
|
4559
|
+
let isSummaryCreated = false;
|
|
4560
|
+
let html = `<p>This list includes all information about the patient's pregnancy history, sorted by effective date (most recent first).</p>
|
|
4561
|
+
`;
|
|
4562
|
+
html += `
|
|
4563
|
+
<div>
|
|
4564
|
+
<table>
|
|
4565
|
+
<thead>
|
|
4566
|
+
<tr>
|
|
4567
|
+
<th>Result</th>
|
|
4568
|
+
<th>Code (System)</th>
|
|
4569
|
+
<th>Comments</th>
|
|
4570
|
+
<th>Date</th>
|
|
4571
|
+
<th>Source</th>
|
|
4572
|
+
</tr>
|
|
4573
|
+
</thead>
|
|
4574
|
+
<tbody>`;
|
|
4575
|
+
for (const resourceItem of resources) {
|
|
4576
|
+
for (const rowData of resourceItem.section ?? []) {
|
|
4577
|
+
const sectionCodeableConcept = rowData.code;
|
|
4578
|
+
const rowTitle = rowData.title;
|
|
4579
|
+
const data = {};
|
|
4580
|
+
data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
|
|
4581
|
+
for (const columnData of rowData.section ?? []) {
|
|
4582
|
+
const columnTitle = columnData.title;
|
|
4583
|
+
if (columnTitle) {
|
|
4584
|
+
data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
if (rowTitle === "Expected Delivery Date") {
|
|
4588
|
+
data["Result"] = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(data, timezone));
|
|
4589
|
+
} else {
|
|
4590
|
+
data["Result"] = data["Pregnancy Result"];
|
|
4591
|
+
}
|
|
4592
|
+
if (data["Result"]?.toLowerCase() === "unknown") {
|
|
4593
|
+
continue;
|
|
4594
|
+
}
|
|
4595
|
+
isSummaryCreated = true;
|
|
4596
|
+
html += `
|
|
4597
|
+
<tr>
|
|
4598
|
+
<td class="Result">${data["Result"] ? templateUtilities.capitalizeFirstLetter(data["Result"]) : ""}</td>
|
|
4599
|
+
<td class="CodeSystem">${data["codeSystem"]}</td>
|
|
4600
|
+
<td class="Comments">${data["Comments"] ?? ""}</td>
|
|
4601
|
+
<td class="Date">${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
|
|
4602
|
+
<td class="Source">${data["Source"] ?? ""}</td>
|
|
4603
|
+
</tr>`;
|
|
4604
|
+
}
|
|
4605
|
+
}
|
|
4606
|
+
html += `
|
|
4607
|
+
</tbody>
|
|
4608
|
+
</table>
|
|
4609
|
+
</div>`;
|
|
4610
|
+
return isSummaryCreated ? html : void 0;
|
|
4611
|
+
}
|
|
4530
4612
|
/**
|
|
4531
4613
|
* Internal static implementation that actually generates the narrative
|
|
4532
4614
|
* @param resources - FHIR Observation resources
|
|
@@ -4535,10 +4617,13 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4535
4617
|
*/
|
|
4536
4618
|
static generateStaticNarrative(resources, timezone) {
|
|
4537
4619
|
const templateUtilities = new TemplateUtilities(resources);
|
|
4620
|
+
const patients = resources.filter((r) => r.resourceType === "Patient");
|
|
4621
|
+
if (patients.length === 0) {
|
|
4622
|
+
return;
|
|
4623
|
+
}
|
|
4538
4624
|
const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
|
|
4539
4625
|
const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
|
|
4540
4626
|
const observations = filteredResources.filter((r) => r.resourceType === "Observation");
|
|
4541
|
-
const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
|
|
4542
4627
|
const EDD_LOINC = "11778-8";
|
|
4543
4628
|
const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
|
|
4544
4629
|
const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
|
|
@@ -4559,10 +4644,10 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4559
4644
|
const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
|
|
4560
4645
|
return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
4561
4646
|
});
|
|
4562
|
-
if (!pregnancyStatusObs && !eddObs && historyObs.length === 0
|
|
4563
|
-
return
|
|
4647
|
+
if (!pregnancyStatusObs && !eddObs && historyObs.length === 0) {
|
|
4648
|
+
return;
|
|
4564
4649
|
}
|
|
4565
|
-
let html = `<p>This list includes Observation
|
|
4650
|
+
let html = `<p>This list includes Observation resources relevant to pregnancy, sorted by date (most recent first).</p>`;
|
|
4566
4651
|
html += `
|
|
4567
4652
|
<table>
|
|
4568
4653
|
<thead>
|
|
@@ -4601,11 +4686,6 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4601
4686
|
const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
|
|
4602
4687
|
rowResources.push({ resource: obs, date, type: "history" });
|
|
4603
4688
|
}
|
|
4604
|
-
for (const cond of conditions) {
|
|
4605
|
-
const condition = cond;
|
|
4606
|
-
const date = condition.onsetDateTime || condition.onsetPeriod?.start;
|
|
4607
|
-
rowResources.push({ resource: condition, date, type: "condition" });
|
|
4608
|
-
}
|
|
4609
4689
|
rowResources.sort((a, b) => {
|
|
4610
4690
|
if (!a.date && !b.date) return 0;
|
|
4611
4691
|
if (!a.date) return 1;
|
|
@@ -4621,7 +4701,7 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4621
4701
|
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4622
4702
|
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
4623
4703
|
} else if (type === "edd") {
|
|
4624
|
-
result = "Estimated Delivery Date: " +
|
|
4704
|
+
result = "Estimated Delivery Date: " + (templateUtilities.extractObservationValue(resource) ?? "");
|
|
4625
4705
|
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
4626
4706
|
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4627
4707
|
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
@@ -4630,11 +4710,6 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
4630
4710
|
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
4631
4711
|
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4632
4712
|
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
4633
|
-
} else if (type === "condition") {
|
|
4634
|
-
result = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(resource.code));
|
|
4635
|
-
comments = templateUtilities.renderNotes(resource.note, timezone);
|
|
4636
|
-
dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
|
|
4637
|
-
codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
|
|
4638
4713
|
}
|
|
4639
4714
|
const rowKey = `${result}|${codeSystem}`;
|
|
4640
4715
|
if (!addedRows.has(rowKey)) {
|
|
@@ -5047,16 +5122,33 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5047
5122
|
this.addSectionAsync(narrative, sectionType, validResources);
|
|
5048
5123
|
return this;
|
|
5049
5124
|
}
|
|
5050
|
-
async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone) {
|
|
5125
|
+
async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone, includeSummaryCompositionOnly = false) {
|
|
5051
5126
|
const sectionResources = [];
|
|
5052
5127
|
for (const summaryComposition of summaryCompositions) {
|
|
5053
5128
|
const resourceEntries = summaryComposition?.section?.flatMap((sec) => sec.entry || []) ?? [];
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5129
|
+
if (includeSummaryCompositionOnly) {
|
|
5130
|
+
const addedEntries = /* @__PURE__ */ new Set();
|
|
5131
|
+
resourceEntries.forEach((entry) => {
|
|
5132
|
+
if (entry.reference && !addedEntries.has(entry.reference)) {
|
|
5133
|
+
const reference = entry.reference.split("/");
|
|
5134
|
+
if (reference.length === 2) {
|
|
5135
|
+
const resource = {
|
|
5136
|
+
id: reference[1],
|
|
5137
|
+
resourceType: reference[0]
|
|
5138
|
+
};
|
|
5139
|
+
sectionResources.push(resource);
|
|
5140
|
+
addedEntries.add(entry.reference);
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
});
|
|
5144
|
+
} else {
|
|
5145
|
+
resources.forEach((resource) => {
|
|
5146
|
+
if (resourceEntries?.some((entry) => entry.reference === `${resource.resourceType}/${resource.id}`)) {
|
|
5147
|
+
this.resources.add(resource);
|
|
5148
|
+
sectionResources.push(resource);
|
|
5149
|
+
}
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5060
5152
|
}
|
|
5061
5153
|
let narrative = await NarrativeGenerator.generateNarrativeAsync(
|
|
5062
5154
|
sectionType,
|
|
@@ -5081,8 +5173,10 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5081
5173
|
* @param bundle - FHIR Bundle containing resources
|
|
5082
5174
|
* @param timezone - Optional timezone to use for date formatting
|
|
5083
5175
|
* @param useSummaryCompositions - Whether to use summary compositions (default: false)
|
|
5176
|
+
* @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
|
|
5177
|
+
* @param consoleLogger - Optional console logger for logging (default: console)
|
|
5084
5178
|
*/
|
|
5085
|
-
async readBundleAsync(bundle, timezone, useSummaryCompositions = false, consoleLogger = console) {
|
|
5179
|
+
async readBundleAsync(bundle, timezone, useSummaryCompositions = false, includeSummaryCompositionOnly = false, consoleLogger = console) {
|
|
5086
5180
|
if (!bundle.entry) {
|
|
5087
5181
|
return this;
|
|
5088
5182
|
}
|
|
@@ -5106,14 +5200,14 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5106
5200
|
const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
|
|
5107
5201
|
if (sectionIPSSummary.length > 0) {
|
|
5108
5202
|
consoleLogger.info(`Using IPS summary composition for section: ${sectionType}`);
|
|
5109
|
-
await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone);
|
|
5203
|
+
await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone, includeSummaryCompositionOnly);
|
|
5110
5204
|
continue;
|
|
5111
5205
|
}
|
|
5112
5206
|
const summaryCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType) : void 0;
|
|
5113
5207
|
const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
|
|
5114
5208
|
if (sectionSummary.length > 0) {
|
|
5115
5209
|
consoleLogger.info(`Using summary composition for section: ${sectionType}`);
|
|
5116
|
-
await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone);
|
|
5210
|
+
await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone, includeSummaryCompositionOnly);
|
|
5117
5211
|
} else {
|
|
5118
5212
|
consoleLogger.info(`Using individual resources for section: ${sectionType}`);
|
|
5119
5213
|
const sectionFilter = IPSSectionResourceHelper.getResourceFilterForSection(sectionType);
|
|
@@ -5129,10 +5223,11 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5129
5223
|
* @param authorOrganizationName - Name of the authoring organization
|
|
5130
5224
|
* @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
|
|
5131
5225
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
5226
|
+
* @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
|
|
5132
5227
|
* @param patientId - Optional patient ID to use as primary patient for composition reference
|
|
5133
5228
|
* @param now - Optional current date to use for composition date (defaults to new Date())
|
|
5134
5229
|
*/
|
|
5135
|
-
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
|
|
5230
|
+
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, includeSummaryCompositionOnly = false, patientId, now) {
|
|
5136
5231
|
if (baseUrl.endsWith("/")) {
|
|
5137
5232
|
baseUrl = baseUrl.slice(0, -1);
|
|
5138
5233
|
}
|
|
@@ -5181,30 +5276,32 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5181
5276
|
fullUrl: `${baseUrl}/Composition/${composition.id}`,
|
|
5182
5277
|
resource: composition
|
|
5183
5278
|
});
|
|
5184
|
-
|
|
5279
|
+
if (!includeSummaryCompositionOnly) {
|
|
5280
|
+
this.patients.forEach((patient) => {
|
|
5281
|
+
bundle.entry?.push({
|
|
5282
|
+
fullUrl: `${baseUrl}/Patient/${patient.id}`,
|
|
5283
|
+
resource: patient
|
|
5284
|
+
});
|
|
5285
|
+
});
|
|
5286
|
+
this.resources.forEach((resource) => {
|
|
5287
|
+
if (resource.resourceType !== "Patient") {
|
|
5288
|
+
bundle.entry?.push(
|
|
5289
|
+
{
|
|
5290
|
+
fullUrl: `${baseUrl}/${resource.resourceType}/${resource.id}`,
|
|
5291
|
+
resource
|
|
5292
|
+
}
|
|
5293
|
+
);
|
|
5294
|
+
}
|
|
5295
|
+
});
|
|
5185
5296
|
bundle.entry?.push({
|
|
5186
|
-
fullUrl: `${baseUrl}/
|
|
5187
|
-
resource:
|
|
5297
|
+
fullUrl: `${baseUrl}/Organization/${authorOrganizationId}`,
|
|
5298
|
+
resource: {
|
|
5299
|
+
resourceType: "Organization",
|
|
5300
|
+
id: authorOrganizationId,
|
|
5301
|
+
name: authorOrganizationName
|
|
5302
|
+
}
|
|
5188
5303
|
});
|
|
5189
|
-
}
|
|
5190
|
-
this.resources.forEach((resource) => {
|
|
5191
|
-
if (resource.resourceType !== "Patient") {
|
|
5192
|
-
bundle.entry?.push(
|
|
5193
|
-
{
|
|
5194
|
-
fullUrl: `${baseUrl}/${resource.resourceType}/${resource.id}`,
|
|
5195
|
-
resource
|
|
5196
|
-
}
|
|
5197
|
-
);
|
|
5198
|
-
}
|
|
5199
|
-
});
|
|
5200
|
-
bundle.entry?.push({
|
|
5201
|
-
fullUrl: `${baseUrl}/Organization/${authorOrganizationId}`,
|
|
5202
|
-
resource: {
|
|
5203
|
-
resourceType: "Organization",
|
|
5204
|
-
id: authorOrganizationId,
|
|
5205
|
-
name: authorOrganizationName
|
|
5206
|
-
}
|
|
5207
|
-
});
|
|
5304
|
+
}
|
|
5208
5305
|
return bundle;
|
|
5209
5306
|
}
|
|
5210
5307
|
/**
|
|
@@ -5214,6 +5311,35 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
5214
5311
|
getSections() {
|
|
5215
5312
|
return this.sections;
|
|
5216
5313
|
}
|
|
5314
|
+
/**
|
|
5315
|
+
* Identifies remaining resource types that are missing from the composition bundle.
|
|
5316
|
+
* @param bundle - FHIR Bundle containing resources
|
|
5317
|
+
* @returns Array of missing resource type strings
|
|
5318
|
+
*/
|
|
5319
|
+
getRemainingResourcesFromCompositionBundle(bundle) {
|
|
5320
|
+
const resources = [];
|
|
5321
|
+
bundle.entry?.forEach((e) => {
|
|
5322
|
+
if (e.resource) {
|
|
5323
|
+
resources.push(e.resource);
|
|
5324
|
+
}
|
|
5325
|
+
});
|
|
5326
|
+
const remainingResources = /* @__PURE__ */ new Set();
|
|
5327
|
+
for (const sectionType of Object.values(IPSSections)) {
|
|
5328
|
+
const summaryIPSCompositionFilter = IPSSectionResourceHelper.getSummaryIPSCompositionFilterForSection(sectionType);
|
|
5329
|
+
const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
|
|
5330
|
+
const summaryCompositionFilter = IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType);
|
|
5331
|
+
const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
|
|
5332
|
+
if (sectionSummary.length === 0 && sectionIPSSummary.length === 0) {
|
|
5333
|
+
const resourcesForSection = IPSSectionResourceHelper.getResourceTypesForSection(sectionType);
|
|
5334
|
+
resourcesForSection.forEach((resourceType) => {
|
|
5335
|
+
if (!remainingResources.has(resourceType) && !resources.some((r) => r.resourceType === resourceType)) {
|
|
5336
|
+
remainingResources.add(resourceType);
|
|
5337
|
+
}
|
|
5338
|
+
});
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
return Array.from(remainingResources);
|
|
5342
|
+
}
|
|
5217
5343
|
};
|
|
5218
5344
|
|
|
5219
5345
|
// src/index.ts
|