@imranq2/fhirpatientsummary 1.0.37 → 1.0.38

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 +199 -76
  2. package/dist/index.js +199 -76
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -138,6 +138,67 @@ var ESSENTIAL_LAB_PANELS = {
138
138
  "80235-8": "Cardiac Markers Panel",
139
139
  "69738-3": "CBC with Auto Differential"
140
140
  };
141
+ var FUNCTIONAL_STATUS_SNOMED_CODES = {
142
+ "118233009": "Core Functional Status",
143
+ "364644000": "Core Functional Status",
144
+ "160245001": "Core Functional Status",
145
+ "21134002": "Disability & Impairment",
146
+ "2655002": "Disability & Impairment",
147
+ "2219003": "Disability & Impairment",
148
+ "962000": "Disability & Impairment",
149
+ "3089009": "Disability & Impairment",
150
+ "4197006": "Disability & Impairment",
151
+ "284545001": "Activities of Daily Living",
152
+ "719930002": "Activities of Daily Living",
153
+ "301438001": "Activities of Daily Living",
154
+ "282145008": "Mobility & Movement",
155
+ "165245003": "Mobility & Movement",
156
+ "713458007": "Mobility & Movement"
157
+ };
158
+ var FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES = {
159
+ "54522-8": "Functional Status Panel",
160
+ "54523-6": "ADL Assistance",
161
+ "45588-1": "Bed Mobility",
162
+ "45589-9": "Bed Mobility",
163
+ "45590-7": "Transfer",
164
+ "45591-5": "Transfer",
165
+ "45592-3": "Walk in room",
166
+ "45593-1": "Walk in room",
167
+ "45594-9": "Walk in corridor",
168
+ "45595-6": "Walk in corridor",
169
+ "45596-4": "Locomotion on unit",
170
+ "45597-2": "Locomotion on unit",
171
+ "45598-0": "Locomotion off unit",
172
+ "45599-8": "Locomotion off unit",
173
+ "45600-4": "Dressing",
174
+ "45601-2": "Dressing",
175
+ "45602-0": "Eating",
176
+ "45603-8": "Eating",
177
+ "45604-6": "Toilet use",
178
+ "45605-3": "Toilet use",
179
+ "45606-1": "Personal hygiene",
180
+ "45607-9": "Personal hygiene",
181
+ "46008-9": "Bathing",
182
+ "45608-7": "Bathing",
183
+ "45609-5": "Bathing",
184
+ "54524-4": "Balance during Transitions and Walking",
185
+ "54749-7": "Moving from Seated to Standing",
186
+ "54750-5": "Walking with Assistive Device",
187
+ "54751-3": "Turning Around While Walking",
188
+ "54753-9": "Surface-to-Surface Transfer",
189
+ "54525-1": "Functional Limitation in Range of Motion",
190
+ "54754-7": "Upper Extremity ROM",
191
+ "54755-4": "Lower Extremity ROM",
192
+ "54526-9": "Mobility Devices",
193
+ "54756-2": "Cane/Crutch",
194
+ "54757-0": "Walker",
195
+ "54758-8": "Wheelchair",
196
+ "54759-6": "Limb Prosthesis",
197
+ "54760-4": "No Mobility Devices",
198
+ "54527-7": "Functional Rehabilitation Potential",
199
+ "55123-4": "Resident Believes Capable of Increased Independence",
200
+ "45613-7": "Staff Believes Capable of Increased Independence"
201
+ };
141
202
 
142
203
  // src/structures/ips_section_constants.ts
143
204
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -173,11 +234,9 @@ var IPSSectionResourceFilters = {
173
234
  ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
174
235
  // Only include pregnancy history Observations or relevant Conditions
175
236
  ["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")),
176
- // Only include Observations with LOINC 47420-5, category 'functional-status', or category display containing 'functional', and completed ClinicalImpressions
177
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, "47420-5", "http://loinc.org") || resource.category?.some(
178
- (cat) => cat.coding?.some(
179
- (c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
180
- )
237
+ // Only include Condition with Functional Status LOINC and SNOMED codes, category code 'problem-list-item', and completed ClinicalImpressions
238
+ ["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(
239
+ (cat) => cat.coding?.some((c) => c.code === "problem-list-item")
181
240
  )) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
182
241
  // Only include resolved medical history Conditions
183
242
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
@@ -4103,7 +4162,7 @@ var PlanOfCareTemplate = class {
4103
4162
  var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4104
4163
  /**
4105
4164
  * Generate HTML narrative for Functional Status
4106
- * @param resources - FHIR resources array containing Observation resources
4165
+ * @param resources - FHIR resources array containing Condition and ClinicalImpression resources
4107
4166
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4108
4167
  * @returns HTML string for rendering
4109
4168
  */
@@ -4143,8 +4202,8 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4143
4202
  <thead>
4144
4203
  <tr>
4145
4204
  <th>Name</th>
4146
- <th>Date</th>
4147
4205
  <th>Code (System)</th>
4206
+ <th>Date</th>
4148
4207
  <th>Description</th>
4149
4208
  <th>Summary</th>
4150
4209
  <th>Source</th>
@@ -4210,8 +4269,8 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4210
4269
  clinicalImpressionsHtml += `
4211
4270
  <tr>
4212
4271
  <td>${templateUtilities.capitalizeFirstLetter(name)}</td>
4213
- <td>${date ?? ""}</td>
4214
4272
  <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
4273
+ <td>${date ?? ""}</td>
4215
4274
  <td>${data["Description"] ?? ""}</td>
4216
4275
  <td>${data["Summary"] ?? ""}</td>
4217
4276
  <td>${data["Source"] ?? ""}</td>
@@ -4240,92 +4299,156 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4240
4299
  }
4241
4300
  /**
4242
4301
  * Internal static implementation that actually generates the narrative
4243
- * @param resources - FHIR resources array containing Observation resources
4302
+ * @param resources - FHIR resources array containing Condition and ClinicalImpression resources
4244
4303
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4245
4304
  * @returns HTML string for rendering
4246
4305
  */
4247
4306
  static generateStaticNarrative(resources, timezone) {
4248
4307
  const templateUtilities = new TemplateUtilities(resources);
4249
- let html = `<p>This section summarizes key observations and assessments related to the person's functional status and ability to perform daily activities.</p>`;
4250
- let functionalObservations = resources.filter((r) => r.resourceType === "Observation").filter((r) => {
4251
- const hasFunctionalLoinc = r.code?.coding?.some(
4252
- (c) => c.system?.toLowerCase().includes("loinc") && c.code === "47420-5"
4253
- );
4254
- const hasFunctionalCategory = r.category?.some(
4255
- (cat) => cat.coding?.some(
4256
- (c) => c.code === "functional-status" || c.display?.toLowerCase().includes("functional")
4257
- )
4258
- );
4259
- return hasFunctionalLoinc || hasFunctionalCategory;
4260
- });
4261
- functionalObservations = functionalObservations.sort((a, b) => {
4262
- const getObsDate = (obs) => obs.effectiveDateTime ? new Date(obs.effectiveDateTime).getTime() : obs.issued ? new Date(obs.issued).getTime() : 0;
4263
- return getObsDate(b) - getObsDate(a);
4264
- });
4265
- let clinicalImpressions = resources.filter((r) => r.resourceType === "ClinicalImpression").filter((r) => r.status === "completed");
4266
- clinicalImpressions = clinicalImpressions.sort((a, b) => {
4267
- 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;
4268
- return getImpressionDate(b) - getImpressionDate(a);
4308
+ let html = `<div>
4309
+ <p>This section summarizes key conditions and assessments related to the person's functional status and ability to perform daily activities.</p>`;
4310
+ let conditionHtml = `
4311
+ <div>
4312
+ <h3>Conditions</h3>
4313
+ <table>
4314
+ <thead>
4315
+ <tr>
4316
+ <th>Problem</th>
4317
+ <th>Code (System)</th>
4318
+ <th>Onset Date</th>
4319
+ <th>Recorded Date</th>
4320
+ <th>Source</th>
4321
+ </tr>
4322
+ </thead>
4323
+ <tbody>`;
4324
+ let clinicalImpressionsHtml = `
4325
+ <div>
4326
+ <h3>Clinical Impressions</h3>
4327
+ <table>
4328
+ <thead>
4329
+ <tr>
4330
+ <th>Name</th>
4331
+ <th>Code (System)</th>
4332
+ <th>Date</th>
4333
+ <th>Description</th>
4334
+ <th>Summary</th>
4335
+ <th>Source</th>
4336
+ </tr>
4337
+ </thead>
4338
+ <tbody>`;
4339
+ const conditions = resources.filter((entry) => entry.resourceType === "Condition").map((entry) => entry);
4340
+ conditions.sort((a, b) => {
4341
+ const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
4342
+ const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
4343
+ return dateB - dateA;
4269
4344
  });
4270
- if (functionalObservations.length > 0) {
4271
- 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>`;
4272
- for (const obs of functionalObservations) {
4273
- const observation = obs;
4274
- const obsName = templateUtilities.codeableConceptDisplay(observation.code);
4275
- if (obsName?.toLowerCase() === "unknown") {
4276
- continue;
4345
+ const addedConditions = /* @__PURE__ */ new Set();
4346
+ for (const cond of conditions) {
4347
+ const functionalStatusName = this.getFunctionalStatusNameFromCode(cond.code);
4348
+ const problem = templateUtilities.codeableConceptDisplay(cond.code) || functionalStatusName;
4349
+ const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
4350
+ if (!codeAndSystem || !problem || !functionalStatusName || addedConditions.has(functionalStatusName)) {
4351
+ continue;
4352
+ }
4353
+ if (problem?.toLowerCase() === "unknown") {
4354
+ continue;
4355
+ }
4356
+ addedConditions.add(functionalStatusName);
4357
+ let date = cond.onsetDateTime ? templateUtilities.renderTime(cond.onsetDateTime, timezone) : "";
4358
+ if (!date && cond.onsetPeriod?.start) {
4359
+ date = templateUtilities.renderTime(
4360
+ cond.onsetPeriod?.start,
4361
+ timezone
4362
+ );
4363
+ if (cond.onsetPeriod?.end) {
4364
+ date += " - " + templateUtilities.renderTime(cond.onsetPeriod?.end, timezone);
4277
4365
  }
4278
- const value = templateUtilities.extractObservationValue(observation);
4279
- const date = observation.effectiveDateTime ? templateUtilities.renderDate(observation.effectiveDateTime) : observation.issued ? templateUtilities.renderDate(observation.issued) : "";
4280
- const interpretation = observation.interpretation ? templateUtilities.codeableConceptDisplay(observation.interpretation[0]) : "";
4281
- const comments = observation.comment || observation.note?.map((n) => n.text).join("; ") || "";
4282
- html += `<tr>
4283
- <td>${templateUtilities.capitalizeFirstLetter(obsName)}</td>
4284
- <td>${value ?? ""}</td>
4366
+ }
4367
+ conditionHtml += `<tr>
4368
+ <td>${templateUtilities.capitalizeFirstLetter(problem)}</td>
4369
+ <td>${codeAndSystem}</td>
4285
4370
  <td>${date}</td>
4286
- <td>${interpretation}</td>
4287
- <td>${comments}</td>
4288
- <td>${templateUtilities.getOwnerTag(observation)}</td>
4371
+ <td>${templateUtilities.renderTime(cond.recordedDate, timezone)}</td>
4372
+ <td>${templateUtilities.getOwnerTag(cond)}</td>
4289
4373
  </tr>`;
4374
+ }
4375
+ const clinicalImpressions = resources.filter((entry) => entry.resourceType === "ClinicalImpression").map((entry) => entry);
4376
+ clinicalImpressions.sort((a, b) => {
4377
+ const dateA = this.getClinicalImpressionEffectiveDate(a);
4378
+ const dateB = this.getClinicalImpressionEffectiveDate(b);
4379
+ return dateB && dateA ? dateB.getTime() - dateA.getTime() : 0;
4380
+ });
4381
+ const addedClinicalImpressions = /* @__PURE__ */ new Set();
4382
+ for (const impression of clinicalImpressions) {
4383
+ const name = templateUtilities.codeableConceptDisplay(impression.code);
4384
+ const codeAndSystem = templateUtilities.codeableConceptCoding(impression.code);
4385
+ if (!codeAndSystem || addedClinicalImpressions.has(name)) {
4386
+ continue;
4290
4387
  }
4291
- html += `</tbody></table>`;
4292
- }
4293
- if (clinicalImpressions.length > 0) {
4294
- 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>`;
4295
- for (const impression of clinicalImpressions) {
4296
- let formattedDate = "";
4297
- if (impression.effectiveDateTime) {
4298
- formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
4299
- } else if (impression.effectivePeriod) {
4300
- formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
4301
- } else if (impression.date) {
4302
- formattedDate = templateUtilities.renderDate(impression.date);
4303
- }
4304
- let findingsHtml = "";
4305
- if (impression.finding && impression.finding.length > 0) {
4306
- findingsHtml = "<ul>";
4307
- for (const finding of impression.finding) {
4308
- const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
4309
- const cause = finding.basis || "";
4310
- findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
4311
- }
4312
- findingsHtml += "</ul>";
4388
+ if (!name || name?.toLowerCase() === "unknown") {
4389
+ continue;
4390
+ }
4391
+ addedClinicalImpressions.add(name);
4392
+ let date = impression.effectiveDateTime ? templateUtilities.renderTime(impression.effectiveDateTime, timezone) : "";
4393
+ if (!date && impression.effectivePeriod?.start) {
4394
+ date = templateUtilities.renderTime(
4395
+ impression.effectivePeriod?.start,
4396
+ timezone
4397
+ );
4398
+ if (impression.effectivePeriod?.end) {
4399
+ date += " - " + templateUtilities.renderTime(impression.effectivePeriod?.end, timezone);
4313
4400
  }
4314
- html += `<tr>
4315
- <td>${formattedDate}</td>
4316
- <td>${impression.status || ""}</td>
4401
+ }
4402
+ clinicalImpressionsHtml += `<tr>
4403
+ <td>${templateUtilities.capitalizeFirstLetter(name)}</td>
4404
+ <td>${codeAndSystem}</td>
4405
+ <td>${date}</td>
4317
4406
  <td>${impression.description || ""}</td>
4318
4407
  <td>${impression.summary || ""}</td>
4319
- <td>${findingsHtml}</td>
4320
4408
  <td>${templateUtilities.getOwnerTag(impression)}</td>
4321
4409
  </tr>`;
4410
+ }
4411
+ if (addedConditions.size > 0) {
4412
+ html += conditionHtml;
4413
+ html += `
4414
+ </tbody>
4415
+ </table>
4416
+ </div>`;
4417
+ }
4418
+ if (addedClinicalImpressions.size > 0) {
4419
+ html += clinicalImpressionsHtml;
4420
+ html += `
4421
+ </tbody>
4422
+ </table>
4423
+ </div>`;
4424
+ }
4425
+ html += `
4426
+ </div>`;
4427
+ return addedConditions.size > 0 || addedClinicalImpressions.size > 0 ? html : void 0;
4428
+ }
4429
+ static getFunctionalStatusNameFromCode(cc) {
4430
+ if (!cc) return "";
4431
+ for (const coding of cc.coding || []) {
4432
+ let functionalStatusName = FUNCTIONAL_STATUS_SNOMED_CODES[coding.code];
4433
+ if (functionalStatusName) {
4434
+ return functionalStatusName;
4435
+ }
4436
+ functionalStatusName = FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES[coding.code];
4437
+ if (functionalStatusName) {
4438
+ return functionalStatusName;
4322
4439
  }
4323
- html += `</tbody></table>`;
4324
4440
  }
4325
- if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
4326
- html += `<p>No functional status information available.</p>`;
4441
+ }
4442
+ static getClinicalImpressionEffectiveDate(impression) {
4443
+ if (impression.effectiveDateTime) {
4444
+ return new Date(impression.effectiveDateTime);
4445
+ } else if (impression.effectivePeriod) {
4446
+ if (impression.effectivePeriod.start) {
4447
+ return new Date(impression.effectivePeriod.start);
4448
+ } else if (impression.effectivePeriod.end) {
4449
+ return new Date(impression.effectivePeriod.end);
4450
+ }
4327
4451
  }
4328
- return html;
4329
4452
  }
4330
4453
  };
4331
4454
 
package/dist/index.js CHANGED
@@ -110,6 +110,67 @@ 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
+ };
113
174
 
114
175
  // src/structures/ips_section_constants.ts
115
176
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -145,11 +206,9 @@ var IPSSectionResourceFilters = {
145
206
  ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
146
207
  // Only include pregnancy history Observations or relevant Conditions
147
208
  ["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
- )
209
+ // Only include Condition with Functional Status LOINC and SNOMED codes, category code 'problem-list-item', and completed ClinicalImpressions
210
+ ["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(
211
+ (cat) => cat.coding?.some((c) => c.code === "problem-list-item")
153
212
  )) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
154
213
  // Only include resolved medical history Conditions
155
214
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
@@ -4075,7 +4134,7 @@ var PlanOfCareTemplate = class {
4075
4134
  var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4076
4135
  /**
4077
4136
  * Generate HTML narrative for Functional Status
4078
- * @param resources - FHIR resources array containing Observation resources
4137
+ * @param resources - FHIR resources array containing Condition and ClinicalImpression resources
4079
4138
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4080
4139
  * @returns HTML string for rendering
4081
4140
  */
@@ -4115,8 +4174,8 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4115
4174
  <thead>
4116
4175
  <tr>
4117
4176
  <th>Name</th>
4118
- <th>Date</th>
4119
4177
  <th>Code (System)</th>
4178
+ <th>Date</th>
4120
4179
  <th>Description</th>
4121
4180
  <th>Summary</th>
4122
4181
  <th>Source</th>
@@ -4182,8 +4241,8 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4182
4241
  clinicalImpressionsHtml += `
4183
4242
  <tr>
4184
4243
  <td>${templateUtilities.capitalizeFirstLetter(name)}</td>
4185
- <td>${date ?? ""}</td>
4186
4244
  <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
4245
+ <td>${date ?? ""}</td>
4187
4246
  <td>${data["Description"] ?? ""}</td>
4188
4247
  <td>${data["Summary"] ?? ""}</td>
4189
4248
  <td>${data["Source"] ?? ""}</td>
@@ -4212,92 +4271,156 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
4212
4271
  }
4213
4272
  /**
4214
4273
  * Internal static implementation that actually generates the narrative
4215
- * @param resources - FHIR resources array containing Observation resources
4274
+ * @param resources - FHIR resources array containing Condition and ClinicalImpression resources
4216
4275
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
4217
4276
  * @returns HTML string for rendering
4218
4277
  */
4219
4278
  static generateStaticNarrative(resources, timezone) {
4220
4279
  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);
4280
+ let html = `<div>
4281
+ <p>This section summarizes key conditions and assessments related to the person's functional status and ability to perform daily activities.</p>`;
4282
+ let conditionHtml = `
4283
+ <div>
4284
+ <h3>Conditions</h3>
4285
+ <table>
4286
+ <thead>
4287
+ <tr>
4288
+ <th>Problem</th>
4289
+ <th>Code (System)</th>
4290
+ <th>Onset Date</th>
4291
+ <th>Recorded Date</th>
4292
+ <th>Source</th>
4293
+ </tr>
4294
+ </thead>
4295
+ <tbody>`;
4296
+ let clinicalImpressionsHtml = `
4297
+ <div>
4298
+ <h3>Clinical Impressions</h3>
4299
+ <table>
4300
+ <thead>
4301
+ <tr>
4302
+ <th>Name</th>
4303
+ <th>Code (System)</th>
4304
+ <th>Date</th>
4305
+ <th>Description</th>
4306
+ <th>Summary</th>
4307
+ <th>Source</th>
4308
+ </tr>
4309
+ </thead>
4310
+ <tbody>`;
4311
+ const conditions = resources.filter((entry) => entry.resourceType === "Condition").map((entry) => entry);
4312
+ conditions.sort((a, b) => {
4313
+ const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
4314
+ const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
4315
+ return dateB - dateA;
4241
4316
  });
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;
4317
+ const addedConditions = /* @__PURE__ */ new Set();
4318
+ for (const cond of conditions) {
4319
+ const functionalStatusName = this.getFunctionalStatusNameFromCode(cond.code);
4320
+ const problem = templateUtilities.codeableConceptDisplay(cond.code) || functionalStatusName;
4321
+ const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
4322
+ if (!codeAndSystem || !problem || !functionalStatusName || addedConditions.has(functionalStatusName)) {
4323
+ continue;
4324
+ }
4325
+ if (problem?.toLowerCase() === "unknown") {
4326
+ continue;
4327
+ }
4328
+ addedConditions.add(functionalStatusName);
4329
+ let date = cond.onsetDateTime ? templateUtilities.renderTime(cond.onsetDateTime, timezone) : "";
4330
+ if (!date && cond.onsetPeriod?.start) {
4331
+ date = templateUtilities.renderTime(
4332
+ cond.onsetPeriod?.start,
4333
+ timezone
4334
+ );
4335
+ if (cond.onsetPeriod?.end) {
4336
+ date += " - " + templateUtilities.renderTime(cond.onsetPeriod?.end, timezone);
4249
4337
  }
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>
4338
+ }
4339
+ conditionHtml += `<tr>
4340
+ <td>${templateUtilities.capitalizeFirstLetter(problem)}</td>
4341
+ <td>${codeAndSystem}</td>
4257
4342
  <td>${date}</td>
4258
- <td>${interpretation}</td>
4259
- <td>${comments}</td>
4260
- <td>${templateUtilities.getOwnerTag(observation)}</td>
4343
+ <td>${templateUtilities.renderTime(cond.recordedDate, timezone)}</td>
4344
+ <td>${templateUtilities.getOwnerTag(cond)}</td>
4261
4345
  </tr>`;
4346
+ }
4347
+ const clinicalImpressions = resources.filter((entry) => entry.resourceType === "ClinicalImpression").map((entry) => entry);
4348
+ clinicalImpressions.sort((a, b) => {
4349
+ const dateA = this.getClinicalImpressionEffectiveDate(a);
4350
+ const dateB = this.getClinicalImpressionEffectiveDate(b);
4351
+ return dateB && dateA ? dateB.getTime() - dateA.getTime() : 0;
4352
+ });
4353
+ const addedClinicalImpressions = /* @__PURE__ */ new Set();
4354
+ for (const impression of clinicalImpressions) {
4355
+ const name = templateUtilities.codeableConceptDisplay(impression.code);
4356
+ const codeAndSystem = templateUtilities.codeableConceptCoding(impression.code);
4357
+ if (!codeAndSystem || addedClinicalImpressions.has(name)) {
4358
+ continue;
4262
4359
  }
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>";
4360
+ if (!name || name?.toLowerCase() === "unknown") {
4361
+ continue;
4362
+ }
4363
+ addedClinicalImpressions.add(name);
4364
+ let date = impression.effectiveDateTime ? templateUtilities.renderTime(impression.effectiveDateTime, timezone) : "";
4365
+ if (!date && impression.effectivePeriod?.start) {
4366
+ date = templateUtilities.renderTime(
4367
+ impression.effectivePeriod?.start,
4368
+ timezone
4369
+ );
4370
+ if (impression.effectivePeriod?.end) {
4371
+ date += " - " + templateUtilities.renderTime(impression.effectivePeriod?.end, timezone);
4285
4372
  }
4286
- html += `<tr>
4287
- <td>${formattedDate}</td>
4288
- <td>${impression.status || ""}</td>
4373
+ }
4374
+ clinicalImpressionsHtml += `<tr>
4375
+ <td>${templateUtilities.capitalizeFirstLetter(name)}</td>
4376
+ <td>${codeAndSystem}</td>
4377
+ <td>${date}</td>
4289
4378
  <td>${impression.description || ""}</td>
4290
4379
  <td>${impression.summary || ""}</td>
4291
- <td>${findingsHtml}</td>
4292
4380
  <td>${templateUtilities.getOwnerTag(impression)}</td>
4293
4381
  </tr>`;
4382
+ }
4383
+ if (addedConditions.size > 0) {
4384
+ html += conditionHtml;
4385
+ html += `
4386
+ </tbody>
4387
+ </table>
4388
+ </div>`;
4389
+ }
4390
+ if (addedClinicalImpressions.size > 0) {
4391
+ html += clinicalImpressionsHtml;
4392
+ html += `
4393
+ </tbody>
4394
+ </table>
4395
+ </div>`;
4396
+ }
4397
+ html += `
4398
+ </div>`;
4399
+ return addedConditions.size > 0 || addedClinicalImpressions.size > 0 ? html : void 0;
4400
+ }
4401
+ static getFunctionalStatusNameFromCode(cc) {
4402
+ if (!cc) return "";
4403
+ for (const coding of cc.coding || []) {
4404
+ let functionalStatusName = FUNCTIONAL_STATUS_SNOMED_CODES[coding.code];
4405
+ if (functionalStatusName) {
4406
+ return functionalStatusName;
4407
+ }
4408
+ functionalStatusName = FUNCTIONAL_STATUS_ASSESSMENT_LOINC_CODES[coding.code];
4409
+ if (functionalStatusName) {
4410
+ return functionalStatusName;
4294
4411
  }
4295
- html += `</tbody></table>`;
4296
4412
  }
4297
- if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
4298
- html += `<p>No functional status information available.</p>`;
4413
+ }
4414
+ static getClinicalImpressionEffectiveDate(impression) {
4415
+ if (impression.effectiveDateTime) {
4416
+ return new Date(impression.effectiveDateTime);
4417
+ } else if (impression.effectivePeriod) {
4418
+ if (impression.effectivePeriod.start) {
4419
+ return new Date(impression.effectivePeriod.start);
4420
+ } else if (impression.effectivePeriod.end) {
4421
+ return new Date(impression.effectivePeriod.end);
4422
+ }
4299
4423
  }
4300
- return html;
4301
4424
  }
4302
4425
  };
4303
4426
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.37",
3
+ "version": "1.0.38",
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",