@imranq2/fhirpatientsummary 1.0.39 → 1.0.41

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,9 @@ 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)),
302
+ ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, ["ips_diagnosticreportlab_summary_document", "ips_lab_summary_document"], IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
284
303
  };
285
304
  var IPSSectionResourceHelper = class {
286
305
  static getResourceFilterForSection(section) {
@@ -291,6 +310,9 @@ var IPSSectionResourceHelper = class {
291
310
  if (!filter) return [];
292
311
  return resources.filter(filter);
293
312
  }
313
+ static getResourceTypesForSection(section) {
314
+ return IPSSectionResourcesMap[section] || [];
315
+ }
294
316
  static getSummaryCompositionFilterForSection(section) {
295
317
  const sectionCompositionEnabled = process.env.SUMMARY_COMPOSITION_SECTIONS ? process.env.SUMMARY_COMPOSITION_SECTIONS.split(",").some(
296
318
  (s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
@@ -934,6 +956,57 @@ var TemplateUtilities = class {
934
956
  }
935
957
  return "";
936
958
  }
959
+ /**
960
+ * Extracts and formats reference range data including low/high values and age ranges.
961
+ * @param listItems - Array of reference range objects
962
+ * @returns Formatted reference range string
963
+ */
964
+ extractObservationReferenceRange(listItems) {
965
+ if (!listItems || !Array.isArray(listItems) || listItems.length === 0) {
966
+ return "";
967
+ }
968
+ const referenceRange = listItems[0];
969
+ let result = "";
970
+ const low = referenceRange.low;
971
+ const high = referenceRange.high;
972
+ if (low) {
973
+ if (low.value != void 0 && low.value !== null) {
974
+ result += this.formatFloatValue(low.value);
975
+ if (low.unit) {
976
+ result += ` ${low.unit}`;
977
+ }
978
+ }
979
+ }
980
+ if (high) {
981
+ if (result && (high.value !== void 0 && high.value !== null)) {
982
+ result += " - ";
983
+ }
984
+ result += this.formatFloatValue(high.value);
985
+ if (high.unit) {
986
+ result += ` ${high.unit}`;
987
+ }
988
+ }
989
+ const age = referenceRange.age;
990
+ if (age) {
991
+ const ageParts = [];
992
+ const ageLow = age.low;
993
+ const ageHigh = age.high;
994
+ if (ageLow && ageLow.value != void 0 && ageLow.value !== null) {
995
+ ageParts.push(`${this.formatFloatValue(ageLow.value)}${ageLow.unit ? ` ${ageLow.unit}` : ""}`);
996
+ }
997
+ if (ageHigh && ageHigh.value != void 0 && ageHigh.value !== null) {
998
+ ageParts.push(`${this.formatFloatValue(ageHigh.value)}${ageHigh.unit ? ` ${ageHigh.unit}` : ""}`);
999
+ }
1000
+ if (ageParts.length > 0) {
1001
+ if (result) {
1002
+ result += ` (Age: ${ageParts.join(" - ")})`;
1003
+ } else {
1004
+ result = `Age: ${ageParts.join(" - ")}`;
1005
+ }
1006
+ }
1007
+ }
1008
+ return result;
1009
+ }
937
1010
  extractObservationSummaryReferenceRange(data) {
938
1011
  let referenceRange = "";
939
1012
  if (data["referenceRange.low.value"]) {
@@ -3664,7 +3737,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3664
3737
  <td>${templateUtilities.capitalizeFirstLetter(obsCodeDisplay)}</td>
3665
3738
  <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
3666
3739
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3667
- <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
3740
+ <td>${templateUtilities.extractObservationReferenceRange(obs.referenceRange)}</td>
3668
3741
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3669
3742
  <td>${templateUtilities.getOwnerTag(obs)}</td>
3670
3743
  </tr>`;
@@ -4555,6 +4628,67 @@ var PregnancyTemplate = class _PregnancyTemplate {
4555
4628
  generateNarrative(resources, timezone) {
4556
4629
  return _PregnancyTemplate.generateStaticNarrative(resources, timezone);
4557
4630
  }
4631
+ /**
4632
+ * Generate HTML narrative for pregnancy using summary
4633
+ * @param resources - FHIR Composition resources
4634
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4635
+ * @returns HTML string for rendering
4636
+ */
4637
+ generateSummaryNarrative(resources, timezone) {
4638
+ const templateUtilities = new TemplateUtilities(resources);
4639
+ let isSummaryCreated = false;
4640
+ let html = `<p>This list includes all information about the patient's pregnancy history, sorted by effective date (most recent first).</p>
4641
+ `;
4642
+ html += `
4643
+ <div>
4644
+ <table>
4645
+ <thead>
4646
+ <tr>
4647
+ <th>Result</th>
4648
+ <th>Code (System)</th>
4649
+ <th>Comments</th>
4650
+ <th>Date</th>
4651
+ <th>Source</th>
4652
+ </tr>
4653
+ </thead>
4654
+ <tbody>`;
4655
+ for (const resourceItem of resources) {
4656
+ for (const rowData of resourceItem.section ?? []) {
4657
+ const sectionCodeableConcept = rowData.code;
4658
+ const rowTitle = rowData.title;
4659
+ const data = {};
4660
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
4661
+ for (const columnData of rowData.section ?? []) {
4662
+ const columnTitle = columnData.title;
4663
+ if (columnTitle) {
4664
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
4665
+ }
4666
+ }
4667
+ if (rowTitle === "Expected Delivery Date") {
4668
+ data["Result"] = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(data, timezone));
4669
+ } else {
4670
+ data["Result"] = data["Pregnancy Result"];
4671
+ }
4672
+ if (data["Result"]?.toLowerCase() === "unknown") {
4673
+ continue;
4674
+ }
4675
+ isSummaryCreated = true;
4676
+ html += `
4677
+ <tr>
4678
+ <td class="Result">${data["Result"] ? templateUtilities.capitalizeFirstLetter(data["Result"]) : ""}</td>
4679
+ <td class="CodeSystem">${data["codeSystem"]}</td>
4680
+ <td class="Comments">${data["Comments"] ?? ""}</td>
4681
+ <td class="Date">${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
4682
+ <td class="Source">${data["Source"] ?? ""}</td>
4683
+ </tr>`;
4684
+ }
4685
+ }
4686
+ html += `
4687
+ </tbody>
4688
+ </table>
4689
+ </div>`;
4690
+ return isSummaryCreated ? html : void 0;
4691
+ }
4558
4692
  /**
4559
4693
  * Internal static implementation that actually generates the narrative
4560
4694
  * @param resources - FHIR Observation resources
@@ -4563,10 +4697,13 @@ var PregnancyTemplate = class _PregnancyTemplate {
4563
4697
  */
4564
4698
  static generateStaticNarrative(resources, timezone) {
4565
4699
  const templateUtilities = new TemplateUtilities(resources);
4700
+ const patients = resources.filter((r) => r.resourceType === "Patient");
4701
+ if (patients.length === 0) {
4702
+ return;
4703
+ }
4566
4704
  const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
4567
4705
  const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
4568
4706
  const observations = filteredResources.filter((r) => r.resourceType === "Observation");
4569
- const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
4570
4707
  const EDD_LOINC = "11778-8";
4571
4708
  const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
4572
4709
  const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
@@ -4587,10 +4724,10 @@ var PregnancyTemplate = class _PregnancyTemplate {
4587
4724
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
4588
4725
  return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
4589
4726
  });
4590
- if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
4591
- return `<p>No history of pregnancy found.</p>`;
4727
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0) {
4728
+ return;
4592
4729
  }
4593
- let html = `<p>This list includes Observation and Condition resources relevant to pregnancy, sorted by date (most recent first).</p>`;
4730
+ let html = `<p>This list includes Observation resources relevant to pregnancy, sorted by date (most recent first).</p>`;
4594
4731
  html += `
4595
4732
  <table>
4596
4733
  <thead>
@@ -4629,11 +4766,6 @@ var PregnancyTemplate = class _PregnancyTemplate {
4629
4766
  const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
4630
4767
  rowResources.push({ resource: obs, date, type: "history" });
4631
4768
  }
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
4769
  rowResources.sort((a, b) => {
4638
4770
  if (!a.date && !b.date) return 0;
4639
4771
  if (!a.date) return 1;
@@ -4649,7 +4781,7 @@ var PregnancyTemplate = class _PregnancyTemplate {
4649
4781
  dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
4650
4782
  codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
4651
4783
  } else if (type === "edd") {
4652
- result = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
4784
+ result = "Estimated Delivery Date: " + (templateUtilities.extractObservationValue(resource) ?? "");
4653
4785
  comments = templateUtilities.renderNotes(resource.note, timezone);
4654
4786
  dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
4655
4787
  codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
@@ -4658,11 +4790,6 @@ var PregnancyTemplate = class _PregnancyTemplate {
4658
4790
  comments = templateUtilities.renderNotes(resource.note, timezone);
4659
4791
  dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
4660
4792
  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
4793
  }
4667
4794
  const rowKey = `${result}|${codeSystem}`;
4668
4795
  if (!addedRows.has(rowKey)) {
@@ -5075,16 +5202,33 @@ var ComprehensiveIPSCompositionBuilder = class {
5075
5202
  this.addSectionAsync(narrative, sectionType, validResources);
5076
5203
  return this;
5077
5204
  }
5078
- async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone) {
5205
+ async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone, includeSummaryCompositionOnly = false) {
5079
5206
  const sectionResources = [];
5080
5207
  for (const summaryComposition of summaryCompositions) {
5081
5208
  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
- });
5209
+ if (includeSummaryCompositionOnly) {
5210
+ const addedEntries = /* @__PURE__ */ new Set();
5211
+ resourceEntries.forEach((entry) => {
5212
+ if (entry.reference && !addedEntries.has(entry.reference)) {
5213
+ const reference = entry.reference.split("/");
5214
+ if (reference.length === 2) {
5215
+ const resource = {
5216
+ id: reference[1],
5217
+ resourceType: reference[0]
5218
+ };
5219
+ sectionResources.push(resource);
5220
+ addedEntries.add(entry.reference);
5221
+ }
5222
+ }
5223
+ });
5224
+ } else {
5225
+ resources.forEach((resource) => {
5226
+ if (resourceEntries?.some((entry) => entry.reference === `${resource.resourceType}/${resource.id}`)) {
5227
+ this.resources.add(resource);
5228
+ sectionResources.push(resource);
5229
+ }
5230
+ });
5231
+ }
5088
5232
  }
5089
5233
  let narrative = await NarrativeGenerator.generateNarrativeAsync(
5090
5234
  sectionType,
@@ -5109,8 +5253,10 @@ var ComprehensiveIPSCompositionBuilder = class {
5109
5253
  * @param bundle - FHIR Bundle containing resources
5110
5254
  * @param timezone - Optional timezone to use for date formatting
5111
5255
  * @param useSummaryCompositions - Whether to use summary compositions (default: false)
5256
+ * @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
5257
+ * @param consoleLogger - Optional console logger for logging (default: console)
5112
5258
  */
5113
- async readBundleAsync(bundle, timezone, useSummaryCompositions = false, consoleLogger = console) {
5259
+ async readBundleAsync(bundle, timezone, useSummaryCompositions = false, includeSummaryCompositionOnly = false, consoleLogger = console) {
5114
5260
  if (!bundle.entry) {
5115
5261
  return this;
5116
5262
  }
@@ -5134,14 +5280,14 @@ var ComprehensiveIPSCompositionBuilder = class {
5134
5280
  const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
5135
5281
  if (sectionIPSSummary.length > 0) {
5136
5282
  consoleLogger.info(`Using IPS summary composition for section: ${sectionType}`);
5137
- await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone);
5283
+ await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone, includeSummaryCompositionOnly);
5138
5284
  continue;
5139
5285
  }
5140
5286
  const summaryCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType) : void 0;
5141
5287
  const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
5142
5288
  if (sectionSummary.length > 0) {
5143
5289
  consoleLogger.info(`Using summary composition for section: ${sectionType}`);
5144
- await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone);
5290
+ await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone, includeSummaryCompositionOnly);
5145
5291
  } else {
5146
5292
  consoleLogger.info(`Using individual resources for section: ${sectionType}`);
5147
5293
  const sectionFilter = IPSSectionResourceHelper.getResourceFilterForSection(sectionType);
@@ -5157,10 +5303,11 @@ var ComprehensiveIPSCompositionBuilder = class {
5157
5303
  * @param authorOrganizationName - Name of the authoring organization
5158
5304
  * @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
5159
5305
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
5306
+ * @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
5160
5307
  * @param patientId - Optional patient ID to use as primary patient for composition reference
5161
5308
  * @param now - Optional current date to use for composition date (defaults to new Date())
5162
5309
  */
5163
- async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
5310
+ async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, includeSummaryCompositionOnly = false, patientId, now) {
5164
5311
  if (baseUrl.endsWith("/")) {
5165
5312
  baseUrl = baseUrl.slice(0, -1);
5166
5313
  }
@@ -5209,30 +5356,32 @@ var ComprehensiveIPSCompositionBuilder = class {
5209
5356
  fullUrl: `${baseUrl}/Composition/${composition.id}`,
5210
5357
  resource: composition
5211
5358
  });
5212
- this.patients.forEach((patient) => {
5359
+ if (!includeSummaryCompositionOnly) {
5360
+ this.patients.forEach((patient) => {
5361
+ bundle.entry?.push({
5362
+ fullUrl: `${baseUrl}/Patient/${patient.id}`,
5363
+ resource: patient
5364
+ });
5365
+ });
5366
+ this.resources.forEach((resource) => {
5367
+ if (resource.resourceType !== "Patient") {
5368
+ bundle.entry?.push(
5369
+ {
5370
+ fullUrl: `${baseUrl}/${resource.resourceType}/${resource.id}`,
5371
+ resource
5372
+ }
5373
+ );
5374
+ }
5375
+ });
5213
5376
  bundle.entry?.push({
5214
- fullUrl: `${baseUrl}/Patient/${patient.id}`,
5215
- resource: patient
5377
+ fullUrl: `${baseUrl}/Organization/${authorOrganizationId}`,
5378
+ resource: {
5379
+ resourceType: "Organization",
5380
+ id: authorOrganizationId,
5381
+ name: authorOrganizationName
5382
+ }
5216
5383
  });
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
- });
5384
+ }
5236
5385
  return bundle;
5237
5386
  }
5238
5387
  /**
@@ -5242,6 +5391,35 @@ var ComprehensiveIPSCompositionBuilder = class {
5242
5391
  getSections() {
5243
5392
  return this.sections;
5244
5393
  }
5394
+ /**
5395
+ * Identifies remaining resource types that are missing from the composition bundle.
5396
+ * @param bundle - FHIR Bundle containing resources
5397
+ * @returns Array of missing resource type strings
5398
+ */
5399
+ getRemainingResourcesFromCompositionBundle(bundle) {
5400
+ const resources = [];
5401
+ bundle.entry?.forEach((e) => {
5402
+ if (e.resource) {
5403
+ resources.push(e.resource);
5404
+ }
5405
+ });
5406
+ const remainingResources = /* @__PURE__ */ new Set();
5407
+ for (const sectionType of Object.values(IPSSections)) {
5408
+ const summaryIPSCompositionFilter = IPSSectionResourceHelper.getSummaryIPSCompositionFilterForSection(sectionType);
5409
+ const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
5410
+ const summaryCompositionFilter = IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType);
5411
+ const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
5412
+ if (sectionSummary.length === 0 && sectionIPSSummary.length === 0) {
5413
+ const resourcesForSection = IPSSectionResourceHelper.getResourceTypesForSection(sectionType);
5414
+ resourcesForSection.forEach((resourceType) => {
5415
+ if (!remainingResources.has(resourceType) && !resources.some((r) => r.resourceType === resourceType)) {
5416
+ remainingResources.add(resourceType);
5417
+ }
5418
+ });
5419
+ }
5420
+ }
5421
+ return Array.from(remainingResources);
5422
+ }
5245
5423
  };
5246
5424
 
5247
5425
  // 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,9 @@ 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)),
274
+ ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, ["ips_diagnosticreportlab_summary_document", "ips_lab_summary_document"], IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
256
275
  };
257
276
  var IPSSectionResourceHelper = class {
258
277
  static getResourceFilterForSection(section) {
@@ -263,6 +282,9 @@ var IPSSectionResourceHelper = class {
263
282
  if (!filter) return [];
264
283
  return resources.filter(filter);
265
284
  }
285
+ static getResourceTypesForSection(section) {
286
+ return IPSSectionResourcesMap[section] || [];
287
+ }
266
288
  static getSummaryCompositionFilterForSection(section) {
267
289
  const sectionCompositionEnabled = process.env.SUMMARY_COMPOSITION_SECTIONS ? process.env.SUMMARY_COMPOSITION_SECTIONS.split(",").some(
268
290
  (s) => s.trim().toLowerCase() === section.toString().toLowerCase() || s.trim().toLowerCase() === "all"
@@ -906,6 +928,57 @@ var TemplateUtilities = class {
906
928
  }
907
929
  return "";
908
930
  }
931
+ /**
932
+ * Extracts and formats reference range data including low/high values and age ranges.
933
+ * @param listItems - Array of reference range objects
934
+ * @returns Formatted reference range string
935
+ */
936
+ extractObservationReferenceRange(listItems) {
937
+ if (!listItems || !Array.isArray(listItems) || listItems.length === 0) {
938
+ return "";
939
+ }
940
+ const referenceRange = listItems[0];
941
+ let result = "";
942
+ const low = referenceRange.low;
943
+ const high = referenceRange.high;
944
+ if (low) {
945
+ if (low.value != void 0 && low.value !== null) {
946
+ result += this.formatFloatValue(low.value);
947
+ if (low.unit) {
948
+ result += ` ${low.unit}`;
949
+ }
950
+ }
951
+ }
952
+ if (high) {
953
+ if (result && (high.value !== void 0 && high.value !== null)) {
954
+ result += " - ";
955
+ }
956
+ result += this.formatFloatValue(high.value);
957
+ if (high.unit) {
958
+ result += ` ${high.unit}`;
959
+ }
960
+ }
961
+ const age = referenceRange.age;
962
+ if (age) {
963
+ const ageParts = [];
964
+ const ageLow = age.low;
965
+ const ageHigh = age.high;
966
+ if (ageLow && ageLow.value != void 0 && ageLow.value !== null) {
967
+ ageParts.push(`${this.formatFloatValue(ageLow.value)}${ageLow.unit ? ` ${ageLow.unit}` : ""}`);
968
+ }
969
+ if (ageHigh && ageHigh.value != void 0 && ageHigh.value !== null) {
970
+ ageParts.push(`${this.formatFloatValue(ageHigh.value)}${ageHigh.unit ? ` ${ageHigh.unit}` : ""}`);
971
+ }
972
+ if (ageParts.length > 0) {
973
+ if (result) {
974
+ result += ` (Age: ${ageParts.join(" - ")})`;
975
+ } else {
976
+ result = `Age: ${ageParts.join(" - ")}`;
977
+ }
978
+ }
979
+ }
980
+ return result;
981
+ }
909
982
  extractObservationSummaryReferenceRange(data) {
910
983
  let referenceRange = "";
911
984
  if (data["referenceRange.low.value"]) {
@@ -3636,7 +3709,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3636
3709
  <td>${templateUtilities.capitalizeFirstLetter(obsCodeDisplay)}</td>
3637
3710
  <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
3638
3711
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3639
- <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
3712
+ <td>${templateUtilities.extractObservationReferenceRange(obs.referenceRange)}</td>
3640
3713
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3641
3714
  <td>${templateUtilities.getOwnerTag(obs)}</td>
3642
3715
  </tr>`;
@@ -4527,6 +4600,67 @@ var PregnancyTemplate = class _PregnancyTemplate {
4527
4600
  generateNarrative(resources, timezone) {
4528
4601
  return _PregnancyTemplate.generateStaticNarrative(resources, timezone);
4529
4602
  }
4603
+ /**
4604
+ * Generate HTML narrative for pregnancy using summary
4605
+ * @param resources - FHIR Composition resources
4606
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4607
+ * @returns HTML string for rendering
4608
+ */
4609
+ generateSummaryNarrative(resources, timezone) {
4610
+ const templateUtilities = new TemplateUtilities(resources);
4611
+ let isSummaryCreated = false;
4612
+ let html = `<p>This list includes all information about the patient's pregnancy history, sorted by effective date (most recent first).</p>
4613
+ `;
4614
+ html += `
4615
+ <div>
4616
+ <table>
4617
+ <thead>
4618
+ <tr>
4619
+ <th>Result</th>
4620
+ <th>Code (System)</th>
4621
+ <th>Comments</th>
4622
+ <th>Date</th>
4623
+ <th>Source</th>
4624
+ </tr>
4625
+ </thead>
4626
+ <tbody>`;
4627
+ for (const resourceItem of resources) {
4628
+ for (const rowData of resourceItem.section ?? []) {
4629
+ const sectionCodeableConcept = rowData.code;
4630
+ const rowTitle = rowData.title;
4631
+ const data = {};
4632
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
4633
+ for (const columnData of rowData.section ?? []) {
4634
+ const columnTitle = columnData.title;
4635
+ if (columnTitle) {
4636
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
4637
+ }
4638
+ }
4639
+ if (rowTitle === "Expected Delivery Date") {
4640
+ data["Result"] = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(data, timezone));
4641
+ } else {
4642
+ data["Result"] = data["Pregnancy Result"];
4643
+ }
4644
+ if (data["Result"]?.toLowerCase() === "unknown") {
4645
+ continue;
4646
+ }
4647
+ isSummaryCreated = true;
4648
+ html += `
4649
+ <tr>
4650
+ <td class="Result">${data["Result"] ? templateUtilities.capitalizeFirstLetter(data["Result"]) : ""}</td>
4651
+ <td class="CodeSystem">${data["codeSystem"]}</td>
4652
+ <td class="Comments">${data["Comments"] ?? ""}</td>
4653
+ <td class="Date">${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
4654
+ <td class="Source">${data["Source"] ?? ""}</td>
4655
+ </tr>`;
4656
+ }
4657
+ }
4658
+ html += `
4659
+ </tbody>
4660
+ </table>
4661
+ </div>`;
4662
+ return isSummaryCreated ? html : void 0;
4663
+ }
4530
4664
  /**
4531
4665
  * Internal static implementation that actually generates the narrative
4532
4666
  * @param resources - FHIR Observation resources
@@ -4535,10 +4669,13 @@ var PregnancyTemplate = class _PregnancyTemplate {
4535
4669
  */
4536
4670
  static generateStaticNarrative(resources, timezone) {
4537
4671
  const templateUtilities = new TemplateUtilities(resources);
4672
+ const patients = resources.filter((r) => r.resourceType === "Patient");
4673
+ if (patients.length === 0) {
4674
+ return;
4675
+ }
4538
4676
  const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
4539
4677
  const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
4540
4678
  const observations = filteredResources.filter((r) => r.resourceType === "Observation");
4541
- const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
4542
4679
  const EDD_LOINC = "11778-8";
4543
4680
  const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
4544
4681
  const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
@@ -4559,10 +4696,10 @@ var PregnancyTemplate = class _PregnancyTemplate {
4559
4696
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
4560
4697
  return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
4561
4698
  });
4562
- if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
4563
- return `<p>No history of pregnancy found.</p>`;
4699
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0) {
4700
+ return;
4564
4701
  }
4565
- let html = `<p>This list includes Observation and Condition resources relevant to pregnancy, sorted by date (most recent first).</p>`;
4702
+ let html = `<p>This list includes Observation resources relevant to pregnancy, sorted by date (most recent first).</p>`;
4566
4703
  html += `
4567
4704
  <table>
4568
4705
  <thead>
@@ -4601,11 +4738,6 @@ var PregnancyTemplate = class _PregnancyTemplate {
4601
4738
  const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
4602
4739
  rowResources.push({ resource: obs, date, type: "history" });
4603
4740
  }
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
4741
  rowResources.sort((a, b) => {
4610
4742
  if (!a.date && !b.date) return 0;
4611
4743
  if (!a.date) return 1;
@@ -4621,7 +4753,7 @@ var PregnancyTemplate = class _PregnancyTemplate {
4621
4753
  dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
4622
4754
  codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
4623
4755
  } else if (type === "edd") {
4624
- result = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
4756
+ result = "Estimated Delivery Date: " + (templateUtilities.extractObservationValue(resource) ?? "");
4625
4757
  comments = templateUtilities.renderNotes(resource.note, timezone);
4626
4758
  dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
4627
4759
  codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
@@ -4630,11 +4762,6 @@ var PregnancyTemplate = class _PregnancyTemplate {
4630
4762
  comments = templateUtilities.renderNotes(resource.note, timezone);
4631
4763
  dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
4632
4764
  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
4765
  }
4639
4766
  const rowKey = `${result}|${codeSystem}`;
4640
4767
  if (!addedRows.has(rowKey)) {
@@ -5047,16 +5174,33 @@ var ComprehensiveIPSCompositionBuilder = class {
5047
5174
  this.addSectionAsync(narrative, sectionType, validResources);
5048
5175
  return this;
5049
5176
  }
5050
- async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone) {
5177
+ async makeSectionFromSummaryAsync(sectionType, summaryCompositions, resources, timezone, includeSummaryCompositionOnly = false) {
5051
5178
  const sectionResources = [];
5052
5179
  for (const summaryComposition of summaryCompositions) {
5053
5180
  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
- });
5181
+ if (includeSummaryCompositionOnly) {
5182
+ const addedEntries = /* @__PURE__ */ new Set();
5183
+ resourceEntries.forEach((entry) => {
5184
+ if (entry.reference && !addedEntries.has(entry.reference)) {
5185
+ const reference = entry.reference.split("/");
5186
+ if (reference.length === 2) {
5187
+ const resource = {
5188
+ id: reference[1],
5189
+ resourceType: reference[0]
5190
+ };
5191
+ sectionResources.push(resource);
5192
+ addedEntries.add(entry.reference);
5193
+ }
5194
+ }
5195
+ });
5196
+ } else {
5197
+ resources.forEach((resource) => {
5198
+ if (resourceEntries?.some((entry) => entry.reference === `${resource.resourceType}/${resource.id}`)) {
5199
+ this.resources.add(resource);
5200
+ sectionResources.push(resource);
5201
+ }
5202
+ });
5203
+ }
5060
5204
  }
5061
5205
  let narrative = await NarrativeGenerator.generateNarrativeAsync(
5062
5206
  sectionType,
@@ -5081,8 +5225,10 @@ var ComprehensiveIPSCompositionBuilder = class {
5081
5225
  * @param bundle - FHIR Bundle containing resources
5082
5226
  * @param timezone - Optional timezone to use for date formatting
5083
5227
  * @param useSummaryCompositions - Whether to use summary compositions (default: false)
5228
+ * @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
5229
+ * @param consoleLogger - Optional console logger for logging (default: console)
5084
5230
  */
5085
- async readBundleAsync(bundle, timezone, useSummaryCompositions = false, consoleLogger = console) {
5231
+ async readBundleAsync(bundle, timezone, useSummaryCompositions = false, includeSummaryCompositionOnly = false, consoleLogger = console) {
5086
5232
  if (!bundle.entry) {
5087
5233
  return this;
5088
5234
  }
@@ -5106,14 +5252,14 @@ var ComprehensiveIPSCompositionBuilder = class {
5106
5252
  const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
5107
5253
  if (sectionIPSSummary.length > 0) {
5108
5254
  consoleLogger.info(`Using IPS summary composition for section: ${sectionType}`);
5109
- await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone);
5255
+ await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone, includeSummaryCompositionOnly);
5110
5256
  continue;
5111
5257
  }
5112
5258
  const summaryCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType) : void 0;
5113
5259
  const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
5114
5260
  if (sectionSummary.length > 0) {
5115
5261
  consoleLogger.info(`Using summary composition for section: ${sectionType}`);
5116
- await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone);
5262
+ await this.makeSectionFromSummaryAsync(sectionType, sectionSummary, resources, timezone, includeSummaryCompositionOnly);
5117
5263
  } else {
5118
5264
  consoleLogger.info(`Using individual resources for section: ${sectionType}`);
5119
5265
  const sectionFilter = IPSSectionResourceHelper.getResourceFilterForSection(sectionType);
@@ -5129,10 +5275,11 @@ var ComprehensiveIPSCompositionBuilder = class {
5129
5275
  * @param authorOrganizationName - Name of the authoring organization
5130
5276
  * @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
5131
5277
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
5278
+ * @param includeSummaryCompositionOnly - Whether to include only summary composition resources (default: false)
5132
5279
  * @param patientId - Optional patient ID to use as primary patient for composition reference
5133
5280
  * @param now - Optional current date to use for composition date (defaults to new Date())
5134
5281
  */
5135
- async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
5282
+ async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, includeSummaryCompositionOnly = false, patientId, now) {
5136
5283
  if (baseUrl.endsWith("/")) {
5137
5284
  baseUrl = baseUrl.slice(0, -1);
5138
5285
  }
@@ -5181,30 +5328,32 @@ var ComprehensiveIPSCompositionBuilder = class {
5181
5328
  fullUrl: `${baseUrl}/Composition/${composition.id}`,
5182
5329
  resource: composition
5183
5330
  });
5184
- this.patients.forEach((patient) => {
5331
+ if (!includeSummaryCompositionOnly) {
5332
+ this.patients.forEach((patient) => {
5333
+ bundle.entry?.push({
5334
+ fullUrl: `${baseUrl}/Patient/${patient.id}`,
5335
+ resource: patient
5336
+ });
5337
+ });
5338
+ this.resources.forEach((resource) => {
5339
+ if (resource.resourceType !== "Patient") {
5340
+ bundle.entry?.push(
5341
+ {
5342
+ fullUrl: `${baseUrl}/${resource.resourceType}/${resource.id}`,
5343
+ resource
5344
+ }
5345
+ );
5346
+ }
5347
+ });
5185
5348
  bundle.entry?.push({
5186
- fullUrl: `${baseUrl}/Patient/${patient.id}`,
5187
- resource: patient
5349
+ fullUrl: `${baseUrl}/Organization/${authorOrganizationId}`,
5350
+ resource: {
5351
+ resourceType: "Organization",
5352
+ id: authorOrganizationId,
5353
+ name: authorOrganizationName
5354
+ }
5188
5355
  });
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
- });
5356
+ }
5208
5357
  return bundle;
5209
5358
  }
5210
5359
  /**
@@ -5214,6 +5363,35 @@ var ComprehensiveIPSCompositionBuilder = class {
5214
5363
  getSections() {
5215
5364
  return this.sections;
5216
5365
  }
5366
+ /**
5367
+ * Identifies remaining resource types that are missing from the composition bundle.
5368
+ * @param bundle - FHIR Bundle containing resources
5369
+ * @returns Array of missing resource type strings
5370
+ */
5371
+ getRemainingResourcesFromCompositionBundle(bundle) {
5372
+ const resources = [];
5373
+ bundle.entry?.forEach((e) => {
5374
+ if (e.resource) {
5375
+ resources.push(e.resource);
5376
+ }
5377
+ });
5378
+ const remainingResources = /* @__PURE__ */ new Set();
5379
+ for (const sectionType of Object.values(IPSSections)) {
5380
+ const summaryIPSCompositionFilter = IPSSectionResourceHelper.getSummaryIPSCompositionFilterForSection(sectionType);
5381
+ const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
5382
+ const summaryCompositionFilter = IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType);
5383
+ const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
5384
+ if (sectionSummary.length === 0 && sectionIPSSummary.length === 0) {
5385
+ const resourcesForSection = IPSSectionResourceHelper.getResourceTypesForSection(sectionType);
5386
+ resourcesForSection.forEach((resourceType) => {
5387
+ if (!remainingResources.has(resourceType) && !resources.some((r) => r.resourceType === resourceType)) {
5388
+ remainingResources.add(resourceType);
5389
+ }
5390
+ });
5391
+ }
5392
+ }
5393
+ return Array.from(remainingResources);
5394
+ }
5217
5395
  };
5218
5396
 
5219
5397
  // 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.41",
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",