@imranq2/fhirpatientsummary 1.0.37 → 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 +410 -103
  2. package/dist/index.js +410 -103
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -110,6 +110,85 @@ var ESSENTIAL_LAB_PANELS = {
110
110
  "80235-8": "Cardiac Markers Panel",
111
111
  "69738-3": "CBC with Auto Differential"
112
112
  };
113
+ var FUNCTIONAL_STATUS_SNOMED_CODES = {
114
+ "118233009": "Core Functional Status",
115
+ "364644000": "Core Functional Status",
116
+ "160245001": "Core Functional Status",
117
+ "21134002": "Disability & Impairment",
118
+ "2655002": "Disability & Impairment",
119
+ "2219003": "Disability & Impairment",
120
+ "962000": "Disability & Impairment",
121
+ "3089009": "Disability & Impairment",
122
+ "4197006": "Disability & Impairment",
123
+ "284545001": "Activities of Daily Living",
124
+ "719930002": "Activities of Daily Living",
125
+ "301438001": "Activities of Daily Living",
126
+ "282145008": "Mobility & Movement",
127
+ "165245003": "Mobility & Movement",
128
+ "713458007": "Mobility & Movement"
129
+ };
130
+ var FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES = {
131
+ "54522-8": "Functional Status Panel",
132
+ "54523-6": "ADL Assistance",
133
+ "45588-1": "Bed Mobility",
134
+ "45589-9": "Bed Mobility",
135
+ "45590-7": "Transfer",
136
+ "45591-5": "Transfer",
137
+ "45592-3": "Walk in room",
138
+ "45593-1": "Walk in room",
139
+ "45594-9": "Walk in corridor",
140
+ "45595-6": "Walk in corridor",
141
+ "45596-4": "Locomotion on unit",
142
+ "45597-2": "Locomotion on unit",
143
+ "45598-0": "Locomotion off unit",
144
+ "45599-8": "Locomotion off unit",
145
+ "45600-4": "Dressing",
146
+ "45601-2": "Dressing",
147
+ "45602-0": "Eating",
148
+ "45603-8": "Eating",
149
+ "45604-6": "Toilet use",
150
+ "45605-3": "Toilet use",
151
+ "45606-1": "Personal hygiene",
152
+ "45607-9": "Personal hygiene",
153
+ "46008-9": "Bathing",
154
+ "45608-7": "Bathing",
155
+ "45609-5": "Bathing",
156
+ "54524-4": "Balance during Transitions and Walking",
157
+ "54749-7": "Moving from Seated to Standing",
158
+ "54750-5": "Walking with Assistive Device",
159
+ "54751-3": "Turning Around While Walking",
160
+ "54753-9": "Surface-to-Surface Transfer",
161
+ "54525-1": "Functional Limitation in Range of Motion",
162
+ "54754-7": "Upper Extremity ROM",
163
+ "54755-4": "Lower Extremity ROM",
164
+ "54526-9": "Mobility Devices",
165
+ "54756-2": "Cane/Crutch",
166
+ "54757-0": "Walker",
167
+ "54758-8": "Wheelchair",
168
+ "54759-6": "Limb Prosthesis",
169
+ "54760-4": "No Mobility Devices",
170
+ "54527-7": "Functional Rehabilitation Potential",
171
+ "55123-4": "Resident Believes Capable of Increased Independence",
172
+ "45613-7": "Staff Believes Capable of Increased Independence"
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";
113
192
 
114
193
  // src/structures/ips_section_constants.ts
115
194
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -145,19 +224,16 @@ var IPSSectionResourceFilters = {
145
224
  ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
146
225
  // Only include pregnancy history Observations or relevant Conditions
147
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")),
148
- // Only include Observations with LOINC 47420-5, category 'functional-status', or category display containing 'functional', and completed ClinicalImpressions
149
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, "47420-5", "http://loinc.org") || resource.category?.some(
150
- (cat) => cat.coding?.some(
151
- (c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
152
- )
227
+ // Only include Condition with Functional Status LOINC and SNOMED codes, category code 'problem-list-item', and completed ClinicalImpressions
228
+ ["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
+ (cat) => cat.coding?.some((c) => c.code === "problem-list-item")
153
230
  )) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
154
231
  // Only include resolved medical history Conditions
155
232
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
156
233
  // Only include active care plans
157
234
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
158
235
  // Only include active advance directives (Consent resources)
159
- // TODO: disable this until we right logic to get these
160
- ["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"))
161
237
  };
162
238
  var IPSSectionSummaryCompositionFilter = {
163
239
  ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
@@ -173,8 +249,10 @@ var IPSSectionSummaryCompositionFilter = {
173
249
  var IPSSectionSummaryIPSCompositionFilter = {
174
250
  ["Patient" /* PATIENT */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "ips_patient_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
175
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)),
176
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)),
177
- ["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))
178
256
  };
179
257
  var IPSSectionResourceHelper = class {
180
258
  static getResourceFilterForSection(section) {
@@ -306,13 +384,26 @@ var TemplateUtilities = class {
306
384
  return code ? `${code} (${systemDisplay})` : "";
307
385
  }
308
386
  /**
309
- * 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.
310
389
  * @param deviceRef - Reference to a Device resource
311
- * @returns Formatted device description
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.
401
+ * @param deviceRef - Reference to a Device resource
402
+ * @returns Sanitized device name or empty string
312
403
  */
313
404
  renderDevice(deviceRef) {
314
- const device = deviceRef && this.resolveReference(deviceRef);
315
- 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) {
316
407
  return this.safeConcat(device.deviceName, "name");
317
408
  }
318
409
  return "";
@@ -2728,11 +2819,64 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2728
2819
  return _MedicalDevicesTemplate.generateStaticNarrative(resources, timezone);
2729
2820
  }
2730
2821
  /**
2731
- * Internal static implementation that actually generates the narrative
2732
- * @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
2733
2824
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2734
2825
  * @returns HTML string for rendering
2735
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
+ */
2736
2880
  static generateStaticNarrative(resources, timezone) {
2737
2881
  const templateUtilities = new TemplateUtilities(resources);
2738
2882
  let html = `<p>This list includes all DeviceUseStatement resources, sorted by recorded date (most recent first).</p>
@@ -2740,6 +2884,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2740
2884
  <thead>
2741
2885
  <tr>
2742
2886
  <th>Device</th>
2887
+ <th>Code (System)</th>
2743
2888
  <th>Status</th>
2744
2889
  <th>Comments</th>
2745
2890
  <th>Date Recorded</th>
@@ -2752,26 +2897,32 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2752
2897
  const dateB = b.recordedOn;
2753
2898
  return typeof dateA === "string" && typeof dateB === "string" ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2754
2899
  });
2755
- let isDeviceAdded = false;
2900
+ const devicesAdded = /* @__PURE__ */ new Set();
2756
2901
  for (const dus of deviceStatements) {
2757
- const deviceName = templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device));
2758
- 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) {
2759
2909
  continue;
2760
2910
  }
2761
- isDeviceAdded = true;
2911
+ devicesAdded.add(deviceName);
2762
2912
  html += `
2763
2913
  <tr>
2764
2914
  <td>${templateUtilities.capitalizeFirstLetter(deviceName)}</td>
2915
+ <td>${codeSystem}</td>
2765
2916
  <td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
2766
2917
  <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
2767
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderRecorded(dus.recordedOn, timezone))}</td>
2918
+ <td>${templateUtilities.renderTime(dus.recordedOn, timezone)}</td>
2768
2919
  <td>${templateUtilities.getOwnerTag(dus)}</td>
2769
2920
  </tr>`;
2770
2921
  }
2771
2922
  html += `
2772
2923
  </tbody>
2773
2924
  </table>`;
2774
- return isDeviceAdded ? html : void 0;
2925
+ return devicesAdded.size > 0 ? html : void 0;
2775
2926
  }
2776
2927
  };
2777
2928
 
@@ -4075,7 +4226,7 @@ var PlanOfCareTemplate = class {
4075
4226
  var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4076
4227
  /**
4077
4228
  * Generate HTML narrative for Functional Status
4078
- * @param resources - FHIR resources array containing Observation resources
4229
+ * @param resources - FHIR resources array containing Condition and ClinicalImpression resources
4079
4230
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4080
4231
  * @returns HTML string for rendering
4081
4232
  */
@@ -4115,8 +4266,8 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4115
4266
  <thead>
4116
4267
  <tr>
4117
4268
  <th>Name</th>
4118
- <th>Date</th>
4119
4269
  <th>Code (System)</th>
4270
+ <th>Date</th>
4120
4271
  <th>Description</th>
4121
4272
  <th>Summary</th>
4122
4273
  <th>Source</th>
@@ -4182,8 +4333,8 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4182
4333
  clinicalImpressionsHtml += `
4183
4334
  <tr>
4184
4335
  <td>${templateUtilities.capitalizeFirstLetter(name)}</td>
4185
- <td>${date ?? ""}</td>
4186
4336
  <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
4337
+ <td>${date ?? ""}</td>
4187
4338
  <td>${data["Description"] ?? ""}</td>
4188
4339
  <td>${data["Summary"] ?? ""}</td>
4189
4340
  <td>${data["Source"] ?? ""}</td>
@@ -4212,92 +4363,156 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4212
4363
  }
4213
4364
  /**
4214
4365
  * Internal static implementation that actually generates the narrative
4215
- * @param resources - FHIR resources array containing Observation resources
4366
+ * @param resources - FHIR resources array containing Condition and ClinicalImpression resources
4216
4367
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4217
4368
  * @returns HTML string for rendering
4218
4369
  */
4219
4370
  static generateStaticNarrative(resources, timezone) {
4220
4371
  const templateUtilities = new TemplateUtilities(resources);
4221
- let html = `<p>This section summarizes key observations and assessments related to the person's functional status and ability to perform daily activities.</p>`;
4222
- let functionalObservations = resources.filter((r) => r.resourceType === "Observation").filter((r) => {
4223
- const hasFunctionalLoinc = r.code?.coding?.some(
4224
- (c) => c.system?.toLowerCase().includes("loinc") && c.code === "47420-5"
4225
- );
4226
- const hasFunctionalCategory = r.category?.some(
4227
- (cat) => cat.coding?.some(
4228
- (c) => c.code === "functional-status" || c.display?.toLowerCase().includes("functional")
4229
- )
4230
- );
4231
- return hasFunctionalLoinc || hasFunctionalCategory;
4232
- });
4233
- functionalObservations = functionalObservations.sort((a, b) => {
4234
- const getObsDate = (obs) => obs.effectiveDateTime ? new Date(obs.effectiveDateTime).getTime() : obs.issued ? new Date(obs.issued).getTime() : 0;
4235
- return getObsDate(b) - getObsDate(a);
4236
- });
4237
- let clinicalImpressions = resources.filter((r) => r.resourceType === "ClinicalImpression").filter((r) => r.status === "completed");
4238
- clinicalImpressions = clinicalImpressions.sort((a, b) => {
4239
- const getImpressionDate = (ci) => ci.effectiveDateTime ? new Date(ci.effectiveDateTime).getTime() : ci.effectivePeriod?.end ? new Date(ci.effectivePeriod.end).getTime() : ci.date ? new Date(ci.date).getTime() : 0;
4240
- return getImpressionDate(b) - getImpressionDate(a);
4372
+ let html = `<div>
4373
+ <p>This section summarizes key conditions and assessments related to the person's functional status and ability to perform daily activities.</p>`;
4374
+ let conditionHtml = `
4375
+ <div>
4376
+ <h3>Conditions</h3>
4377
+ <table>
4378
+ <thead>
4379
+ <tr>
4380
+ <th>Problem</th>
4381
+ <th>Code (System)</th>
4382
+ <th>Onset Date</th>
4383
+ <th>Recorded Date</th>
4384
+ <th>Source</th>
4385
+ </tr>
4386
+ </thead>
4387
+ <tbody>`;
4388
+ let clinicalImpressionsHtml = `
4389
+ <div>
4390
+ <h3>Clinical Impressions</h3>
4391
+ <table>
4392
+ <thead>
4393
+ <tr>
4394
+ <th>Name</th>
4395
+ <th>Code (System)</th>
4396
+ <th>Date</th>
4397
+ <th>Description</th>
4398
+ <th>Summary</th>
4399
+ <th>Source</th>
4400
+ </tr>
4401
+ </thead>
4402
+ <tbody>`;
4403
+ const conditions = resources.filter((entry) => entry.resourceType === "Condition").map((entry) => entry);
4404
+ conditions.sort((a, b) => {
4405
+ const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
4406
+ const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
4407
+ return dateB - dateA;
4241
4408
  });
4242
- if (functionalObservations.length > 0) {
4243
- html += `<table><thead><tr><th>Observation</th><th>Value</th><th>Date</th><th>Interpretation</th><th>Comments</th><th>Source</th></tr></thead><tbody>`;
4244
- for (const obs of functionalObservations) {
4245
- const observation = obs;
4246
- const obsName = templateUtilities.codeableConceptDisplay(observation.code);
4247
- if (obsName?.toLowerCase() === "unknown") {
4248
- continue;
4409
+ const addedConditions = /* @__PURE__ */ new Set();
4410
+ for (const cond of conditions) {
4411
+ const functionalStatusName = this.getFunctionalStatusNameFromCode(cond.code);
4412
+ const problem = templateUtilities.codeableConceptDisplay(cond.code) || functionalStatusName;
4413
+ const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
4414
+ if (!codeAndSystem || !problem || !functionalStatusName || addedConditions.has(functionalStatusName)) {
4415
+ continue;
4416
+ }
4417
+ if (problem?.toLowerCase() === "unknown") {
4418
+ continue;
4419
+ }
4420
+ addedConditions.add(functionalStatusName);
4421
+ let date = cond.onsetDateTime ? templateUtilities.renderTime(cond.onsetDateTime, timezone) : "";
4422
+ if (!date && cond.onsetPeriod?.start) {
4423
+ date = templateUtilities.renderTime(
4424
+ cond.onsetPeriod?.start,
4425
+ timezone
4426
+ );
4427
+ if (cond.onsetPeriod?.end) {
4428
+ date += " - " + templateUtilities.renderTime(cond.onsetPeriod?.end, timezone);
4249
4429
  }
4250
- const value = templateUtilities.extractObservationValue(observation);
4251
- const date = observation.effectiveDateTime ? templateUtilities.renderDate(observation.effectiveDateTime) : observation.issued ? templateUtilities.renderDate(observation.issued) : "";
4252
- const interpretation = observation.interpretation ? templateUtilities.codeableConceptDisplay(observation.interpretation[0]) : "";
4253
- const comments = observation.comment || observation.note?.map((n) => n.text).join("; ") || "";
4254
- html += `<tr>
4255
- <td>${templateUtilities.capitalizeFirstLetter(obsName)}</td>
4256
- <td>${value ?? ""}</td>
4430
+ }
4431
+ conditionHtml += `<tr>
4432
+ <td>${templateUtilities.capitalizeFirstLetter(problem)}</td>
4433
+ <td>${codeAndSystem}</td>
4257
4434
  <td>${date}</td>
4258
- <td>${interpretation}</td>
4259
- <td>${comments}</td>
4260
- <td>${templateUtilities.getOwnerTag(observation)}</td>
4435
+ <td>${templateUtilities.renderTime(cond.recordedDate, timezone)}</td>
4436
+ <td>${templateUtilities.getOwnerTag(cond)}</td>
4261
4437
  </tr>`;
4438
+ }
4439
+ const clinicalImpressions = resources.filter((entry) => entry.resourceType === "ClinicalImpression").map((entry) => entry);
4440
+ clinicalImpressions.sort((a, b) => {
4441
+ const dateA = this.getClinicalImpressionEffectiveDate(a);
4442
+ const dateB = this.getClinicalImpressionEffectiveDate(b);
4443
+ return dateB && dateA ? dateB.getTime() - dateA.getTime() : 0;
4444
+ });
4445
+ const addedClinicalImpressions = /* @__PURE__ */ new Set();
4446
+ for (const impression of clinicalImpressions) {
4447
+ const name = templateUtilities.codeableConceptDisplay(impression.code);
4448
+ const codeAndSystem = templateUtilities.codeableConceptCoding(impression.code);
4449
+ if (!codeAndSystem || addedClinicalImpressions.has(name)) {
4450
+ continue;
4262
4451
  }
4263
- html += `</tbody></table>`;
4264
- }
4265
- if (clinicalImpressions.length > 0) {
4266
- html += `<table><thead><tr><th>Date</th><th>Status</th><th>Description</th><th>Summary</th><th>Findings</th><th>Source</th></tr></thead><tbody>`;
4267
- for (const impression of clinicalImpressions) {
4268
- let formattedDate = "";
4269
- if (impression.effectiveDateTime) {
4270
- formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
4271
- } else if (impression.effectivePeriod) {
4272
- formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
4273
- } else if (impression.date) {
4274
- formattedDate = templateUtilities.renderDate(impression.date);
4275
- }
4276
- let findingsHtml = "";
4277
- if (impression.finding && impression.finding.length > 0) {
4278
- findingsHtml = "<ul>";
4279
- for (const finding of impression.finding) {
4280
- const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
4281
- const cause = finding.basis || "";
4282
- findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
4283
- }
4284
- findingsHtml += "</ul>";
4452
+ if (!name || name?.toLowerCase() === "unknown") {
4453
+ continue;
4454
+ }
4455
+ addedClinicalImpressions.add(name);
4456
+ let date = impression.effectiveDateTime ? templateUtilities.renderTime(impression.effectiveDateTime, timezone) : "";
4457
+ if (!date && impression.effectivePeriod?.start) {
4458
+ date = templateUtilities.renderTime(
4459
+ impression.effectivePeriod?.start,
4460
+ timezone
4461
+ );
4462
+ if (impression.effectivePeriod?.end) {
4463
+ date += " - " + templateUtilities.renderTime(impression.effectivePeriod?.end, timezone);
4285
4464
  }
4286
- html += `<tr>
4287
- <td>${formattedDate}</td>
4288
- <td>${impression.status || ""}</td>
4465
+ }
4466
+ clinicalImpressionsHtml += `<tr>
4467
+ <td>${templateUtilities.capitalizeFirstLetter(name)}</td>
4468
+ <td>${codeAndSystem}</td>
4469
+ <td>${date}</td>
4289
4470
  <td>${impression.description || ""}</td>
4290
4471
  <td>${impression.summary || ""}</td>
4291
- <td>${findingsHtml}</td>
4292
4472
  <td>${templateUtilities.getOwnerTag(impression)}</td>
4293
4473
  </tr>`;
4474
+ }
4475
+ if (addedConditions.size > 0) {
4476
+ html += conditionHtml;
4477
+ html += `
4478
+ </tbody>
4479
+ </table>
4480
+ </div>`;
4481
+ }
4482
+ if (addedClinicalImpressions.size > 0) {
4483
+ html += clinicalImpressionsHtml;
4484
+ html += `
4485
+ </tbody>
4486
+ </table>
4487
+ </div>`;
4488
+ }
4489
+ html += `
4490
+ </div>`;
4491
+ return addedConditions.size > 0 || addedClinicalImpressions.size > 0 ? html : void 0;
4492
+ }
4493
+ static getFunctionalStatusNameFromCode(cc) {
4494
+ if (!cc) return "";
4495
+ for (const coding of cc.coding || []) {
4496
+ let functionalStatusName = FUNCTIONAL_STATUS_SNOMED_CODES[coding.code];
4497
+ if (functionalStatusName) {
4498
+ return functionalStatusName;
4499
+ }
4500
+ functionalStatusName = FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES[coding.code];
4501
+ if (functionalStatusName) {
4502
+ return functionalStatusName;
4294
4503
  }
4295
- html += `</tbody></table>`;
4296
4504
  }
4297
- if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
4298
- html += `<p>No functional status information available.</p>`;
4505
+ }
4506
+ static getClinicalImpressionEffectiveDate(impression) {
4507
+ if (impression.effectiveDateTime) {
4508
+ return new Date(impression.effectiveDateTime);
4509
+ } else if (impression.effectivePeriod) {
4510
+ if (impression.effectivePeriod.start) {
4511
+ return new Date(impression.effectivePeriod.start);
4512
+ } else if (impression.effectivePeriod.end) {
4513
+ return new Date(impression.effectivePeriod.end);
4514
+ }
4299
4515
  }
4300
- return html;
4301
4516
  }
4302
4517
  };
4303
4518
 
@@ -4448,7 +4663,78 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4448
4663
  const dateB = new Date(b.dateTime || 0);
4449
4664
  return dateB.getTime() - dateA.getTime();
4450
4665
  });
4451
- 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;
4452
4738
  }
4453
4739
  /**
4454
4740
  * Internal static implementation that actually generates the narrative
@@ -4456,7 +4742,6 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4456
4742
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4457
4743
  * @returns HTML string for rendering
4458
4744
  */
4459
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
4460
4745
  static generateStaticNarrative(resources, timezone) {
4461
4746
  const templateUtilities = new TemplateUtilities(resources);
4462
4747
  let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
@@ -4464,35 +4749,57 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
4464
4749
  html += `<table>
4465
4750
  <thead>
4466
4751
  <tr>
4467
- <th>Scope</th>
4468
- <th>Status</th>
4752
+ <th>Directive</th>
4753
+ <th>Code (System)</th>
4469
4754
  <th>Action Controlled</th>
4755
+ <th>Policy Rule</th>
4470
4756
  <th>Date</th>
4471
4757
  <th>Source</th>
4472
4758
  </tr>
4473
4759
  </thead>
4474
4760
  <tbody>`;
4475
- let isConsentAdded = false;
4761
+ const consentAdded = /* @__PURE__ */ new Set();
4476
4762
  for (const resourceItem of resources) {
4477
4763
  const consent = resourceItem;
4478
- const consentScope = templateUtilities.capitalizeFirstLetter(templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display")));
4479
- 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)) {
4480
4785
  continue;
4481
4786
  }
4482
- isConsentAdded = true;
4787
+ consentAdded.add(consentDirective.name);
4788
+ const consentCodeSystem = `${consentDirective.code} (${codingSystemDisplayNames_default[consentDirective.system] || consentDirective.system})`;
4483
4789
  html += `
4484
4790
  <tr>
4485
- <td>${consentScope}</td>
4486
- <td>${consent.status || ""}</td>
4791
+ <td>${consentDirective.name}</td>
4792
+ <td>${consentCodeSystem || ""}</td>
4487
4793
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
4488
- <td>${consent.dateTime || ""}</td>
4794
+ <td>${consent.policyRule ? templateUtilities.formatCodeableConceptValue(consent.policyRule) : ""}</td>
4795
+ <td>${templateUtilities.renderTime(consent.dateTime, timezone) || ""}</td>
4489
4796
  <td>${templateUtilities.getOwnerTag(consent)}</td>
4490
4797
  </tr>`;
4491
4798
  }
4492
4799
  html += `
4493
4800
  </tbody>
4494
4801
  </table>`;
4495
- return isConsentAdded ? html : void 0;
4802
+ return consentAdded.size > 0 ? html : void 0;
4496
4803
  }
4497
4804
  };
4498
4805