@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.js CHANGED
@@ -71,6 +71,13 @@ var PREGNANCY_LOINC_CODES = {
71
71
  "33065-4": "Ectopic Pregnancy"
72
72
  }
73
73
  };
74
+ var PREGNANCY_SNOMED_CODES = [
75
+ // Add SNOMED CT codes for pregnancy history here, e.g.:
76
+ "72892002",
77
+ // Pregnancy (finding)
78
+ "77386006"
79
+ // History of pregnancy (situation)
80
+ ];
74
81
  var SOCIAL_HISTORY_LOINC_CODES = {
75
82
  "72166-2": "Tobacco Use",
76
83
  "74013-4": "Alcohol Use"
@@ -80,6 +87,29 @@ var BLOOD_PRESSURE_LOINC_CODES = {
80
87
  SYSTOLIC: "8480-6",
81
88
  DIASTOLIC: "8462-4"
82
89
  };
90
+ var ESSENTIAL_LAB_PANELS = {
91
+ // Top 20 Most Ordered Panels
92
+ "24323-8": "Comprehensive Metabolic Panel (CMP)",
93
+ "24320-4": "Basic Metabolic Panel (BMP)",
94
+ "58410-2": "Complete Blood Count (CBC)",
95
+ "57021-8": "CBC with Differential",
96
+ "24331-1": "Lipid Panel",
97
+ "57698-3": "Lipid Panel with Direct LDL",
98
+ "24325-3": "Hepatic Function Panel",
99
+ "24362-6": "Renal Function Panel",
100
+ "24326-1": "Electrolyte Panel",
101
+ "24348-5": "Thyroid Panel",
102
+ "24356-8": "Urinalysis Complete",
103
+ "24352-7": "Iron Studies Panel",
104
+ "34714-6": "Coagulation Panel",
105
+ "24364-2": "Prenatal Panel",
106
+ "24108-3": "Acute Hepatitis Panel",
107
+ "24110-9": "Hepatitis B Panel",
108
+ "34574-4": "Arthritis Panel",
109
+ "24360-0": "Anemia Panel",
110
+ "80235-8": "Cardiac Markers Panel",
111
+ "69738-3": "CBC with Auto Differential"
112
+ };
83
113
 
84
114
  // src/structures/ips_section_constants.ts
85
115
  var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
@@ -104,34 +134,42 @@ var IPSSectionResourceFilters = {
104
134
  // Only include completed immunizations
105
135
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
106
136
  // Only include vital sign Observations (category.coding contains 'vital-signs')
107
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c.code === "vital-signs")),
137
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, "vital-signs", c.system))),
108
138
  // Includes DeviceUseStatement. Device is needed for linked device details
109
139
  ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
110
- // Only include finalized diagnostic reports
111
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
140
+ // Only include finalized diagnostic reports and relevant observations
141
+ ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "DiagnosticReport" && resource.status === "final" || resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, RESULT_SUMMARY_OBSERVATION_CATEGORIES, c.system))),
112
142
  // Only include completed procedures
113
143
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
114
144
  // Only include social history Observations
115
- ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code?.coding?.some((c) => Object.keys(SOCIAL_HISTORY_LOINC_CODES).includes(c.code)),
116
- // Only include pregnancy history Observations
117
- ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS).includes(c.code)) || resource.valueCodeableConcept?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME).includes(c.code))),
118
- // Only include Conditions or completed ClinicalImpressions
119
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
145
+ ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
146
+ // Only include pregnancy history Observations or relevant Conditions
147
+ ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "Condition" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")),
148
+ // Only include Observations with LOINC 47420-5, category 'functional-status', or category display containing 'functional', and completed ClinicalImpressions
149
+ ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, "47420-5", "http://loinc.org") || resource.category?.some(
150
+ (cat) => cat.coding?.some(
151
+ (c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
152
+ )
153
+ )) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
120
154
  // Only include resolved medical history Conditions
121
155
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
122
156
  // Only include active care plans
123
157
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
124
158
  // Only include active advance directives (Consent resources)
125
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
159
+ // TODO: disable this until we right logic to get these
160
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
126
161
  };
127
162
  var IPSSectionSummaryCompositionFilter = {
128
- ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
129
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
130
- ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
131
- ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
132
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
163
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
164
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
165
+ ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
166
+ ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
167
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "medication_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
133
168
  // [IPSSections.DIAGNOSTIC_REPORTS]: (resource) => resource.resourceType === 'Composition' && resource.type?.coding?.some((c: any) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && ["lab_summary_document", "diagnosticreportlab_summary_document"].includes(c.code)),
134
- ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
169
+ ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "procedure_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
170
+ };
171
+ var IPSSectionSummaryIPSCompositionFilter = {
172
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "ips_vital_summary_document")
135
173
  };
136
174
  var IPSSectionResourceHelper = class {
137
175
  static getResourceFilterForSection(section) {
@@ -145,10 +183,50 @@ var IPSSectionResourceHelper = class {
145
183
  static getSummaryCompositionFilterForSection(section) {
146
184
  return IPSSectionSummaryCompositionFilter[section];
147
185
  }
186
+ static getSummaryIPSCompositionFilterForSection(section) {
187
+ return IPSSectionSummaryIPSCompositionFilter[section];
188
+ }
148
189
  };
190
+ function codingMatches(coding, code, system) {
191
+ if (!coding || !coding.system) return false;
192
+ if (Array.isArray(code)) {
193
+ return coding.system === system && code.includes(coding.code ?? "");
194
+ }
195
+ return coding.system === system && coding.code === code;
196
+ }
197
+ function codeableConceptMatches(codeableConcept, code, system) {
198
+ if (!codeableConcept || !Array.isArray(codeableConcept.coding)) return false;
199
+ return codeableConcept.coding.some((coding) => codingMatches(coding, code, system));
200
+ }
149
201
 
150
202
  // src/narratives/templates/typescript/TemplateUtilities.ts
151
203
  import { DateTime } from "luxon";
204
+
205
+ // src/structures/codingSystemDisplayNames.ts
206
+ var CODING_SYSTEM_DISPLAY_NAMES = {
207
+ "http://snomed.info/sct": "SNOMED CT",
208
+ "http://loinc.org": "LOINC",
209
+ "http://hl7.org/fhir/sid/icd-10": "ICD-10",
210
+ "http://hl7.org/fhir/sid/icd-10-cm": "ICD-10-CM",
211
+ "http://hl7.org/fhir/sid/icd-9": "ICD-9",
212
+ "http://hl7.org/fhir/sid/cvx": "CVX",
213
+ "http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
214
+ "http://www.ama-assn.org/go/cpt": "CPT",
215
+ "http://unitsofmeasure.org": "UCUM",
216
+ "http://e-imo.com/products/problem-it": "IMO Problem IT",
217
+ "2.16.840.1.113883.6.285": "HCPCS Level II",
218
+ "https://fhir.cerner.com/4ff3b259-e48d-4066-8b35-a6a051f2802a/codeSet/72": "Cerner Code Set 72",
219
+ "http://hl7.org/fhir/sid/ndc": "NDC",
220
+ // Added mapping for NDC
221
+ "urn:oid:2.16.840.1.113883.12.292": "CVX",
222
+ "http://terminology.hl7.org/CodeSystem/data-absent-reason": "Data Absent Reason",
223
+ // Added mapping for Data Absent Reason
224
+ "2.16.840.1.113883.6.208": "NDDF"
225
+ // Add more as needed
226
+ };
227
+ var codingSystemDisplayNames_default = CODING_SYSTEM_DISPLAY_NAMES;
228
+
229
+ // src/narratives/templates/typescript/TemplateUtilities.ts
152
230
  var TemplateUtilities = class {
153
231
  /**
154
232
  * Constructor to initialize the TemplateUtilities with a FHIR resources
@@ -158,47 +236,63 @@ var TemplateUtilities = class {
158
236
  this.resources = resources;
159
237
  }
160
238
  /**
161
- * Formats a CodeableConcept object
162
- * @param cc - The CodeableConcept object
163
- * @param field - Optional specific field to return
164
- * @returns Formatted string representation
239
+ * Returns the preferred coding from a list of codings.
240
+ * If a coding has an extension with url 'https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence' and valueCode 'preferred', returns that coding.
241
+ * Otherwise, returns the first coding if it exists, else null.
242
+ * @param codings Array of coding objects
243
+ * @returns The preferred coding object or null
165
244
  */
166
- codeableConcept(cc, field) {
167
- if (!cc) {
168
- return "";
245
+ getPreferredCoding(codings) {
246
+ if (!Array.isArray(codings) || codings.length === 0) return null;
247
+ for (const coding of codings) {
248
+ if (Array.isArray(coding.extension)) {
249
+ for (const ext of coding.extension) {
250
+ if (ext.url === "https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence" && ext.valueCode === "preferred") {
251
+ return coding;
252
+ }
253
+ }
254
+ }
169
255
  }
256
+ return codings[0] || null;
257
+ }
258
+ /**
259
+ * Returns the display value from a CodeableConcept
260
+ * @param cc - The CodeableConcept object
261
+ * @param field - Optional specific field to extract (e.g., 'text', 'display', 'code')
262
+ * @returns Display string or empty string
263
+ */
264
+ codeableConceptDisplay(cc, field) {
265
+ if (!cc) return "";
170
266
  if (field) {
171
267
  if (cc[field]) {
172
268
  return cc[field];
173
- } else if (cc.coding && cc.coding[0] && cc.coding[0][field]) {
174
- return cc.coding[0][field];
269
+ } else if (cc.coding && cc.coding.length > 0) {
270
+ const preferredCoding = this.getPreferredCoding(cc.coding);
271
+ if (preferredCoding && preferredCoding[field]) {
272
+ return preferredCoding[field];
273
+ }
175
274
  }
176
275
  }
177
- if (cc.text) {
178
- return cc.text;
179
- } else if (cc.coding && cc.coding[0]) {
180
- if (cc.coding[0].display) {
181
- return cc.coding[0].display;
182
- } else if (cc.coding[0].code) {
183
- return cc.coding[0].code;
184
- }
276
+ if (cc.text) return cc.text;
277
+ if (cc.coding && cc.coding.length > 0) {
278
+ const preferredCoding = this.getPreferredCoding(cc.coding);
279
+ if (preferredCoding && preferredCoding.display) return preferredCoding.display;
185
280
  }
186
281
  return "";
187
282
  }
188
- resolveReference(ref) {
189
- if (!ref || !this.resources) {
190
- return null;
191
- }
192
- const referenceParts = ref.reference?.split("/");
193
- if (!referenceParts || referenceParts.length !== 2) {
194
- return null;
195
- }
196
- const referenceResourceType = referenceParts[0];
197
- const referenceResourceId = referenceParts[1];
198
- const resource = this.resources.find((entry) => {
199
- return entry.resourceType === referenceResourceType && entry.id === referenceResourceId;
200
- });
201
- return resource ? resource : null;
283
+ /**
284
+ * Returns the code and system from a CodeableConcept
285
+ * @param cc - The CodeableConcept object
286
+ * @returns Object with code and system, or empty strings if not present
287
+ */
288
+ codeableConceptCoding(cc) {
289
+ if (!cc || !cc.coding || !cc.coding.length) return "";
290
+ const preferredCoding = this.getPreferredCoding(cc.coding);
291
+ if (!preferredCoding) return "";
292
+ const code = preferredCoding.code || "";
293
+ const system = preferredCoding.system || "";
294
+ const systemDisplay = codingSystemDisplayNames_default[system] || system;
295
+ return code ? `${code} (${systemDisplay})` : "";
202
296
  }
203
297
  /**
204
298
  * Renders a Device reference
@@ -246,7 +340,7 @@ var TemplateUtilities = class {
246
340
  return "";
247
341
  }
248
342
  if (medicationType.medicationCodeableConcept) {
249
- return this.codeableConcept(medicationType.medicationCodeableConcept);
343
+ return this.codeableConceptDisplay(medicationType.medicationCodeableConcept);
250
344
  } else if (medicationType.medicationReference) {
251
345
  return this.renderMedicationRef(medicationType.medicationReference);
252
346
  }
@@ -271,7 +365,7 @@ var TemplateUtilities = class {
271
365
  */
272
366
  renderMedicationCode(medication) {
273
367
  if (medication && medication.code) {
274
- return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
368
+ return this.renderTextAsHtml(this.codeableConceptDisplay(medication.code));
275
369
  }
276
370
  return "";
277
371
  }
@@ -440,7 +534,7 @@ var TemplateUtilities = class {
440
534
  */
441
535
  firstFromCodeableConceptList(list) {
442
536
  if (list && Array.isArray(list) && list[0]) {
443
- return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
537
+ return this.renderTextAsHtml(this.codeableConceptDisplay(list[0]));
444
538
  }
445
539
  return "";
446
540
  }
@@ -780,16 +874,28 @@ var TemplateUtilities = class {
780
874
  return this.renderMedicationCode(medicationSource);
781
875
  }
782
876
  if (typeof medicationSource === "object" && ("coding" in medicationSource || "text" in medicationSource)) {
783
- return this.codeableConcept(medicationSource);
877
+ return this.codeableConceptDisplay(medicationSource);
784
878
  }
785
879
  if (typeof medicationSource === "object" && "reference" in medicationSource) {
786
880
  const medication = this.resolveReference(medicationSource);
787
881
  if (medication && medication.code) {
788
- return this.codeableConcept(medication.code);
882
+ return this.codeableConceptDisplay(medication.code);
789
883
  }
790
884
  }
791
885
  return "";
792
886
  }
887
+ /**
888
+ * Returns the owner tag from the resource meta.security array.
889
+ * @param resource - FHIR resource with meta tag
890
+ * @returns The owner code if found, otherwise undefined
891
+ */
892
+ getOwnerTag(resource) {
893
+ if (!resource?.meta?.security) return "";
894
+ const ownerEntry = resource.meta.security.find(
895
+ (sec) => sec.system === "https://www.icanbwell.com/owner" && !!sec.code
896
+ );
897
+ return ownerEntry?.code;
898
+ }
793
899
  /**
794
900
  * Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
795
901
  * This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
@@ -953,6 +1059,21 @@ var TemplateUtilities = class {
953
1059
  const denominatorUnit = valueRatio.denominator.unit ? ` ${valueRatio.denominator.unit}` : "";
954
1060
  return `${numerator}${numeratorUnit} / ${denominator}${denominatorUnit}`;
955
1061
  }
1062
+ /**
1063
+ * Finds the resource that matches the reference
1064
+ * @param ref - Reference to a resource
1065
+ * @returns The resource or null
1066
+ */
1067
+ resolveReference(ref) {
1068
+ if (!ref || !this.resources) {
1069
+ return null;
1070
+ }
1071
+ const refId = ref.reference?.split("/")[1];
1072
+ const refType = ref.reference?.split("/")[0];
1073
+ return this.resources.find(
1074
+ (resource) => resource.resourceType === refType && resource.id === refId
1075
+ ) || null;
1076
+ }
956
1077
  };
957
1078
 
958
1079
  // src/constants.ts
@@ -1581,7 +1702,7 @@ var PatientTemplate = class _PatientTemplate {
1581
1702
  const uniqueLanguages = /* @__PURE__ */ new Set();
1582
1703
  const preferredLanguages = /* @__PURE__ */ new Set();
1583
1704
  patient.communication.forEach((comm) => {
1584
- const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
1705
+ const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(comm.language));
1585
1706
  if (language) {
1586
1707
  if (comm.preferred) {
1587
1708
  preferredLanguages.add(language);
@@ -1628,14 +1749,18 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1628
1749
  <thead>
1629
1750
  <tr>
1630
1751
  <th>Allergen</th>
1752
+ <th>Code (System)</th>
1631
1753
  <th>Criticality</th>
1632
1754
  <th>Recorded Date</th>
1755
+ <th>Source</th>
1633
1756
  </tr>
1634
1757
  </thead>
1635
1758
  <tbody>`;
1636
1759
  for (const resourceItem of resources) {
1637
1760
  for (const rowData of resourceItem.section ?? []) {
1761
+ const sectionCodeableConcept = rowData.code;
1638
1762
  const data = {};
1763
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
1639
1764
  for (const columnData of rowData.section ?? []) {
1640
1765
  switch (columnData.title) {
1641
1766
  case "Allergen Name":
@@ -1647,6 +1772,9 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1647
1772
  case "Recorded Date":
1648
1773
  data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1649
1774
  break;
1775
+ case "Source":
1776
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1777
+ break;
1650
1778
  default:
1651
1779
  break;
1652
1780
  }
@@ -1654,9 +1782,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1654
1782
  isSummaryCreated = true;
1655
1783
  html += `
1656
1784
  <tr>
1657
- <td>${data["allergen"] ?? "-"}</td>
1658
- <td>${data["criticality"] ?? "-"}</td>
1659
- <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? "-"}</td>
1785
+ <td>${data["allergen"] ?? ""}</td>
1786
+ <td>${data["codeSystem"] ?? ""}</td>
1787
+ <td>${data["criticality"] ?? ""}</td>
1788
+ <td>${templateUtilities.renderTime(data["recordedDate"], timezone) ?? ""}</td>
1789
+ <td>${data["source"] ?? ""}</td>
1660
1790
  </tr>`;
1661
1791
  }
1662
1792
  }
@@ -1704,10 +1834,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1704
1834
  <tr>
1705
1835
  <th>Allergen</th>
1706
1836
  <th>Status</th>
1837
+ <th>Code (System)</th>
1707
1838
  <th>Category</th>
1708
1839
  <th>Reaction</th>
1709
1840
  <th>Onset Date</th>
1710
1841
  <th>Comments</th>
1842
+ <th>Source</th>
1711
1843
  </tr>
1712
1844
  </thead>
1713
1845
  <tbody>`;
@@ -1731,11 +1863,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1731
1863
  <tr>
1732
1864
  <th>Allergen</th>
1733
1865
  <th>Status</th>
1866
+ <th>Code (System)</th>
1734
1867
  <th>Category</th>
1735
1868
  <th>Reaction</th>
1736
1869
  <th>Onset Date</th>
1737
1870
  <th>Comments</th>
1738
1871
  <th>Resolved Date</th>
1872
+ <th>Source</th>
1739
1873
  </tr>
1740
1874
  </thead>
1741
1875
  <tbody>`;
@@ -1766,14 +1900,16 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1766
1900
  for (const allergy of allergies) {
1767
1901
  html += `
1768
1902
  <tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
1769
- <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
1770
- <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
1771
- <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
1772
- <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
1773
- <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
1774
- <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
1903
+ <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.code))}</span></td>
1904
+ <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(allergy.clinicalStatus)) || ""}</td>
1905
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(allergy.code)}</td>
1906
+ <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || ""}</td>
1907
+ <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || ""}</td>
1908
+ <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || ""}</td>
1909
+ <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>
1910
+ <td class="Source">${templateUtilities.getOwnerTag(allergy)}</td>`;
1775
1911
  if (includeResolved) {
1776
- let endDate = "-";
1912
+ let endDate = "";
1777
1913
  if (allergy.extension && Array.isArray(allergy.extension)) {
1778
1914
  const endDateExt = allergy.extension.find(
1779
1915
  (ext) => ext.url === "http://hl7.org/fhir/StructureDefinition/allergyintolerance-resolutionDate"
@@ -1797,10 +1933,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1797
1933
  * Generate HTML narrative for Medication resources
1798
1934
  * @param resources - FHIR Medication resources
1799
1935
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1936
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1800
1937
  * @returns HTML string for rendering
1801
1938
  */
1802
- generateNarrative(resources, timezone) {
1803
- return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
1939
+ generateNarrative(resources, timezone, now) {
1940
+ return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone, now);
1804
1941
  }
1805
1942
  /**
1806
1943
  * Generate HTML narrative for Medication resources using summary
@@ -1813,23 +1950,27 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1813
1950
  const templateUtilities = new TemplateUtilities(resources);
1814
1951
  let isSummaryCreated = false;
1815
1952
  const currentDate = now || /* @__PURE__ */ new Date();
1816
- const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
1953
+ const twoYearsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 24, currentDate.getDate());
1817
1954
  let html = `
1818
1955
  <div>
1819
1956
  <table>
1820
1957
  <thead>
1821
1958
  <tr>
1822
1959
  <th>Medication</th>
1960
+ <th>Code (System)</th>
1823
1961
  <th>Status</th>
1824
1962
  <th>Sig</th>
1825
1963
  <th>Days of Supply</th>
1826
1964
  <th>Refills</th>
1827
1965
  <th>Start Date</th>
1966
+ <th>Source</th>
1828
1967
  </tr>
1829
1968
  </thead>
1830
1969
  <tbody>`;
1970
+ let skippedMedications = 0;
1831
1971
  for (const resourceItem of resources) {
1832
1972
  for (const rowData of resourceItem.section ?? []) {
1973
+ const sectionCodeableConcept = rowData.code;
1833
1974
  const data = {};
1834
1975
  for (const columnData of rowData.section ?? []) {
1835
1976
  switch (columnData.title) {
@@ -1854,6 +1995,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1854
1995
  case "Authored On Date":
1855
1996
  data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1856
1997
  break;
1998
+ case "Source":
1999
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2000
+ break;
1857
2001
  default:
1858
2002
  break;
1859
2003
  }
@@ -1865,16 +2009,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1865
2009
  startDateObj = void 0;
1866
2010
  }
1867
2011
  }
1868
- if (data["status"] === "active" || startDateObj && startDateObj >= twelveMonthsAgo) {
2012
+ if (!(data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo)) {
2013
+ skippedMedications++;
2014
+ }
2015
+ if (data["status"] === "active" || startDateObj && startDateObj >= twoYearsAgo) {
1869
2016
  isSummaryCreated = true;
1870
2017
  html += `
1871
2018
  <tr>
1872
2019
  <td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
2020
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
1873
2021
  <td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
1874
2022
  <td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
1875
2023
  <td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
1876
2024
  <td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
1877
2025
  <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
2026
+ <td>${templateUtilities.renderTextAsHtml(data["source"])}</td>
1878
2027
  </tr>`;
1879
2028
  }
1880
2029
  }
@@ -1883,7 +2032,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1883
2032
  </tbody>
1884
2033
  </table>
1885
2034
  </div>`;
1886
- return isSummaryCreated ? html : void 0;
2035
+ if (skippedMedications > 0) {
2036
+ html += `
2037
+ <p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
2038
+ }
2039
+ if (isSummaryCreated || skippedMedications > 0) {
2040
+ return html;
2041
+ }
2042
+ return void 0;
1887
2043
  }
1888
2044
  /**
1889
2045
  * Safely parse a date string and return a valid Date object or null
@@ -1901,21 +2057,42 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1901
2057
  * Internal static implementation that actually generates the narrative
1902
2058
  * @param resources - FHIR Medication resources
1903
2059
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2060
+ * @param now - Optional current date to use for calculations (defaults to new Date())
1904
2061
  * @returns HTML string for rendering
1905
2062
  */
1906
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1907
- static generateStaticNarrative(resources, timezone) {
2063
+ static generateStaticNarrative(resources, timezone, now) {
1908
2064
  const templateUtilities = new TemplateUtilities(resources);
1909
2065
  let html = "";
1910
2066
  const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
1911
2067
  const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
1912
2068
  const allActiveMedications = [];
2069
+ const currentDate = now || /* @__PURE__ */ new Date();
2070
+ const twoYearsAgo = new Date(currentDate);
2071
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2072
+ let skippedMedications = 0;
2073
+ const allMedications = [];
1913
2074
  medicationRequests.forEach((mr) => {
1914
- allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
2075
+ allMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1915
2076
  });
1916
2077
  medicationStatements.forEach((ms) => {
1917
- allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
2078
+ allMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1918
2079
  });
2080
+ for (const med of allMedications) {
2081
+ let dateString;
2082
+ if (med.type === "request") {
2083
+ const mr = med.resource;
2084
+ dateString = mr.dispenseRequest?.validityPeriod?.start || mr.authoredOn;
2085
+ } else {
2086
+ const ms = med.resource;
2087
+ dateString = ms.effectiveDateTime || ms.effectivePeriod?.start;
2088
+ }
2089
+ const dateObj = this.parseDate(dateString);
2090
+ if (!dateObj || dateObj < twoYearsAgo) {
2091
+ skippedMedications++;
2092
+ } else {
2093
+ allActiveMedications.push(med);
2094
+ }
2095
+ }
1919
2096
  const sortMedications = (medications) => {
1920
2097
  medications.sort((a, b) => {
1921
2098
  let dateStringA;
@@ -1946,7 +2123,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1946
2123
  sortMedications(allActiveMedications);
1947
2124
  html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
1948
2125
  }
1949
- return html;
2126
+ if (skippedMedications > 0) {
2127
+ html += `
2128
+ <p><em>${skippedMedications} additional medications older than 2 years ago are present</em></p>`;
2129
+ }
2130
+ if (allActiveMedications.length > 0 || skippedMedications > 0) {
2131
+ return html;
2132
+ }
2133
+ return "";
1950
2134
  }
1951
2135
  /**
1952
2136
  * Extract MedicationRequest resources
@@ -1991,10 +2175,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1991
2175
  <tr>
1992
2176
  <th>Type</th>
1993
2177
  <th>Medication</th>
2178
+ <th>Code (System)</th>
1994
2179
  <th>Sig</th>
1995
2180
  <th>Dispense Quantity</th>
1996
2181
  <th>Refills</th>
1997
2182
  <th>Start Date</th>
2183
+ <th>Source</th>
1998
2184
  </tr>
1999
2185
  </thead>
2000
2186
  <tbody>`;
@@ -2003,27 +2189,31 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2003
2189
  let type;
2004
2190
  let medicationName;
2005
2191
  let sig;
2006
- let dispenseQuantity = "-";
2007
- let refills = "-";
2008
- let startDate = "-";
2192
+ let dispenseQuantity = "";
2193
+ let refills = "";
2194
+ let startDate = "";
2195
+ let codeSystemDisplay = "";
2009
2196
  if (medication.type === "request") {
2010
2197
  const mr = medication.resource;
2011
2198
  type = "Request";
2012
2199
  medicationName = templateUtilities.getMedicationName(
2013
2200
  mr.medicationReference || mr.medicationCodeableConcept
2014
2201
  );
2015
- sig = templateUtilities.concat(mr.dosageInstruction, "text") || "-";
2202
+ sig = templateUtilities.concat(mr.dosageInstruction, "text") || "";
2016
2203
  if (mr.dispenseRequest?.quantity) {
2017
2204
  const quantity = mr.dispenseRequest.quantity;
2018
2205
  if (quantity.value) {
2019
2206
  dispenseQuantity = `${quantity.value} ${quantity.unit || quantity.code || ""}`.trim();
2020
2207
  }
2021
2208
  }
2022
- refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
2209
+ refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "";
2023
2210
  if (mr.dispenseRequest?.validityPeriod) {
2024
- startDate = mr.dispenseRequest.validityPeriod.start || "-";
2211
+ startDate = mr.dispenseRequest.validityPeriod.start || "";
2025
2212
  } else {
2026
- startDate = mr.authoredOn || "-";
2213
+ startDate = mr.authoredOn || "";
2214
+ }
2215
+ if (mr.medicationCodeableConcept && mr.medicationCodeableConcept.coding && mr.medicationCodeableConcept.coding[0]) {
2216
+ codeSystemDisplay = templateUtilities.codeableConceptCoding(mr.medicationCodeableConcept);
2027
2217
  }
2028
2218
  } else {
2029
2219
  const ms = medication.resource;
@@ -2031,21 +2221,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2031
2221
  medicationName = templateUtilities.getMedicationName(
2032
2222
  ms.medicationReference || ms.medicationCodeableConcept
2033
2223
  );
2034
- sig = templateUtilities.concat(ms.dosage, "text") || "-";
2224
+ sig = templateUtilities.concat(ms.dosage, "text") || "";
2035
2225
  if (ms.effectiveDateTime) {
2036
2226
  startDate = ms.effectiveDateTime;
2037
2227
  } else if (ms.effectivePeriod) {
2038
- startDate = ms.effectivePeriod.start || "-";
2228
+ startDate = ms.effectivePeriod.start || "";
2229
+ }
2230
+ if (ms.medicationCodeableConcept && ms.medicationCodeableConcept.coding && ms.medicationCodeableConcept.coding[0]) {
2231
+ codeSystemDisplay = templateUtilities.codeableConceptCoding(ms.medicationCodeableConcept);
2039
2232
  }
2040
2233
  }
2041
2234
  html += `
2042
2235
  <tr${narrativeLinkId ? ` id="${narrativeLinkId}"` : ""}>
2043
2236
  <td>${type}</td>
2044
2237
  <td>${medicationName}<ul></ul></td>
2238
+ <td>${codeSystemDisplay}</td>
2045
2239
  <td>${sig}</td>
2046
2240
  <td>${dispenseQuantity}</td>
2047
2241
  <td>${refills}</td>
2048
2242
  <td>${startDate}</td>
2243
+ <td>${templateUtilities.getOwnerTag(medication.resource)}</td>
2049
2244
  </tr>`;
2050
2245
  }
2051
2246
  html += `
@@ -2086,14 +2281,18 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2086
2281
  <thead>
2087
2282
  <tr>
2088
2283
  <th>Immunization</th>
2284
+ <th>Code (System)</th>
2089
2285
  <th>Status</th>
2090
2286
  <th>Date</th>
2287
+ <th>Source</th>
2091
2288
  </tr>
2092
2289
  </thead>
2093
2290
  <tbody>`;
2094
2291
  for (const resourceItem of resources) {
2095
2292
  for (const rowData of resourceItem.section ?? []) {
2293
+ const sectionCodeableConcept = rowData.code;
2096
2294
  const data = {};
2295
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2097
2296
  for (const columnData of rowData.section ?? []) {
2098
2297
  switch (columnData.title) {
2099
2298
  case "Immunization Name":
@@ -2105,6 +2304,9 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2105
2304
  case "occurrenceDateTime":
2106
2305
  data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2107
2306
  break;
2307
+ case "Source":
2308
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2309
+ break;
2108
2310
  default:
2109
2311
  break;
2110
2312
  }
@@ -2113,9 +2315,11 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2113
2315
  isSummaryCreated = true;
2114
2316
  html += `
2115
2317
  <tr>
2116
- <td>${data["immunization"] ?? "-"}</td>
2117
- <td>${data["status"] ?? "-"}</td>
2118
- <td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? "-"}</td>
2318
+ <td>${data["immunization"] ?? ""}</td>
2319
+ <td>${data["codeSystem"] ?? ""}</td>
2320
+ <td>${data["status"] ?? ""}</td>
2321
+ <td>${templateUtilities.renderTime(data["occurrenceDateTime"], timezone) ?? ""}</td>
2322
+ <td>${data["source"] ?? ""}</td>
2119
2323
  </tr>`;
2120
2324
  }
2121
2325
  }
@@ -2139,12 +2343,14 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2139
2343
  <thead>
2140
2344
  <tr>
2141
2345
  <th>Immunization</th>
2346
+ <th>Code (System)</th>
2142
2347
  <th>Status</th>
2143
2348
  <th>Dose Number</th>
2144
2349
  <th>Manufacturer</th>
2145
2350
  <th>Lot Number</th>
2146
2351
  <th>Comments</th>
2147
2352
  <th>Date</th>
2353
+ <th>Source</th>
2148
2354
  </tr>
2149
2355
  </thead>
2150
2356
  <tbody>`;
@@ -2154,13 +2360,15 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2154
2360
  const imm = resourceItem;
2155
2361
  html += `
2156
2362
  <tr id="${templateUtilities.narrativeLinkId(imm)}">
2157
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
2363
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(imm.vaccineCode))}</td>
2364
+ <td>${templateUtilities.codeableConceptCoding(imm.vaccineCode)}</td>
2158
2365
  <td>${imm.status || ""}</td>
2159
2366
  <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
2160
2367
  <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
2161
2368
  <td>${imm.lotNumber || ""}</td>
2162
2369
  <td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
2163
2370
  <td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
2371
+ <td>${templateUtilities.getOwnerTag(imm)}</td>
2164
2372
  </tr>`;
2165
2373
  }
2166
2374
  }
@@ -2194,8 +2402,11 @@ var ProblemListTemplate = class _ProblemListTemplate {
2194
2402
  let html = ``;
2195
2403
  const activeConditions = resources.map((entry) => entry) || [];
2196
2404
  activeConditions.sort((a, b) => {
2197
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
2198
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
2405
+ if (!a.recordedDate && b.recordedDate) return -1;
2406
+ if (a.recordedDate && !b.recordedDate) return 1;
2407
+ if (!a.recordedDate && !b.recordedDate) return 0;
2408
+ const dateA = new Date(a.recordedDate).getTime();
2409
+ const dateB = new Date(b.recordedDate).getTime();
2199
2410
  return dateB - dateA;
2200
2411
  });
2201
2412
  html += `
@@ -2203,22 +2414,28 @@ var ProblemListTemplate = class _ProblemListTemplate {
2203
2414
  <thead>
2204
2415
  <tr>
2205
2416
  <th>Problem</th>
2417
+ <th>Code (System)</th>
2206
2418
  <th>Onset Date</th>
2207
2419
  <th>Recorded Date</th>
2420
+ <th>Source</th>
2208
2421
  </tr>
2209
2422
  </thead>
2210
2423
  <tbody>`;
2211
- const addedConditionCodes = /* @__PURE__ */ new Set();
2424
+ const seenCodeAndSystems = /* @__PURE__ */ new Set();
2212
2425
  for (const cond of activeConditions) {
2213
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2214
- if (!addedConditionCodes.has(conditionCode)) {
2215
- addedConditionCodes.add(conditionCode);
2216
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2217
- <td class="Name">${conditionCode}</td>
2218
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2219
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2220
- </tr>`;
2426
+ const conditionDisplay = templateUtilities.codeableConceptDisplay(cond.code);
2427
+ const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
2428
+ if (codeAndSystem && seenCodeAndSystems.has(codeAndSystem)) {
2429
+ continue;
2221
2430
  }
2431
+ seenCodeAndSystems.add(codeAndSystem);
2432
+ html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2433
+ <td class="Name">${conditionDisplay}</td>
2434
+ <td class="CodeSystem">${codeAndSystem}</td>
2435
+ <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2436
+ <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2437
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
2438
+ </tr>`;
2222
2439
  }
2223
2440
  html += `</tbody>
2224
2441
  </table>`;
@@ -2251,16 +2468,19 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2251
2468
  <table>
2252
2469
  <thead>
2253
2470
  <tr>
2254
- <th>Vital Name</th>
2471
+ <th>Name</th>
2472
+ <th>Code (System)</th>
2255
2473
  <th>Result</th>
2256
- <th>Reference Range</th>
2257
2474
  <th>Date</th>
2475
+ <th>Source</th>
2258
2476
  </tr>
2259
2477
  </thead>
2260
2478
  <tbody>`;
2261
2479
  for (const resourceItem of resources) {
2262
2480
  for (const rowData of resourceItem.section ?? []) {
2481
+ const sectionCodeableConcept = rowData.code;
2263
2482
  const data = {};
2483
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2264
2484
  for (const columnData of rowData.section ?? []) {
2265
2485
  const columnTitle = columnData.title;
2266
2486
  if (columnTitle) {
@@ -2288,10 +2508,11 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2288
2508
  isSummaryCreated = true;
2289
2509
  html += `
2290
2510
  <tr>
2291
- <td>${data["Vital Name"] ?? "-"}</td>
2292
- <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? "-"}</td>
2293
- <td>${templateUtilities.extractObservationSummaryReferenceRange(data) ?? "-"}</td>
2294
- <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "-"}</td>
2511
+ <td>${data["Vital Name"] ?? ""}</td>
2512
+ <td>${data["codeSystem"] ?? ""}</td>
2513
+ <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? ""}</td>
2514
+ <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? ""}</td>
2515
+ <td>${data["Source"] ?? ""}</td>
2295
2516
  </tr>`;
2296
2517
  }
2297
2518
  }
@@ -2319,26 +2540,30 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2319
2540
  <table>
2320
2541
  <thead>
2321
2542
  <tr>
2322
- <th>Vital Name</th>
2543
+ <th>Name</th>
2544
+ <th>Code (System)</th>
2323
2545
  <th>Result</th>
2324
2546
  <th>Unit</th>
2325
2547
  <th>Interpretation</th>
2326
2548
  <th>Component(s)</th>
2327
2549
  <th>Comments</th>
2328
2550
  <th>Date</th>
2551
+ <th>Source</th>
2329
2552
  </tr>
2330
2553
  </thead>
2331
2554
  <tbody>`;
2332
2555
  for (const obs of observations) {
2333
2556
  html += `
2334
2557
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2335
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
2558
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code, "display"))}</td>
2559
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2336
2560
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2337
2561
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2338
2562
  <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
2339
2563
  <td>${templateUtilities.renderComponent(obs.component)}</td>
2340
2564
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
2341
2565
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
2566
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2342
2567
  </tr>`;
2343
2568
  }
2344
2569
  html += `
@@ -2411,10 +2636,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2411
2636
  * Generate HTML narrative for Diagnostic Results
2412
2637
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2413
2638
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2639
+ * @param now - Optional current date for filtering
2414
2640
  * @returns HTML string for rendering
2415
2641
  */
2416
- generateNarrative(resources, timezone) {
2417
- return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2642
+ generateNarrative(resources, timezone, now) {
2643
+ return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone, now);
2418
2644
  }
2419
2645
  /**
2420
2646
  * Helper function to format observation data fields
@@ -2627,6 +2853,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2627
2853
  break;
2628
2854
  case "valueRange.high.value":
2629
2855
  targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2856
+ targetData["valueType"] = "valueRange";
2630
2857
  break;
2631
2858
  case "valueRange.high.unit":
2632
2859
  targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2641,6 +2868,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2641
2868
  break;
2642
2869
  case "valueRatio.denominator.value":
2643
2870
  targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2871
+ targetData["valueType"] = "valueRatio";
2644
2872
  break;
2645
2873
  case "valueRatio.denominator.unit":
2646
2874
  targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2678,10 +2906,71 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2678
2906
  * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2679
2907
  * @param resources - FHIR Composition resources
2680
2908
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2909
+ * @param now - Optional current date for filtering
2681
2910
  * @returns HTML string for rendering
2682
2911
  */
2683
- generateSummaryNarrative(resources, timezone) {
2912
+ generateSummaryNarrative(resources, timezone, now) {
2684
2913
  const templateUtilities = new TemplateUtilities(resources);
2914
+ const currentDate = now || /* @__PURE__ */ new Date();
2915
+ const twoYearsAgo = new Date(currentDate);
2916
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2917
+ let skippedObservations = 0;
2918
+ let skippedDiagnosticReports = 0;
2919
+ for (const resourceItem of resources) {
2920
+ for (const rowData of resourceItem.section ?? []) {
2921
+ if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2922
+ for (const columnData of rowData.section ?? []) {
2923
+ if (columnData.text?.div === "Observation.component" && columnData.section) {
2924
+ for (const componentSection of columnData.section) {
2925
+ const componentData = {};
2926
+ for (const nestedColumn of componentSection.section ?? []) {
2927
+ this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2928
+ }
2929
+ let compDate = void 0;
2930
+ if (componentData["effectiveDateTime"]) {
2931
+ compDate = new Date(componentData["effectiveDateTime"]);
2932
+ } else if (componentData["effectivePeriodStart"]) {
2933
+ compDate = new Date(componentData["effectivePeriodStart"]);
2934
+ }
2935
+ if (compDate && compDate < twoYearsAgo) {
2936
+ skippedObservations++;
2937
+ }
2938
+ }
2939
+ } else {
2940
+ const data = {};
2941
+ this.extractSummaryObservationFields(columnData, data, templateUtilities);
2942
+ let obsDate = void 0;
2943
+ if (data["effectiveDateTime"]) {
2944
+ obsDate = new Date(data["effectiveDateTime"]);
2945
+ } else if (data["effectivePeriodStart"]) {
2946
+ obsDate = new Date(data["effectivePeriodStart"]);
2947
+ }
2948
+ if (obsDate && obsDate < twoYearsAgo) {
2949
+ skippedObservations++;
2950
+ }
2951
+ }
2952
+ }
2953
+ } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2954
+ let issuedDate = void 0;
2955
+ let status = void 0;
2956
+ let reportFound = false;
2957
+ for (const columnData of rowData.section ?? []) {
2958
+ if (columnData.title === "Issued Date") {
2959
+ issuedDate = columnData.text?.div ? new Date(columnData.text.div) : void 0;
2960
+ }
2961
+ if (columnData.title === "Status") {
2962
+ status = columnData.text?.div;
2963
+ }
2964
+ if (columnData.title === "Diagnostic Report Name") {
2965
+ reportFound = true;
2966
+ }
2967
+ }
2968
+ if (status === "final" && issuedDate && issuedDate < twoYearsAgo && reportFound) {
2969
+ skippedDiagnosticReports++;
2970
+ }
2971
+ }
2972
+ }
2973
+ }
2685
2974
  let html = `
2686
2975
  <div>`;
2687
2976
  let observationhtml = `
@@ -2690,10 +2979,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2690
2979
  <table>
2691
2980
  <thead>
2692
2981
  <tr>
2693
- <th>Code</th>
2982
+ <th>Name</th>
2983
+ <th>Code (System)</th>
2694
2984
  <th>Result</th>
2695
2985
  <th>Reference Range</th>
2696
2986
  <th>Date</th>
2987
+ <th>Source</th>
2697
2988
  </tr>
2698
2989
  </thead>
2699
2990
  <tbody>`;
@@ -2706,6 +2997,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2706
2997
  <th>Report</th>
2707
2998
  <th>Performer</th>
2708
2999
  <th>Issued</th>
3000
+ <th>Source</th>
2709
3001
  </tr>
2710
3002
  </thead>
2711
3003
  <tbody>`;
@@ -2713,7 +3005,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2713
3005
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2714
3006
  for (const resourceItem of resources) {
2715
3007
  for (const rowData of resourceItem.section ?? []) {
3008
+ const sectionCodeableConcept = rowData.code;
2716
3009
  const data = {};
3010
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2717
3011
  const components = [];
2718
3012
  for (const columnData of rowData.section ?? []) {
2719
3013
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
@@ -2723,7 +3017,13 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2723
3017
  for (const nestedColumn of componentSection.section ?? []) {
2724
3018
  this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2725
3019
  }
2726
- if (Object.keys(componentData).length > 0) {
3020
+ let compDate = void 0;
3021
+ if (componentData["effectiveDateTime"]) {
3022
+ compDate = new Date(componentData["effectiveDateTime"]);
3023
+ } else if (componentData["effectivePeriodStart"]) {
3024
+ compDate = new Date(componentData["effectivePeriodStart"]);
3025
+ }
3026
+ if (compDate && compDate >= twoYearsAgo && Object.keys(componentData).length > 0) {
2727
3027
  components.push(componentData);
2728
3028
  }
2729
3029
  }
@@ -2744,6 +3044,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2744
3044
  case "Status":
2745
3045
  data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2746
3046
  break;
3047
+ case "Source":
3048
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3049
+ break;
2747
3050
  default:
2748
3051
  break;
2749
3052
  }
@@ -2751,6 +3054,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2751
3054
  }
2752
3055
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2753
3056
  let date = data["effectiveDateTime"] ? templateUtilities.renderTime(data["effectiveDateTime"], timezone) : "";
3057
+ let obsDate = void 0;
3058
+ if (data["effectiveDateTime"]) {
3059
+ obsDate = new Date(data["effectiveDateTime"]);
3060
+ } else if (data["effectivePeriodStart"]) {
3061
+ obsDate = new Date(data["effectivePeriodStart"]);
3062
+ }
2754
3063
  if (!date && data["effectivePeriodStart"]) {
2755
3064
  date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2756
3065
  if (data["effectivePeriodEnd"]) {
@@ -2767,36 +3076,47 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2767
3076
  observationhtml += `
2768
3077
  <tr>
2769
3078
  <td>${componentCode}</td>
2770
- <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
2771
- <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
2772
- <td>${date ?? "-"}</td>
3079
+ <td></td>
3080
+ <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? ""}</td>
3081
+ <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? ""}</td>
3082
+ <td>${date ?? ""}</td>
3083
+ <td>${data["source"] ?? ""}</td>
2773
3084
  </tr>`;
2774
3085
  }
2775
3086
  }
2776
3087
  } else {
2777
- const code = data["code"] ?? "";
2778
- if (code && !observationAdded.has(code)) {
2779
- observationAdded.add(code);
2780
- this.formatSummaryObservationData(data);
2781
- observationhtml += `
3088
+ if (obsDate && obsDate >= twoYearsAgo) {
3089
+ const code = data["code"] ?? "";
3090
+ if (code && !observationAdded.has(code)) {
3091
+ observationAdded.add(code);
3092
+ this.formatSummaryObservationData(data);
3093
+ observationhtml += `
2782
3094
  <tr>
2783
- <td>${data["code"] ?? "-"}</td>
2784
- <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
2785
- <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
2786
- <td>${date ?? "-"}</td>
3095
+ <td>${data["code"] ?? ""}</td>
3096
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
3097
+ <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? ""}</td>
3098
+ <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? ""}</td>
3099
+ <td>${date ?? ""}</td>
3100
+ <td>${data["source"] ?? ""}</td>
2787
3101
  </tr>`;
3102
+ }
2788
3103
  }
2789
3104
  }
2790
3105
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2791
- if (data["status"] === "final") {
3106
+ let issuedDate = void 0;
3107
+ if (data["issued"]) {
3108
+ issuedDate = new Date(data["issued"]);
3109
+ }
3110
+ if (data["status"] === "final" && issuedDate && issuedDate >= twoYearsAgo) {
2792
3111
  const reportName = data["report"] ?? "";
2793
3112
  if (reportName && !diagnosticReportAdded.has(reportName)) {
2794
3113
  diagnosticReportAdded.add(reportName);
2795
3114
  diagnosticReporthtml += `
2796
3115
  <tr>
2797
- <td>${data["report"] ?? "-"}</td>
2798
- <td>${data["performer"] ?? "-"}</td>
2799
- <td>${templateUtilities.renderTime(data["issued"], timezone) ?? "-"}</td>
3116
+ <td>${data["report"] ?? ""}</td>
3117
+ <td>${data["performer"] ?? ""}</td>
3118
+ <td>${templateUtilities.renderTime(data["issued"], timezone) ?? ""}</td>
3119
+ <td>${data["source"] ?? ""}</td>
2800
3120
  </tr>`;
2801
3121
  }
2802
3122
  }
@@ -2809,6 +3129,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2809
3129
  </tbody>
2810
3130
  </table>
2811
3131
  </div>`;
3132
+ if (skippedObservations > 0) {
3133
+ html += `
3134
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3135
+ }
2812
3136
  }
2813
3137
  if (diagnosticReportAdded.size > 0) {
2814
3138
  html += diagnosticReporthtml;
@@ -2816,6 +3140,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2816
3140
  </tbody>
2817
3141
  </table>
2818
3142
  </div>`;
3143
+ if (skippedDiagnosticReports > 0) {
3144
+ html += `
3145
+ <p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
3146
+ }
2819
3147
  }
2820
3148
  html += `
2821
3149
  </div>`;
@@ -2825,12 +3153,32 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2825
3153
  * Internal static implementation that actually generates the narrative
2826
3154
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2827
3155
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3156
+ * @param now - Optional current date for filtering
2828
3157
  * @returns HTML string for rendering
2829
3158
  */
2830
- static generateStaticNarrative(resources, timezone) {
3159
+ static generateStaticNarrative(resources, timezone, now) {
2831
3160
  const templateUtilities = new TemplateUtilities(resources);
3161
+ const currentDate = now || /* @__PURE__ */ new Date();
3162
+ const twoYearsAgo = new Date(currentDate);
3163
+ twoYearsAgo.setFullYear(currentDate.getFullYear() - 2);
2832
3164
  let html = "";
2833
- const observations = this.getObservations(resources);
3165
+ let skippedObservations = 0;
3166
+ let skippedDiagnosticReports = 0;
3167
+ for (const resourceItem of resources) {
3168
+ if (resourceItem.resourceType === "Observation") {
3169
+ const obsDate = this.getObservationDate(resourceItem);
3170
+ if (obsDate && obsDate < twoYearsAgo) {
3171
+ skippedObservations++;
3172
+ }
3173
+ } else if (resourceItem.resourceType === "DiagnosticReport") {
3174
+ const issued = resourceItem.issued;
3175
+ const status = resourceItem.status;
3176
+ if (status === "final" && issued && new Date(issued) < twoYearsAgo) {
3177
+ skippedDiagnosticReports++;
3178
+ }
3179
+ }
3180
+ }
3181
+ const observations = this.getObservations(resources, twoYearsAgo);
2834
3182
  if (observations.length > 0) {
2835
3183
  observations.sort((a, b) => {
2836
3184
  const dateA = this.getObservationDate(a);
@@ -2839,16 +3187,22 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2839
3187
  });
2840
3188
  this.filterObservationForLoincCodes(observations);
2841
3189
  html += this.renderObservations(templateUtilities, observations, timezone);
3190
+ if (skippedObservations > 0) {
3191
+ html += `
3192
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3193
+ }
2842
3194
  }
2843
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2844
- const diagnosticReports = this.getDiagnosticReports(resources);
2845
- if (diagnosticReports.length > 0) {
2846
- diagnosticReports.sort((a, b) => {
2847
- const dateA = a.issued;
2848
- const dateB = b.issued;
2849
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2850
- });
2851
- html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
3195
+ const diagnosticReports = this.getDiagnosticReports(resources, twoYearsAgo).filter((resource) => !this.isPanelDiagnosticReport(resource));
3196
+ if (diagnosticReports.length > 0) {
3197
+ diagnosticReports.sort((a, b) => {
3198
+ const dateA = a.issued;
3199
+ const dateB = b.issued;
3200
+ return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3201
+ });
3202
+ html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
3203
+ if (skippedDiagnosticReports > 0) {
3204
+ html += `
3205
+ <p><em>${skippedDiagnosticReports} additional diagnostic reports older than 2 years ago are present</em></p>`;
2852
3206
  }
2853
3207
  }
2854
3208
  return html;
@@ -2893,15 +3247,16 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2893
3247
  return obsDate;
2894
3248
  }
2895
3249
  /**
2896
- * Get all Observation resources from the resource array
2897
- * @param resources - FHIR resources array
2898
- * @returns Array of Observation resources
2899
- */
2900
- static getObservations(resources) {
3250
+ * Get all Observation resources from the resource array, filtered by twoYearsAgo
3251
+ * @param resources - FHIR resources array
3252
+ * @param twoYearsAgo - Date object representing the cutoff
3253
+ * @returns Array of Observation resources
3254
+ */
3255
+ static getObservations(resources, twoYearsAgo) {
2901
3256
  return resources.filter((resourceItem) => {
2902
3257
  if (resourceItem.resourceType === "Observation") {
2903
3258
  const obsDate = this.getObservationDate(resourceItem);
2904
- if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
3259
+ if (obsDate && obsDate >= twoYearsAgo) {
2905
3260
  return true;
2906
3261
  }
2907
3262
  }
@@ -2909,12 +3264,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2909
3264
  }).map((resourceItem) => resourceItem);
2910
3265
  }
2911
3266
  /**
2912
- * Get all DiagnosticReport resources from the resource array
3267
+ * Get all DiagnosticReport resources from the resource array, filtered by twoYearsAgo
2913
3268
  * @param resources - FHIR resources array
3269
+ * @param twoYearsAgo - Date object representing the cutoff
2914
3270
  * @returns Array of DiagnosticReport resources
2915
3271
  */
2916
- static getDiagnosticReports(resources) {
2917
- return resources.filter((resourceItem) => resourceItem.resourceType === "DiagnosticReport").map((resourceItem) => resourceItem);
3272
+ static getDiagnosticReports(resources, twoYearsAgo) {
3273
+ return resources.filter((resourceItem) => {
3274
+ if (resourceItem.resourceType === "DiagnosticReport") {
3275
+ const issued = resourceItem.issued;
3276
+ if (issued && new Date(issued) >= twoYearsAgo) {
3277
+ return true;
3278
+ }
3279
+ }
3280
+ return false;
3281
+ }).map((resourceItem) => resourceItem);
2918
3282
  }
2919
3283
  /**
2920
3284
  * Render HTML table for Observation resources
@@ -2925,32 +3289,36 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2925
3289
  */
2926
3290
  static renderObservations(templateUtilities, observations, timezone) {
2927
3291
  let html = "";
2928
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2929
- html += `
2930
- <h3>Observations</h3>`;
2931
- }
3292
+ html += `
3293
+ <h3>Observations</h3>`;
2932
3294
  html += `
2933
3295
  <table>
2934
3296
  <thead>
2935
3297
  <tr>
2936
- <th>Code</th>
3298
+ <th>Name</th>
3299
+ <th>Code (System)</th>
2937
3300
  <th>Result</th>
2938
3301
  <th>Reference Range</th>
2939
3302
  <th>Date</th>
3303
+ <th>Source</th>
2940
3304
  </tr>
2941
3305
  </thead>
2942
3306
  <tbody>`;
2943
3307
  const observationAdded = /* @__PURE__ */ new Set();
2944
3308
  for (const obs of observations) {
2945
- const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
2946
- if (!observationAdded.has(obsCode)) {
2947
- observationAdded.add(obsCode);
3309
+ const obsCodeDisplay = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code));
3310
+ const obsCodeAndSystem = templateUtilities.codeableConceptCoding(obs.code);
3311
+ if (!observationAdded.has(obsCodeDisplay) && !observationAdded.has(obsCodeAndSystem)) {
3312
+ observationAdded.add(obsCodeDisplay);
3313
+ observationAdded.add(obsCodeAndSystem);
2948
3314
  html += `
2949
3315
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2950
- <td>${obsCode}</td>
3316
+ <td>${obsCodeDisplay}</td>
3317
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2951
3318
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2952
3319
  <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
2953
3320
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3321
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2954
3322
  </tr>`;
2955
3323
  }
2956
3324
  }
@@ -2973,17 +3341,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2973
3341
  <thead>
2974
3342
  <tr>
2975
3343
  <th>Report</th>
3344
+ <th>Code (System)</th>
2976
3345
  <th>Category</th>
2977
3346
  <th>Result</th>
2978
3347
  <th>Issued</th>
3348
+ <th>Source</th>
2979
3349
  </tr>
2980
3350
  </thead>
2981
3351
  <tbody>`;
2982
3352
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2983
3353
  for (const report of reports) {
2984
- const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
2985
- if (!diagnosticReportAdded.has(reportName)) {
3354
+ const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(report.code));
3355
+ const codeAndSystem = templateUtilities.codeableConceptCoding(report.code);
3356
+ if (!diagnosticReportAdded.has(reportName) && !diagnosticReportAdded.has(codeAndSystem)) {
2986
3357
  diagnosticReportAdded.add(reportName);
3358
+ diagnosticReportAdded.add(codeAndSystem);
2987
3359
  let resultCount = "";
2988
3360
  if (report.result && Array.isArray(report.result)) {
2989
3361
  resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
@@ -2991,9 +3363,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2991
3363
  html += `
2992
3364
  <tr id="${templateUtilities.narrativeLinkId(report)}">
2993
3365
  <td>${reportName}</td>
3366
+ <td>${codeAndSystem}</td>
2994
3367
  <td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
2995
3368
  <td>${resultCount}</td>
2996
3369
  <td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
3370
+ <td>${templateUtilities.getOwnerTag(report)}</td>
2997
3371
  </tr>`;
2998
3372
  }
2999
3373
  }
@@ -3002,6 +3376,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3002
3376
  </table>`;
3003
3377
  return html;
3004
3378
  }
3379
+ /**
3380
+ * 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)
3381
+ * @returns true if the report is just a panel
3382
+ */
3383
+ static isPanelDiagnosticReport(report) {
3384
+ return this.hasEssentialLabPanelLoinc(
3385
+ report
3386
+ );
3387
+ }
3388
+ /**
3389
+ * Check if a DiagnosticReport or Observation has a LOINC code in ESSENTIAL_LAB_PANELS
3390
+ * @param resource - DiagnosticReport or Observation
3391
+ * @returns true if any LOINC code is in ESSENTIAL_LAB_PANELS, false otherwise
3392
+ */
3393
+ static hasEssentialLabPanelLoinc(resource) {
3394
+ const codings = resource?.code?.coding ?? [];
3395
+ return codings.some(
3396
+ (coding) => coding && coding.system && coding.code && coding.system.toLowerCase().includes("loinc") && Object.keys(ESSENTIAL_LAB_PANELS).includes(coding.code)
3397
+ );
3398
+ }
3005
3399
  };
3006
3400
 
3007
3401
  // src/narratives/templates/typescript/HistoryOfProceduresTemplate.ts
@@ -3035,14 +3429,18 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3035
3429
  <thead>
3036
3430
  <tr>
3037
3431
  <th>Procedure</th>
3432
+ <th>Code (System)</th>
3038
3433
  <th>Performer</th>
3039
3434
  <th>Date</th>
3435
+ <th>Source</th>
3040
3436
  </tr>
3041
3437
  </thead>
3042
3438
  <tbody>`;
3043
3439
  for (const resourceItem of resources) {
3044
3440
  for (const rowData of resourceItem.section ?? []) {
3441
+ const sectionCodeableConcept = rowData.code;
3045
3442
  const data = {};
3443
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
3046
3444
  for (const columnData of rowData.section ?? []) {
3047
3445
  switch (columnData.title) {
3048
3446
  case "Procedure Name":
@@ -3054,6 +3452,9 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3054
3452
  case "Performed Date":
3055
3453
  data["date"] = columnData.text?.div ?? "";
3056
3454
  break;
3455
+ case "Source":
3456
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3457
+ break;
3057
3458
  default:
3058
3459
  break;
3059
3460
  }
@@ -3061,9 +3462,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3061
3462
  isSummaryCreated = true;
3062
3463
  html += `
3063
3464
  <tr>
3064
- <td>${data["procedure"] ?? "-"}</td>
3065
- <td>${data["performer"] ?? "-"}</td>
3066
- <td>${templateUtilities.renderTime(data["date"], timezone) ?? "-"}</td>
3465
+ <td>${data["procedure"] ?? ""}</td>
3466
+ <td>${data["codeSystem"] ?? ""}</td>
3467
+ <td>${data["performer"] ?? ""}</td>
3468
+ <td>${templateUtilities.renderTime(data["date"], timezone) ?? ""}</td>
3469
+ <td>${data["source"] ?? ""}</td>
3067
3470
  </tr>`;
3068
3471
  }
3069
3472
  }
@@ -3086,8 +3489,10 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3086
3489
  <thead>
3087
3490
  <tr>
3088
3491
  <th>Procedure</th>
3492
+ <th>Code (System)</th>
3089
3493
  <th>Comments</th>
3090
3494
  <th>Date</th>
3495
+ <th>Source</th>
3091
3496
  </tr>
3092
3497
  </thead>
3093
3498
  <tbody>`;
@@ -3095,9 +3500,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3095
3500
  const proc = resourceItem;
3096
3501
  html += `
3097
3502
  <tr id="${templateUtilities.narrativeLinkId(proc)}">
3098
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
3503
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(proc.code, "display"))}</td>
3504
+ <td>${templateUtilities.codeableConceptCoding(proc.code)}</td>
3099
3505
  <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
3100
3506
  <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
3507
+ <td>${templateUtilities.getOwnerTag(proc)}</td>
3101
3508
  </tr>`;
3102
3509
  }
3103
3510
  html += `
@@ -3136,22 +3543,26 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3136
3543
  <table>
3137
3544
  <thead>
3138
3545
  <tr>
3139
- <th>Code</th>
3546
+ <th>Name</th>
3547
+ <th>Code (System)</th>
3140
3548
  <th>Result</th>
3141
3549
  <th>Unit</th>
3142
3550
  <th>Comments</th>
3143
3551
  <th>Date</th>
3552
+ <th>Source</th>
3144
3553
  </tr>
3145
3554
  </thead>
3146
3555
  <tbody>`;
3147
3556
  for (const obs of observations) {
3148
3557
  html += `
3149
3558
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
3150
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
3559
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code))}</td>
3560
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
3151
3561
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3152
3562
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
3153
3563
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3154
3564
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3565
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
3155
3566
  </tr>`;
3156
3567
  }
3157
3568
  html += `
@@ -3167,14 +3578,26 @@ var PastHistoryOfIllnessTemplate = class {
3167
3578
  * Generate HTML narrative for Past History of Illnesses
3168
3579
  * @param resources - FHIR Condition resources
3169
3580
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
3581
+ * @param now - Optional current date to use for generating relative dates in the narrative
3170
3582
  * @returns HTML string for rendering
3171
3583
  */
3172
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3173
- generateNarrative(resources, timezone) {
3584
+ generateNarrative(resources, timezone, now) {
3174
3585
  const templateUtilities = new TemplateUtilities(resources);
3175
3586
  let html = ``;
3176
3587
  const resolvedConditions = resources.map((entry) => entry) || [];
3177
- resolvedConditions.sort((a, b) => {
3588
+ const currentDate = now || /* @__PURE__ */ new Date();
3589
+ const fiveYearsAgo = new Date(currentDate);
3590
+ fiveYearsAgo.setFullYear(currentDate.getFullYear() - 5);
3591
+ let skippedConditions = 0;
3592
+ const filteredConditions = [];
3593
+ for (const cond of resolvedConditions) {
3594
+ if (cond.recordedDate && new Date(cond.recordedDate) >= fiveYearsAgo) {
3595
+ filteredConditions.push(cond);
3596
+ } else {
3597
+ skippedConditions++;
3598
+ }
3599
+ }
3600
+ filteredConditions.sort((a, b) => {
3178
3601
  const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3179
3602
  const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3180
3603
  return dateB - dateA;
@@ -3184,27 +3607,35 @@ var PastHistoryOfIllnessTemplate = class {
3184
3607
  <thead>
3185
3608
  <tr>
3186
3609
  <th>Problem</th>
3610
+ <th>Code (System)</th>
3187
3611
  <th>Onset Date</th>
3188
3612
  <th>Recorded Date</th>
3189
3613
  <th>Resolved Date</th>
3614
+ <th>Source</th>
3190
3615
  </tr>
3191
3616
  </thead>
3192
3617
  <tbody>`;
3193
3618
  const addedConditionCodes = /* @__PURE__ */ new Set();
3194
- for (const cond of resolvedConditions) {
3195
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3619
+ for (const cond of filteredConditions) {
3620
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(cond.code));
3196
3621
  if (!addedConditionCodes.has(conditionCode)) {
3197
3622
  addedConditionCodes.add(conditionCode);
3198
3623
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3199
3624
  <td class="Name">${conditionCode}</td>
3625
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(cond.code)}</td>
3200
3626
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3201
3627
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3202
3628
  <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
3629
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
3203
3630
  </tr>`;
3204
3631
  }
3205
3632
  }
3206
3633
  html += `</tbody>
3207
3634
  </table>`;
3635
+ if (skippedConditions > 0) {
3636
+ html += `
3637
+ <p><em>${skippedConditions} additional past illnesses older than 5 years ago are present</em></p>`;
3638
+ }
3208
3639
  return html;
3209
3640
  }
3210
3641
  };
@@ -3234,6 +3665,7 @@ var PlanOfCareTemplate = class {
3234
3665
  <th>Comments</th>
3235
3666
  <th>Planned Start</th>
3236
3667
  <th>Planned End</th>
3668
+ <th>Source</th>
3237
3669
  </tr>
3238
3670
  </thead>
3239
3671
  <tbody>`;
@@ -3245,6 +3677,7 @@ var PlanOfCareTemplate = class {
3245
3677
  <td>${templateUtilities.concat(cp.note, "text")}</td>
3246
3678
  <td>${cp.period?.start ? templateUtilities.renderTime(cp.period?.start, timezone) : ""}</td>
3247
3679
  <td>${cp.period?.end ? templateUtilities.renderTime(cp.period?.end, timezone) : ""}</td>
3680
+ <td>${templateUtilities.getOwnerTag(cp)}</td>
3248
3681
  </tr>`;
3249
3682
  }
3250
3683
  html += `
@@ -3270,6 +3703,7 @@ var PlanOfCareTemplate = class {
3270
3703
  <th>Created</th>
3271
3704
  <th>Planned Start</th>
3272
3705
  <th>Planned End</th>
3706
+ <th>Source</th>
3273
3707
  </tr>
3274
3708
  </thead>
3275
3709
  <tbody>`;
@@ -3287,10 +3721,11 @@ var PlanOfCareTemplate = class {
3287
3721
  isSummaryCreated = true;
3288
3722
  html += `
3289
3723
  <tr>
3290
- <td>${data["CarePlan Name"] ?? "-"}</td>
3291
- <td>${templateUtilities.renderTime(data["created"], timezone) ?? "-"}</td>
3292
- <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? "-"}</td>
3293
- <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? "-"}</td>
3724
+ <td>${data["CarePlan Name"] ?? ""}</td>
3725
+ <td>${templateUtilities.renderTime(data["created"], timezone) ?? ""}</td>
3726
+ <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? ""}</td>
3727
+ <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? ""}</td>
3728
+ <td>${data["source"] ?? ""}</td>
3294
3729
  </tr>`;
3295
3730
  }
3296
3731
  }
@@ -3321,77 +3756,54 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3321
3756
  */
3322
3757
  static generateStaticNarrative(resources, timezone) {
3323
3758
  const templateUtilities = new TemplateUtilities(resources);
3324
- let html = ``;
3325
- const activeConditions = [];
3326
- const clinicalImpressions = [];
3327
- for (const resourceItem of resources) {
3328
- if (resourceItem.resourceType === "Condition") {
3329
- activeConditions.push(resourceItem);
3330
- } else if (resourceItem.resourceType === "ClinicalImpression") {
3331
- clinicalImpressions.push(resourceItem);
3332
- }
3333
- }
3334
- activeConditions.sort((a, b) => {
3335
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3336
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3337
- return dateB - dateA;
3759
+ let html = "";
3760
+ let functionalObservations = resources.filter((r) => r.resourceType === "Observation").filter((r) => {
3761
+ const hasFunctionalLoinc = r.code?.coding?.some(
3762
+ (c) => c.system?.toLowerCase().includes("loinc") && c.code === "47420-5"
3763
+ );
3764
+ const hasFunctionalCategory = r.category?.some(
3765
+ (cat) => cat.coding?.some(
3766
+ (c) => c.code === "functional-status" || c.display?.toLowerCase().includes("functional")
3767
+ )
3768
+ );
3769
+ return hasFunctionalLoinc || hasFunctionalCategory;
3338
3770
  });
3339
- clinicalImpressions.sort((a, b) => {
3340
- 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;
3341
- 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;
3342
- return dateB - dateA;
3771
+ functionalObservations = functionalObservations.sort((a, b) => {
3772
+ const getObsDate = (obs) => obs.effectiveDateTime ? new Date(obs.effectiveDateTime).getTime() : obs.issued ? new Date(obs.issued).getTime() : 0;
3773
+ return getObsDate(b) - getObsDate(a);
3343
3774
  });
3344
- if (activeConditions.length > 0) {
3345
- html += `<h3>Conditions</h3>
3346
- <table>
3347
- <thead>
3348
- <tr>
3349
- <th>Problem</th>
3350
- <th>Onset Date</th>
3351
- <th>Recorded Date</th>
3352
- </tr>
3353
- </thead>
3354
- <tbody>`;
3355
- const addedConditionCodes = /* @__PURE__ */ new Set();
3356
- for (const cond of activeConditions) {
3357
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3358
- if (!addedConditionCodes.has(conditionCode)) {
3359
- addedConditionCodes.add(conditionCode);
3360
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3361
- <td class="Name">${conditionCode}</td>
3362
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3363
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3364
- </tr>`;
3365
- }
3775
+ let clinicalImpressions = resources.filter((r) => r.resourceType === "ClinicalImpression").filter((r) => r.status === "completed");
3776
+ clinicalImpressions = clinicalImpressions.sort((a, b) => {
3777
+ 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;
3778
+ return getImpressionDate(b) - getImpressionDate(a);
3779
+ });
3780
+ if (functionalObservations.length > 0) {
3781
+ html += `<table><thead><tr><th>Observation</th><th>Value</th><th>Date</th><th>Interpretation</th><th>Comments</th></tr></thead><tbody>`;
3782
+ for (const obs of functionalObservations) {
3783
+ const observation = obs;
3784
+ const obsName = templateUtilities.codeableConceptDisplay(observation.code);
3785
+ const value = templateUtilities.extractObservationValue(observation);
3786
+ const date = observation.effectiveDateTime ? templateUtilities.renderDate(observation.effectiveDateTime) : observation.issued ? templateUtilities.renderDate(observation.issued) : "";
3787
+ const interpretation = observation.interpretation ? templateUtilities.codeableConceptDisplay(observation.interpretation[0]) : "";
3788
+ const comments = observation.comment || observation.note?.map((n) => n.text).join("; ") || "";
3789
+ html += `<tr id="${templateUtilities.narrativeLinkId(observation)}">
3790
+ <td>${obsName}</td>
3791
+ <td>${value ?? ""}</td>
3792
+ <td>${date}</td>
3793
+ <td>${interpretation}</td>
3794
+ <td>${comments}</td>
3795
+ </tr>`;
3366
3796
  }
3367
- html += `</tbody>
3368
- </table>`;
3797
+ html += `</tbody></table>`;
3369
3798
  }
3370
3799
  if (clinicalImpressions.length > 0) {
3371
- html += `<h3>Clinical Impressions</h3>
3372
- <table>
3373
- <thead>
3374
- <tr>
3375
- <th>Date</th>
3376
- <th>Status</th>
3377
- <th>Description</th>
3378
- <th>Summary</th>
3379
- <th>Findings</th>
3380
- </tr>
3381
- </thead>
3382
- <tbody>`;
3800
+ html += `<table><thead><tr><th>Date</th><th>Status</th><th>Description</th><th>Summary</th><th>Findings</th></tr></thead><tbody>`;
3383
3801
  for (const impression of clinicalImpressions) {
3384
3802
  let formattedDate = "";
3385
3803
  if (impression.effectiveDateTime) {
3386
- formattedDate = templateUtilities.renderTime(
3387
- impression.effectiveDateTime,
3388
- timezone
3389
- );
3804
+ formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
3390
3805
  } else if (impression.effectivePeriod) {
3391
- formattedDate = templateUtilities.renderPeriod(
3392
- impression.effectivePeriod,
3393
- timezone
3394
- );
3806
+ formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
3395
3807
  } else if (impression.date) {
3396
3808
  formattedDate = templateUtilities.renderDate(impression.date);
3397
3809
  }
@@ -3399,23 +3811,24 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3399
3811
  if (impression.finding && impression.finding.length > 0) {
3400
3812
  findingsHtml = "<ul>";
3401
3813
  for (const finding of impression.finding) {
3402
- const findingText = finding.itemCodeableConcept ? templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(finding.itemCodeableConcept)) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3814
+ const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConceptDisplay(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
3403
3815
  const cause = finding.basis || "";
3404
3816
  findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
3405
3817
  }
3406
3818
  findingsHtml += "</ul>";
3407
3819
  }
3408
- html += `
3409
- <tr id="${templateUtilities.narrativeLinkId(impression)}">
3410
- <td>${formattedDate}</td>
3411
- <td>${impression.status || ""}</td>
3412
- <td>${impression.description || ""}</td>
3413
- <td>${impression.summary || ""}</td>
3414
- <td>${findingsHtml}</td>
3415
- </tr>`;
3820
+ html += `<tr id="${templateUtilities.narrativeLinkId(impression)}">
3821
+ <td>${formattedDate}</td>
3822
+ <td>${impression.status || ""}</td>
3823
+ <td>${impression.description || ""}</td>
3824
+ <td>${impression.summary || ""}</td>
3825
+ <td>${findingsHtml}</td>
3826
+ </tr>`;
3416
3827
  }
3417
- html += `</tbody>
3418
- </table>`;
3828
+ html += `</tbody></table>`;
3829
+ }
3830
+ if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
3831
+ html += `<p>No functional status information available.</p>`;
3419
3832
  }
3420
3833
  return html;
3421
3834
  }
@@ -3440,34 +3853,109 @@ var PregnancyTemplate = class _PregnancyTemplate {
3440
3853
  */
3441
3854
  static generateStaticNarrative(resources, timezone) {
3442
3855
  const templateUtilities = new TemplateUtilities(resources);
3443
- const observations = resources.map((entry) => entry) || [];
3444
- observations.sort((a, b) => {
3856
+ const pregnancyHistoryFilter = IPSSectionResourceFilters["HistoryOfPregnancySection"];
3857
+ const filteredResources = pregnancyHistoryFilter ? resources.filter(pregnancyHistoryFilter) : resources;
3858
+ const observations = filteredResources.filter((r) => r.resourceType === "Observation");
3859
+ const conditions = filteredResources.filter((r) => r.resourceType === "Condition");
3860
+ const EDD_LOINC = "11778-8";
3861
+ const pregnancyStatusCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS);
3862
+ const pregnancyOutcomeCodes = Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME);
3863
+ const pregnancyStatusObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code && pregnancyStatusCodes.includes(c.code))).sort((a, b) => {
3445
3864
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3446
3865
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3447
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3866
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3867
+ })[0];
3868
+ const eddObs = observations.filter((obs) => obs.code?.coding?.some((c) => c.code === EDD_LOINC)).sort((a, b) => {
3869
+ const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3870
+ const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3871
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3872
+ })[0];
3873
+ const historyObs = observations.filter(
3874
+ (obs) => obs.code?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code)) || obs.valueCodeableConcept?.coding?.some((c) => c.code && pregnancyOutcomeCodes.includes(c.code))
3875
+ ).sort((a, b) => {
3876
+ const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3877
+ const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3878
+ return dateB && dateA ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
3448
3879
  });
3880
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
3881
+ return `<p>No history of pregnancy found.</p>`;
3882
+ }
3449
3883
  let html = `
3450
- <table>
3451
- <thead>
3452
- <tr>
3453
- <th>Result</th>
3454
- <th>Comments</th>
3455
- <th>Date</th>
3456
- </tr>
3457
- </thead>
3458
- <tbody>`;
3459
- for (const resource of observations) {
3460
- const obs = resource;
3884
+ <table>
3885
+ <thead>
3886
+ <tr>
3887
+ <th>Result</th>
3888
+ <th>Code (System)</th>
3889
+ <th>Comments</th>
3890
+ <th>Date</th>
3891
+ <th>Source</th>
3892
+ </tr>
3893
+ </thead>
3894
+ <tbody>`;
3895
+ function renderRow({ id, result, comments, date, codeSystem, owner }) {
3461
3896
  html += `
3462
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
3463
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
3464
- <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3465
- <td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
3466
- </tr>`;
3897
+ <tr id="${id}">
3898
+ <td class="Result">${result}</td>
3899
+ <td class="CodeSystem">${codeSystem}</td>
3900
+ <td class="Comments">${comments}</td>
3901
+ <td class="Date">${date}</td>
3902
+ <td class="Source">${owner}</td>
3903
+ </tr>`;
3904
+ }
3905
+ const rowResources = [];
3906
+ if (pregnancyStatusObs) {
3907
+ const date = pregnancyStatusObs.effectiveDateTime || pregnancyStatusObs.effectivePeriod?.start;
3908
+ rowResources.push({ resource: pregnancyStatusObs, date, type: "status" });
3909
+ }
3910
+ if (eddObs) {
3911
+ const date = eddObs.effectiveDateTime || eddObs.effectivePeriod?.start;
3912
+ rowResources.push({ resource: eddObs, date, type: "edd" });
3913
+ }
3914
+ for (const obs of historyObs) {
3915
+ const date = obs.effectiveDateTime || obs.effectivePeriod?.start;
3916
+ rowResources.push({ resource: obs, date, type: "history" });
3917
+ }
3918
+ for (const cond of conditions) {
3919
+ const condition = cond;
3920
+ const date = condition.onsetDateTime || condition.onsetPeriod?.start;
3921
+ rowResources.push({ resource: condition, date, type: "condition" });
3922
+ }
3923
+ rowResources.sort((a, b) => {
3924
+ if (!a.date && !b.date) return 0;
3925
+ if (!a.date) return 1;
3926
+ if (!b.date) return -1;
3927
+ return new Date(b.date).getTime() - new Date(a.date).getTime();
3928
+ });
3929
+ for (const { resource, date, type } of rowResources) {
3930
+ let result = "", comments = "", dateStr = "", codeSystem = "";
3931
+ const id = templateUtilities.narrativeLinkId(resource);
3932
+ if (type === "status") {
3933
+ result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
3934
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3935
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3936
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3937
+ } else if (type === "edd") {
3938
+ result = "Estimated Delivery Date: " + templateUtilities.renderTextAsHtml(templateUtilities.extractObservationSummaryValue(resource, timezone));
3939
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3940
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3941
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3942
+ } else if (type === "history") {
3943
+ result = templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(resource));
3944
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3945
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3946
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3947
+ } else if (type === "condition") {
3948
+ result = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(resource.code));
3949
+ comments = templateUtilities.renderNotes(resource.note, timezone);
3950
+ dateStr = date ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(date, timezone)) : "";
3951
+ codeSystem = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptCoding(resource.code));
3952
+ }
3953
+ const owner = templateUtilities.getOwnerTag(resource);
3954
+ renderRow({ id, result, comments, date: dateStr, codeSystem, owner });
3467
3955
  }
3468
3956
  html += `
3469
- </tbody>
3470
- </table>`;
3957
+ </tbody>
3958
+ </table>`;
3471
3959
  return html;
3472
3960
  }
3473
3961
  };
@@ -3512,7 +4000,7 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3512
4000
  const consent = resourceItem;
3513
4001
  html += `
3514
4002
  <tr id="${templateUtilities.narrativeLinkId(consent)}">
3515
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
4003
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display"))}</td>
3516
4004
  <td>${consent.status || ""}</td>
3517
4005
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
3518
4006
  <td>${consent.dateTime || ""}</td>
@@ -3545,7 +4033,7 @@ var TypeScriptTemplateMapper = class {
3545
4033
  resources,
3546
4034
  timezone,
3547
4035
  now
3548
- ) : templateClass.generateNarrative(resources, timezone);
4036
+ ) : templateClass.generateNarrative(resources, timezone, now);
3549
4037
  }
3550
4038
  };
3551
4039
  // Map of section types to their template classes
@@ -3824,6 +4312,12 @@ var ComprehensiveIPSCompositionBuilder = class {
3824
4312
  if (sectionType === "Patient" /* PATIENT */) {
3825
4313
  continue;
3826
4314
  }
4315
+ const summaryIPSCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryIPSCompositionFilterForSection(sectionType) : void 0;
4316
+ const sectionIPSSummary = summaryIPSCompositionFilter ? resources.filter((resource) => summaryIPSCompositionFilter(resource)) : [];
4317
+ if (sectionIPSSummary.length > 0) {
4318
+ await this.makeSectionFromSummaryAsync(sectionType, sectionIPSSummary, resources, timezone);
4319
+ continue;
4320
+ }
3827
4321
  const summaryCompositionFilter = useSummaryCompositions ? IPSSectionResourceHelper.getSummaryCompositionFilterForSection(sectionType) : void 0;
3828
4322
  const sectionSummary = summaryCompositionFilter ? resources.filter((resource) => summaryCompositionFilter(resource)) : [];
3829
4323
  if (sectionSummary.length > 0) {