@imranq2/fhirpatientsummary 1.0.38 → 1.0.39

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.
Files changed (3) hide show
  1. package/dist/index.cjs +211 -27
  2. package/dist/index.js +211 -27
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -199,6 +199,24 @@ var FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES = {
199
199
  "55123-4": "Resident Believes Capable of Increased Independence",
200
200
  "45613-7": "Staff Believes Capable of Increased Independence"
201
201
  };
202
+ var ADVANCED_DIRECTIVE_CATEGORY_CODES = {
203
+ acd: "Advance Directive",
204
+ dnr: "Do Not Resuscitate",
205
+ polst: "POLST",
206
+ hcd: "Health Care Directive"
207
+ };
208
+ var ADVANCED_DIRECTIVE_CATEGORY_SYSTEM = "http://terminology.hl7.org/CodeSystem/consentcategorycodes";
209
+ var ADVANCED_DIRECTIVE_LOINC_CODES = {
210
+ "45473-6": "Advance healthcare directive Completed",
211
+ "45474-4": "Do not resuscitate",
212
+ "45475-1": "Do not hospitalize",
213
+ "45476-9": "Organ donation",
214
+ "45477-7": "Autopsy request",
215
+ "45478-5": "Feeding restrictions",
216
+ "45479-3": "Medication restrictions",
217
+ "45480-1": "Other treatment restrictions"
218
+ };
219
+ var LOINC_SYSTEM = "http://loinc.org";
202
220
 
203
221
  // src/structures/ips_section_constants.ts
204
222
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -243,8 +261,7 @@ var IPSSectionResourceFilters = {
243
261
  // Only include active care plans
244
262
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
245
263
  // Only include active advance directives (Consent resources)
246
- // TODO: disable this until we right logic to get these
247
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
264
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active" && resource.scope?.coding?.some((c) => codingMatches(c, "adr", "http://terminology.hl7.org/CodeSystem/consentscope"))
248
265
  };
249
266
  var IPSSectionSummaryCompositionFilter = {
250
267
  ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
@@ -260,8 +277,10 @@ var IPSSectionSummaryCompositionFilter = {
260
277
  var IPSSectionSummaryIPSCompositionFilter = {
261
278
  ["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_patient_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
262
279
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
280
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_advanced_directives_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
263
281
  ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_social_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
264
- ["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))
282
+ ["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))
265
284
  };
266
285
  var IPSSectionResourceHelper = class {
267
286
  static getResourceFilterForSection(section) {
@@ -393,13 +412,26 @@ var TemplateUtilities = class {
393
412
  return code ? `${code} (${systemDisplay})` : "";
394
413
  }
395
414
  /**
396
- * Renders a Device reference
415
+ * Resolves and returns a Device resource from a Device Reference.
416
+ * Returns null if the reference cannot be resolved or is not a Device.
417
+ * @param deviceRef - Reference to a Device resource
418
+ * @returns The resolved Device resource or null
419
+ */
420
+ getDeviceFromReference(deviceRef) {
421
+ if (!deviceRef) return null;
422
+ const device = this.resolveReference(deviceRef);
423
+ return device && device.resourceType === "Device" ? device : null;
424
+ }
425
+ /**
426
+ * Renders a Device name from a Device reference.
427
+ * Uses getDeviceFromReference to resolve the Device and returns a concatenated device.deviceName.
428
+ * Returns an empty string if no deviceName is available. The returned text is HTML-safe.
397
429
  * @param deviceRef - Reference to a Device resource
398
- * @returns Formatted device description
430
+ * @returns Sanitized device name or empty string
399
431
  */
400
432
  renderDevice(deviceRef) {
401
- const device = deviceRef && this.resolveReference(deviceRef);
402
- if (device && device.resourceType === "Device" && device.deviceName && device.deviceName.length > 0) {
433
+ const device = this.getDeviceFromReference(deviceRef);
434
+ if (device && device.deviceName && device.deviceName.length > 0) {
403
435
  return this.safeConcat(device.deviceName, "name");
404
436
  }
405
437
  return "";
@@ -2815,11 +2847,64 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2815
2847
  return _MedicalDevicesTemplate.generateStaticNarrative(resources, timezone);
2816
2848
  }
2817
2849
  /**
2818
- * Internal static implementation that actually generates the narrative
2819
- * @param resources - FHIR resources array containing Device resources
2850
+ * Generate HTML narrative for history of medical devices using summary
2851
+ * @param resources - FHIR Composition resources
2820
2852
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2821
2853
  * @returns HTML string for rendering
2822
2854
  */
2855
+ generateSummaryNarrative(resources, timezone) {
2856
+ const templateUtilities = new TemplateUtilities(resources);
2857
+ let isSummaryCreated = false;
2858
+ let html = `<p>This list includes all information about the patient's medical devices history, sorted by recorded date (most recent first).</p>
2859
+ `;
2860
+ html += `
2861
+ <table>
2862
+ <thead>
2863
+ <tr>
2864
+ <th>Device</th>
2865
+ <th>Code (System)</th>
2866
+ <th>Status</th>
2867
+ <th>Comments</th>
2868
+ <th>Date Recorded</th>
2869
+ <th>Source</th>
2870
+ </tr>
2871
+ </thead>
2872
+ <tbody>`;
2873
+ for (const resourceItem of resources) {
2874
+ for (const rowData of resourceItem.section ?? []) {
2875
+ const sectionCodeableConcept = rowData.code;
2876
+ const data = {};
2877
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2878
+ for (const columnData of rowData.section ?? []) {
2879
+ const columnTitle = columnData.title;
2880
+ if (columnTitle) {
2881
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2882
+ }
2883
+ }
2884
+ if (data["Device Name"]?.toLowerCase() === "unknown") {
2885
+ continue;
2886
+ }
2887
+ isSummaryCreated = true;
2888
+ html += `
2889
+ <tr>
2890
+ <td>${templateUtilities.capitalizeFirstLetter(data["Device Name"] ?? "")}</td>
2891
+ <td>${data["codeSystem"] ?? ""}</td>
2892
+ <td>${templateUtilities.renderTextAsHtml(data["Status"] ?? "")}</td>
2893
+ <td>${templateUtilities.renderTextAsHtml(data["Notes"] ?? "")}</td>
2894
+ <td>${templateUtilities.renderTime(data["Recorded On"] ?? "", timezone)}</td>
2895
+ <td>${data["Source"] ?? ""}</td>
2896
+ </tr>`;
2897
+ }
2898
+ }
2899
+ html += `
2900
+ </tbody>
2901
+ </table>`;
2902
+ return isSummaryCreated ? html : void 0;
2903
+ }
2904
+ /**
2905
+ * Internal static implementation that generates the narrative from DeviceUseStatement resources.
2906
+ * The “Code (System)” column renders Device.type as "code (SystemDisplay)" using TemplateUtilities.codeableConceptCoding.
2907
+ */
2823
2908
  static generateStaticNarrative(resources, timezone) {
2824
2909
  const templateUtilities = new TemplateUtilities(resources);
2825
2910
  let html = `<p>This list includes all DeviceUseStatement resources, sorted by recorded date (most recent first).</p>
@@ -2827,6 +2912,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2827
2912
  <thead>
2828
2913
  <tr>
2829
2914
  <th>Device</th>
2915
+ <th>Code (System)</th>
2830
2916
  <th>Status</th>
2831
2917
  <th>Comments</th>
2832
2918
  <th>Date Recorded</th>
@@ -2839,26 +2925,32 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2839
2925
  const dateB = b.recordedOn;
2840
2926
  return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2841
2927
  });
2842
- let isDeviceAdded = false;
2928
+ const devicesAdded = /* @__PURE__ */ new Set();
2843
2929
  for (const dus of deviceStatements) {
2844
- const deviceName = templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device));
2845
- if (deviceName?.toLowerCase() === "unknown") {
2930
+ const deviceName = templateUtilities.renderDevice(dus.device);
2931
+ if (deviceName?.toLowerCase() === "unknown" || devicesAdded.has(deviceName)) {
2932
+ continue;
2933
+ }
2934
+ const device = templateUtilities.getDeviceFromReference(dus.device);
2935
+ const codeSystem = templateUtilities.codeableConceptCoding(device?.type);
2936
+ if (!codeSystem) {
2846
2937
  continue;
2847
2938
  }
2848
- isDeviceAdded = true;
2939
+ devicesAdded.add(deviceName);
2849
2940
  html += `
2850
2941
  <tr>
2851
2942
  <td>${templateUtilities.capitalizeFirstLetter(deviceName)}</td>
2943
+ <td>${codeSystem}</td>
2852
2944
  <td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
2853
2945
  <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
2854
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderRecorded(dus.recordedOn, timezone))}</td>
2946
+ <td>${templateUtilities.renderTime(dus.recordedOn, timezone)}</td>
2855
2947
  <td>${templateUtilities.getOwnerTag(dus)}</td>
2856
2948
  </tr>`;
2857
2949
  }
2858
2950
  html += `
2859
2951
  </tbody>
2860
2952
  </table>`;
2861
- return isDeviceAdded ? html : void 0;
2953
+ return devicesAdded.size > 0 ? html : void 0;
2862
2954
  }
2863
2955
  };
2864
2956
 
@@ -4599,7 +4691,78 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4599
4691
  const dateB = new Date(b.dateTime || 0);
4600
4692
  return dateB.getTime() - dateA.getTime();
4601
4693
  });
4602
- return _AdvanceDirectivesTemplate.generateStaticNarrative(resources, timezone);
4694
+ return _AdvanceDirectivesTemplate.generateStaticNarrative(
4695
+ resources,
4696
+ timezone
4697
+ );
4698
+ }
4699
+ /**
4700
+ * Generate HTML narrative for advance directives using summary
4701
+ * @param resources - FHIR Composition resources
4702
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4703
+ * @returns HTML string for rendering
4704
+ */
4705
+ generateSummaryNarrative(resources, timezone) {
4706
+ const templateUtilities = new TemplateUtilities(resources);
4707
+ let isSummaryCreated = false;
4708
+ let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
4709
+ `;
4710
+ html += `
4711
+ <div>
4712
+ <table>
4713
+ <thead>
4714
+ <tr>
4715
+ <th>Directive</th>
4716
+ <th>Code (System)</th>
4717
+ <th>Action Controlled</th>
4718
+ <th>Policy Rule</th>
4719
+ <th>Date</th>
4720
+ <th>Source</th>
4721
+ </tr>
4722
+ </thead>
4723
+ <tbody>`;
4724
+ for (const resourceItem of resources) {
4725
+ for (const rowData of resourceItem.section ?? []) {
4726
+ const sectionCodeableConcept = rowData.code;
4727
+ const data = {};
4728
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(
4729
+ sectionCodeableConcept
4730
+ );
4731
+ for (const columnData of rowData.section ?? []) {
4732
+ const columnTitle = columnData.title;
4733
+ if (columnTitle) {
4734
+ if (columnTitle === "DateTime") {
4735
+ data[columnTitle] = templateUtilities.renderTime(
4736
+ columnData.text?.div ?? "",
4737
+ timezone
4738
+ );
4739
+ } else {
4740
+ data[columnTitle] = templateUtilities.renderTextAsHtml(
4741
+ columnData.text?.div ?? ""
4742
+ );
4743
+ }
4744
+ }
4745
+ }
4746
+ if (data["Advanced Directive Name"]?.toLowerCase() === "unknown") {
4747
+ continue;
4748
+ }
4749
+ isSummaryCreated = true;
4750
+ html += `
4751
+ <tr>
4752
+ <td>${templateUtilities.capitalizeFirstLetter(data["Advanced Directive Name"] ?? "")}</td>
4753
+ <td>${data["codeSystem"] ?? ""}</td>
4754
+ <td>${data["Provision Action"] ?? ""}</td>
4755
+ <td>${data["Policy Rule"] ?? ""}</td>
4756
+ <td>${data["DateTime"] ?? ""}</td>
4757
+ <td>${data["Source"] ?? ""}</td>
4758
+ </tr>`;
4759
+ }
4760
+ }
4761
+ html += `
4762
+ </tbody>
4763
+ </table>
4764
+ </div>`;
4765
+ return isSummaryCreated ? html : void 0;
4603
4766
  }
4604
4767
  /**
4605
4768
  * Internal static implementation that actually generates the narrative
@@ -4607,7 +4770,6 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4607
4770
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4608
4771
  * @returns HTML string for rendering
4609
4772
  */
4610
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4611
4773
  static generateStaticNarrative(resources, timezone) {
4612
4774
  const templateUtilities = new TemplateUtilities(resources);
4613
4775
  let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
@@ -4615,35 +4777,57 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4615
4777
  html += `<table>
4616
4778
  <thead>
4617
4779
  <tr>
4618
- <th>Scope</th>
4619
- <th>Status</th>
4780
+ <th>Directive</th>
4781
+ <th>Code (System)</th>
4620
4782
  <th>Action Controlled</th>
4783
+ <th>Policy Rule</th>
4621
4784
  <th>Date</th>
4622
4785
  <th>Source</th>
4623
4786
  </tr>
4624
4787
  </thead>
4625
4788
  <tbody>`;
4626
- let isConsentAdded = false;
4789
+ const consentAdded = /* @__PURE__ */ new Set();
4627
4790
  for (const resourceItem of resources) {
4628
4791
  const consent = resourceItem;
4629
- const consentScope = templateUtilities.capitalizeFirstLetter(templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display")));
4630
- if (!consentScope || consentScope.toLowerCase() === "unknown") {
4792
+ let consentDirective = {};
4793
+ for (const category of consent.category || []) {
4794
+ for (const coding of category.coding || []) {
4795
+ if (coding.system === ADVANCED_DIRECTIVE_CATEGORY_SYSTEM && coding.code && coding.code in ADVANCED_DIRECTIVE_CATEGORY_CODES) {
4796
+ consentDirective = {
4797
+ name: ADVANCED_DIRECTIVE_CATEGORY_CODES[coding.code],
4798
+ system: coding.system,
4799
+ code: coding.code
4800
+ };
4801
+ break;
4802
+ } else if (coding.system === LOINC_SYSTEM && coding.code && coding.code in ADVANCED_DIRECTIVE_LOINC_CODES) {
4803
+ consentDirective = {
4804
+ name: ADVANCED_DIRECTIVE_LOINC_CODES[coding.code],
4805
+ system: coding.system,
4806
+ code: coding.code
4807
+ };
4808
+ break;
4809
+ }
4810
+ }
4811
+ }
4812
+ if (!consentDirective.name || !consentDirective.code || !consentDirective.system || consentDirective.name.toLowerCase() === "unknown" || consentAdded.has(consentDirective.name)) {
4631
4813
  continue;
4632
4814
  }
4633
- isConsentAdded = true;
4815
+ consentAdded.add(consentDirective.name);
4816
+ const consentCodeSystem = `${consentDirective.code} (${codingSystemDisplayNames_default[consentDirective.system] || consentDirective.system})`;
4634
4817
  html += `
4635
4818
  <tr>
4636
- <td>${consentScope}</td>
4637
- <td>${consent.status || ""}</td>
4819
+ <td>${consentDirective.name}</td>
4820
+ <td>${consentCodeSystem || ""}</td>
4638
4821
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
4639
- <td>${consent.dateTime || ""}</td>
4822
+ <td>${consent.policyRule ? templateUtilities.formatCodeableConceptValue(consent.policyRule) : ""}</td>
4823
+ <td>${templateUtilities.renderTime(consent.dateTime, timezone) || ""}</td>
4640
4824
  <td>${templateUtilities.getOwnerTag(consent)}</td>
4641
4825
  </tr>`;
4642
4826
  }
4643
4827
  html += `
4644
4828
  </tbody>
4645
4829
  </table>`;
4646
- return isConsentAdded ? html : void 0;
4830
+ return consentAdded.size > 0 ? html : void 0;
4647
4831
  }
4648
4832
  };
4649
4833
 
package/dist/index.js CHANGED
@@ -171,6 +171,24 @@ var FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES = {
171
171
  "55123-4": "Resident Believes Capable of Increased Independence",
172
172
  "45613-7": "Staff Believes Capable of Increased Independence"
173
173
  };
174
+ var ADVANCED_DIRECTIVE_CATEGORY_CODES = {
175
+ acd: "Advance Directive",
176
+ dnr: "Do Not Resuscitate",
177
+ polst: "POLST",
178
+ hcd: "Health Care Directive"
179
+ };
180
+ var ADVANCED_DIRECTIVE_CATEGORY_SYSTEM = "http://terminology.hl7.org/CodeSystem/consentcategorycodes";
181
+ var ADVANCED_DIRECTIVE_LOINC_CODES = {
182
+ "45473-6": "Advance healthcare directive Completed",
183
+ "45474-4": "Do not resuscitate",
184
+ "45475-1": "Do not hospitalize",
185
+ "45476-9": "Organ donation",
186
+ "45477-7": "Autopsy request",
187
+ "45478-5": "Feeding restrictions",
188
+ "45479-3": "Medication restrictions",
189
+ "45480-1": "Other treatment restrictions"
190
+ };
191
+ var LOINC_SYSTEM = "http://loinc.org";
174
192
 
175
193
  // src/structures/ips_section_constants.ts
176
194
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -215,8 +233,7 @@ var IPSSectionResourceFilters = {
215
233
  // Only include active care plans
216
234
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
217
235
  // Only include active advance directives (Consent resources)
218
- // TODO: disable this until we right logic to get these
219
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
236
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active" && resource.scope?.coding?.some((c) => codingMatches(c, "adr", "http://terminology.hl7.org/CodeSystem/consentscope"))
220
237
  };
221
238
  var IPSSectionSummaryCompositionFilter = {
222
239
  ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
@@ -232,8 +249,10 @@ var IPSSectionSummaryCompositionFilter = {
232
249
  var IPSSectionSummaryIPSCompositionFilter = {
233
250
  ["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_patient_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
234
251
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
252
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_advanced_directives_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
235
253
  ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_social_history_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
236
- ["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))
254
+ ["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))
237
256
  };
238
257
  var IPSSectionResourceHelper = class {
239
258
  static getResourceFilterForSection(section) {
@@ -365,13 +384,26 @@ var TemplateUtilities = class {
365
384
  return code ? `${code} (${systemDisplay})` : "";
366
385
  }
367
386
  /**
368
- * Renders a Device reference
387
+ * Resolves and returns a Device resource from a Device Reference.
388
+ * Returns null if the reference cannot be resolved or is not a Device.
389
+ * @param deviceRef - Reference to a Device resource
390
+ * @returns The resolved Device resource or null
391
+ */
392
+ getDeviceFromReference(deviceRef) {
393
+ if (!deviceRef) return null;
394
+ const device = this.resolveReference(deviceRef);
395
+ return device && device.resourceType === "Device" ? device : null;
396
+ }
397
+ /**
398
+ * Renders a Device name from a Device reference.
399
+ * Uses getDeviceFromReference to resolve the Device and returns a concatenated device.deviceName.
400
+ * Returns an empty string if no deviceName is available. The returned text is HTML-safe.
369
401
  * @param deviceRef - Reference to a Device resource
370
- * @returns Formatted device description
402
+ * @returns Sanitized device name or empty string
371
403
  */
372
404
  renderDevice(deviceRef) {
373
- const device = deviceRef && this.resolveReference(deviceRef);
374
- if (device && device.resourceType === "Device" && device.deviceName && device.deviceName.length > 0) {
405
+ const device = this.getDeviceFromReference(deviceRef);
406
+ if (device && device.deviceName && device.deviceName.length > 0) {
375
407
  return this.safeConcat(device.deviceName, "name");
376
408
  }
377
409
  return "";
@@ -2787,11 +2819,64 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2787
2819
  return _MedicalDevicesTemplate.generateStaticNarrative(resources, timezone);
2788
2820
  }
2789
2821
  /**
2790
- * Internal static implementation that actually generates the narrative
2791
- * @param resources - FHIR resources array containing Device resources
2822
+ * Generate HTML narrative for history of medical devices using summary
2823
+ * @param resources - FHIR Composition resources
2792
2824
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2793
2825
  * @returns HTML string for rendering
2794
2826
  */
2827
+ generateSummaryNarrative(resources, timezone) {
2828
+ const templateUtilities = new TemplateUtilities(resources);
2829
+ let isSummaryCreated = false;
2830
+ let html = `<p>This list includes all information about the patient's medical devices history, sorted by recorded date (most recent first).</p>
2831
+ `;
2832
+ html += `
2833
+ <table>
2834
+ <thead>
2835
+ <tr>
2836
+ <th>Device</th>
2837
+ <th>Code (System)</th>
2838
+ <th>Status</th>
2839
+ <th>Comments</th>
2840
+ <th>Date Recorded</th>
2841
+ <th>Source</th>
2842
+ </tr>
2843
+ </thead>
2844
+ <tbody>`;
2845
+ for (const resourceItem of resources) {
2846
+ for (const rowData of resourceItem.section ?? []) {
2847
+ const sectionCodeableConcept = rowData.code;
2848
+ const data = {};
2849
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2850
+ for (const columnData of rowData.section ?? []) {
2851
+ const columnTitle = columnData.title;
2852
+ if (columnTitle) {
2853
+ data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2854
+ }
2855
+ }
2856
+ if (data["Device Name"]?.toLowerCase() === "unknown") {
2857
+ continue;
2858
+ }
2859
+ isSummaryCreated = true;
2860
+ html += `
2861
+ <tr>
2862
+ <td>${templateUtilities.capitalizeFirstLetter(data["Device Name"] ?? "")}</td>
2863
+ <td>${data["codeSystem"] ?? ""}</td>
2864
+ <td>${templateUtilities.renderTextAsHtml(data["Status"] ?? "")}</td>
2865
+ <td>${templateUtilities.renderTextAsHtml(data["Notes"] ?? "")}</td>
2866
+ <td>${templateUtilities.renderTime(data["Recorded On"] ?? "", timezone)}</td>
2867
+ <td>${data["Source"] ?? ""}</td>
2868
+ </tr>`;
2869
+ }
2870
+ }
2871
+ html += `
2872
+ </tbody>
2873
+ </table>`;
2874
+ return isSummaryCreated ? html : void 0;
2875
+ }
2876
+ /**
2877
+ * Internal static implementation that generates the narrative from DeviceUseStatement resources.
2878
+ * The “Code (System)” column renders Device.type as "code (SystemDisplay)" using TemplateUtilities.codeableConceptCoding.
2879
+ */
2795
2880
  static generateStaticNarrative(resources, timezone) {
2796
2881
  const templateUtilities = new TemplateUtilities(resources);
2797
2882
  let html = `<p>This list includes all DeviceUseStatement resources, sorted by recorded date (most recent first).</p>
@@ -2799,6 +2884,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2799
2884
  <thead>
2800
2885
  <tr>
2801
2886
  <th>Device</th>
2887
+ <th>Code (System)</th>
2802
2888
  <th>Status</th>
2803
2889
  <th>Comments</th>
2804
2890
  <th>Date Recorded</th>
@@ -2811,26 +2897,32 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2811
2897
  const dateB = b.recordedOn;
2812
2898
  return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2813
2899
  });
2814
- let isDeviceAdded = false;
2900
+ const devicesAdded = /* @__PURE__ */ new Set();
2815
2901
  for (const dus of deviceStatements) {
2816
- const deviceName = templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device));
2817
- if (deviceName?.toLowerCase() === "unknown") {
2902
+ const deviceName = templateUtilities.renderDevice(dus.device);
2903
+ if (deviceName?.toLowerCase() === "unknown" || devicesAdded.has(deviceName)) {
2904
+ continue;
2905
+ }
2906
+ const device = templateUtilities.getDeviceFromReference(dus.device);
2907
+ const codeSystem = templateUtilities.codeableConceptCoding(device?.type);
2908
+ if (!codeSystem) {
2818
2909
  continue;
2819
2910
  }
2820
- isDeviceAdded = true;
2911
+ devicesAdded.add(deviceName);
2821
2912
  html += `
2822
2913
  <tr>
2823
2914
  <td>${templateUtilities.capitalizeFirstLetter(deviceName)}</td>
2915
+ <td>${codeSystem}</td>
2824
2916
  <td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
2825
2917
  <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
2826
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderRecorded(dus.recordedOn, timezone))}</td>
2918
+ <td>${templateUtilities.renderTime(dus.recordedOn, timezone)}</td>
2827
2919
  <td>${templateUtilities.getOwnerTag(dus)}</td>
2828
2920
  </tr>`;
2829
2921
  }
2830
2922
  html += `
2831
2923
  </tbody>
2832
2924
  </table>`;
2833
- return isDeviceAdded ? html : void 0;
2925
+ return devicesAdded.size > 0 ? html : void 0;
2834
2926
  }
2835
2927
  };
2836
2928
 
@@ -4571,7 +4663,78 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4571
4663
  const dateB = new Date(b.dateTime || 0);
4572
4664
  return dateB.getTime() - dateA.getTime();
4573
4665
  });
4574
- return _AdvanceDirectivesTemplate.generateStaticNarrative(resources, timezone);
4666
+ return _AdvanceDirectivesTemplate.generateStaticNarrative(
4667
+ resources,
4668
+ timezone
4669
+ );
4670
+ }
4671
+ /**
4672
+ * Generate HTML narrative for advance directives using summary
4673
+ * @param resources - FHIR Composition resources
4674
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4675
+ * @returns HTML string for rendering
4676
+ */
4677
+ generateSummaryNarrative(resources, timezone) {
4678
+ const templateUtilities = new TemplateUtilities(resources);
4679
+ let isSummaryCreated = false;
4680
+ let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
4681
+ `;
4682
+ html += `
4683
+ <div>
4684
+ <table>
4685
+ <thead>
4686
+ <tr>
4687
+ <th>Directive</th>
4688
+ <th>Code (System)</th>
4689
+ <th>Action Controlled</th>
4690
+ <th>Policy Rule</th>
4691
+ <th>Date</th>
4692
+ <th>Source</th>
4693
+ </tr>
4694
+ </thead>
4695
+ <tbody>`;
4696
+ for (const resourceItem of resources) {
4697
+ for (const rowData of resourceItem.section ?? []) {
4698
+ const sectionCodeableConcept = rowData.code;
4699
+ const data = {};
4700
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(
4701
+ sectionCodeableConcept
4702
+ );
4703
+ for (const columnData of rowData.section ?? []) {
4704
+ const columnTitle = columnData.title;
4705
+ if (columnTitle) {
4706
+ if (columnTitle === "DateTime") {
4707
+ data[columnTitle] = templateUtilities.renderTime(
4708
+ columnData.text?.div ?? "",
4709
+ timezone
4710
+ );
4711
+ } else {
4712
+ data[columnTitle] = templateUtilities.renderTextAsHtml(
4713
+ columnData.text?.div ?? ""
4714
+ );
4715
+ }
4716
+ }
4717
+ }
4718
+ if (data["Advanced Directive Name"]?.toLowerCase() === "unknown") {
4719
+ continue;
4720
+ }
4721
+ isSummaryCreated = true;
4722
+ html += `
4723
+ <tr>
4724
+ <td>${templateUtilities.capitalizeFirstLetter(data["Advanced Directive Name"] ?? "")}</td>
4725
+ <td>${data["codeSystem"] ?? ""}</td>
4726
+ <td>${data["Provision Action"] ?? ""}</td>
4727
+ <td>${data["Policy Rule"] ?? ""}</td>
4728
+ <td>${data["DateTime"] ?? ""}</td>
4729
+ <td>${data["Source"] ?? ""}</td>
4730
+ </tr>`;
4731
+ }
4732
+ }
4733
+ html += `
4734
+ </tbody>
4735
+ </table>
4736
+ </div>`;
4737
+ return isSummaryCreated ? html : void 0;
4575
4738
  }
4576
4739
  /**
4577
4740
  * Internal static implementation that actually generates the narrative
@@ -4579,7 +4742,6 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4579
4742
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4580
4743
  * @returns HTML string for rendering
4581
4744
  */
4582
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4583
4745
  static generateStaticNarrative(resources, timezone) {
4584
4746
  const templateUtilities = new TemplateUtilities(resources);
4585
4747
  let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
@@ -4587,35 +4749,57 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4587
4749
  html += `<table>
4588
4750
  <thead>
4589
4751
  <tr>
4590
- <th>Scope</th>
4591
- <th>Status</th>
4752
+ <th>Directive</th>
4753
+ <th>Code (System)</th>
4592
4754
  <th>Action Controlled</th>
4755
+ <th>Policy Rule</th>
4593
4756
  <th>Date</th>
4594
4757
  <th>Source</th>
4595
4758
  </tr>
4596
4759
  </thead>
4597
4760
  <tbody>`;
4598
- let isConsentAdded = false;
4761
+ const consentAdded = /* @__PURE__ */ new Set();
4599
4762
  for (const resourceItem of resources) {
4600
4763
  const consent = resourceItem;
4601
- const consentScope = templateUtilities.capitalizeFirstLetter(templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display")));
4602
- if (!consentScope || consentScope.toLowerCase() === "unknown") {
4764
+ let consentDirective = {};
4765
+ for (const category of consent.category || []) {
4766
+ for (const coding of category.coding || []) {
4767
+ if (coding.system === ADVANCED_DIRECTIVE_CATEGORY_SYSTEM && coding.code && coding.code in ADVANCED_DIRECTIVE_CATEGORY_CODES) {
4768
+ consentDirective = {
4769
+ name: ADVANCED_DIRECTIVE_CATEGORY_CODES[coding.code],
4770
+ system: coding.system,
4771
+ code: coding.code
4772
+ };
4773
+ break;
4774
+ } else if (coding.system === LOINC_SYSTEM && coding.code && coding.code in ADVANCED_DIRECTIVE_LOINC_CODES) {
4775
+ consentDirective = {
4776
+ name: ADVANCED_DIRECTIVE_LOINC_CODES[coding.code],
4777
+ system: coding.system,
4778
+ code: coding.code
4779
+ };
4780
+ break;
4781
+ }
4782
+ }
4783
+ }
4784
+ if (!consentDirective.name || !consentDirective.code || !consentDirective.system || consentDirective.name.toLowerCase() === "unknown" || consentAdded.has(consentDirective.name)) {
4603
4785
  continue;
4604
4786
  }
4605
- isConsentAdded = true;
4787
+ consentAdded.add(consentDirective.name);
4788
+ const consentCodeSystem = `${consentDirective.code} (${codingSystemDisplayNames_default[consentDirective.system] || consentDirective.system})`;
4606
4789
  html += `
4607
4790
  <tr>
4608
- <td>${consentScope}</td>
4609
- <td>${consent.status || ""}</td>
4791
+ <td>${consentDirective.name}</td>
4792
+ <td>${consentCodeSystem || ""}</td>
4610
4793
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
4611
- <td>${consent.dateTime || ""}</td>
4794
+ <td>${consent.policyRule ? templateUtilities.formatCodeableConceptValue(consent.policyRule) : ""}</td>
4795
+ <td>${templateUtilities.renderTime(consent.dateTime, timezone) || ""}</td>
4612
4796
  <td>${templateUtilities.getOwnerTag(consent)}</td>
4613
4797
  </tr>`;
4614
4798
  }
4615
4799
  html += `
4616
4800
  </tbody>
4617
4801
  </table>`;
4618
- return isConsentAdded ? html : void 0;
4802
+ return consentAdded.size > 0 ? html : void 0;
4619
4803
  }
4620
4804
  };
4621
4805
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.38",
3
+ "version": "1.0.39",
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",