@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.js CHANGED
@@ -71,6 +71,13 @@ var PREGNANCY_LOINC_CODES = {
71
71
  "33065-4": "Ectopic Pregnancy"
72
72
  }
73
73
  };
74
+ var PREGNANCY_SNOMED_CODES = [
75
+ // Add SNOMED CT codes for pregnancy history here, e.g.:
76
+ "72892002",
77
+ // Pregnancy (finding)
78
+ "77386006"
79
+ // History of pregnancy (situation)
80
+ ];
74
81
  var SOCIAL_HISTORY_LOINC_CODES = {
75
82
  "72166-2": "Tobacco Use",
76
83
  "74013-4": "Alcohol Use"
@@ -80,6 +87,29 @@ var BLOOD_PRESSURE_LOINC_CODES = {
80
87
  SYSTOLIC: "8480-6",
81
88
  DIASTOLIC: "8462-4"
82
89
  };
90
+ var ESSENTIAL_LAB_PANELS = {
91
+ // Top 20 Most Ordered Panels
92
+ "24323-8": "Comprehensive Metabolic Panel (CMP)",
93
+ "24320-4": "Basic Metabolic Panel (BMP)",
94
+ "58410-2": "Complete Blood Count (CBC)",
95
+ "57021-8": "CBC with Differential",
96
+ "24331-1": "Lipid Panel",
97
+ "57698-3": "Lipid Panel with Direct LDL",
98
+ "24325-3": "Hepatic Function Panel",
99
+ "24362-6": "Renal Function Panel",
100
+ "24326-1": "Electrolyte Panel",
101
+ "24348-5": "Thyroid Panel",
102
+ "24356-8": "Urinalysis Complete",
103
+ "24352-7": "Iron Studies Panel",
104
+ "34714-6": "Coagulation Panel",
105
+ "24364-2": "Prenatal Panel",
106
+ "24108-3": "Acute Hepatitis Panel",
107
+ "24110-9": "Hepatitis B Panel",
108
+ "34574-4": "Arthritis Panel",
109
+ "24360-0": "Anemia Panel",
110
+ "80235-8": "Cardiac Markers Panel",
111
+ "69738-3": "CBC with Auto Differential"
112
+ };
83
113
 
84
114
  // src/structures/ips_section_constants.ts
85
115
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -104,34 +134,39 @@ var IPSSectionResourceFilters = {
104
134
  // Only include completed immunizations
105
135
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
106
136
  // Only include vital sign Observations (category.coding contains 'vital-signs')
107
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c.code === "vital-signs")),
137
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, "vital-signs", c.system))),
108
138
  // Includes DeviceUseStatement. Device is needed for linked device details
109
139
  ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
110
- // Only include finalized diagnostic reports
111
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
140
+ // Only include finalized diagnostic reports and relevant observations
141
+ ["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))),
112
142
  // Only include completed procedures
113
143
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
114
144
  // Only include social history Observations
115
- ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code?.coding?.some((c) => Object.keys(SOCIAL_HISTORY_LOINC_CODES).includes(c.code)),
116
- // Only include pregnancy history Observations
117
- ["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))),
118
- // Only include Conditions or completed ClinicalImpressions
119
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
145
+ ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
146
+ // Only include pregnancy history Observations or relevant Conditions
147
+ ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "Condition" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")),
148
+ // Only include Observations with LOINC 47420-5, category 'functional-status', or category display containing 'functional', and completed ClinicalImpressions
149
+ ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, "47420-5", "http://loinc.org") || resource.category?.some(
150
+ (cat) => cat.coding?.some(
151
+ (c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
152
+ )
153
+ )) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
120
154
  // Only include resolved medical history Conditions
121
155
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
122
156
  // Only include active care plans
123
157
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
124
158
  // Only include active advance directives (Consent resources)
125
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
159
+ // TODO: disable this until we right logic to get these
160
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
126
161
  };
127
162
  var IPSSectionSummaryCompositionFilter = {
128
- ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
129
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
130
- ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
131
- ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
132
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
163
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
164
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
165
+ ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
166
+ ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
167
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "medication_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
133
168
  // [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)),
134
- ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
169
+ ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "procedure_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
135
170
  };
136
171
  var IPSSectionSummaryIPSCompositionFilter = {
137
172
  ["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")
@@ -152,9 +187,46 @@ var IPSSectionResourceHelper = class {
152
187
  return IPSSectionSummaryIPSCompositionFilter[section];
153
188
  }
154
189
  };
190
+ function codingMatches(coding, code, system) {
191
+ if (!coding || !coding.system) return false;
192
+ if (Array.isArray(code)) {
193
+ return coding.system === system && code.includes(coding.code ?? "");
194
+ }
195
+ return coding.system === system && coding.code === code;
196
+ }
197
+ function codeableConceptMatches(codeableConcept, code, system) {
198
+ if (!codeableConcept || !Array.isArray(codeableConcept.coding)) return false;
199
+ return codeableConcept.coding.some((coding) => codingMatches(coding, code, system));
200
+ }
155
201
 
156
202
  // src/narratives/templates/typescript/TemplateUtilities.ts
157
203
  import { DateTime } from "luxon";
204
+
205
+ // src/structures/codingSystemDisplayNames.ts
206
+ var CODING_SYSTEM_DISPLAY_NAMES = {
207
+ "http://snomed.info/sct": "SNOMED CT",
208
+ "http://loinc.org": "LOINC",
209
+ "http://hl7.org/fhir/sid/icd-10": "ICD-10",
210
+ "http://hl7.org/fhir/sid/icd-10-cm": "ICD-10-CM",
211
+ "http://hl7.org/fhir/sid/icd-9": "ICD-9",
212
+ "http://hl7.org/fhir/sid/cvx": "CVX",
213
+ "http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
214
+ "http://www.ama-assn.org/go/cpt": "CPT",
215
+ "http://unitsofmeasure.org": "UCUM",
216
+ "http://e-imo.com/products/problem-it": "IMO Problem IT",
217
+ "2.16.840.1.113883.6.285": "HCPCS Level II",
218
+ "https://fhir.cerner.com/4ff3b259-e48d-4066-8b35-a6a051f2802a/codeSet/72": "Cerner Code Set 72",
219
+ "http://hl7.org/fhir/sid/ndc": "NDC",
220
+ // Added mapping for NDC
221
+ "urn:oid:2.16.840.1.113883.12.292": "CVX",
222
+ "http://terminology.hl7.org/CodeSystem/data-absent-reason": "Data Absent Reason",
223
+ // Added mapping for Data Absent Reason
224
+ "2.16.840.1.113883.6.208": "NDDF"
225
+ // Add more as needed
226
+ };
227
+ var codingSystemDisplayNames_default = CODING_SYSTEM_DISPLAY_NAMES;
228
+
229
+ // src/narratives/templates/typescript/TemplateUtilities.ts
158
230
  var TemplateUtilities = class {
159
231
  /**
160
232
  * Constructor to initialize the TemplateUtilities with a FHIR resources
@@ -164,47 +236,63 @@ var TemplateUtilities = class {
164
236
  this.resources = resources;
165
237
  }
166
238
  /**
167
- * Formats a CodeableConcept object
168
- * @param cc - The CodeableConcept object
169
- * @param field - Optional specific field to return
170
- * @returns Formatted string representation
239
+ * Returns the preferred coding from a list of codings.
240
+ * If a coding has an extension with url 'https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence' and valueCode 'preferred', returns that coding.
241
+ * Otherwise, returns the first coding if it exists, else null.
242
+ * @param codings Array of coding objects
243
+ * @returns The preferred coding object or null
171
244
  */
172
- codeableConcept(cc, field) {
173
- if (!cc) {
174
- return "";
245
+ getPreferredCoding(codings) {
246
+ if (!Array.isArray(codings) || codings.length === 0) return null;
247
+ for (const coding of codings) {
248
+ if (Array.isArray(coding.extension)) {
249
+ for (const ext of coding.extension) {
250
+ if (ext.url === "https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence" && ext.valueCode === "preferred") {
251
+ return coding;
252
+ }
253
+ }
254
+ }
175
255
  }
256
+ return codings[0] || null;
257
+ }
258
+ /**
259
+ * Returns the display value from a CodeableConcept
260
+ * @param cc - The CodeableConcept object
261
+ * @param field - Optional specific field to extract (e.g., 'text', 'display', 'code')
262
+ * @returns Display string or empty string
263
+ */
264
+ codeableConceptDisplay(cc, field) {
265
+ if (!cc) return "";
176
266
  if (field) {
177
267
  if (cc[field]) {
178
268
  return cc[field];
179
- } else if (cc.coding && cc.coding[0] && cc.coding[0][field]) {
180
- return cc.coding[0][field];
269
+ } else if (cc.coding && cc.coding.length > 0) {
270
+ const preferredCoding = this.getPreferredCoding(cc.coding);
271
+ if (preferredCoding && preferredCoding[field]) {
272
+ return preferredCoding[field];
273
+ }
181
274
  }
182
275
  }
183
- if (cc.text) {
184
- return cc.text;
185
- } else if (cc.coding && cc.coding[0]) {
186
- if (cc.coding[0].display) {
187
- return cc.coding[0].display;
188
- } else if (cc.coding[0].code) {
189
- return cc.coding[0].code;
190
- }
276
+ if (cc.text) return cc.text;
277
+ if (cc.coding && cc.coding.length > 0) {
278
+ const preferredCoding = this.getPreferredCoding(cc.coding);
279
+ if (preferredCoding && preferredCoding.display) return preferredCoding.display;
191
280
  }
192
281
  return "";
193
282
  }
194
- resolveReference(ref) {
195
- if (!ref || !this.resources) {
196
- return null;
197
- }
198
- const referenceParts = ref.reference?.split("/");
199
- if (!referenceParts || referenceParts.length !== 2) {
200
- return null;
201
- }
202
- const referenceResourceType = referenceParts[0];
203
- const referenceResourceId = referenceParts[1];
204
- const resource = this.resources.find((entry) => {
205
- return entry.resourceType === referenceResourceType && entry.id === referenceResourceId;
206
- });
207
- return resource ? resource : null;
283
+ /**
284
+ * Returns the code and system from a CodeableConcept
285
+ * @param cc - The CodeableConcept object
286
+ * @returns Object with code and system, or empty strings if not present
287
+ */
288
+ codeableConceptCoding(cc) {
289
+ if (!cc || !cc.coding || !cc.coding.length) return "";
290
+ const preferredCoding = this.getPreferredCoding(cc.coding);
291
+ if (!preferredCoding) return "";
292
+ const code = preferredCoding.code || "";
293
+ const system = preferredCoding.system || "";
294
+ const systemDisplay = codingSystemDisplayNames_default[system] || system;
295
+ return code ? `${code} (${systemDisplay})` : "";
208
296
  }
209
297
  /**
210
298
  * Renders a Device reference
@@ -252,7 +340,7 @@ var TemplateUtilities = class {
252
340
  return "";
253
341
  }
254
342
  if (medicationType.medicationCodeableConcept) {
255
- return this.codeableConcept(medicationType.medicationCodeableConcept);
343
+ return this.codeableConceptDisplay(medicationType.medicationCodeableConcept);
256
344
  } else if (medicationType.medicationReference) {
257
345
  return this.renderMedicationRef(medicationType.medicationReference);
258
346
  }
@@ -277,7 +365,7 @@ var TemplateUtilities = class {
277
365
  */
278
366
  renderMedicationCode(medication) {
279
367
  if (medication && medication.code) {
280
- return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
368
+ return this.renderTextAsHtml(this.codeableConceptDisplay(medication.code));
281
369
  }
282
370
  return "";
283
371
  }
@@ -446,7 +534,7 @@ var TemplateUtilities = class {
446
534
  */
447
535
  firstFromCodeableConceptList(list) {
448
536
  if (list && Array.isArray(list) && list[0]) {
449
- return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
537
+ return this.renderTextAsHtml(this.codeableConceptDisplay(list[0]));
450
538
  }
451
539
  return "";
452
540
  }
@@ -786,16 +874,28 @@ var TemplateUtilities = class {
786
874
  return this.renderMedicationCode(medicationSource);
787
875
  }
788
876
  if (typeof medicationSource === "object" && ("coding" in medicationSource || "text" in medicationSource)) {
789
- return this.codeableConcept(medicationSource);
877
+ return this.codeableConceptDisplay(medicationSource);
790
878
  }
791
879
  if (typeof medicationSource === "object" && "reference" in medicationSource) {
792
880
  const medication = this.resolveReference(medicationSource);
793
881
  if (medication && medication.code) {
794
- return this.codeableConcept(medication.code);
882
+ return this.codeableConceptDisplay(medication.code);
795
883
  }
796
884
  }
797
885
  return "";
798
886
  }
887
+ /**
888
+ * Returns the owner tag from the resource meta.security array.
889
+ * @param resource - FHIR resource with meta tag
890
+ * @returns The owner code if found, otherwise undefined
891
+ */
892
+ getOwnerTag(resource) {
893
+ if (!resource?.meta?.security) return "";
894
+ const ownerEntry = resource.meta.security.find(
895
+ (sec) => sec.system === "https://www.icanbwell.com/owner" && !!sec.code
896
+ );
897
+ return ownerEntry?.code;
898
+ }
799
899
  /**
800
900
  * Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
801
901
  * This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
@@ -959,6 +1059,21 @@ var TemplateUtilities = class {
959
1059
  const denominatorUnit = valueRatio.denominator.unit ? ` ${valueRatio.denominator.unit}` : "";
960
1060
  return `${numerator}${numeratorUnit} / ${denominator}${denominatorUnit}`;
961
1061
  }
1062
+ /**
1063
+ * Finds the resource that matches the reference
1064
+ * @param ref - Reference to a resource
1065
+ * @returns The resource or null
1066
+ */
1067
+ resolveReference(ref) {
1068
+ if (!ref || !this.resources) {
1069
+ return null;
1070
+ }
1071
+ const refId = ref.reference?.split("/")[1];
1072
+ const refType = ref.reference?.split("/")[0];
1073
+ return this.resources.find(
1074
+ (resource) => resource.resourceType === refType && resource.id === refId
1075
+ ) || null;
1076
+ }
962
1077
  };
963
1078
 
964
1079
  // src/constants.ts
@@ -1292,7 +1407,8 @@ var PatientTemplate = class _PatientTemplate {
1292
1407
  static generateStaticNarrative(resources, timezone) {
1293
1408
  const templateUtilities = new TemplateUtilities(resources);
1294
1409
  const combinedPatient = this.combinePatients(resources);
1295
- return `<div>
1410
+ let html = `<p>This section merges all Patient resources into a single combined patient record, preferring non-empty values for each field.</p>`;
1411
+ html += `<div>
1296
1412
  <ul>
1297
1413
  <li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
1298
1414
  <li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
@@ -1304,6 +1420,7 @@ var PatientTemplate = class _PatientTemplate {
1304
1420
  <li><strong>Language(s):</strong>${this.renderCommunication(templateUtilities, combinedPatient)}</li>
1305
1421
  </ul>
1306
1422
  </div>`;
1423
+ return html;
1307
1424
  }
1308
1425
  /**
1309
1426
  * Combines multiple patient resources into a single patient object
@@ -1587,7 +1704,7 @@ var PatientTemplate = class _PatientTemplate {
1587
1704
  const uniqueLanguages = /* @__PURE__ */ new Set();
1588
1705
  const preferredLanguages = /* @__PURE__ */ new Set();
1589
1706
  patient.communication.forEach((comm) => {
1590
- const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
1707
+ const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(comm.language));
1591
1708
  if (language) {
1592
1709
  if (comm.preferred) {
1593
1710
  preferredLanguages.add(language);
@@ -1628,20 +1745,26 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1628
1745
  generateSummaryNarrative(resources, timezone) {
1629
1746
  const templateUtilities = new TemplateUtilities(resources);
1630
1747
  let isSummaryCreated = false;
1631
- let html = `
1748
+ let html = `<p>This list includes all AllergyIntolerance resources, with no additional filtering, sorted as provided.</p>
1749
+ `;
1750
+ html += `
1632
1751
  <div>
1633
1752
  <table>
1634
1753
  <thead>
1635
1754
  <tr>
1636
1755
  <th>Allergen</th>
1756
+ <th>Code (System)</th>
1637
1757
  <th>Criticality</th>
1638
1758
  <th>Recorded Date</th>
1759
+ <th>Source</th>
1639
1760
  </tr>
1640
1761
  </thead>
1641
1762
  <tbody>`;
1642
1763
  for (const resourceItem of resources) {
1643
1764
  for (const rowData of resourceItem.section ?? []) {
1765
+ const sectionCodeableConcept = rowData.code;
1644
1766
  const data = {};
1767
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
1645
1768
  for (const columnData of rowData.section ?? []) {
1646
1769
  switch (columnData.title) {
1647
1770
  case "Allergen Name":
@@ -1653,6 +1776,9 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1653
1776
  case "Recorded Date":
1654
1777
  data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1655
1778
  break;
1779
+ case "Source":
1780
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1781
+ break;
1656
1782
  default:
1657
1783
  break;
1658
1784
  }
@@ -1660,9 +1786,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1660
1786
  isSummaryCreated = true;
1661
1787
  html += `
1662
1788
  <tr>
1663
- <td>${data["allergen"] ?? "-"}</td>
1664
- <td>${data["criticality"] ?? "-"}</td>
1665
- <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? "-"}</td>
1789
+ <td>${data["allergen"] ?? ""}</td>
1790
+ <td>${data["codeSystem"] ?? ""}</td>
1791
+ <td>${data["criticality"] ?? ""}</td>
1792
+ <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? ""}</td>
1793
+ <td>${data["source"] ?? ""}</td>
1666
1794
  </tr>`;
1667
1795
  }
1668
1796
  }
@@ -1701,7 +1829,8 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1701
1829
  const dateB = b.onsetDateTime;
1702
1830
  return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
1703
1831
  });
1704
- let html = "";
1832
+ let html = `<p>This list includes all AllergyIntolerance resources, with no additional filtering, sorted as provided.</p>
1833
+ `;
1705
1834
  html += `
1706
1835
  <div class="ActiveAllergies">
1707
1836
  <h3>Active</h3>
@@ -1710,10 +1839,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1710
1839
  <tr>
1711
1840
  <th>Allergen</th>
1712
1841
  <th>Status</th>
1842
+ <th>Code (System)</th>
1713
1843
  <th>Category</th>
1714
1844
  <th>Reaction</th>
1715
1845
  <th>Onset Date</th>
1716
1846
  <th>Comments</th>
1847
+ <th>Source</th>
1717
1848
  </tr>
1718
1849
  </thead>
1719
1850
  <tbody>`;
@@ -1737,11 +1868,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1737
1868
  <tr>
1738
1869
  <th>Allergen</th>
1739
1870
  <th>Status</th>
1871
+ <th>Code (System)</th>
1740
1872
  <th>Category</th>
1741
1873
  <th>Reaction</th>
1742
1874
  <th>Onset Date</th>
1743
1875
  <th>Comments</th>
1744
1876
  <th>Resolved Date</th>
1877
+ <th>Source</th>
1745
1878
  </tr>
1746
1879
  </thead>
1747
1880
  <tbody>`;
@@ -1772,14 +1905,16 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1772
1905
  for (const allergy of allergies) {
1773
1906
  html += `
1774
1907
  <tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
1775
- <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
1776
- <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
1777
- <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
1778
- <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
1779
- <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
1780
- <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
1908
+ <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.code))}</span></td>
1909
+ <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.clinicalStatus)) || ""}</td>
1910
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(allergy.code)}</td>
1911
+ <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || ""}</td>
1912
+ <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || ""}</td>
1913
+ <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || ""}</td>
1914
+ <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>
1915
+ <td class="Source">${templateUtilities.getOwnerTag(allergy)}</td>`;
1781
1916
  if (includeResolved) {
1782
- let endDate = "-";
1917
+ let endDate = "";
1783
1918
  if (allergy.extension && Array.isArray(allergy.extension)) {
1784
1919
  const endDateExt = allergy.extension.find(
1785
1920
  (ext) => ext.url === "http://hl7.org/fhir/StructureDefinition/allergyintolerance-resolutionDate"
@@ -1803,10 +1938,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1803
1938
  * Generate HTML narrative for Medication resources
1804
1939
  * @param resources - FHIR Medication resources
1805
1940
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1941
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1806
1942
  * @returns HTML string for rendering
1807
1943
  */
1808
- generateNarrative(resources, timezone) {
1809
- return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
1944
+ generateNarrative(resources, timezone, now) {
1945
+ return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone, now);
1810
1946
  }
1811
1947
  /**
1812
1948
  * Generate HTML narrative for Medication resources using summary
@@ -1819,23 +1955,28 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1819
1955
  const templateUtilities = new TemplateUtilities(resources);
1820
1956
  let isSummaryCreated = false;
1821
1957
  const currentDate = now || /* @__PURE__ */ new Date();
1822
- const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
1823
- let html = `
1958
+ const twoYearsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 24, currentDate.getDate());
1959
+ 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>";
1960
+ html += `
1824
1961
  <div>
1825
1962
  <table>
1826
1963
  <thead>
1827
1964
  <tr>
1828
1965
  <th>Medication</th>
1966
+ <th>Code (System)</th>
1829
1967
  <th>Status</th>
1830
1968
  <th>Sig</th>
1831
1969
  <th>Days of Supply</th>
1832
1970
  <th>Refills</th>
1833
1971
  <th>Start Date</th>
1972
+ <th>Source</th>
1834
1973
  </tr>
1835
1974
  </thead>
1836
1975
  <tbody>`;
1976
+ let skippedMedications = 0;
1837
1977
  for (const resourceItem of resources) {
1838
1978
  for (const rowData of resourceItem.section ?? []) {
1979
+ const sectionCodeableConcept = rowData.code;
1839
1980
  const data = {};
1840
1981
  for (const columnData of rowData.section ?? []) {
1841
1982
  switch (columnData.title) {
@@ -1860,6 +2001,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1860
2001
  case "Authored On Date":
1861
2002
  data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1862
2003
  break;
2004
+ case "Source":
2005
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2006
+ break;
1863
2007
  default:
1864
2008
  break;
1865
2009
  }
@@ -1871,16 +2015,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1871
2015
  startDateObj = void 0;
1872
2016
  }
1873
2017
  }
1874
- if (data["status"] === "active" || startDateObj && startDateObj >= twelveMonthsAgo) {
2018
+ if (!(data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo)) {
2019
+ skippedMedications++;
2020
+ }
2021
+ if (data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo) {
1875
2022
  isSummaryCreated = true;
1876
2023
  html += `
1877
2024
  <tr>
1878
2025
  <td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
2026
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
1879
2027
  <td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
1880
2028
  <td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
1881
2029
  <td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
1882
2030
  <td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
1883
2031
  <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
2032
+ <td>${templateUtilities.renderTextAsHtml(data["source"])}</td>
1884
2033
  </tr>`;
1885
2034
  }
1886
2035
  }
@@ -1889,7 +2038,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1889
2038
  </tbody>
1890
2039
  </table>
1891
2040
  </div>`;
1892
- return isSummaryCreated ? html : void 0;
2041
+ if (skippedMedications > 0) {
2042
+ html += `
2043
+ <p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
2044
+ }
2045
+ if (isSummaryCreated || skippedMedications > 0) {
2046
+ return html;
2047
+ }
2048
+ return void 0;
1893
2049
  }
1894
2050
  /**
1895
2051
  * Safely parse a date string and return a valid Date object or null
@@ -1907,21 +2063,42 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1907
2063
  * Internal static implementation that actually generates the narrative
1908
2064
  * @param resources - FHIR Medication resources
1909
2065
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2066
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1910
2067
  * @returns HTML string for rendering
1911
2068
  */
1912
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1913
- static generateStaticNarrative(resources, timezone) {
2069
+ static generateStaticNarrative(resources, timezone, now) {
1914
2070
  const templateUtilities = new TemplateUtilities(resources);
1915
2071
  let html = "";
1916
2072
  const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
1917
2073
  const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
1918
2074
  const allActiveMedications = [];
2075
+ const currentDate = now || /* @__PURE__ */ new Date();
2076
+ const twoYearsAgo = new Date(currentDate);
2077
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2078
+ let skippedMedications = 0;
2079
+ const allMedications = [];
1919
2080
  medicationRequests.forEach((mr) => {
1920
- allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
2081
+ allMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1921
2082
  });
1922
2083
  medicationStatements.forEach((ms) => {
1923
- allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
2084
+ allMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1924
2085
  });
2086
+ for (const med of allMedications) {
2087
+ let dateString;
2088
+ if (med.type === "request") {
2089
+ const mr = med.resource;
2090
+ dateString = mr.dispenseRequest?.validityPeriod?.start || mr.authoredOn;
2091
+ } else {
2092
+ const ms = med.resource;
2093
+ dateString = ms.effectiveDateTime || ms.effectivePeriod?.start;
2094
+ }
2095
+ const dateObj = this.parseDate(dateString);
2096
+ if (!dateObj || dateObj < twoYearsAgo) {
2097
+ skippedMedications++;
2098
+ } else {
2099
+ allActiveMedications.push(med);
2100
+ }
2101
+ }
1925
2102
  const sortMedications = (medications) => {
1926
2103
  medications.sort((a, b) => {
1927
2104
  let dateStringA;
@@ -1952,7 +2129,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1952
2129
  sortMedications(allActiveMedications);
1953
2130
  html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
1954
2131
  }
1955
- return html;
2132
+ if (skippedMedications > 0) {
2133
+ html += `
2134
+ <p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
2135
+ }
2136
+ if (allActiveMedications.length > 0 || skippedMedications > 0) {
2137
+ return html;
2138
+ }
2139
+ return "";
1956
2140
  }
1957
2141
  /**
1958
2142
  * Extract MedicationRequest resources
@@ -1997,10 +2181,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1997
2181
  <tr>
1998
2182
  <th>Type</th>
1999
2183
  <th>Medication</th>
2184
+ <th>Code (System)</th>
2000
2185
  <th>Sig</th>
2001
2186
  <th>Dispense Quantity</th>
2002
2187
  <th>Refills</th>
2003
2188
  <th>Start Date</th>
2189
+ <th>Source</th>
2004
2190
  </tr>
2005
2191
  </thead>
2006
2192
  <tbody>`;
@@ -2009,27 +2195,31 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2009
2195
  let type;
2010
2196
  let medicationName;
2011
2197
  let sig;
2012
- let dispenseQuantity = "-";
2013
- let refills = "-";
2014
- let startDate = "-";
2198
+ let dispenseQuantity = "";
2199
+ let refills = "";
2200
+ let startDate = "";
2201
+ let codeSystemDisplay = "";
2015
2202
  if (medication.type === "request") {
2016
2203
  const mr = medication.resource;
2017
2204
  type = "Request";
2018
2205
  medicationName = templateUtilities.getMedicationName(
2019
2206
  mr.medicationReference || mr.medicationCodeableConcept
2020
2207
  );
2021
- sig = templateUtilities.concat(mr.dosageInstruction, "text") || "-";
2208
+ sig = templateUtilities.concat(mr.dosageInstruction, "text") || "";
2022
2209
  if (mr.dispenseRequest?.quantity) {
2023
2210
  const quantity = mr.dispenseRequest.quantity;
2024
2211
  if (quantity.value) {
2025
2212
  dispenseQuantity = `${quantity.value} ${quantity.unit || quantity.code || ""}`.trim();
2026
2213
  }
2027
2214
  }
2028
- refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
2215
+ refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "";
2029
2216
  if (mr.dispenseRequest?.validityPeriod) {
2030
- startDate = mr.dispenseRequest.validityPeriod.start || "-";
2217
+ startDate = mr.dispenseRequest.validityPeriod.start || "";
2031
2218
  } else {
2032
- startDate = mr.authoredOn || "-";
2219
+ startDate = mr.authoredOn || "";
2220
+ }
2221
+ if (mr.medicationCodeableConcept && mr.medicationCodeableConcept.coding && mr.medicationCodeableConcept.coding[0]) {
2222
+ codeSystemDisplay = templateUtilities.codeableConceptCoding(mr.medicationCodeableConcept);
2033
2223
  }
2034
2224
  } else {
2035
2225
  const ms = medication.resource;
@@ -2037,21 +2227,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2037
2227
  medicationName = templateUtilities.getMedicationName(
2038
2228
  ms.medicationReference || ms.medicationCodeableConcept
2039
2229
  );
2040
- sig = templateUtilities.concat(ms.dosage, "text") || "-";
2230
+ sig = templateUtilities.concat(ms.dosage, "text") || "";
2041
2231
  if (ms.effectiveDateTime) {
2042
2232
  startDate = ms.effectiveDateTime;
2043
2233
  } else if (ms.effectivePeriod) {
2044
- startDate = ms.effectivePeriod.start || "-";
2234
+ startDate = ms.effectivePeriod.start || "";
2235
+ }
2236
+ if (ms.medicationCodeableConcept && ms.medicationCodeableConcept.coding && ms.medicationCodeableConcept.coding[0]) {
2237
+ codeSystemDisplay = templateUtilities.codeableConceptCoding(ms.medicationCodeableConcept);
2045
2238
  }
2046
2239
  }
2047
2240
  html += `
2048
2241
  <tr${narrativeLinkId ? ` id="${narrativeLinkId}"` : ""}>
2049
2242
  <td>${type}</td>
2050
2243
  <td>${medicationName}<ul></ul></td>
2244
+ <td>${codeSystemDisplay}</td>
2051
2245
  <td>${sig}</td>
2052
2246
  <td>${dispenseQuantity}</td>
2053
2247
  <td>${refills}</td>
2054
2248
  <td>${startDate}</td>
2249
+ <td>${templateUtilities.getOwnerTag(medication.resource)}</td>
2055
2250
  </tr>`;
2056
2251
  }
2057
2252
  html += `
@@ -2088,18 +2283,23 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2088
2283
  let isSummaryCreated = false;
2089
2284
  let html = `
2090
2285
  <div>
2286
+ <p>This list includes all vaccinations, sorted by occurrence date (most recent first).</p>
2091
2287
  <table>
2092
2288
  <thead>
2093
2289
  <tr>
2094
2290
  <th>Immunization</th>
2291
+ <th>Code (System)</th>
2095
2292
  <th>Status</th>
2096
2293
  <th>Date</th>
2294
+ <th>Source</th>
2097
2295
  </tr>
2098
2296
  </thead>
2099
2297
  <tbody>`;
2100
2298
  for (const resourceItem of resources) {
2101
2299
  for (const rowData of resourceItem.section ?? []) {
2300
+ const sectionCodeableConcept = rowData.code;
2102
2301
  const data = {};
2302
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2103
2303
  for (const columnData of rowData.section ?? []) {
2104
2304
  switch (columnData.title) {
2105
2305
  case "Immunization Name":
@@ -2111,6 +2311,9 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2111
2311
  case "occurrenceDateTime":
2112
2312
  data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2113
2313
  break;
2314
+ case "Source":
2315
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2316
+ break;
2114
2317
  default:
2115
2318
  break;
2116
2319
  }
@@ -2119,9 +2322,11 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2119
2322
  isSummaryCreated = true;
2120
2323
  html += `
2121
2324
  <tr>
2122
- <td>${data["immunization"] ?? "-"}</td>
2123
- <td>${data["status"] ?? "-"}</td>
2124
- <td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? "-"}</td>
2325
+ <td>${data["immunization"] ?? ""}</td>
2326
+ <td>${data["codeSystem"] ?? ""}</td>
2327
+ <td>${data["status"] ?? ""}</td>
2328
+ <td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? ""}</td>
2329
+ <td>${data["source"] ?? ""}</td>
2125
2330
  </tr>`;
2126
2331
  }
2127
2332
  }
@@ -2145,12 +2350,14 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2145
2350
  <thead>
2146
2351
  <tr>
2147
2352
  <th>Immunization</th>
2353
+ <th>Code (System)</th>
2148
2354
  <th>Status</th>
2149
2355
  <th>Dose Number</th>
2150
2356
  <th>Manufacturer</th>
2151
2357
  <th>Lot Number</th>
2152
2358
  <th>Comments</th>
2153
2359
  <th>Date</th>
2360
+ <th>Source</th>
2154
2361
  </tr>
2155
2362
  </thead>
2156
2363
  <tbody>`;
@@ -2159,14 +2366,16 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2159
2366
  for (const resourceItem of immunizations) {
2160
2367
  const imm = resourceItem;
2161
2368
  html += `
2162
- <tr id="${templateUtilities.narrativeLinkId(imm)}">
2163
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
2369
+ <tr>
2370
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(imm.vaccineCode))}</td>
2371
+ <td>${templateUtilities.codeableConceptCoding(imm.vaccineCode)}</td>
2164
2372
  <td>${imm.status || ""}</td>
2165
2373
  <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
2166
2374
  <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
2167
2375
  <td>${imm.lotNumber || ""}</td>
2168
2376
  <td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
2169
2377
  <td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
2378
+ <td>${templateUtilities.getOwnerTag(imm)}</td>
2170
2379
  </tr>`;
2171
2380
  }
2172
2381
  }
@@ -2197,11 +2406,15 @@ var ProblemListTemplate = class _ProblemListTemplate {
2197
2406
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2198
2407
  static generateStaticNarrative(resources, timezone) {
2199
2408
  const templateUtilities = new TemplateUtilities(resources);
2200
- let html = ``;
2409
+ let html = `<p>This list includes patient problems, sorted by recorded date (most recent first)</p>
2410
+ `;
2201
2411
  const activeConditions = resources.map((entry) => entry) || [];
2202
2412
  activeConditions.sort((a, b) => {
2203
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
2204
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
2413
+ if (!a.recordedDate && b.recordedDate) return -1;
2414
+ if (a.recordedDate && !b.recordedDate) return 1;
2415
+ if (!a.recordedDate && !b.recordedDate) return 0;
2416
+ const dateA = new Date(a.recordedDate).getTime();
2417
+ const dateB = new Date(b.recordedDate).getTime();
2205
2418
  return dateB - dateA;
2206
2419
  });
2207
2420
  html += `
@@ -2209,22 +2422,28 @@ var ProblemListTemplate = class _ProblemListTemplate {
2209
2422
  <thead>
2210
2423
  <tr>
2211
2424
  <th>Problem</th>
2425
+ <th>Code (System)</th>
2212
2426
  <th>Onset Date</th>
2213
2427
  <th>Recorded Date</th>
2428
+ <th>Source</th>
2214
2429
  </tr>
2215
2430
  </thead>
2216
2431
  <tbody>`;
2217
- const addedConditionCodes = /* @__PURE__ */ new Set();
2432
+ const seenCodeAndSystems = /* @__PURE__ */ new Set();
2218
2433
  for (const cond of activeConditions) {
2219
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2220
- if (!addedConditionCodes.has(conditionCode)) {
2221
- addedConditionCodes.add(conditionCode);
2222
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2223
- <td class="Name">${conditionCode}</td>
2224
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2225
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2226
- </tr>`;
2434
+ const conditionDisplay = templateUtilities.codeableConceptDisplay(cond.code);
2435
+ const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
2436
+ if (codeAndSystem && seenCodeAndSystems.has(codeAndSystem)) {
2437
+ continue;
2227
2438
  }
2439
+ seenCodeAndSystems.add(codeAndSystem);
2440
+ html += `<tr>
2441
+ <td class="Name">${conditionDisplay}</td>
2442
+ <td class="CodeSystem">${codeAndSystem}</td>
2443
+ <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2444
+ <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2445
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
2446
+ </tr>`;
2228
2447
  }
2229
2448
  html += `</tbody>
2230
2449
  </table>`;
@@ -2252,21 +2471,26 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2252
2471
  generateSummaryNarrative(resources, timezone) {
2253
2472
  const templateUtilities = new TemplateUtilities(resources);
2254
2473
  let isSummaryCreated = false;
2255
- let html = `
2474
+ let html = `<p>This list includes the latest vital signs, sorted by effective date (most recent first).</p>
2475
+ `;
2476
+ html += `
2256
2477
  <div>
2257
2478
  <table>
2258
2479
  <thead>
2259
2480
  <tr>
2260
- <th>Vital Name</th>
2481
+ <th>Name</th>
2482
+ <th>Code (System)</th>
2261
2483
  <th>Result</th>
2262
- <th>Reference Range</th>
2263
2484
  <th>Date</th>
2485
+ <th>Source</th>
2264
2486
  </tr>
2265
2487
  </thead>
2266
2488
  <tbody>`;
2267
2489
  for (const resourceItem of resources) {
2268
2490
  for (const rowData of resourceItem.section ?? []) {
2491
+ const sectionCodeableConcept = rowData.code;
2269
2492
  const data = {};
2493
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2270
2494
  for (const columnData of rowData.section ?? []) {
2271
2495
  const columnTitle = columnData.title;
2272
2496
  if (columnTitle) {
@@ -2294,10 +2518,11 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2294
2518
  isSummaryCreated = true;
2295
2519
  html += `
2296
2520
  <tr>
2297
- <td>${data["Vital Name"] ?? "-"}</td>
2298
- <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? "-"}</td>
2299
- <td>${templateUtilities.extractObservationSummaryReferenceRange(data) ?? "-"}</td>
2300
- <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "-"}</td>
2521
+ <td>${data["Vital Name"] ?? ""}</td>
2522
+ <td>${data["codeSystem"] ?? ""}</td>
2523
+ <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? ""}</td>
2524
+ <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
2525
+ <td>${data["Source"] ?? ""}</td>
2301
2526
  </tr>`;
2302
2527
  }
2303
2528
  }
@@ -2321,30 +2546,36 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2321
2546
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
2322
2547
  return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2323
2548
  });
2324
- let html = `
2549
+ let html = `<p>This list includes the latest vital signs, sorted by effective date (most recent first).</p>
2550
+ `;
2551
+ html += `
2325
2552
  <table>
2326
2553
  <thead>
2327
2554
  <tr>
2328
- <th>Vital Name</th>
2555
+ <th>Name</th>
2556
+ <th>Code (System)</th>
2329
2557
  <th>Result</th>
2330
2558
  <th>Unit</th>
2331
2559
  <th>Interpretation</th>
2332
2560
  <th>Component(s)</th>
2333
2561
  <th>Comments</th>
2334
2562
  <th>Date</th>
2563
+ <th>Source</th>
2335
2564
  </tr>
2336
2565
  </thead>
2337
2566
  <tbody>`;
2338
2567
  for (const obs of observations) {
2339
2568
  html += `
2340
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
2341
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
2569
+ <tr>
2570
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code, "display"))}</td>
2571
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2342
2572
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2343
2573
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2344
2574
  <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
2345
2575
  <td>${templateUtilities.renderComponent(obs.component)}</td>
2346
2576
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
2347
2577
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
2578
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2348
2579
  </tr>`;
2349
2580
  }
2350
2581
  html += `
@@ -2373,7 +2604,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2373
2604
  */
2374
2605
  static generateStaticNarrative(resources, timezone) {
2375
2606
  const templateUtilities = new TemplateUtilities(resources);
2376
- let html = `
2607
+ let html = `<p>This list includes all DeviceUseStatement resources, sorted by recorded date (most recent first).</p>
2377
2608
  <table>
2378
2609
  <thead>
2379
2610
  <tr>
@@ -2391,7 +2622,7 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
2391
2622
  });
2392
2623
  for (const dus of deviceStatements) {
2393
2624
  html += `
2394
- <tr id="${templateUtilities.narrativeLinkId(dus)}">
2625
+ <tr>
2395
2626
  <td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
2396
2627
  <td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
2397
2628
  <td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
@@ -2417,10 +2648,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2417
2648
  * Generate HTML narrative for Diagnostic Results
2418
2649
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2419
2650
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2651
+ * @param now - Optional current date for filtering
2420
2652
  * @returns HTML string for rendering
2421
2653
  */
2422
- generateNarrative(resources, timezone) {
2423
- return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2654
+ generateNarrative(resources, timezone, now) {
2655
+ return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone, now);
2424
2656
  }
2425
2657
  /**
2426
2658
  * Helper function to format observation data fields
@@ -2633,6 +2865,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2633
2865
  break;
2634
2866
  case "valueRange.high.value":
2635
2867
  targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2868
+ targetData["valueType"] = "valueRange";
2636
2869
  break;
2637
2870
  case "valueRange.high.unit":
2638
2871
  targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2647,6 +2880,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2647
2880
  break;
2648
2881
  case "valueRatio.denominator.value":
2649
2882
  targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2883
+ targetData["valueType"] = "valueRatio";
2650
2884
  break;
2651
2885
  case "valueRatio.denominator.unit":
2652
2886
  targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2684,34 +2918,100 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2684
2918
  * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2685
2919
  * @param resources - FHIR Composition resources
2686
2920
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2921
+ * @param now - Optional current date for filtering
2687
2922
  * @returns HTML string for rendering
2688
2923
  */
2689
- generateSummaryNarrative(resources, timezone) {
2924
+ generateSummaryNarrative(resources, timezone, now) {
2690
2925
  const templateUtilities = new TemplateUtilities(resources);
2691
- let html = `
2692
- <div>`;
2926
+ const currentDate = now || /* @__PURE__ */ new Date();
2927
+ const twoYearsAgo = new Date(currentDate);
2928
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2929
+ let skippedObservations = 0;
2930
+ let skippedDiagnosticReports = 0;
2931
+ for (const resourceItem of resources) {
2932
+ for (const rowData of resourceItem.section ?? []) {
2933
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2934
+ for (const columnData of rowData.section ?? []) {
2935
+ if (columnData.text?.div === "Observation.component" && columnData.section) {
2936
+ for (const componentSection of columnData.section) {
2937
+ const componentData = {};
2938
+ for (const nestedColumn of componentSection.section ?? []) {
2939
+ this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2940
+ }
2941
+ let compDate = void 0;
2942
+ if (componentData["effectiveDateTime"]) {
2943
+ compDate = new Date(componentData["effectiveDateTime"]);
2944
+ } else if (componentData["effectivePeriodStart"]) {
2945
+ compDate = new Date(componentData["effectivePeriodStart"]);
2946
+ }
2947
+ if (compDate && compDate < twoYearsAgo) {
2948
+ skippedObservations++;
2949
+ }
2950
+ }
2951
+ } else {
2952
+ const data = {};
2953
+ this.extractSummaryObservationFields(columnData, data, templateUtilities);
2954
+ let obsDate = void 0;
2955
+ if (data["effectiveDateTime"]) {
2956
+ obsDate = new Date(data["effectiveDateTime"]);
2957
+ } else if (data["effectivePeriodStart"]) {
2958
+ obsDate = new Date(data["effectivePeriodStart"]);
2959
+ }
2960
+ if (obsDate && obsDate < twoYearsAgo) {
2961
+ skippedObservations++;
2962
+ }
2963
+ }
2964
+ }
2965
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2966
+ let issuedDate = void 0;
2967
+ let status = void 0;
2968
+ let reportFound = false;
2969
+ for (const columnData of rowData.section ?? []) {
2970
+ if (columnData.title === "Issued Date") {
2971
+ issuedDate = columnData.text?.div ? new Date(columnData.text.div) : void 0;
2972
+ }
2973
+ if (columnData.title === "Status") {
2974
+ status = columnData.text?.div;
2975
+ }
2976
+ if (columnData.title === "Diagnostic Report Name") {
2977
+ reportFound = true;
2978
+ }
2979
+ }
2980
+ if (status === "final" && issuedDate && issuedDate < twoYearsAgo && reportFound) {
2981
+ skippedDiagnosticReports++;
2982
+ }
2983
+ }
2984
+ }
2985
+ }
2986
+ 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>
2987
+ `;
2693
2988
  let observationhtml = `
2694
2989
  <div>
2695
2990
  <h3>Observations</h3>
2991
+ ${html}
2696
2992
  <table>
2697
2993
  <thead>
2698
2994
  <tr>
2699
- <th>Code</th>
2995
+ <th>Name</th>
2996
+ <th>Code (System)</th>
2700
2997
  <th>Result</th>
2701
2998
  <th>Reference Range</th>
2702
2999
  <th>Date</th>
3000
+ <th>Source</th>
2703
3001
  </tr>
2704
3002
  </thead>
2705
3003
  <tbody>`;
2706
3004
  let diagnosticReporthtml = `
2707
3005
  <div>
2708
3006
  <h3>Diagnostic Reports</h3>
3007
+ ${html}
2709
3008
  <table>
2710
3009
  <thead>
2711
3010
  <tr>
2712
3011
  <th>Report</th>
2713
3012
  <th>Performer</th>
2714
3013
  <th>Issued</th>
3014
+ <th>Source</th>
2715
3015
  </tr>
2716
3016
  </thead>
2717
3017
  <tbody>`;
@@ -2719,7 +3019,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2719
3019
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2720
3020
  for (const resourceItem of resources) {
2721
3021
  for (const rowData of resourceItem.section ?? []) {
3022
+ const sectionCodeableConcept = rowData.code;
2722
3023
  const data = {};
3024
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2723
3025
  const components = [];
2724
3026
  for (const columnData of rowData.section ?? []) {
2725
3027
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
@@ -2729,7 +3031,13 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2729
3031
  for (const nestedColumn of componentSection.section ?? []) {
2730
3032
  this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2731
3033
  }
2732
- if (Object.keys(componentData).length > 0) {
3034
+ let compDate = void 0;
3035
+ if (componentData["effectiveDateTime"]) {
3036
+ compDate = new Date(componentData["effectiveDateTime"]);
3037
+ } else if (componentData["effectivePeriodStart"]) {
3038
+ compDate = new Date(componentData["effectivePeriodStart"]);
3039
+ }
3040
+ if (compDate && compDate >= twoYearsAgo && Object.keys(componentData).length > 0) {
2733
3041
  components.push(componentData);
2734
3042
  }
2735
3043
  }
@@ -2750,6 +3058,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2750
3058
  case "Status":
2751
3059
  data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2752
3060
  break;
3061
+ case "Source":
3062
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3063
+ break;
2753
3064
  default:
2754
3065
  break;
2755
3066
  }
@@ -2757,6 +3068,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2757
3068
  }
2758
3069
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2759
3070
  let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
3071
+ let obsDate = void 0;
3072
+ if (data["effectiveDateTime"]) {
3073
+ obsDate = new Date(data["effectiveDateTime"]);
3074
+ } else if (data["effectivePeriodStart"]) {
3075
+ obsDate = new Date(data["effectivePeriodStart"]);
3076
+ }
2760
3077
  if (!date && data["effectivePeriodStart"]) {
2761
3078
  date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2762
3079
  if (data["effectivePeriodEnd"]) {
@@ -2773,36 +3090,47 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2773
3090
  observationhtml += `
2774
3091
  <tr>
2775
3092
  <td>${componentCode}</td>
2776
- <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
2777
- <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
2778
- <td>${date ?? "-"}</td>
3093
+ <td></td>
3094
+ <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? ""}</td>
3095
+ <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? ""}</td>
3096
+ <td>${date ?? ""}</td>
3097
+ <td>${data["source"] ?? ""}</td>
2779
3098
  </tr>`;
2780
3099
  }
2781
3100
  }
2782
3101
  } else {
2783
- const code = data["code"] ?? "";
2784
- if (code && !observationAdded.has(code)) {
2785
- observationAdded.add(code);
2786
- this.formatSummaryObservationData(data);
2787
- observationhtml += `
3102
+ if (obsDate && obsDate >= twoYearsAgo) {
3103
+ const code = data["code"] ?? "";
3104
+ if (code && !observationAdded.has(code)) {
3105
+ observationAdded.add(code);
3106
+ this.formatSummaryObservationData(data);
3107
+ observationhtml += `
2788
3108
  <tr>
2789
- <td>${data["code"] ?? "-"}</td>
2790
- <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
2791
- <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
2792
- <td>${date ?? "-"}</td>
3109
+ <td>${data["code"] ?? ""}</td>
3110
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
3111
+ <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? ""}</td>
3112
+ <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? ""}</td>
3113
+ <td>${date ?? ""}</td>
3114
+ <td>${data["source"] ?? ""}</td>
2793
3115
  </tr>`;
3116
+ }
2794
3117
  }
2795
3118
  }
2796
3119
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2797
- if (data["status"] === "final") {
3120
+ let issuedDate = void 0;
3121
+ if (data["issued"]) {
3122
+ issuedDate = new Date(data["issued"]);
3123
+ }
3124
+ if (data["status"] === "final" && issuedDate && issuedDate >= twoYearsAgo) {
2798
3125
  const reportName = data["report"] ?? "";
2799
3126
  if (reportName && !diagnosticReportAdded.has(reportName)) {
2800
3127
  diagnosticReportAdded.add(reportName);
2801
3128
  diagnosticReporthtml += `
2802
3129
  <tr>
2803
- <td>${data["report"] ?? "-"}</td>
2804
- <td>${data["performer"] ?? "-"}</td>
2805
- <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
3130
+ <td>${data["report"] ?? ""}</td>
3131
+ <td>${data["performer"] ?? ""}</td>
3132
+ <td>${templateUtilities.renderTime(data["issued"], timezone) ?? ""}</td>
3133
+ <td>${data["source"] ?? ""}</td>
2806
3134
  </tr>`;
2807
3135
  }
2808
3136
  }
@@ -2815,6 +3143,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2815
3143
  </tbody>
2816
3144
  </table>
2817
3145
  </div>`;
3146
+ if (skippedObservations > 0) {
3147
+ html += `
3148
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3149
+ }
2818
3150
  }
2819
3151
  if (diagnosticReportAdded.size > 0) {
2820
3152
  html += diagnosticReporthtml;
@@ -2822,6 +3154,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2822
3154
  </tbody>
2823
3155
  </table>
2824
3156
  </div>`;
3157
+ if (skippedDiagnosticReports > 0) {
3158
+ html += `
3159
+ <p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
3160
+ }
2825
3161
  }
2826
3162
  html += `
2827
3163
  </div>`;
@@ -2831,12 +3167,33 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2831
3167
  * Internal static implementation that actually generates the narrative
2832
3168
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2833
3169
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3170
+ * @param now - Optional current date for filtering
2834
3171
  * @returns HTML string for rendering
2835
3172
  */
2836
- static generateStaticNarrative(resources, timezone) {
3173
+ static generateStaticNarrative(resources, timezone, now) {
2837
3174
  const templateUtilities = new TemplateUtilities(resources);
2838
- let html = "";
2839
- const observations = this.getObservations(resources);
3175
+ const currentDate = now || /* @__PURE__ */ new Date();
3176
+ const twoYearsAgo = new Date(currentDate);
3177
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
3178
+ 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>
3179
+ `;
3180
+ let skippedObservations = 0;
3181
+ let skippedDiagnosticReports = 0;
3182
+ for (const resourceItem of resources) {
3183
+ if (resourceItem.resourceType === "Observation") {
3184
+ const obsDate = this.getObservationDate(resourceItem);
3185
+ if (obsDate && obsDate < twoYearsAgo) {
3186
+ skippedObservations++;
3187
+ }
3188
+ } else if (resourceItem.resourceType === "DiagnosticReport") {
3189
+ const issued = resourceItem.issued;
3190
+ const status = resourceItem.status;
3191
+ if (status === "final" && issued && new Date(issued) < twoYearsAgo) {
3192
+ skippedDiagnosticReports++;
3193
+ }
3194
+ }
3195
+ }
3196
+ const observations = this.getObservations(resources, twoYearsAgo);
2840
3197
  if (observations.length > 0) {
2841
3198
  observations.sort((a, b) => {
2842
3199
  const dateA = this.getObservationDate(a);
@@ -2845,16 +3202,22 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2845
3202
  });
2846
3203
  this.filterObservationForLoincCodes(observations);
2847
3204
  html += this.renderObservations(templateUtilities, observations, timezone);
3205
+ if (skippedObservations > 0) {
3206
+ html += `
3207
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3208
+ }
2848
3209
  }
2849
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2850
- const diagnosticReports = this.getDiagnosticReports(resources);
2851
- if (diagnosticReports.length > 0) {
2852
- diagnosticReports.sort((a, b) => {
2853
- const dateA = a.issued;
2854
- const dateB = b.issued;
2855
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2856
- });
2857
- html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
3210
+ const diagnosticReports = this.getDiagnosticReports(resources, twoYearsAgo).filter((resource) => !this.isPanelDiagnosticReport(resource));
3211
+ if (diagnosticReports.length > 0) {
3212
+ diagnosticReports.sort((a, b) => {
3213
+ const dateA = a.issued;
3214
+ const dateB = b.issued;
3215
+ return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3216
+ });
3217
+ html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
3218
+ if (skippedDiagnosticReports > 0) {
3219
+ html += `
3220
+ <p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
2858
3221
  }
2859
3222
  }
2860
3223
  return html;
@@ -2899,15 +3262,16 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2899
3262
  return obsDate;
2900
3263
  }
2901
3264
  /**
2902
- * Get all Observation resources from the resource array
2903
- * @param resources - FHIR resources array
2904
- * @returns Array of Observation resources
2905
- */
2906
- static getObservations(resources) {
3265
+ * Get all Observation resources from the resource array, filtered by twoYearsAgo
3266
+ * @param resources - FHIR resources array
3267
+ * @param twoYearsAgo - Date object representing the cutoff
3268
+ * @returns Array of Observation resources
3269
+ */
3270
+ static getObservations(resources, twoYearsAgo) {
2907
3271
  return resources.filter((resourceItem) => {
2908
3272
  if (resourceItem.resourceType === "Observation") {
2909
3273
  const obsDate = this.getObservationDate(resourceItem);
2910
- if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
3274
+ if (obsDate && obsDate >= twoYearsAgo) {
2911
3275
  return true;
2912
3276
  }
2913
3277
  }
@@ -2915,12 +3279,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2915
3279
  }).map((resourceItem) => resourceItem);
2916
3280
  }
2917
3281
  /**
2918
- * Get all DiagnosticReport resources from the resource array
3282
+ * Get all DiagnosticReport resources from the resource array, filtered by twoYearsAgo
2919
3283
  * @param resources - FHIR resources array
3284
+ * @param twoYearsAgo - Date object representing the cutoff
2920
3285
  * @returns Array of DiagnosticReport resources
2921
3286
  */
2922
- static getDiagnosticReports(resources) {
2923
- return resources.filter((resourceItem) => resourceItem.resourceType === "DiagnosticReport").map((resourceItem) => resourceItem);
3287
+ static getDiagnosticReports(resources, twoYearsAgo) {
3288
+ return resources.filter((resourceItem) => {
3289
+ if (resourceItem.resourceType === "DiagnosticReport") {
3290
+ const issued = resourceItem.issued;
3291
+ if (issued && new Date(issued) >= twoYearsAgo) {
3292
+ return true;
3293
+ }
3294
+ }
3295
+ return false;
3296
+ }).map((resourceItem) => resourceItem);
2924
3297
  }
2925
3298
  /**
2926
3299
  * Render HTML table for Observation resources
@@ -2931,32 +3304,36 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2931
3304
  */
2932
3305
  static renderObservations(templateUtilities, observations, timezone) {
2933
3306
  let html = "";
2934
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2935
- html += `
2936
- <h3>Observations</h3>`;
2937
- }
3307
+ html += `
3308
+ <h3>Observations</h3>`;
2938
3309
  html += `
2939
3310
  <table>
2940
3311
  <thead>
2941
3312
  <tr>
2942
- <th>Code</th>
3313
+ <th>Name</th>
3314
+ <th>Code (System)</th>
2943
3315
  <th>Result</th>
2944
3316
  <th>Reference Range</th>
2945
3317
  <th>Date</th>
3318
+ <th>Source</th>
2946
3319
  </tr>
2947
3320
  </thead>
2948
3321
  <tbody>`;
2949
3322
  const observationAdded = /* @__PURE__ */ new Set();
2950
3323
  for (const obs of observations) {
2951
- const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
2952
- if (!observationAdded.has(obsCode)) {
2953
- observationAdded.add(obsCode);
3324
+ const obsCodeDisplay = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code));
3325
+ const obsCodeAndSystem = templateUtilities.codeableConceptCoding(obs.code);
3326
+ if (!observationAdded.has(obsCodeDisplay) && !observationAdded.has(obsCodeAndSystem)) {
3327
+ observationAdded.add(obsCodeDisplay);
3328
+ observationAdded.add(obsCodeAndSystem);
2954
3329
  html += `
2955
3330
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2956
- <td>${obsCode}</td>
3331
+ <td>${obsCodeDisplay}</td>
3332
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2957
3333
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2958
3334
  <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
2959
3335
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3336
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2960
3337
  </tr>`;
2961
3338
  }
2962
3339
  }
@@ -2979,17 +3356,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2979
3356
  <thead>
2980
3357
  <tr>
2981
3358
  <th>Report</th>
3359
+ <th>Code (System)</th>
2982
3360
  <th>Category</th>
2983
3361
  <th>Result</th>
2984
3362
  <th>Issued</th>
3363
+ <th>Source</th>
2985
3364
  </tr>
2986
3365
  </thead>
2987
3366
  <tbody>`;
2988
3367
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2989
3368
  for (const report of reports) {
2990
- const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
2991
- if (!diagnosticReportAdded.has(reportName)) {
3369
+ const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(report.code));
3370
+ const codeAndSystem = templateUtilities.codeableConceptCoding(report.code);
3371
+ if (!diagnosticReportAdded.has(reportName) && !diagnosticReportAdded.has(codeAndSystem)) {
2992
3372
  diagnosticReportAdded.add(reportName);
3373
+ diagnosticReportAdded.add(codeAndSystem);
2993
3374
  let resultCount = "";
2994
3375
  if (report.result && Array.isArray(report.result)) {
2995
3376
  resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
@@ -2997,9 +3378,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2997
3378
  html += `
2998
3379
  <tr id="${templateUtilities.narrativeLinkId(report)}">
2999
3380
  <td>${reportName}</td>
3381
+ <td>${codeAndSystem}</td>
3000
3382
  <td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
3001
3383
  <td>${resultCount}</td>
3002
3384
  <td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
3385
+ <td>${templateUtilities.getOwnerTag(report)}</td>
3003
3386
  </tr>`;
3004
3387
  }
3005
3388
  }
@@ -3008,6 +3391,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3008
3391
  </table>`;
3009
3392
  return html;
3010
3393
  }
3394
+ /**
3395
+ * 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)
3396
+ * @returns true if the report is just a panel
3397
+ */
3398
+ static isPanelDiagnosticReport(report) {
3399
+ return this.hasEssentialLabPanelLoinc(
3400
+ report
3401
+ );
3402
+ }
3403
+ /**
3404
+ * Check if a DiagnosticReport or Observation has a LOINC code in ESSENTIAL_LAB_PANELS
3405
+ * @param resource - DiagnosticReport or Observation
3406
+ * @returns true if any LOINC code is in ESSENTIAL_LAB_PANELS, false otherwise
3407
+ */
3408
+ static hasEssentialLabPanelLoinc(resource) {
3409
+ const codings = resource?.code?.coding ?? [];
3410
+ return codings.some(
3411
+ (coding) => coding && coding.system && coding.code && coding.system.toLowerCase().includes("loinc") && Object.keys(ESSENTIAL_LAB_PANELS).includes(coding.code)
3412
+ );
3413
+ }
3011
3414
  };
3012
3415
 
3013
3416
  // src/narratives/templates/typescript/HistoryOfProceduresTemplate.ts
@@ -3035,20 +3438,26 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3035
3438
  generateSummaryNarrative(resources, timezone) {
3036
3439
  const templateUtilities = new TemplateUtilities(resources);
3037
3440
  let isSummaryCreated = false;
3038
- let html = `
3441
+ let html = `<p>This list includes all Procedure resources, sorted by performed date (most recent first).</p>
3442
+ `;
3443
+ html += `
3039
3444
  <div>
3040
3445
  <table>
3041
3446
  <thead>
3042
3447
  <tr>
3043
3448
  <th>Procedure</th>
3449
+ <th>Code (System)</th>
3044
3450
  <th>Performer</th>
3045
3451
  <th>Date</th>
3452
+ <th>Source</th>
3046
3453
  </tr>
3047
3454
  </thead>
3048
3455
  <tbody>`;
3049
3456
  for (const resourceItem of resources) {
3050
3457
  for (const rowData of resourceItem.section ?? []) {
3458
+ const sectionCodeableConcept = rowData.code;
3051
3459
  const data = {};
3460
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
3052
3461
  for (const columnData of rowData.section ?? []) {
3053
3462
  switch (columnData.title) {
3054
3463
  case "Procedure Name":
@@ -3060,6 +3469,9 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3060
3469
  case "Performed Date":
3061
3470
  data["date"] = columnData.text?.div ?? "";
3062
3471
  break;
3472
+ case "Source":
3473
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3474
+ break;
3063
3475
  default:
3064
3476
  break;
3065
3477
  }
@@ -3067,9 +3479,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3067
3479
  isSummaryCreated = true;
3068
3480
  html += `
3069
3481
  <tr>
3070
- <td>${data["procedure"] ?? "-"}</td>
3071
- <td>${data["performer"] ?? "-"}</td>
3072
- <td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
3482
+ <td>${data["procedure"] ?? ""}</td>
3483
+ <td>${data["codeSystem"] ?? ""}</td>
3484
+ <td>${data["performer"] ?? ""}</td>
3485
+ <td>${templateUtilities.renderTime(data["date"], timezone) ?? ""}</td>
3486
+ <td>${data["source"] ?? ""}</td>
3073
3487
  </tr>`;
3074
3488
  }
3075
3489
  }
@@ -3087,23 +3501,29 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3087
3501
  */
3088
3502
  static generateStaticNarrative(resources, timezone) {
3089
3503
  const templateUtilities = new TemplateUtilities(resources);
3090
- let html = `
3504
+ let html = `<p>This list includes all Procedure resources, sorted by performed date (most recent first).</p>
3505
+ `;
3506
+ html += `
3091
3507
  <table>
3092
3508
  <thead>
3093
3509
  <tr>
3094
3510
  <th>Procedure</th>
3511
+ <th>Code (System)</th>
3095
3512
  <th>Comments</th>
3096
3513
  <th>Date</th>
3514
+ <th>Source</th>
3097
3515
  </tr>
3098
3516
  </thead>
3099
3517
  <tbody>`;
3100
3518
  for (const resourceItem of resources) {
3101
3519
  const proc = resourceItem;
3102
3520
  html += `
3103
- <tr id="${templateUtilities.narrativeLinkId(proc)}">
3104
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
3521
+ <tr>
3522
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(proc.code, "display"))}</td>
3523
+ <td>${templateUtilities.codeableConceptCoding(proc.code)}</td>
3105
3524
  <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
3106
- <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
3525
+ <td>${templateUtilities.renderTime(proc.performedDateTime || proc.performedPeriod?.start, timezone)}</td>
3526
+ <td>${templateUtilities.getOwnerTag(proc)}</td>
3107
3527
  </tr>`;
3108
3528
  }
3109
3529
  html += `
@@ -3138,26 +3558,32 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3138
3558
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3139
3559
  return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3140
3560
  });
3141
- let html = `
3561
+ let html = `<p>This list includes all information about the patient's social history, sorted by effective date (most recent first).</p>
3562
+ `;
3563
+ html += `
3142
3564
  <table>
3143
3565
  <thead>
3144
3566
  <tr>
3145
- <th>Code</th>
3567
+ <th>Name</th>
3568
+ <th>Code (System)</th>
3146
3569
  <th>Result</th>
3147
3570
  <th>Unit</th>
3148
3571
  <th>Comments</th>
3149
3572
  <th>Date</th>
3573
+ <th>Source</th>
3150
3574
  </tr>
3151
3575
  </thead>
3152
3576
  <tbody>`;
3153
3577
  for (const obs of observations) {
3154
3578
  html += `
3155
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
3156
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
3579
+ <tr>
3580
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code))}</td>
3581
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
3157
3582
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3158
3583
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
3159
3584
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3160
3585
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3586
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
3161
3587
  </tr>`;
3162
3588
  }
3163
3589
  html += `
@@ -3173,14 +3599,27 @@ var PastHistoryOfIllnessTemplate = class {
3173
3599
  * Generate HTML narrative for Past History of Illnesses
3174
3600
  * @param resources - FHIR Condition resources
3175
3601
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3602
+ * @param now - Optional current date to use for generating relative dates in the narrative
3176
3603
  * @returns HTML string for rendering
3177
3604
  */
3178
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3179
- generateNarrative(resources, timezone) {
3605
+ generateNarrative(resources, timezone, now) {
3180
3606
  const templateUtilities = new TemplateUtilities(resources);
3181
- let html = ``;
3607
+ 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>
3608
+ `;
3182
3609
  const resolvedConditions = resources.map((entry) => entry) || [];
3183
- resolvedConditions.sort((a, b) => {
3610
+ const currentDate = now || /* @__PURE__ */ new Date();
3611
+ const fiveYearsAgo = new Date(currentDate);
3612
+ fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
3613
+ let skippedConditions = 0;
3614
+ const filteredConditions = [];
3615
+ for (const cond of resolvedConditions) {
3616
+ if (cond.recordedDate && new Date(cond.recordedDate) >= fiveYearsAgo) {
3617
+ filteredConditions.push(cond);
3618
+ } else {
3619
+ skippedConditions++;
3620
+ }
3621
+ }
3622
+ filteredConditions.sort((a, b) => {
3184
3623
  const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3185
3624
  const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3186
3625
  return dateB - dateA;
@@ -3190,27 +3629,35 @@ var PastHistoryOfIllnessTemplate = class {
3190
3629
  <thead>
3191
3630
  <tr>
3192
3631
  <th>Problem</th>
3632
+ <th>Code (System)</th>
3193
3633
  <th>Onset Date</th>
3194
3634
  <th>Recorded Date</th>
3195
3635
  <th>Resolved Date</th>
3636
+ <th>Source</th>
3196
3637
  </tr>
3197
3638
  </thead>
3198
3639
  <tbody>`;
3199
3640
  const addedConditionCodes = /* @__PURE__ */ new Set();
3200
- for (const cond of resolvedConditions) {
3201
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3641
+ for (const cond of filteredConditions) {
3642
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(cond.code));
3202
3643
  if (!addedConditionCodes.has(conditionCode)) {
3203
3644
  addedConditionCodes.add(conditionCode);
3204
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3645
+ html += `<tr>
3205
3646
  <td class="Name">${conditionCode}</td>
3647
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(cond.code)}</td>
3206
3648
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3207
3649
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3208
3650
  <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
3651
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
3209
3652
  </tr>`;
3210
3653
  }
3211
3654
  }
3212
3655
  html += `</tbody>
3213
3656
  </table>`;
3657
+ if (skippedConditions > 0) {
3658
+ html += `
3659
+ <p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
3660
+ }
3214
3661
  return html;
3215
3662
  }
3216
3663
  };
@@ -3231,7 +3678,9 @@ var PlanOfCareTemplate = class {
3231
3678
  const endB = b.period?.end ? new Date(b.period?.end).getTime() : 0;
3232
3679
  return endB - endA;
3233
3680
  });
3234
- let html = `
3681
+ let html = `<p>This list includes all CarePlan resources, sorted by planned end date (most recent first).</p>
3682
+ `;
3683
+ html += `
3235
3684
  <table>
3236
3685
  <thead>
3237
3686
  <tr>
@@ -3240,6 +3689,7 @@ var PlanOfCareTemplate = class {
3240
3689
  <th>Comments</th>
3241
3690
  <th>Planned Start</th>
3242
3691
  <th>Planned End</th>
3692
+ <th>Source</th>
3243
3693
  </tr>
3244
3694
  </thead>
3245
3695
  <tbody>`;
@@ -3251,6 +3701,7 @@ var PlanOfCareTemplate = class {
3251
3701
  <td>${templateUtilities.concat(cp.note, "text")}</td>
3252
3702
  <td>${cp.period?.start ? templateUtilities.renderTime(cp.period?.start, timezone) : ""}</td>
3253
3703
  <td>${cp.period?.end ? templateUtilities.renderTime(cp.period?.end, timezone) : ""}</td>
3704
+ <td>${templateUtilities.getOwnerTag(cp)}</td>
3254
3705
  </tr>`;
3255
3706
  }
3256
3707
  html += `
@@ -3267,7 +3718,9 @@ var PlanOfCareTemplate = class {
3267
3718
  generateSummaryNarrative(resources, timezone) {
3268
3719
  const templateUtilities = new TemplateUtilities(resources);
3269
3720
  let isSummaryCreated = false;
3270
- let html = `
3721
+ let html = `<p>This list includes all CarePlan resources, sorted by planned end date (most recent first).</p>
3722
+ `;
3723
+ html += `
3271
3724
  <div>
3272
3725
  <table>
3273
3726
  <thead>
@@ -3276,6 +3729,7 @@ var PlanOfCareTemplate = class {
3276
3729
  <th>Created</th>
3277
3730
  <th>Planned Start</th>
3278
3731
  <th>Planned End</th>
3732
+ <th>Source</th>
3279
3733
  </tr>
3280
3734
  </thead>
3281
3735
  <tbody>`;
@@ -3293,10 +3747,11 @@ var PlanOfCareTemplate = class {
3293
3747
  isSummaryCreated = true;
3294
3748
  html += `
3295
3749
  <tr>
3296
- <td>${data["CarePlan Name"] ?? "-"}</td>
3297
- <td>${templateUtilities.renderTime(data["created"], timezone) ?? "-"}</td>
3298
- <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? "-"}</td>
3299
- <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? "-"}</td>
3750
+ <td>${data["CarePlan Name"] ?? ""}</td>
3751
+ <td>${templateUtilities.renderTime(data["created"], timezone) ?? ""}</td>
3752
+ <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? ""}</td>
3753
+ <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? ""}</td>
3754
+ <td>${data["source"] ?? ""}</td>
3300
3755
  </tr>`;
3301
3756
  }
3302
3757
  }
@@ -3327,77 +3782,54 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3327
3782
  */
3328
3783
  static generateStaticNarrative(resources, timezone) {
3329
3784
  const templateUtilities = new TemplateUtilities(resources);
3330
- let html = ``;
3331
- const activeConditions = [];
3332
- const clinicalImpressions = [];
3333
- for (const resourceItem of resources) {
3334
- if (resourceItem.resourceType === "Condition") {
3335
- activeConditions.push(resourceItem);
3336
- } else if (resourceItem.resourceType === "ClinicalImpression") {
3337
- clinicalImpressions.push(resourceItem);
3338
- }
3339
- }
3340
- activeConditions.sort((a, b) => {
3341
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3342
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3343
- return dateB - dateA;
3785
+ let html = `<p>This section summarizes key observations and assessments related to the person's functional status and ability to perform daily activities.</p>`;
3786
+ let functionalObservations = resources.filter((r) => r.resourceType === "Observation").filter((r) => {
3787
+ const hasFunctionalLoinc = r.code?.coding?.some(
3788
+ (c) => c.system?.toLowerCase().includes("loinc") && c.code === "47420-5"
3789
+ );
3790
+ const hasFunctionalCategory = r.category?.some(
3791
+ (cat) => cat.coding?.some(
3792
+ (c) => c.code === "functional-status" || c.display?.toLowerCase().includes("functional")
3793
+ )
3794
+ );
3795
+ return hasFunctionalLoinc || hasFunctionalCategory;
3344
3796
  });
3345
- clinicalImpressions.sort((a, b) => {
3346
- 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;
3347
- 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;
3348
- return dateB - dateA;
3797
+ functionalObservations = functionalObservations.sort((a, b) => {
3798
+ const getObsDate = (obs) => obs.effectiveDateTime ? new Date(obs.effectiveDateTime).getTime() : obs.issued ? new Date(obs.issued).getTime() : 0;
3799
+ return getObsDate(b) - getObsDate(a);
3349
3800
  });
3350
- if (activeConditions.length > 0) {
3351
- html += `<h3>Conditions</h3>
3352
- <table>
3353
- <thead>
3354
- <tr>
3355
- <th>Problem</th>
3356
- <th>Onset Date</th>
3357
- <th>Recorded Date</th>
3358
- </tr>
3359
- </thead>
3360
- <tbody>`;
3361
- const addedConditionCodes = /* @__PURE__ */ new Set();
3362
- for (const cond of activeConditions) {
3363
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3364
- if (!addedConditionCodes.has(conditionCode)) {
3365
- addedConditionCodes.add(conditionCode);
3366
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3367
- <td class="Name">${conditionCode}</td>
3368
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3369
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3370
- </tr>`;
3371
- }
3801
+ let clinicalImpressions = resources.filter((r) => r.resourceType === "ClinicalImpression").filter((r) => r.status === "completed");
3802
+ clinicalImpressions = clinicalImpressions.sort((a, b) => {
3803
+ 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;
3804
+ return getImpressionDate(b) - getImpressionDate(a);
3805
+ });
3806
+ if (functionalObservations.length > 0) {
3807
+ html += `<table><thead><tr><th>Observation</th><th>Value</th><th>Date</th><th>Interpretation</th><th>Comments</th></tr></thead><tbody>`;
3808
+ for (const obs of functionalObservations) {
3809
+ const observation = obs;
3810
+ const obsName = templateUtilities.codeableConceptDisplay(observation.code);
3811
+ const value = templateUtilities.extractObservationValue(observation);
3812
+ const date = observation.effectiveDateTime ? templateUtilities.renderDate(observation.effectiveDateTime) : observation.issued ? templateUtilities.renderDate(observation.issued) : "";
3813
+ const interpretation = observation.interpretation ? templateUtilities.codeableConceptDisplay(observation.interpretation[0]) : "";
3814
+ const comments = observation.comment || observation.note?.map((n) => n.text).join("; ") || "";
3815
+ html += `<tr id="${templateUtilities.narrativeLinkId(observation)}">
3816
+ <td>${obsName}</td>
3817
+ <td>${value ?? ""}</td>
3818
+ <td>${date}</td>
3819
+ <td>${interpretation}</td>
3820
+ <td>${comments}</td>
3821
+ </tr>`;
3372
3822
  }
3373
- html += `</tbody>
3374
- </table>`;
3823
+ html += `</tbody></table>`;
3375
3824
  }
3376
3825
  if (clinicalImpressions.length > 0) {
3377
- html += `<h3>Clinical Impressions</h3>
3378
- <table>
3379
- <thead>
3380
- <tr>
3381
- <th>Date</th>
3382
- <th>Status</th>
3383
- <th>Description</th>
3384
- <th>Summary</th>
3385
- <th>Findings</th>
3386
- </tr>
3387
- </thead>
3388
- <tbody>`;
3826
+ html += `<table><thead><tr><th>Date</th><th>Status</th><th>Description</th><th>Summary</th><th>Findings</th></tr></thead><tbody>`;
3389
3827
  for (const impression of clinicalImpressions) {
3390
3828
  let formattedDate = "";
3391
3829
  if (impression.effectiveDateTime) {
3392
- formattedDate = templateUtilities.renderTime(
3393
- impression.effectiveDateTime,
3394
- timezone
3395
- );
3830
+ formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
3396
3831
  } else if (impression.effectivePeriod) {
3397
- formattedDate = templateUtilities.renderPeriod(
3398
- impression.effectivePeriod,
3399
- timezone
3400
- );
3832
+ formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
3401
3833
  } else if (impression.date) {
3402
3834
  formattedDate = templateUtilities.renderDate(impression.date);
3403
3835
  }
@@ -3405,23 +3837,24 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3405
3837
  if (impression.finding && impression.finding.length > 0) {
3406
3838
  findingsHtml = "<ul>";
3407
3839
  for (const finding of impression.finding) {
3408
- const findingText = finding.itemCodeableConcept ? templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(finding.itemCodeableConcept)) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3840
+ const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3409
3841
  const cause = finding.basis || "";
3410
3842
  findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
3411
3843
  }
3412
3844
  findingsHtml += "</ul>";
3413
3845
  }
3414
- html += `
3415
- <tr id="${templateUtilities.narrativeLinkId(impression)}">
3416
- <td>${formattedDate}</td>
3417
- <td>${impression.status || ""}</td>
3418
- <td>${impression.description || ""}</td>
3419
- <td>${impression.summary || ""}</td>
3420
- <td>${findingsHtml}</td>
3421
- </tr>`;
3846
+ html += `<tr id="${templateUtilities.narrativeLinkId(impression)}">
3847
+ <td>${formattedDate}</td>
3848
+ <td>${impression.status || ""}</td>
3849
+ <td>${impression.description || ""}</td>
3850
+ <td>${impression.summary || ""}</td>
3851
+ <td>${findingsHtml}</td>
3852
+ </tr>`;
3422
3853
  }
3423
- html += `</tbody>
3424
- </table>`;
3854
+ html += `</tbody></table>`;
3855
+ }
3856
+ if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
3857
+ html += `<p>No functional status information available.</p>`;
3425
3858
  }
3426
3859
  return html;
3427
3860
  }
@@ -3446,34 +3879,110 @@ var PregnancyTemplate = class _PregnancyTemplate {
3446
3879
  */
3447
3880
  static generateStaticNarrative(resources, timezone) {
3448
3881
  const templateUtilities = new TemplateUtilities(resources);
3449
- const observations = resources.map((entry) => entry) || [];
3450
- observations.sort((a, b) => {
3882
+ const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
3883
+ const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
3884
+ const observations = filteredResources.filter((r) => r.resourceType === "Observation");
3885
+ const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
3886
+ const EDD_LOINC = "11778-8";
3887
+ const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
3888
+ const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
3889
+ const pregnancyStatusObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code && pregnancyStatusCodes.includes(c.code))).sort((a, b) => {
3451
3890
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3452
3891
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3453
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3892
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3893
+ })[0];
3894
+ const eddObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code === EDD_LOINC)).sort((a, b) => {
3895
+ const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3896
+ const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3897
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3898
+ })[0];
3899
+ const historyObs = observations.filter(
3900
+ (obs) => obs.code?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code)) || obs.valueCodeableConcept?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code))
3901
+ ).sort((a, b) => {
3902
+ const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3903
+ const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3904
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3454
3905
  });
3455
- let html = `
3456
- <table>
3457
- <thead>
3458
- <tr>
3459
- <th>Result</th>
3460
- <th>Comments</th>
3461
- <th>Date</th>
3462
- </tr>
3463
- </thead>
3464
- <tbody>`;
3465
- for (const resource of observations) {
3466
- const obs = resource;
3906
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
3907
+ return `<p>No history of pregnancy found.</p>`;
3908
+ }
3909
+ let html = `<p>This list includes Observation and Condition resources relevant to pregnancy, sorted by date (most recent first).</p>`;
3910
+ html += `
3911
+ <table>
3912
+ <thead>
3913
+ <tr>
3914
+ <th>Result</th>
3915
+ <th>Code (System)</th>
3916
+ <th>Comments</th>
3917
+ <th>Date</th>
3918
+ <th>Source</th>
3919
+ </tr>
3920
+ </thead>
3921
+ <tbody>`;
3922
+ function renderRow({ id, result, comments, date, codeSystem, owner }) {
3467
3923
  html += `
3468
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
3469
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
3470
- <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3471
- <td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
3472
- </tr>`;
3924
+ <tr id="${id}">
3925
+ <td class="Result">${result}</td>
3926
+ <td class="CodeSystem">${codeSystem}</td>
3927
+ <td class="Comments">${comments}</td>
3928
+ <td class="Date">${date}</td>
3929
+ <td class="Source">${owner}</td>
3930
+ </tr>`;
3931
+ }
3932
+ const rowResources = [];
3933
+ if (pregnancyStatusObs) {
3934
+ const date = pregnancyStatusObs.effectiveDateTime || pregnancyStatusObs.effectivePeriod?.start;
3935
+ rowResources.push({ resource: pregnancyStatusObs, date, type: "status" });
3936
+ }
3937
+ if (eddObs) {
3938
+ const date = eddObs.effectiveDateTime || eddObs.effectivePeriod?.start;
3939
+ rowResources.push({ resource: eddObs, date, type: "edd" });
3940
+ }
3941
+ for (const obs of historyObs) {
3942
+ const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
3943
+ rowResources.push({ resource: obs, date, type: "history" });
3944
+ }
3945
+ for (const cond of conditions) {
3946
+ const condition = cond;
3947
+ const date = condition.onsetDateTime || condition.onsetPeriod?.start;
3948
+ rowResources.push({ resource: condition, date, type: "condition" });
3949
+ }
3950
+ rowResources.sort((a, b) => {
3951
+ if (!a.date && !b.date) return 0;
3952
+ if (!a.date) return 1;
3953
+ if (!b.date) return -1;
3954
+ return new Date(b.date).getTime() - new Date(a.date).getTime();
3955
+ });
3956
+ for (const { resource, date, type } of rowResources) {
3957
+ let result = "", comments = "", dateStr = "", codeSystem = "";
3958
+ const id = templateUtilities.narrativeLinkId(resource);
3959
+ if (type === "status") {
3960
+ result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
3961
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3962
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3963
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3964
+ } else if (type === "edd") {
3965
+ result = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
3966
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3967
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3968
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3969
+ } else if (type === "history") {
3970
+ result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
3971
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3972
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3973
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3974
+ } else if (type === "condition") {
3975
+ result = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(resource.code));
3976
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3977
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3978
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3979
+ }
3980
+ const owner = templateUtilities.getOwnerTag(resource);
3981
+ renderRow({ id, result, comments, date: dateStr, codeSystem, owner });
3473
3982
  }
3474
3983
  html += `
3475
- </tbody>
3476
- </table>`;
3984
+ </tbody>
3985
+ </table>`;
3477
3986
  return html;
3478
3987
  }
3479
3988
  };
@@ -3503,8 +4012,9 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3503
4012
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
3504
4013
  static generateStaticNarrative(resources, timezone) {
3505
4014
  const templateUtilities = new TemplateUtilities(resources);
3506
- let html = `
3507
- <table>
4015
+ let html = `<p>This list includes all Consent resources, sorted by date (most recent first).</p>
4016
+ `;
4017
+ html += `<table>
3508
4018
  <thead>
3509
4019
  <tr>
3510
4020
  <th>Scope</th>
@@ -3517,8 +4027,8 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3517
4027
  for (const resourceItem of resources) {
3518
4028
  const consent = resourceItem;
3519
4029
  html += `
3520
- <tr id="${templateUtilities.narrativeLinkId(consent)}">
3521
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
4030
+ <tr>
4031
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display"))}</td>
3522
4032
  <td>${consent.status || ""}</td>
3523
4033
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
3524
4034
  <td>${consent.dateTime || ""}</td>
@@ -3551,7 +4061,7 @@ var TypeScriptTemplateMapper = class {
3551
4061
  resources,
3552
4062
  timezone,
3553
4063
  now
3554
- ) : templateClass.generateNarrative(resources, timezone);
4064
+ ) : templateClass.generateNarrative(resources, timezone, now);
3555
4065
  }
3556
4066
  };
3557
4067
  // Map of section types to their template classes