@imranq2/fhirpatientsummary 1.0.29 → 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 +749 -267
  2. package/dist/index.js +749 -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,39 @@ var IPSSectionResourceFilters = {
104
134
  // Only include completed immunizations
105
135
  ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Immunization" && resource.status === "completed" || resource.resourceType === "Organization",
106
136
  // Only include vital sign Observations (category.coding contains 'vital-signs')
107
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => c.code === "vital-signs")),
137
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, "vital-signs", c.system))),
108
138
  // Includes DeviceUseStatement. Device is needed for linked device details
109
139
  ["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
110
- // Only include finalized diagnostic reports
111
- ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
140
+ // Only include finalized diagnostic reports and relevant observations
141
+ ["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "DiagnosticReport" && resource.status === "final" || resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => codingMatches(c, RESULT_SUMMARY_OBSERVATION_CATEGORIES, c.system))),
112
142
  // Only include completed procedures
113
143
  ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
114
144
  // Only include social history Observations
115
- ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && resource.code?.coding?.some((c) => Object.keys(SOCIAL_HISTORY_LOINC_CODES).includes(c.code)),
116
- // Only include pregnancy history Observations
117
- ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (resource.code?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS).includes(c.code)) || resource.valueCodeableConcept?.coding?.some((c) => Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME).includes(c.code))),
118
- // Only include Conditions or completed ClinicalImpressions
119
- ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
145
+ ["SocialHistorySection" /* SOCIAL_HISTORY */]: (resource) => resource.resourceType === "Observation" && codeableConceptMatches(resource.code, Object.keys(SOCIAL_HISTORY_LOINC_CODES), "http://loinc.org"),
146
+ // Only include pregnancy history Observations or relevant Conditions
147
+ ["HistoryOfPregnancySection" /* PREGNANCY_HISTORY */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.valueCodeableConcept, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct") || codingMatches(resource.valueCodeableConcept?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")) || resource.resourceType === "Condition" && (codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_STATUS), "http://loinc.org") || codeableConceptMatches(resource.code, Object.keys(PREGNANCY_LOINC_CODES.PREGNANCY_OUTCOME), "http://loinc.org") || codingMatches(resource.code?.coding?.[0], PREGNANCY_SNOMED_CODES, "http://snomed.info/sct")),
148
+ // Only include Observations with LOINC 47420-5, category 'functional-status', or category display containing 'functional', and completed ClinicalImpressions
149
+ ["FunctionalStatusSection" /* FUNCTIONAL_STATUS */]: (resource) => resource.resourceType === "Observation" && (codeableConceptMatches(resource.code, "47420-5", "http://loinc.org") || resource.category?.some(
150
+ (cat) => cat.coding?.some(
151
+ (c) => c.code === "functional-status" && c.system === "http://terminology.hl7.org/CodeSystem/observation-category" || typeof c.display === "string" && c.display.toLowerCase().includes("functional")
152
+ )
153
+ )) || resource.resourceType === "ClinicalImpression" && resource.status === "completed",
120
154
  // Only include resolved medical history Conditions
121
155
  ["HistoryOfPastIllnessSection" /* MEDICAL_HISTORY */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => ["inactive", "resolved"].includes(c.code)),
122
156
  // Only include active care plans
123
157
  ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "CarePlan" && resource.status === "active",
124
158
  // Only include active advance directives (Consent resources)
125
- ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
159
+ // TODO: disable this until we right logic to get these
160
+ ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: () => false
126
161
  };
127
162
  var IPSSectionSummaryCompositionFilter = {
128
- ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
129
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
130
- ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
131
- ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
132
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
163
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "allergy_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
164
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "vital_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
165
+ ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "careplan_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
166
+ ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "immunization_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
167
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "medication_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM)),
133
168
  // [IPSSections.DIAGNOSTIC_REPORTS]: (resource) => resource.resourceType === 'Composition' && resource.type?.coding?.some((c: any) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && ["lab_summary_document", "diagnosticreportlab_summary_document"].includes(c.code)),
134
- ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
169
+ ["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => codingMatches(c, "procedure_summary_document", IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM))
135
170
  };
136
171
  var IPSSectionSummaryIPSCompositionFilter = {
137
172
  ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "ips_vital_summary_document")
@@ -152,9 +187,46 @@ var IPSSectionResourceHelper = class {
152
187
  return IPSSectionSummaryIPSCompositionFilter[section];
153
188
  }
154
189
  };
190
+ function codingMatches(coding, code, system) {
191
+ if (!coding || !coding.system) return false;
192
+ if (Array.isArray(code)) {
193
+ return coding.system === system && code.includes(coding.code ?? "");
194
+ }
195
+ return coding.system === system && coding.code === code;
196
+ }
197
+ function codeableConceptMatches(codeableConcept, code, system) {
198
+ if (!codeableConcept || !Array.isArray(codeableConcept.coding)) return false;
199
+ return codeableConcept.coding.some((coding) => codingMatches(coding, code, system));
200
+ }
155
201
 
156
202
  // src/narratives/templates/typescript/TemplateUtilities.ts
157
203
  import { DateTime } from "luxon";
204
+
205
+ // src/structures/codingSystemDisplayNames.ts
206
+ var CODING_SYSTEM_DISPLAY_NAMES = {
207
+ "http://snomed.info/sct": "SNOMED CT",
208
+ "http://loinc.org": "LOINC",
209
+ "http://hl7.org/fhir/sid/icd-10": "ICD-10",
210
+ "http://hl7.org/fhir/sid/icd-10-cm": "ICD-10-CM",
211
+ "http://hl7.org/fhir/sid/icd-9": "ICD-9",
212
+ "http://hl7.org/fhir/sid/cvx": "CVX",
213
+ "http://www.nlm.nih.gov/research/umls/rxnorm": "RxNorm",
214
+ "http://www.ama-assn.org/go/cpt": "CPT",
215
+ "http://unitsofmeasure.org": "UCUM",
216
+ "http://e-imo.com/products/problem-it": "IMO Problem IT",
217
+ "2.16.840.1.113883.6.285": "HCPCS Level II",
218
+ "https://fhir.cerner.com/4ff3b259-e48d-4066-8b35-a6a051f2802a/codeSet/72": "Cerner Code Set 72",
219
+ "http://hl7.org/fhir/sid/ndc": "NDC",
220
+ // Added mapping for NDC
221
+ "urn:oid:2.16.840.1.113883.12.292": "CVX",
222
+ "http://terminology.hl7.org/CodeSystem/data-absent-reason": "Data Absent Reason",
223
+ // Added mapping for Data Absent Reason
224
+ "2.16.840.1.113883.6.208": "NDDF"
225
+ // Add more as needed
226
+ };
227
+ var codingSystemDisplayNames_default = CODING_SYSTEM_DISPLAY_NAMES;
228
+
229
+ // src/narratives/templates/typescript/TemplateUtilities.ts
158
230
  var TemplateUtilities = class {
159
231
  /**
160
232
  * Constructor to initialize the TemplateUtilities with a FHIR resources
@@ -164,47 +236,63 @@ var TemplateUtilities = class {
164
236
  this.resources = resources;
165
237
  }
166
238
  /**
167
- * Formats a CodeableConcept object
168
- * @param cc - The CodeableConcept object
169
- * @param field - Optional specific field to return
170
- * @returns Formatted string representation
239
+ * Returns the preferred coding from a list of codings.
240
+ * If a coding has an extension with url 'https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence' and valueCode 'preferred', returns that coding.
241
+ * Otherwise, returns the first coding if it exists, else null.
242
+ * @param codings Array of coding objects
243
+ * @returns The preferred coding object or null
171
244
  */
172
- codeableConcept(cc, field) {
173
- if (!cc) {
174
- return "";
245
+ getPreferredCoding(codings) {
246
+ if (!Array.isArray(codings) || codings.length === 0) return null;
247
+ for (const coding of codings) {
248
+ if (Array.isArray(coding.extension)) {
249
+ for (const ext of coding.extension) {
250
+ if (ext.url === "https://fhir.icanbwell.com/4_0_0/StructureDefinition/intelligence" && ext.valueCode === "preferred") {
251
+ return coding;
252
+ }
253
+ }
254
+ }
175
255
  }
256
+ return codings[0] || null;
257
+ }
258
+ /**
259
+ * Returns the display value from a CodeableConcept
260
+ * @param cc - The CodeableConcept object
261
+ * @param field - Optional specific field to extract (e.g., 'text', 'display', 'code')
262
+ * @returns Display string or empty string
263
+ */
264
+ codeableConceptDisplay(cc, field) {
265
+ if (!cc) return "";
176
266
  if (field) {
177
267
  if (cc[field]) {
178
268
  return cc[field];
179
- } else if (cc.coding && cc.coding[0] && cc.coding[0][field]) {
180
- return cc.coding[0][field];
269
+ } else if (cc.coding && cc.coding.length > 0) {
270
+ const preferredCoding = this.getPreferredCoding(cc.coding);
271
+ if (preferredCoding && preferredCoding[field]) {
272
+ return preferredCoding[field];
273
+ }
181
274
  }
182
275
  }
183
- if (cc.text) {
184
- return cc.text;
185
- } else if (cc.coding && cc.coding[0]) {
186
- if (cc.coding[0].display) {
187
- return cc.coding[0].display;
188
- } else if (cc.coding[0].code) {
189
- return cc.coding[0].code;
190
- }
276
+ if (cc.text) return cc.text;
277
+ if (cc.coding && cc.coding.length > 0) {
278
+ const preferredCoding = this.getPreferredCoding(cc.coding);
279
+ if (preferredCoding && preferredCoding.display) return preferredCoding.display;
191
280
  }
192
281
  return "";
193
282
  }
194
- resolveReference(ref) {
195
- if (!ref || !this.resources) {
196
- return null;
197
- }
198
- const referenceParts = ref.reference?.split("/");
199
- if (!referenceParts || referenceParts.length !== 2) {
200
- return null;
201
- }
202
- const referenceResourceType = referenceParts[0];
203
- const referenceResourceId = referenceParts[1];
204
- const resource = this.resources.find((entry) => {
205
- return entry.resourceType === referenceResourceType && entry.id === referenceResourceId;
206
- });
207
- return resource ? resource : null;
283
+ /**
284
+ * Returns the code and system from a CodeableConcept
285
+ * @param cc - The CodeableConcept object
286
+ * @returns Object with code and system, or empty strings if not present
287
+ */
288
+ codeableConceptCoding(cc) {
289
+ if (!cc || !cc.coding || !cc.coding.length) return "";
290
+ const preferredCoding = this.getPreferredCoding(cc.coding);
291
+ if (!preferredCoding) return "";
292
+ const code = preferredCoding.code || "";
293
+ const system = preferredCoding.system || "";
294
+ const systemDisplay = codingSystemDisplayNames_default[system] || system;
295
+ return code ? `${code} (${systemDisplay})` : "";
208
296
  }
209
297
  /**
210
298
  * Renders a Device reference
@@ -252,7 +340,7 @@ var TemplateUtilities = class {
252
340
  return "";
253
341
  }
254
342
  if (medicationType.medicationCodeableConcept) {
255
- return this.codeableConcept(medicationType.medicationCodeableConcept);
343
+ return this.codeableConceptDisplay(medicationType.medicationCodeableConcept);
256
344
  } else if (medicationType.medicationReference) {
257
345
  return this.renderMedicationRef(medicationType.medicationReference);
258
346
  }
@@ -277,7 +365,7 @@ var TemplateUtilities = class {
277
365
  */
278
366
  renderMedicationCode(medication) {
279
367
  if (medication && medication.code) {
280
- return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
368
+ return this.renderTextAsHtml(this.codeableConceptDisplay(medication.code));
281
369
  }
282
370
  return "";
283
371
  }
@@ -446,7 +534,7 @@ var TemplateUtilities = class {
446
534
  */
447
535
  firstFromCodeableConceptList(list) {
448
536
  if (list && Array.isArray(list) && list[0]) {
449
- return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
537
+ return this.renderTextAsHtml(this.codeableConceptDisplay(list[0]));
450
538
  }
451
539
  return "";
452
540
  }
@@ -786,16 +874,28 @@ var TemplateUtilities = class {
786
874
  return this.renderMedicationCode(medicationSource);
787
875
  }
788
876
  if (typeof medicationSource === "object" && ("coding" in medicationSource || "text" in medicationSource)) {
789
- return this.codeableConcept(medicationSource);
877
+ return this.codeableConceptDisplay(medicationSource);
790
878
  }
791
879
  if (typeof medicationSource === "object" && "reference" in medicationSource) {
792
880
  const medication = this.resolveReference(medicationSource);
793
881
  if (medication && medication.code) {
794
- return this.codeableConcept(medication.code);
882
+ return this.codeableConceptDisplay(medication.code);
795
883
  }
796
884
  }
797
885
  return "";
798
886
  }
887
+ /**
888
+ * Returns the owner tag from the resource meta.security array.
889
+ * @param resource - FHIR resource with meta tag
890
+ * @returns The owner code if found, otherwise undefined
891
+ */
892
+ getOwnerTag(resource) {
893
+ if (!resource?.meta?.security) return "";
894
+ const ownerEntry = resource.meta.security.find(
895
+ (sec) => sec.system === "https://www.icanbwell.com/owner" && !!sec.code
896
+ );
897
+ return ownerEntry?.code;
898
+ }
799
899
  /**
800
900
  * Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
801
901
  * This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
@@ -959,6 +1059,21 @@ var TemplateUtilities = class {
959
1059
  const denominatorUnit = valueRatio.denominator.unit ? ` ${valueRatio.denominator.unit}` : "";
960
1060
  return `${numerator}${numeratorUnit} / ${denominator}${denominatorUnit}`;
961
1061
  }
1062
+ /**
1063
+ * Finds the resource that matches the reference
1064
+ * @param ref - Reference to a resource
1065
+ * @returns The resource or null
1066
+ */
1067
+ resolveReference(ref) {
1068
+ if (!ref || !this.resources) {
1069
+ return null;
1070
+ }
1071
+ const refId = ref.reference?.split("/")[1];
1072
+ const refType = ref.reference?.split("/")[0];
1073
+ return this.resources.find(
1074
+ (resource) => resource.resourceType === refType && resource.id === refId
1075
+ ) || null;
1076
+ }
962
1077
  };
963
1078
 
964
1079
  // src/constants.ts
@@ -1587,7 +1702,7 @@ var PatientTemplate = class _PatientTemplate {
1587
1702
  const uniqueLanguages = /* @__PURE__ */ new Set();
1588
1703
  const preferredLanguages = /* @__PURE__ */ new Set();
1589
1704
  patient.communication.forEach((comm) => {
1590
- const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
1705
+ const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(comm.language));
1591
1706
  if (language) {
1592
1707
  if (comm.preferred) {
1593
1708
  preferredLanguages.add(language);
@@ -1634,14 +1749,18 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1634
1749
  <thead>
1635
1750
  <tr>
1636
1751
  <th>Allergen</th>
1752
+ <th>Code (System)</th>
1637
1753
  <th>Criticality</th>
1638
1754
  <th>Recorded Date</th>
1755
+ <th>Source</th>
1639
1756
  </tr>
1640
1757
  </thead>
1641
1758
  <tbody>`;
1642
1759
  for (const resourceItem of resources) {
1643
1760
  for (const rowData of resourceItem.section ?? []) {
1761
+ const sectionCodeableConcept = rowData.code;
1644
1762
  const data = {};
1763
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
1645
1764
  for (const columnData of rowData.section ?? []) {
1646
1765
  switch (columnData.title) {
1647
1766
  case "Allergen Name":
@@ -1653,6 +1772,9 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1653
1772
  case "Recorded Date":
1654
1773
  data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1655
1774
  break;
1775
+ case "Source":
1776
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1777
+ break;
1656
1778
  default:
1657
1779
  break;
1658
1780
  }
@@ -1660,9 +1782,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1660
1782
  isSummaryCreated = true;
1661
1783
  html += `
1662
1784
  <tr>
1663
- <td>${data["allergen"] ?? "-"}</td>
1664
- <td>${data["criticality"] ?? "-"}</td>
1665
- <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>
1666
1790
  </tr>`;
1667
1791
  }
1668
1792
  }
@@ -1710,10 +1834,12 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1710
1834
  <tr>
1711
1835
  <th>Allergen</th>
1712
1836
  <th>Status</th>
1837
+ <th>Code (System)</th>
1713
1838
  <th>Category</th>
1714
1839
  <th>Reaction</th>
1715
1840
  <th>Onset Date</th>
1716
1841
  <th>Comments</th>
1842
+ <th>Source</th>
1717
1843
  </tr>
1718
1844
  </thead>
1719
1845
  <tbody>`;
@@ -1737,11 +1863,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1737
1863
  <tr>
1738
1864
  <th>Allergen</th>
1739
1865
  <th>Status</th>
1866
+ <th>Code (System)</th>
1740
1867
  <th>Category</th>
1741
1868
  <th>Reaction</th>
1742
1869
  <th>Onset Date</th>
1743
1870
  <th>Comments</th>
1744
1871
  <th>Resolved Date</th>
1872
+ <th>Source</th>
1745
1873
  </tr>
1746
1874
  </thead>
1747
1875
  <tbody>`;
@@ -1772,14 +1900,16 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
1772
1900
  for (const allergy of allergies) {
1773
1901
  html += `
1774
1902
  <tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
1775
- <td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
1776
- <td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
1777
- <td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
1778
- <td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
1779
- <td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
1780
- <td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
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>`;
1781
1911
  if (includeResolved) {
1782
- let endDate = "-";
1912
+ let endDate = "";
1783
1913
  if (allergy.extension && Array.isArray(allergy.extension)) {
1784
1914
  const endDateExt = allergy.extension.find(
1785
1915
  (ext) => ext.url === "http://hl7.org/fhir/StructureDefinition/allergyintolerance-resolutionDate"
@@ -1803,10 +1933,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1803
1933
  * Generate HTML narrative for Medication resources
1804
1934
  * @param resources - FHIR Medication resources
1805
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())
1806
1937
  * @returns HTML string for rendering
1807
1938
  */
1808
- generateNarrative(resources, timezone) {
1809
- return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
1939
+ generateNarrative(resources, timezone, now) {
1940
+ return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone, now);
1810
1941
  }
1811
1942
  /**
1812
1943
  * Generate HTML narrative for Medication resources using summary
@@ -1819,23 +1950,27 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1819
1950
  const templateUtilities = new TemplateUtilities(resources);
1820
1951
  let isSummaryCreated = false;
1821
1952
  const currentDate = now || /* @__PURE__ */ new Date();
1822
- const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
1953
+ const twoYearsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 24, currentDate.getDate());
1823
1954
  let html = `
1824
1955
  <div>
1825
1956
  <table>
1826
1957
  <thead>
1827
1958
  <tr>
1828
1959
  <th>Medication</th>
1960
+ <th>Code (System)</th>
1829
1961
  <th>Status</th>
1830
1962
  <th>Sig</th>
1831
1963
  <th>Days of Supply</th>
1832
1964
  <th>Refills</th>
1833
1965
  <th>Start Date</th>
1966
+ <th>Source</th>
1834
1967
  </tr>
1835
1968
  </thead>
1836
1969
  <tbody>`;
1970
+ let skippedMedications = 0;
1837
1971
  for (const resourceItem of resources) {
1838
1972
  for (const rowData of resourceItem.section ?? []) {
1973
+ const sectionCodeableConcept = rowData.code;
1839
1974
  const data = {};
1840
1975
  for (const columnData of rowData.section ?? []) {
1841
1976
  switch (columnData.title) {
@@ -1860,6 +1995,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1860
1995
  case "Authored On Date":
1861
1996
  data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
1862
1997
  break;
1998
+ case "Source":
1999
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2000
+ break;
1863
2001
  default:
1864
2002
  break;
1865
2003
  }
@@ -1871,16 +2009,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1871
2009
  startDateObj = void 0;
1872
2010
  }
1873
2011
  }
1874
- 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) {
1875
2016
  isSummaryCreated = true;
1876
2017
  html += `
1877
2018
  <tr>
1878
2019
  <td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
2020
+ <td>${templateUtilities.codeableConceptCoding(sectionCodeableConcept)}</td>
1879
2021
  <td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
1880
2022
  <td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
1881
2023
  <td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
1882
2024
  <td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
1883
2025
  <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
2026
+ <td>${templateUtilities.renderTextAsHtml(data["source"])}</td>
1884
2027
  </tr>`;
1885
2028
  }
1886
2029
  }
@@ -1889,7 +2032,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1889
2032
  </tbody>
1890
2033
  </table>
1891
2034
  </div>`;
1892
- 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;
1893
2043
  }
1894
2044
  /**
1895
2045
  * Safely parse a date string and return a valid Date object or null
@@ -1907,21 +2057,42 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1907
2057
  * Internal static implementation that actually generates the narrative
1908
2058
  * @param resources - FHIR Medication resources
1909
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())
1910
2061
  * @returns HTML string for rendering
1911
2062
  */
1912
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1913
- static generateStaticNarrative(resources, timezone) {
2063
+ static generateStaticNarrative(resources, timezone, now) {
1914
2064
  const templateUtilities = new TemplateUtilities(resources);
1915
2065
  let html = "";
1916
2066
  const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
1917
2067
  const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
1918
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 = [];
1919
2074
  medicationRequests.forEach((mr) => {
1920
- allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
2075
+ allMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1921
2076
  });
1922
2077
  medicationStatements.forEach((ms) => {
1923
- allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
2078
+ allMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1924
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
+ }
1925
2096
  const sortMedications = (medications) => {
1926
2097
  medications.sort((a, b) => {
1927
2098
  let dateStringA;
@@ -1952,7 +2123,14 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1952
2123
  sortMedications(allActiveMedications);
1953
2124
  html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
1954
2125
  }
1955
- 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 "";
1956
2134
  }
1957
2135
  /**
1958
2136
  * Extract MedicationRequest resources
@@ -1997,10 +2175,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1997
2175
  <tr>
1998
2176
  <th>Type</th>
1999
2177
  <th>Medication</th>
2178
+ <th>Code (System)</th>
2000
2179
  <th>Sig</th>
2001
2180
  <th>Dispense Quantity</th>
2002
2181
  <th>Refills</th>
2003
2182
  <th>Start Date</th>
2183
+ <th>Source</th>
2004
2184
  </tr>
2005
2185
  </thead>
2006
2186
  <tbody>`;
@@ -2009,27 +2189,31 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2009
2189
  let type;
2010
2190
  let medicationName;
2011
2191
  let sig;
2012
- let dispenseQuantity = "-";
2013
- let refills = "-";
2014
- let startDate = "-";
2192
+ let dispenseQuantity = "";
2193
+ let refills = "";
2194
+ let startDate = "";
2195
+ let codeSystemDisplay = "";
2015
2196
  if (medication.type === "request") {
2016
2197
  const mr = medication.resource;
2017
2198
  type = "Request";
2018
2199
  medicationName = templateUtilities.getMedicationName(
2019
2200
  mr.medicationReference || mr.medicationCodeableConcept
2020
2201
  );
2021
- sig = templateUtilities.concat(mr.dosageInstruction, "text") || "-";
2202
+ sig = templateUtilities.concat(mr.dosageInstruction, "text") || "";
2022
2203
  if (mr.dispenseRequest?.quantity) {
2023
2204
  const quantity = mr.dispenseRequest.quantity;
2024
2205
  if (quantity.value) {
2025
2206
  dispenseQuantity = `${quantity.value} ${quantity.unit || quantity.code || ""}`.trim();
2026
2207
  }
2027
2208
  }
2028
- refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
2209
+ refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "";
2029
2210
  if (mr.dispenseRequest?.validityPeriod) {
2030
- startDate = mr.dispenseRequest.validityPeriod.start || "-";
2211
+ startDate = mr.dispenseRequest.validityPeriod.start || "";
2031
2212
  } else {
2032
- 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);
2033
2217
  }
2034
2218
  } else {
2035
2219
  const ms = medication.resource;
@@ -2037,21 +2221,26 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
2037
2221
  medicationName = templateUtilities.getMedicationName(
2038
2222
  ms.medicationReference || ms.medicationCodeableConcept
2039
2223
  );
2040
- sig = templateUtilities.concat(ms.dosage, "text") || "-";
2224
+ sig = templateUtilities.concat(ms.dosage, "text") || "";
2041
2225
  if (ms.effectiveDateTime) {
2042
2226
  startDate = ms.effectiveDateTime;
2043
2227
  } else if (ms.effectivePeriod) {
2044
- 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);
2045
2232
  }
2046
2233
  }
2047
2234
  html += `
2048
2235
  <tr${narrativeLinkId ? ` id="${narrativeLinkId}"` : ""}>
2049
2236
  <td>${type}</td>
2050
2237
  <td>${medicationName}<ul></ul></td>
2238
+ <td>${codeSystemDisplay}</td>
2051
2239
  <td>${sig}</td>
2052
2240
  <td>${dispenseQuantity}</td>
2053
2241
  <td>${refills}</td>
2054
2242
  <td>${startDate}</td>
2243
+ <td>${templateUtilities.getOwnerTag(medication.resource)}</td>
2055
2244
  </tr>`;
2056
2245
  }
2057
2246
  html += `
@@ -2092,14 +2281,18 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2092
2281
  <thead>
2093
2282
  <tr>
2094
2283
  <th>Immunization</th>
2284
+ <th>Code (System)</th>
2095
2285
  <th>Status</th>
2096
2286
  <th>Date</th>
2287
+ <th>Source</th>
2097
2288
  </tr>
2098
2289
  </thead>
2099
2290
  <tbody>`;
2100
2291
  for (const resourceItem of resources) {
2101
2292
  for (const rowData of resourceItem.section ?? []) {
2293
+ const sectionCodeableConcept = rowData.code;
2102
2294
  const data = {};
2295
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2103
2296
  for (const columnData of rowData.section ?? []) {
2104
2297
  switch (columnData.title) {
2105
2298
  case "Immunization Name":
@@ -2111,6 +2304,9 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2111
2304
  case "occurrenceDateTime":
2112
2305
  data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2113
2306
  break;
2307
+ case "Source":
2308
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2309
+ break;
2114
2310
  default:
2115
2311
  break;
2116
2312
  }
@@ -2119,9 +2315,11 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2119
2315
  isSummaryCreated = true;
2120
2316
  html += `
2121
2317
  <tr>
2122
- <td>${data["immunization"] ?? "-"}</td>
2123
- <td>${data["status"] ?? "-"}</td>
2124
- <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>
2125
2323
  </tr>`;
2126
2324
  }
2127
2325
  }
@@ -2145,12 +2343,14 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2145
2343
  <thead>
2146
2344
  <tr>
2147
2345
  <th>Immunization</th>
2346
+ <th>Code (System)</th>
2148
2347
  <th>Status</th>
2149
2348
  <th>Dose Number</th>
2150
2349
  <th>Manufacturer</th>
2151
2350
  <th>Lot Number</th>
2152
2351
  <th>Comments</th>
2153
2352
  <th>Date</th>
2353
+ <th>Source</th>
2154
2354
  </tr>
2155
2355
  </thead>
2156
2356
  <tbody>`;
@@ -2160,13 +2360,15 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
2160
2360
  const imm = resourceItem;
2161
2361
  html += `
2162
2362
  <tr id="${templateUtilities.narrativeLinkId(imm)}">
2163
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
2363
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(imm.vaccineCode))}</td>
2364
+ <td>${templateUtilities.codeableConceptCoding(imm.vaccineCode)}</td>
2164
2365
  <td>${imm.status || ""}</td>
2165
2366
  <td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
2166
2367
  <td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
2167
2368
  <td>${imm.lotNumber || ""}</td>
2168
2369
  <td>${templateUtilities.renderNotes(imm.note, timezone)}</td>
2169
2370
  <td>${templateUtilities.renderTime(imm.occurrenceDateTime, timezone)}</td>
2371
+ <td>${templateUtilities.getOwnerTag(imm)}</td>
2170
2372
  </tr>`;
2171
2373
  }
2172
2374
  }
@@ -2200,8 +2402,11 @@ var ProblemListTemplate = class _ProblemListTemplate {
2200
2402
  let html = ``;
2201
2403
  const activeConditions = resources.map((entry) => entry) || [];
2202
2404
  activeConditions.sort((a, b) => {
2203
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
2204
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
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();
2205
2410
  return dateB - dateA;
2206
2411
  });
2207
2412
  html += `
@@ -2209,22 +2414,28 @@ var ProblemListTemplate = class _ProblemListTemplate {
2209
2414
  <thead>
2210
2415
  <tr>
2211
2416
  <th>Problem</th>
2417
+ <th>Code (System)</th>
2212
2418
  <th>Onset Date</th>
2213
2419
  <th>Recorded Date</th>
2420
+ <th>Source</th>
2214
2421
  </tr>
2215
2422
  </thead>
2216
2423
  <tbody>`;
2217
- const addedConditionCodes = /* @__PURE__ */ new Set();
2424
+ const seenCodeAndSystems = /* @__PURE__ */ new Set();
2218
2425
  for (const cond of activeConditions) {
2219
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
2220
- if (!addedConditionCodes.has(conditionCode)) {
2221
- addedConditionCodes.add(conditionCode);
2222
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
2223
- <td class="Name">${conditionCode}</td>
2224
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2225
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2226
- </tr>`;
2426
+ const conditionDisplay = templateUtilities.codeableConceptDisplay(cond.code);
2427
+ const codeAndSystem = templateUtilities.codeableConceptCoding(cond.code);
2428
+ if (codeAndSystem && seenCodeAndSystems.has(codeAndSystem)) {
2429
+ continue;
2227
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>`;
2228
2439
  }
2229
2440
  html += `</tbody>
2230
2441
  </table>`;
@@ -2257,16 +2468,19 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2257
2468
  <table>
2258
2469
  <thead>
2259
2470
  <tr>
2260
- <th>Vital Name</th>
2471
+ <th>Name</th>
2472
+ <th>Code (System)</th>
2261
2473
  <th>Result</th>
2262
- <th>Reference Range</th>
2263
2474
  <th>Date</th>
2475
+ <th>Source</th>
2264
2476
  </tr>
2265
2477
  </thead>
2266
2478
  <tbody>`;
2267
2479
  for (const resourceItem of resources) {
2268
2480
  for (const rowData of resourceItem.section ?? []) {
2481
+ const sectionCodeableConcept = rowData.code;
2269
2482
  const data = {};
2483
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2270
2484
  for (const columnData of rowData.section ?? []) {
2271
2485
  const columnTitle = columnData.title;
2272
2486
  if (columnTitle) {
@@ -2294,10 +2508,11 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2294
2508
  isSummaryCreated = true;
2295
2509
  html += `
2296
2510
  <tr>
2297
- <td>${data["Vital Name"] ?? "-"}</td>
2298
- <td>${templateUtilities.extractObservationSummaryValue(data, timezone) ?? "-"}</td>
2299
- <td>${templateUtilities.extractObservationSummaryReferenceRange(data) ?? "-"}</td>
2300
- <td>${templateUtilities.extractObservationSummaryEffectiveTime(data, timezone) ?? "-"}</td>
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>
2301
2516
  </tr>`;
2302
2517
  }
2303
2518
  }
@@ -2325,26 +2540,30 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
2325
2540
  <table>
2326
2541
  <thead>
2327
2542
  <tr>
2328
- <th>Vital Name</th>
2543
+ <th>Name</th>
2544
+ <th>Code (System)</th>
2329
2545
  <th>Result</th>
2330
2546
  <th>Unit</th>
2331
2547
  <th>Interpretation</th>
2332
2548
  <th>Component(s)</th>
2333
2549
  <th>Comments</th>
2334
2550
  <th>Date</th>
2551
+ <th>Source</th>
2335
2552
  </tr>
2336
2553
  </thead>
2337
2554
  <tbody>`;
2338
2555
  for (const obs of observations) {
2339
2556
  html += `
2340
2557
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2341
- <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>
2342
2560
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2343
2561
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
2344
2562
  <td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
2345
2563
  <td>${templateUtilities.renderComponent(obs.component)}</td>
2346
2564
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
2347
2565
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
2566
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2348
2567
  </tr>`;
2349
2568
  }
2350
2569
  html += `
@@ -2417,10 +2636,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2417
2636
  * Generate HTML narrative for Diagnostic Results
2418
2637
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2419
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
2420
2640
  * @returns HTML string for rendering
2421
2641
  */
2422
- generateNarrative(resources, timezone) {
2423
- return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone);
2642
+ generateNarrative(resources, timezone, now) {
2643
+ return _DiagnosticResultsTemplate.generateStaticNarrative(resources, timezone, now);
2424
2644
  }
2425
2645
  /**
2426
2646
  * Helper function to format observation data fields
@@ -2633,6 +2853,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2633
2853
  break;
2634
2854
  case "valueRange.high.value":
2635
2855
  targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2856
+ targetData["valueType"] = "valueRange";
2636
2857
  break;
2637
2858
  case "valueRange.high.unit":
2638
2859
  targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2647,6 +2868,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2647
2868
  break;
2648
2869
  case "valueRatio.denominator.value":
2649
2870
  targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
2871
+ targetData["valueType"] = "valueRatio";
2650
2872
  break;
2651
2873
  case "valueRatio.denominator.unit":
2652
2874
  targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
@@ -2684,10 +2906,71 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2684
2906
  * Generate HTML narrative for Diagnostic Results & Observation resources using summary
2685
2907
  * @param resources - FHIR Composition resources
2686
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
2687
2910
  * @returns HTML string for rendering
2688
2911
  */
2689
- generateSummaryNarrative(resources, timezone) {
2912
+ generateSummaryNarrative(resources, timezone, now) {
2690
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
+ }
2691
2974
  let html = `
2692
2975
  <div>`;
2693
2976
  let observationhtml = `
@@ -2696,10 +2979,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2696
2979
  <table>
2697
2980
  <thead>
2698
2981
  <tr>
2699
- <th>Code</th>
2982
+ <th>Name</th>
2983
+ <th>Code (System)</th>
2700
2984
  <th>Result</th>
2701
2985
  <th>Reference Range</th>
2702
2986
  <th>Date</th>
2987
+ <th>Source</th>
2703
2988
  </tr>
2704
2989
  </thead>
2705
2990
  <tbody>`;
@@ -2712,6 +2997,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2712
2997
  <th>Report</th>
2713
2998
  <th>Performer</th>
2714
2999
  <th>Issued</th>
3000
+ <th>Source</th>
2715
3001
  </tr>
2716
3002
  </thead>
2717
3003
  <tbody>`;
@@ -2719,7 +3005,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2719
3005
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2720
3006
  for (const resourceItem of resources) {
2721
3007
  for (const rowData of resourceItem.section ?? []) {
3008
+ const sectionCodeableConcept = rowData.code;
2722
3009
  const data = {};
3010
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
2723
3011
  const components = [];
2724
3012
  for (const columnData of rowData.section ?? []) {
2725
3013
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
@@ -2729,7 +3017,13 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2729
3017
  for (const nestedColumn of componentSection.section ?? []) {
2730
3018
  this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
2731
3019
  }
2732
- 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) {
2733
3027
  components.push(componentData);
2734
3028
  }
2735
3029
  }
@@ -2750,6 +3044,9 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2750
3044
  case "Status":
2751
3045
  data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
2752
3046
  break;
3047
+ case "Source":
3048
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3049
+ break;
2753
3050
  default:
2754
3051
  break;
2755
3052
  }
@@ -2757,6 +3054,12 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2757
3054
  }
2758
3055
  if (resourceItem.title === "Observation|Labs Summary Grouped by Lab Code") {
2759
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
+ }
2760
3063
  if (!date && data["effectivePeriodStart"]) {
2761
3064
  date = templateUtilities.renderTime(data["effectivePeriodStart"], timezone);
2762
3065
  if (data["effectivePeriodEnd"]) {
@@ -2773,36 +3076,47 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2773
3076
  observationhtml += `
2774
3077
  <tr>
2775
3078
  <td>${componentCode}</td>
2776
- <td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
2777
- <td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
2778
- <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>
2779
3084
  </tr>`;
2780
3085
  }
2781
3086
  }
2782
3087
  } else {
2783
- const code = data["code"] ?? "";
2784
- if (code && !observationAdded.has(code)) {
2785
- observationAdded.add(code);
2786
- this.formatSummaryObservationData(data);
2787
- 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 += `
2788
3094
  <tr>
2789
- <td>${data["code"] ?? "-"}</td>
2790
- <td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
2791
- <td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
2792
- <td>${date ?? "-"}</td>
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>
2793
3101
  </tr>`;
3102
+ }
2794
3103
  }
2795
3104
  }
2796
3105
  } else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
2797
- 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) {
2798
3111
  const reportName = data["report"] ?? "";
2799
3112
  if (reportName && !diagnosticReportAdded.has(reportName)) {
2800
3113
  diagnosticReportAdded.add(reportName);
2801
3114
  diagnosticReporthtml += `
2802
3115
  <tr>
2803
- <td>${data["report"] ?? "-"}</td>
2804
- <td>${data["performer"] ?? "-"}</td>
2805
- <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>
2806
3120
  </tr>`;
2807
3121
  }
2808
3122
  }
@@ -2815,6 +3129,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2815
3129
  </tbody>
2816
3130
  </table>
2817
3131
  </div>`;
3132
+ if (skippedObservations > 0) {
3133
+ html += `
3134
+ <p><em>${skippedObservations} additional observations older than 2 years ago are present</em></p>`;
3135
+ }
2818
3136
  }
2819
3137
  if (diagnosticReportAdded.size > 0) {
2820
3138
  html += diagnosticReporthtml;
@@ -2822,6 +3140,10 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2822
3140
  </tbody>
2823
3141
  </table>
2824
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
+ }
2825
3147
  }
2826
3148
  html += `
2827
3149
  </div>`;
@@ -2831,12 +3153,32 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2831
3153
  * Internal static implementation that actually generates the narrative
2832
3154
  * @param resources - FHIR resources array containing Observation and DiagnosticReport resources
2833
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
2834
3157
  * @returns HTML string for rendering
2835
3158
  */
2836
- static generateStaticNarrative(resources, timezone) {
3159
+ static generateStaticNarrative(resources, timezone, now) {
2837
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);
2838
3164
  let html = "";
2839
- 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);
2840
3182
  if (observations.length > 0) {
2841
3183
  observations.sort((a, b) => {
2842
3184
  const dateA = this.getObservationDate(a);
@@ -2845,16 +3187,22 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2845
3187
  });
2846
3188
  this.filterObservationForLoincCodes(observations);
2847
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
+ }
2848
3194
  }
2849
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2850
- const diagnosticReports = this.getDiagnosticReports(resources);
2851
- if (diagnosticReports.length > 0) {
2852
- diagnosticReports.sort((a, b) => {
2853
- const dateA = a.issued;
2854
- const dateB = b.issued;
2855
- return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
2856
- });
2857
- html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
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>`;
2858
3206
  }
2859
3207
  }
2860
3208
  return html;
@@ -2899,15 +3247,16 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2899
3247
  return obsDate;
2900
3248
  }
2901
3249
  /**
2902
- * Get all Observation resources from the resource array
2903
- * @param resources - FHIR resources array
2904
- * @returns Array of Observation resources
2905
- */
2906
- static getObservations(resources) {
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) {
2907
3256
  return resources.filter((resourceItem) => {
2908
3257
  if (resourceItem.resourceType === "Observation") {
2909
3258
  const obsDate = this.getObservationDate(resourceItem);
2910
- if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
3259
+ if (obsDate && obsDate >= twoYearsAgo) {
2911
3260
  return true;
2912
3261
  }
2913
3262
  }
@@ -2915,12 +3264,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2915
3264
  }).map((resourceItem) => resourceItem);
2916
3265
  }
2917
3266
  /**
2918
- * Get all DiagnosticReport resources from the resource array
3267
+ * Get all DiagnosticReport resources from the resource array, filtered by twoYearsAgo
2919
3268
  * @param resources - FHIR resources array
3269
+ * @param twoYearsAgo - Date object representing the cutoff
2920
3270
  * @returns Array of DiagnosticReport resources
2921
3271
  */
2922
- static getDiagnosticReports(resources) {
2923
- 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);
2924
3282
  }
2925
3283
  /**
2926
3284
  * Render HTML table for Observation resources
@@ -2931,32 +3289,36 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2931
3289
  */
2932
3290
  static renderObservations(templateUtilities, observations, timezone) {
2933
3291
  let html = "";
2934
- if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
2935
- html += `
2936
- <h3>Observations</h3>`;
2937
- }
3292
+ html += `
3293
+ <h3>Observations</h3>`;
2938
3294
  html += `
2939
3295
  <table>
2940
3296
  <thead>
2941
3297
  <tr>
2942
- <th>Code</th>
3298
+ <th>Name</th>
3299
+ <th>Code (System)</th>
2943
3300
  <th>Result</th>
2944
3301
  <th>Reference Range</th>
2945
3302
  <th>Date</th>
3303
+ <th>Source</th>
2946
3304
  </tr>
2947
3305
  </thead>
2948
3306
  <tbody>`;
2949
3307
  const observationAdded = /* @__PURE__ */ new Set();
2950
3308
  for (const obs of observations) {
2951
- const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
2952
- if (!observationAdded.has(obsCode)) {
2953
- 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);
2954
3314
  html += `
2955
3315
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
2956
- <td>${obsCode}</td>
3316
+ <td>${obsCodeDisplay}</td>
3317
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
2957
3318
  <td>${templateUtilities.extractObservationValue(obs)}</td>
2958
3319
  <td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
2959
3320
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3321
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
2960
3322
  </tr>`;
2961
3323
  }
2962
3324
  }
@@ -2979,17 +3341,21 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2979
3341
  <thead>
2980
3342
  <tr>
2981
3343
  <th>Report</th>
3344
+ <th>Code (System)</th>
2982
3345
  <th>Category</th>
2983
3346
  <th>Result</th>
2984
3347
  <th>Issued</th>
3348
+ <th>Source</th>
2985
3349
  </tr>
2986
3350
  </thead>
2987
3351
  <tbody>`;
2988
3352
  const diagnosticReportAdded = /* @__PURE__ */ new Set();
2989
3353
  for (const report of reports) {
2990
- const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
2991
- 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)) {
2992
3357
  diagnosticReportAdded.add(reportName);
3358
+ diagnosticReportAdded.add(codeAndSystem);
2993
3359
  let resultCount = "";
2994
3360
  if (report.result && Array.isArray(report.result)) {
2995
3361
  resultCount = `${report.result.length} result${report.result.length !== 1 ? "s" : ""}`;
@@ -2997,9 +3363,11 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
2997
3363
  html += `
2998
3364
  <tr id="${templateUtilities.narrativeLinkId(report)}">
2999
3365
  <td>${reportName}</td>
3366
+ <td>${codeAndSystem}</td>
3000
3367
  <td>${templateUtilities.firstFromCodeableConceptList(report.category)}</td>
3001
3368
  <td>${resultCount}</td>
3002
3369
  <td>${report.issued ? templateUtilities.renderTime(report.issued, timezone) : ""}</td>
3370
+ <td>${templateUtilities.getOwnerTag(report)}</td>
3003
3371
  </tr>`;
3004
3372
  }
3005
3373
  }
@@ -3008,6 +3376,26 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
3008
3376
  </table>`;
3009
3377
  return html;
3010
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
+ }
3011
3399
  };
3012
3400
 
3013
3401
  // src/narratives/templates/typescript/HistoryOfProceduresTemplate.ts
@@ -3041,14 +3429,18 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3041
3429
  <thead>
3042
3430
  <tr>
3043
3431
  <th>Procedure</th>
3432
+ <th>Code (System)</th>
3044
3433
  <th>Performer</th>
3045
3434
  <th>Date</th>
3435
+ <th>Source</th>
3046
3436
  </tr>
3047
3437
  </thead>
3048
3438
  <tbody>`;
3049
3439
  for (const resourceItem of resources) {
3050
3440
  for (const rowData of resourceItem.section ?? []) {
3441
+ const sectionCodeableConcept = rowData.code;
3051
3442
  const data = {};
3443
+ data["codeSystem"] = templateUtilities.codeableConceptCoding(sectionCodeableConcept);
3052
3444
  for (const columnData of rowData.section ?? []) {
3053
3445
  switch (columnData.title) {
3054
3446
  case "Procedure Name":
@@ -3060,6 +3452,9 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3060
3452
  case "Performed Date":
3061
3453
  data["date"] = columnData.text?.div ?? "";
3062
3454
  break;
3455
+ case "Source":
3456
+ data["source"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
3457
+ break;
3063
3458
  default:
3064
3459
  break;
3065
3460
  }
@@ -3067,9 +3462,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3067
3462
  isSummaryCreated = true;
3068
3463
  html += `
3069
3464
  <tr>
3070
- <td>${data["procedure"] ?? "-"}</td>
3071
- <td>${data["performer"] ?? "-"}</td>
3072
- <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>
3073
3470
  </tr>`;
3074
3471
  }
3075
3472
  }
@@ -3092,8 +3489,10 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3092
3489
  <thead>
3093
3490
  <tr>
3094
3491
  <th>Procedure</th>
3492
+ <th>Code (System)</th>
3095
3493
  <th>Comments</th>
3096
3494
  <th>Date</th>
3495
+ <th>Source</th>
3097
3496
  </tr>
3098
3497
  </thead>
3099
3498
  <tbody>`;
@@ -3101,9 +3500,11 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
3101
3500
  const proc = resourceItem;
3102
3501
  html += `
3103
3502
  <tr id="${templateUtilities.narrativeLinkId(proc)}">
3104
- <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>
3105
3505
  <td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
3106
3506
  <td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
3507
+ <td>${templateUtilities.getOwnerTag(proc)}</td>
3107
3508
  </tr>`;
3108
3509
  }
3109
3510
  html += `
@@ -3142,22 +3543,26 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
3142
3543
  <table>
3143
3544
  <thead>
3144
3545
  <tr>
3145
- <th>Code</th>
3546
+ <th>Name</th>
3547
+ <th>Code (System)</th>
3146
3548
  <th>Result</th>
3147
3549
  <th>Unit</th>
3148
3550
  <th>Comments</th>
3149
3551
  <th>Date</th>
3552
+ <th>Source</th>
3150
3553
  </tr>
3151
3554
  </thead>
3152
3555
  <tbody>`;
3153
3556
  for (const obs of observations) {
3154
3557
  html += `
3155
3558
  <tr id="${templateUtilities.narrativeLinkId(obs)}">
3156
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
3559
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(obs.code))}</td>
3560
+ <td>${templateUtilities.codeableConceptCoding(obs.code)}</td>
3157
3561
  <td>${templateUtilities.extractObservationValue(obs)}</td>
3158
3562
  <td>${templateUtilities.extractObservationValueUnit(obs)}</td>
3159
3563
  <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3160
3564
  <td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
3565
+ <td>${templateUtilities.getOwnerTag(obs)}</td>
3161
3566
  </tr>`;
3162
3567
  }
3163
3568
  html += `
@@ -3173,14 +3578,26 @@ var PastHistoryOfIllnessTemplate = class {
3173
3578
  * Generate HTML narrative for Past History of Illnesses
3174
3579
  * @param resources - FHIR Condition resources
3175
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
3176
3582
  * @returns HTML string for rendering
3177
3583
  */
3178
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
3179
- generateNarrative(resources, timezone) {
3584
+ generateNarrative(resources, timezone, now) {
3180
3585
  const templateUtilities = new TemplateUtilities(resources);
3181
3586
  let html = ``;
3182
3587
  const resolvedConditions = resources.map((entry) => entry) || [];
3183
- 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) => {
3184
3601
  const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3185
3602
  const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3186
3603
  return dateB - dateA;
@@ -3190,27 +3607,35 @@ var PastHistoryOfIllnessTemplate = class {
3190
3607
  <thead>
3191
3608
  <tr>
3192
3609
  <th>Problem</th>
3610
+ <th>Code (System)</th>
3193
3611
  <th>Onset Date</th>
3194
3612
  <th>Recorded Date</th>
3195
3613
  <th>Resolved Date</th>
3614
+ <th>Source</th>
3196
3615
  </tr>
3197
3616
  </thead>
3198
3617
  <tbody>`;
3199
3618
  const addedConditionCodes = /* @__PURE__ */ new Set();
3200
- for (const cond of resolvedConditions) {
3201
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3619
+ for (const cond of filteredConditions) {
3620
+ const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(cond.code));
3202
3621
  if (!addedConditionCodes.has(conditionCode)) {
3203
3622
  addedConditionCodes.add(conditionCode);
3204
3623
  html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3205
3624
  <td class="Name">${conditionCode}</td>
3625
+ <td class="CodeSystem">${templateUtilities.codeableConceptCoding(cond.code)}</td>
3206
3626
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3207
3627
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3208
3628
  <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
3629
+ <td class="Source">${templateUtilities.getOwnerTag(cond)}</td>
3209
3630
  </tr>`;
3210
3631
  }
3211
3632
  }
3212
3633
  html += `</tbody>
3213
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
+ }
3214
3639
  return html;
3215
3640
  }
3216
3641
  };
@@ -3240,6 +3665,7 @@ var PlanOfCareTemplate = class {
3240
3665
  <th>Comments</th>
3241
3666
  <th>Planned Start</th>
3242
3667
  <th>Planned End</th>
3668
+ <th>Source</th>
3243
3669
  </tr>
3244
3670
  </thead>
3245
3671
  <tbody>`;
@@ -3251,6 +3677,7 @@ var PlanOfCareTemplate = class {
3251
3677
  <td>${templateUtilities.concat(cp.note, "text")}</td>
3252
3678
  <td>${cp.period?.start ? templateUtilities.renderTime(cp.period?.start, timezone) : ""}</td>
3253
3679
  <td>${cp.period?.end ? templateUtilities.renderTime(cp.period?.end, timezone) : ""}</td>
3680
+ <td>${templateUtilities.getOwnerTag(cp)}</td>
3254
3681
  </tr>`;
3255
3682
  }
3256
3683
  html += `
@@ -3276,6 +3703,7 @@ var PlanOfCareTemplate = class {
3276
3703
  <th>Created</th>
3277
3704
  <th>Planned Start</th>
3278
3705
  <th>Planned End</th>
3706
+ <th>Source</th>
3279
3707
  </tr>
3280
3708
  </thead>
3281
3709
  <tbody>`;
@@ -3293,10 +3721,11 @@ var PlanOfCareTemplate = class {
3293
3721
  isSummaryCreated = true;
3294
3722
  html += `
3295
3723
  <tr>
3296
- <td>${data["CarePlan Name"] ?? "-"}</td>
3297
- <td>${templateUtilities.renderTime(data["created"], timezone) ?? "-"}</td>
3298
- <td>${templateUtilities.renderTime(data["period.start"], timezone) ?? "-"}</td>
3299
- <td>${templateUtilities.renderTime(data["period.end"], timezone) ?? "-"}</td>
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>
3300
3729
  </tr>`;
3301
3730
  }
3302
3731
  }
@@ -3327,77 +3756,54 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3327
3756
  */
3328
3757
  static generateStaticNarrative(resources, timezone) {
3329
3758
  const templateUtilities = new TemplateUtilities(resources);
3330
- let html = ``;
3331
- const activeConditions = [];
3332
- const clinicalImpressions = [];
3333
- for (const resourceItem of resources) {
3334
- if (resourceItem.resourceType === "Condition") {
3335
- activeConditions.push(resourceItem);
3336
- } else if (resourceItem.resourceType === "ClinicalImpression") {
3337
- clinicalImpressions.push(resourceItem);
3338
- }
3339
- }
3340
- activeConditions.sort((a, b) => {
3341
- const dateA = a.recordedDate ? new Date(a.recordedDate).getTime() : 0;
3342
- const dateB = b.recordedDate ? new Date(b.recordedDate).getTime() : 0;
3343
- return dateB - dateA;
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;
3344
3770
  });
3345
- clinicalImpressions.sort((a, b) => {
3346
- const dateA = a.effectiveDateTime ? new Date(a.effectiveDateTime).getTime() : a.effectivePeriod?.start ? new Date(a.effectivePeriod.start).getTime() : a.date ? new Date(a.date).getTime() : 0;
3347
- const dateB = b.effectiveDateTime ? new Date(b.effectiveDateTime).getTime() : b.effectivePeriod?.start ? new Date(b.effectivePeriod.start).getTime() : b.date ? new Date(b.date).getTime() : 0;
3348
- return dateB - dateA;
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);
3349
3774
  });
3350
- if (activeConditions.length > 0) {
3351
- html += `<h3>Conditions</h3>
3352
- <table>
3353
- <thead>
3354
- <tr>
3355
- <th>Problem</th>
3356
- <th>Onset Date</th>
3357
- <th>Recorded Date</th>
3358
- </tr>
3359
- </thead>
3360
- <tbody>`;
3361
- const addedConditionCodes = /* @__PURE__ */ new Set();
3362
- for (const cond of activeConditions) {
3363
- const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
3364
- if (!addedConditionCodes.has(conditionCode)) {
3365
- addedConditionCodes.add(conditionCode);
3366
- html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
3367
- <td class="Name">${conditionCode}</td>
3368
- <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
3369
- <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
3370
- </tr>`;
3371
- }
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>`;
3372
3796
  }
3373
- html += `</tbody>
3374
- </table>`;
3797
+ html += `</tbody></table>`;
3375
3798
  }
3376
3799
  if (clinicalImpressions.length > 0) {
3377
- html += `<h3>Clinical Impressions</h3>
3378
- <table>
3379
- <thead>
3380
- <tr>
3381
- <th>Date</th>
3382
- <th>Status</th>
3383
- <th>Description</th>
3384
- <th>Summary</th>
3385
- <th>Findings</th>
3386
- </tr>
3387
- </thead>
3388
- <tbody>`;
3800
+ html += `<table><thead><tr><th>Date</th><th>Status</th><th>Description</th><th>Summary</th><th>Findings</th></tr></thead><tbody>`;
3389
3801
  for (const impression of clinicalImpressions) {
3390
3802
  let formattedDate = "";
3391
3803
  if (impression.effectiveDateTime) {
3392
- formattedDate = templateUtilities.renderTime(
3393
- impression.effectiveDateTime,
3394
- timezone
3395
- );
3804
+ formattedDate = templateUtilities.renderTime(impression.effectiveDateTime, timezone);
3396
3805
  } else if (impression.effectivePeriod) {
3397
- formattedDate = templateUtilities.renderPeriod(
3398
- impression.effectivePeriod,
3399
- timezone
3400
- );
3806
+ formattedDate = templateUtilities.renderPeriod(impression.effectivePeriod, timezone);
3401
3807
  } else if (impression.date) {
3402
3808
  formattedDate = templateUtilities.renderDate(impression.date);
3403
3809
  }
@@ -3405,23 +3811,24 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
3405
3811
  if (impression.finding && impression.finding.length > 0) {
3406
3812
  findingsHtml = "<ul>";
3407
3813
  for (const finding of impression.finding) {
3408
- 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) : "";
3409
3815
  const cause = finding.basis || "";
3410
3816
  findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
3411
3817
  }
3412
3818
  findingsHtml += "</ul>";
3413
3819
  }
3414
- html += `
3415
- <tr id="${templateUtilities.narrativeLinkId(impression)}">
3416
- <td>${formattedDate}</td>
3417
- <td>${impression.status || ""}</td>
3418
- <td>${impression.description || ""}</td>
3419
- <td>${impression.summary || ""}</td>
3420
- <td>${findingsHtml}</td>
3421
- </tr>`;
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>`;
3422
3827
  }
3423
- html += `</tbody>
3424
- </table>`;
3828
+ html += `</tbody></table>`;
3829
+ }
3830
+ if (functionalObservations.length === 0 && clinicalImpressions.length === 0) {
3831
+ html += `<p>No functional status information available.</p>`;
3425
3832
  }
3426
3833
  return html;
3427
3834
  }
@@ -3446,34 +3853,109 @@ var PregnancyTemplate = class _PregnancyTemplate {
3446
3853
  */
3447
3854
  static generateStaticNarrative(resources, timezone) {
3448
3855
  const templateUtilities = new TemplateUtilities(resources);
3449
- const observations = resources.map((entry) => entry) || [];
3450
- 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) => {
3451
3864
  const dateA = a.effectiveDateTime || a.effectivePeriod?.start;
3452
3865
  const dateB = b.effectiveDateTime || b.effectivePeriod?.start;
3453
- 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;
3454
3879
  });
3880
+ if (!pregnancyStatusObs && !eddObs && historyObs.length === 0 && conditions.length === 0) {
3881
+ return `<p>No history of pregnancy found.</p>`;
3882
+ }
3455
3883
  let html = `
3456
- <table>
3457
- <thead>
3458
- <tr>
3459
- <th>Result</th>
3460
- <th>Comments</th>
3461
- <th>Date</th>
3462
- </tr>
3463
- </thead>
3464
- <tbody>`;
3465
- for (const resource of observations) {
3466
- const obs = resource;
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 }) {
3467
3896
  html += `
3468
- <tr id="${templateUtilities.narrativeLinkId(obs)}">
3469
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
3470
- <td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
3471
- <td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
3472
- </tr>`;
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 });
3473
3955
  }
3474
3956
  html += `
3475
- </tbody>
3476
- </table>`;
3957
+ </tbody>
3958
+ </table>`;
3477
3959
  return html;
3478
3960
  }
3479
3961
  };
@@ -3518,7 +4000,7 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
3518
4000
  const consent = resourceItem;
3519
4001
  html += `
3520
4002
  <tr id="${templateUtilities.narrativeLinkId(consent)}">
3521
- <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
4003
+ <td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConceptDisplay(consent.scope, "display"))}</td>
3522
4004
  <td>${consent.status || ""}</td>
3523
4005
  <td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
3524
4006
  <td>${consent.dateTime || ""}</td>
@@ -3551,7 +4033,7 @@ var TypeScriptTemplateMapper = class {
3551
4033
  resources,
3552
4034
  timezone,
3553
4035
  now
3554
- ) : templateClass.generateNarrative(resources, timezone);
4036
+ ) : templateClass.generateNarrative(resources, timezone, now);
3555
4037
  }
3556
4038
  };
3557
4039
  // Map of section types to their template classes