@imranq2/fhirpatientsummary 1.0.29 → 1.0.31

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 +805 -295
  2. package/dist/index.js +805 -295
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -99,6 +99,13 @@ var PREGNANCY_LOINC_CODES = {
99
99
  "33065-4": "Ectopic Pregnancy"
100
100
  }
101
101
  };
102
+ var PREGNANCY_SNOMED_CODES = [
103
+ // Add SNOMED CT codes for pregnancy history here, e.g.:
104
+ "72892002",
105
+ // Pregnancy (finding)
106
+ "77386006"
107
+ // History of pregnancy (situation)
108
+ ];
102
109
  var SOCIAL_HISTORY_LOINC_CODES = {
103
110
  "72166-2": "Tobacco Use",
104
111
  "74013-4": "Alcohol Use"
@@ -108,6 +115,29 @@ var BLOOD_PRESSURE_LOINC_CODES = {
108
115
  SYSTOLIC: "8480-6",
109
116
  DIASTOLIC: "8462-4"
110
117
  };
118
+ var ESSENTIAL_LAB_PANELS = {
119
+ // Top 20 Most Ordered Panels
120
+ "24323-8": "Comprehensive Metabolic Panel (CMP)",
121
+ "24320-4": "Basic Metabolic Panel (BMP)",
122
+ "58410-2": "Complete Blood Count (CBC)",
123
+ "57021-8": "CBC with Differential",
124
+ "24331-1": "Lipid Panel",
125
+ "57698-3": "Lipid Panel with Direct LDL",
126
+ "24325-3": "Hepatic Function Panel",
127
+ "24362-6": "Renal Function Panel",
128
+ "24326-1": "Electrolyte Panel",
129
+ "24348-5": "Thyroid Panel",
130
+ "24356-8": "Urinalysis Complete",
131
+ "24352-7": "Iron Studies Panel",
132
+ "34714-6": "Coagulation Panel",
133
+ "24364-2": "Prenatal Panel",
134
+ "24108-3": "Acute Hepatitis Panel",
135
+ "24110-9": "Hepatitis B Panel",
136
+ "34574-4": "Arthritis Panel",
137
+ "24360-0": "Anemia Panel",
138
+ "80235-8": "Cardiac Markers Panel",
139
+ "69738-3": "CBC with Auto Differential"
140
+ };
111
141
 
112
142
  // src/structures/ips_section_constants.ts
113
143
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -132,34 +162,39 @@ var IPSSectionResourceFilters = {
132
162
  // Only include completed immunizations
133
163
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
134
164
  // Only include vital sign Observations (category.coding contains 'vital-signs')
135
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c.code === "vital-signs")),
165
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, "vital-signs", c.system))),
136
166
  // Includes DeviceUseStatement. Device is needed for linked device details
137
167
  ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
138
- // Only include finalized diagnostic reports
139
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
168
+ // Only include finalized diagnostic reports and relevant observations
169
+ ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "DiagnosticReport" && resource.status === "final" || resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, RESULT_SUMMARY_OBSERVATION_CATEGORIES, c.system))),
140
170
  // Only include completed procedures
141
171
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
142
172
  // Only include social history Observations
143
- ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code?.coding?.some((c) => Object.keys(SOCIAL_HISTORY_LOINC_CODES).includes(c.code)),
144
- // Only include pregnancy history Observations
145
- ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS).includes(c.code)) || resource.valueCodeableConcept?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME).includes(c.code))),
146
- // Only include Conditions or completed ClinicalImpressions
147
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
173
+ ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
174
+ // Only include pregnancy history Observations or relevant Conditions
175
+ ["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
+ )
181
+ )) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
148
182
  // Only include resolved medical history Conditions
149
183
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
150
184
  // Only include active care plans
151
185
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
152
186
  // Only include active advance directives (Consent resources)
153
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
187
+ // TODO: disable this until we right logic to get these
188
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
154
189
  };
155
190
  var IPSSectionSummaryCompositionFilter = {
156
- ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
157
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
158
- ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
159
- ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
160
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
191
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
192
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
193
+ ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
194
+ ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
195
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "medication_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
161
196
  // [IPSSections.DIAGNOSTIC_REPORTS]: (resource) => resource.resourceType === 'Composition' && resource.type?.coding?.some((c: any) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && ["lab_summary_document", "diagnosticreportlab_summary_document"].includes(c.code)),
162
- ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
197
+ ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "procedure_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
163
198
  };
164
199
  var IPSSectionSummaryIPSCompositionFilter = {
165
200
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "ips_vital_summary_document")
@@ -180,9 +215,46 @@ var IPSSectionResourceHelper = class {
180
215
  return IPSSectionSummaryIPSCompositionFilter[section];
181
216
  }
182
217
  };
218
+ function codingMatches(coding, code, system) {
219
+ if (!coding || !coding.system) return false;
220
+ if (Array.isArray(code)) {
221
+ return coding.system === system && code.includes(coding.code ?? "");
222
+ }
223
+ return coding.system === system && coding.code === code;
224
+ }
225
+ function codeableConceptMatches(codeableConcept, code, system) {
226
+ if (!codeableConcept || !Array.isArray(codeableConcept.coding)) return false;
227
+ return codeableConcept.coding.some((coding) => codingMatches(coding, code, system));
228
+ }
183
229
 
184
230
  // src/narratives/templates/typescript/TemplateUtilities.ts
185
231
  var import_luxon = require("luxon");
232
+
233
+ // src/structures/codingSystemDisplayNames.ts
234
+ var CODING_SYSTEM_DISPLAY_NAMES = {
235
+ "http://snomed.info/sct": "SNOMED CT",
236
+ "http://loinc.org": "LOINC",
237
+ "http://hl7.org/fhir/sid/icd-10": "ICD-10",
238
+ "http://hl7.org/fhir/sid/icd-10-cm": "ICD-10-CM",
239
+ "http://hl7.org/fhir/sid/icd-9": "ICD-9",
240
+ "http://hl7.org/fhir/sid/cvx": "CVX",
241
+ "http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
242
+ "http://www.ama-assn.org/go/cpt": "CPT",
243
+ "http://unitsofmeasure.org": "UCUM",
244
+ "http://e-imo.com/products/problem-it": "IMO Problem IT",
245
+ "2.16.840.1.113883.6.285": "HCPCS Level II",
246
+ "https://fhir.cerner.com/4ff3b259-e48d-4066-8b35-a6a051f2802a/codeSet/72": "Cerner Code Set 72",
247
+ "http://hl7.org/fhir/sid/ndc": "NDC",
248
+ // Added mapping for NDC
249
+ "urn:oid:2.16.840.1.113883.12.292": "CVX",
250
+ "http://terminology.hl7.org/CodeSystem/data-absent-reason": "Data Absent Reason",
251
+ // Added mapping for Data Absent Reason
252
+ "2.16.840.1.113883.6.208": "NDDF"
253
+ // Add more as needed
254
+ };
255
+ var codingSystemDisplayNames_default = CODING_SYSTEM_DISPLAY_NAMES;
256
+
257
+ // src/narratives/templates/typescript/TemplateUtilities.ts
186
258
  var TemplateUtilities = class {
187
259
  /**
188
260
  * Constructor to initialize the TemplateUtilities with a FHIR resources
@@ -192,47 +264,63 @@ var TemplateUtilities = class {
192
264
  this.resources = resources;
193
265
  }
194
266
  /**
195
- * Formats a CodeableConcept object
196
- * @param cc - The CodeableConcept object
197
- * @param field - Optional specific field to return
198
- * @returns Formatted string representation
267
+ * Returns the preferred coding from a list of codings.
268
+ * If a coding has an extension with url 'https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence' and valueCode 'preferred', returns that coding.
269
+ * Otherwise, returns the first coding if it exists, else null.
270
+ * @param codings Array of coding objects
271
+ * @returns The preferred coding object or null
199
272
  */
200
- codeableConcept(cc, field) {
201
- if (!cc) {
202
- return "";
273
+ getPreferredCoding(codings) {
274
+ if (!Array.isArray(codings) || codings.length === 0) return null;
275
+ for (const coding of codings) {
276
+ if (Array.isArray(coding.extension)) {
277
+ for (const ext of coding.extension) {
278
+ if (ext.url === "https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence" && ext.valueCode === "preferred") {
279
+ return coding;
280
+ }
281
+ }
282
+ }
203
283
  }
284
+ return codings[0] || null;
285
+ }
286
+ /**
287
+ * Returns the display value from a CodeableConcept
288
+ * @param cc - The CodeableConcept object
289
+ * @param field - Optional specific field to extract (e.g., 'text', 'display', 'code')
290
+ * @returns Display string or empty string
291
+ */
292
+ codeableConceptDisplay(cc, field) {
293
+ if (!cc) return "";
204
294
  if (field) {
205
295
  if (cc[field]) {
206
296
  return cc[field];
207
- } else if (cc.coding && cc.coding[0] && cc.coding[0][field]) {
208
- return cc.coding[0][field];
297
+ } else if (cc.coding && cc.coding.length > 0) {
298
+ const preferredCoding = this.getPreferredCoding(cc.coding);
299
+ if (preferredCoding && preferredCoding[field]) {
300
+ return preferredCoding[field];
301
+ }
209
302
  }
210
303
  }
211
- if (cc.text) {
212
- return cc.text;
213
- } else if (cc.coding && cc.coding[0]) {
214
- if (cc.coding[0].display) {
215
- return cc.coding[0].display;
216
- } else if (cc.coding[0].code) {
217
- return cc.coding[0].code;
218
- }
304
+ if (cc.text) return cc.text;
305
+ if (cc.coding && cc.coding.length > 0) {
306
+ const preferredCoding = this.getPreferredCoding(cc.coding);
307
+ if (preferredCoding && preferredCoding.display) return preferredCoding.display;
219
308
  }
220
309
  return "";
221
310
  }
222
- resolveReference(ref) {
223
- if (!ref || !this.resources) {
224
- return null;
225
- }
226
- const referenceParts = ref.reference?.split("/");
227
- if (!referenceParts || referenceParts.length !== 2) {
228
- return null;
229
- }
230
- const referenceResourceType = referenceParts[0];
231
- const referenceResourceId = referenceParts[1];
232
- const resource = this.resources.find((entry) => {
233
- return entry.resourceType === referenceResourceType && entry.id === referenceResourceId;
234
- });
235
- return resource ? resource : null;
311
+ /**
312
+ * Returns the code and system from a CodeableConcept
313
+ * @param cc - The CodeableConcept object
314
+ * @returns Object with code and system, or empty strings if not present
315
+ */
316
+ codeableConceptCoding(cc) {
317
+ if (!cc || !cc.coding || !cc.coding.length) return "";
318
+ const preferredCoding = this.getPreferredCoding(cc.coding);
319
+ if (!preferredCoding) return "";
320
+ const code = preferredCoding.code || "";
321
+ const system = preferredCoding.system || "";
322
+ const systemDisplay = codingSystemDisplayNames_default[system] || system;
323
+ return code ? `${code} (${systemDisplay})` : "";
236
324
  }
237
325
  /**
238
326
  * Renders a Device reference
@@ -280,7 +368,7 @@ var TemplateUtilities = class {
280
368
  return "";
281
369
  }
282
370
  if (medicationType.medicationCodeableConcept) {
283
- return this.codeableConcept(medicationType.medicationCodeableConcept);
371
+ return this.codeableConceptDisplay(medicationType.medicationCodeableConcept);
284
372
  } else if (medicationType.medicationReference) {
285
373
  return this.renderMedicationRef(medicationType.medicationReference);
286
374
  }
@@ -305,7 +393,7 @@ var TemplateUtilities = class {
305
393
  */
306
394
  renderMedicationCode(medication) {
307
395
  if (medication && medication.code) {
308
- return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
396
+ return this.renderTextAsHtml(this.codeableConceptDisplay(medication.code));
309
397
  }
310
398
  return "";
311
399
  }
@@ -474,7 +562,7 @@ var TemplateUtilities = class {
474
562
  */
475
563
  firstFromCodeableConceptList(list) {
476
564
  if (list && Array.isArray(list) && list[0]) {
477
- return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
565
+ return this.renderTextAsHtml(this.codeableConceptDisplay(list[0]));
478
566
  }
479
567
  return "";
480
568
  }
@@ -814,16 +902,28 @@ var TemplateUtilities = class {
814
902
  return this.renderMedicationCode(medicationSource);
815
903
  }
816
904
  if (typeof medicationSource === "object" && ("coding" in medicationSource || "text" in medicationSource)) {
817
- return this.codeableConcept(medicationSource);
905
+ return this.codeableConceptDisplay(medicationSource);
818
906
  }
819
907
  if (typeof medicationSource === "object" && "reference" in medicationSource) {
820
908
  const medication = this.resolveReference(medicationSource);
821
909
  if (medication && medication.code) {
822
- return this.codeableConcept(medication.code);
910
+ return this.codeableConceptDisplay(medication.code);
823
911
  }
824
912
  }
825
913
  return "";
826
914
  }
915
+ /**
916
+ * Returns the owner tag from the resource meta.security array.
917
+ * @param resource - FHIR resource with meta tag
918
+ * @returns The owner code if found, otherwise undefined
919
+ */
920
+ getOwnerTag(resource) {
921
+ if (!resource?.meta?.security) return "";
922
+ const ownerEntry = resource.meta.security.find(
923
+ (sec) => sec.system === "https://www.icanbwell.com/owner" && !!sec.code
924
+ );
925
+ return ownerEntry?.code;
926
+ }
827
927
  /**
828
928
  * Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
829
929
  * This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
@@ -987,6 +1087,21 @@ var TemplateUtilities = class {
987
1087
  const denominatorUnit = valueRatio.denominator.unit ? ` ${valueRatio.denominator.unit}` : "";
988
1088
  return `${numerator}${numeratorUnit} / ${denominator}${denominatorUnit}`;
989
1089
  }
1090
+ /**
1091
+ * Finds the resource that matches the reference
1092
+ * @param ref - Reference to a resource
1093
+ * @returns The resource or null
1094
+ */
1095
+ resolveReference(ref) {
1096
+ if (!ref || !this.resources) {
1097
+ return null;
1098
+ }
1099
+ const refId = ref.reference?.split("/")[1];
1100
+ const refType = ref.reference?.split("/")[0];
1101
+ return this.resources.find(
1102
+ (resource) => resource.resourceType === refType && resource.id === refId
1103
+ ) || null;
1104
+ }
990
1105
  };
991
1106
 
992
1107
  // src/constants.ts
@@ -1320,7 +1435,8 @@ var PatientTemplate = class _PatientTemplate {
1320
1435
  static generateStaticNarrative(resources, timezone) {
1321
1436
  const templateUtilities = new TemplateUtilities(resources);
1322
1437
  const combinedPatient = this.combinePatients(resources);
1323
- return `<div>
1438
+ let html = `<p>This section merges all Patient resources into a single combined patient record, preferring non-empty values for each field.</p>`;
1439
+ html += `<div>
1324
1440
  <ul>
1325
1441
  <li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
1326
1442
  <li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
@@ -1332,6 +1448,7 @@ var PatientTemplate = class _PatientTemplate {
1332
1448
  <li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
1333
1449
  </ul>
1334
1450
  </div>`;
1451
+ return html;
1335
1452
  }
1336
1453
  /**
1337
1454
  * Combines multiple patient resources into a single patient object
@@ -1615,7 +1732,7 @@ var PatientTemplate = class _PatientTemplate {
1615
1732
  const uniqueLanguages = /* @__PURE__ */ new Set();
1616
1733
  const preferredLanguages = /* @__PURE__ */ new Set();
1617
1734
  patient.communication.forEach((comm) => {
1618
- const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
1735
+ const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(comm.language));
1619
1736
  if (language) {
1620
1737
  if (comm.preferred) {
1621
1738
  preferredLanguages.add(language);
@@ -1656,20 +1773,26 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1656
1773
  generateSummaryNarrative(resources, timezone) {
1657
1774
  const templateUtilities = new TemplateUtilities(resources);
1658
1775
  let isSummaryCreated = false;
1659
- let html = `
1776
+ let html = `<p>This list includes all AllergyIntolerance resources, with no additional filtering, sorted as provided.</p>
1777
+ `;
1778
+ html += `
1660
1779
  <div>
1661
1780
  <table>
1662
1781
  <thead>
1663
1782
  <tr>
1664
1783
  <th>Allergen</th>
1784
+ <th>Code (System)</th>
1665
1785
  <th>Criticality</th>
1666
1786
  <th>Recorded Date</th>
1787
+ <th>Source</th>
1667
1788
  </tr>
1668
1789
  </thead>
1669
1790
  <tbody>`;
1670
1791
  for (const resourceItem of resources) {
1671
1792
  for (const rowData of resourceItem.section ?? []) {
1793
+ const sectionCodeableConcept = rowData.code;
1672
1794
  const data = {};
1795
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
1673
1796
  for (const columnData of rowData.section ?? []) {
1674
1797
  switch (columnData.title) {
1675
1798
  case "Allergen Name":
@@ -1681,6 +1804,9 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1681
1804
  case "Recorded Date":
1682
1805
  data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1683
1806
  break;
1807
+ case "Source":
1808
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1809
+ break;
1684
1810
  default:
1685
1811
  break;
1686
1812
  }
@@ -1688,9 +1814,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1688
1814
  isSummaryCreated = true;
1689
1815
  html += `
1690
1816
  <tr>
1691
- <td>${data["allergen"] ?? "-"}</td>
1692
- <td>${data["criticality"] ?? "-"}</td>
1693
- <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? "-"}</td>
1817
+ <td>${data["allergen"] ?? ""}</td>
1818
+ <td>${data["codeSystem"] ?? ""}</td>
1819
+ <td>${data["criticality"] ?? ""}</td>
1820
+ <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? ""}</td>
1821
+ <td>${data["source"] ?? ""}</td>
1694
1822
  </tr>`;
1695
1823
  }
1696
1824
  }
@@ -1729,7 +1857,8 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1729
1857
  const dateB = b.onsetDateTime;
1730
1858
  return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
1731
1859
  });
1732
- let html = "";
1860
+ let html = `<p>This list includes all AllergyIntolerance resources, with no additional filtering, sorted as provided.</p>
1861
+ `;
1733
1862
  html += `
1734
1863
  <div class="ActiveAllergies">
1735
1864
  <h3>Active</h3>
@@ -1738,10 +1867,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1738
1867
  <tr>
1739
1868
  <th>Allergen</th>
1740
1869
  <th>Status</th>
1870
+ <th>Code (System)</th>
1741
1871
  <th>Category</th>
1742
1872
  <th>Reaction</th>
1743
1873
  <th>Onset Date</th>
1744
1874
  <th>Comments</th>
1875
+ <th>Source</th>
1745
1876
  </tr>
1746
1877
  </thead>
1747
1878
  <tbody>`;
@@ -1765,11 +1896,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1765
1896
  <tr>
1766
1897
  <th>Allergen</th>
1767
1898
  <th>Status</th>
1899
+ <th>Code (System)</th>
1768
1900
  <th>Category</th>
1769
1901
  <th>Reaction</th>
1770
1902
  <th>Onset Date</th>
1771
1903
  <th>Comments</th>
1772
1904
  <th>Resolved Date</th>
1905
+ <th>Source</th>
1773
1906
  </tr>
1774
1907
  </thead>
1775
1908
  <tbody>`;
@@ -1800,14 +1933,16 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1800
1933
  for (const allergy of allergies) {
1801
1934
  html += `
1802
1935
  <tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
1803
- <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
1804
- <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
1805
- <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
1806
- <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
1807
- <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
1808
- <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
1936
+ <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.code))}</span></td>
1937
+ <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.clinicalStatus)) || ""}</td>
1938
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(allergy.code)}</td>
1939
+ <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || ""}</td>
1940
+ <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || ""}</td>
1941
+ <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || ""}</td>
1942
+ <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>
1943
+ <td class="Source">${templateUtilities.getOwnerTag(allergy)}</td>`;
1809
1944
  if (includeResolved) {
1810
- let endDate = "-";
1945
+ let endDate = "";
1811
1946
  if (allergy.extension && Array.isArray(allergy.extension)) {
1812
1947
  const endDateExt = allergy.extension.find(
1813
1948
  (ext) => ext.url === "http://hl7.org/fhir/StructureDefinition/allergyintolerance-resolutionDate"
@@ -1831,10 +1966,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1831
1966
  * Generate HTML narrative for Medication resources
1832
1967
  * @param resources - FHIR Medication resources
1833
1968
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1969
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1834
1970
  * @returns HTML string for rendering
1835
1971
  */
1836
- generateNarrative(resources, timezone) {
1837
- return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
1972
+ generateNarrative(resources, timezone, now) {
1973
+ return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone, now);
1838
1974
  }
1839
1975
  /**
1840
1976
  * Generate HTML narrative for Medication resources using summary
@@ -1847,23 +1983,28 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1847
1983
  const templateUtilities = new TemplateUtilities(resources);
1848
1984
  let isSummaryCreated = false;
1849
1985
  const currentDate = now || /* @__PURE__ */ new Date();
1850
- const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
1851
- let html = `
1986
+ const twoYearsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 24, currentDate.getDate());
1987
+ let html = "<p>This list includes all the medications that were ordered for the patient, the medications they filled from the pharmacy and the medications they reported, filtered to past 2 years and sorted by start date.</p>";
1988
+ html += `
1852
1989
  <div>
1853
1990
  <table>
1854
1991
  <thead>
1855
1992
  <tr>
1856
1993
  <th>Medication</th>
1994
+ <th>Code (System)</th>
1857
1995
  <th>Status</th>
1858
1996
  <th>Sig</th>
1859
1997
  <th>Days of Supply</th>
1860
1998
  <th>Refills</th>
1861
1999
  <th>Start Date</th>
2000
+ <th>Source</th>
1862
2001
  </tr>
1863
2002
  </thead>
1864
2003
  <tbody>`;
2004
+ let skippedMedications = 0;
1865
2005
  for (const resourceItem of resources) {
1866
2006
  for (const rowData of resourceItem.section ?? []) {
2007
+ const sectionCodeableConcept = rowData.code;
1867
2008
  const data = {};
1868
2009
  for (const columnData of rowData.section ?? []) {
1869
2010
  switch (columnData.title) {
@@ -1888,6 +2029,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1888
2029
  case "Authored On Date":
1889
2030
  data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1890
2031
  break;
2032
+ case "Source":
2033
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2034
+ break;
1891
2035
  default:
1892
2036
  break;
1893
2037
  }
@@ -1899,16 +2043,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1899
2043
  startDateObj = void 0;
1900
2044
  }
1901
2045
  }
1902
- if (data["status"] === "active" || startDateObj && startDateObj >= twelveMonthsAgo) {
2046
+ if (!(data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo)) {
2047
+ skippedMedications++;
2048
+ }
2049
+ if (data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo) {
1903
2050
  isSummaryCreated = true;
1904
2051
  html += `
1905
2052
  <tr>
1906
2053
  <td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
2054
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
1907
2055
  <td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
1908
2056
  <td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
1909
2057
  <td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
1910
2058
  <td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
1911
2059
  <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
2060
+ <td>${templateUtilities.renderTextAsHtml(data["source"])}</td>
1912
2061
  </tr>`;
1913
2062
  }
1914
2063
  }
@@ -1917,7 +2066,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1917
2066
  </tbody>
1918
2067
  </table>
1919
2068
  </div>`;
1920
- return isSummaryCreated ? html : void 0;
2069
+ if (skippedMedications > 0) {
2070
+ html += `
2071
+ <p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
2072
+ }
2073
+ if (isSummaryCreated || skippedMedications > 0) {
2074
+ return html;
2075
+ }
2076
+ return void 0;
1921
2077
  }
1922
2078
  /**
1923
2079
  * Safely parse a date string and return a valid Date object or null
@@ -1935,21 +2091,42 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1935
2091
  * Internal static implementation that actually generates the narrative
1936
2092
  * @param resources - FHIR Medication resources
1937
2093
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2094
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1938
2095
  * @returns HTML string for rendering
1939
2096
  */
1940
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1941
- static generateStaticNarrative(resources, timezone) {
2097
+ static generateStaticNarrative(resources, timezone, now) {
1942
2098
  const templateUtilities = new TemplateUtilities(resources);
1943
2099
  let html = "";
1944
2100
  const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
1945
2101
  const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
1946
2102
  const allActiveMedications = [];
2103
+ const currentDate = now || /* @__PURE__ */ new Date();
2104
+ const twoYearsAgo = new Date(currentDate);
2105
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2106
+ let skippedMedications = 0;
2107
+ const allMedications = [];
1947
2108
  medicationRequests.forEach((mr) => {
1948
- allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
2109
+ allMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1949
2110
  });
1950
2111
  medicationStatements.forEach((ms) => {
1951
- allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
2112
+ allMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1952
2113
  });
2114
+ for (const med of allMedications) {
2115
+ let dateString;
2116
+ if (med.type === "request") {
2117
+ const mr = med.resource;
2118
+ dateString = mr.dispenseRequest?.validityPeriod?.start || mr.authoredOn;
2119
+ } else {
2120
+ const ms = med.resource;
2121
+ dateString = ms.effectiveDateTime || ms.effectivePeriod?.start;
2122
+ }
2123
+ const dateObj = this.parseDate(dateString);
2124
+ if (!dateObj || dateObj < twoYearsAgo) {
2125
+ skippedMedications++;
2126
+ } else {
2127
+ allActiveMedications.push(med);
2128
+ }
2129
+ }
1953
2130
  const sortMedications = (medications) => {
1954
2131
  medications.sort((a, b) => {
1955
2132
  let dateStringA;
@@ -1980,7 +2157,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1980
2157
  sortMedications(allActiveMedications);
1981
2158
  html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
1982
2159
  }
1983
- return html;
2160
+ if (skippedMedications > 0) {
2161
+ html += `
2162
+ <p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
2163
+ }
2164
+ if (allActiveMedications.length > 0 || skippedMedications > 0) {
2165
+ return html;
2166
+ }
2167
+ return "";
1984
2168
  }
1985
2169
  /**
1986
2170
  * Extract MedicationRequest resources
@@ -2025,10 +2209,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2025
2209
  <tr>
2026
2210
  <th>Type</th>
2027
2211
  <th>Medication</th>
2212
+ <th>Code (System)</th>
2028
2213
  <th>Sig</th>
2029
2214
  <th>Dispense Quantity</th>
2030
2215
  <th>Refills</th>
2031
2216
  <th>Start Date</th>
2217
+ <th>Source</th>
2032
2218
  </tr>
2033
2219
  </thead>
2034
2220
  <tbody>`;
@@ -2037,27 +2223,31 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2037
2223
  let type;
2038
2224
  let medicationName;
2039
2225
  let sig;
2040
- let dispenseQuantity = "-";
2041
- let refills = "-";
2042
- let startDate = "-";
2226
+ let dispenseQuantity = "";
2227
+ let refills = "";
2228
+ let startDate = "";
2229
+ let codeSystemDisplay = "";
2043
2230
  if (medication.type === "request") {
2044
2231
  const mr = medication.resource;
2045
2232
  type = "Request";
2046
2233
  medicationName = templateUtilities.getMedicationName(
2047
2234
  mr.medicationReference || mr.medicationCodeableConcept
2048
2235
  );
2049
- sig = templateUtilities.concat(mr.dosageInstruction, "text") || "-";
2236
+ sig = templateUtilities.concat(mr.dosageInstruction, "text") || "";
2050
2237
  if (mr.dispenseRequest?.quantity) {
2051
2238
  const quantity = mr.dispenseRequest.quantity;
2052
2239
  if (quantity.value) {
2053
2240
  dispenseQuantity = `${quantity.value} ${quantity.unit || quantity.code || ""}`.trim();
2054
2241
  }
2055
2242
  }
2056
- refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
2243
+ refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "";
2057
2244
  if (mr.dispenseRequest?.validityPeriod) {
2058
- startDate = mr.dispenseRequest.validityPeriod.start || "-";
2245
+ startDate = mr.dispenseRequest.validityPeriod.start || "";
2059
2246
  } else {
2060
- startDate = mr.authoredOn || "-";
2247
+ startDate = mr.authoredOn || "";
2248
+ }
2249
+ if (mr.medicationCodeableConcept && mr.medicationCodeableConcept.coding && mr.medicationCodeableConcept.coding[0]) {
2250
+ codeSystemDisplay = templateUtilities.codeableConceptCoding(mr.medicationCodeableConcept);
2061
2251
  }
2062
2252
  } else {
2063
2253
  const ms = medication.resource;
@@ -2065,21 +2255,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2065
2255
  medicationName = templateUtilities.getMedicationName(
2066
2256
  ms.medicationReference || ms.medicationCodeableConcept
2067
2257
  );
2068
- sig = templateUtilities.concat(ms.dosage, "text") || "-";
2258
+ sig = templateUtilities.concat(ms.dosage, "text") || "";
2069
2259
  if (ms.effectiveDateTime) {
2070
2260
  startDate = ms.effectiveDateTime;
2071
2261
  } else if (ms.effectivePeriod) {
2072
- startDate = ms.effectivePeriod.start || "-";
2262
+ startDate = ms.effectivePeriod.start || "";
2263
+ }
2264
+ if (ms.medicationCodeableConcept && ms.medicationCodeableConcept.coding && ms.medicationCodeableConcept.coding[0]) {
2265
+ codeSystemDisplay = templateUtilities.codeableConceptCoding(ms.medicationCodeableConcept);
2073
2266
  }
2074
2267
  }
2075
2268
  html += `
2076
2269
  <tr${narrativeLinkId ? ` id="${narrativeLinkId}"` : ""}>
2077
2270
  <td>${type}</td>
2078
2271
  <td>${medicationName}<ul></ul></td>
2272
+ <td>${codeSystemDisplay}</td>
2079
2273
  <td>${sig}</td>
2080
2274
  <td>${dispenseQuantity}</td>
2081
2275
  <td>${refills}</td>
2082
2276
  <td>${startDate}</td>
2277
+ <td>${templateUtilities.getOwnerTag(medication.resource)}</td>
2083
2278
  </tr>`;
2084
2279
  }
2085
2280
  html += `
@@ -2116,18 +2311,23 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2116
2311
  let isSummaryCreated = false;
2117
2312
  let html = `
2118
2313
  <div>
2314
+ <p>This list includes all vaccinations, sorted by occurrence date (most recent first).</p>
2119
2315
  <table>
2120
2316
  <thead>
2121
2317
  <tr>
2122
2318
  <th>Immunization</th>
2319
+ <th>Code (System)</th>
2123
2320
  <th>Status</th>
2124
2321
  <th>Date</th>
2322
+ <th>Source</th>
2125
2323
  </tr>
2126
2324
  </thead>
2127
2325
  <tbody>`;
2128
2326
  for (const resourceItem of resources) {
2129
2327
  for (const rowData of resourceItem.section ?? []) {
2328
+ const sectionCodeableConcept = rowData.code;
2130
2329
  const data = {};
2330
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2131
2331
  for (const columnData of rowData.section ?? []) {
2132
2332
  switch (columnData.title) {
2133
2333
  case "Immunization Name":
@@ -2139,6 +2339,9 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2139
2339
  case "occurrenceDateTime":
2140
2340
  data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2141
2341
  break;
2342
+ case "Source":
2343
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2344
+ break;
2142
2345
  default:
2143
2346
  break;
2144
2347
  }
@@ -2147,9 +2350,11 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2147
2350
  isSummaryCreated = true;
2148
2351
  html += `
2149
2352
  <tr>
2150
- <td>${data["immunization"] ?? "-"}</td>
2151
- <td>${data["status"] ?? "-"}</td>
2152
- <td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? "-"}</td>
2353
+ <td>${data["immunization"] ?? ""}</td>
2354
+ <td>${data["codeSystem"] ?? ""}</td>
2355
+ <td>${data["status"] ?? ""}</td>
2356
+ <td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? ""}</td>
2357
+ <td>${data["source"] ?? ""}</td>
2153
2358
  </tr>`;
2154
2359
  }
2155
2360
  }
@@ -2173,12 +2378,14 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2173
2378
  <thead>
2174
2379
  <tr>
2175
2380
  <th>Immunization</th>
2381
+ <th>Code (System)</th>
2176
2382
  <th>Status</th>
2177
2383
  <th>Dose Number</th>
2178
2384
  <th>Manufacturer</th>
2179
2385
  <th>Lot Number</th>
2180
2386
  <th>Comments</th>
2181
2387
  <th>Date</th>
2388
+ <th>Source</th>
2182
2389
  </tr>
2183
2390
  </thead>
2184
2391
  <tbody>`;
@@ -2187,14 +2394,16 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2187
2394
  for (const resourceItem of immunizations) {
2188
2395
  const imm = resourceItem;
2189
2396
  html += `
2190
- <tr id="${templateUtilities.narrativeLinkId(imm)}">
2191
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
2397
+ <tr>
2398
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(imm.vaccineCode))}</td>
2399
+ <td>${templateUtilities.codeableConceptCoding(imm.vaccineCode)}</td>
2192
2400
  <td>${imm.status || ""}</td>
2193
2401
  <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
2194
2402
  <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
2195
2403
  <td>${imm.lotNumber || ""}</td>
2196
2404
  <td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
2197
2405
  <td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
2406
+ <td>${templateUtilities.getOwnerTag(imm)}</td>
2198
2407
  </tr>`;
2199
2408
  }
2200
2409
  }
@@ -2225,11 +2434,15 @@ var ProblemListTemplate = class _ProblemListTemplate {
2225
2434
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2226
2435
  static generateStaticNarrative(resources, timezone) {
2227
2436
  const templateUtilities = new TemplateUtilities(resources);
2228
- let html = ``;
2437
+ let html = `<p>This list includes patient problems, sorted by recorded date (most recent first)</p>
2438
+ `;
2229
2439
  const activeConditions = resources.map((entry) => entry) || [];
2230
2440
  activeConditions.sort((a, b) => {
2231
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
2232
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
2441
+ if (!a.recordedDate && b.recordedDate) return -1;
2442
+ if (a.recordedDate && !b.recordedDate) return 1;
2443
+ if (!a.recordedDate && !b.recordedDate) return 0;
2444
+ const dateA = new Date(a.recordedDate).getTime();
2445
+ const dateB = new Date(b.recordedDate).getTime();
2233
2446
  return dateB - dateA;
2234
2447
  });
2235
2448
  html += `
@@ -2237,22 +2450,28 @@ var ProblemListTemplate = class _ProblemListTemplate {
2237
2450
  <thead>
2238
2451
  <tr>
2239
2452
  <th>Problem</th>
2453
+ <th>Code (System)</th>
2240
2454
  <th>Onset Date</th>
2241
2455
  <th>Recorded Date</th>
2456
+ <th>Source</th>
2242
2457
  </tr>
2243
2458
  </thead>
2244
2459
  <tbody>`;
2245
- const addedConditionCodes = /* @__PURE__ */ new Set();
2460
+ const seenCodeAndSystems = /* @__PURE__ */ new Set();
2246
2461
  for (const cond of activeConditions) {
2247
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2248
- if (!addedConditionCodes.has(conditionCode)) {
2249
- addedConditionCodes.add(conditionCode);
2250
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2251
- <td class="Name">${conditionCode}</td>
2252
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2253
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2254
- </tr>`;
2462
+ const conditionDisplay = templateUtilities.codeableConceptDisplay(cond.code);
2463
+ const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
2464
+ if (codeAndSystem && seenCodeAndSystems.has(codeAndSystem)) {
2465
+ continue;
2255
2466
  }
2467
+ seenCodeAndSystems.add(codeAndSystem);
2468
+ html += `<tr>
2469
+ <td class="Name">${conditionDisplay}</td>
2470
+ <td class="CodeSystem">${codeAndSystem}</td>
2471
+ <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2472
+ <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2473
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
2474
+ </tr>`;
2256
2475
  }
2257
2476
  html += `</tbody>
2258
2477
  </table>`;
@@ -2280,21 +2499,26 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2280
2499
  generateSummaryNarrative(resources, timezone) {
2281
2500
  const templateUtilities = new TemplateUtilities(resources);
2282
2501
  let isSummaryCreated = false;
2283
- let html = `
2502
+ let html = `<p>This list includes the latest vital signs, sorted by effective date (most recent first).</p>
2503
+ `;
2504
+ html += `
2284
2505
  <div>
2285
2506
  <table>
2286
2507
  <thead>
2287
2508
  <tr>
2288
- <th>Vital Name</th>
2509
+ <th>Name</th>
2510
+ <th>Code (System)</th>
2289
2511
  <th>Result</th>
2290
- <th>Reference Range</th>
2291
2512
  <th>Date</th>
2513
+ <th>Source</th>
2292
2514
  </tr>
2293
2515
  </thead>
2294
2516
  <tbody>`;
2295
2517
  for (const resourceItem of resources) {
2296
2518
  for (const rowData of resourceItem.section ?? []) {
2519
+ const sectionCodeableConcept = rowData.code;
2297
2520
  const data = {};
2521
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2298
2522
  for (const columnData of rowData.section ?? []) {
2299
2523
  const columnTitle = columnData.title;
2300
2524
  if (columnTitle) {
@@ -2322,10 +2546,11 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2322
2546
  isSummaryCreated = true;
2323
2547
  html += `
2324
2548
  <tr>
2325
- <td>${data["Vital Name"] ?? "-"}</td>
2326
- <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? "-"}</td>
2327
- <td>${templateUtilities.extractObservationSummaryReferenceRange(data) ?? "-"}</td>
2328
- <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "-"}</td>
2549
+ <td>${data["Vital Name"] ?? ""}</td>
2550
+ <td>${data["codeSystem"] ?? ""}</td>
2551
+ <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? ""}</td>
2552
+ <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
2553
+ <td>${data["Source"] ?? ""}</td>
2329
2554
  </tr>`;
2330
2555
  }
2331
2556
  }
@@ -2349,30 +2574,36 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2349
2574
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
2350
2575
  return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2351
2576
  });
2352
- let html = `
2577
+ let html = `<p>This list includes the latest vital signs, sorted by effective date (most recent first).</p>
2578
+ `;
2579
+ html += `
2353
2580
  <table>
2354
2581
  <thead>
2355
2582
  <tr>
2356
- <th>Vital Name</th>
2583
+ <th>Name</th>
2584
+ <th>Code (System)</th>
2357
2585
  <th>Result</th>
2358
2586
  <th>Unit</th>
2359
2587
  <th>Interpretation</th>
2360
2588
  <th>Component(s)</th>
2361
2589
  <th>Comments</th>
2362
2590
  <th>Date</th>
2591
+ <th>Source</th>
2363
2592
  </tr>
2364
2593
  </thead>
2365
2594
  <tbody>`;
2366
2595
  for (const obs of observations) {
2367
2596
  html += `
2368
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
2369
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
2597
+ <tr>
2598
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code, "display"))}</td>
2599
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2370
2600
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2371
2601
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2372
2602
  <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
2373
2603
  <td>${templateUtilities.renderComponent(obs.component)}</td>
2374
2604
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
2375
2605
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
2606
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2376
2607
  </tr>`;
2377
2608
  }
2378
2609
  html += `
@@ -2401,7 +2632,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2401
2632
  */
2402
2633
  static generateStaticNarrative(resources, timezone) {
2403
2634
  const templateUtilities = new TemplateUtilities(resources);
2404
- let html = `
2635
+ let html = `<p>This list includes all DeviceUseStatement resources, sorted by recorded date (most recent first).</p>
2405
2636
  <table>
2406
2637
  <thead>
2407
2638
  <tr>
@@ -2419,7 +2650,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2419
2650
  });
2420
2651
  for (const dus of deviceStatements) {
2421
2652
  html += `
2422
- <tr id="${templateUtilities.narrativeLinkId(dus)}">
2653
+ <tr>
2423
2654
  <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
2424
2655
  <td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
2425
2656
  <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
@@ -2445,10 +2676,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2445
2676
  * Generate HTML narrative for Diagnostic Results
2446
2677
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2447
2678
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2679
+ * @param now - Optional current date for filtering
2448
2680
  * @returns HTML string for rendering
2449
2681
  */
2450
- generateNarrative(resources, timezone) {
2451
- return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2682
+ generateNarrative(resources, timezone, now) {
2683
+ return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone, now);
2452
2684
  }
2453
2685
  /**
2454
2686
  * Helper function to format observation data fields
@@ -2661,6 +2893,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2661
2893
  break;
2662
2894
  case "valueRange.high.value":
2663
2895
  targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2896
+ targetData["valueType"] = "valueRange";
2664
2897
  break;
2665
2898
  case "valueRange.high.unit":
2666
2899
  targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2675,6 +2908,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2675
2908
  break;
2676
2909
  case "valueRatio.denominator.value":
2677
2910
  targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2911
+ targetData["valueType"] = "valueRatio";
2678
2912
  break;
2679
2913
  case "valueRatio.denominator.unit":
2680
2914
  targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2712,34 +2946,100 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2712
2946
  * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2713
2947
  * @param resources - FHIR Composition resources
2714
2948
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2949
+ * @param now - Optional current date for filtering
2715
2950
  * @returns HTML string for rendering
2716
2951
  */
2717
- generateSummaryNarrative(resources, timezone) {
2952
+ generateSummaryNarrative(resources, timezone, now) {
2718
2953
  const templateUtilities = new TemplateUtilities(resources);
2719
- let html = `
2720
- <div>`;
2954
+ const currentDate = now || /* @__PURE__ */ new Date();
2955
+ const twoYearsAgo = new Date(currentDate);
2956
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2957
+ let skippedObservations = 0;
2958
+ let skippedDiagnosticReports = 0;
2959
+ for (const resourceItem of resources) {
2960
+ for (const rowData of resourceItem.section ?? []) {
2961
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2962
+ for (const columnData of rowData.section ?? []) {
2963
+ if (columnData.text?.div === "Observation.component" && columnData.section) {
2964
+ for (const componentSection of columnData.section) {
2965
+ const componentData = {};
2966
+ for (const nestedColumn of componentSection.section ?? []) {
2967
+ this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2968
+ }
2969
+ let compDate = void 0;
2970
+ if (componentData["effectiveDateTime"]) {
2971
+ compDate = new Date(componentData["effectiveDateTime"]);
2972
+ } else if (componentData["effectivePeriodStart"]) {
2973
+ compDate = new Date(componentData["effectivePeriodStart"]);
2974
+ }
2975
+ if (compDate && compDate < twoYearsAgo) {
2976
+ skippedObservations++;
2977
+ }
2978
+ }
2979
+ } else {
2980
+ const data = {};
2981
+ this.extractSummaryObservationFields(columnData, data, templateUtilities);
2982
+ let obsDate = void 0;
2983
+ if (data["effectiveDateTime"]) {
2984
+ obsDate = new Date(data["effectiveDateTime"]);
2985
+ } else if (data["effectivePeriodStart"]) {
2986
+ obsDate = new Date(data["effectivePeriodStart"]);
2987
+ }
2988
+ if (obsDate && obsDate < twoYearsAgo) {
2989
+ skippedObservations++;
2990
+ }
2991
+ }
2992
+ }
2993
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2994
+ let issuedDate = void 0;
2995
+ let status = void 0;
2996
+ let reportFound = false;
2997
+ for (const columnData of rowData.section ?? []) {
2998
+ if (columnData.title === "Issued Date") {
2999
+ issuedDate = columnData.text?.div ? new Date(columnData.text.div) : void 0;
3000
+ }
3001
+ if (columnData.title === "Status") {
3002
+ status = columnData.text?.div;
3003
+ }
3004
+ if (columnData.title === "Diagnostic Report Name") {
3005
+ reportFound = true;
3006
+ }
3007
+ }
3008
+ if (status === "final" && issuedDate && issuedDate < twoYearsAgo && reportFound) {
3009
+ skippedDiagnosticReports++;
3010
+ }
3011
+ }
3012
+ }
3013
+ }
3014
+ let html = `<p>This section includes Observations from the last 2 years (one per lab name, using code mapping) and DiagnosticReports with status 'final' issued in the last 2 years. Both are sorted by date (most recent first). Older results are counted and noted below the tables.</p>
3015
+ `;
2721
3016
  let observationhtml = `
2722
3017
  <div>
2723
3018
  <h3>Observations</h3>
3019
+ ${html}
2724
3020
  <table>
2725
3021
  <thead>
2726
3022
  <tr>
2727
- <th>Code</th>
3023
+ <th>Name</th>
3024
+ <th>Code (System)</th>
2728
3025
  <th>Result</th>
2729
3026
  <th>Reference Range</th>
2730
3027
  <th>Date</th>
3028
+ <th>Source</th>
2731
3029
  </tr>
2732
3030
  </thead>
2733
3031
  <tbody>`;
2734
3032
  let diagnosticReporthtml = `
2735
3033
  <div>
2736
3034
  <h3>Diagnostic Reports</h3>
3035
+ ${html}
2737
3036
  <table>
2738
3037
  <thead>
2739
3038
  <tr>
2740
3039
  <th>Report</th>
2741
3040
  <th>Performer</th>
2742
3041
  <th>Issued</th>
3042
+ <th>Source</th>
2743
3043
  </tr>
2744
3044
  </thead>
2745
3045
  <tbody>`;
@@ -2747,7 +3047,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2747
3047
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2748
3048
  for (const resourceItem of resources) {
2749
3049
  for (const rowData of resourceItem.section ?? []) {
3050
+ const sectionCodeableConcept = rowData.code;
2750
3051
  const data = {};
3052
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2751
3053
  const components = [];
2752
3054
  for (const columnData of rowData.section ?? []) {
2753
3055
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
@@ -2757,7 +3059,13 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2757
3059
  for (const nestedColumn of componentSection.section ?? []) {
2758
3060
  this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2759
3061
  }
2760
- if (Object.keys(componentData).length > 0) {
3062
+ let compDate = void 0;
3063
+ if (componentData["effectiveDateTime"]) {
3064
+ compDate = new Date(componentData["effectiveDateTime"]);
3065
+ } else if (componentData["effectivePeriodStart"]) {
3066
+ compDate = new Date(componentData["effectivePeriodStart"]);
3067
+ }
3068
+ if (compDate && compDate >= twoYearsAgo && Object.keys(componentData).length > 0) {
2761
3069
  components.push(componentData);
2762
3070
  }
2763
3071
  }
@@ -2778,6 +3086,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2778
3086
  case "Status":
2779
3087
  data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2780
3088
  break;
3089
+ case "Source":
3090
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3091
+ break;
2781
3092
  default:
2782
3093
  break;
2783
3094
  }
@@ -2785,6 +3096,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2785
3096
  }
2786
3097
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2787
3098
  let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
3099
+ let obsDate = void 0;
3100
+ if (data["effectiveDateTime"]) {
3101
+ obsDate = new Date(data["effectiveDateTime"]);
3102
+ } else if (data["effectivePeriodStart"]) {
3103
+ obsDate = new Date(data["effectivePeriodStart"]);
3104
+ }
2788
3105
  if (!date && data["effectivePeriodStart"]) {
2789
3106
  date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2790
3107
  if (data["effectivePeriodEnd"]) {
@@ -2801,36 +3118,47 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2801
3118
  observationhtml += `
2802
3119
  <tr>
2803
3120
  <td>${componentCode}</td>
2804
- <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
2805
- <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
2806
- <td>${date ?? "-"}</td>
3121
+ <td></td>
3122
+ <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? ""}</td>
3123
+ <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? ""}</td>
3124
+ <td>${date ?? ""}</td>
3125
+ <td>${data["source"] ?? ""}</td>
2807
3126
  </tr>`;
2808
3127
  }
2809
3128
  }
2810
3129
  } else {
2811
- const code = data["code"] ?? "";
2812
- if (code && !observationAdded.has(code)) {
2813
- observationAdded.add(code);
2814
- this.formatSummaryObservationData(data);
2815
- observationhtml += `
3130
+ if (obsDate && obsDate >= twoYearsAgo) {
3131
+ const code = data["code"] ?? "";
3132
+ if (code && !observationAdded.has(code)) {
3133
+ observationAdded.add(code);
3134
+ this.formatSummaryObservationData(data);
3135
+ observationhtml += `
2816
3136
  <tr>
2817
- <td>${data["code"] ?? "-"}</td>
2818
- <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
2819
- <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
2820
- <td>${date ?? "-"}</td>
3137
+ <td>${data["code"] ?? ""}</td>
3138
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
3139
+ <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? ""}</td>
3140
+ <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? ""}</td>
3141
+ <td>${date ?? ""}</td>
3142
+ <td>${data["source"] ?? ""}</td>
2821
3143
  </tr>`;
3144
+ }
2822
3145
  }
2823
3146
  }
2824
3147
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2825
- if (data["status"] === "final") {
3148
+ let issuedDate = void 0;
3149
+ if (data["issued"]) {
3150
+ issuedDate = new Date(data["issued"]);
3151
+ }
3152
+ if (data["status"] === "final" && issuedDate && issuedDate >= twoYearsAgo) {
2826
3153
  const reportName = data["report"] ?? "";
2827
3154
  if (reportName && !diagnosticReportAdded.has(reportName)) {
2828
3155
  diagnosticReportAdded.add(reportName);
2829
3156
  diagnosticReporthtml += `
2830
3157
  <tr>
2831
- <td>${data["report"] ?? "-"}</td>
2832
- <td>${data["performer"] ?? "-"}</td>
2833
- <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
3158
+ <td>${data["report"] ?? ""}</td>
3159
+ <td>${data["performer"] ?? ""}</td>
3160
+ <td>${templateUtilities.renderTime(data["issued"], timezone) ?? ""}</td>
3161
+ <td>${data["source"] ?? ""}</td>
2834
3162
  </tr>`;
2835
3163
  }
2836
3164
  }
@@ -2843,6 +3171,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2843
3171
  </tbody>
2844
3172
  </table>
2845
3173
  </div>`;
3174
+ if (skippedObservations > 0) {
3175
+ html += `
3176
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3177
+ }
2846
3178
  }
2847
3179
  if (diagnosticReportAdded.size > 0) {
2848
3180
  html += diagnosticReporthtml;
@@ -2850,6 +3182,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2850
3182
  </tbody>
2851
3183
  </table>
2852
3184
  </div>`;
3185
+ if (skippedDiagnosticReports > 0) {
3186
+ html += `
3187
+ <p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
3188
+ }
2853
3189
  }
2854
3190
  html += `
2855
3191
  </div>`;
@@ -2859,12 +3195,33 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2859
3195
  * Internal static implementation that actually generates the narrative
2860
3196
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2861
3197
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3198
+ * @param now - Optional current date for filtering
2862
3199
  * @returns HTML string for rendering
2863
3200
  */
2864
- static generateStaticNarrative(resources, timezone) {
3201
+ static generateStaticNarrative(resources, timezone, now) {
2865
3202
  const templateUtilities = new TemplateUtilities(resources);
2866
- let html = "";
2867
- const observations = this.getObservations(resources);
3203
+ const currentDate = now || /* @__PURE__ */ new Date();
3204
+ const twoYearsAgo = new Date(currentDate);
3205
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
3206
+ let html = `<p>This section includes Observations from the last 2 years (one per lab name, using code mapping) and DiagnosticReports with status 'final' issued in the last 2 years. Both are sorted by date (most recent first). Older results are counted and noted below the tables.</p>
3207
+ `;
3208
+ let skippedObservations = 0;
3209
+ let skippedDiagnosticReports = 0;
3210
+ for (const resourceItem of resources) {
3211
+ if (resourceItem.resourceType === "Observation") {
3212
+ const obsDate = this.getObservationDate(resourceItem);
3213
+ if (obsDate && obsDate < twoYearsAgo) {
3214
+ skippedObservations++;
3215
+ }
3216
+ } else if (resourceItem.resourceType === "DiagnosticReport") {
3217
+ const issued = resourceItem.issued;
3218
+ const status = resourceItem.status;
3219
+ if (status === "final" && issued && new Date(issued) < twoYearsAgo) {
3220
+ skippedDiagnosticReports++;
3221
+ }
3222
+ }
3223
+ }
3224
+ const observations = this.getObservations(resources, twoYearsAgo);
2868
3225
  if (observations.length > 0) {
2869
3226
  observations.sort((a, b) => {
2870
3227
  const dateA = this.getObservationDate(a);
@@ -2873,16 +3230,22 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2873
3230
  });
2874
3231
  this.filterObservationForLoincCodes(observations);
2875
3232
  html += this.renderObservations(templateUtilities, observations, timezone);
3233
+ if (skippedObservations > 0) {
3234
+ html += `
3235
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3236
+ }
2876
3237
  }
2877
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2878
- const diagnosticReports = this.getDiagnosticReports(resources);
2879
- if (diagnosticReports.length > 0) {
2880
- diagnosticReports.sort((a, b) => {
2881
- const dateA = a.issued;
2882
- const dateB = b.issued;
2883
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2884
- });
2885
- html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
3238
+ const diagnosticReports = this.getDiagnosticReports(resources, twoYearsAgo).filter((resource) => !this.isPanelDiagnosticReport(resource));
3239
+ if (diagnosticReports.length > 0) {
3240
+ diagnosticReports.sort((a, b) => {
3241
+ const dateA = a.issued;
3242
+ const dateB = b.issued;
3243
+ return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3244
+ });
3245
+ html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
3246
+ if (skippedDiagnosticReports > 0) {
3247
+ html += `
3248
+ <p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
2886
3249
  }
2887
3250
  }
2888
3251
  return html;
@@ -2927,15 +3290,16 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2927
3290
  return obsDate;
2928
3291
  }
2929
3292
  /**
2930
- * Get all Observation resources from the resource array
2931
- * @param resources - FHIR resources array
2932
- * @returns Array of Observation resources
2933
- */
2934
- static getObservations(resources) {
3293
+ * Get all Observation resources from the resource array, filtered by twoYearsAgo
3294
+ * @param resources - FHIR resources array
3295
+ * @param twoYearsAgo - Date object representing the cutoff
3296
+ * @returns Array of Observation resources
3297
+ */
3298
+ static getObservations(resources, twoYearsAgo) {
2935
3299
  return resources.filter((resourceItem) => {
2936
3300
  if (resourceItem.resourceType === "Observation") {
2937
3301
  const obsDate = this.getObservationDate(resourceItem);
2938
- if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
3302
+ if (obsDate && obsDate >= twoYearsAgo) {
2939
3303
  return true;
2940
3304
  }
2941
3305
  }
@@ -2943,12 +3307,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2943
3307
  }).map((resourceItem) => resourceItem);
2944
3308
  }
2945
3309
  /**
2946
- * Get all DiagnosticReport resources from the resource array
3310
+ * Get all DiagnosticReport resources from the resource array, filtered by twoYearsAgo
2947
3311
  * @param resources - FHIR resources array
3312
+ * @param twoYearsAgo - Date object representing the cutoff
2948
3313
  * @returns Array of DiagnosticReport resources
2949
3314
  */
2950
- static getDiagnosticReports(resources) {
2951
- return resources.filter((resourceItem) => resourceItem.resourceType === "DiagnosticReport").map((resourceItem) => resourceItem);
3315
+ static getDiagnosticReports(resources, twoYearsAgo) {
3316
+ return resources.filter((resourceItem) => {
3317
+ if (resourceItem.resourceType === "DiagnosticReport") {
3318
+ const issued = resourceItem.issued;
3319
+ if (issued && new Date(issued) >= twoYearsAgo) {
3320
+ return true;
3321
+ }
3322
+ }
3323
+ return false;
3324
+ }).map((resourceItem) => resourceItem);
2952
3325
  }
2953
3326
  /**
2954
3327
  * Render HTML table for Observation resources
@@ -2959,32 +3332,36 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2959
3332
  */
2960
3333
  static renderObservations(templateUtilities, observations, timezone) {
2961
3334
  let html = "";
2962
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2963
- html += `
2964
- <h3>Observations</h3>`;
2965
- }
3335
+ html += `
3336
+ <h3>Observations</h3>`;
2966
3337
  html += `
2967
3338
  <table>
2968
3339
  <thead>
2969
3340
  <tr>
2970
- <th>Code</th>
3341
+ <th>Name</th>
3342
+ <th>Code (System)</th>
2971
3343
  <th>Result</th>
2972
3344
  <th>Reference Range</th>
2973
3345
  <th>Date</th>
3346
+ <th>Source</th>
2974
3347
  </tr>
2975
3348
  </thead>
2976
3349
  <tbody>`;
2977
3350
  const observationAdded = /* @__PURE__ */ new Set();
2978
3351
  for (const obs of observations) {
2979
- const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
2980
- if (!observationAdded.has(obsCode)) {
2981
- observationAdded.add(obsCode);
3352
+ const obsCodeDisplay = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code));
3353
+ const obsCodeAndSystem = templateUtilities.codeableConceptCoding(obs.code);
3354
+ if (!observationAdded.has(obsCodeDisplay) && !observationAdded.has(obsCodeAndSystem)) {
3355
+ observationAdded.add(obsCodeDisplay);
3356
+ observationAdded.add(obsCodeAndSystem);
2982
3357
  html += `
2983
3358
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2984
- <td>${obsCode}</td>
3359
+ <td>${obsCodeDisplay}</td>
3360
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2985
3361
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2986
3362
  <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
2987
3363
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3364
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2988
3365
  </tr>`;
2989
3366
  }
2990
3367
  }
@@ -3007,17 +3384,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3007
3384
  <thead>
3008
3385
  <tr>
3009
3386
  <th>Report</th>
3387
+ <th>Code (System)</th>
3010
3388
  <th>Category</th>
3011
3389
  <th>Result</th>
3012
3390
  <th>Issued</th>
3391
+ <th>Source</th>
3013
3392
  </tr>
3014
3393
  </thead>
3015
3394
  <tbody>`;
3016
3395
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
3017
3396
  for (const report of reports) {
3018
- const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
3019
- if (!diagnosticReportAdded.has(reportName)) {
3397
+ const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(report.code));
3398
+ const codeAndSystem = templateUtilities.codeableConceptCoding(report.code);
3399
+ if (!diagnosticReportAdded.has(reportName) && !diagnosticReportAdded.has(codeAndSystem)) {
3020
3400
  diagnosticReportAdded.add(reportName);
3401
+ diagnosticReportAdded.add(codeAndSystem);
3021
3402
  let resultCount = "";
3022
3403
  if (report.result && Array.isArray(report.result)) {
3023
3404
  resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
@@ -3025,9 +3406,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3025
3406
  html += `
3026
3407
  <tr id="${templateUtilities.narrativeLinkId(report)}">
3027
3408
  <td>${reportName}</td>
3409
+ <td>${codeAndSystem}</td>
3028
3410
  <td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
3029
3411
  <td>${resultCount}</td>
3030
3412
  <td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
3413
+ <td>${templateUtilities.getOwnerTag(report)}</td>
3031
3414
  </tr>`;
3032
3415
  }
3033
3416
  }
@@ -3036,6 +3419,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3036
3419
  </table>`;
3037
3420
  return html;
3038
3421
  }
3422
+ /**
3423
+ * Helper to determine if a DiagnosticReport is just a panel (i.e., only references Observations, has no conclusion, and code is a known panel code)
3424
+ * @returns true if the report is just a panel
3425
+ */
3426
+ static isPanelDiagnosticReport(report) {
3427
+ return this.hasEssentialLabPanelLoinc(
3428
+ report
3429
+ );
3430
+ }
3431
+ /**
3432
+ * Check if a DiagnosticReport or Observation has a LOINC code in ESSENTIAL_LAB_PANELS
3433
+ * @param resource - DiagnosticReport or Observation
3434
+ * @returns true if any LOINC code is in ESSENTIAL_LAB_PANELS, false otherwise
3435
+ */
3436
+ static hasEssentialLabPanelLoinc(resource) {
3437
+ const codings = resource?.code?.coding ?? [];
3438
+ return codings.some(
3439
+ (coding) => coding && coding.system && coding.code && coding.system.toLowerCase().includes("loinc") && Object.keys(ESSENTIAL_LAB_PANELS).includes(coding.code)
3440
+ );
3441
+ }
3039
3442
  };
3040
3443
 
3041
3444
  // src/narratives/templates/typescript/HistoryOfProceduresTemplate.ts
@@ -3063,20 +3466,26 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3063
3466
  generateSummaryNarrative(resources, timezone) {
3064
3467
  const templateUtilities = new TemplateUtilities(resources);
3065
3468
  let isSummaryCreated = false;
3066
- let html = `
3469
+ let html = `<p>This list includes all Procedure resources, sorted by performed date (most recent first).</p>
3470
+ `;
3471
+ html += `
3067
3472
  <div>
3068
3473
  <table>
3069
3474
  <thead>
3070
3475
  <tr>
3071
3476
  <th>Procedure</th>
3477
+ <th>Code (System)</th>
3072
3478
  <th>Performer</th>
3073
3479
  <th>Date</th>
3480
+ <th>Source</th>
3074
3481
  </tr>
3075
3482
  </thead>
3076
3483
  <tbody>`;
3077
3484
  for (const resourceItem of resources) {
3078
3485
  for (const rowData of resourceItem.section ?? []) {
3486
+ const sectionCodeableConcept = rowData.code;
3079
3487
  const data = {};
3488
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
3080
3489
  for (const columnData of rowData.section ?? []) {
3081
3490
  switch (columnData.title) {
3082
3491
  case "Procedure Name":
@@ -3088,6 +3497,9 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3088
3497
  case "Performed Date":
3089
3498
  data["date"] = columnData.text?.div ?? "";
3090
3499
  break;
3500
+ case "Source":
3501
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3502
+ break;
3091
3503
  default:
3092
3504
  break;
3093
3505
  }
@@ -3095,9 +3507,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3095
3507
  isSummaryCreated = true;
3096
3508
  html += `
3097
3509
  <tr>
3098
- <td>${data["procedure"] ?? "-"}</td>
3099
- <td>${data["performer"] ?? "-"}</td>
3100
- <td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
3510
+ <td>${data["procedure"] ?? ""}</td>
3511
+ <td>${data["codeSystem"] ?? ""}</td>
3512
+ <td>${data["performer"] ?? ""}</td>
3513
+ <td>${templateUtilities.renderTime(data["date"], timezone) ?? ""}</td>
3514
+ <td>${data["source"] ?? ""}</td>
3101
3515
  </tr>`;
3102
3516
  }
3103
3517
  }
@@ -3115,23 +3529,29 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3115
3529
  */
3116
3530
  static generateStaticNarrative(resources, timezone) {
3117
3531
  const templateUtilities = new TemplateUtilities(resources);
3118
- let html = `
3532
+ let html = `<p>This list includes all Procedure resources, sorted by performed date (most recent first).</p>
3533
+ `;
3534
+ html += `
3119
3535
  <table>
3120
3536
  <thead>
3121
3537
  <tr>
3122
3538
  <th>Procedure</th>
3539
+ <th>Code (System)</th>
3123
3540
  <th>Comments</th>
3124
3541
  <th>Date</th>
3542
+ <th>Source</th>
3125
3543
  </tr>
3126
3544
  </thead>
3127
3545
  <tbody>`;
3128
3546
  for (const resourceItem of resources) {
3129
3547
  const proc = resourceItem;
3130
3548
  html += `
3131
- <tr id="${templateUtilities.narrativeLinkId(proc)}">
3132
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
3549
+ <tr>
3550
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(proc.code, "display"))}</td>
3551
+ <td>${templateUtilities.codeableConceptCoding(proc.code)}</td>
3133
3552
  <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
3134
- <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
3553
+ <td>${templateUtilities.renderTime(proc.performedDateTime || proc.performedPeriod?.start, timezone)}</td>
3554
+ <td>${templateUtilities.getOwnerTag(proc)}</td>
3135
3555
  </tr>`;
3136
3556
  }
3137
3557
  html += `
@@ -3166,26 +3586,32 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3166
3586
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3167
3587
  return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3168
3588
  });
3169
- let html = `
3589
+ let html = `<p>This list includes all information about the patient's social history, sorted by effective date (most recent first).</p>
3590
+ `;
3591
+ html += `
3170
3592
  <table>
3171
3593
  <thead>
3172
3594
  <tr>
3173
- <th>Code</th>
3595
+ <th>Name</th>
3596
+ <th>Code (System)</th>
3174
3597
  <th>Result</th>
3175
3598
  <th>Unit</th>
3176
3599
  <th>Comments</th>
3177
3600
  <th>Date</th>
3601
+ <th>Source</th>
3178
3602
  </tr>
3179
3603
  </thead>
3180
3604
  <tbody>`;
3181
3605
  for (const obs of observations) {
3182
3606
  html += `
3183
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
3184
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
3607
+ <tr>
3608
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code))}</td>
3609
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
3185
3610
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3186
3611
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
3187
3612
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3188
3613
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3614
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
3189
3615
  </tr>`;
3190
3616
  }
3191
3617
  html += `
@@ -3201,14 +3627,27 @@ var PastHistoryOfIllnessTemplate = class {
3201
3627
  * Generate HTML narrative for Past History of Illnesses
3202
3628
  * @param resources - FHIR Condition resources
3203
3629
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3630
+ * @param now - Optional current date to use for generating relative dates in the narrative
3204
3631
  * @returns HTML string for rendering
3205
3632
  */
3206
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3207
- generateNarrative(resources, timezone) {
3633
+ generateNarrative(resources, timezone, now) {
3208
3634
  const templateUtilities = new TemplateUtilities(resources);
3209
- let html = ``;
3635
+ let html = `<p>This list includes past problems for the patient with a recorded date within the last 5 years, sorted by recorded date (most recent first).</p>
3636
+ `;
3210
3637
  const resolvedConditions = resources.map((entry) => entry) || [];
3211
- resolvedConditions.sort((a, b) => {
3638
+ const currentDate = now || /* @__PURE__ */ new Date();
3639
+ const fiveYearsAgo = new Date(currentDate);
3640
+ fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
3641
+ let skippedConditions = 0;
3642
+ const filteredConditions = [];
3643
+ for (const cond of resolvedConditions) {
3644
+ if (cond.recordedDate && new Date(cond.recordedDate) >= fiveYearsAgo) {
3645
+ filteredConditions.push(cond);
3646
+ } else {
3647
+ skippedConditions++;
3648
+ }
3649
+ }
3650
+ filteredConditions.sort((a, b) => {
3212
3651
  const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3213
3652
  const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3214
3653
  return dateB - dateA;
@@ -3218,27 +3657,35 @@ var PastHistoryOfIllnessTemplate = class {
3218
3657
  <thead>
3219
3658
  <tr>
3220
3659
  <th>Problem</th>
3660
+ <th>Code (System)</th>
3221
3661
  <th>Onset Date</th>
3222
3662
  <th>Recorded Date</th>
3223
3663
  <th>Resolved Date</th>
3664
+ <th>Source</th>
3224
3665
  </tr>
3225
3666
  </thead>
3226
3667
  <tbody>`;
3227
3668
  const addedConditionCodes = /* @__PURE__ */ new Set();
3228
- for (const cond of resolvedConditions) {
3229
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3669
+ for (const cond of filteredConditions) {
3670
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(cond.code));
3230
3671
  if (!addedConditionCodes.has(conditionCode)) {
3231
3672
  addedConditionCodes.add(conditionCode);
3232
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3673
+ html += `<tr>
3233
3674
  <td class="Name">${conditionCode}</td>
3675
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(cond.code)}</td>
3234
3676
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3235
3677
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3236
3678
  <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
3679
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
3237
3680
  </tr>`;
3238
3681
  }
3239
3682
  }
3240
3683
  html += `</tbody>
3241
3684
  </table>`;
3685
+ if (skippedConditions > 0) {
3686
+ html += `
3687
+ <p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
3688
+ }
3242
3689
  return html;
3243
3690
  }
3244
3691
  };
@@ -3259,7 +3706,9 @@ var PlanOfCareTemplate = class {
3259
3706
  const endB = b.period?.end ? new Date(b.period?.end).getTime() : 0;
3260
3707
  return endB - endA;
3261
3708
  });
3262
- let html = `
3709
+ let html = `<p>This list includes all CarePlan resources, sorted by planned end date (most recent first).</p>
3710
+ `;
3711
+ html += `
3263
3712
  <table>
3264
3713
  <thead>
3265
3714
  <tr>
@@ -3268,6 +3717,7 @@ var PlanOfCareTemplate = class {
3268
3717
  <th>Comments</th>
3269
3718
  <th>Planned Start</th>
3270
3719
  <th>Planned End</th>
3720
+ <th>Source</th>
3271
3721
  </tr>
3272
3722
  </thead>
3273
3723
  <tbody>`;
@@ -3279,6 +3729,7 @@ var PlanOfCareTemplate = class {
3279
3729
  <td>${templateUtilities.concat(cp.note, "text")}</td>
3280
3730
  <td>${cp.period?.start ? templateUtilities.renderTime(cp.period?.start, timezone) : ""}</td>
3281
3731
  <td>${cp.period?.end ? templateUtilities.renderTime(cp.period?.end, timezone) : ""}</td>
3732
+ <td>${templateUtilities.getOwnerTag(cp)}</td>
3282
3733
  </tr>`;
3283
3734
  }
3284
3735
  html += `
@@ -3295,7 +3746,9 @@ var PlanOfCareTemplate = class {
3295
3746
  generateSummaryNarrative(resources, timezone) {
3296
3747
  const templateUtilities = new TemplateUtilities(resources);
3297
3748
  let isSummaryCreated = false;
3298
- let html = `
3749
+ let html = `<p>This list includes all CarePlan resources, sorted by planned end date (most recent first).</p>
3750
+ `;
3751
+ html += `
3299
3752
  <div>
3300
3753
  <table>
3301
3754
  <thead>
@@ -3304,6 +3757,7 @@ var PlanOfCareTemplate = class {
3304
3757
  <th>Created</th>
3305
3758
  <th>Planned Start</th>
3306
3759
  <th>Planned End</th>
3760
+ <th>Source</th>
3307
3761
  </tr>
3308
3762
  </thead>
3309
3763
  <tbody>`;
@@ -3321,10 +3775,11 @@ var PlanOfCareTemplate = class {
3321
3775
  isSummaryCreated = true;
3322
3776
  html += `
3323
3777
  <tr>
3324
- <td>${data["CarePlan Name"] ?? "-"}</td>
3325
- <td>${templateUtilities.renderTime(data["created"], timezone) ?? "-"}</td>
3326
- <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? "-"}</td>
3327
- <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? "-"}</td>
3778
+ <td>${data["CarePlan Name"] ?? ""}</td>
3779
+ <td>${templateUtilities.renderTime(data["created"], timezone) ?? ""}</td>
3780
+ <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? ""}</td>
3781
+ <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? ""}</td>
3782
+ <td>${data["source"] ?? ""}</td>
3328
3783
  </tr>`;
3329
3784
  }
3330
3785
  }
@@ -3355,77 +3810,54 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3355
3810
  */
3356
3811
  static generateStaticNarrative(resources, timezone) {
3357
3812
  const templateUtilities = new TemplateUtilities(resources);
3358
- let html = ``;
3359
- const activeConditions = [];
3360
- const clinicalImpressions = [];
3361
- for (const resourceItem of resources) {
3362
- if (resourceItem.resourceType === "Condition") {
3363
- activeConditions.push(resourceItem);
3364
- } else if (resourceItem.resourceType === "ClinicalImpression") {
3365
- clinicalImpressions.push(resourceItem);
3366
- }
3367
- }
3368
- activeConditions.sort((a, b) => {
3369
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3370
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3371
- return dateB - dateA;
3813
+ let html = `<p>This section summarizes key observations and assessments related to the person's functional status and ability to perform daily activities.</p>`;
3814
+ let functionalObservations = resources.filter((r) => r.resourceType === "Observation").filter((r) => {
3815
+ const hasFunctionalLoinc = r.code?.coding?.some(
3816
+ (c) => c.system?.toLowerCase().includes("loinc") && c.code === "47420-5"
3817
+ );
3818
+ const hasFunctionalCategory = r.category?.some(
3819
+ (cat) => cat.coding?.some(
3820
+ (c) => c.code === "functional-status" || c.display?.toLowerCase().includes("functional")
3821
+ )
3822
+ );
3823
+ return hasFunctionalLoinc || hasFunctionalCategory;
3372
3824
  });
3373
- clinicalImpressions.sort((a, b) => {
3374
- const dateA = a.effectiveDateTime ? new Date(a.effectiveDateTime).getTime() : a.effectivePeriod?.start ? new Date(a.effectivePeriod.start).getTime() : a.date ? new Date(a.date).getTime() : 0;
3375
- const dateB = b.effectiveDateTime ? new Date(b.effectiveDateTime).getTime() : b.effectivePeriod?.start ? new Date(b.effectivePeriod.start).getTime() : b.date ? new Date(b.date).getTime() : 0;
3376
- return dateB - dateA;
3825
+ functionalObservations = functionalObservations.sort((a, b) => {
3826
+ const getObsDate = (obs) => obs.effectiveDateTime ? new Date(obs.effectiveDateTime).getTime() : obs.issued ? new Date(obs.issued).getTime() : 0;
3827
+ return getObsDate(b) - getObsDate(a);
3377
3828
  });
3378
- if (activeConditions.length > 0) {
3379
- html += `<h3>Conditions</h3>
3380
- <table>
3381
- <thead>
3382
- <tr>
3383
- <th>Problem</th>
3384
- <th>Onset Date</th>
3385
- <th>Recorded Date</th>
3386
- </tr>
3387
- </thead>
3388
- <tbody>`;
3389
- const addedConditionCodes = /* @__PURE__ */ new Set();
3390
- for (const cond of activeConditions) {
3391
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3392
- if (!addedConditionCodes.has(conditionCode)) {
3393
- addedConditionCodes.add(conditionCode);
3394
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3395
- <td class="Name">${conditionCode}</td>
3396
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3397
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3398
- </tr>`;
3399
- }
3829
+ let clinicalImpressions = resources.filter((r) => r.resourceType === "ClinicalImpression").filter((r) => r.status === "completed");
3830
+ clinicalImpressions = clinicalImpressions.sort((a, b) => {
3831
+ 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;
3832
+ return getImpressionDate(b) - getImpressionDate(a);
3833
+ });
3834
+ if (functionalObservations.length > 0) {
3835
+ html += `<table><thead><tr><th>Observation</th><th>Value</th><th>Date</th><th>Interpretation</th><th>Comments</th></tr></thead><tbody>`;
3836
+ for (const obs of functionalObservations) {
3837
+ const observation = obs;
3838
+ const obsName = templateUtilities.codeableConceptDisplay(observation.code);
3839
+ const value = templateUtilities.extractObservationValue(observation);
3840
+ const date = observation.effectiveDateTime ? templateUtilities.renderDate(observation.effectiveDateTime) : observation.issued ? templateUtilities.renderDate(observation.issued) : "";
3841
+ const interpretation = observation.interpretation ? templateUtilities.codeableConceptDisplay(observation.interpretation[0]) : "";
3842
+ const comments = observation.comment || observation.note?.map((n) => n.text).join("; ") || "";
3843
+ html += `<tr id="${templateUtilities.narrativeLinkId(observation)}">
3844
+ <td>${obsName}</td>
3845
+ <td>${value ?? ""}</td>
3846
+ <td>${date}</td>
3847
+ <td>${interpretation}</td>
3848
+ <td>${comments}</td>
3849
+ </tr>`;
3400
3850
  }
3401
- html += `</tbody>
3402
- </table>`;
3851
+ html += `</tbody></table>`;
3403
3852
  }
3404
3853
  if (clinicalImpressions.length > 0) {
3405
- html += `<h3>Clinical Impressions</h3>
3406
- <table>
3407
- <thead>
3408
- <tr>
3409
- <th>Date</th>
3410
- <th>Status</th>
3411
- <th>Description</th>
3412
- <th>Summary</th>
3413
- <th>Findings</th>
3414
- </tr>
3415
- </thead>
3416
- <tbody>`;
3854
+ html += `<table><thead><tr><th>Date</th><th>Status</th><th>Description</th><th>Summary</th><th>Findings</th></tr></thead><tbody>`;
3417
3855
  for (const impression of clinicalImpressions) {
3418
3856
  let formattedDate = "";
3419
3857
  if (impression.effectiveDateTime) {
3420
- formattedDate = templateUtilities.renderTime(
3421
- impression.effectiveDateTime,
3422
- timezone
3423
- );
3858
+ formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
3424
3859
  } else if (impression.effectivePeriod) {
3425
- formattedDate = templateUtilities.renderPeriod(
3426
- impression.effectivePeriod,
3427
- timezone
3428
- );
3860
+ formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
3429
3861
  } else if (impression.date) {
3430
3862
  formattedDate = templateUtilities.renderDate(impression.date);
3431
3863
  }
@@ -3433,23 +3865,24 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3433
3865
  if (impression.finding && impression.finding.length > 0) {
3434
3866
  findingsHtml = "<ul>";
3435
3867
  for (const finding of impression.finding) {
3436
- const findingText = finding.itemCodeableConcept ? templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(finding.itemCodeableConcept)) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3868
+ const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3437
3869
  const cause = finding.basis || "";
3438
3870
  findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
3439
3871
  }
3440
3872
  findingsHtml += "</ul>";
3441
3873
  }
3442
- html += `
3443
- <tr id="${templateUtilities.narrativeLinkId(impression)}">
3444
- <td>${formattedDate}</td>
3445
- <td>${impression.status || ""}</td>
3446
- <td>${impression.description || ""}</td>
3447
- <td>${impression.summary || ""}</td>
3448
- <td>${findingsHtml}</td>
3449
- </tr>`;
3874
+ html += `<tr id="${templateUtilities.narrativeLinkId(impression)}">
3875
+ <td>${formattedDate}</td>
3876
+ <td>${impression.status || ""}</td>
3877
+ <td>${impression.description || ""}</td>
3878
+ <td>${impression.summary || ""}</td>
3879
+ <td>${findingsHtml}</td>
3880
+ </tr>`;
3450
3881
  }
3451
- html += `</tbody>
3452
- </table>`;
3882
+ html += `</tbody></table>`;
3883
+ }
3884
+ if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
3885
+ html += `<p>No functional status information available.</p>`;
3453
3886
  }
3454
3887
  return html;
3455
3888
  }
@@ -3474,34 +3907,110 @@ var PregnancyTemplate = class _PregnancyTemplate {
3474
3907
  */
3475
3908
  static generateStaticNarrative(resources, timezone) {
3476
3909
  const templateUtilities = new TemplateUtilities(resources);
3477
- const observations = resources.map((entry) => entry) || [];
3478
- observations.sort((a, b) => {
3910
+ const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
3911
+ const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
3912
+ const observations = filteredResources.filter((r) => r.resourceType === "Observation");
3913
+ const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
3914
+ const EDD_LOINC = "11778-8";
3915
+ const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
3916
+ const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
3917
+ const pregnancyStatusObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code && pregnancyStatusCodes.includes(c.code))).sort((a, b) => {
3479
3918
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3480
3919
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3481
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3920
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3921
+ })[0];
3922
+ const eddObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code === EDD_LOINC)).sort((a, b) => {
3923
+ const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3924
+ const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3925
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3926
+ })[0];
3927
+ const historyObs = observations.filter(
3928
+ (obs) => obs.code?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code)) || obs.valueCodeableConcept?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code))
3929
+ ).sort((a, b) => {
3930
+ const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3931
+ const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3932
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3482
3933
  });
3483
- let html = `
3484
- <table>
3485
- <thead>
3486
- <tr>
3487
- <th>Result</th>
3488
- <th>Comments</th>
3489
- <th>Date</th>
3490
- </tr>
3491
- </thead>
3492
- <tbody>`;
3493
- for (const resource of observations) {
3494
- const obs = resource;
3934
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
3935
+ return `<p>No history of pregnancy found.</p>`;
3936
+ }
3937
+ let html = `<p>This list includes Observation and Condition resources relevant to pregnancy, sorted by date (most recent first).</p>`;
3938
+ html += `
3939
+ <table>
3940
+ <thead>
3941
+ <tr>
3942
+ <th>Result</th>
3943
+ <th>Code (System)</th>
3944
+ <th>Comments</th>
3945
+ <th>Date</th>
3946
+ <th>Source</th>
3947
+ </tr>
3948
+ </thead>
3949
+ <tbody>`;
3950
+ function renderRow({ id, result, comments, date, codeSystem, owner }) {
3495
3951
  html += `
3496
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
3497
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
3498
- <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3499
- <td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
3500
- </tr>`;
3952
+ <tr id="${id}">
3953
+ <td class="Result">${result}</td>
3954
+ <td class="CodeSystem">${codeSystem}</td>
3955
+ <td class="Comments">${comments}</td>
3956
+ <td class="Date">${date}</td>
3957
+ <td class="Source">${owner}</td>
3958
+ </tr>`;
3959
+ }
3960
+ const rowResources = [];
3961
+ if (pregnancyStatusObs) {
3962
+ const date = pregnancyStatusObs.effectiveDateTime || pregnancyStatusObs.effectivePeriod?.start;
3963
+ rowResources.push({ resource: pregnancyStatusObs, date, type: "status" });
3964
+ }
3965
+ if (eddObs) {
3966
+ const date = eddObs.effectiveDateTime || eddObs.effectivePeriod?.start;
3967
+ rowResources.push({ resource: eddObs, date, type: "edd" });
3968
+ }
3969
+ for (const obs of historyObs) {
3970
+ const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
3971
+ rowResources.push({ resource: obs, date, type: "history" });
3972
+ }
3973
+ for (const cond of conditions) {
3974
+ const condition = cond;
3975
+ const date = condition.onsetDateTime || condition.onsetPeriod?.start;
3976
+ rowResources.push({ resource: condition, date, type: "condition" });
3977
+ }
3978
+ rowResources.sort((a, b) => {
3979
+ if (!a.date && !b.date) return 0;
3980
+ if (!a.date) return 1;
3981
+ if (!b.date) return -1;
3982
+ return new Date(b.date).getTime() - new Date(a.date).getTime();
3983
+ });
3984
+ for (const { resource, date, type } of rowResources) {
3985
+ let result = "", comments = "", dateStr = "", codeSystem = "";
3986
+ const id = templateUtilities.narrativeLinkId(resource);
3987
+ if (type === "status") {
3988
+ result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
3989
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3990
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3991
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3992
+ } else if (type === "edd") {
3993
+ result = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
3994
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3995
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3996
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3997
+ } else if (type === "history") {
3998
+ result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
3999
+ comments = templateUtilities.renderNotes(resource.note, timezone);
4000
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
4001
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
4002
+ } else if (type === "condition") {
4003
+ result = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(resource.code));
4004
+ comments = templateUtilities.renderNotes(resource.note, timezone);
4005
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
4006
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
4007
+ }
4008
+ const owner = templateUtilities.getOwnerTag(resource);
4009
+ renderRow({ id, result, comments, date: dateStr, codeSystem, owner });
3501
4010
  }
3502
4011
  html += `
3503
- </tbody>
3504
- </table>`;
4012
+ </tbody>
4013
+ </table>`;
3505
4014
  return html;
3506
4015
  }
3507
4016
  };
@@ -3531,8 +4040,9 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3531
4040
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
3532
4041
  static generateStaticNarrative(resources, timezone) {
3533
4042
  const templateUtilities = new TemplateUtilities(resources);
3534
- let html = `
3535
- <table>
4043
+ let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
4044
+ `;
4045
+ html += `<table>
3536
4046
  <thead>
3537
4047
  <tr>
3538
4048
  <th>Scope</th>
@@ -3545,8 +4055,8 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3545
4055
  for (const resourceItem of resources) {
3546
4056
  const consent = resourceItem;
3547
4057
  html += `
3548
- <tr id="${templateUtilities.narrativeLinkId(consent)}">
3549
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
4058
+ <tr>
4059
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display"))}</td>
3550
4060
  <td>${consent.status || ""}</td>
3551
4061
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
3552
4062
  <td>${consent.dateTime || ""}</td>
@@ -3579,7 +4089,7 @@ var TypeScriptTemplateMapper = class {
3579
4089
  resources,
3580
4090
  timezone,
3581
4091
  now
3582
- ) : templateClass.generateNarrative(resources, timezone);
4092
+ ) : templateClass.generateNarrative(resources, timezone, now);
3583
4093
  }
3584
4094
  };
3585
4095
  // Map of section types to their template classes