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