@imranq2/fhirpatientsummary 1.0.28 → 1.0.30

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 +761 -267
  2. package/dist/index.js +761 -267
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -99,6 +99,13 @@ var PREGNANCY_LOINC_CODES = {
99
99
  "33065-4": "Ectopic Pregnancy"
100
100
  }
101
101
  };
102
+ var PREGNANCY_SNOMED_CODES = [
103
+ // Add SNOMED CT codes for pregnancy history here, e.g.:
104
+ "72892002",
105
+ // Pregnancy (finding)
106
+ "77386006"
107
+ // History of pregnancy (situation)
108
+ ];
102
109
  var SOCIAL_HISTORY_LOINC_CODES = {
103
110
  "72166-2": "Tobacco Use",
104
111
  "74013-4": "Alcohol Use"
@@ -108,6 +115,29 @@ var BLOOD_PRESSURE_LOINC_CODES = {
108
115
  SYSTOLIC: "8480-6",
109
116
  DIASTOLIC: "8462-4"
110
117
  };
118
+ var ESSENTIAL_LAB_PANELS = {
119
+ // Top 20 Most Ordered Panels
120
+ "24323-8": "Comprehensive Metabolic Panel (CMP)",
121
+ "24320-4": "Basic Metabolic Panel (BMP)",
122
+ "58410-2": "Complete Blood Count (CBC)",
123
+ "57021-8": "CBC with Differential",
124
+ "24331-1": "Lipid Panel",
125
+ "57698-3": "Lipid Panel with Direct LDL",
126
+ "24325-3": "Hepatic Function Panel",
127
+ "24362-6": "Renal Function Panel",
128
+ "24326-1": "Electrolyte Panel",
129
+ "24348-5": "Thyroid Panel",
130
+ "24356-8": "Urinalysis Complete",
131
+ "24352-7": "Iron Studies Panel",
132
+ "34714-6": "Coagulation Panel",
133
+ "24364-2": "Prenatal Panel",
134
+ "24108-3": "Acute Hepatitis Panel",
135
+ "24110-9": "Hepatitis B Panel",
136
+ "34574-4": "Arthritis Panel",
137
+ "24360-0": "Anemia Panel",
138
+ "80235-8": "Cardiac Markers Panel",
139
+ "69738-3": "CBC with Auto Differential"
140
+ };
111
141
 
112
142
  // src/structures/ips_section_constants.ts
113
143
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -132,34 +162,42 @@ var IPSSectionResourceFilters = {
132
162
  // Only include completed immunizations
133
163
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
134
164
  // Only include vital sign Observations (category.coding contains 'vital-signs')
135
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c.code === "vital-signs")),
165
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, "vital-signs", c.system))),
136
166
  // Includes DeviceUseStatement. Device is needed for linked device details
137
167
  ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
138
- // Only include finalized diagnostic reports
139
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
168
+ // Only include finalized diagnostic reports and relevant observations
169
+ ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "DiagnosticReport" && resource.status === "final" || resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, RESULT_SUMMARY_OBSERVATION_CATEGORIES, c.system))),
140
170
  // Only include completed procedures
141
171
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
142
172
  // Only include social history Observations
143
- ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code?.coding?.some((c) => Object.keys(SOCIAL_HISTORY_LOINC_CODES).includes(c.code)),
144
- // Only include pregnancy history Observations
145
- ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS).includes(c.code)) || resource.valueCodeableConcept?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME).includes(c.code))),
146
- // Only include Conditions or completed ClinicalImpressions
147
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
173
+ ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
174
+ // Only include pregnancy history Observations or relevant Conditions
175
+ ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "Condition" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")),
176
+ // Only include Observations with LOINC 47420-5, category 'functional-status', or category display containing 'functional', and completed ClinicalImpressions
177
+ ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, "47420-5", "http://loinc.org") || resource.category?.some(
178
+ (cat) => cat.coding?.some(
179
+ (c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
180
+ )
181
+ )) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
148
182
  // Only include resolved medical history Conditions
149
183
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
150
184
  // Only include active care plans
151
185
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
152
186
  // Only include active advance directives (Consent resources)
153
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
187
+ // TODO: disable this until we right logic to get these
188
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
154
189
  };
155
190
  var IPSSectionSummaryCompositionFilter = {
156
- ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
157
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
158
- ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
159
- ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
160
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
191
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
192
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
193
+ ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
194
+ ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
195
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "medication_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
161
196
  // [IPSSections.DIAGNOSTIC_REPORTS]: (resource) => resource.resourceType === 'Composition' && resource.type?.coding?.some((c: any) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && ["lab_summary_document", "diagnosticreportlab_summary_document"].includes(c.code)),
162
- ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
197
+ ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "procedure_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
198
+ };
199
+ var IPSSectionSummaryIPSCompositionFilter = {
200
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "ips_vital_summary_document")
163
201
  };
164
202
  var IPSSectionResourceHelper = class {
165
203
  static getResourceFilterForSection(section) {
@@ -173,10 +211,50 @@ var IPSSectionResourceHelper = class {
173
211
  static getSummaryCompositionFilterForSection(section) {
174
212
  return IPSSectionSummaryCompositionFilter[section];
175
213
  }
214
+ static getSummaryIPSCompositionFilterForSection(section) {
215
+ return IPSSectionSummaryIPSCompositionFilter[section];
216
+ }
176
217
  };
218
+ function codingMatches(coding, code, system) {
219
+ if (!coding || !coding.system) return false;
220
+ if (Array.isArray(code)) {
221
+ return coding.system === system && code.includes(coding.code ?? "");
222
+ }
223
+ return coding.system === system && coding.code === code;
224
+ }
225
+ function codeableConceptMatches(codeableConcept, code, system) {
226
+ if (!codeableConcept || !Array.isArray(codeableConcept.coding)) return false;
227
+ return codeableConcept.coding.some((coding) => codingMatches(coding, code, system));
228
+ }
177
229
 
178
230
  // src/narratives/templates/typescript/TemplateUtilities.ts
179
231
  var import_luxon = require("luxon");
232
+
233
+ // src/structures/codingSystemDisplayNames.ts
234
+ var CODING_SYSTEM_DISPLAY_NAMES = {
235
+ "http://snomed.info/sct": "SNOMED CT",
236
+ "http://loinc.org": "LOINC",
237
+ "http://hl7.org/fhir/sid/icd-10": "ICD-10",
238
+ "http://hl7.org/fhir/sid/icd-10-cm": "ICD-10-CM",
239
+ "http://hl7.org/fhir/sid/icd-9": "ICD-9",
240
+ "http://hl7.org/fhir/sid/cvx": "CVX",
241
+ "http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
242
+ "http://www.ama-assn.org/go/cpt": "CPT",
243
+ "http://unitsofmeasure.org": "UCUM",
244
+ "http://e-imo.com/products/problem-it": "IMO Problem IT",
245
+ "2.16.840.1.113883.6.285": "HCPCS Level II",
246
+ "https://fhir.cerner.com/4ff3b259-e48d-4066-8b35-a6a051f2802a/codeSet/72": "Cerner Code Set 72",
247
+ "http://hl7.org/fhir/sid/ndc": "NDC",
248
+ // Added mapping for NDC
249
+ "urn:oid:2.16.840.1.113883.12.292": "CVX",
250
+ "http://terminology.hl7.org/CodeSystem/data-absent-reason": "Data Absent Reason",
251
+ // Added mapping for Data Absent Reason
252
+ "2.16.840.1.113883.6.208": "NDDF"
253
+ // Add more as needed
254
+ };
255
+ var codingSystemDisplayNames_default = CODING_SYSTEM_DISPLAY_NAMES;
256
+
257
+ // src/narratives/templates/typescript/TemplateUtilities.ts
180
258
  var TemplateUtilities = class {
181
259
  /**
182
260
  * Constructor to initialize the TemplateUtilities with a FHIR resources
@@ -186,47 +264,63 @@ var TemplateUtilities = class {
186
264
  this.resources = resources;
187
265
  }
188
266
  /**
189
- * Formats a CodeableConcept object
190
- * @param cc - The CodeableConcept object
191
- * @param field - Optional specific field to return
192
- * @returns Formatted string representation
267
+ * Returns the preferred coding from a list of codings.
268
+ * If a coding has an extension with url 'https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence' and valueCode 'preferred', returns that coding.
269
+ * Otherwise, returns the first coding if it exists, else null.
270
+ * @param codings Array of coding objects
271
+ * @returns The preferred coding object or null
193
272
  */
194
- codeableConcept(cc, field) {
195
- if (!cc) {
196
- return "";
273
+ getPreferredCoding(codings) {
274
+ if (!Array.isArray(codings) || codings.length === 0) return null;
275
+ for (const coding of codings) {
276
+ if (Array.isArray(coding.extension)) {
277
+ for (const ext of coding.extension) {
278
+ if (ext.url === "https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence" && ext.valueCode === "preferred") {
279
+ return coding;
280
+ }
281
+ }
282
+ }
197
283
  }
284
+ return codings[0] || null;
285
+ }
286
+ /**
287
+ * Returns the display value from a CodeableConcept
288
+ * @param cc - The CodeableConcept object
289
+ * @param field - Optional specific field to extract (e.g., 'text', 'display', 'code')
290
+ * @returns Display string or empty string
291
+ */
292
+ codeableConceptDisplay(cc, field) {
293
+ if (!cc) return "";
198
294
  if (field) {
199
295
  if (cc[field]) {
200
296
  return cc[field];
201
- } else if (cc.coding && cc.coding[0] && cc.coding[0][field]) {
202
- return cc.coding[0][field];
297
+ } else if (cc.coding && cc.coding.length > 0) {
298
+ const preferredCoding = this.getPreferredCoding(cc.coding);
299
+ if (preferredCoding && preferredCoding[field]) {
300
+ return preferredCoding[field];
301
+ }
203
302
  }
204
303
  }
205
- if (cc.text) {
206
- return cc.text;
207
- } else if (cc.coding && cc.coding[0]) {
208
- if (cc.coding[0].display) {
209
- return cc.coding[0].display;
210
- } else if (cc.coding[0].code) {
211
- return cc.coding[0].code;
212
- }
304
+ if (cc.text) return cc.text;
305
+ if (cc.coding && cc.coding.length > 0) {
306
+ const preferredCoding = this.getPreferredCoding(cc.coding);
307
+ if (preferredCoding && preferredCoding.display) return preferredCoding.display;
213
308
  }
214
309
  return "";
215
310
  }
216
- resolveReference(ref) {
217
- if (!ref || !this.resources) {
218
- return null;
219
- }
220
- const referenceParts = ref.reference?.split("/");
221
- if (!referenceParts || referenceParts.length !== 2) {
222
- return null;
223
- }
224
- const referenceResourceType = referenceParts[0];
225
- const referenceResourceId = referenceParts[1];
226
- const resource = this.resources.find((entry) => {
227
- return entry.resourceType === referenceResourceType && entry.id === referenceResourceId;
228
- });
229
- return resource ? resource : null;
311
+ /**
312
+ * Returns the code and system from a CodeableConcept
313
+ * @param cc - The CodeableConcept object
314
+ * @returns Object with code and system, or empty strings if not present
315
+ */
316
+ codeableConceptCoding(cc) {
317
+ if (!cc || !cc.coding || !cc.coding.length) return "";
318
+ const preferredCoding = this.getPreferredCoding(cc.coding);
319
+ if (!preferredCoding) return "";
320
+ const code = preferredCoding.code || "";
321
+ const system = preferredCoding.system || "";
322
+ const systemDisplay = codingSystemDisplayNames_default[system] || system;
323
+ return code ? `${code} (${systemDisplay})` : "";
230
324
  }
231
325
  /**
232
326
  * Renders a Device reference
@@ -274,7 +368,7 @@ var TemplateUtilities = class {
274
368
  return "";
275
369
  }
276
370
  if (medicationType.medicationCodeableConcept) {
277
- return this.codeableConcept(medicationType.medicationCodeableConcept);
371
+ return this.codeableConceptDisplay(medicationType.medicationCodeableConcept);
278
372
  } else if (medicationType.medicationReference) {
279
373
  return this.renderMedicationRef(medicationType.medicationReference);
280
374
  }
@@ -299,7 +393,7 @@ var TemplateUtilities = class {
299
393
  */
300
394
  renderMedicationCode(medication) {
301
395
  if (medication && medication.code) {
302
- return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
396
+ return this.renderTextAsHtml(this.codeableConceptDisplay(medication.code));
303
397
  }
304
398
  return "";
305
399
  }
@@ -468,7 +562,7 @@ var TemplateUtilities = class {
468
562
  */
469
563
  firstFromCodeableConceptList(list) {
470
564
  if (list && Array.isArray(list) && list[0]) {
471
- return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
565
+ return this.renderTextAsHtml(this.codeableConceptDisplay(list[0]));
472
566
  }
473
567
  return "";
474
568
  }
@@ -808,16 +902,28 @@ var TemplateUtilities = class {
808
902
  return this.renderMedicationCode(medicationSource);
809
903
  }
810
904
  if (typeof medicationSource === "object" && ("coding" in medicationSource || "text" in medicationSource)) {
811
- return this.codeableConcept(medicationSource);
905
+ return this.codeableConceptDisplay(medicationSource);
812
906
  }
813
907
  if (typeof medicationSource === "object" && "reference" in medicationSource) {
814
908
  const medication = this.resolveReference(medicationSource);
815
909
  if (medication && medication.code) {
816
- return this.codeableConcept(medication.code);
910
+ return this.codeableConceptDisplay(medication.code);
817
911
  }
818
912
  }
819
913
  return "";
820
914
  }
915
+ /**
916
+ * Returns the owner tag from the resource meta.security array.
917
+ * @param resource - FHIR resource with meta tag
918
+ * @returns The owner code if found, otherwise undefined
919
+ */
920
+ getOwnerTag(resource) {
921
+ if (!resource?.meta?.security) return "";
922
+ const ownerEntry = resource.meta.security.find(
923
+ (sec) => sec.system === "https://www.icanbwell.com/owner" && !!sec.code
924
+ );
925
+ return ownerEntry?.code;
926
+ }
821
927
  /**
822
928
  * Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
823
929
  * This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
@@ -981,6 +1087,21 @@ var TemplateUtilities = class {
981
1087
  const denominatorUnit = valueRatio.denominator.unit ? ` ${valueRatio.denominator.unit}` : "";
982
1088
  return `${numerator}${numeratorUnit} / ${denominator}${denominatorUnit}`;
983
1089
  }
1090
+ /**
1091
+ * Finds the resource that matches the reference
1092
+ * @param ref - Reference to a resource
1093
+ * @returns The resource or null
1094
+ */
1095
+ resolveReference(ref) {
1096
+ if (!ref || !this.resources) {
1097
+ return null;
1098
+ }
1099
+ const refId = ref.reference?.split("/")[1];
1100
+ const refType = ref.reference?.split("/")[0];
1101
+ return this.resources.find(
1102
+ (resource) => resource.resourceType === refType && resource.id === refId
1103
+ ) || null;
1104
+ }
984
1105
  };
985
1106
 
986
1107
  // src/constants.ts
@@ -1609,7 +1730,7 @@ var PatientTemplate = class _PatientTemplate {
1609
1730
  const uniqueLanguages = /* @__PURE__ */ new Set();
1610
1731
  const preferredLanguages = /* @__PURE__ */ new Set();
1611
1732
  patient.communication.forEach((comm) => {
1612
- const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
1733
+ const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(comm.language));
1613
1734
  if (language) {
1614
1735
  if (comm.preferred) {
1615
1736
  preferredLanguages.add(language);
@@ -1656,14 +1777,18 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1656
1777
  <thead>
1657
1778
  <tr>
1658
1779
  <th>Allergen</th>
1780
+ <th>Code (System)</th>
1659
1781
  <th>Criticality</th>
1660
1782
  <th>Recorded Date</th>
1783
+ <th>Source</th>
1661
1784
  </tr>
1662
1785
  </thead>
1663
1786
  <tbody>`;
1664
1787
  for (const resourceItem of resources) {
1665
1788
  for (const rowData of resourceItem.section ?? []) {
1789
+ const sectionCodeableConcept = rowData.code;
1666
1790
  const data = {};
1791
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
1667
1792
  for (const columnData of rowData.section ?? []) {
1668
1793
  switch (columnData.title) {
1669
1794
  case "Allergen Name":
@@ -1675,6 +1800,9 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1675
1800
  case "Recorded Date":
1676
1801
  data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1677
1802
  break;
1803
+ case "Source":
1804
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1805
+ break;
1678
1806
  default:
1679
1807
  break;
1680
1808
  }
@@ -1682,9 +1810,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1682
1810
  isSummaryCreated = true;
1683
1811
  html += `
1684
1812
  <tr>
1685
- <td>${data["allergen"] ?? "-"}</td>
1686
- <td>${data["criticality"] ?? "-"}</td>
1687
- <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? "-"}</td>
1813
+ <td>${data["allergen"] ?? ""}</td>
1814
+ <td>${data["codeSystem"] ?? ""}</td>
1815
+ <td>${data["criticality"] ?? ""}</td>
1816
+ <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? ""}</td>
1817
+ <td>${data["source"] ?? ""}</td>
1688
1818
  </tr>`;
1689
1819
  }
1690
1820
  }
@@ -1732,10 +1862,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1732
1862
  <tr>
1733
1863
  <th>Allergen</th>
1734
1864
  <th>Status</th>
1865
+ <th>Code (System)</th>
1735
1866
  <th>Category</th>
1736
1867
  <th>Reaction</th>
1737
1868
  <th>Onset Date</th>
1738
1869
  <th>Comments</th>
1870
+ <th>Source</th>
1739
1871
  </tr>
1740
1872
  </thead>
1741
1873
  <tbody>`;
@@ -1759,11 +1891,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1759
1891
  <tr>
1760
1892
  <th>Allergen</th>
1761
1893
  <th>Status</th>
1894
+ <th>Code (System)</th>
1762
1895
  <th>Category</th>
1763
1896
  <th>Reaction</th>
1764
1897
  <th>Onset Date</th>
1765
1898
  <th>Comments</th>
1766
1899
  <th>Resolved Date</th>
1900
+ <th>Source</th>
1767
1901
  </tr>
1768
1902
  </thead>
1769
1903
  <tbody>`;
@@ -1794,14 +1928,16 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1794
1928
  for (const allergy of allergies) {
1795
1929
  html += `
1796
1930
  <tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
1797
- <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
1798
- <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
1799
- <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
1800
- <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
1801
- <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
1802
- <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
1931
+ <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.code))}</span></td>
1932
+ <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.clinicalStatus)) || ""}</td>
1933
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(allergy.code)}</td>
1934
+ <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || ""}</td>
1935
+ <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || ""}</td>
1936
+ <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || ""}</td>
1937
+ <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>
1938
+ <td class="Source">${templateUtilities.getOwnerTag(allergy)}</td>`;
1803
1939
  if (includeResolved) {
1804
- let endDate = "-";
1940
+ let endDate = "";
1805
1941
  if (allergy.extension && Array.isArray(allergy.extension)) {
1806
1942
  const endDateExt = allergy.extension.find(
1807
1943
  (ext) => ext.url === "http://hl7.org/fhir/StructureDefinition/allergyintolerance-resolutionDate"
@@ -1825,10 +1961,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1825
1961
  * Generate HTML narrative for Medication resources
1826
1962
  * @param resources - FHIR Medication resources
1827
1963
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1964
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1828
1965
  * @returns HTML string for rendering
1829
1966
  */
1830
- generateNarrative(resources, timezone) {
1831
- return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
1967
+ generateNarrative(resources, timezone, now) {
1968
+ return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone, now);
1832
1969
  }
1833
1970
  /**
1834
1971
  * Generate HTML narrative for Medication resources using summary
@@ -1841,23 +1978,27 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1841
1978
  const templateUtilities = new TemplateUtilities(resources);
1842
1979
  let isSummaryCreated = false;
1843
1980
  const currentDate = now || /* @__PURE__ */ new Date();
1844
- const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
1981
+ const twoYearsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 24, currentDate.getDate());
1845
1982
  let html = `
1846
1983
  <div>
1847
1984
  <table>
1848
1985
  <thead>
1849
1986
  <tr>
1850
1987
  <th>Medication</th>
1988
+ <th>Code (System)</th>
1851
1989
  <th>Status</th>
1852
1990
  <th>Sig</th>
1853
1991
  <th>Days of Supply</th>
1854
1992
  <th>Refills</th>
1855
1993
  <th>Start Date</th>
1994
+ <th>Source</th>
1856
1995
  </tr>
1857
1996
  </thead>
1858
1997
  <tbody>`;
1998
+ let skippedMedications = 0;
1859
1999
  for (const resourceItem of resources) {
1860
2000
  for (const rowData of resourceItem.section ?? []) {
2001
+ const sectionCodeableConcept = rowData.code;
1861
2002
  const data = {};
1862
2003
  for (const columnData of rowData.section ?? []) {
1863
2004
  switch (columnData.title) {
@@ -1882,6 +2023,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1882
2023
  case "Authored On Date":
1883
2024
  data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1884
2025
  break;
2026
+ case "Source":
2027
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2028
+ break;
1885
2029
  default:
1886
2030
  break;
1887
2031
  }
@@ -1893,16 +2037,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1893
2037
  startDateObj = void 0;
1894
2038
  }
1895
2039
  }
1896
- if (data["status"] === "active" || startDateObj && startDateObj >= twelveMonthsAgo) {
2040
+ if (!(data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo)) {
2041
+ skippedMedications++;
2042
+ }
2043
+ if (data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo) {
1897
2044
  isSummaryCreated = true;
1898
2045
  html += `
1899
2046
  <tr>
1900
2047
  <td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
2048
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
1901
2049
  <td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
1902
2050
  <td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
1903
2051
  <td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
1904
2052
  <td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
1905
2053
  <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
2054
+ <td>${templateUtilities.renderTextAsHtml(data["source"])}</td>
1906
2055
  </tr>`;
1907
2056
  }
1908
2057
  }
@@ -1911,7 +2060,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1911
2060
  </tbody>
1912
2061
  </table>
1913
2062
  </div>`;
1914
- return isSummaryCreated ? html : void 0;
2063
+ if (skippedMedications > 0) {
2064
+ html += `
2065
+ <p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
2066
+ }
2067
+ if (isSummaryCreated || skippedMedications > 0) {
2068
+ return html;
2069
+ }
2070
+ return void 0;
1915
2071
  }
1916
2072
  /**
1917
2073
  * Safely parse a date string and return a valid Date object or null
@@ -1929,21 +2085,42 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1929
2085
  * Internal static implementation that actually generates the narrative
1930
2086
  * @param resources - FHIR Medication resources
1931
2087
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2088
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1932
2089
  * @returns HTML string for rendering
1933
2090
  */
1934
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1935
- static generateStaticNarrative(resources, timezone) {
2091
+ static generateStaticNarrative(resources, timezone, now) {
1936
2092
  const templateUtilities = new TemplateUtilities(resources);
1937
2093
  let html = "";
1938
2094
  const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
1939
2095
  const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
1940
2096
  const allActiveMedications = [];
2097
+ const currentDate = now || /* @__PURE__ */ new Date();
2098
+ const twoYearsAgo = new Date(currentDate);
2099
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2100
+ let skippedMedications = 0;
2101
+ const allMedications = [];
1941
2102
  medicationRequests.forEach((mr) => {
1942
- allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
2103
+ allMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1943
2104
  });
1944
2105
  medicationStatements.forEach((ms) => {
1945
- allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
2106
+ allMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1946
2107
  });
2108
+ for (const med of allMedications) {
2109
+ let dateString;
2110
+ if (med.type === "request") {
2111
+ const mr = med.resource;
2112
+ dateString = mr.dispenseRequest?.validityPeriod?.start || mr.authoredOn;
2113
+ } else {
2114
+ const ms = med.resource;
2115
+ dateString = ms.effectiveDateTime || ms.effectivePeriod?.start;
2116
+ }
2117
+ const dateObj = this.parseDate(dateString);
2118
+ if (!dateObj || dateObj < twoYearsAgo) {
2119
+ skippedMedications++;
2120
+ } else {
2121
+ allActiveMedications.push(med);
2122
+ }
2123
+ }
1947
2124
  const sortMedications = (medications) => {
1948
2125
  medications.sort((a, b) => {
1949
2126
  let dateStringA;
@@ -1974,7 +2151,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1974
2151
  sortMedications(allActiveMedications);
1975
2152
  html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
1976
2153
  }
1977
- return html;
2154
+ if (skippedMedications > 0) {
2155
+ html += `
2156
+ <p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
2157
+ }
2158
+ if (allActiveMedications.length > 0 || skippedMedications > 0) {
2159
+ return html;
2160
+ }
2161
+ return "";
1978
2162
  }
1979
2163
  /**
1980
2164
  * Extract MedicationRequest resources
@@ -2019,10 +2203,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2019
2203
  <tr>
2020
2204
  <th>Type</th>
2021
2205
  <th>Medication</th>
2206
+ <th>Code (System)</th>
2022
2207
  <th>Sig</th>
2023
2208
  <th>Dispense Quantity</th>
2024
2209
  <th>Refills</th>
2025
2210
  <th>Start Date</th>
2211
+ <th>Source</th>
2026
2212
  </tr>
2027
2213
  </thead>
2028
2214
  <tbody>`;
@@ -2031,27 +2217,31 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2031
2217
  let type;
2032
2218
  let medicationName;
2033
2219
  let sig;
2034
- let dispenseQuantity = "-";
2035
- let refills = "-";
2036
- let startDate = "-";
2220
+ let dispenseQuantity = "";
2221
+ let refills = "";
2222
+ let startDate = "";
2223
+ let codeSystemDisplay = "";
2037
2224
  if (medication.type === "request") {
2038
2225
  const mr = medication.resource;
2039
2226
  type = "Request";
2040
2227
  medicationName = templateUtilities.getMedicationName(
2041
2228
  mr.medicationReference || mr.medicationCodeableConcept
2042
2229
  );
2043
- sig = templateUtilities.concat(mr.dosageInstruction, "text") || "-";
2230
+ sig = templateUtilities.concat(mr.dosageInstruction, "text") || "";
2044
2231
  if (mr.dispenseRequest?.quantity) {
2045
2232
  const quantity = mr.dispenseRequest.quantity;
2046
2233
  if (quantity.value) {
2047
2234
  dispenseQuantity = `${quantity.value} ${quantity.unit || quantity.code || ""}`.trim();
2048
2235
  }
2049
2236
  }
2050
- refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
2237
+ refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "";
2051
2238
  if (mr.dispenseRequest?.validityPeriod) {
2052
- startDate = mr.dispenseRequest.validityPeriod.start || "-";
2239
+ startDate = mr.dispenseRequest.validityPeriod.start || "";
2053
2240
  } else {
2054
- startDate = mr.authoredOn || "-";
2241
+ startDate = mr.authoredOn || "";
2242
+ }
2243
+ if (mr.medicationCodeableConcept && mr.medicationCodeableConcept.coding && mr.medicationCodeableConcept.coding[0]) {
2244
+ codeSystemDisplay = templateUtilities.codeableConceptCoding(mr.medicationCodeableConcept);
2055
2245
  }
2056
2246
  } else {
2057
2247
  const ms = medication.resource;
@@ -2059,21 +2249,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2059
2249
  medicationName = templateUtilities.getMedicationName(
2060
2250
  ms.medicationReference || ms.medicationCodeableConcept
2061
2251
  );
2062
- sig = templateUtilities.concat(ms.dosage, "text") || "-";
2252
+ sig = templateUtilities.concat(ms.dosage, "text") || "";
2063
2253
  if (ms.effectiveDateTime) {
2064
2254
  startDate = ms.effectiveDateTime;
2065
2255
  } else if (ms.effectivePeriod) {
2066
- startDate = ms.effectivePeriod.start || "-";
2256
+ startDate = ms.effectivePeriod.start || "";
2257
+ }
2258
+ if (ms.medicationCodeableConcept && ms.medicationCodeableConcept.coding && ms.medicationCodeableConcept.coding[0]) {
2259
+ codeSystemDisplay = templateUtilities.codeableConceptCoding(ms.medicationCodeableConcept);
2067
2260
  }
2068
2261
  }
2069
2262
  html += `
2070
2263
  <tr${narrativeLinkId ? ` id="${narrativeLinkId}"` : ""}>
2071
2264
  <td>${type}</td>
2072
2265
  <td>${medicationName}<ul></ul></td>
2266
+ <td>${codeSystemDisplay}</td>
2073
2267
  <td>${sig}</td>
2074
2268
  <td>${dispenseQuantity}</td>
2075
2269
  <td>${refills}</td>
2076
2270
  <td>${startDate}</td>
2271
+ <td>${templateUtilities.getOwnerTag(medication.resource)}</td>
2077
2272
  </tr>`;
2078
2273
  }
2079
2274
  html += `
@@ -2114,14 +2309,18 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2114
2309
  <thead>
2115
2310
  <tr>
2116
2311
  <th>Immunization</th>
2312
+ <th>Code (System)</th>
2117
2313
  <th>Status</th>
2118
2314
  <th>Date</th>
2315
+ <th>Source</th>
2119
2316
  </tr>
2120
2317
  </thead>
2121
2318
  <tbody>`;
2122
2319
  for (const resourceItem of resources) {
2123
2320
  for (const rowData of resourceItem.section ?? []) {
2321
+ const sectionCodeableConcept = rowData.code;
2124
2322
  const data = {};
2323
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2125
2324
  for (const columnData of rowData.section ?? []) {
2126
2325
  switch (columnData.title) {
2127
2326
  case "Immunization Name":
@@ -2133,6 +2332,9 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2133
2332
  case "occurrenceDateTime":
2134
2333
  data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2135
2334
  break;
2335
+ case "Source":
2336
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2337
+ break;
2136
2338
  default:
2137
2339
  break;
2138
2340
  }
@@ -2141,9 +2343,11 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2141
2343
  isSummaryCreated = true;
2142
2344
  html += `
2143
2345
  <tr>
2144
- <td>${data["immunization"] ?? "-"}</td>
2145
- <td>${data["status"] ?? "-"}</td>
2146
- <td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? "-"}</td>
2346
+ <td>${data["immunization"] ?? ""}</td>
2347
+ <td>${data["codeSystem"] ?? ""}</td>
2348
+ <td>${data["status"] ?? ""}</td>
2349
+ <td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? ""}</td>
2350
+ <td>${data["source"] ?? ""}</td>
2147
2351
  </tr>`;
2148
2352
  }
2149
2353
  }
@@ -2167,12 +2371,14 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2167
2371
  <thead>
2168
2372
  <tr>
2169
2373
  <th>Immunization</th>
2374
+ <th>Code (System)</th>
2170
2375
  <th>Status</th>
2171
2376
  <th>Dose Number</th>
2172
2377
  <th>Manufacturer</th>
2173
2378
  <th>Lot Number</th>
2174
2379
  <th>Comments</th>
2175
2380
  <th>Date</th>
2381
+ <th>Source</th>
2176
2382
  </tr>
2177
2383
  </thead>
2178
2384
  <tbody>`;
@@ -2182,13 +2388,15 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2182
2388
  const imm = resourceItem;
2183
2389
  html += `
2184
2390
  <tr id="${templateUtilities.narrativeLinkId(imm)}">
2185
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
2391
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(imm.vaccineCode))}</td>
2392
+ <td>${templateUtilities.codeableConceptCoding(imm.vaccineCode)}</td>
2186
2393
  <td>${imm.status || ""}</td>
2187
2394
  <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
2188
2395
  <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
2189
2396
  <td>${imm.lotNumber || ""}</td>
2190
2397
  <td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
2191
2398
  <td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
2399
+ <td>${templateUtilities.getOwnerTag(imm)}</td>
2192
2400
  </tr>`;
2193
2401
  }
2194
2402
  }
@@ -2222,8 +2430,11 @@ var ProblemListTemplate = class _ProblemListTemplate {
2222
2430
  let html = ``;
2223
2431
  const activeConditions = resources.map((entry) => entry) || [];
2224
2432
  activeConditions.sort((a, b) => {
2225
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
2226
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
2433
+ if (!a.recordedDate && b.recordedDate) return -1;
2434
+ if (a.recordedDate && !b.recordedDate) return 1;
2435
+ if (!a.recordedDate && !b.recordedDate) return 0;
2436
+ const dateA = new Date(a.recordedDate).getTime();
2437
+ const dateB = new Date(b.recordedDate).getTime();
2227
2438
  return dateB - dateA;
2228
2439
  });
2229
2440
  html += `
@@ -2231,22 +2442,28 @@ var ProblemListTemplate = class _ProblemListTemplate {
2231
2442
  <thead>
2232
2443
  <tr>
2233
2444
  <th>Problem</th>
2445
+ <th>Code (System)</th>
2234
2446
  <th>Onset Date</th>
2235
2447
  <th>Recorded Date</th>
2448
+ <th>Source</th>
2236
2449
  </tr>
2237
2450
  </thead>
2238
2451
  <tbody>`;
2239
- const addedConditionCodes = /* @__PURE__ */ new Set();
2452
+ const seenCodeAndSystems = /* @__PURE__ */ new Set();
2240
2453
  for (const cond of activeConditions) {
2241
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2242
- if (!addedConditionCodes.has(conditionCode)) {
2243
- addedConditionCodes.add(conditionCode);
2244
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2245
- <td class="Name">${conditionCode}</td>
2246
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2247
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2248
- </tr>`;
2454
+ const conditionDisplay = templateUtilities.codeableConceptDisplay(cond.code);
2455
+ const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
2456
+ if (codeAndSystem && seenCodeAndSystems.has(codeAndSystem)) {
2457
+ continue;
2249
2458
  }
2459
+ seenCodeAndSystems.add(codeAndSystem);
2460
+ html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2461
+ <td class="Name">${conditionDisplay}</td>
2462
+ <td class="CodeSystem">${codeAndSystem}</td>
2463
+ <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2464
+ <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2465
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
2466
+ </tr>`;
2250
2467
  }
2251
2468
  html += `</tbody>
2252
2469
  </table>`;
@@ -2279,16 +2496,19 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2279
2496
  <table>
2280
2497
  <thead>
2281
2498
  <tr>
2282
- <th>Vital Name</th>
2499
+ <th>Name</th>
2500
+ <th>Code (System)</th>
2283
2501
  <th>Result</th>
2284
- <th>Reference Range</th>
2285
2502
  <th>Date</th>
2503
+ <th>Source</th>
2286
2504
  </tr>
2287
2505
  </thead>
2288
2506
  <tbody>`;
2289
2507
  for (const resourceItem of resources) {
2290
2508
  for (const rowData of resourceItem.section ?? []) {
2509
+ const sectionCodeableConcept = rowData.code;
2291
2510
  const data = {};
2511
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2292
2512
  for (const columnData of rowData.section ?? []) {
2293
2513
  const columnTitle = columnData.title;
2294
2514
  if (columnTitle) {
@@ -2316,10 +2536,11 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2316
2536
  isSummaryCreated = true;
2317
2537
  html += `
2318
2538
  <tr>
2319
- <td>${data["Vital Name"] ?? "-"}</td>
2320
- <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? "-"}</td>
2321
- <td>${templateUtilities.extractObservationSummaryReferenceRange(data) ?? "-"}</td>
2322
- <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "-"}</td>
2539
+ <td>${data["Vital Name"] ?? ""}</td>
2540
+ <td>${data["codeSystem"] ?? ""}</td>
2541
+ <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? ""}</td>
2542
+ <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
2543
+ <td>${data["Source"] ?? ""}</td>
2323
2544
  </tr>`;
2324
2545
  }
2325
2546
  }
@@ -2347,26 +2568,30 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2347
2568
  <table>
2348
2569
  <thead>
2349
2570
  <tr>
2350
- <th>Vital Name</th>
2571
+ <th>Name</th>
2572
+ <th>Code (System)</th>
2351
2573
  <th>Result</th>
2352
2574
  <th>Unit</th>
2353
2575
  <th>Interpretation</th>
2354
2576
  <th>Component(s)</th>
2355
2577
  <th>Comments</th>
2356
2578
  <th>Date</th>
2579
+ <th>Source</th>
2357
2580
  </tr>
2358
2581
  </thead>
2359
2582
  <tbody>`;
2360
2583
  for (const obs of observations) {
2361
2584
  html += `
2362
2585
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2363
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
2586
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code, "display"))}</td>
2587
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2364
2588
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2365
2589
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2366
2590
  <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
2367
2591
  <td>${templateUtilities.renderComponent(obs.component)}</td>
2368
2592
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
2369
2593
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
2594
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2370
2595
  </tr>`;
2371
2596
  }
2372
2597
  html += `
@@ -2439,10 +2664,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2439
2664
  * Generate HTML narrative for Diagnostic Results
2440
2665
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2441
2666
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2667
+ * @param now - Optional current date for filtering
2442
2668
  * @returns HTML string for rendering
2443
2669
  */
2444
- generateNarrative(resources, timezone) {
2445
- return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2670
+ generateNarrative(resources, timezone, now) {
2671
+ return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone, now);
2446
2672
  }
2447
2673
  /**
2448
2674
  * Helper function to format observation data fields
@@ -2655,6 +2881,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2655
2881
  break;
2656
2882
  case "valueRange.high.value":
2657
2883
  targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2884
+ targetData["valueType"] = "valueRange";
2658
2885
  break;
2659
2886
  case "valueRange.high.unit":
2660
2887
  targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2669,6 +2896,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2669
2896
  break;
2670
2897
  case "valueRatio.denominator.value":
2671
2898
  targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2899
+ targetData["valueType"] = "valueRatio";
2672
2900
  break;
2673
2901
  case "valueRatio.denominator.unit":
2674
2902
  targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2706,10 +2934,71 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2706
2934
  * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2707
2935
  * @param resources - FHIR Composition resources
2708
2936
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2937
+ * @param now - Optional current date for filtering
2709
2938
  * @returns HTML string for rendering
2710
2939
  */
2711
- generateSummaryNarrative(resources, timezone) {
2940
+ generateSummaryNarrative(resources, timezone, now) {
2712
2941
  const templateUtilities = new TemplateUtilities(resources);
2942
+ const currentDate = now || /* @__PURE__ */ new Date();
2943
+ const twoYearsAgo = new Date(currentDate);
2944
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2945
+ let skippedObservations = 0;
2946
+ let skippedDiagnosticReports = 0;
2947
+ for (const resourceItem of resources) {
2948
+ for (const rowData of resourceItem.section ?? []) {
2949
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2950
+ for (const columnData of rowData.section ?? []) {
2951
+ if (columnData.text?.div === "Observation.component" && columnData.section) {
2952
+ for (const componentSection of columnData.section) {
2953
+ const componentData = {};
2954
+ for (const nestedColumn of componentSection.section ?? []) {
2955
+ this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2956
+ }
2957
+ let compDate = void 0;
2958
+ if (componentData["effectiveDateTime"]) {
2959
+ compDate = new Date(componentData["effectiveDateTime"]);
2960
+ } else if (componentData["effectivePeriodStart"]) {
2961
+ compDate = new Date(componentData["effectivePeriodStart"]);
2962
+ }
2963
+ if (compDate && compDate < twoYearsAgo) {
2964
+ skippedObservations++;
2965
+ }
2966
+ }
2967
+ } else {
2968
+ const data = {};
2969
+ this.extractSummaryObservationFields(columnData, data, templateUtilities);
2970
+ let obsDate = void 0;
2971
+ if (data["effectiveDateTime"]) {
2972
+ obsDate = new Date(data["effectiveDateTime"]);
2973
+ } else if (data["effectivePeriodStart"]) {
2974
+ obsDate = new Date(data["effectivePeriodStart"]);
2975
+ }
2976
+ if (obsDate && obsDate < twoYearsAgo) {
2977
+ skippedObservations++;
2978
+ }
2979
+ }
2980
+ }
2981
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2982
+ let issuedDate = void 0;
2983
+ let status = void 0;
2984
+ let reportFound = false;
2985
+ for (const columnData of rowData.section ?? []) {
2986
+ if (columnData.title === "Issued Date") {
2987
+ issuedDate = columnData.text?.div ? new Date(columnData.text.div) : void 0;
2988
+ }
2989
+ if (columnData.title === "Status") {
2990
+ status = columnData.text?.div;
2991
+ }
2992
+ if (columnData.title === "Diagnostic Report Name") {
2993
+ reportFound = true;
2994
+ }
2995
+ }
2996
+ if (status === "final" && issuedDate && issuedDate < twoYearsAgo && reportFound) {
2997
+ skippedDiagnosticReports++;
2998
+ }
2999
+ }
3000
+ }
3001
+ }
2713
3002
  let html = `
2714
3003
  <div>`;
2715
3004
  let observationhtml = `
@@ -2718,10 +3007,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2718
3007
  <table>
2719
3008
  <thead>
2720
3009
  <tr>
2721
- <th>Code</th>
3010
+ <th>Name</th>
3011
+ <th>Code (System)</th>
2722
3012
  <th>Result</th>
2723
3013
  <th>Reference Range</th>
2724
3014
  <th>Date</th>
3015
+ <th>Source</th>
2725
3016
  </tr>
2726
3017
  </thead>
2727
3018
  <tbody>`;
@@ -2734,6 +3025,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2734
3025
  <th>Report</th>
2735
3026
  <th>Performer</th>
2736
3027
  <th>Issued</th>
3028
+ <th>Source</th>
2737
3029
  </tr>
2738
3030
  </thead>
2739
3031
  <tbody>`;
@@ -2741,7 +3033,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2741
3033
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2742
3034
  for (const resourceItem of resources) {
2743
3035
  for (const rowData of resourceItem.section ?? []) {
3036
+ const sectionCodeableConcept = rowData.code;
2744
3037
  const data = {};
3038
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2745
3039
  const components = [];
2746
3040
  for (const columnData of rowData.section ?? []) {
2747
3041
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
@@ -2751,7 +3045,13 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2751
3045
  for (const nestedColumn of componentSection.section ?? []) {
2752
3046
  this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2753
3047
  }
2754
- if (Object.keys(componentData).length > 0) {
3048
+ let compDate = void 0;
3049
+ if (componentData["effectiveDateTime"]) {
3050
+ compDate = new Date(componentData["effectiveDateTime"]);
3051
+ } else if (componentData["effectivePeriodStart"]) {
3052
+ compDate = new Date(componentData["effectivePeriodStart"]);
3053
+ }
3054
+ if (compDate && compDate >= twoYearsAgo && Object.keys(componentData).length > 0) {
2755
3055
  components.push(componentData);
2756
3056
  }
2757
3057
  }
@@ -2772,6 +3072,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2772
3072
  case "Status":
2773
3073
  data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2774
3074
  break;
3075
+ case "Source":
3076
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3077
+ break;
2775
3078
  default:
2776
3079
  break;
2777
3080
  }
@@ -2779,6 +3082,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2779
3082
  }
2780
3083
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2781
3084
  let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
3085
+ let obsDate = void 0;
3086
+ if (data["effectiveDateTime"]) {
3087
+ obsDate = new Date(data["effectiveDateTime"]);
3088
+ } else if (data["effectivePeriodStart"]) {
3089
+ obsDate = new Date(data["effectivePeriodStart"]);
3090
+ }
2782
3091
  if (!date && data["effectivePeriodStart"]) {
2783
3092
  date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2784
3093
  if (data["effectivePeriodEnd"]) {
@@ -2795,36 +3104,47 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2795
3104
  observationhtml += `
2796
3105
  <tr>
2797
3106
  <td>${componentCode}</td>
2798
- <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
2799
- <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
2800
- <td>${date ?? "-"}</td>
3107
+ <td></td>
3108
+ <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? ""}</td>
3109
+ <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? ""}</td>
3110
+ <td>${date ?? ""}</td>
3111
+ <td>${data["source"] ?? ""}</td>
2801
3112
  </tr>`;
2802
3113
  }
2803
3114
  }
2804
3115
  } else {
2805
- const code = data["code"] ?? "";
2806
- if (code && !observationAdded.has(code)) {
2807
- observationAdded.add(code);
2808
- this.formatSummaryObservationData(data);
2809
- observationhtml += `
3116
+ if (obsDate && obsDate >= twoYearsAgo) {
3117
+ const code = data["code"] ?? "";
3118
+ if (code && !observationAdded.has(code)) {
3119
+ observationAdded.add(code);
3120
+ this.formatSummaryObservationData(data);
3121
+ observationhtml += `
2810
3122
  <tr>
2811
- <td>${data["code"] ?? "-"}</td>
2812
- <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
2813
- <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
2814
- <td>${date ?? "-"}</td>
3123
+ <td>${data["code"] ?? ""}</td>
3124
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
3125
+ <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? ""}</td>
3126
+ <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? ""}</td>
3127
+ <td>${date ?? ""}</td>
3128
+ <td>${data["source"] ?? ""}</td>
2815
3129
  </tr>`;
3130
+ }
2816
3131
  }
2817
3132
  }
2818
3133
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2819
- if (data["status"] === "final") {
3134
+ let issuedDate = void 0;
3135
+ if (data["issued"]) {
3136
+ issuedDate = new Date(data["issued"]);
3137
+ }
3138
+ if (data["status"] === "final" && issuedDate && issuedDate >= twoYearsAgo) {
2820
3139
  const reportName = data["report"] ?? "";
2821
3140
  if (reportName && !diagnosticReportAdded.has(reportName)) {
2822
3141
  diagnosticReportAdded.add(reportName);
2823
3142
  diagnosticReporthtml += `
2824
3143
  <tr>
2825
- <td>${data["report"] ?? "-"}</td>
2826
- <td>${data["performer"] ?? "-"}</td>
2827
- <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
3144
+ <td>${data["report"] ?? ""}</td>
3145
+ <td>${data["performer"] ?? ""}</td>
3146
+ <td>${templateUtilities.renderTime(data["issued"], timezone) ?? ""}</td>
3147
+ <td>${data["source"] ?? ""}</td>
2828
3148
  </tr>`;
2829
3149
  }
2830
3150
  }
@@ -2837,6 +3157,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2837
3157
  </tbody>
2838
3158
  </table>
2839
3159
  </div>`;
3160
+ if (skippedObservations > 0) {
3161
+ html += `
3162
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3163
+ }
2840
3164
  }
2841
3165
  if (diagnosticReportAdded.size > 0) {
2842
3166
  html += diagnosticReporthtml;
@@ -2844,6 +3168,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2844
3168
  </tbody>
2845
3169
  </table>
2846
3170
  </div>`;
3171
+ if (skippedDiagnosticReports > 0) {
3172
+ html += `
3173
+ <p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
3174
+ }
2847
3175
  }
2848
3176
  html += `
2849
3177
  </div>`;
@@ -2853,12 +3181,32 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2853
3181
  * Internal static implementation that actually generates the narrative
2854
3182
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2855
3183
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3184
+ * @param now - Optional current date for filtering
2856
3185
  * @returns HTML string for rendering
2857
3186
  */
2858
- static generateStaticNarrative(resources, timezone) {
3187
+ static generateStaticNarrative(resources, timezone, now) {
2859
3188
  const templateUtilities = new TemplateUtilities(resources);
3189
+ const currentDate = now || /* @__PURE__ */ new Date();
3190
+ const twoYearsAgo = new Date(currentDate);
3191
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2860
3192
  let html = "";
2861
- const observations = this.getObservations(resources);
3193
+ let skippedObservations = 0;
3194
+ let skippedDiagnosticReports = 0;
3195
+ for (const resourceItem of resources) {
3196
+ if (resourceItem.resourceType === "Observation") {
3197
+ const obsDate = this.getObservationDate(resourceItem);
3198
+ if (obsDate && obsDate < twoYearsAgo) {
3199
+ skippedObservations++;
3200
+ }
3201
+ } else if (resourceItem.resourceType === "DiagnosticReport") {
3202
+ const issued = resourceItem.issued;
3203
+ const status = resourceItem.status;
3204
+ if (status === "final" && issued && new Date(issued) < twoYearsAgo) {
3205
+ skippedDiagnosticReports++;
3206
+ }
3207
+ }
3208
+ }
3209
+ const observations = this.getObservations(resources, twoYearsAgo);
2862
3210
  if (observations.length > 0) {
2863
3211
  observations.sort((a, b) => {
2864
3212
  const dateA = this.getObservationDate(a);
@@ -2867,16 +3215,22 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2867
3215
  });
2868
3216
  this.filterObservationForLoincCodes(observations);
2869
3217
  html += this.renderObservations(templateUtilities, observations, timezone);
3218
+ if (skippedObservations > 0) {
3219
+ html += `
3220
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3221
+ }
2870
3222
  }
2871
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2872
- const diagnosticReports = this.getDiagnosticReports(resources);
2873
- if (diagnosticReports.length > 0) {
2874
- diagnosticReports.sort((a, b) => {
2875
- const dateA = a.issued;
2876
- const dateB = b.issued;
2877
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2878
- });
2879
- html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
3223
+ const diagnosticReports = this.getDiagnosticReports(resources, twoYearsAgo).filter((resource) => !this.isPanelDiagnosticReport(resource));
3224
+ if (diagnosticReports.length > 0) {
3225
+ diagnosticReports.sort((a, b) => {
3226
+ const dateA = a.issued;
3227
+ const dateB = b.issued;
3228
+ return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3229
+ });
3230
+ html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
3231
+ if (skippedDiagnosticReports > 0) {
3232
+ html += `
3233
+ <p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
2880
3234
  }
2881
3235
  }
2882
3236
  return html;
@@ -2921,15 +3275,16 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2921
3275
  return obsDate;
2922
3276
  }
2923
3277
  /**
2924
- * Get all Observation resources from the resource array
2925
- * @param resources - FHIR resources array
2926
- * @returns Array of Observation resources
2927
- */
2928
- static getObservations(resources) {
3278
+ * Get all Observation resources from the resource array, filtered by twoYearsAgo
3279
+ * @param resources - FHIR resources array
3280
+ * @param twoYearsAgo - Date object representing the cutoff
3281
+ * @returns Array of Observation resources
3282
+ */
3283
+ static getObservations(resources, twoYearsAgo) {
2929
3284
  return resources.filter((resourceItem) => {
2930
3285
  if (resourceItem.resourceType === "Observation") {
2931
3286
  const obsDate = this.getObservationDate(resourceItem);
2932
- if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
3287
+ if (obsDate && obsDate >= twoYearsAgo) {
2933
3288
  return true;
2934
3289
  }
2935
3290
  }
@@ -2937,12 +3292,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2937
3292
  }).map((resourceItem) => resourceItem);
2938
3293
  }
2939
3294
  /**
2940
- * Get all DiagnosticReport resources from the resource array
3295
+ * Get all DiagnosticReport resources from the resource array, filtered by twoYearsAgo
2941
3296
  * @param resources - FHIR resources array
3297
+ * @param twoYearsAgo - Date object representing the cutoff
2942
3298
  * @returns Array of DiagnosticReport resources
2943
3299
  */
2944
- static getDiagnosticReports(resources) {
2945
- return resources.filter((resourceItem) => resourceItem.resourceType === "DiagnosticReport").map((resourceItem) => resourceItem);
3300
+ static getDiagnosticReports(resources, twoYearsAgo) {
3301
+ return resources.filter((resourceItem) => {
3302
+ if (resourceItem.resourceType === "DiagnosticReport") {
3303
+ const issued = resourceItem.issued;
3304
+ if (issued && new Date(issued) >= twoYearsAgo) {
3305
+ return true;
3306
+ }
3307
+ }
3308
+ return false;
3309
+ }).map((resourceItem) => resourceItem);
2946
3310
  }
2947
3311
  /**
2948
3312
  * Render HTML table for Observation resources
@@ -2953,32 +3317,36 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2953
3317
  */
2954
3318
  static renderObservations(templateUtilities, observations, timezone) {
2955
3319
  let html = "";
2956
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2957
- html += `
2958
- <h3>Observations</h3>`;
2959
- }
3320
+ html += `
3321
+ <h3>Observations</h3>`;
2960
3322
  html += `
2961
3323
  <table>
2962
3324
  <thead>
2963
3325
  <tr>
2964
- <th>Code</th>
3326
+ <th>Name</th>
3327
+ <th>Code (System)</th>
2965
3328
  <th>Result</th>
2966
3329
  <th>Reference Range</th>
2967
3330
  <th>Date</th>
3331
+ <th>Source</th>
2968
3332
  </tr>
2969
3333
  </thead>
2970
3334
  <tbody>`;
2971
3335
  const observationAdded = /* @__PURE__ */ new Set();
2972
3336
  for (const obs of observations) {
2973
- const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
2974
- if (!observationAdded.has(obsCode)) {
2975
- observationAdded.add(obsCode);
3337
+ const obsCodeDisplay = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code));
3338
+ const obsCodeAndSystem = templateUtilities.codeableConceptCoding(obs.code);
3339
+ if (!observationAdded.has(obsCodeDisplay) && !observationAdded.has(obsCodeAndSystem)) {
3340
+ observationAdded.add(obsCodeDisplay);
3341
+ observationAdded.add(obsCodeAndSystem);
2976
3342
  html += `
2977
3343
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2978
- <td>${obsCode}</td>
3344
+ <td>${obsCodeDisplay}</td>
3345
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2979
3346
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2980
3347
  <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
2981
3348
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3349
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2982
3350
  </tr>`;
2983
3351
  }
2984
3352
  }
@@ -3001,17 +3369,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3001
3369
  <thead>
3002
3370
  <tr>
3003
3371
  <th>Report</th>
3372
+ <th>Code (System)</th>
3004
3373
  <th>Category</th>
3005
3374
  <th>Result</th>
3006
3375
  <th>Issued</th>
3376
+ <th>Source</th>
3007
3377
  </tr>
3008
3378
  </thead>
3009
3379
  <tbody>`;
3010
3380
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
3011
3381
  for (const report of reports) {
3012
- const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
3013
- if (!diagnosticReportAdded.has(reportName)) {
3382
+ const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(report.code));
3383
+ const codeAndSystem = templateUtilities.codeableConceptCoding(report.code);
3384
+ if (!diagnosticReportAdded.has(reportName) && !diagnosticReportAdded.has(codeAndSystem)) {
3014
3385
  diagnosticReportAdded.add(reportName);
3386
+ diagnosticReportAdded.add(codeAndSystem);
3015
3387
  let resultCount = "";
3016
3388
  if (report.result && Array.isArray(report.result)) {
3017
3389
  resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
@@ -3019,9 +3391,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3019
3391
  html += `
3020
3392
  <tr id="${templateUtilities.narrativeLinkId(report)}">
3021
3393
  <td>${reportName}</td>
3394
+ <td>${codeAndSystem}</td>
3022
3395
  <td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
3023
3396
  <td>${resultCount}</td>
3024
3397
  <td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
3398
+ <td>${templateUtilities.getOwnerTag(report)}</td>
3025
3399
  </tr>`;
3026
3400
  }
3027
3401
  }
@@ -3030,6 +3404,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3030
3404
  </table>`;
3031
3405
  return html;
3032
3406
  }
3407
+ /**
3408
+ * 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)
3409
+ * @returns true if the report is just a panel
3410
+ */
3411
+ static isPanelDiagnosticReport(report) {
3412
+ return this.hasEssentialLabPanelLoinc(
3413
+ report
3414
+ );
3415
+ }
3416
+ /**
3417
+ * Check if a DiagnosticReport or Observation has a LOINC code in ESSENTIAL_LAB_PANELS
3418
+ * @param resource - DiagnosticReport or Observation
3419
+ * @returns true if any LOINC code is in ESSENTIAL_LAB_PANELS, false otherwise
3420
+ */
3421
+ static hasEssentialLabPanelLoinc(resource) {
3422
+ const codings = resource?.code?.coding ?? [];
3423
+ return codings.some(
3424
+ (coding) => coding && coding.system && coding.code && coding.system.toLowerCase().includes("loinc") && Object.keys(ESSENTIAL_LAB_PANELS).includes(coding.code)
3425
+ );
3426
+ }
3033
3427
  };
3034
3428
 
3035
3429
  // src/narratives/templates/typescript/HistoryOfProceduresTemplate.ts
@@ -3063,14 +3457,18 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3063
3457
  <thead>
3064
3458
  <tr>
3065
3459
  <th>Procedure</th>
3460
+ <th>Code (System)</th>
3066
3461
  <th>Performer</th>
3067
3462
  <th>Date</th>
3463
+ <th>Source</th>
3068
3464
  </tr>
3069
3465
  </thead>
3070
3466
  <tbody>`;
3071
3467
  for (const resourceItem of resources) {
3072
3468
  for (const rowData of resourceItem.section ?? []) {
3469
+ const sectionCodeableConcept = rowData.code;
3073
3470
  const data = {};
3471
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
3074
3472
  for (const columnData of rowData.section ?? []) {
3075
3473
  switch (columnData.title) {
3076
3474
  case "Procedure Name":
@@ -3082,6 +3480,9 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3082
3480
  case "Performed Date":
3083
3481
  data["date"] = columnData.text?.div ?? "";
3084
3482
  break;
3483
+ case "Source":
3484
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3485
+ break;
3085
3486
  default:
3086
3487
  break;
3087
3488
  }
@@ -3089,9 +3490,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3089
3490
  isSummaryCreated = true;
3090
3491
  html += `
3091
3492
  <tr>
3092
- <td>${data["procedure"] ?? "-"}</td>
3093
- <td>${data["performer"] ?? "-"}</td>
3094
- <td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
3493
+ <td>${data["procedure"] ?? ""}</td>
3494
+ <td>${data["codeSystem"] ?? ""}</td>
3495
+ <td>${data["performer"] ?? ""}</td>
3496
+ <td>${templateUtilities.renderTime(data["date"], timezone) ?? ""}</td>
3497
+ <td>${data["source"] ?? ""}</td>
3095
3498
  </tr>`;
3096
3499
  }
3097
3500
  }
@@ -3114,8 +3517,10 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3114
3517
  <thead>
3115
3518
  <tr>
3116
3519
  <th>Procedure</th>
3520
+ <th>Code (System)</th>
3117
3521
  <th>Comments</th>
3118
3522
  <th>Date</th>
3523
+ <th>Source</th>
3119
3524
  </tr>
3120
3525
  </thead>
3121
3526
  <tbody>`;
@@ -3123,9 +3528,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3123
3528
  const proc = resourceItem;
3124
3529
  html += `
3125
3530
  <tr id="${templateUtilities.narrativeLinkId(proc)}">
3126
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
3531
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(proc.code, "display"))}</td>
3532
+ <td>${templateUtilities.codeableConceptCoding(proc.code)}</td>
3127
3533
  <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
3128
3534
  <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
3535
+ <td>${templateUtilities.getOwnerTag(proc)}</td>
3129
3536
  </tr>`;
3130
3537
  }
3131
3538
  html += `
@@ -3164,22 +3571,26 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3164
3571
  <table>
3165
3572
  <thead>
3166
3573
  <tr>
3167
- <th>Code</th>
3574
+ <th>Name</th>
3575
+ <th>Code (System)</th>
3168
3576
  <th>Result</th>
3169
3577
  <th>Unit</th>
3170
3578
  <th>Comments</th>
3171
3579
  <th>Date</th>
3580
+ <th>Source</th>
3172
3581
  </tr>
3173
3582
  </thead>
3174
3583
  <tbody>`;
3175
3584
  for (const obs of observations) {
3176
3585
  html += `
3177
3586
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
3178
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
3587
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code))}</td>
3588
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
3179
3589
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3180
3590
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
3181
3591
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3182
3592
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3593
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
3183
3594
  </tr>`;
3184
3595
  }
3185
3596
  html += `
@@ -3195,14 +3606,26 @@ var PastHistoryOfIllnessTemplate = class {
3195
3606
  * Generate HTML narrative for Past History of Illnesses
3196
3607
  * @param resources - FHIR Condition resources
3197
3608
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3609
+ * @param now - Optional current date to use for generating relative dates in the narrative
3198
3610
  * @returns HTML string for rendering
3199
3611
  */
3200
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3201
- generateNarrative(resources, timezone) {
3612
+ generateNarrative(resources, timezone, now) {
3202
3613
  const templateUtilities = new TemplateUtilities(resources);
3203
3614
  let html = ``;
3204
3615
  const resolvedConditions = resources.map((entry) => entry) || [];
3205
- resolvedConditions.sort((a, b) => {
3616
+ const currentDate = now || /* @__PURE__ */ new Date();
3617
+ const fiveYearsAgo = new Date(currentDate);
3618
+ fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
3619
+ let skippedConditions = 0;
3620
+ const filteredConditions = [];
3621
+ for (const cond of resolvedConditions) {
3622
+ if (cond.recordedDate && new Date(cond.recordedDate) >= fiveYearsAgo) {
3623
+ filteredConditions.push(cond);
3624
+ } else {
3625
+ skippedConditions++;
3626
+ }
3627
+ }
3628
+ filteredConditions.sort((a, b) => {
3206
3629
  const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3207
3630
  const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3208
3631
  return dateB - dateA;
@@ -3212,27 +3635,35 @@ var PastHistoryOfIllnessTemplate = class {
3212
3635
  <thead>
3213
3636
  <tr>
3214
3637
  <th>Problem</th>
3638
+ <th>Code (System)</th>
3215
3639
  <th>Onset Date</th>
3216
3640
  <th>Recorded Date</th>
3217
3641
  <th>Resolved Date</th>
3642
+ <th>Source</th>
3218
3643
  </tr>
3219
3644
  </thead>
3220
3645
  <tbody>`;
3221
3646
  const addedConditionCodes = /* @__PURE__ */ new Set();
3222
- for (const cond of resolvedConditions) {
3223
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3647
+ for (const cond of filteredConditions) {
3648
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(cond.code));
3224
3649
  if (!addedConditionCodes.has(conditionCode)) {
3225
3650
  addedConditionCodes.add(conditionCode);
3226
3651
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3227
3652
  <td class="Name">${conditionCode}</td>
3653
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(cond.code)}</td>
3228
3654
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3229
3655
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3230
3656
  <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
3657
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
3231
3658
  </tr>`;
3232
3659
  }
3233
3660
  }
3234
3661
  html += `</tbody>
3235
3662
  </table>`;
3663
+ if (skippedConditions > 0) {
3664
+ html += `
3665
+ <p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
3666
+ }
3236
3667
  return html;
3237
3668
  }
3238
3669
  };
@@ -3262,6 +3693,7 @@ var PlanOfCareTemplate = class {
3262
3693
  <th>Comments</th>
3263
3694
  <th>Planned Start</th>
3264
3695
  <th>Planned End</th>
3696
+ <th>Source</th>
3265
3697
  </tr>
3266
3698
  </thead>
3267
3699
  <tbody>`;
@@ -3273,6 +3705,7 @@ var PlanOfCareTemplate = class {
3273
3705
  <td>${templateUtilities.concat(cp.note, "text")}</td>
3274
3706
  <td>${cp.period?.start ? templateUtilities.renderTime(cp.period?.start, timezone) : ""}</td>
3275
3707
  <td>${cp.period?.end ? templateUtilities.renderTime(cp.period?.end, timezone) : ""}</td>
3708
+ <td>${templateUtilities.getOwnerTag(cp)}</td>
3276
3709
  </tr>`;
3277
3710
  }
3278
3711
  html += `
@@ -3298,6 +3731,7 @@ var PlanOfCareTemplate = class {
3298
3731
  <th>Created</th>
3299
3732
  <th>Planned Start</th>
3300
3733
  <th>Planned End</th>
3734
+ <th>Source</th>
3301
3735
  </tr>
3302
3736
  </thead>
3303
3737
  <tbody>`;
@@ -3315,10 +3749,11 @@ var PlanOfCareTemplate = class {
3315
3749
  isSummaryCreated = true;
3316
3750
  html += `
3317
3751
  <tr>
3318
- <td>${data["CarePlan Name"] ?? "-"}</td>
3319
- <td>${templateUtilities.renderTime(data["created"], timezone) ?? "-"}</td>
3320
- <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? "-"}</td>
3321
- <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? "-"}</td>
3752
+ <td>${data["CarePlan Name"] ?? ""}</td>
3753
+ <td>${templateUtilities.renderTime(data["created"], timezone) ?? ""}</td>
3754
+ <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? ""}</td>
3755
+ <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? ""}</td>
3756
+ <td>${data["source"] ?? ""}</td>
3322
3757
  </tr>`;
3323
3758
  }
3324
3759
  }
@@ -3349,77 +3784,54 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3349
3784
  */
3350
3785
  static generateStaticNarrative(resources, timezone) {
3351
3786
  const templateUtilities = new TemplateUtilities(resources);
3352
- let html = ``;
3353
- const activeConditions = [];
3354
- const clinicalImpressions = [];
3355
- for (const resourceItem of resources) {
3356
- if (resourceItem.resourceType === "Condition") {
3357
- activeConditions.push(resourceItem);
3358
- } else if (resourceItem.resourceType === "ClinicalImpression") {
3359
- clinicalImpressions.push(resourceItem);
3360
- }
3361
- }
3362
- activeConditions.sort((a, b) => {
3363
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3364
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3365
- return dateB - dateA;
3787
+ let html = "";
3788
+ let functionalObservations = resources.filter((r) => r.resourceType === "Observation").filter((r) => {
3789
+ const hasFunctionalLoinc = r.code?.coding?.some(
3790
+ (c) => c.system?.toLowerCase().includes("loinc") && c.code === "47420-5"
3791
+ );
3792
+ const hasFunctionalCategory = r.category?.some(
3793
+ (cat) => cat.coding?.some(
3794
+ (c) => c.code === "functional-status" || c.display?.toLowerCase().includes("functional")
3795
+ )
3796
+ );
3797
+ return hasFunctionalLoinc || hasFunctionalCategory;
3366
3798
  });
3367
- clinicalImpressions.sort((a, b) => {
3368
- 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;
3369
- 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;
3370
- return dateB - dateA;
3799
+ functionalObservations = functionalObservations.sort((a, b) => {
3800
+ const getObsDate = (obs) => obs.effectiveDateTime ? new Date(obs.effectiveDateTime).getTime() : obs.issued ? new Date(obs.issued).getTime() : 0;
3801
+ return getObsDate(b) - getObsDate(a);
3371
3802
  });
3372
- if (activeConditions.length > 0) {
3373
- html += `<h3>Conditions</h3>
3374
- <table>
3375
- <thead>
3376
- <tr>
3377
- <th>Problem</th>
3378
- <th>Onset Date</th>
3379
- <th>Recorded Date</th>
3380
- </tr>
3381
- </thead>
3382
- <tbody>`;
3383
- const addedConditionCodes = /* @__PURE__ */ new Set();
3384
- for (const cond of activeConditions) {
3385
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3386
- if (!addedConditionCodes.has(conditionCode)) {
3387
- addedConditionCodes.add(conditionCode);
3388
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3389
- <td class="Name">${conditionCode}</td>
3390
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3391
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3392
- </tr>`;
3393
- }
3803
+ let clinicalImpressions = resources.filter((r) => r.resourceType === "ClinicalImpression").filter((r) => r.status === "completed");
3804
+ clinicalImpressions = clinicalImpressions.sort((a, b) => {
3805
+ 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;
3806
+ return getImpressionDate(b) - getImpressionDate(a);
3807
+ });
3808
+ if (functionalObservations.length > 0) {
3809
+ html += `<table><thead><tr><th>Observation</th><th>Value</th><th>Date</th><th>Interpretation</th><th>Comments</th></tr></thead><tbody>`;
3810
+ for (const obs of functionalObservations) {
3811
+ const observation = obs;
3812
+ const obsName = templateUtilities.codeableConceptDisplay(observation.code);
3813
+ const value = templateUtilities.extractObservationValue(observation);
3814
+ const date = observation.effectiveDateTime ? templateUtilities.renderDate(observation.effectiveDateTime) : observation.issued ? templateUtilities.renderDate(observation.issued) : "";
3815
+ const interpretation = observation.interpretation ? templateUtilities.codeableConceptDisplay(observation.interpretation[0]) : "";
3816
+ const comments = observation.comment || observation.note?.map((n) => n.text).join("; ") || "";
3817
+ html += `<tr id="${templateUtilities.narrativeLinkId(observation)}">
3818
+ <td>${obsName}</td>
3819
+ <td>${value ?? ""}</td>
3820
+ <td>${date}</td>
3821
+ <td>${interpretation}</td>
3822
+ <td>${comments}</td>
3823
+ </tr>`;
3394
3824
  }
3395
- html += `</tbody>
3396
- </table>`;
3825
+ html += `</tbody></table>`;
3397
3826
  }
3398
3827
  if (clinicalImpressions.length > 0) {
3399
- html += `<h3>Clinical Impressions</h3>
3400
- <table>
3401
- <thead>
3402
- <tr>
3403
- <th>Date</th>
3404
- <th>Status</th>
3405
- <th>Description</th>
3406
- <th>Summary</th>
3407
- <th>Findings</th>
3408
- </tr>
3409
- </thead>
3410
- <tbody>`;
3828
+ html += `<table><thead><tr><th>Date</th><th>Status</th><th>Description</th><th>Summary</th><th>Findings</th></tr></thead><tbody>`;
3411
3829
  for (const impression of clinicalImpressions) {
3412
3830
  let formattedDate = "";
3413
3831
  if (impression.effectiveDateTime) {
3414
- formattedDate = templateUtilities.renderTime(
3415
- impression.effectiveDateTime,
3416
- timezone
3417
- );
3832
+ formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
3418
3833
  } else if (impression.effectivePeriod) {
3419
- formattedDate = templateUtilities.renderPeriod(
3420
- impression.effectivePeriod,
3421
- timezone
3422
- );
3834
+ formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
3423
3835
  } else if (impression.date) {
3424
3836
  formattedDate = templateUtilities.renderDate(impression.date);
3425
3837
  }
@@ -3427,23 +3839,24 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3427
3839
  if (impression.finding && impression.finding.length > 0) {
3428
3840
  findingsHtml = "<ul>";
3429
3841
  for (const finding of impression.finding) {
3430
- const findingText = finding.itemCodeableConcept ? templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(finding.itemCodeableConcept)) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3842
+ const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3431
3843
  const cause = finding.basis || "";
3432
3844
  findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
3433
3845
  }
3434
3846
  findingsHtml += "</ul>";
3435
3847
  }
3436
- html += `
3437
- <tr id="${templateUtilities.narrativeLinkId(impression)}">
3438
- <td>${formattedDate}</td>
3439
- <td>${impression.status || ""}</td>
3440
- <td>${impression.description || ""}</td>
3441
- <td>${impression.summary || ""}</td>
3442
- <td>${findingsHtml}</td>
3443
- </tr>`;
3848
+ html += `<tr id="${templateUtilities.narrativeLinkId(impression)}">
3849
+ <td>${formattedDate}</td>
3850
+ <td>${impression.status || ""}</td>
3851
+ <td>${impression.description || ""}</td>
3852
+ <td>${impression.summary || ""}</td>
3853
+ <td>${findingsHtml}</td>
3854
+ </tr>`;
3444
3855
  }
3445
- html += `</tbody>
3446
- </table>`;
3856
+ html += `</tbody></table>`;
3857
+ }
3858
+ if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
3859
+ html += `<p>No functional status information available.</p>`;
3447
3860
  }
3448
3861
  return html;
3449
3862
  }
@@ -3468,34 +3881,109 @@ var PregnancyTemplate = class _PregnancyTemplate {
3468
3881
  */
3469
3882
  static generateStaticNarrative(resources, timezone) {
3470
3883
  const templateUtilities = new TemplateUtilities(resources);
3471
- const observations = resources.map((entry) => entry) || [];
3472
- observations.sort((a, b) => {
3884
+ const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
3885
+ const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
3886
+ const observations = filteredResources.filter((r) => r.resourceType === "Observation");
3887
+ const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
3888
+ const EDD_LOINC = "11778-8";
3889
+ const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
3890
+ const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
3891
+ const pregnancyStatusObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code && pregnancyStatusCodes.includes(c.code))).sort((a, b) => {
3473
3892
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3474
3893
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3475
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3894
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3895
+ })[0];
3896
+ const eddObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code === EDD_LOINC)).sort((a, b) => {
3897
+ const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3898
+ const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3899
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3900
+ })[0];
3901
+ const historyObs = observations.filter(
3902
+ (obs) => obs.code?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code)) || obs.valueCodeableConcept?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code))
3903
+ ).sort((a, b) => {
3904
+ const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3905
+ const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3906
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3476
3907
  });
3908
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
3909
+ return `<p>No history of pregnancy found.</p>`;
3910
+ }
3477
3911
  let html = `
3478
- <table>
3479
- <thead>
3480
- <tr>
3481
- <th>Result</th>
3482
- <th>Comments</th>
3483
- <th>Date</th>
3484
- </tr>
3485
- </thead>
3486
- <tbody>`;
3487
- for (const resource of observations) {
3488
- const obs = resource;
3912
+ <table>
3913
+ <thead>
3914
+ <tr>
3915
+ <th>Result</th>
3916
+ <th>Code (System)</th>
3917
+ <th>Comments</th>
3918
+ <th>Date</th>
3919
+ <th>Source</th>
3920
+ </tr>
3921
+ </thead>
3922
+ <tbody>`;
3923
+ function renderRow({ id, result, comments, date, codeSystem, owner }) {
3489
3924
  html += `
3490
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
3491
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
3492
- <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3493
- <td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
3494
- </tr>`;
3925
+ <tr id="${id}">
3926
+ <td class="Result">${result}</td>
3927
+ <td class="CodeSystem">${codeSystem}</td>
3928
+ <td class="Comments">${comments}</td>
3929
+ <td class="Date">${date}</td>
3930
+ <td class="Source">${owner}</td>
3931
+ </tr>`;
3932
+ }
3933
+ const rowResources = [];
3934
+ if (pregnancyStatusObs) {
3935
+ const date = pregnancyStatusObs.effectiveDateTime || pregnancyStatusObs.effectivePeriod?.start;
3936
+ rowResources.push({ resource: pregnancyStatusObs, date, type: "status" });
3937
+ }
3938
+ if (eddObs) {
3939
+ const date = eddObs.effectiveDateTime || eddObs.effectivePeriod?.start;
3940
+ rowResources.push({ resource: eddObs, date, type: "edd" });
3941
+ }
3942
+ for (const obs of historyObs) {
3943
+ const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
3944
+ rowResources.push({ resource: obs, date, type: "history" });
3945
+ }
3946
+ for (const cond of conditions) {
3947
+ const condition = cond;
3948
+ const date = condition.onsetDateTime || condition.onsetPeriod?.start;
3949
+ rowResources.push({ resource: condition, date, type: "condition" });
3950
+ }
3951
+ rowResources.sort((a, b) => {
3952
+ if (!a.date && !b.date) return 0;
3953
+ if (!a.date) return 1;
3954
+ if (!b.date) return -1;
3955
+ return new Date(b.date).getTime() - new Date(a.date).getTime();
3956
+ });
3957
+ for (const { resource, date, type } of rowResources) {
3958
+ let result = "", comments = "", dateStr = "", codeSystem = "";
3959
+ const id = templateUtilities.narrativeLinkId(resource);
3960
+ if (type === "status") {
3961
+ result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
3962
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3963
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3964
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3965
+ } else if (type === "edd") {
3966
+ result = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
3967
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3968
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3969
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3970
+ } else if (type === "history") {
3971
+ result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
3972
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3973
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3974
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3975
+ } else if (type === "condition") {
3976
+ result = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(resource.code));
3977
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3978
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3979
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3980
+ }
3981
+ const owner = templateUtilities.getOwnerTag(resource);
3982
+ renderRow({ id, result, comments, date: dateStr, codeSystem, owner });
3495
3983
  }
3496
3984
  html += `
3497
- </tbody>
3498
- </table>`;
3985
+ </tbody>
3986
+ </table>`;
3499
3987
  return html;
3500
3988
  }
3501
3989
  };
@@ -3540,7 +4028,7 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3540
4028
  const consent = resourceItem;
3541
4029
  html += `
3542
4030
  <tr id="${templateUtilities.narrativeLinkId(consent)}">
3543
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
4031
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display"))}</td>
3544
4032
  <td>${consent.status || ""}</td>
3545
4033
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
3546
4034
  <td>${consent.dateTime || ""}</td>
@@ -3573,7 +4061,7 @@ var TypeScriptTemplateMapper = class {
3573
4061
  resources,
3574
4062
  timezone,
3575
4063
  now
3576
- ) : templateClass.generateNarrative(resources, timezone);
4064
+ ) : templateClass.generateNarrative(resources, timezone, now);
3577
4065
  }
3578
4066
  };
3579
4067
  // Map of section types to their template classes
@@ -3852,6 +4340,12 @@ var ComprehensiveIPSCompositionBuilder = class {
3852
4340
  if (sectionType === "Patient" /* PATIENT */) {
3853
4341
  continue;
3854
4342
  }
4343
+ const summaryIPSCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryIPSCompositionFilterForSection(sectionType) : void 0;
4344
+ const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
4345
+ if (sectionIPSSummary.length > 0) {
4346
+ await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone);
4347
+ continue;
4348
+ }
3855
4349
  const summaryCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType) : void 0;
3856
4350
  const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
3857
4351
  if (sectionSummary.length > 0) {