@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 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 === "Condition" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")),
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 && conditions.length === 0) {
4591
- return `<p>No history of pregnancy found.</p>`;
4675
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0) {
4676
+ return;
4592
4677
  }
4593
- let html = `<p>This list includes Observation and Condition resources relevant to pregnancy, sorted by date (most recent first).</p>`;
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: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
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
- resources.forEach((resource) => {
5083
- if (resourceEntries?.some((entry) => entry.reference === `${resource.resourceType}/${resource.id}`)) {
5084
- this.resources.add(resource);
5085
- sectionResources.push(resource);
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
- this.patients.forEach((patient) => {
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}/Patient/${patient.id}`,
5215
- resource: patient
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 === "Condition" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")),
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 && conditions.length === 0) {
4563
- return `<p>No history of pregnancy found.</p>`;
4647
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0) {
4648
+ return;
4564
4649
  }
4565
- let html = `<p>This list includes Observation and Condition resources relevant to pregnancy, sorted by date (most recent first).</p>`;
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: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
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
- resources.forEach((resource) => {
5055
- if (resourceEntries?.some((entry) => entry.reference === `${resource.resourceType}/${resource.id}`)) {
5056
- this.resources.add(resource);
5057
- sectionResources.push(resource);
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
- this.patients.forEach((patient) => {
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}/Patient/${patient.id}`,
5187
- resource: patient
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.39",
3
+ "version": "1.0.40",
4
4
  "description": "A template for creating npm packages using TypeScript and VSCode",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",