@imranq2/fhirpatientsummary 1.0.26 → 1.0.28
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.
- package/dist/index.cjs +555 -158
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +555 -158
- package/package.json +8 -5
package/dist/index.cjs
CHANGED
|
@@ -115,6 +115,8 @@ var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
|
115
115
|
"Diastolic Blood Pressure": "valueRatio.denominator.value",
|
|
116
116
|
"Default": "valueString"
|
|
117
117
|
};
|
|
118
|
+
var RESULT_SUMMARY_OBSERVATION_CATEGORIES = ["laboratory", "Lab", "LAB"];
|
|
119
|
+
var RESULT_SUMMARY_OBSERVATION_DATE_FILTER = 2 * 365 * 24 * 60 * 60 * 1e3;
|
|
118
120
|
var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
|
|
119
121
|
|
|
120
122
|
// src/structures/ips_section_resource_map.ts
|
|
@@ -134,7 +136,7 @@ var IPSSectionResourceFilters = {
|
|
|
134
136
|
// Includes DeviceUseStatement. Device is needed for linked device details
|
|
135
137
|
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
|
|
136
138
|
// Only include finalized diagnostic reports
|
|
137
|
-
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) =>
|
|
139
|
+
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
|
|
138
140
|
// Only include completed procedures
|
|
139
141
|
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
|
|
140
142
|
// Only include social history Observations
|
|
@@ -156,7 +158,7 @@ var IPSSectionSummaryCompositionFilter = {
|
|
|
156
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"),
|
|
157
159
|
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
|
|
158
160
|
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
|
|
159
|
-
[
|
|
161
|
+
// [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)),
|
|
160
162
|
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
|
|
161
163
|
};
|
|
162
164
|
var IPSSectionResourceHelper = class {
|
|
@@ -246,7 +248,7 @@ var TemplateUtilities = class {
|
|
|
246
248
|
renderOrganization(orgRef) {
|
|
247
249
|
const organization = orgRef && this.resolveReference(orgRef);
|
|
248
250
|
if (organization && organization.resourceType === "Organization" && organization.name) {
|
|
249
|
-
return organization.name;
|
|
251
|
+
return this.renderTextAsHtml(organization.name);
|
|
250
252
|
}
|
|
251
253
|
return "";
|
|
252
254
|
}
|
|
@@ -258,7 +260,7 @@ var TemplateUtilities = class {
|
|
|
258
260
|
renderVaccineManufacturer(immunization) {
|
|
259
261
|
const organization = immunization.manufacturer && this.resolveReference(immunization.manufacturer);
|
|
260
262
|
if (organization && organization.resourceType === "Organization" && organization.name) {
|
|
261
|
-
return organization.name;
|
|
263
|
+
return this.renderTextAsHtml(organization.name);
|
|
262
264
|
}
|
|
263
265
|
return "";
|
|
264
266
|
}
|
|
@@ -297,7 +299,7 @@ var TemplateUtilities = class {
|
|
|
297
299
|
*/
|
|
298
300
|
renderMedicationCode(medication) {
|
|
299
301
|
if (medication && medication.code) {
|
|
300
|
-
return this.codeableConcept(medication.code, "display");
|
|
302
|
+
return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
|
|
301
303
|
}
|
|
302
304
|
return "";
|
|
303
305
|
}
|
|
@@ -308,7 +310,7 @@ var TemplateUtilities = class {
|
|
|
308
310
|
*/
|
|
309
311
|
renderDoseNumber(doseNumber) {
|
|
310
312
|
if (doseNumber && doseNumber.value !== void 0) {
|
|
311
|
-
return doseNumber.value.toString();
|
|
313
|
+
return this.renderTextAsHtml(doseNumber.value.toString());
|
|
312
314
|
}
|
|
313
315
|
return "";
|
|
314
316
|
}
|
|
@@ -319,7 +321,7 @@ var TemplateUtilities = class {
|
|
|
319
321
|
*/
|
|
320
322
|
renderValueUnit(value) {
|
|
321
323
|
if (value && value.constructor?.name === "Quantity" && value.unit) {
|
|
322
|
-
return value.unit;
|
|
324
|
+
return this.renderTextAsHtml(value.unit);
|
|
323
325
|
}
|
|
324
326
|
return "";
|
|
325
327
|
}
|
|
@@ -385,7 +387,7 @@ var TemplateUtilities = class {
|
|
|
385
387
|
* @returns Comma-separated string of items
|
|
386
388
|
*/
|
|
387
389
|
safeConcat(list, attr) {
|
|
388
|
-
return this.concat(list || [], attr);
|
|
390
|
+
return this.renderTextAsHtml(this.concat(list || [], attr));
|
|
389
391
|
}
|
|
390
392
|
/**
|
|
391
393
|
* Concatenates text from a list of CodeableConcept objects
|
|
@@ -402,7 +404,7 @@ var TemplateUtilities = class {
|
|
|
402
404
|
items.push(item.text);
|
|
403
405
|
}
|
|
404
406
|
}
|
|
405
|
-
return items.join(", ");
|
|
407
|
+
return this.renderTextAsHtml(items.join(", "));
|
|
406
408
|
}
|
|
407
409
|
/**
|
|
408
410
|
* Concatenates reaction manifestations
|
|
@@ -423,7 +425,7 @@ var TemplateUtilities = class {
|
|
|
423
425
|
}
|
|
424
426
|
}
|
|
425
427
|
}
|
|
426
|
-
return texts.join(", ");
|
|
428
|
+
return this.renderTextAsHtml(texts.join(", "));
|
|
427
429
|
}
|
|
428
430
|
/**
|
|
429
431
|
* Concatenates dose numbers
|
|
@@ -440,7 +442,7 @@ var TemplateUtilities = class {
|
|
|
440
442
|
doseNumbers.push(item.doseNumberPositiveInt.toString());
|
|
441
443
|
}
|
|
442
444
|
}
|
|
443
|
-
return doseNumbers.join(", ");
|
|
445
|
+
return this.renderTextAsHtml(doseNumbers.join(", "));
|
|
444
446
|
}
|
|
445
447
|
/**
|
|
446
448
|
* Concatenates dosage routes
|
|
@@ -457,7 +459,7 @@ var TemplateUtilities = class {
|
|
|
457
459
|
routes.push(item.route.text);
|
|
458
460
|
}
|
|
459
461
|
}
|
|
460
|
-
return routes.join(", ");
|
|
462
|
+
return this.renderTextAsHtml(routes.join(", "));
|
|
461
463
|
}
|
|
462
464
|
/**
|
|
463
465
|
* Returns the first item from a list of CodeableConcept objects
|
|
@@ -466,7 +468,7 @@ var TemplateUtilities = class {
|
|
|
466
468
|
*/
|
|
467
469
|
firstFromCodeableConceptList(list) {
|
|
468
470
|
if (list && Array.isArray(list) && list[0]) {
|
|
469
|
-
return this.codeableConcept(list[0], "display");
|
|
471
|
+
return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
|
|
470
472
|
}
|
|
471
473
|
return "";
|
|
472
474
|
}
|
|
@@ -485,7 +487,7 @@ var TemplateUtilities = class {
|
|
|
485
487
|
texts.push(item.text);
|
|
486
488
|
}
|
|
487
489
|
}
|
|
488
|
-
return texts.join(", ");
|
|
490
|
+
return this.renderTextAsHtml(texts.join(", "));
|
|
489
491
|
}
|
|
490
492
|
/**
|
|
491
493
|
* Renders component codes
|
|
@@ -620,9 +622,18 @@ var TemplateUtilities = class {
|
|
|
620
622
|
}
|
|
621
623
|
return status;
|
|
622
624
|
}
|
|
625
|
+
formatFloatValue(value) {
|
|
626
|
+
if (typeof value === "number") {
|
|
627
|
+
return value.toFixed(2).replace(/\.?0+$/, "");
|
|
628
|
+
} else if (typeof value === "string" && !isNaN(Number(value))) {
|
|
629
|
+
return parseFloat(value).toFixed(2).replace(/\.?0+$/, "");
|
|
630
|
+
}
|
|
631
|
+
return value;
|
|
632
|
+
}
|
|
623
633
|
extractObservationSummaryValue(data, timezone) {
|
|
624
634
|
if (data["valueQuantity.value"] !== void 0) {
|
|
625
|
-
|
|
635
|
+
let value = data["valueQuantity.value"];
|
|
636
|
+
value = this.formatFloatValue(value);
|
|
626
637
|
const unit = data["valueQuantity.unit"];
|
|
627
638
|
return unit ? `${value} ${unit}` : `${value}`;
|
|
628
639
|
}
|
|
@@ -639,7 +650,9 @@ var TemplateUtilities = class {
|
|
|
639
650
|
return String(data["valueBoolean"]);
|
|
640
651
|
}
|
|
641
652
|
if (data["valueInteger"] !== void 0) {
|
|
642
|
-
|
|
653
|
+
let value = String(data["valueInteger"]);
|
|
654
|
+
value = this.formatFloatValue(value);
|
|
655
|
+
return value;
|
|
643
656
|
}
|
|
644
657
|
if (data["valueDateTime"] !== void 0) {
|
|
645
658
|
return this.renderTime(data["valueDateTime"], timezone);
|
|
@@ -659,7 +672,8 @@ var TemplateUtilities = class {
|
|
|
659
672
|
return this.renderTime(data["valueTime"], timezone);
|
|
660
673
|
}
|
|
661
674
|
if (data["valueSampledData.origin.value"] !== void 0 || data["valueSampledData.origin.unit"] !== void 0) {
|
|
662
|
-
|
|
675
|
+
let originValue = data["valueSampledData.origin.value"];
|
|
676
|
+
originValue = this.formatFloatValue(originValue);
|
|
663
677
|
const originUnit = data["valueSampledData.origin.unit"];
|
|
664
678
|
let result = "";
|
|
665
679
|
if (originValue !== void 0 && originUnit !== void 0) {
|
|
@@ -669,10 +683,10 @@ var TemplateUtilities = class {
|
|
|
669
683
|
} else if (originUnit !== void 0) {
|
|
670
684
|
result = `${originUnit}`;
|
|
671
685
|
}
|
|
672
|
-
const period = data["valueSampledData.period"];
|
|
673
|
-
const factor = data["valueSampledData.factor"];
|
|
674
|
-
const lowerLimit = data["valueSampledData.lowerLimit"];
|
|
675
|
-
const upperLimit = data["valueSampledData.upperLimit"];
|
|
686
|
+
const period = this.formatFloatValue(data["valueSampledData.period"]);
|
|
687
|
+
const factor = this.formatFloatValue(data["valueSampledData.factor"]);
|
|
688
|
+
const lowerLimit = this.formatFloatValue(data["valueSampledData.lowerLimit"]);
|
|
689
|
+
const upperLimit = this.formatFloatValue(data["valueSampledData.upperLimit"]);
|
|
676
690
|
const sampledData = data["valueSampledData.data"];
|
|
677
691
|
const extras = [];
|
|
678
692
|
if (period !== void 0) extras.push(`period: ${period}`);
|
|
@@ -708,14 +722,14 @@ var TemplateUtilities = class {
|
|
|
708
722
|
if (data["valueRatio.numerator.value"] !== void 0 || data["valueRatio.denominator.value"] !== void 0) {
|
|
709
723
|
let ratio = "";
|
|
710
724
|
if (data["valueRatio.numerator.value"] !== void 0) {
|
|
711
|
-
ratio += `${data["valueRatio.numerator.value"]}`;
|
|
725
|
+
ratio += `${this.formatFloatValue(data["valueRatio.numerator.value"])}`;
|
|
712
726
|
if (data["valueRatio.numerator.unit"] !== void 0) {
|
|
713
727
|
ratio += ` ${data["valueRatio.numerator.unit"]}`;
|
|
714
728
|
}
|
|
715
729
|
}
|
|
716
730
|
if (data["valueRatio.denominator.value"] !== void 0) {
|
|
717
731
|
ratio += " / ";
|
|
718
|
-
ratio += `${data["valueRatio.denominator.value"]}`;
|
|
732
|
+
ratio += `${this.formatFloatValue(data["valueRatio.denominator.value"])}`;
|
|
719
733
|
if (data["valueRatio.denominator.unit"] !== void 0) {
|
|
720
734
|
ratio += ` ${data["valueRatio.denominator.unit"]}`;
|
|
721
735
|
}
|
|
@@ -805,9 +819,11 @@ var TemplateUtilities = class {
|
|
|
805
819
|
return "";
|
|
806
820
|
}
|
|
807
821
|
/**
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
822
|
+
* Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
|
|
823
|
+
* This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
|
|
824
|
+
* and to preserve formatting. Use this in templates or UI components that need to safely display multi-line or arbitrary text.
|
|
825
|
+
* @param text - The text to render as HTML
|
|
826
|
+
* @returns The HTML-safe string with newlines converted to <br />
|
|
811
827
|
*/
|
|
812
828
|
renderTextAsHtml(text) {
|
|
813
829
|
if (!text || text.trim() === "") {
|
|
@@ -871,7 +887,7 @@ var TemplateUtilities = class {
|
|
|
871
887
|
dateTime = import_luxon.DateTime.fromISO(String(dateValue));
|
|
872
888
|
}
|
|
873
889
|
if (!dateTime.isValid) {
|
|
874
|
-
return String(dateValue);
|
|
890
|
+
return this.renderTextAsHtml(String(dateValue));
|
|
875
891
|
}
|
|
876
892
|
if (dateOnly) {
|
|
877
893
|
dateTime = dateTime.toUTC();
|
|
@@ -887,9 +903,9 @@ var TemplateUtilities = class {
|
|
|
887
903
|
hour12: true,
|
|
888
904
|
timeZoneName: "short"
|
|
889
905
|
};
|
|
890
|
-
return dateTime.toLocaleString(formatOptions);
|
|
906
|
+
return this.renderTextAsHtml(dateTime.toLocaleString(formatOptions));
|
|
891
907
|
} catch {
|
|
892
|
-
return String(dateValue);
|
|
908
|
+
return this.renderTextAsHtml(String(dateValue));
|
|
893
909
|
}
|
|
894
910
|
}
|
|
895
911
|
/**
|
|
@@ -969,6 +985,313 @@ var TemplateUtilities = class {
|
|
|
969
985
|
|
|
970
986
|
// src/constants.ts
|
|
971
987
|
var ADDRESS_SIMILARITY_THRESHOLD = 70;
|
|
988
|
+
var LAB_LOINC_MAP = {
|
|
989
|
+
// METABOLIC PANELS (VERY COMMONLY ORDERED)
|
|
990
|
+
"Basic Metabolic Panel": [
|
|
991
|
+
"24321-2",
|
|
992
|
+
// Basic metabolic 2000 panel - Serum or Plasma
|
|
993
|
+
"51990-0"
|
|
994
|
+
// Basic metabolic 2000 panel - Blood
|
|
995
|
+
],
|
|
996
|
+
"Comprehensive Metabolic Panel": [
|
|
997
|
+
"24323-8"
|
|
998
|
+
// Comprehensive metabolic 2000 panel - Serum or Plasma
|
|
999
|
+
],
|
|
1000
|
+
// CBC COMPONENTS
|
|
1001
|
+
Hemoglobin: [
|
|
1002
|
+
"718-7"
|
|
1003
|
+
// Hemoglobin [Mass/volume] in Blood
|
|
1004
|
+
],
|
|
1005
|
+
Hematocrit: [
|
|
1006
|
+
"4544-3"
|
|
1007
|
+
// Hematocrit [Volume Fraction] of Blood by Automated count
|
|
1008
|
+
],
|
|
1009
|
+
"White Blood Cell Count": [
|
|
1010
|
+
"6690-2"
|
|
1011
|
+
// Leukocytes [///volume] in Blood by Automated count
|
|
1012
|
+
],
|
|
1013
|
+
"Platelet Count": [
|
|
1014
|
+
"777-3"
|
|
1015
|
+
// Platelets [///volume] in Blood by Automated count
|
|
1016
|
+
],
|
|
1017
|
+
"Complete Blood Count": [
|
|
1018
|
+
"58410-2",
|
|
1019
|
+
// CBC panel - Blood by Automated count
|
|
1020
|
+
"57021-8",
|
|
1021
|
+
// CBC W Auto Differential panel - Blood
|
|
1022
|
+
"69738-3"
|
|
1023
|
+
// CBC W Auto Differential panel - Blood by Automated count
|
|
1024
|
+
],
|
|
1025
|
+
// CHEMISTRY - GLUCOSE
|
|
1026
|
+
Glucose: [
|
|
1027
|
+
"2345-7",
|
|
1028
|
+
// Glucose [Mass/volume] in Serum or Plasma
|
|
1029
|
+
"1558-6",
|
|
1030
|
+
// Fasting glucose [Mass/volume] in Serum or Plasma
|
|
1031
|
+
"2339-0"
|
|
1032
|
+
// Glucose [Mass/volume] in Blood
|
|
1033
|
+
],
|
|
1034
|
+
// RENAL FUNCTION
|
|
1035
|
+
Creatinine: [
|
|
1036
|
+
"2160-0"
|
|
1037
|
+
// Creatinine [Mass/volume] in Serum or Plasma
|
|
1038
|
+
],
|
|
1039
|
+
"Blood Urea Nitrogen": [
|
|
1040
|
+
"3094-0",
|
|
1041
|
+
// Urea nitrogen [Mass/volume] in Serum or Plasma
|
|
1042
|
+
"6299-2"
|
|
1043
|
+
// Urea nitrogen [Mass/volume] in Blood
|
|
1044
|
+
],
|
|
1045
|
+
// ELECTROLYTES
|
|
1046
|
+
Sodium: [
|
|
1047
|
+
"2951-2"
|
|
1048
|
+
// Sodium [Moles/volume] in Serum or Plasma
|
|
1049
|
+
],
|
|
1050
|
+
Potassium: [
|
|
1051
|
+
"2823-3"
|
|
1052
|
+
// Potassium [Moles/volume] in Serum or Plasma
|
|
1053
|
+
],
|
|
1054
|
+
Chloride: [
|
|
1055
|
+
"2075-0"
|
|
1056
|
+
// Chloride [Moles/volume] in Serum or Plasma
|
|
1057
|
+
],
|
|
1058
|
+
Calcium: [
|
|
1059
|
+
"17861-6",
|
|
1060
|
+
// Calcium [Mass/volume] in Serum or Plasma
|
|
1061
|
+
"1994-3"
|
|
1062
|
+
// Calcium.ionized [Moles/volume] in Serum or Plasma
|
|
1063
|
+
],
|
|
1064
|
+
Magnesium: [
|
|
1065
|
+
"19123-9"
|
|
1066
|
+
// Magnesium [Mass/volume] in Serum or Plasma
|
|
1067
|
+
],
|
|
1068
|
+
Phosphate: [
|
|
1069
|
+
"14879-1"
|
|
1070
|
+
// Phosphate [Mass/volume] in Serum or Plasma
|
|
1071
|
+
],
|
|
1072
|
+
// PROTEINS
|
|
1073
|
+
Albumin: [
|
|
1074
|
+
"1751-7"
|
|
1075
|
+
// Albumin [Mass/volume] in Serum or Plasma
|
|
1076
|
+
],
|
|
1077
|
+
"Total Protein": [
|
|
1078
|
+
"2885-2"
|
|
1079
|
+
// Protein [Mass/volume] in Serum or Plasma
|
|
1080
|
+
],
|
|
1081
|
+
// LIVER FUNCTION
|
|
1082
|
+
Bilirubin: [
|
|
1083
|
+
"1975-2",
|
|
1084
|
+
// Bilirubin.total [Mass/volume] in Serum or Plasma
|
|
1085
|
+
"1968-7",
|
|
1086
|
+
// Bilirubin.direct [Mass/volume] in Serum or Plasma
|
|
1087
|
+
"1971-1"
|
|
1088
|
+
// Bilirubin.indirect [Mass/volume] in Serum or Plasma
|
|
1089
|
+
],
|
|
1090
|
+
"Alkaline Phosphatase": [
|
|
1091
|
+
"6768-6"
|
|
1092
|
+
// Alkaline phosphatase [Enzymatic activity/volume] in Serum or Plasma
|
|
1093
|
+
],
|
|
1094
|
+
AST: [
|
|
1095
|
+
"1920-8"
|
|
1096
|
+
// Aspartate aminotransferase [Enzymatic activity/volume] in Serum or Plasma
|
|
1097
|
+
],
|
|
1098
|
+
ALT: [
|
|
1099
|
+
"1742-6"
|
|
1100
|
+
// Alanine aminotransferase [Enzymatic activity/volume] in Serum or Plasma
|
|
1101
|
+
],
|
|
1102
|
+
GGT: [
|
|
1103
|
+
"2324-2"
|
|
1104
|
+
// Gamma glutamyl transferase [Enzymatic activity/volume] in Serum or Plasma
|
|
1105
|
+
],
|
|
1106
|
+
// ENDOCRINE
|
|
1107
|
+
TSH: [
|
|
1108
|
+
"3016-3"
|
|
1109
|
+
// Thyrotropin [Units/volume] in Serum or Plasma
|
|
1110
|
+
],
|
|
1111
|
+
"Free T4": [
|
|
1112
|
+
"3024-7"
|
|
1113
|
+
// Thyroxine (T4) free [Mass/volume] in Serum or Plasma
|
|
1114
|
+
],
|
|
1115
|
+
"Total T4": [
|
|
1116
|
+
"3026-2"
|
|
1117
|
+
// Thyroxine (T4) [Mass/volume] in Serum or Plasma
|
|
1118
|
+
],
|
|
1119
|
+
"Free T3": [
|
|
1120
|
+
"3051-0"
|
|
1121
|
+
// Triiodothyronine (T3) free [Mass/volume] in Serum or Plasma
|
|
1122
|
+
],
|
|
1123
|
+
"Total T3": [
|
|
1124
|
+
"3053-6"
|
|
1125
|
+
// Triiodothyronine (T3) [Mass/volume] in Serum or Plasma
|
|
1126
|
+
],
|
|
1127
|
+
HbA1c: [
|
|
1128
|
+
"4548-4",
|
|
1129
|
+
// Hemoglobin A1c/Hemoglobin.total in Blood
|
|
1130
|
+
"17856-6"
|
|
1131
|
+
// Hemoglobin A1c/Hemoglobin.total in Blood by HPLC
|
|
1132
|
+
],
|
|
1133
|
+
// LIPID PANEL
|
|
1134
|
+
"Lipid Panel": [
|
|
1135
|
+
"24331-1",
|
|
1136
|
+
// Lipid 1996 panel - Serum or Plasma
|
|
1137
|
+
"57698-3"
|
|
1138
|
+
// Lipid panel with direct LDL - Serum or Plasma
|
|
1139
|
+
],
|
|
1140
|
+
"Cholesterol Total": [
|
|
1141
|
+
"2093-3"
|
|
1142
|
+
// Cholesterol [Mass/volume] in Serum or Plasma
|
|
1143
|
+
],
|
|
1144
|
+
"HDL Cholesterol": [
|
|
1145
|
+
"2085-9"
|
|
1146
|
+
// Cholesterol in HDL [Mass/volume] in Serum or Plasma
|
|
1147
|
+
],
|
|
1148
|
+
"LDL Cholesterol": [
|
|
1149
|
+
"13457-7",
|
|
1150
|
+
// Cholesterol in LDL [Mass/volume] in Serum or Plasma by calculation
|
|
1151
|
+
"18262-6"
|
|
1152
|
+
// Cholesterol in LDL [Mass/volume] in Serum or Plasma by Direct assay
|
|
1153
|
+
],
|
|
1154
|
+
Triglycerides: [
|
|
1155
|
+
"2571-8"
|
|
1156
|
+
// Triglyceride [Mass/volume] in Serum or Plasma
|
|
1157
|
+
],
|
|
1158
|
+
// COAGULATION STUDIES (COMMONLY MISSING!)
|
|
1159
|
+
PT: [
|
|
1160
|
+
"5902-2"
|
|
1161
|
+
// Prothrombin time (PT)
|
|
1162
|
+
],
|
|
1163
|
+
INR: [
|
|
1164
|
+
"6301-6"
|
|
1165
|
+
// INR in Platelet poor plasma by Coagulation assay
|
|
1166
|
+
],
|
|
1167
|
+
PTT: [
|
|
1168
|
+
"3173-2",
|
|
1169
|
+
// aPTT in Blood by Coagulation assay
|
|
1170
|
+
"14979-9"
|
|
1171
|
+
// aPTT in Platelet poor plasma by Coagulation assay
|
|
1172
|
+
],
|
|
1173
|
+
Fibrinogen: [
|
|
1174
|
+
"3255-7"
|
|
1175
|
+
// Fibrinogen [Mass/volume] in Platelet poor plasma by Coagulation assay
|
|
1176
|
+
],
|
|
1177
|
+
"D-Dimer": [
|
|
1178
|
+
"48065-7",
|
|
1179
|
+
// D-dimer FEU [Mass/volume] in Platelet poor plasma
|
|
1180
|
+
"48066-5"
|
|
1181
|
+
// D-dimer DDU [Mass/volume] in Platelet poor plasma
|
|
1182
|
+
],
|
|
1183
|
+
// CARDIAC MARKERS (CRITICAL FOR ER!)
|
|
1184
|
+
"Troponin I": [
|
|
1185
|
+
"10839-9",
|
|
1186
|
+
// Troponin I.cardiac [Mass/volume] in Serum or Plasma
|
|
1187
|
+
"42757-5",
|
|
1188
|
+
// Troponin I.cardiac [Mass/volume] in Blood
|
|
1189
|
+
"89579-7"
|
|
1190
|
+
// Troponin I.cardiac [Mass/volume] in Serum or Plasma by High sensitivity method
|
|
1191
|
+
],
|
|
1192
|
+
"Troponin T": [
|
|
1193
|
+
"6598-7",
|
|
1194
|
+
// Troponin T.cardiac [Mass/volume] in Serum or Plasma
|
|
1195
|
+
"48425-3"
|
|
1196
|
+
// Troponin T.cardiac [Mass/volume] in Serum or Plasma by High sensitivity method
|
|
1197
|
+
],
|
|
1198
|
+
BNP: [
|
|
1199
|
+
"30934-4"
|
|
1200
|
+
// BNP [Mass/volume] in Serum or Plasma
|
|
1201
|
+
],
|
|
1202
|
+
"NT-proBNP": [
|
|
1203
|
+
"33762-6"
|
|
1204
|
+
// NT-proBNP [Mass/volume] in Serum or Plasma
|
|
1205
|
+
],
|
|
1206
|
+
"CK-MB": [
|
|
1207
|
+
"13969-1"
|
|
1208
|
+
// Creatine kinase.MB [Mass/volume] in Serum or Plasma
|
|
1209
|
+
],
|
|
1210
|
+
// INFLAMMATORY MARKERS
|
|
1211
|
+
CRP: [
|
|
1212
|
+
"1988-5",
|
|
1213
|
+
// C reactive protein [Mass/volume] in Serum or Plasma
|
|
1214
|
+
"30522-7"
|
|
1215
|
+
// C reactive protein [Mass/volume] in Serum or Plasma by High sensitivity method
|
|
1216
|
+
],
|
|
1217
|
+
ESR: [
|
|
1218
|
+
"30341-2",
|
|
1219
|
+
// Erythrocyte sedimentation rate by Westergren method
|
|
1220
|
+
"4537-7"
|
|
1221
|
+
// Erythrocyte sedimentation rate
|
|
1222
|
+
],
|
|
1223
|
+
// VITAMINS & MINERALS (VERY HIGH VOLUME!)
|
|
1224
|
+
"Vitamin D": [
|
|
1225
|
+
"1990-1",
|
|
1226
|
+
// Vitamin D [Mass/volume] in Serum or Plasma (obsolete, but still used)
|
|
1227
|
+
"14635-7",
|
|
1228
|
+
// 25-Hydroxyvitamin D3 [Mass/volume] in Serum or Plasma
|
|
1229
|
+
"62292-8"
|
|
1230
|
+
// 25-Hydroxyvitamin D2+D3 [Mass/volume] in Serum or Plasma
|
|
1231
|
+
],
|
|
1232
|
+
"Vitamin B12": [
|
|
1233
|
+
"2132-9"
|
|
1234
|
+
// Cobalamin (Vitamin B12) [Mass/volume] in Serum or Plasma
|
|
1235
|
+
],
|
|
1236
|
+
Folate: [
|
|
1237
|
+
"2284-8",
|
|
1238
|
+
// Folate [Mass/volume] in Serum or Plasma
|
|
1239
|
+
"15152-2"
|
|
1240
|
+
// Folate [Mass/volume] in Red Blood Cells
|
|
1241
|
+
],
|
|
1242
|
+
Iron: [
|
|
1243
|
+
"2498-4"
|
|
1244
|
+
// Iron [Mass/volume] in Serum or Plasma
|
|
1245
|
+
],
|
|
1246
|
+
Ferritin: [
|
|
1247
|
+
"2276-4"
|
|
1248
|
+
// Ferritin [Mass/volume] in Serum or Plasma
|
|
1249
|
+
],
|
|
1250
|
+
TIBC: [
|
|
1251
|
+
"2500-7"
|
|
1252
|
+
// Iron binding capacity [Mass/volume] in Serum or Plasma
|
|
1253
|
+
],
|
|
1254
|
+
// OTHER COMMON TESTS
|
|
1255
|
+
PSA: [
|
|
1256
|
+
"2857-1",
|
|
1257
|
+
// Prostate specific Ag [Mass/volume] in Serum or Plasma
|
|
1258
|
+
"10886-0"
|
|
1259
|
+
// Prostate specific Ag Free [Mass/volume] in Serum or Plasma
|
|
1260
|
+
],
|
|
1261
|
+
"Uric Acid": [
|
|
1262
|
+
"3084-1"
|
|
1263
|
+
// Urate [Mass/volume] in Serum or Plasma
|
|
1264
|
+
],
|
|
1265
|
+
LDH: [
|
|
1266
|
+
"2532-0",
|
|
1267
|
+
// Lactate dehydrogenase [Enzymatic activity/volume] in Serum or Plasma
|
|
1268
|
+
"14804-9"
|
|
1269
|
+
// Lactate dehydrogenase [Enzymatic activity/volume] in Serum or Plasma by Lactate to pyruvate reaction
|
|
1270
|
+
],
|
|
1271
|
+
Amylase: [
|
|
1272
|
+
"1798-8"
|
|
1273
|
+
// Amylase [Enzymatic activity/volume] in Serum or Plasma
|
|
1274
|
+
],
|
|
1275
|
+
Lipase: [
|
|
1276
|
+
"3040-3"
|
|
1277
|
+
// Lipase [Enzymatic activity/volume] in Serum or Plasma
|
|
1278
|
+
],
|
|
1279
|
+
hCG: [
|
|
1280
|
+
"21198-7",
|
|
1281
|
+
// Choriogonadotropin.beta subunit [Units/volume] in Serum or Plasma
|
|
1282
|
+
"2118-8",
|
|
1283
|
+
// Choriogonadotropin (pregnancy test) [Presence] in Serum or Plasma
|
|
1284
|
+
"2106-3"
|
|
1285
|
+
// Choriogonadotropin (pregnancy test) [Presence] in Urine
|
|
1286
|
+
],
|
|
1287
|
+
// URINALYSIS
|
|
1288
|
+
Urinalysis: [
|
|
1289
|
+
"24357-6",
|
|
1290
|
+
// Urinalysis macro (dipstick) panel - Urine
|
|
1291
|
+
"24356-8"
|
|
1292
|
+
// Urinalysis complete panel - Urine
|
|
1293
|
+
]
|
|
1294
|
+
};
|
|
972
1295
|
|
|
973
1296
|
// src/narratives/templates/typescript/PatientTemplate.ts
|
|
974
1297
|
var PatientTemplate = class _PatientTemplate {
|
|
@@ -1286,7 +1609,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1286
1609
|
const uniqueLanguages = /* @__PURE__ */ new Set();
|
|
1287
1610
|
const preferredLanguages = /* @__PURE__ */ new Set();
|
|
1288
1611
|
patient.communication.forEach((comm) => {
|
|
1289
|
-
const language = templateUtilities.codeableConcept(comm.language);
|
|
1612
|
+
const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
|
|
1290
1613
|
if (language) {
|
|
1291
1614
|
if (comm.preferred) {
|
|
1292
1615
|
preferredLanguages.add(language);
|
|
@@ -1344,13 +1667,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1344
1667
|
for (const columnData of rowData.section ?? []) {
|
|
1345
1668
|
switch (columnData.title) {
|
|
1346
1669
|
case "Allergen Name":
|
|
1347
|
-
data["allergen"] = columnData.text?.div ?? "";
|
|
1670
|
+
data["allergen"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1348
1671
|
break;
|
|
1349
1672
|
case "Criticality":
|
|
1350
|
-
data["criticality"] = columnData.text?.div ?? "";
|
|
1673
|
+
data["criticality"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1351
1674
|
break;
|
|
1352
1675
|
case "Recorded Date":
|
|
1353
|
-
data["recordedDate"] = columnData.text?.div ?? "";
|
|
1676
|
+
data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1354
1677
|
break;
|
|
1355
1678
|
default:
|
|
1356
1679
|
break;
|
|
@@ -1471,11 +1794,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1471
1794
|
for (const allergy of allergies) {
|
|
1472
1795
|
html += `
|
|
1473
1796
|
<tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
|
|
1474
|
-
<td class="Name"><span class="AllergenName">${templateUtilities.codeableConcept(allergy.code)}</span></td>
|
|
1475
|
-
<td class="Status">${templateUtilities.codeableConcept(allergy.clinicalStatus) || "-"}</td>
|
|
1476
|
-
<td class="Category">${templateUtilities.safeConcat(allergy.category) || "-"}</td>
|
|
1477
|
-
<td class="Reaction">${templateUtilities.concatReactionManifestation(allergy.reaction) || "-"}</td>
|
|
1478
|
-
<td class="OnsetDate">${templateUtilities.renderTime(allergy.onsetDateTime, timezone) || "-"}</td>
|
|
1797
|
+
<td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
|
|
1798
|
+
<td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
|
|
1799
|
+
<td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
|
|
1800
|
+
<td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
|
|
1801
|
+
<td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
|
|
1479
1802
|
<td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
|
|
1480
1803
|
if (includeResolved) {
|
|
1481
1804
|
let endDate = "-";
|
|
@@ -1488,7 +1811,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1488
1811
|
}
|
|
1489
1812
|
}
|
|
1490
1813
|
html += `
|
|
1491
|
-
<td class="ResolvedDate">${endDate}</td>`;
|
|
1814
|
+
<td class="ResolvedDate">${templateUtilities.renderTextAsHtml(endDate)}</td>`;
|
|
1492
1815
|
}
|
|
1493
1816
|
html += `</tr>`;
|
|
1494
1817
|
}
|
|
@@ -1511,17 +1834,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1511
1834
|
* Generate HTML narrative for Medication resources using summary
|
|
1512
1835
|
* @param resources - FHIR Composition resources
|
|
1513
1836
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1837
|
+
* @param now - Optional current date to use for calculations (defaults to new Date())
|
|
1514
1838
|
* @returns HTML string for rendering
|
|
1515
1839
|
*/
|
|
1516
|
-
generateSummaryNarrative(resources, timezone) {
|
|
1840
|
+
generateSummaryNarrative(resources, timezone, now) {
|
|
1517
1841
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1518
1842
|
let isSummaryCreated = false;
|
|
1843
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
1844
|
+
const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
|
|
1519
1845
|
let html = `
|
|
1520
1846
|
<div>
|
|
1521
1847
|
<table>
|
|
1522
1848
|
<thead>
|
|
1523
1849
|
<tr>
|
|
1524
1850
|
<th>Medication</th>
|
|
1851
|
+
<th>Status</th>
|
|
1525
1852
|
<th>Sig</th>
|
|
1526
1853
|
<th>Days of Supply</th>
|
|
1527
1854
|
<th>Refills</th>
|
|
@@ -1535,40 +1862,48 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1535
1862
|
for (const columnData of rowData.section ?? []) {
|
|
1536
1863
|
switch (columnData.title) {
|
|
1537
1864
|
case "Medication Name":
|
|
1538
|
-
data["medication"] = columnData.text?.div ?? "";
|
|
1865
|
+
data["medication"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1539
1866
|
break;
|
|
1540
1867
|
case "Status":
|
|
1541
|
-
data["status"] = columnData.text?.div ?? "";
|
|
1868
|
+
data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1542
1869
|
break;
|
|
1543
1870
|
case "Prescriber Instruction":
|
|
1544
|
-
data["sig-prescriber"] = columnData.text?.div ?? "";
|
|
1871
|
+
data["sig-prescriber"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1545
1872
|
break;
|
|
1546
1873
|
case "Pharmacy Instruction":
|
|
1547
|
-
data["sig-pharmacy"] = columnData.text?.div ?? "";
|
|
1874
|
+
data["sig-pharmacy"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1548
1875
|
break;
|
|
1549
1876
|
case "Days Of Supply":
|
|
1550
|
-
data["daysOfSupply"] = columnData.text?.div ?? "";
|
|
1877
|
+
data["daysOfSupply"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1551
1878
|
break;
|
|
1552
1879
|
case "Refills Remaining":
|
|
1553
|
-
data["refills"] = columnData.text?.div ?? "";
|
|
1880
|
+
data["refills"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1554
1881
|
break;
|
|
1555
1882
|
case "Authored On Date":
|
|
1556
|
-
data["startDate"] = columnData.text?.div ?? "";
|
|
1883
|
+
data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1557
1884
|
break;
|
|
1558
1885
|
default:
|
|
1559
1886
|
break;
|
|
1560
1887
|
}
|
|
1561
1888
|
}
|
|
1562
|
-
|
|
1889
|
+
let startDateObj;
|
|
1890
|
+
if (data["startDate"]) {
|
|
1891
|
+
startDateObj = new Date(data["startDate"]);
|
|
1892
|
+
if (isNaN(startDateObj.getTime())) {
|
|
1893
|
+
startDateObj = void 0;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
if (data["status"] === "active" || startDateObj && startDateObj >= twelveMonthsAgo) {
|
|
1563
1897
|
isSummaryCreated = true;
|
|
1564
1898
|
html += `
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1899
|
+
<tr>
|
|
1900
|
+
<td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
|
|
1901
|
+
<td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
|
|
1902
|
+
<td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
|
|
1903
|
+
<td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
|
|
1904
|
+
<td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
|
|
1905
|
+
<td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
|
|
1906
|
+
</tr>`;
|
|
1572
1907
|
}
|
|
1573
1908
|
}
|
|
1574
1909
|
}
|
|
@@ -1790,13 +2125,13 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
1790
2125
|
for (const columnData of rowData.section ?? []) {
|
|
1791
2126
|
switch (columnData.title) {
|
|
1792
2127
|
case "Immunization Name":
|
|
1793
|
-
data["immunization"] = columnData.text?.div ?? "";
|
|
2128
|
+
data["immunization"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1794
2129
|
break;
|
|
1795
2130
|
case "Status":
|
|
1796
|
-
data["status"] = columnData.text?.div ?? "";
|
|
2131
|
+
data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1797
2132
|
break;
|
|
1798
2133
|
case "occurrenceDateTime":
|
|
1799
|
-
data["occurrenceDateTime"] = columnData.text?.div ?? "";
|
|
2134
|
+
data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1800
2135
|
break;
|
|
1801
2136
|
default:
|
|
1802
2137
|
break;
|
|
@@ -1847,7 +2182,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
1847
2182
|
const imm = resourceItem;
|
|
1848
2183
|
html += `
|
|
1849
2184
|
<tr id="${templateUtilities.narrativeLinkId(imm)}">
|
|
1850
|
-
<td>${templateUtilities.codeableConcept(imm.vaccineCode)}</td>
|
|
2185
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
|
|
1851
2186
|
<td>${imm.status || ""}</td>
|
|
1852
2187
|
<td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
|
|
1853
2188
|
<td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
|
|
@@ -1903,7 +2238,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1903
2238
|
<tbody>`;
|
|
1904
2239
|
const addedConditionCodes = /* @__PURE__ */ new Set();
|
|
1905
2240
|
for (const cond of activeConditions) {
|
|
1906
|
-
const conditionCode = templateUtilities.codeableConcept(cond.code);
|
|
2241
|
+
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
|
|
1907
2242
|
if (!addedConditionCodes.has(conditionCode)) {
|
|
1908
2243
|
addedConditionCodes.add(conditionCode);
|
|
1909
2244
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
@@ -1963,7 +2298,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
1963
2298
|
const vitalData = {};
|
|
1964
2299
|
for (const component of columnData.section?.[0]?.section ?? []) {
|
|
1965
2300
|
if (component.title) {
|
|
1966
|
-
vitalData[component.title] = component.text?.div ?? "";
|
|
2301
|
+
vitalData[component.title] = templateUtilities.renderTextAsHtml(component.text?.div ?? "");
|
|
1967
2302
|
}
|
|
1968
2303
|
}
|
|
1969
2304
|
const vitalValue = templateUtilities.extractObservationSummaryValue(
|
|
@@ -1975,7 +2310,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
1975
2310
|
data[dataKey] = vitalValue;
|
|
1976
2311
|
}
|
|
1977
2312
|
}
|
|
1978
|
-
data[columnTitle] = columnData.text?.div ?? "";
|
|
2313
|
+
data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1979
2314
|
}
|
|
1980
2315
|
}
|
|
1981
2316
|
isSummaryCreated = true;
|
|
@@ -2025,7 +2360,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
2025
2360
|
for (const obs of observations) {
|
|
2026
2361
|
html += `
|
|
2027
2362
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2028
|
-
<td>${templateUtilities.codeableConcept(obs.code, "display")}</td>
|
|
2363
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
|
|
2029
2364
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2030
2365
|
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
2031
2366
|
<td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
|
|
@@ -2079,10 +2414,10 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2079
2414
|
for (const dus of deviceStatements) {
|
|
2080
2415
|
html += `
|
|
2081
2416
|
<tr id="${templateUtilities.narrativeLinkId(dus)}">
|
|
2082
|
-
<td>${templateUtilities.renderDevice(dus.device)}</td>
|
|
2083
|
-
<td>${dus.status || ""}</td>
|
|
2417
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
|
|
2418
|
+
<td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
|
|
2084
2419
|
<td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
|
|
2085
|
-
<td>${templateUtilities.renderRecorded(dus.recordedOn, timezone)}</td>
|
|
2420
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.renderRecorded(dus.recordedOn, timezone))}</td>
|
|
2086
2421
|
</tr>`;
|
|
2087
2422
|
}
|
|
2088
2423
|
html += `
|
|
@@ -2093,6 +2428,12 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2093
2428
|
};
|
|
2094
2429
|
|
|
2095
2430
|
// src/narratives/templates/typescript/DiagnosticResultsTemplate.ts
|
|
2431
|
+
var loincToLabName = {};
|
|
2432
|
+
for (const [labName, loincCodes] of Object.entries(LAB_LOINC_MAP)) {
|
|
2433
|
+
for (const code of loincCodes) {
|
|
2434
|
+
loincToLabName[code] = labName;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2096
2437
|
var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
2097
2438
|
/**
|
|
2098
2439
|
* Generate HTML narrative for Diagnostic Results
|
|
@@ -2212,149 +2553,150 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2212
2553
|
* Helper function to extract observation field data
|
|
2213
2554
|
* @param column - Column data from the summary
|
|
2214
2555
|
* @param targetData - Record to populate with extracted data
|
|
2556
|
+
* @param templateUtilities - Instance of TemplateUtilities for utility functions
|
|
2215
2557
|
*/
|
|
2216
|
-
extractSummaryObservationFields(column, targetData) {
|
|
2558
|
+
extractSummaryObservationFields(column, targetData, templateUtilities) {
|
|
2217
2559
|
switch (column.title) {
|
|
2218
2560
|
case "Labs Name":
|
|
2219
|
-
targetData["code"] = column.text?.div ?? "";
|
|
2561
|
+
targetData["code"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2220
2562
|
break;
|
|
2221
2563
|
case "effectiveDateTime":
|
|
2222
|
-
targetData["effectiveDateTime"] = column.text?.div ?? "";
|
|
2564
|
+
targetData["effectiveDateTime"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2223
2565
|
break;
|
|
2224
2566
|
case "effectivePeriod.start":
|
|
2225
|
-
targetData["effectivePeriodStart"] = column.text?.div ?? "";
|
|
2567
|
+
targetData["effectivePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2226
2568
|
break;
|
|
2227
2569
|
case "effectivePeriod.end":
|
|
2228
|
-
targetData["effectivePeriodEnd"] = column.text?.div ?? "";
|
|
2570
|
+
targetData["effectivePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2229
2571
|
break;
|
|
2230
2572
|
// valueQuantity
|
|
2231
2573
|
case "valueQuantity.value":
|
|
2232
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2574
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2233
2575
|
targetData["valueType"] = "valueQuantity";
|
|
2234
2576
|
break;
|
|
2235
2577
|
case "valueQuantity.unit":
|
|
2236
|
-
targetData["unit"] = column.text?.div ?? "";
|
|
2578
|
+
targetData["unit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2237
2579
|
break;
|
|
2238
2580
|
// valueCodeableConcept
|
|
2239
2581
|
case "valueCodeableConcept.text":
|
|
2240
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2582
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2241
2583
|
targetData["valueType"] = "valueCodeableConcept";
|
|
2242
2584
|
break;
|
|
2243
2585
|
case "valueCodeableConcept.coding.display":
|
|
2244
2586
|
if (!targetData["value"]) {
|
|
2245
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2587
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2246
2588
|
targetData["valueType"] = "valueCodeableConcept";
|
|
2247
2589
|
}
|
|
2248
2590
|
break;
|
|
2249
2591
|
// valueString
|
|
2250
2592
|
case "valueString":
|
|
2251
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2593
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2252
2594
|
targetData["valueType"] = "valueString";
|
|
2253
2595
|
break;
|
|
2254
2596
|
// valueBoolean
|
|
2255
2597
|
case "valueBoolean":
|
|
2256
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2598
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2257
2599
|
targetData["valueType"] = "valueBoolean";
|
|
2258
2600
|
break;
|
|
2259
2601
|
// valueInteger
|
|
2260
2602
|
case "valueInteger":
|
|
2261
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2603
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2262
2604
|
targetData["valueType"] = "valueInteger";
|
|
2263
2605
|
break;
|
|
2264
2606
|
// valueDateTime
|
|
2265
2607
|
case "valueDateTime":
|
|
2266
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2608
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2267
2609
|
targetData["valueType"] = "valueDateTime";
|
|
2268
2610
|
break;
|
|
2269
2611
|
// valuePeriod
|
|
2270
2612
|
case "valuePeriod.start":
|
|
2271
|
-
targetData["valuePeriodStart"] = column.text?.div ?? "";
|
|
2613
|
+
targetData["valuePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2272
2614
|
targetData["valueType"] = "valuePeriod";
|
|
2273
2615
|
break;
|
|
2274
2616
|
case "valuePeriod.end":
|
|
2275
|
-
targetData["valuePeriodEnd"] = column.text?.div ?? "";
|
|
2617
|
+
targetData["valuePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2276
2618
|
targetData["valueType"] = "valuePeriod";
|
|
2277
2619
|
break;
|
|
2278
2620
|
// valueTime
|
|
2279
2621
|
case "valueTime":
|
|
2280
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2622
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2281
2623
|
targetData["valueType"] = "valueTime";
|
|
2282
2624
|
break;
|
|
2283
2625
|
// valueSampledData
|
|
2284
2626
|
case "valueSampledData.origin.value":
|
|
2285
|
-
targetData["sampledDataOriginValue"] = column.text?.div ?? "";
|
|
2627
|
+
targetData["sampledDataOriginValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2286
2628
|
targetData["valueType"] = "valueSampledData";
|
|
2287
2629
|
break;
|
|
2288
2630
|
case "valueSampledData.origin.unit":
|
|
2289
|
-
targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
|
|
2631
|
+
targetData["sampledDataOriginUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2290
2632
|
break;
|
|
2291
2633
|
case "valueSampledData.period":
|
|
2292
|
-
targetData["sampledDataPeriod"] = column.text?.div ?? "";
|
|
2634
|
+
targetData["sampledDataPeriod"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2293
2635
|
break;
|
|
2294
2636
|
case "valueSampledData.factor":
|
|
2295
|
-
targetData["sampledDataFactor"] = column.text?.div ?? "";
|
|
2637
|
+
targetData["sampledDataFactor"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2296
2638
|
break;
|
|
2297
2639
|
case "valueSampledData.lowerLimit":
|
|
2298
|
-
targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
|
|
2640
|
+
targetData["sampledDataLowerLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2299
2641
|
break;
|
|
2300
2642
|
case "valueSampledData.upperLimit":
|
|
2301
|
-
targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
|
|
2643
|
+
targetData["sampledDataUpperLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2302
2644
|
break;
|
|
2303
2645
|
case "valueSampledData.data":
|
|
2304
|
-
targetData["sampledDataData"] = column.text?.div ?? "";
|
|
2646
|
+
targetData["sampledDataData"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2305
2647
|
break;
|
|
2306
2648
|
// valueRange
|
|
2307
2649
|
case "valueRange.low.value":
|
|
2308
|
-
targetData["valueRangeLowValue"] = column.text?.div ?? "";
|
|
2650
|
+
targetData["valueRangeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2309
2651
|
targetData["valueType"] = "valueRange";
|
|
2310
2652
|
break;
|
|
2311
2653
|
case "valueRange.low.unit":
|
|
2312
|
-
targetData["valueRangeLowUnit"] = column.text?.div ?? "";
|
|
2654
|
+
targetData["valueRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2313
2655
|
break;
|
|
2314
2656
|
case "valueRange.high.value":
|
|
2315
|
-
targetData["valueRangeHighValue"] = column.text?.div ?? "";
|
|
2657
|
+
targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2316
2658
|
break;
|
|
2317
2659
|
case "valueRange.high.unit":
|
|
2318
|
-
targetData["valueRangeHighUnit"] = column.text?.div ?? "";
|
|
2660
|
+
targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2319
2661
|
break;
|
|
2320
2662
|
// valueRatio
|
|
2321
2663
|
case "valueRatio.numerator.value":
|
|
2322
|
-
targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
|
|
2664
|
+
targetData["valueRatioNumeratorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2323
2665
|
targetData["valueType"] = "valueRatio";
|
|
2324
2666
|
break;
|
|
2325
2667
|
case "valueRatio.numerator.unit":
|
|
2326
|
-
targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
|
|
2668
|
+
targetData["valueRatioNumeratorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2327
2669
|
break;
|
|
2328
2670
|
case "valueRatio.denominator.value":
|
|
2329
|
-
targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
|
|
2671
|
+
targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2330
2672
|
break;
|
|
2331
2673
|
case "valueRatio.denominator.unit":
|
|
2332
|
-
targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
|
|
2674
|
+
targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2333
2675
|
break;
|
|
2334
2676
|
// referenceRange
|
|
2335
2677
|
case "referenceRange.low.value":
|
|
2336
|
-
targetData["referenceRangeLow"] = column.text?.div ?? "";
|
|
2678
|
+
targetData["referenceRangeLow"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2337
2679
|
break;
|
|
2338
2680
|
case "referenceRange.low.unit":
|
|
2339
|
-
targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
|
|
2681
|
+
targetData["referenceRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2340
2682
|
break;
|
|
2341
2683
|
case "referenceRange.high.value":
|
|
2342
|
-
targetData["referenceRangeHigh"] = column.text?.div ?? "";
|
|
2684
|
+
targetData["referenceRangeHigh"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2343
2685
|
break;
|
|
2344
2686
|
case "referenceRange.high.unit":
|
|
2345
|
-
targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
|
|
2687
|
+
targetData["referenceRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2346
2688
|
break;
|
|
2347
2689
|
case "referenceRange.age.low.value":
|
|
2348
|
-
targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
|
|
2690
|
+
targetData["referenceRangeAgeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2349
2691
|
break;
|
|
2350
2692
|
case "referenceRange.age.low.unit":
|
|
2351
|
-
targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
|
|
2693
|
+
targetData["referenceRangeAgeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2352
2694
|
break;
|
|
2353
2695
|
case "referenceRange.age.high.value":
|
|
2354
|
-
targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
|
|
2696
|
+
targetData["referenceRangeAgeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2355
2697
|
break;
|
|
2356
2698
|
case "referenceRange.age.high.unit":
|
|
2357
|
-
targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
|
|
2699
|
+
targetData["referenceRangeAgeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2358
2700
|
break;
|
|
2359
2701
|
default:
|
|
2360
2702
|
break;
|
|
@@ -2407,28 +2749,28 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2407
2749
|
for (const componentSection of columnData.section) {
|
|
2408
2750
|
const componentData = {};
|
|
2409
2751
|
for (const nestedColumn of componentSection.section ?? []) {
|
|
2410
|
-
this.extractSummaryObservationFields(nestedColumn, componentData);
|
|
2752
|
+
this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
|
|
2411
2753
|
}
|
|
2412
2754
|
if (Object.keys(componentData).length > 0) {
|
|
2413
2755
|
components.push(componentData);
|
|
2414
2756
|
}
|
|
2415
2757
|
}
|
|
2416
2758
|
} else {
|
|
2417
|
-
this.extractSummaryObservationFields(columnData, data);
|
|
2759
|
+
this.extractSummaryObservationFields(columnData, data, templateUtilities);
|
|
2418
2760
|
}
|
|
2419
2761
|
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2420
2762
|
switch (columnData.title) {
|
|
2421
2763
|
case "Diagnostic Report Name":
|
|
2422
|
-
data["report"] = columnData.text?.div ?? "";
|
|
2764
|
+
data["report"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2423
2765
|
break;
|
|
2424
2766
|
case "Performer":
|
|
2425
|
-
data["performer"] = columnData.text?.div ?? "";
|
|
2767
|
+
data["performer"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2426
2768
|
break;
|
|
2427
2769
|
case "Issued Date":
|
|
2428
|
-
data["issued"] = columnData.text?.div ?? "";
|
|
2770
|
+
data["issued"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2429
2771
|
break;
|
|
2430
2772
|
case "Status":
|
|
2431
|
-
data["status"] = columnData.text?.div ?? "";
|
|
2773
|
+
data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2432
2774
|
break;
|
|
2433
2775
|
default:
|
|
2434
2776
|
break;
|
|
@@ -2453,8 +2795,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2453
2795
|
observationhtml += `
|
|
2454
2796
|
<tr>
|
|
2455
2797
|
<td>${componentCode}</td>
|
|
2456
|
-
<td>${component["formattedValue"] ?? "-"}</td>
|
|
2457
|
-
<td>${component["referenceRange"]?.trim() ?? "-"}</td>
|
|
2798
|
+
<td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
|
|
2799
|
+
<td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
|
|
2458
2800
|
<td>${date ?? "-"}</td>
|
|
2459
2801
|
</tr>`;
|
|
2460
2802
|
}
|
|
@@ -2467,8 +2809,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2467
2809
|
observationhtml += `
|
|
2468
2810
|
<tr>
|
|
2469
2811
|
<td>${data["code"] ?? "-"}</td>
|
|
2470
|
-
<td>${data["formattedValue"] ?? "-"}</td>
|
|
2471
|
-
<td>${data["referenceRange"]?.trim() ?? "-"}</td>
|
|
2812
|
+
<td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
|
|
2813
|
+
<td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
|
|
2472
2814
|
<td>${date ?? "-"}</td>
|
|
2473
2815
|
</tr>`;
|
|
2474
2816
|
}
|
|
@@ -2519,30 +2861,80 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2519
2861
|
const observations = this.getObservations(resources);
|
|
2520
2862
|
if (observations.length > 0) {
|
|
2521
2863
|
observations.sort((a, b) => {
|
|
2522
|
-
const dateA =
|
|
2523
|
-
const dateB =
|
|
2524
|
-
return dateA && dateB ?
|
|
2864
|
+
const dateA = this.getObservationDate(a);
|
|
2865
|
+
const dateB = this.getObservationDate(b);
|
|
2866
|
+
return dateA && dateB ? dateB.getTime() - dateA.getTime() : 0;
|
|
2525
2867
|
});
|
|
2868
|
+
this.filterObservationForLoincCodes(observations);
|
|
2526
2869
|
html += this.renderObservations(templateUtilities, observations, timezone);
|
|
2527
2870
|
}
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
diagnosticReports.
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2871
|
+
if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
|
|
2872
|
+
const diagnosticReports = this.getDiagnosticReports(resources);
|
|
2873
|
+
if (diagnosticReports.length > 0) {
|
|
2874
|
+
diagnosticReports.sort((a, b) => {
|
|
2875
|
+
const dateA = a.issued;
|
|
2876
|
+
const dateB = b.issued;
|
|
2877
|
+
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
2878
|
+
});
|
|
2879
|
+
html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
|
|
2880
|
+
}
|
|
2536
2881
|
}
|
|
2537
2882
|
return html;
|
|
2538
2883
|
}
|
|
2884
|
+
static filterObservationForLoincCodes(observations) {
|
|
2885
|
+
const labsAdded = /* @__PURE__ */ new Set();
|
|
2886
|
+
const filteredObservations = [];
|
|
2887
|
+
for (const obs of observations) {
|
|
2888
|
+
const loincCode = this.getObservationLoincCode(obs);
|
|
2889
|
+
if (loincCode && loincToLabName[loincCode]) {
|
|
2890
|
+
const labName = loincToLabName[loincCode];
|
|
2891
|
+
if (!labsAdded.has(labName)) {
|
|
2892
|
+
labsAdded.add(labName);
|
|
2893
|
+
filteredObservations.push(obs);
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
observations.length = 0;
|
|
2898
|
+
observations.push(...filteredObservations);
|
|
2899
|
+
}
|
|
2900
|
+
static getObservationLoincCode(obs) {
|
|
2901
|
+
if (obs.code && obs.code.coding) {
|
|
2902
|
+
for (const coding of obs.code.coding) {
|
|
2903
|
+
if (coding.system && coding.system.toLowerCase().includes("loinc") && coding.code) {
|
|
2904
|
+
return coding.code;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
return void 0;
|
|
2909
|
+
}
|
|
2910
|
+
static getObservationDate(obs) {
|
|
2911
|
+
let obsDate = void 0;
|
|
2912
|
+
if (obs.effectiveDateTime) {
|
|
2913
|
+
obsDate = new Date(obs.effectiveDateTime);
|
|
2914
|
+
} else if (obs.effectivePeriod) {
|
|
2915
|
+
if (obs.effectivePeriod.start) {
|
|
2916
|
+
obsDate = new Date(obs.effectivePeriod.start);
|
|
2917
|
+
} else if (obs.effectivePeriod.end) {
|
|
2918
|
+
obsDate = new Date(obs.effectivePeriod.end);
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
return obsDate;
|
|
2922
|
+
}
|
|
2539
2923
|
/**
|
|
2540
2924
|
* Get all Observation resources from the resource array
|
|
2541
2925
|
* @param resources - FHIR resources array
|
|
2542
2926
|
* @returns Array of Observation resources
|
|
2543
2927
|
*/
|
|
2544
2928
|
static getObservations(resources) {
|
|
2545
|
-
return resources.filter((resourceItem) =>
|
|
2929
|
+
return resources.filter((resourceItem) => {
|
|
2930
|
+
if (resourceItem.resourceType === "Observation") {
|
|
2931
|
+
const obsDate = this.getObservationDate(resourceItem);
|
|
2932
|
+
if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
|
|
2933
|
+
return true;
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
return false;
|
|
2937
|
+
}).map((resourceItem) => resourceItem);
|
|
2546
2938
|
}
|
|
2547
2939
|
/**
|
|
2548
2940
|
* Get all DiagnosticReport resources from the resource array
|
|
@@ -2560,34 +2952,32 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2560
2952
|
* @returns HTML string for rendering
|
|
2561
2953
|
*/
|
|
2562
2954
|
static renderObservations(templateUtilities, observations, timezone) {
|
|
2563
|
-
let html =
|
|
2564
|
-
|
|
2955
|
+
let html = "";
|
|
2956
|
+
if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
|
|
2957
|
+
html += `
|
|
2958
|
+
<h3>Observations</h3>`;
|
|
2959
|
+
}
|
|
2960
|
+
html += `
|
|
2565
2961
|
<table>
|
|
2566
2962
|
<thead>
|
|
2567
2963
|
<tr>
|
|
2568
2964
|
<th>Code</th>
|
|
2569
2965
|
<th>Result</th>
|
|
2570
|
-
<th>Unit</th>
|
|
2571
|
-
<th>Interpretation</th>
|
|
2572
2966
|
<th>Reference Range</th>
|
|
2573
|
-
<th>Comments</th>
|
|
2574
2967
|
<th>Date</th>
|
|
2575
2968
|
</tr>
|
|
2576
2969
|
</thead>
|
|
2577
2970
|
<tbody>`;
|
|
2578
2971
|
const observationAdded = /* @__PURE__ */ new Set();
|
|
2579
2972
|
for (const obs of observations) {
|
|
2580
|
-
const obsCode = templateUtilities.codeableConcept(obs.code);
|
|
2973
|
+
const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
|
|
2581
2974
|
if (!observationAdded.has(obsCode)) {
|
|
2582
2975
|
observationAdded.add(obsCode);
|
|
2583
2976
|
html += `
|
|
2584
2977
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2585
2978
|
<td>${obsCode}</td>
|
|
2586
2979
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2587
|
-
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
2588
|
-
<td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
|
|
2589
2980
|
<td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
|
|
2590
|
-
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
2591
2981
|
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
2592
2982
|
</tr>`;
|
|
2593
2983
|
}
|
|
@@ -2619,7 +3009,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2619
3009
|
<tbody>`;
|
|
2620
3010
|
const diagnosticReportAdded = /* @__PURE__ */ new Set();
|
|
2621
3011
|
for (const report of reports) {
|
|
2622
|
-
const reportName = templateUtilities.codeableConcept(report.code);
|
|
3012
|
+
const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
|
|
2623
3013
|
if (!diagnosticReportAdded.has(reportName)) {
|
|
2624
3014
|
diagnosticReportAdded.add(reportName);
|
|
2625
3015
|
let resultCount = "";
|
|
@@ -2733,7 +3123,7 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
2733
3123
|
const proc = resourceItem;
|
|
2734
3124
|
html += `
|
|
2735
3125
|
<tr id="${templateUtilities.narrativeLinkId(proc)}">
|
|
2736
|
-
<td>${templateUtilities.codeableConcept(proc.code, "display")}</td>
|
|
3126
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
|
|
2737
3127
|
<td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
|
|
2738
3128
|
<td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
|
|
2739
3129
|
</tr>`;
|
|
@@ -2785,7 +3175,7 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
|
|
|
2785
3175
|
for (const obs of observations) {
|
|
2786
3176
|
html += `
|
|
2787
3177
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2788
|
-
<td>${templateUtilities.codeableConcept(obs.code)}</td>
|
|
3178
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
|
|
2789
3179
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2790
3180
|
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
2791
3181
|
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
@@ -2830,7 +3220,7 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
2830
3220
|
<tbody>`;
|
|
2831
3221
|
const addedConditionCodes = /* @__PURE__ */ new Set();
|
|
2832
3222
|
for (const cond of resolvedConditions) {
|
|
2833
|
-
const conditionCode = templateUtilities.codeableConcept(cond.code);
|
|
3223
|
+
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
|
|
2834
3224
|
if (!addedConditionCodes.has(conditionCode)) {
|
|
2835
3225
|
addedConditionCodes.add(conditionCode);
|
|
2836
3226
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
@@ -2916,7 +3306,7 @@ var PlanOfCareTemplate = class {
|
|
|
2916
3306
|
const data = {};
|
|
2917
3307
|
for (const columnData of rowData.section ?? []) {
|
|
2918
3308
|
if (columnData.title) {
|
|
2919
|
-
data[columnData.title] = columnData.text?.div ?? "";
|
|
3309
|
+
data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2920
3310
|
}
|
|
2921
3311
|
}
|
|
2922
3312
|
if (data["status"] !== "active") {
|
|
@@ -2992,7 +3382,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2992
3382
|
<tbody>`;
|
|
2993
3383
|
const addedConditionCodes = /* @__PURE__ */ new Set();
|
|
2994
3384
|
for (const cond of activeConditions) {
|
|
2995
|
-
const conditionCode = templateUtilities.codeableConcept(cond.code);
|
|
3385
|
+
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
|
|
2996
3386
|
if (!addedConditionCodes.has(conditionCode)) {
|
|
2997
3387
|
addedConditionCodes.add(conditionCode);
|
|
2998
3388
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
@@ -3037,7 +3427,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
3037
3427
|
if (impression.finding && impression.finding.length > 0) {
|
|
3038
3428
|
findingsHtml = "<ul>";
|
|
3039
3429
|
for (const finding of impression.finding) {
|
|
3040
|
-
const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConcept(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
|
|
3430
|
+
const findingText = finding.itemCodeableConcept ? templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(finding.itemCodeableConcept)) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
|
|
3041
3431
|
const cause = finding.basis || "";
|
|
3042
3432
|
findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
|
|
3043
3433
|
}
|
|
@@ -3098,9 +3488,9 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
3098
3488
|
const obs = resource;
|
|
3099
3489
|
html += `
|
|
3100
3490
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
3101
|
-
<td>${templateUtilities.extractPregnancyStatus(obs)}</td>
|
|
3491
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
|
|
3102
3492
|
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
3103
|
-
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
3493
|
+
<td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
|
|
3104
3494
|
</tr>`;
|
|
3105
3495
|
}
|
|
3106
3496
|
html += `
|
|
@@ -3150,7 +3540,7 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
3150
3540
|
const consent = resourceItem;
|
|
3151
3541
|
html += `
|
|
3152
3542
|
<tr id="${templateUtilities.narrativeLinkId(consent)}">
|
|
3153
|
-
<td>${templateUtilities.codeableConcept(consent.scope, "display")}</td>
|
|
3543
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
|
|
3154
3544
|
<td>${consent.status || ""}</td>
|
|
3155
3545
|
<td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
|
|
3156
3546
|
<td>${consent.dateTime || ""}</td>
|
|
@@ -3171,16 +3561,18 @@ var TypeScriptTemplateMapper = class {
|
|
|
3171
3561
|
* @param resources - FHIR resources
|
|
3172
3562
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3173
3563
|
* @param useSectionSummary - Whether to use the section summary for narrative generation
|
|
3564
|
+
* @param now - Optional current date to use for generating relative dates in the narrative
|
|
3174
3565
|
* @returns HTML string for rendering
|
|
3175
3566
|
*/
|
|
3176
|
-
static generateNarrative(section, resources, timezone, useSectionSummary = false) {
|
|
3567
|
+
static generateNarrative(section, resources, timezone, useSectionSummary = false, now) {
|
|
3177
3568
|
const templateClass = this.sectionToTemplate[section];
|
|
3178
3569
|
if (!templateClass) {
|
|
3179
3570
|
throw new Error(`No template found for section: ${section}`);
|
|
3180
3571
|
}
|
|
3181
3572
|
return useSectionSummary ? templateClass.generateSummaryNarrative(
|
|
3182
3573
|
resources,
|
|
3183
|
-
timezone
|
|
3574
|
+
timezone,
|
|
3575
|
+
now
|
|
3184
3576
|
) : templateClass.generateNarrative(resources, timezone);
|
|
3185
3577
|
}
|
|
3186
3578
|
};
|
|
@@ -3238,14 +3630,15 @@ var NarrativeGenerator = class {
|
|
|
3238
3630
|
* @param resources - Array of domain resources
|
|
3239
3631
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3240
3632
|
* @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
|
|
3633
|
+
* @param now - Optional date parameter
|
|
3241
3634
|
* @returns Generated HTML content or undefined if no resources
|
|
3242
3635
|
*/
|
|
3243
|
-
static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false) {
|
|
3636
|
+
static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false, now) {
|
|
3244
3637
|
if (!resources || resources.length === 0) {
|
|
3245
3638
|
return void 0;
|
|
3246
3639
|
}
|
|
3247
3640
|
try {
|
|
3248
|
-
const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary);
|
|
3641
|
+
const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary, now);
|
|
3249
3642
|
if (!content) {
|
|
3250
3643
|
return void 0;
|
|
3251
3644
|
}
|
|
@@ -3267,8 +3660,8 @@ var NarrativeGenerator = class {
|
|
|
3267
3660
|
const options = aggressive ? AGGRESSIVE_MINIFY_OPTIONS : DEFAULT_MINIFY_OPTIONS;
|
|
3268
3661
|
return await (0, import_html_minifier_terser.minify)(html, options);
|
|
3269
3662
|
} catch (error) {
|
|
3270
|
-
console.warn("HTML minification failed", error);
|
|
3271
|
-
return
|
|
3663
|
+
console.warn("HTML minification failed", error, html);
|
|
3664
|
+
return `${error instanceof Error ? error.message : String(error)}`;
|
|
3272
3665
|
}
|
|
3273
3666
|
}
|
|
3274
3667
|
/**
|
|
@@ -3297,10 +3690,11 @@ var NarrativeGenerator = class {
|
|
|
3297
3690
|
* @param timezone - Optional timezone to use for date formatting
|
|
3298
3691
|
* @param minify - Whether to minify the HTML content (default: true)
|
|
3299
3692
|
* @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
|
|
3693
|
+
* @param now - Optional date parameter
|
|
3300
3694
|
* @returns Promise that resolves to a FHIR Narrative object or undefined if no resources
|
|
3301
3695
|
*/
|
|
3302
|
-
static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false) {
|
|
3303
|
-
const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary);
|
|
3696
|
+
static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false, now) {
|
|
3697
|
+
const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary, now);
|
|
3304
3698
|
if (!content) {
|
|
3305
3699
|
return void 0;
|
|
3306
3700
|
}
|
|
@@ -3477,8 +3871,9 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
3477
3871
|
* @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
|
|
3478
3872
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3479
3873
|
* @param patientId - Optional patient ID to use as primary patient for composition reference
|
|
3874
|
+
* @param now - Optional current date to use for composition date (defaults to new Date())
|
|
3480
3875
|
*/
|
|
3481
|
-
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId) {
|
|
3876
|
+
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
|
|
3482
3877
|
if (baseUrl.endsWith("/")) {
|
|
3483
3878
|
baseUrl = baseUrl.slice(0, -1);
|
|
3484
3879
|
}
|
|
@@ -3505,20 +3900,22 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
3505
3900
|
// Assuming patient is also a practitioner for simplicity
|
|
3506
3901
|
display: authorOrganizationName
|
|
3507
3902
|
}],
|
|
3508
|
-
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3903
|
+
date: (now || /* @__PURE__ */ new Date()).toISOString(),
|
|
3509
3904
|
title: "International Patient Summary",
|
|
3510
3905
|
section: this.sections,
|
|
3511
3906
|
text: await NarrativeGenerator.generateNarrativeAsync(
|
|
3512
3907
|
"Patient" /* PATIENT */,
|
|
3513
3908
|
this.patients,
|
|
3514
3909
|
timezone,
|
|
3515
|
-
true
|
|
3910
|
+
true,
|
|
3911
|
+
false,
|
|
3912
|
+
now
|
|
3516
3913
|
)
|
|
3517
3914
|
};
|
|
3518
3915
|
const bundle = {
|
|
3519
3916
|
resourceType: "Bundle",
|
|
3520
3917
|
type: "document",
|
|
3521
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3918
|
+
timestamp: (now || /* @__PURE__ */ new Date()).toISOString(),
|
|
3522
3919
|
identifier: {
|
|
3523
3920
|
"system": "urn:ietf:rfc:3986",
|
|
3524
3921
|
"value": "urn:uuid:4dcfd353-49fd-4ab0-b521-c8d57ced74d6"
|