@imranq2/fhirpatientsummary 1.0.21 → 1.0.23

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 +189 -126
  2. package/dist/index.js +189 -126
  3. package/package.json +21 -20
package/dist/index.cjs CHANGED
@@ -109,6 +109,14 @@ var BLOOD_PRESSURE_LOINC_CODES = {
109
109
  DIASTOLIC: "8462-4"
110
110
  };
111
111
 
112
+ // src/structures/ips_section_constants.ts
113
+ var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
114
+ "Systolic Blood Pressure": "valueRatio.numerator.value",
115
+ "Diastolic Blood Pressure": "valueRatio.denominator.value",
116
+ "Default": "valueString"
117
+ };
118
+ var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
119
+
112
120
  // src/structures/ips_section_resource_map.ts
113
121
  var IPSSectionResourceFilters = {
114
122
  // Patient section: only Patient resource
@@ -116,7 +124,7 @@ var IPSSectionResourceFilters = {
116
124
  // Only include allergies
117
125
  ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "AllergyIntolerance",
118
126
  // includes MedicationRequest, MedicationStatement. Medication is needed for medication names
119
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement", "Medication"].includes(resource.resourceType),
127
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement"].includes(resource.resourceType) && resource.status === "active" || resource.resourceType === "Medication",
120
128
  // Only include active conditions
121
129
  ["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
122
130
  // Only include completed immunizations
@@ -143,15 +151,21 @@ var IPSSectionResourceFilters = {
143
151
  ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
144
152
  };
145
153
  var IPSSectionSummaryCompositionFilter = {
146
- ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "allergy_summary_document"),
147
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "vital_summary_document"),
148
- ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "careplan_summary_document"),
149
- ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "immunization_summary_document")
154
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
155
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
156
+ ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
157
+ ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
158
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document")
150
159
  };
151
160
  var IPSSectionResourceHelper = class {
152
161
  static getResourceFilterForSection(section) {
153
162
  return IPSSectionResourceFilters[section];
154
163
  }
164
+ static getSectionResources(section, resources) {
165
+ const filter = IPSSectionResourceFilters[section];
166
+ if (!filter) return [];
167
+ return resources.filter(filter);
168
+ }
155
169
  static getSummaryCompositionFilterForSection(section) {
156
170
  return IPSSectionSummaryCompositionFilter[section];
157
171
  }
@@ -331,7 +345,7 @@ var TemplateUtilities = class {
331
345
  * @returns Formatted date string (date only, no time component)
332
346
  */
333
347
  renderDate(date) {
334
- return this.formatDateTime(date, void 0, true);
348
+ return this.formatDateTime(date, "UTC", true);
335
349
  }
336
350
  /**
337
351
  * Renders a recorded date
@@ -845,9 +859,11 @@ var TemplateUtilities = class {
845
859
  if (dateValue instanceof Date) {
846
860
  dateTime = import_luxon.DateTime.fromJSDate(dateValue);
847
861
  } else if (typeof dateValue === "string") {
848
- dateTime = import_luxon.DateTime.fromISO(dateValue);
849
862
  if (!dateValue.includes("T")) {
850
863
  dateOnly = true;
864
+ dateTime = import_luxon.DateTime.fromISO(dateValue, { zone: "utc" });
865
+ } else {
866
+ dateTime = import_luxon.DateTime.fromISO(dateValue);
851
867
  }
852
868
  } else {
853
869
  dateTime = import_luxon.DateTime.fromISO(String(dateValue));
@@ -855,7 +871,9 @@ var TemplateUtilities = class {
855
871
  if (!dateTime.isValid) {
856
872
  return String(dateValue);
857
873
  }
858
- if (timezone && !dateOnly) {
874
+ if (dateOnly) {
875
+ dateTime = dateTime.toUTC();
876
+ } else if (timezone) {
859
877
  dateTime = dateTime.toUTC().setZone(timezone);
860
878
  }
861
879
  const formatOptions = dateOnly ? { year: "numeric", month: "2-digit", day: "2-digit" } : {
@@ -947,6 +965,9 @@ var TemplateUtilities = class {
947
965
  }
948
966
  };
949
967
 
968
+ // src/constants.ts
969
+ var ADDRESS_SIMILARITY_THRESHOLD = 70;
970
+
950
971
  // src/narratives/templates/typescript/PatientTemplate.ts
951
972
  var PatientTemplate = class _PatientTemplate {
952
973
  /**
@@ -973,7 +994,6 @@ var PatientTemplate = class _PatientTemplate {
973
994
  <li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
974
995
  <li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
975
996
  <li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
976
- <li><strong>Identifier(s):</strong>${this.renderIdentifiers(combinedPatient)}</li>
977
997
  <li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
978
998
  <li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
979
999
  <li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus?.text || ""}</li>
@@ -994,7 +1014,6 @@ var PatientTemplate = class _PatientTemplate {
994
1014
  }
995
1015
  const combined = patients[0];
996
1016
  const allNames = [];
997
- const allIdentifiers = [];
998
1017
  const allTelecom = [];
999
1018
  const allAddresses = [];
1000
1019
  const allCommunication = [];
@@ -1002,9 +1021,6 @@ var PatientTemplate = class _PatientTemplate {
1002
1021
  if (patient.name) {
1003
1022
  allNames.push(...patient.name);
1004
1023
  }
1005
- if (patient.identifier) {
1006
- allIdentifiers.push(...patient.identifier);
1007
- }
1008
1024
  if (patient.telecom) {
1009
1025
  allTelecom.push(...patient.telecom);
1010
1026
  }
@@ -1031,7 +1047,6 @@ var PatientTemplate = class _PatientTemplate {
1031
1047
  }
1032
1048
  });
1033
1049
  combined.name = allNames;
1034
- combined.identifier = allIdentifiers;
1035
1050
  combined.telecom = allTelecom;
1036
1051
  combined.address = allAddresses;
1037
1052
  combined.communication = allCommunication;
@@ -1057,21 +1072,6 @@ var PatientTemplate = class _PatientTemplate {
1057
1072
  });
1058
1073
  return Array.from(uniqueNames).map((nameText) => `<ul><li>${nameText}</li></ul>`).join("");
1059
1074
  }
1060
- /**
1061
- * Renders patient identifiers as HTML list items
1062
- * @param patient - Patient resources
1063
- * @returns HTML string of list items
1064
- */
1065
- static renderIdentifiers(patient) {
1066
- if (!patient.identifier || patient.identifier.length === 0) {
1067
- return "";
1068
- }
1069
- return patient.identifier.map((id) => {
1070
- const system = id.system || "";
1071
- const value = id.value || "";
1072
- return `<ul><li>${system}: ${value}</li></ul>`;
1073
- }).join("");
1074
- }
1075
1075
  /**
1076
1076
  * Renders patient telecom information grouped by system
1077
1077
  * @param patient - Patient resources
@@ -1173,7 +1173,89 @@ var PatientTemplate = class _PatientTemplate {
1173
1173
  uniqueAddresses.add(addressText);
1174
1174
  }
1175
1175
  });
1176
- return Array.from(uniqueAddresses).map((addressText) => `<ul><li>${addressText}</li></ul>`).join("");
1176
+ const deduplicatedAddresses = this.deduplicateSimilarAddresses(Array.from(uniqueAddresses));
1177
+ return deduplicatedAddresses.map((addressText) => `<ul><li>${addressText}</li></ul>`).join("");
1178
+ }
1179
+ /**
1180
+ * Calculates the similarity between two strings using Levenshtein distance
1181
+ * Returns a percentage (0-100) indicating how similar the strings are
1182
+ * @param str1 - First string
1183
+ * @param str2 - Second string
1184
+ * @returns Similarity percentage (0-100)
1185
+ */
1186
+ static calculateStringSimilarity(str1, str2) {
1187
+ const longer = str1.length > str2.length ? str1 : str2;
1188
+ const shorter = str1.length > str2.length ? str2 : str1;
1189
+ if (longer.length === 0) {
1190
+ return 100;
1191
+ }
1192
+ const editDistance = this.levenshteinDistance(longer.toLowerCase(), shorter.toLowerCase());
1193
+ return (longer.length - editDistance) / longer.length * 100;
1194
+ }
1195
+ /**
1196
+ * Calculates the Levenshtein distance between two strings
1197
+ * @param str1 - First string
1198
+ * @param str2 - Second string
1199
+ * @returns The Levenshtein distance
1200
+ */
1201
+ static levenshteinDistance(str1, str2) {
1202
+ const matrix = [];
1203
+ for (let i = 0; i <= str2.length; i++) {
1204
+ matrix[i] = [i];
1205
+ }
1206
+ for (let j = 0; j <= str1.length; j++) {
1207
+ matrix[0][j] = j;
1208
+ }
1209
+ for (let i = 1; i <= str2.length; i++) {
1210
+ for (let j = 1; j <= str1.length; j++) {
1211
+ if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
1212
+ matrix[i][j] = matrix[i - 1][j - 1];
1213
+ } else {
1214
+ matrix[i][j] = Math.min(
1215
+ matrix[i - 1][j - 1] + 1,
1216
+ // substitution
1217
+ matrix[i][j - 1] + 1,
1218
+ // insertion
1219
+ matrix[i - 1][j] + 1
1220
+ // deletion
1221
+ );
1222
+ }
1223
+ }
1224
+ }
1225
+ return matrix[str2.length][str1.length];
1226
+ }
1227
+ /**
1228
+ * Deduplicates addresses that are more than ADDRESS_SIMILARITY_THRESHOLD% similar
1229
+ * @param addresses - Array of address strings
1230
+ * @returns Array of deduplicated addresses
1231
+ */
1232
+ static deduplicateSimilarAddresses(addresses) {
1233
+ if (addresses.length <= 1) {
1234
+ return addresses;
1235
+ }
1236
+ const deduplicated = [];
1237
+ const processed = /* @__PURE__ */ new Set();
1238
+ for (let i = 0; i < addresses.length; i++) {
1239
+ if (processed.has(i)) {
1240
+ continue;
1241
+ }
1242
+ let keepAddress = addresses[i];
1243
+ processed.add(i);
1244
+ for (let j = i + 1; j < addresses.length; j++) {
1245
+ if (processed.has(j)) {
1246
+ continue;
1247
+ }
1248
+ const similarity = this.calculateStringSimilarity(addresses[i], addresses[j]);
1249
+ if (similarity > ADDRESS_SIMILARITY_THRESHOLD) {
1250
+ processed.add(j);
1251
+ if (addresses[j].length > keepAddress.length) {
1252
+ keepAddress = addresses[j];
1253
+ }
1254
+ }
1255
+ }
1256
+ deduplicated.push(keepAddress);
1257
+ }
1258
+ return deduplicated;
1177
1259
  }
1178
1260
  /**
1179
1261
  * Renders patient deceased status
@@ -1421,6 +1503,75 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1421
1503
  generateNarrative(resources, timezone) {
1422
1504
  return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
1423
1505
  }
1506
+ /**
1507
+ * Generate HTML narrative for Medication resources using summary
1508
+ * @param resources - FHIR Composition resources
1509
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1510
+ * @returns HTML string for rendering
1511
+ */
1512
+ generateSummaryNarrative(resources, timezone) {
1513
+ const templateUtilities = new TemplateUtilities(resources);
1514
+ let html = `
1515
+ <div>
1516
+ <table>
1517
+ <thead>
1518
+ <tr>
1519
+ <th>Medication</th>
1520
+ <th>Sig</th>
1521
+ <th>Days of Supply</th>
1522
+ <th>Refills</th>
1523
+ <th>Start Date</th>
1524
+ </tr>
1525
+ </thead>
1526
+ <tbody>`;
1527
+ for (const resourceItem of resources) {
1528
+ for (const rowData of resourceItem.section ?? []) {
1529
+ const data = {};
1530
+ for (const columnData of rowData.section ?? []) {
1531
+ switch (columnData.title) {
1532
+ case "Medication Name":
1533
+ data["medication"] = columnData.text?.div ?? "";
1534
+ break;
1535
+ case "Status":
1536
+ data["status"] = columnData.text?.div ?? "";
1537
+ break;
1538
+ case "Prescriber Instruction":
1539
+ data["sig-prescriber"] = columnData.text?.div ?? "";
1540
+ break;
1541
+ case "Pharmacy Instruction":
1542
+ data["sig-pharmacy"] = columnData.text?.div ?? "";
1543
+ break;
1544
+ case "Days Of Supply":
1545
+ data["daysOfSupply"] = columnData.text?.div ?? "";
1546
+ break;
1547
+ case "Refills Remaining":
1548
+ data["refills"] = columnData.text?.div ?? "";
1549
+ break;
1550
+ case "Authored On Date":
1551
+ data["startDate"] = columnData.text?.div ?? "";
1552
+ break;
1553
+ default:
1554
+ break;
1555
+ }
1556
+ }
1557
+ if (data["status"] === "active") {
1558
+ html += `
1559
+ <tr>
1560
+ <td>${data["medication"]}</td>
1561
+ <td>${data["sig-prescriber"] || data["sig-pharmacy"]}</td>
1562
+ <td>${data["daysOfSupply"]}</td>
1563
+ <td>${data["refills"]}</td>
1564
+ <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
1565
+ </tr>`;
1566
+ }
1567
+ }
1568
+ }
1569
+ html += `
1570
+ </tbody>
1571
+ </table>
1572
+ </div>`;
1573
+ return html;
1574
+ }
1424
1575
  /**
1425
1576
  * Safely parse a date string and return a valid Date object or null
1426
1577
  * @param dateString - The date string to parse
@@ -1433,55 +1584,6 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1433
1584
  const date = new Date(dateString);
1434
1585
  return !isNaN(date.getTime()) ? date : null;
1435
1586
  }
1436
- /**
1437
- * Determine if a MedicationRequest is active
1438
- * @param medicationRequest - The MedicationRequest resource
1439
- * @returns boolean indicating if the medication request is active
1440
- */
1441
- static isActiveMedicationRequest(medicationRequest) {
1442
- const status = medicationRequest.status?.toLowerCase();
1443
- if (status === "active" || status === "unknown") {
1444
- return true;
1445
- }
1446
- if (status === "completed" || status === "cancelled" || status === "stopped" || status === "draft") {
1447
- return false;
1448
- }
1449
- const endDate = medicationRequest.dispenseRequest?.validityPeriod?.end;
1450
- if (!endDate) {
1451
- return true;
1452
- }
1453
- const parsedEndDate = this.parseDate(endDate);
1454
- if (!parsedEndDate) {
1455
- return true;
1456
- }
1457
- return parsedEndDate.getTime() > Date.now();
1458
- }
1459
- /**
1460
- * Determine if a MedicationStatement is active
1461
- * @param medicationStatement - The MedicationStatement resource
1462
- * @returns boolean indicating if the medication statement is active
1463
- */
1464
- static isActiveMedicationStatement(medicationStatement) {
1465
- const status = medicationStatement.status?.toLowerCase();
1466
- if (status === "active" || status === "intended" || status === "unknown") {
1467
- return true;
1468
- }
1469
- if (status === "completed" || status === "stopped" || status === "not-taken") {
1470
- return false;
1471
- }
1472
- let endDate;
1473
- if (medicationStatement.effectivePeriod?.end) {
1474
- endDate = medicationStatement.effectivePeriod.end;
1475
- }
1476
- if (!endDate) {
1477
- return true;
1478
- }
1479
- const parsedEndDate = this.parseDate(endDate);
1480
- if (!parsedEndDate) {
1481
- return true;
1482
- }
1483
- return parsedEndDate.getTime() > Date.now();
1484
- }
1485
1587
  /**
1486
1588
  * Internal static implementation that actually generates the narrative
1487
1589
  * @param resources - FHIR Medication resources
@@ -1495,20 +1597,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1495
1597
  const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
1496
1598
  const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
1497
1599
  const allActiveMedications = [];
1498
- const allInactiveMedications = [];
1499
1600
  medicationRequests.forEach((mr) => {
1500
- if (this.isActiveMedicationRequest(mr.resource)) {
1501
- allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1502
- } else {
1503
- allInactiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1504
- }
1601
+ allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1505
1602
  });
1506
1603
  medicationStatements.forEach((ms) => {
1507
- if (this.isActiveMedicationStatement(ms.resource)) {
1508
- allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1509
- } else {
1510
- allInactiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1511
- }
1604
+ allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1512
1605
  });
1513
1606
  const sortMedications = (medications) => {
1514
1607
  medications.sort((a, b) => {
@@ -1538,11 +1631,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1538
1631
  };
1539
1632
  if (allActiveMedications.length > 0) {
1540
1633
  sortMedications(allActiveMedications);
1541
- html += this.renderCombinedMedications(templateUtilities, allActiveMedications, true);
1542
- }
1543
- if (allInactiveMedications.length > 0) {
1544
- sortMedications(allInactiveMedications);
1545
- html += this.renderCombinedMedications(templateUtilities, allInactiveMedications, false);
1634
+ html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
1546
1635
  }
1547
1636
  return html;
1548
1637
  }
@@ -1580,12 +1669,10 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1580
1669
  * Render HTML table for combined MedicationRequest and MedicationStatement resources
1581
1670
  * @param templateUtilities - Instance of TemplateUtilities for utility functions
1582
1671
  * @param medications - Array of combined medication resources
1583
- * @param sectionTitle - Title for the section
1584
1672
  * @returns HTML string for rendering
1585
1673
  */
1586
- static renderCombinedMedications(templateUtilities, medications, isActiveSection) {
1674
+ static renderCombinedMedications(templateUtilities, medications) {
1587
1675
  let html = `
1588
- <h3>${isActiveSection ? "Active Medications" : "Inactive Medications"}</h3>
1589
1676
  <table>
1590
1677
  <thead>
1591
1678
  <tr>
@@ -1594,9 +1681,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1594
1681
  <th>Sig</th>
1595
1682
  <th>Dispense Quantity</th>
1596
1683
  <th>Refills</th>
1597
- <th>Start Date</th>${isActiveSection ? "" : `
1598
- <th>End Date</th>`}
1599
- <th>Status</th>
1684
+ <th>Start Date</th>
1600
1685
  </tr>
1601
1686
  </thead>
1602
1687
  <tbody>`;
@@ -1608,12 +1693,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1608
1693
  let dispenseQuantity = "-";
1609
1694
  let refills = "-";
1610
1695
  let startDate = "-";
1611
- let endDate = "-";
1612
- let status;
1613
1696
  if (medication.type === "request") {
1614
1697
  const mr = medication.resource;
1615
1698
  type = "Request";
1616
- status = mr.status ? String(mr.status) : "-";
1617
1699
  medicationName = templateUtilities.getMedicationName(
1618
1700
  mr.medicationReference || mr.medicationCodeableConcept
1619
1701
  );
@@ -1627,14 +1709,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1627
1709
  refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
1628
1710
  if (mr.dispenseRequest?.validityPeriod) {
1629
1711
  startDate = mr.dispenseRequest.validityPeriod.start || "-";
1630
- endDate = mr.dispenseRequest.validityPeriod.end || "-";
1631
1712
  } else {
1632
1713
  startDate = mr.authoredOn || "-";
1633
1714
  }
1634
1715
  } else {
1635
1716
  const ms = medication.resource;
1636
1717
  type = "Statement";
1637
- status = ms.status ? String(ms.status) : "-";
1638
1718
  medicationName = templateUtilities.getMedicationName(
1639
1719
  ms.medicationReference || ms.medicationCodeableConcept
1640
1720
  );
@@ -1643,7 +1723,6 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1643
1723
  startDate = ms.effectiveDateTime;
1644
1724
  } else if (ms.effectivePeriod) {
1645
1725
  startDate = ms.effectivePeriod.start || "-";
1646
- endDate = ms.effectivePeriod.end || "-";
1647
1726
  }
1648
1727
  }
1649
1728
  html += `
@@ -1653,9 +1732,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1653
1732
  <td>${sig}</td>
1654
1733
  <td>${dispenseQuantity}</td>
1655
1734
  <td>${refills}</td>
1656
- <td>${startDate}</td>${isActiveSection ? "" : `
1657
- <td>${endDate}</td>`}
1658
- <td>${status}</td>
1735
+ <td>${startDate}</td>
1659
1736
  </tr>`;
1660
1737
  }
1661
1738
  html += `
@@ -1796,6 +1873,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
1796
1873
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1797
1874
  * @returns HTML string for rendering
1798
1875
  */
1876
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1799
1877
  static generateStaticNarrative(resources, timezone) {
1800
1878
  const templateUtilities = new TemplateUtilities(resources);
1801
1879
  let html = ``;
@@ -1812,7 +1890,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
1812
1890
  <th>Problem</th>
1813
1891
  <th>Onset Date</th>
1814
1892
  <th>Recorded Date</th>
1815
- <th>Notes</th>
1816
1893
  </tr>
1817
1894
  </thead>
1818
1895
  <tbody>`;
@@ -1821,7 +1898,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
1821
1898
  <td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
1822
1899
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
1823
1900
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
1824
- <td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
1825
1901
  </tr>`;
1826
1902
  }
1827
1903
  html += `</tbody>
@@ -1830,13 +1906,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
1830
1906
  }
1831
1907
  };
1832
1908
 
1833
- // src/structures/ips_section_constants.ts
1834
- var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
1835
- "Systolic Blood Pressure": "valueRatio.numerator.value",
1836
- "Diastolic Blood Pressure": "valueRatio.denominator.value",
1837
- "Default": "valueString"
1838
- };
1839
-
1840
1909
  // src/narratives/templates/typescript/VitalSignsTemplate.ts
1841
1910
  var VitalSignsTemplate = class _VitalSignsTemplate {
1842
1911
  /**
@@ -2256,6 +2325,7 @@ var PastHistoryOfIllnessTemplate = class {
2256
2325
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2257
2326
  * @returns HTML string for rendering
2258
2327
  */
2328
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2259
2329
  generateNarrative(resources, timezone) {
2260
2330
  const templateUtilities = new TemplateUtilities(resources);
2261
2331
  let html = ``;
@@ -2273,7 +2343,6 @@ var PastHistoryOfIllnessTemplate = class {
2273
2343
  <th>Onset Date</th>
2274
2344
  <th>Recorded Date</th>
2275
2345
  <th>Resolved Date</th>
2276
- <th>Notes</th>
2277
2346
  </tr>
2278
2347
  </thead>
2279
2348
  <tbody>`;
@@ -2283,7 +2352,6 @@ var PastHistoryOfIllnessTemplate = class {
2283
2352
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2284
2353
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2285
2354
  <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
2286
- <td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
2287
2355
  </tr>`;
2288
2356
  }
2289
2357
  html += `</tbody>
@@ -2436,7 +2504,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2436
2504
  <th>Problem</th>
2437
2505
  <th>Onset Date</th>
2438
2506
  <th>Recorded Date</th>
2439
- <th>Notes</th>
2440
2507
  </tr>
2441
2508
  </thead>
2442
2509
  <tbody>`;
@@ -2445,7 +2512,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2445
2512
  <td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
2446
2513
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2447
2514
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2448
- <td class="Notes">${templateUtilities.renderNotes(cond.note, timezone, { styled: true, warning: true })}</td>
2449
2515
  </tr>`;
2450
2516
  }
2451
2517
  html += `</tbody>
@@ -2461,7 +2527,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2461
2527
  <th>Description</th>
2462
2528
  <th>Summary</th>
2463
2529
  <th>Findings</th>
2464
- <th>Notes</th>
2465
2530
  </tr>
2466
2531
  </thead>
2467
2532
  <tbody>`;
@@ -2490,7 +2555,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2490
2555
  }
2491
2556
  findingsHtml += "</ul>";
2492
2557
  }
2493
- const notes = templateUtilities.renderNotes(impression.note, timezone);
2494
2558
  html += `
2495
2559
  <tr id="${templateUtilities.narrativeLinkId(impression)}">
2496
2560
  <td>${formattedDate}</td>
@@ -2498,7 +2562,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2498
2562
  <td>${impression.description || ""}</td>
2499
2563
  <td>${impression.summary || ""}</td>
2500
2564
  <td>${findingsHtml}</td>
2501
- <td>${notes}</td>
2502
2565
  </tr>`;
2503
2566
  }
2504
2567
  html += `</tbody>
package/dist/index.js CHANGED
@@ -81,6 +81,14 @@ var BLOOD_PRESSURE_LOINC_CODES = {
81
81
  DIASTOLIC: "8462-4"
82
82
  };
83
83
 
84
+ // src/structures/ips_section_constants.ts
85
+ var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
86
+ "Systolic Blood Pressure": "valueRatio.numerator.value",
87
+ "Diastolic Blood Pressure": "valueRatio.denominator.value",
88
+ "Default": "valueString"
89
+ };
90
+ var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
91
+
84
92
  // src/structures/ips_section_resource_map.ts
85
93
  var IPSSectionResourceFilters = {
86
94
  // Patient section: only Patient resource
@@ -88,7 +96,7 @@ var IPSSectionResourceFilters = {
88
96
  // Only include allergies
89
97
  ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "AllergyIntolerance",
90
98
  // includes MedicationRequest, MedicationStatement. Medication is needed for medication names
91
- ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement", "Medication"].includes(resource.resourceType),
99
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => ["MedicationRequest", "MedicationStatement"].includes(resource.resourceType) && resource.status === "active" || resource.resourceType === "Medication",
92
100
  // Only include active conditions
93
101
  ["ProblemSection" /* PROBLEMS */]: (resource) => resource.resourceType === "Condition" && resource.clinicalStatus?.coding?.some((c) => !["inactive", "resolved"].includes(c.code)),
94
102
  // Only include completed immunizations
@@ -115,15 +123,21 @@ var IPSSectionResourceFilters = {
115
123
  ["AdvanceDirectivesSection" /* ADVANCE_DIRECTIVES */]: (resource) => resource.resourceType === "Consent" && resource.status === "active"
116
124
  };
117
125
  var IPSSectionSummaryCompositionFilter = {
118
- ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "allergy_summary_document"),
119
- ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "vital_summary_document"),
120
- ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "careplan_summary_document"),
121
- ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/" && c.code === "immunization_summary_document")
126
+ ["AllergyIntoleranceSection" /* ALLERGIES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "allergy_summary_document"),
127
+ ["VitalSignsSection" /* VITAL_SIGNS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "vital_summary_document"),
128
+ ["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
129
+ ["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
130
+ ["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document")
122
131
  };
123
132
  var IPSSectionResourceHelper = class {
124
133
  static getResourceFilterForSection(section) {
125
134
  return IPSSectionResourceFilters[section];
126
135
  }
136
+ static getSectionResources(section, resources) {
137
+ const filter = IPSSectionResourceFilters[section];
138
+ if (!filter) return [];
139
+ return resources.filter(filter);
140
+ }
127
141
  static getSummaryCompositionFilterForSection(section) {
128
142
  return IPSSectionSummaryCompositionFilter[section];
129
143
  }
@@ -303,7 +317,7 @@ var TemplateUtilities = class {
303
317
  * @returns Formatted date string (date only, no time component)
304
318
  */
305
319
  renderDate(date) {
306
- return this.formatDateTime(date, void 0, true);
320
+ return this.formatDateTime(date, "UTC", true);
307
321
  }
308
322
  /**
309
323
  * Renders a recorded date
@@ -817,9 +831,11 @@ var TemplateUtilities = class {
817
831
  if (dateValue instanceof Date) {
818
832
  dateTime = DateTime.fromJSDate(dateValue);
819
833
  } else if (typeof dateValue === "string") {
820
- dateTime = DateTime.fromISO(dateValue);
821
834
  if (!dateValue.includes("T")) {
822
835
  dateOnly = true;
836
+ dateTime = DateTime.fromISO(dateValue, { zone: "utc" });
837
+ } else {
838
+ dateTime = DateTime.fromISO(dateValue);
823
839
  }
824
840
  } else {
825
841
  dateTime = DateTime.fromISO(String(dateValue));
@@ -827,7 +843,9 @@ var TemplateUtilities = class {
827
843
  if (!dateTime.isValid) {
828
844
  return String(dateValue);
829
845
  }
830
- if (timezone && !dateOnly) {
846
+ if (dateOnly) {
847
+ dateTime = dateTime.toUTC();
848
+ } else if (timezone) {
831
849
  dateTime = dateTime.toUTC().setZone(timezone);
832
850
  }
833
851
  const formatOptions = dateOnly ? { year: "numeric", month: "2-digit", day: "2-digit" } : {
@@ -919,6 +937,9 @@ var TemplateUtilities = class {
919
937
  }
920
938
  };
921
939
 
940
+ // src/constants.ts
941
+ var ADDRESS_SIMILARITY_THRESHOLD = 70;
942
+
922
943
  // src/narratives/templates/typescript/PatientTemplate.ts
923
944
  var PatientTemplate = class _PatientTemplate {
924
945
  /**
@@ -945,7 +966,6 @@ var PatientTemplate = class _PatientTemplate {
945
966
  <li><strong>Name(s):</strong>${this.renderNames(combinedPatient)}</li>
946
967
  <li><strong>Gender:</strong>${combinedPatient.gender ? this.capitalize(combinedPatient.gender) : ""}</li>
947
968
  <li><strong>Date of Birth:</strong>${combinedPatient.birthDate || ""}</li>
948
- <li><strong>Identifier(s):</strong>${this.renderIdentifiers(combinedPatient)}</li>
949
969
  <li><strong>Telecom:</strong><ul>${this.renderTelecom(combinedPatient)}</ul></li>
950
970
  <li><strong>Address(es):</strong>${this.renderAddresses(combinedPatient)}</li>
951
971
  <li><strong>Marital Status:</strong> ${combinedPatient.maritalStatus?.text || ""}</li>
@@ -966,7 +986,6 @@ var PatientTemplate = class _PatientTemplate {
966
986
  }
967
987
  const combined = patients[0];
968
988
  const allNames = [];
969
- const allIdentifiers = [];
970
989
  const allTelecom = [];
971
990
  const allAddresses = [];
972
991
  const allCommunication = [];
@@ -974,9 +993,6 @@ var PatientTemplate = class _PatientTemplate {
974
993
  if (patient.name) {
975
994
  allNames.push(...patient.name);
976
995
  }
977
- if (patient.identifier) {
978
- allIdentifiers.push(...patient.identifier);
979
- }
980
996
  if (patient.telecom) {
981
997
  allTelecom.push(...patient.telecom);
982
998
  }
@@ -1003,7 +1019,6 @@ var PatientTemplate = class _PatientTemplate {
1003
1019
  }
1004
1020
  });
1005
1021
  combined.name = allNames;
1006
- combined.identifier = allIdentifiers;
1007
1022
  combined.telecom = allTelecom;
1008
1023
  combined.address = allAddresses;
1009
1024
  combined.communication = allCommunication;
@@ -1029,21 +1044,6 @@ var PatientTemplate = class _PatientTemplate {
1029
1044
  });
1030
1045
  return Array.from(uniqueNames).map((nameText) => `<ul><li>${nameText}</li></ul>`).join("");
1031
1046
  }
1032
- /**
1033
- * Renders patient identifiers as HTML list items
1034
- * @param patient - Patient resources
1035
- * @returns HTML string of list items
1036
- */
1037
- static renderIdentifiers(patient) {
1038
- if (!patient.identifier || patient.identifier.length === 0) {
1039
- return "";
1040
- }
1041
- return patient.identifier.map((id) => {
1042
- const system = id.system || "";
1043
- const value = id.value || "";
1044
- return `<ul><li>${system}: ${value}</li></ul>`;
1045
- }).join("");
1046
- }
1047
1047
  /**
1048
1048
  * Renders patient telecom information grouped by system
1049
1049
  * @param patient - Patient resources
@@ -1145,7 +1145,89 @@ var PatientTemplate = class _PatientTemplate {
1145
1145
  uniqueAddresses.add(addressText);
1146
1146
  }
1147
1147
  });
1148
- return Array.from(uniqueAddresses).map((addressText) => `<ul><li>${addressText}</li></ul>`).join("");
1148
+ const deduplicatedAddresses = this.deduplicateSimilarAddresses(Array.from(uniqueAddresses));
1149
+ return deduplicatedAddresses.map((addressText) => `<ul><li>${addressText}</li></ul>`).join("");
1150
+ }
1151
+ /**
1152
+ * Calculates the similarity between two strings using Levenshtein distance
1153
+ * Returns a percentage (0-100) indicating how similar the strings are
1154
+ * @param str1 - First string
1155
+ * @param str2 - Second string
1156
+ * @returns Similarity percentage (0-100)
1157
+ */
1158
+ static calculateStringSimilarity(str1, str2) {
1159
+ const longer = str1.length > str2.length ? str1 : str2;
1160
+ const shorter = str1.length > str2.length ? str2 : str1;
1161
+ if (longer.length === 0) {
1162
+ return 100;
1163
+ }
1164
+ const editDistance = this.levenshteinDistance(longer.toLowerCase(), shorter.toLowerCase());
1165
+ return (longer.length - editDistance) / longer.length * 100;
1166
+ }
1167
+ /**
1168
+ * Calculates the Levenshtein distance between two strings
1169
+ * @param str1 - First string
1170
+ * @param str2 - Second string
1171
+ * @returns The Levenshtein distance
1172
+ */
1173
+ static levenshteinDistance(str1, str2) {
1174
+ const matrix = [];
1175
+ for (let i = 0; i <= str2.length; i++) {
1176
+ matrix[i] = [i];
1177
+ }
1178
+ for (let j = 0; j <= str1.length; j++) {
1179
+ matrix[0][j] = j;
1180
+ }
1181
+ for (let i = 1; i <= str2.length; i++) {
1182
+ for (let j = 1; j <= str1.length; j++) {
1183
+ if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
1184
+ matrix[i][j] = matrix[i - 1][j - 1];
1185
+ } else {
1186
+ matrix[i][j] = Math.min(
1187
+ matrix[i - 1][j - 1] + 1,
1188
+ // substitution
1189
+ matrix[i][j - 1] + 1,
1190
+ // insertion
1191
+ matrix[i - 1][j] + 1
1192
+ // deletion
1193
+ );
1194
+ }
1195
+ }
1196
+ }
1197
+ return matrix[str2.length][str1.length];
1198
+ }
1199
+ /**
1200
+ * Deduplicates addresses that are more than ADDRESS_SIMILARITY_THRESHOLD% similar
1201
+ * @param addresses - Array of address strings
1202
+ * @returns Array of deduplicated addresses
1203
+ */
1204
+ static deduplicateSimilarAddresses(addresses) {
1205
+ if (addresses.length <= 1) {
1206
+ return addresses;
1207
+ }
1208
+ const deduplicated = [];
1209
+ const processed = /* @__PURE__ */ new Set();
1210
+ for (let i = 0; i < addresses.length; i++) {
1211
+ if (processed.has(i)) {
1212
+ continue;
1213
+ }
1214
+ let keepAddress = addresses[i];
1215
+ processed.add(i);
1216
+ for (let j = i + 1; j < addresses.length; j++) {
1217
+ if (processed.has(j)) {
1218
+ continue;
1219
+ }
1220
+ const similarity = this.calculateStringSimilarity(addresses[i], addresses[j]);
1221
+ if (similarity > ADDRESS_SIMILARITY_THRESHOLD) {
1222
+ processed.add(j);
1223
+ if (addresses[j].length > keepAddress.length) {
1224
+ keepAddress = addresses[j];
1225
+ }
1226
+ }
1227
+ }
1228
+ deduplicated.push(keepAddress);
1229
+ }
1230
+ return deduplicated;
1149
1231
  }
1150
1232
  /**
1151
1233
  * Renders patient deceased status
@@ -1393,6 +1475,75 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1393
1475
  generateNarrative(resources, timezone) {
1394
1476
  return _MedicationSummaryTemplate.generateStaticNarrative(resources, timezone);
1395
1477
  }
1478
+ /**
1479
+ * Generate HTML narrative for Medication resources using summary
1480
+ * @param resources - FHIR Composition resources
1481
+ * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1482
+ * @returns HTML string for rendering
1483
+ */
1484
+ generateSummaryNarrative(resources, timezone) {
1485
+ const templateUtilities = new TemplateUtilities(resources);
1486
+ let html = `
1487
+ <div>
1488
+ <table>
1489
+ <thead>
1490
+ <tr>
1491
+ <th>Medication</th>
1492
+ <th>Sig</th>
1493
+ <th>Days of Supply</th>
1494
+ <th>Refills</th>
1495
+ <th>Start Date</th>
1496
+ </tr>
1497
+ </thead>
1498
+ <tbody>`;
1499
+ for (const resourceItem of resources) {
1500
+ for (const rowData of resourceItem.section ?? []) {
1501
+ const data = {};
1502
+ for (const columnData of rowData.section ?? []) {
1503
+ switch (columnData.title) {
1504
+ case "Medication Name":
1505
+ data["medication"] = columnData.text?.div ?? "";
1506
+ break;
1507
+ case "Status":
1508
+ data["status"] = columnData.text?.div ?? "";
1509
+ break;
1510
+ case "Prescriber Instruction":
1511
+ data["sig-prescriber"] = columnData.text?.div ?? "";
1512
+ break;
1513
+ case "Pharmacy Instruction":
1514
+ data["sig-pharmacy"] = columnData.text?.div ?? "";
1515
+ break;
1516
+ case "Days Of Supply":
1517
+ data["daysOfSupply"] = columnData.text?.div ?? "";
1518
+ break;
1519
+ case "Refills Remaining":
1520
+ data["refills"] = columnData.text?.div ?? "";
1521
+ break;
1522
+ case "Authored On Date":
1523
+ data["startDate"] = columnData.text?.div ?? "";
1524
+ break;
1525
+ default:
1526
+ break;
1527
+ }
1528
+ }
1529
+ if (data["status"] === "active") {
1530
+ html += `
1531
+ <tr>
1532
+ <td>${data["medication"]}</td>
1533
+ <td>${data["sig-prescriber"] || data["sig-pharmacy"]}</td>
1534
+ <td>${data["daysOfSupply"]}</td>
1535
+ <td>${data["refills"]}</td>
1536
+ <td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
1537
+ </tr>`;
1538
+ }
1539
+ }
1540
+ }
1541
+ html += `
1542
+ </tbody>
1543
+ </table>
1544
+ </div>`;
1545
+ return html;
1546
+ }
1396
1547
  /**
1397
1548
  * Safely parse a date string and return a valid Date object or null
1398
1549
  * @param dateString - The date string to parse
@@ -1405,55 +1556,6 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1405
1556
  const date = new Date(dateString);
1406
1557
  return !isNaN(date.getTime()) ? date : null;
1407
1558
  }
1408
- /**
1409
- * Determine if a MedicationRequest is active
1410
- * @param medicationRequest - The MedicationRequest resource
1411
- * @returns boolean indicating if the medication request is active
1412
- */
1413
- static isActiveMedicationRequest(medicationRequest) {
1414
- const status = medicationRequest.status?.toLowerCase();
1415
- if (status === "active" || status === "unknown") {
1416
- return true;
1417
- }
1418
- if (status === "completed" || status === "cancelled" || status === "stopped" || status === "draft") {
1419
- return false;
1420
- }
1421
- const endDate = medicationRequest.dispenseRequest?.validityPeriod?.end;
1422
- if (!endDate) {
1423
- return true;
1424
- }
1425
- const parsedEndDate = this.parseDate(endDate);
1426
- if (!parsedEndDate) {
1427
- return true;
1428
- }
1429
- return parsedEndDate.getTime() > Date.now();
1430
- }
1431
- /**
1432
- * Determine if a MedicationStatement is active
1433
- * @param medicationStatement - The MedicationStatement resource
1434
- * @returns boolean indicating if the medication statement is active
1435
- */
1436
- static isActiveMedicationStatement(medicationStatement) {
1437
- const status = medicationStatement.status?.toLowerCase();
1438
- if (status === "active" || status === "intended" || status === "unknown") {
1439
- return true;
1440
- }
1441
- if (status === "completed" || status === "stopped" || status === "not-taken") {
1442
- return false;
1443
- }
1444
- let endDate;
1445
- if (medicationStatement.effectivePeriod?.end) {
1446
- endDate = medicationStatement.effectivePeriod.end;
1447
- }
1448
- if (!endDate) {
1449
- return true;
1450
- }
1451
- const parsedEndDate = this.parseDate(endDate);
1452
- if (!parsedEndDate) {
1453
- return true;
1454
- }
1455
- return parsedEndDate.getTime() > Date.now();
1456
- }
1457
1559
  /**
1458
1560
  * Internal static implementation that actually generates the narrative
1459
1561
  * @param resources - FHIR Medication resources
@@ -1467,20 +1569,11 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1467
1569
  const medicationRequests = this.getMedicationRequests(templateUtilities, resources);
1468
1570
  const medicationStatements = this.getMedicationStatements(templateUtilities, resources);
1469
1571
  const allActiveMedications = [];
1470
- const allInactiveMedications = [];
1471
1572
  medicationRequests.forEach((mr) => {
1472
- if (this.isActiveMedicationRequest(mr.resource)) {
1473
- allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1474
- } else {
1475
- allInactiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1476
- }
1573
+ allActiveMedications.push({ type: "request", resource: mr.resource, extension: mr.extension });
1477
1574
  });
1478
1575
  medicationStatements.forEach((ms) => {
1479
- if (this.isActiveMedicationStatement(ms.resource)) {
1480
- allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1481
- } else {
1482
- allInactiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1483
- }
1576
+ allActiveMedications.push({ type: "statement", resource: ms.resource, extension: ms.extension });
1484
1577
  });
1485
1578
  const sortMedications = (medications) => {
1486
1579
  medications.sort((a, b) => {
@@ -1510,11 +1603,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1510
1603
  };
1511
1604
  if (allActiveMedications.length > 0) {
1512
1605
  sortMedications(allActiveMedications);
1513
- html += this.renderCombinedMedications(templateUtilities, allActiveMedications, true);
1514
- }
1515
- if (allInactiveMedications.length > 0) {
1516
- sortMedications(allInactiveMedications);
1517
- html += this.renderCombinedMedications(templateUtilities, allInactiveMedications, false);
1606
+ html += this.renderCombinedMedications(templateUtilities, allActiveMedications);
1518
1607
  }
1519
1608
  return html;
1520
1609
  }
@@ -1552,12 +1641,10 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1552
1641
  * Render HTML table for combined MedicationRequest and MedicationStatement resources
1553
1642
  * @param templateUtilities - Instance of TemplateUtilities for utility functions
1554
1643
  * @param medications - Array of combined medication resources
1555
- * @param sectionTitle - Title for the section
1556
1644
  * @returns HTML string for rendering
1557
1645
  */
1558
- static renderCombinedMedications(templateUtilities, medications, isActiveSection) {
1646
+ static renderCombinedMedications(templateUtilities, medications) {
1559
1647
  let html = `
1560
- <h3>${isActiveSection ? "Active Medications" : "Inactive Medications"}</h3>
1561
1648
  <table>
1562
1649
  <thead>
1563
1650
  <tr>
@@ -1566,9 +1653,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1566
1653
  <th>Sig</th>
1567
1654
  <th>Dispense Quantity</th>
1568
1655
  <th>Refills</th>
1569
- <th>Start Date</th>${isActiveSection ? "" : `
1570
- <th>End Date</th>`}
1571
- <th>Status</th>
1656
+ <th>Start Date</th>
1572
1657
  </tr>
1573
1658
  </thead>
1574
1659
  <tbody>`;
@@ -1580,12 +1665,9 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1580
1665
  let dispenseQuantity = "-";
1581
1666
  let refills = "-";
1582
1667
  let startDate = "-";
1583
- let endDate = "-";
1584
- let status;
1585
1668
  if (medication.type === "request") {
1586
1669
  const mr = medication.resource;
1587
1670
  type = "Request";
1588
- status = mr.status ? String(mr.status) : "-";
1589
1671
  medicationName = templateUtilities.getMedicationName(
1590
1672
  mr.medicationReference || mr.medicationCodeableConcept
1591
1673
  );
@@ -1599,14 +1681,12 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1599
1681
  refills = mr.dispenseRequest?.numberOfRepeatsAllowed?.toString() || "-";
1600
1682
  if (mr.dispenseRequest?.validityPeriod) {
1601
1683
  startDate = mr.dispenseRequest.validityPeriod.start || "-";
1602
- endDate = mr.dispenseRequest.validityPeriod.end || "-";
1603
1684
  } else {
1604
1685
  startDate = mr.authoredOn || "-";
1605
1686
  }
1606
1687
  } else {
1607
1688
  const ms = medication.resource;
1608
1689
  type = "Statement";
1609
- status = ms.status ? String(ms.status) : "-";
1610
1690
  medicationName = templateUtilities.getMedicationName(
1611
1691
  ms.medicationReference || ms.medicationCodeableConcept
1612
1692
  );
@@ -1615,7 +1695,6 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1615
1695
  startDate = ms.effectiveDateTime;
1616
1696
  } else if (ms.effectivePeriod) {
1617
1697
  startDate = ms.effectivePeriod.start || "-";
1618
- endDate = ms.effectivePeriod.end || "-";
1619
1698
  }
1620
1699
  }
1621
1700
  html += `
@@ -1625,9 +1704,7 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
1625
1704
  <td>${sig}</td>
1626
1705
  <td>${dispenseQuantity}</td>
1627
1706
  <td>${refills}</td>
1628
- <td>${startDate}</td>${isActiveSection ? "" : `
1629
- <td>${endDate}</td>`}
1630
- <td>${status}</td>
1707
+ <td>${startDate}</td>
1631
1708
  </tr>`;
1632
1709
  }
1633
1710
  html += `
@@ -1768,6 +1845,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
1768
1845
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
1769
1846
  * @returns HTML string for rendering
1770
1847
  */
1848
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1771
1849
  static generateStaticNarrative(resources, timezone) {
1772
1850
  const templateUtilities = new TemplateUtilities(resources);
1773
1851
  let html = ``;
@@ -1784,7 +1862,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
1784
1862
  <th>Problem</th>
1785
1863
  <th>Onset Date</th>
1786
1864
  <th>Recorded Date</th>
1787
- <th>Notes</th>
1788
1865
  </tr>
1789
1866
  </thead>
1790
1867
  <tbody>`;
@@ -1793,7 +1870,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
1793
1870
  <td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
1794
1871
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
1795
1872
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
1796
- <td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
1797
1873
  </tr>`;
1798
1874
  }
1799
1875
  html += `</tbody>
@@ -1802,13 +1878,6 @@ var ProblemListTemplate = class _ProblemListTemplate {
1802
1878
  }
1803
1879
  };
1804
1880
 
1805
- // src/structures/ips_section_constants.ts
1806
- var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
1807
- "Systolic Blood Pressure": "valueRatio.numerator.value",
1808
- "Diastolic Blood Pressure": "valueRatio.denominator.value",
1809
- "Default": "valueString"
1810
- };
1811
-
1812
1881
  // src/narratives/templates/typescript/VitalSignsTemplate.ts
1813
1882
  var VitalSignsTemplate = class _VitalSignsTemplate {
1814
1883
  /**
@@ -2228,6 +2297,7 @@ var PastHistoryOfIllnessTemplate = class {
2228
2297
  * @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
2229
2298
  * @returns HTML string for rendering
2230
2299
  */
2300
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2231
2301
  generateNarrative(resources, timezone) {
2232
2302
  const templateUtilities = new TemplateUtilities(resources);
2233
2303
  let html = ``;
@@ -2245,7 +2315,6 @@ var PastHistoryOfIllnessTemplate = class {
2245
2315
  <th>Onset Date</th>
2246
2316
  <th>Recorded Date</th>
2247
2317
  <th>Resolved Date</th>
2248
- <th>Notes</th>
2249
2318
  </tr>
2250
2319
  </thead>
2251
2320
  <tbody>`;
@@ -2255,7 +2324,6 @@ var PastHistoryOfIllnessTemplate = class {
2255
2324
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2256
2325
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2257
2326
  <td class="ResolvedDate">${templateUtilities.renderDate(cond.abatementDateTime)}</td>
2258
- <td class="Notes">${templateUtilities.renderNotes(cond.note, timezone)}</td>
2259
2327
  </tr>`;
2260
2328
  }
2261
2329
  html += `</tbody>
@@ -2408,7 +2476,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2408
2476
  <th>Problem</th>
2409
2477
  <th>Onset Date</th>
2410
2478
  <th>Recorded Date</th>
2411
- <th>Notes</th>
2412
2479
  </tr>
2413
2480
  </thead>
2414
2481
  <tbody>`;
@@ -2417,7 +2484,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2417
2484
  <td class="Name">${templateUtilities.codeableConcept(cond.code)}</td>
2418
2485
  <td class="OnsetDate">${templateUtilities.renderDate(cond.onsetDateTime)}</td>
2419
2486
  <td class="RecordedDate">${templateUtilities.renderDate(cond.recordedDate)}</td>
2420
- <td class="Notes">${templateUtilities.renderNotes(cond.note, timezone, { styled: true, warning: true })}</td>
2421
2487
  </tr>`;
2422
2488
  }
2423
2489
  html += `</tbody>
@@ -2433,7 +2499,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2433
2499
  <th>Description</th>
2434
2500
  <th>Summary</th>
2435
2501
  <th>Findings</th>
2436
- <th>Notes</th>
2437
2502
  </tr>
2438
2503
  </thead>
2439
2504
  <tbody>`;
@@ -2462,7 +2527,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2462
2527
  }
2463
2528
  findingsHtml += "</ul>";
2464
2529
  }
2465
- const notes = templateUtilities.renderNotes(impression.note, timezone);
2466
2530
  html += `
2467
2531
  <tr id="${templateUtilities.narrativeLinkId(impression)}">
2468
2532
  <td>${formattedDate}</td>
@@ -2470,7 +2534,6 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
2470
2534
  <td>${impression.description || ""}</td>
2471
2535
  <td>${impression.summary || ""}</td>
2472
2536
  <td>${findingsHtml}</td>
2473
- <td>${notes}</td>
2474
2537
  </tr>`;
2475
2538
  }
2476
2539
  html += `</tbody>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imranq2/fhirpatientsummary",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "A template for creating npm packages using TypeScript and VSCode",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -28,7 +28,7 @@
28
28
  "prebuild": "npm run clean:dist",
29
29
  "pretest": "npm run clean:coverage",
30
30
  "cm": "cz",
31
- "lint": "eslint ./src/ --ext .ts --fix",
31
+ "lint": "eslint ./src/ ./tests/ --ext .ts --fix",
32
32
  "prepare": "husky",
33
33
  "semantic-release": "semantic-release",
34
34
  "test:watch": "jest --watch",
@@ -36,7 +36,8 @@
36
36
  "typecheck": "tsc --noEmit",
37
37
  "check:cjs-import": "node tests/build/cjs-import-check.cjs",
38
38
  "check:esm-import": "node --experimental-vm-modules tests/build/esm-import-check.mjs",
39
- "check:ts-import": "ts-node --project tsconfig.json tests/build/ts-import-check.ts"
39
+ "check:ts-import": "ts-node --project tsconfig.json tests/build/ts-import-check.ts",
40
+ "split:bundle": "node tests/full_record/split_bundle.cjs"
40
41
  },
41
42
  "repository": {
42
43
  "type": "git",
@@ -68,34 +69,34 @@
68
69
  "homepage": "https://github.com/icanbwell/fhir-patient-summary#readme",
69
70
  "dependencies": {
70
71
  "html-minifier-terser": "^7.2.0",
71
- "luxon": "^3.6.1",
72
- "turndown": "^7.2.0"
72
+ "luxon": "^3.7.2",
73
+ "turndown": "^7.2.2"
73
74
  },
74
75
  "devDependencies": {
75
- "@eslint/js": "^9.29.0",
76
+ "@eslint/js": "^9.39.1",
76
77
  "@types/html-minifier-terser": "^7.0.2",
77
78
  "@types/jest": "^30.0.0",
78
79
  "@types/js-beautify": "^1.14.3",
79
- "@types/luxon": "^3.6.2",
80
- "@types/node": "^24.0.3",
81
- "@types/turndown": "^5.0.5",
80
+ "@types/luxon": "^3.7.1",
81
+ "@types/node": "^24.10.1",
82
+ "@types/turndown": "^5.0.6",
82
83
  "commitizen": "^4.3.1",
83
- "conventional-changelog-conventionalcommits": "^8.0.0",
84
- "eslint": "^9.29.0",
85
- "eslint-config-prettier": "^10.1.5",
84
+ "conventional-changelog-conventionalcommits": "^9.1.0",
85
+ "eslint": "^9.39.1",
86
+ "eslint-config-prettier": "^10.1.8",
86
87
  "eslint-plugin-node": "^11.1.0",
87
- "eslint-plugin-prettier": "^5.5.0",
88
+ "eslint-plugin-prettier": "^5.5.4",
88
89
  "husky": "^9.1.7",
89
- "jest": "^30.0.0",
90
+ "jest": "^30.2.0",
90
91
  "js-beautify": "^1.15.4",
91
- "lint-staged": "^16.1.2",
92
- "prettier": "^3.5.3",
93
- "semantic-release": "^24.2.5",
94
- "ts-jest": "^29.4.0",
92
+ "lint-staged": "^16.2.7",
93
+ "prettier": "^3.7.4",
94
+ "semantic-release": "^25.0.2",
95
+ "ts-jest": "^29.4.6",
95
96
  "ts-node": "^10.9.2",
96
97
  "tsup": "8.5.1",
97
- "typescript": "^5.8.3",
98
- "typescript-eslint": "^8.34.1"
98
+ "typescript": "^5.9.3",
99
+ "typescript-eslint": "^8.48.1"
99
100
  },
100
101
  "lint-staged": {
101
102
  "src/**/*.{ts,js}": "eslint --cache --cache-location .eslintcache --fix",