@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.js
CHANGED
|
@@ -87,6 +87,8 @@ var VITAL_SIGNS_SUMMARY_COMPONENT_MAP = {
|
|
|
87
87
|
"Diastolic Blood Pressure": "valueRatio.denominator.value",
|
|
88
88
|
"Default": "valueString"
|
|
89
89
|
};
|
|
90
|
+
var RESULT_SUMMARY_OBSERVATION_CATEGORIES = ["laboratory", "Lab", "LAB"];
|
|
91
|
+
var RESULT_SUMMARY_OBSERVATION_DATE_FILTER = 2 * 365 * 24 * 60 * 60 * 1e3;
|
|
90
92
|
var IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM = "https://fhir.icanbwell.com/4_0_0/CodeSystem/composition/";
|
|
91
93
|
|
|
92
94
|
// src/structures/ips_section_resource_map.ts
|
|
@@ -106,7 +108,7 @@ var IPSSectionResourceFilters = {
|
|
|
106
108
|
// Includes DeviceUseStatement. Device is needed for linked device details
|
|
107
109
|
["MedicalDeviceSection" /* MEDICAL_DEVICES */]: (resource) => ["DeviceUseStatement", "Device"].includes(resource.resourceType),
|
|
108
110
|
// Only include finalized diagnostic reports
|
|
109
|
-
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) =>
|
|
111
|
+
["ResultsSection" /* DIAGNOSTIC_REPORTS */]: (resource) => resource.resourceType === "Observation" && resource.category?.some((cat) => cat.coding?.some((c) => RESULT_SUMMARY_OBSERVATION_CATEGORIES.includes(c.code))),
|
|
110
112
|
// Only include completed procedures
|
|
111
113
|
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Procedure" && resource.status === "completed",
|
|
112
114
|
// Only include social history Observations
|
|
@@ -128,7 +130,7 @@ var IPSSectionSummaryCompositionFilter = {
|
|
|
128
130
|
["PlanOfCareSection" /* CARE_PLAN */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "careplan_summary_document"),
|
|
129
131
|
["ImmunizationSection" /* IMMUNIZATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "immunization_summary_document"),
|
|
130
132
|
["MedicationSummarySection" /* MEDICATIONS */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "medication_summary_document"),
|
|
131
|
-
[
|
|
133
|
+
// [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)),
|
|
132
134
|
["HistoryOfProceduresSection" /* PROCEDURES */]: (resource) => resource.resourceType === "Composition" && resource.type?.coding?.some((c) => c.system === IPS_SUMMARY_COMPOSITION_TYPE_SYSTEM && c.code === "procedure_summary_document")
|
|
133
135
|
};
|
|
134
136
|
var IPSSectionResourceHelper = class {
|
|
@@ -218,7 +220,7 @@ var TemplateUtilities = class {
|
|
|
218
220
|
renderOrganization(orgRef) {
|
|
219
221
|
const organization = orgRef && this.resolveReference(orgRef);
|
|
220
222
|
if (organization && organization.resourceType === "Organization" && organization.name) {
|
|
221
|
-
return organization.name;
|
|
223
|
+
return this.renderTextAsHtml(organization.name);
|
|
222
224
|
}
|
|
223
225
|
return "";
|
|
224
226
|
}
|
|
@@ -230,7 +232,7 @@ var TemplateUtilities = class {
|
|
|
230
232
|
renderVaccineManufacturer(immunization) {
|
|
231
233
|
const organization = immunization.manufacturer && this.resolveReference(immunization.manufacturer);
|
|
232
234
|
if (organization && organization.resourceType === "Organization" && organization.name) {
|
|
233
|
-
return organization.name;
|
|
235
|
+
return this.renderTextAsHtml(organization.name);
|
|
234
236
|
}
|
|
235
237
|
return "";
|
|
236
238
|
}
|
|
@@ -269,7 +271,7 @@ var TemplateUtilities = class {
|
|
|
269
271
|
*/
|
|
270
272
|
renderMedicationCode(medication) {
|
|
271
273
|
if (medication && medication.code) {
|
|
272
|
-
return this.codeableConcept(medication.code, "display");
|
|
274
|
+
return this.renderTextAsHtml(this.codeableConcept(medication.code, "display"));
|
|
273
275
|
}
|
|
274
276
|
return "";
|
|
275
277
|
}
|
|
@@ -280,7 +282,7 @@ var TemplateUtilities = class {
|
|
|
280
282
|
*/
|
|
281
283
|
renderDoseNumber(doseNumber) {
|
|
282
284
|
if (doseNumber && doseNumber.value !== void 0) {
|
|
283
|
-
return doseNumber.value.toString();
|
|
285
|
+
return this.renderTextAsHtml(doseNumber.value.toString());
|
|
284
286
|
}
|
|
285
287
|
return "";
|
|
286
288
|
}
|
|
@@ -291,7 +293,7 @@ var TemplateUtilities = class {
|
|
|
291
293
|
*/
|
|
292
294
|
renderValueUnit(value) {
|
|
293
295
|
if (value && value.constructor?.name === "Quantity" && value.unit) {
|
|
294
|
-
return value.unit;
|
|
296
|
+
return this.renderTextAsHtml(value.unit);
|
|
295
297
|
}
|
|
296
298
|
return "";
|
|
297
299
|
}
|
|
@@ -357,7 +359,7 @@ var TemplateUtilities = class {
|
|
|
357
359
|
* @returns Comma-separated string of items
|
|
358
360
|
*/
|
|
359
361
|
safeConcat(list, attr) {
|
|
360
|
-
return this.concat(list || [], attr);
|
|
362
|
+
return this.renderTextAsHtml(this.concat(list || [], attr));
|
|
361
363
|
}
|
|
362
364
|
/**
|
|
363
365
|
* Concatenates text from a list of CodeableConcept objects
|
|
@@ -374,7 +376,7 @@ var TemplateUtilities = class {
|
|
|
374
376
|
items.push(item.text);
|
|
375
377
|
}
|
|
376
378
|
}
|
|
377
|
-
return items.join(", ");
|
|
379
|
+
return this.renderTextAsHtml(items.join(", "));
|
|
378
380
|
}
|
|
379
381
|
/**
|
|
380
382
|
* Concatenates reaction manifestations
|
|
@@ -395,7 +397,7 @@ var TemplateUtilities = class {
|
|
|
395
397
|
}
|
|
396
398
|
}
|
|
397
399
|
}
|
|
398
|
-
return texts.join(", ");
|
|
400
|
+
return this.renderTextAsHtml(texts.join(", "));
|
|
399
401
|
}
|
|
400
402
|
/**
|
|
401
403
|
* Concatenates dose numbers
|
|
@@ -412,7 +414,7 @@ var TemplateUtilities = class {
|
|
|
412
414
|
doseNumbers.push(item.doseNumberPositiveInt.toString());
|
|
413
415
|
}
|
|
414
416
|
}
|
|
415
|
-
return doseNumbers.join(", ");
|
|
417
|
+
return this.renderTextAsHtml(doseNumbers.join(", "));
|
|
416
418
|
}
|
|
417
419
|
/**
|
|
418
420
|
* Concatenates dosage routes
|
|
@@ -429,7 +431,7 @@ var TemplateUtilities = class {
|
|
|
429
431
|
routes.push(item.route.text);
|
|
430
432
|
}
|
|
431
433
|
}
|
|
432
|
-
return routes.join(", ");
|
|
434
|
+
return this.renderTextAsHtml(routes.join(", "));
|
|
433
435
|
}
|
|
434
436
|
/**
|
|
435
437
|
* Returns the first item from a list of CodeableConcept objects
|
|
@@ -438,7 +440,7 @@ var TemplateUtilities = class {
|
|
|
438
440
|
*/
|
|
439
441
|
firstFromCodeableConceptList(list) {
|
|
440
442
|
if (list && Array.isArray(list) && list[0]) {
|
|
441
|
-
return this.codeableConcept(list[0], "display");
|
|
443
|
+
return this.renderTextAsHtml(this.codeableConcept(list[0], "display"));
|
|
442
444
|
}
|
|
443
445
|
return "";
|
|
444
446
|
}
|
|
@@ -457,7 +459,7 @@ var TemplateUtilities = class {
|
|
|
457
459
|
texts.push(item.text);
|
|
458
460
|
}
|
|
459
461
|
}
|
|
460
|
-
return texts.join(", ");
|
|
462
|
+
return this.renderTextAsHtml(texts.join(", "));
|
|
461
463
|
}
|
|
462
464
|
/**
|
|
463
465
|
* Renders component codes
|
|
@@ -592,9 +594,18 @@ var TemplateUtilities = class {
|
|
|
592
594
|
}
|
|
593
595
|
return status;
|
|
594
596
|
}
|
|
597
|
+
formatFloatValue(value) {
|
|
598
|
+
if (typeof value === "number") {
|
|
599
|
+
return value.toFixed(2).replace(/\.?0+$/, "");
|
|
600
|
+
} else if (typeof value === "string" && !isNaN(Number(value))) {
|
|
601
|
+
return parseFloat(value).toFixed(2).replace(/\.?0+$/, "");
|
|
602
|
+
}
|
|
603
|
+
return value;
|
|
604
|
+
}
|
|
595
605
|
extractObservationSummaryValue(data, timezone) {
|
|
596
606
|
if (data["valueQuantity.value"] !== void 0) {
|
|
597
|
-
|
|
607
|
+
let value = data["valueQuantity.value"];
|
|
608
|
+
value = this.formatFloatValue(value);
|
|
598
609
|
const unit = data["valueQuantity.unit"];
|
|
599
610
|
return unit ? `${value} ${unit}` : `${value}`;
|
|
600
611
|
}
|
|
@@ -611,7 +622,9 @@ var TemplateUtilities = class {
|
|
|
611
622
|
return String(data["valueBoolean"]);
|
|
612
623
|
}
|
|
613
624
|
if (data["valueInteger"] !== void 0) {
|
|
614
|
-
|
|
625
|
+
let value = String(data["valueInteger"]);
|
|
626
|
+
value = this.formatFloatValue(value);
|
|
627
|
+
return value;
|
|
615
628
|
}
|
|
616
629
|
if (data["valueDateTime"] !== void 0) {
|
|
617
630
|
return this.renderTime(data["valueDateTime"], timezone);
|
|
@@ -631,7 +644,8 @@ var TemplateUtilities = class {
|
|
|
631
644
|
return this.renderTime(data["valueTime"], timezone);
|
|
632
645
|
}
|
|
633
646
|
if (data["valueSampledData.origin.value"] !== void 0 || data["valueSampledData.origin.unit"] !== void 0) {
|
|
634
|
-
|
|
647
|
+
let originValue = data["valueSampledData.origin.value"];
|
|
648
|
+
originValue = this.formatFloatValue(originValue);
|
|
635
649
|
const originUnit = data["valueSampledData.origin.unit"];
|
|
636
650
|
let result = "";
|
|
637
651
|
if (originValue !== void 0 && originUnit !== void 0) {
|
|
@@ -641,10 +655,10 @@ var TemplateUtilities = class {
|
|
|
641
655
|
} else if (originUnit !== void 0) {
|
|
642
656
|
result = `${originUnit}`;
|
|
643
657
|
}
|
|
644
|
-
const period = data["valueSampledData.period"];
|
|
645
|
-
const factor = data["valueSampledData.factor"];
|
|
646
|
-
const lowerLimit = data["valueSampledData.lowerLimit"];
|
|
647
|
-
const upperLimit = data["valueSampledData.upperLimit"];
|
|
658
|
+
const period = this.formatFloatValue(data["valueSampledData.period"]);
|
|
659
|
+
const factor = this.formatFloatValue(data["valueSampledData.factor"]);
|
|
660
|
+
const lowerLimit = this.formatFloatValue(data["valueSampledData.lowerLimit"]);
|
|
661
|
+
const upperLimit = this.formatFloatValue(data["valueSampledData.upperLimit"]);
|
|
648
662
|
const sampledData = data["valueSampledData.data"];
|
|
649
663
|
const extras = [];
|
|
650
664
|
if (period !== void 0) extras.push(`period: ${period}`);
|
|
@@ -680,14 +694,14 @@ var TemplateUtilities = class {
|
|
|
680
694
|
if (data["valueRatio.numerator.value"] !== void 0 || data["valueRatio.denominator.value"] !== void 0) {
|
|
681
695
|
let ratio = "";
|
|
682
696
|
if (data["valueRatio.numerator.value"] !== void 0) {
|
|
683
|
-
ratio += `${data["valueRatio.numerator.value"]}`;
|
|
697
|
+
ratio += `${this.formatFloatValue(data["valueRatio.numerator.value"])}`;
|
|
684
698
|
if (data["valueRatio.numerator.unit"] !== void 0) {
|
|
685
699
|
ratio += ` ${data["valueRatio.numerator.unit"]}`;
|
|
686
700
|
}
|
|
687
701
|
}
|
|
688
702
|
if (data["valueRatio.denominator.value"] !== void 0) {
|
|
689
703
|
ratio += " / ";
|
|
690
|
-
ratio += `${data["valueRatio.denominator.value"]}`;
|
|
704
|
+
ratio += `${this.formatFloatValue(data["valueRatio.denominator.value"])}`;
|
|
691
705
|
if (data["valueRatio.denominator.unit"] !== void 0) {
|
|
692
706
|
ratio += ` ${data["valueRatio.denominator.unit"]}`;
|
|
693
707
|
}
|
|
@@ -777,9 +791,11 @@ var TemplateUtilities = class {
|
|
|
777
791
|
return "";
|
|
778
792
|
}
|
|
779
793
|
/**
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
794
|
+
* Public method to render plain text as HTML, escaping special characters and replacing newlines with <br />.
|
|
795
|
+
* This method should be used whenever displaying user-supplied or FHIR resource text in HTML to prevent XSS vulnerabilities
|
|
796
|
+
* and to preserve formatting. Use this in templates or UI components that need to safely display multi-line or arbitrary text.
|
|
797
|
+
* @param text - The text to render as HTML
|
|
798
|
+
* @returns The HTML-safe string with newlines converted to <br />
|
|
783
799
|
*/
|
|
784
800
|
renderTextAsHtml(text) {
|
|
785
801
|
if (!text || text.trim() === "") {
|
|
@@ -843,7 +859,7 @@ var TemplateUtilities = class {
|
|
|
843
859
|
dateTime = DateTime.fromISO(String(dateValue));
|
|
844
860
|
}
|
|
845
861
|
if (!dateTime.isValid) {
|
|
846
|
-
return String(dateValue);
|
|
862
|
+
return this.renderTextAsHtml(String(dateValue));
|
|
847
863
|
}
|
|
848
864
|
if (dateOnly) {
|
|
849
865
|
dateTime = dateTime.toUTC();
|
|
@@ -859,9 +875,9 @@ var TemplateUtilities = class {
|
|
|
859
875
|
hour12: true,
|
|
860
876
|
timeZoneName: "short"
|
|
861
877
|
};
|
|
862
|
-
return dateTime.toLocaleString(formatOptions);
|
|
878
|
+
return this.renderTextAsHtml(dateTime.toLocaleString(formatOptions));
|
|
863
879
|
} catch {
|
|
864
|
-
return String(dateValue);
|
|
880
|
+
return this.renderTextAsHtml(String(dateValue));
|
|
865
881
|
}
|
|
866
882
|
}
|
|
867
883
|
/**
|
|
@@ -941,6 +957,313 @@ var TemplateUtilities = class {
|
|
|
941
957
|
|
|
942
958
|
// src/constants.ts
|
|
943
959
|
var ADDRESS_SIMILARITY_THRESHOLD = 70;
|
|
960
|
+
var LAB_LOINC_MAP = {
|
|
961
|
+
// METABOLIC PANELS (VERY COMMONLY ORDERED)
|
|
962
|
+
"Basic Metabolic Panel": [
|
|
963
|
+
"24321-2",
|
|
964
|
+
// Basic metabolic 2000 panel - Serum or Plasma
|
|
965
|
+
"51990-0"
|
|
966
|
+
// Basic metabolic 2000 panel - Blood
|
|
967
|
+
],
|
|
968
|
+
"Comprehensive Metabolic Panel": [
|
|
969
|
+
"24323-8"
|
|
970
|
+
// Comprehensive metabolic 2000 panel - Serum or Plasma
|
|
971
|
+
],
|
|
972
|
+
// CBC COMPONENTS
|
|
973
|
+
Hemoglobin: [
|
|
974
|
+
"718-7"
|
|
975
|
+
// Hemoglobin [Mass/volume] in Blood
|
|
976
|
+
],
|
|
977
|
+
Hematocrit: [
|
|
978
|
+
"4544-3"
|
|
979
|
+
// Hematocrit [Volume Fraction] of Blood by Automated count
|
|
980
|
+
],
|
|
981
|
+
"White Blood Cell Count": [
|
|
982
|
+
"6690-2"
|
|
983
|
+
// Leukocytes [///volume] in Blood by Automated count
|
|
984
|
+
],
|
|
985
|
+
"Platelet Count": [
|
|
986
|
+
"777-3"
|
|
987
|
+
// Platelets [///volume] in Blood by Automated count
|
|
988
|
+
],
|
|
989
|
+
"Complete Blood Count": [
|
|
990
|
+
"58410-2",
|
|
991
|
+
// CBC panel - Blood by Automated count
|
|
992
|
+
"57021-8",
|
|
993
|
+
// CBC W Auto Differential panel - Blood
|
|
994
|
+
"69738-3"
|
|
995
|
+
// CBC W Auto Differential panel - Blood by Automated count
|
|
996
|
+
],
|
|
997
|
+
// CHEMISTRY - GLUCOSE
|
|
998
|
+
Glucose: [
|
|
999
|
+
"2345-7",
|
|
1000
|
+
// Glucose [Mass/volume] in Serum or Plasma
|
|
1001
|
+
"1558-6",
|
|
1002
|
+
// Fasting glucose [Mass/volume] in Serum or Plasma
|
|
1003
|
+
"2339-0"
|
|
1004
|
+
// Glucose [Mass/volume] in Blood
|
|
1005
|
+
],
|
|
1006
|
+
// RENAL FUNCTION
|
|
1007
|
+
Creatinine: [
|
|
1008
|
+
"2160-0"
|
|
1009
|
+
// Creatinine [Mass/volume] in Serum or Plasma
|
|
1010
|
+
],
|
|
1011
|
+
"Blood Urea Nitrogen": [
|
|
1012
|
+
"3094-0",
|
|
1013
|
+
// Urea nitrogen [Mass/volume] in Serum or Plasma
|
|
1014
|
+
"6299-2"
|
|
1015
|
+
// Urea nitrogen [Mass/volume] in Blood
|
|
1016
|
+
],
|
|
1017
|
+
// ELECTROLYTES
|
|
1018
|
+
Sodium: [
|
|
1019
|
+
"2951-2"
|
|
1020
|
+
// Sodium [Moles/volume] in Serum or Plasma
|
|
1021
|
+
],
|
|
1022
|
+
Potassium: [
|
|
1023
|
+
"2823-3"
|
|
1024
|
+
// Potassium [Moles/volume] in Serum or Plasma
|
|
1025
|
+
],
|
|
1026
|
+
Chloride: [
|
|
1027
|
+
"2075-0"
|
|
1028
|
+
// Chloride [Moles/volume] in Serum or Plasma
|
|
1029
|
+
],
|
|
1030
|
+
Calcium: [
|
|
1031
|
+
"17861-6",
|
|
1032
|
+
// Calcium [Mass/volume] in Serum or Plasma
|
|
1033
|
+
"1994-3"
|
|
1034
|
+
// Calcium.ionized [Moles/volume] in Serum or Plasma
|
|
1035
|
+
],
|
|
1036
|
+
Magnesium: [
|
|
1037
|
+
"19123-9"
|
|
1038
|
+
// Magnesium [Mass/volume] in Serum or Plasma
|
|
1039
|
+
],
|
|
1040
|
+
Phosphate: [
|
|
1041
|
+
"14879-1"
|
|
1042
|
+
// Phosphate [Mass/volume] in Serum or Plasma
|
|
1043
|
+
],
|
|
1044
|
+
// PROTEINS
|
|
1045
|
+
Albumin: [
|
|
1046
|
+
"1751-7"
|
|
1047
|
+
// Albumin [Mass/volume] in Serum or Plasma
|
|
1048
|
+
],
|
|
1049
|
+
"Total Protein": [
|
|
1050
|
+
"2885-2"
|
|
1051
|
+
// Protein [Mass/volume] in Serum or Plasma
|
|
1052
|
+
],
|
|
1053
|
+
// LIVER FUNCTION
|
|
1054
|
+
Bilirubin: [
|
|
1055
|
+
"1975-2",
|
|
1056
|
+
// Bilirubin.total [Mass/volume] in Serum or Plasma
|
|
1057
|
+
"1968-7",
|
|
1058
|
+
// Bilirubin.direct [Mass/volume] in Serum or Plasma
|
|
1059
|
+
"1971-1"
|
|
1060
|
+
// Bilirubin.indirect [Mass/volume] in Serum or Plasma
|
|
1061
|
+
],
|
|
1062
|
+
"Alkaline Phosphatase": [
|
|
1063
|
+
"6768-6"
|
|
1064
|
+
// Alkaline phosphatase [Enzymatic activity/volume] in Serum or Plasma
|
|
1065
|
+
],
|
|
1066
|
+
AST: [
|
|
1067
|
+
"1920-8"
|
|
1068
|
+
// Aspartate aminotransferase [Enzymatic activity/volume] in Serum or Plasma
|
|
1069
|
+
],
|
|
1070
|
+
ALT: [
|
|
1071
|
+
"1742-6"
|
|
1072
|
+
// Alanine aminotransferase [Enzymatic activity/volume] in Serum or Plasma
|
|
1073
|
+
],
|
|
1074
|
+
GGT: [
|
|
1075
|
+
"2324-2"
|
|
1076
|
+
// Gamma glutamyl transferase [Enzymatic activity/volume] in Serum or Plasma
|
|
1077
|
+
],
|
|
1078
|
+
// ENDOCRINE
|
|
1079
|
+
TSH: [
|
|
1080
|
+
"3016-3"
|
|
1081
|
+
// Thyrotropin [Units/volume] in Serum or Plasma
|
|
1082
|
+
],
|
|
1083
|
+
"Free T4": [
|
|
1084
|
+
"3024-7"
|
|
1085
|
+
// Thyroxine (T4) free [Mass/volume] in Serum or Plasma
|
|
1086
|
+
],
|
|
1087
|
+
"Total T4": [
|
|
1088
|
+
"3026-2"
|
|
1089
|
+
// Thyroxine (T4) [Mass/volume] in Serum or Plasma
|
|
1090
|
+
],
|
|
1091
|
+
"Free T3": [
|
|
1092
|
+
"3051-0"
|
|
1093
|
+
// Triiodothyronine (T3) free [Mass/volume] in Serum or Plasma
|
|
1094
|
+
],
|
|
1095
|
+
"Total T3": [
|
|
1096
|
+
"3053-6"
|
|
1097
|
+
// Triiodothyronine (T3) [Mass/volume] in Serum or Plasma
|
|
1098
|
+
],
|
|
1099
|
+
HbA1c: [
|
|
1100
|
+
"4548-4",
|
|
1101
|
+
// Hemoglobin A1c/Hemoglobin.total in Blood
|
|
1102
|
+
"17856-6"
|
|
1103
|
+
// Hemoglobin A1c/Hemoglobin.total in Blood by HPLC
|
|
1104
|
+
],
|
|
1105
|
+
// LIPID PANEL
|
|
1106
|
+
"Lipid Panel": [
|
|
1107
|
+
"24331-1",
|
|
1108
|
+
// Lipid 1996 panel - Serum or Plasma
|
|
1109
|
+
"57698-3"
|
|
1110
|
+
// Lipid panel with direct LDL - Serum or Plasma
|
|
1111
|
+
],
|
|
1112
|
+
"Cholesterol Total": [
|
|
1113
|
+
"2093-3"
|
|
1114
|
+
// Cholesterol [Mass/volume] in Serum or Plasma
|
|
1115
|
+
],
|
|
1116
|
+
"HDL Cholesterol": [
|
|
1117
|
+
"2085-9"
|
|
1118
|
+
// Cholesterol in HDL [Mass/volume] in Serum or Plasma
|
|
1119
|
+
],
|
|
1120
|
+
"LDL Cholesterol": [
|
|
1121
|
+
"13457-7",
|
|
1122
|
+
// Cholesterol in LDL [Mass/volume] in Serum or Plasma by calculation
|
|
1123
|
+
"18262-6"
|
|
1124
|
+
// Cholesterol in LDL [Mass/volume] in Serum or Plasma by Direct assay
|
|
1125
|
+
],
|
|
1126
|
+
Triglycerides: [
|
|
1127
|
+
"2571-8"
|
|
1128
|
+
// Triglyceride [Mass/volume] in Serum or Plasma
|
|
1129
|
+
],
|
|
1130
|
+
// COAGULATION STUDIES (COMMONLY MISSING!)
|
|
1131
|
+
PT: [
|
|
1132
|
+
"5902-2"
|
|
1133
|
+
// Prothrombin time (PT)
|
|
1134
|
+
],
|
|
1135
|
+
INR: [
|
|
1136
|
+
"6301-6"
|
|
1137
|
+
// INR in Platelet poor plasma by Coagulation assay
|
|
1138
|
+
],
|
|
1139
|
+
PTT: [
|
|
1140
|
+
"3173-2",
|
|
1141
|
+
// aPTT in Blood by Coagulation assay
|
|
1142
|
+
"14979-9"
|
|
1143
|
+
// aPTT in Platelet poor plasma by Coagulation assay
|
|
1144
|
+
],
|
|
1145
|
+
Fibrinogen: [
|
|
1146
|
+
"3255-7"
|
|
1147
|
+
// Fibrinogen [Mass/volume] in Platelet poor plasma by Coagulation assay
|
|
1148
|
+
],
|
|
1149
|
+
"D-Dimer": [
|
|
1150
|
+
"48065-7",
|
|
1151
|
+
// D-dimer FEU [Mass/volume] in Platelet poor plasma
|
|
1152
|
+
"48066-5"
|
|
1153
|
+
// D-dimer DDU [Mass/volume] in Platelet poor plasma
|
|
1154
|
+
],
|
|
1155
|
+
// CARDIAC MARKERS (CRITICAL FOR ER!)
|
|
1156
|
+
"Troponin I": [
|
|
1157
|
+
"10839-9",
|
|
1158
|
+
// Troponin I.cardiac [Mass/volume] in Serum or Plasma
|
|
1159
|
+
"42757-5",
|
|
1160
|
+
// Troponin I.cardiac [Mass/volume] in Blood
|
|
1161
|
+
"89579-7"
|
|
1162
|
+
// Troponin I.cardiac [Mass/volume] in Serum or Plasma by High sensitivity method
|
|
1163
|
+
],
|
|
1164
|
+
"Troponin T": [
|
|
1165
|
+
"6598-7",
|
|
1166
|
+
// Troponin T.cardiac [Mass/volume] in Serum or Plasma
|
|
1167
|
+
"48425-3"
|
|
1168
|
+
// Troponin T.cardiac [Mass/volume] in Serum or Plasma by High sensitivity method
|
|
1169
|
+
],
|
|
1170
|
+
BNP: [
|
|
1171
|
+
"30934-4"
|
|
1172
|
+
// BNP [Mass/volume] in Serum or Plasma
|
|
1173
|
+
],
|
|
1174
|
+
"NT-proBNP": [
|
|
1175
|
+
"33762-6"
|
|
1176
|
+
// NT-proBNP [Mass/volume] in Serum or Plasma
|
|
1177
|
+
],
|
|
1178
|
+
"CK-MB": [
|
|
1179
|
+
"13969-1"
|
|
1180
|
+
// Creatine kinase.MB [Mass/volume] in Serum or Plasma
|
|
1181
|
+
],
|
|
1182
|
+
// INFLAMMATORY MARKERS
|
|
1183
|
+
CRP: [
|
|
1184
|
+
"1988-5",
|
|
1185
|
+
// C reactive protein [Mass/volume] in Serum or Plasma
|
|
1186
|
+
"30522-7"
|
|
1187
|
+
// C reactive protein [Mass/volume] in Serum or Plasma by High sensitivity method
|
|
1188
|
+
],
|
|
1189
|
+
ESR: [
|
|
1190
|
+
"30341-2",
|
|
1191
|
+
// Erythrocyte sedimentation rate by Westergren method
|
|
1192
|
+
"4537-7"
|
|
1193
|
+
// Erythrocyte sedimentation rate
|
|
1194
|
+
],
|
|
1195
|
+
// VITAMINS & MINERALS (VERY HIGH VOLUME!)
|
|
1196
|
+
"Vitamin D": [
|
|
1197
|
+
"1990-1",
|
|
1198
|
+
// Vitamin D [Mass/volume] in Serum or Plasma (obsolete, but still used)
|
|
1199
|
+
"14635-7",
|
|
1200
|
+
// 25-Hydroxyvitamin D3 [Mass/volume] in Serum or Plasma
|
|
1201
|
+
"62292-8"
|
|
1202
|
+
// 25-Hydroxyvitamin D2+D3 [Mass/volume] in Serum or Plasma
|
|
1203
|
+
],
|
|
1204
|
+
"Vitamin B12": [
|
|
1205
|
+
"2132-9"
|
|
1206
|
+
// Cobalamin (Vitamin B12) [Mass/volume] in Serum or Plasma
|
|
1207
|
+
],
|
|
1208
|
+
Folate: [
|
|
1209
|
+
"2284-8",
|
|
1210
|
+
// Folate [Mass/volume] in Serum or Plasma
|
|
1211
|
+
"15152-2"
|
|
1212
|
+
// Folate [Mass/volume] in Red Blood Cells
|
|
1213
|
+
],
|
|
1214
|
+
Iron: [
|
|
1215
|
+
"2498-4"
|
|
1216
|
+
// Iron [Mass/volume] in Serum or Plasma
|
|
1217
|
+
],
|
|
1218
|
+
Ferritin: [
|
|
1219
|
+
"2276-4"
|
|
1220
|
+
// Ferritin [Mass/volume] in Serum or Plasma
|
|
1221
|
+
],
|
|
1222
|
+
TIBC: [
|
|
1223
|
+
"2500-7"
|
|
1224
|
+
// Iron binding capacity [Mass/volume] in Serum or Plasma
|
|
1225
|
+
],
|
|
1226
|
+
// OTHER COMMON TESTS
|
|
1227
|
+
PSA: [
|
|
1228
|
+
"2857-1",
|
|
1229
|
+
// Prostate specific Ag [Mass/volume] in Serum or Plasma
|
|
1230
|
+
"10886-0"
|
|
1231
|
+
// Prostate specific Ag Free [Mass/volume] in Serum or Plasma
|
|
1232
|
+
],
|
|
1233
|
+
"Uric Acid": [
|
|
1234
|
+
"3084-1"
|
|
1235
|
+
// Urate [Mass/volume] in Serum or Plasma
|
|
1236
|
+
],
|
|
1237
|
+
LDH: [
|
|
1238
|
+
"2532-0",
|
|
1239
|
+
// Lactate dehydrogenase [Enzymatic activity/volume] in Serum or Plasma
|
|
1240
|
+
"14804-9"
|
|
1241
|
+
// Lactate dehydrogenase [Enzymatic activity/volume] in Serum or Plasma by Lactate to pyruvate reaction
|
|
1242
|
+
],
|
|
1243
|
+
Amylase: [
|
|
1244
|
+
"1798-8"
|
|
1245
|
+
// Amylase [Enzymatic activity/volume] in Serum or Plasma
|
|
1246
|
+
],
|
|
1247
|
+
Lipase: [
|
|
1248
|
+
"3040-3"
|
|
1249
|
+
// Lipase [Enzymatic activity/volume] in Serum or Plasma
|
|
1250
|
+
],
|
|
1251
|
+
hCG: [
|
|
1252
|
+
"21198-7",
|
|
1253
|
+
// Choriogonadotropin.beta subunit [Units/volume] in Serum or Plasma
|
|
1254
|
+
"2118-8",
|
|
1255
|
+
// Choriogonadotropin (pregnancy test) [Presence] in Serum or Plasma
|
|
1256
|
+
"2106-3"
|
|
1257
|
+
// Choriogonadotropin (pregnancy test) [Presence] in Urine
|
|
1258
|
+
],
|
|
1259
|
+
// URINALYSIS
|
|
1260
|
+
Urinalysis: [
|
|
1261
|
+
"24357-6",
|
|
1262
|
+
// Urinalysis macro (dipstick) panel - Urine
|
|
1263
|
+
"24356-8"
|
|
1264
|
+
// Urinalysis complete panel - Urine
|
|
1265
|
+
]
|
|
1266
|
+
};
|
|
944
1267
|
|
|
945
1268
|
// src/narratives/templates/typescript/PatientTemplate.ts
|
|
946
1269
|
var PatientTemplate = class _PatientTemplate {
|
|
@@ -1258,7 +1581,7 @@ var PatientTemplate = class _PatientTemplate {
|
|
|
1258
1581
|
const uniqueLanguages = /* @__PURE__ */ new Set();
|
|
1259
1582
|
const preferredLanguages = /* @__PURE__ */ new Set();
|
|
1260
1583
|
patient.communication.forEach((comm) => {
|
|
1261
|
-
const language = templateUtilities.codeableConcept(comm.language);
|
|
1584
|
+
const language = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(comm.language));
|
|
1262
1585
|
if (language) {
|
|
1263
1586
|
if (comm.preferred) {
|
|
1264
1587
|
preferredLanguages.add(language);
|
|
@@ -1316,13 +1639,13 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1316
1639
|
for (const columnData of rowData.section ?? []) {
|
|
1317
1640
|
switch (columnData.title) {
|
|
1318
1641
|
case "Allergen Name":
|
|
1319
|
-
data["allergen"] = columnData.text?.div ?? "";
|
|
1642
|
+
data["allergen"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1320
1643
|
break;
|
|
1321
1644
|
case "Criticality":
|
|
1322
|
-
data["criticality"] = columnData.text?.div ?? "";
|
|
1645
|
+
data["criticality"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1323
1646
|
break;
|
|
1324
1647
|
case "Recorded Date":
|
|
1325
|
-
data["recordedDate"] = columnData.text?.div ?? "";
|
|
1648
|
+
data["recordedDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1326
1649
|
break;
|
|
1327
1650
|
default:
|
|
1328
1651
|
break;
|
|
@@ -1443,11 +1766,11 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1443
1766
|
for (const allergy of allergies) {
|
|
1444
1767
|
html += `
|
|
1445
1768
|
<tr id="${templateUtilities.narrativeLinkId(allergy.extension)}">
|
|
1446
|
-
<td class="Name"><span class="AllergenName">${templateUtilities.codeableConcept(allergy.code)}</span></td>
|
|
1447
|
-
<td class="Status">${templateUtilities.codeableConcept(allergy.clinicalStatus) || "-"}</td>
|
|
1448
|
-
<td class="Category">${templateUtilities.safeConcat(allergy.category) || "-"}</td>
|
|
1449
|
-
<td class="Reaction">${templateUtilities.concatReactionManifestation(allergy.reaction) || "-"}</td>
|
|
1450
|
-
<td class="OnsetDate">${templateUtilities.renderTime(allergy.onsetDateTime, timezone) || "-"}</td>
|
|
1769
|
+
<td class="Name"><span class="AllergenName">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.code))}</span></td>
|
|
1770
|
+
<td class="Status">${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(allergy.clinicalStatus)) || "-"}</td>
|
|
1771
|
+
<td class="Category">${templateUtilities.renderTextAsHtml(templateUtilities.safeConcat(allergy.category)) || "-"}</td>
|
|
1772
|
+
<td class="Reaction">${templateUtilities.renderTextAsHtml(templateUtilities.concatReactionManifestation(allergy.reaction)) || "-"}</td>
|
|
1773
|
+
<td class="OnsetDate">${templateUtilities.renderTextAsHtml(templateUtilities.renderTime(allergy.onsetDateTime, timezone)) || "-"}</td>
|
|
1451
1774
|
<td class="Comments">${templateUtilities.renderNotes(allergy.note, timezone, { styled: true, warning: true })}</td>`;
|
|
1452
1775
|
if (includeResolved) {
|
|
1453
1776
|
let endDate = "-";
|
|
@@ -1460,7 +1783,7 @@ var AllergyIntoleranceTemplate = class _AllergyIntoleranceTemplate {
|
|
|
1460
1783
|
}
|
|
1461
1784
|
}
|
|
1462
1785
|
html += `
|
|
1463
|
-
<td class="ResolvedDate">${endDate}</td>`;
|
|
1786
|
+
<td class="ResolvedDate">${templateUtilities.renderTextAsHtml(endDate)}</td>`;
|
|
1464
1787
|
}
|
|
1465
1788
|
html += `</tr>`;
|
|
1466
1789
|
}
|
|
@@ -1483,17 +1806,21 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1483
1806
|
* Generate HTML narrative for Medication resources using summary
|
|
1484
1807
|
* @param resources - FHIR Composition resources
|
|
1485
1808
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
1809
|
+
* @param now - Optional current date to use for calculations (defaults to new Date())
|
|
1486
1810
|
* @returns HTML string for rendering
|
|
1487
1811
|
*/
|
|
1488
|
-
generateSummaryNarrative(resources, timezone) {
|
|
1812
|
+
generateSummaryNarrative(resources, timezone, now) {
|
|
1489
1813
|
const templateUtilities = new TemplateUtilities(resources);
|
|
1490
1814
|
let isSummaryCreated = false;
|
|
1815
|
+
const currentDate = now || /* @__PURE__ */ new Date();
|
|
1816
|
+
const twelveMonthsAgo = new Date(currentDate.getFullYear(), currentDate.getMonth() - 12, currentDate.getDate());
|
|
1491
1817
|
let html = `
|
|
1492
1818
|
<div>
|
|
1493
1819
|
<table>
|
|
1494
1820
|
<thead>
|
|
1495
1821
|
<tr>
|
|
1496
1822
|
<th>Medication</th>
|
|
1823
|
+
<th>Status</th>
|
|
1497
1824
|
<th>Sig</th>
|
|
1498
1825
|
<th>Days of Supply</th>
|
|
1499
1826
|
<th>Refills</th>
|
|
@@ -1507,40 +1834,48 @@ var MedicationSummaryTemplate = class _MedicationSummaryTemplate {
|
|
|
1507
1834
|
for (const columnData of rowData.section ?? []) {
|
|
1508
1835
|
switch (columnData.title) {
|
|
1509
1836
|
case "Medication Name":
|
|
1510
|
-
data["medication"] = columnData.text?.div ?? "";
|
|
1837
|
+
data["medication"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1511
1838
|
break;
|
|
1512
1839
|
case "Status":
|
|
1513
|
-
data["status"] = columnData.text?.div ?? "";
|
|
1840
|
+
data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1514
1841
|
break;
|
|
1515
1842
|
case "Prescriber Instruction":
|
|
1516
|
-
data["sig-prescriber"] = columnData.text?.div ?? "";
|
|
1843
|
+
data["sig-prescriber"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1517
1844
|
break;
|
|
1518
1845
|
case "Pharmacy Instruction":
|
|
1519
|
-
data["sig-pharmacy"] = columnData.text?.div ?? "";
|
|
1846
|
+
data["sig-pharmacy"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1520
1847
|
break;
|
|
1521
1848
|
case "Days Of Supply":
|
|
1522
|
-
data["daysOfSupply"] = columnData.text?.div ?? "";
|
|
1849
|
+
data["daysOfSupply"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1523
1850
|
break;
|
|
1524
1851
|
case "Refills Remaining":
|
|
1525
|
-
data["refills"] = columnData.text?.div ?? "";
|
|
1852
|
+
data["refills"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1526
1853
|
break;
|
|
1527
1854
|
case "Authored On Date":
|
|
1528
|
-
data["startDate"] = columnData.text?.div ?? "";
|
|
1855
|
+
data["startDate"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1529
1856
|
break;
|
|
1530
1857
|
default:
|
|
1531
1858
|
break;
|
|
1532
1859
|
}
|
|
1533
1860
|
}
|
|
1534
|
-
|
|
1861
|
+
let startDateObj;
|
|
1862
|
+
if (data["startDate"]) {
|
|
1863
|
+
startDateObj = new Date(data["startDate"]);
|
|
1864
|
+
if (isNaN(startDateObj.getTime())) {
|
|
1865
|
+
startDateObj = void 0;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
if (data["status"] === "active" || startDateObj && startDateObj >= twelveMonthsAgo) {
|
|
1535
1869
|
isSummaryCreated = true;
|
|
1536
1870
|
html += `
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1871
|
+
<tr>
|
|
1872
|
+
<td>${templateUtilities.renderTextAsHtml(data["medication"])}</td>
|
|
1873
|
+
<td>${templateUtilities.renderTextAsHtml(data["status"])}</td>
|
|
1874
|
+
<td>${templateUtilities.renderTextAsHtml(data["sig-prescriber"] || data["sig-pharmacy"])}</td>
|
|
1875
|
+
<td>${templateUtilities.renderTextAsHtml(data["daysOfSupply"])}</td>
|
|
1876
|
+
<td>${templateUtilities.renderTextAsHtml(data["refills"])}</td>
|
|
1877
|
+
<td>${templateUtilities.renderTime(data["startDate"], timezone)}</td>
|
|
1878
|
+
</tr>`;
|
|
1544
1879
|
}
|
|
1545
1880
|
}
|
|
1546
1881
|
}
|
|
@@ -1762,13 +2097,13 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
1762
2097
|
for (const columnData of rowData.section ?? []) {
|
|
1763
2098
|
switch (columnData.title) {
|
|
1764
2099
|
case "Immunization Name":
|
|
1765
|
-
data["immunization"] = columnData.text?.div ?? "";
|
|
2100
|
+
data["immunization"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1766
2101
|
break;
|
|
1767
2102
|
case "Status":
|
|
1768
|
-
data["status"] = columnData.text?.div ?? "";
|
|
2103
|
+
data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1769
2104
|
break;
|
|
1770
2105
|
case "occurrenceDateTime":
|
|
1771
|
-
data["occurrenceDateTime"] = columnData.text?.div ?? "";
|
|
2106
|
+
data["occurrenceDateTime"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1772
2107
|
break;
|
|
1773
2108
|
default:
|
|
1774
2109
|
break;
|
|
@@ -1819,7 +2154,7 @@ var ImmunizationsTemplate = class _ImmunizationsTemplate {
|
|
|
1819
2154
|
const imm = resourceItem;
|
|
1820
2155
|
html += `
|
|
1821
2156
|
<tr id="${templateUtilities.narrativeLinkId(imm)}">
|
|
1822
|
-
<td>${templateUtilities.codeableConcept(imm.vaccineCode)}</td>
|
|
2157
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(imm.vaccineCode))}</td>
|
|
1823
2158
|
<td>${imm.status || ""}</td>
|
|
1824
2159
|
<td>${templateUtilities.concatDoseNumber(imm.protocolApplied)}</td>
|
|
1825
2160
|
<td>${templateUtilities.renderVaccineManufacturer(imm)}</td>
|
|
@@ -1875,7 +2210,7 @@ var ProblemListTemplate = class _ProblemListTemplate {
|
|
|
1875
2210
|
<tbody>`;
|
|
1876
2211
|
const addedConditionCodes = /* @__PURE__ */ new Set();
|
|
1877
2212
|
for (const cond of activeConditions) {
|
|
1878
|
-
const conditionCode = templateUtilities.codeableConcept(cond.code);
|
|
2213
|
+
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
|
|
1879
2214
|
if (!addedConditionCodes.has(conditionCode)) {
|
|
1880
2215
|
addedConditionCodes.add(conditionCode);
|
|
1881
2216
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
@@ -1935,7 +2270,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
1935
2270
|
const vitalData = {};
|
|
1936
2271
|
for (const component of columnData.section?.[0]?.section ?? []) {
|
|
1937
2272
|
if (component.title) {
|
|
1938
|
-
vitalData[component.title] = component.text?.div ?? "";
|
|
2273
|
+
vitalData[component.title] = templateUtilities.renderTextAsHtml(component.text?.div ?? "");
|
|
1939
2274
|
}
|
|
1940
2275
|
}
|
|
1941
2276
|
const vitalValue = templateUtilities.extractObservationSummaryValue(
|
|
@@ -1947,7 +2282,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
1947
2282
|
data[dataKey] = vitalValue;
|
|
1948
2283
|
}
|
|
1949
2284
|
}
|
|
1950
|
-
data[columnTitle] = columnData.text?.div ?? "";
|
|
2285
|
+
data[columnTitle] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
1951
2286
|
}
|
|
1952
2287
|
}
|
|
1953
2288
|
isSummaryCreated = true;
|
|
@@ -1997,7 +2332,7 @@ var VitalSignsTemplate = class _VitalSignsTemplate {
|
|
|
1997
2332
|
for (const obs of observations) {
|
|
1998
2333
|
html += `
|
|
1999
2334
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2000
|
-
<td>${templateUtilities.codeableConcept(obs.code, "display")}</td>
|
|
2335
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code, "display"))}</td>
|
|
2001
2336
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2002
2337
|
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
2003
2338
|
<td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
|
|
@@ -2051,10 +2386,10 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2051
2386
|
for (const dus of deviceStatements) {
|
|
2052
2387
|
html += `
|
|
2053
2388
|
<tr id="${templateUtilities.narrativeLinkId(dus)}">
|
|
2054
|
-
<td>${templateUtilities.renderDevice(dus.device)}</td>
|
|
2055
|
-
<td>${dus.status || ""}</td>
|
|
2389
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.renderDevice(dus.device))}</td>
|
|
2390
|
+
<td>${templateUtilities.renderTextAsHtml(dus.status || "")}</td>
|
|
2056
2391
|
<td>${templateUtilities.renderNotes(dus.note, timezone)}</td>
|
|
2057
|
-
<td>${templateUtilities.renderRecorded(dus.recordedOn, timezone)}</td>
|
|
2392
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.renderRecorded(dus.recordedOn, timezone))}</td>
|
|
2058
2393
|
</tr>`;
|
|
2059
2394
|
}
|
|
2060
2395
|
html += `
|
|
@@ -2065,6 +2400,12 @@ var MedicalDevicesTemplate = class _MedicalDevicesTemplate {
|
|
|
2065
2400
|
};
|
|
2066
2401
|
|
|
2067
2402
|
// src/narratives/templates/typescript/DiagnosticResultsTemplate.ts
|
|
2403
|
+
var loincToLabName = {};
|
|
2404
|
+
for (const [labName, loincCodes] of Object.entries(LAB_LOINC_MAP)) {
|
|
2405
|
+
for (const code of loincCodes) {
|
|
2406
|
+
loincToLabName[code] = labName;
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2068
2409
|
var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
2069
2410
|
/**
|
|
2070
2411
|
* Generate HTML narrative for Diagnostic Results
|
|
@@ -2184,149 +2525,150 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2184
2525
|
* Helper function to extract observation field data
|
|
2185
2526
|
* @param column - Column data from the summary
|
|
2186
2527
|
* @param targetData - Record to populate with extracted data
|
|
2528
|
+
* @param templateUtilities - Instance of TemplateUtilities for utility functions
|
|
2187
2529
|
*/
|
|
2188
|
-
extractSummaryObservationFields(column, targetData) {
|
|
2530
|
+
extractSummaryObservationFields(column, targetData, templateUtilities) {
|
|
2189
2531
|
switch (column.title) {
|
|
2190
2532
|
case "Labs Name":
|
|
2191
|
-
targetData["code"] = column.text?.div ?? "";
|
|
2533
|
+
targetData["code"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2192
2534
|
break;
|
|
2193
2535
|
case "effectiveDateTime":
|
|
2194
|
-
targetData["effectiveDateTime"] = column.text?.div ?? "";
|
|
2536
|
+
targetData["effectiveDateTime"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2195
2537
|
break;
|
|
2196
2538
|
case "effectivePeriod.start":
|
|
2197
|
-
targetData["effectivePeriodStart"] = column.text?.div ?? "";
|
|
2539
|
+
targetData["effectivePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2198
2540
|
break;
|
|
2199
2541
|
case "effectivePeriod.end":
|
|
2200
|
-
targetData["effectivePeriodEnd"] = column.text?.div ?? "";
|
|
2542
|
+
targetData["effectivePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2201
2543
|
break;
|
|
2202
2544
|
// valueQuantity
|
|
2203
2545
|
case "valueQuantity.value":
|
|
2204
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2546
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2205
2547
|
targetData["valueType"] = "valueQuantity";
|
|
2206
2548
|
break;
|
|
2207
2549
|
case "valueQuantity.unit":
|
|
2208
|
-
targetData["unit"] = column.text?.div ?? "";
|
|
2550
|
+
targetData["unit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2209
2551
|
break;
|
|
2210
2552
|
// valueCodeableConcept
|
|
2211
2553
|
case "valueCodeableConcept.text":
|
|
2212
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2554
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2213
2555
|
targetData["valueType"] = "valueCodeableConcept";
|
|
2214
2556
|
break;
|
|
2215
2557
|
case "valueCodeableConcept.coding.display":
|
|
2216
2558
|
if (!targetData["value"]) {
|
|
2217
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2559
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2218
2560
|
targetData["valueType"] = "valueCodeableConcept";
|
|
2219
2561
|
}
|
|
2220
2562
|
break;
|
|
2221
2563
|
// valueString
|
|
2222
2564
|
case "valueString":
|
|
2223
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2565
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2224
2566
|
targetData["valueType"] = "valueString";
|
|
2225
2567
|
break;
|
|
2226
2568
|
// valueBoolean
|
|
2227
2569
|
case "valueBoolean":
|
|
2228
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2570
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2229
2571
|
targetData["valueType"] = "valueBoolean";
|
|
2230
2572
|
break;
|
|
2231
2573
|
// valueInteger
|
|
2232
2574
|
case "valueInteger":
|
|
2233
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2575
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2234
2576
|
targetData["valueType"] = "valueInteger";
|
|
2235
2577
|
break;
|
|
2236
2578
|
// valueDateTime
|
|
2237
2579
|
case "valueDateTime":
|
|
2238
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2580
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2239
2581
|
targetData["valueType"] = "valueDateTime";
|
|
2240
2582
|
break;
|
|
2241
2583
|
// valuePeriod
|
|
2242
2584
|
case "valuePeriod.start":
|
|
2243
|
-
targetData["valuePeriodStart"] = column.text?.div ?? "";
|
|
2585
|
+
targetData["valuePeriodStart"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2244
2586
|
targetData["valueType"] = "valuePeriod";
|
|
2245
2587
|
break;
|
|
2246
2588
|
case "valuePeriod.end":
|
|
2247
|
-
targetData["valuePeriodEnd"] = column.text?.div ?? "";
|
|
2589
|
+
targetData["valuePeriodEnd"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2248
2590
|
targetData["valueType"] = "valuePeriod";
|
|
2249
2591
|
break;
|
|
2250
2592
|
// valueTime
|
|
2251
2593
|
case "valueTime":
|
|
2252
|
-
targetData["value"] = column.text?.div ?? "";
|
|
2594
|
+
targetData["value"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2253
2595
|
targetData["valueType"] = "valueTime";
|
|
2254
2596
|
break;
|
|
2255
2597
|
// valueSampledData
|
|
2256
2598
|
case "valueSampledData.origin.value":
|
|
2257
|
-
targetData["sampledDataOriginValue"] = column.text?.div ?? "";
|
|
2599
|
+
targetData["sampledDataOriginValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2258
2600
|
targetData["valueType"] = "valueSampledData";
|
|
2259
2601
|
break;
|
|
2260
2602
|
case "valueSampledData.origin.unit":
|
|
2261
|
-
targetData["sampledDataOriginUnit"] = column.text?.div ?? "";
|
|
2603
|
+
targetData["sampledDataOriginUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2262
2604
|
break;
|
|
2263
2605
|
case "valueSampledData.period":
|
|
2264
|
-
targetData["sampledDataPeriod"] = column.text?.div ?? "";
|
|
2606
|
+
targetData["sampledDataPeriod"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2265
2607
|
break;
|
|
2266
2608
|
case "valueSampledData.factor":
|
|
2267
|
-
targetData["sampledDataFactor"] = column.text?.div ?? "";
|
|
2609
|
+
targetData["sampledDataFactor"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2268
2610
|
break;
|
|
2269
2611
|
case "valueSampledData.lowerLimit":
|
|
2270
|
-
targetData["sampledDataLowerLimit"] = column.text?.div ?? "";
|
|
2612
|
+
targetData["sampledDataLowerLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2271
2613
|
break;
|
|
2272
2614
|
case "valueSampledData.upperLimit":
|
|
2273
|
-
targetData["sampledDataUpperLimit"] = column.text?.div ?? "";
|
|
2615
|
+
targetData["sampledDataUpperLimit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2274
2616
|
break;
|
|
2275
2617
|
case "valueSampledData.data":
|
|
2276
|
-
targetData["sampledDataData"] = column.text?.div ?? "";
|
|
2618
|
+
targetData["sampledDataData"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2277
2619
|
break;
|
|
2278
2620
|
// valueRange
|
|
2279
2621
|
case "valueRange.low.value":
|
|
2280
|
-
targetData["valueRangeLowValue"] = column.text?.div ?? "";
|
|
2622
|
+
targetData["valueRangeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2281
2623
|
targetData["valueType"] = "valueRange";
|
|
2282
2624
|
break;
|
|
2283
2625
|
case "valueRange.low.unit":
|
|
2284
|
-
targetData["valueRangeLowUnit"] = column.text?.div ?? "";
|
|
2626
|
+
targetData["valueRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2285
2627
|
break;
|
|
2286
2628
|
case "valueRange.high.value":
|
|
2287
|
-
targetData["valueRangeHighValue"] = column.text?.div ?? "";
|
|
2629
|
+
targetData["valueRangeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2288
2630
|
break;
|
|
2289
2631
|
case "valueRange.high.unit":
|
|
2290
|
-
targetData["valueRangeHighUnit"] = column.text?.div ?? "";
|
|
2632
|
+
targetData["valueRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2291
2633
|
break;
|
|
2292
2634
|
// valueRatio
|
|
2293
2635
|
case "valueRatio.numerator.value":
|
|
2294
|
-
targetData["valueRatioNumeratorValue"] = column.text?.div ?? "";
|
|
2636
|
+
targetData["valueRatioNumeratorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2295
2637
|
targetData["valueType"] = "valueRatio";
|
|
2296
2638
|
break;
|
|
2297
2639
|
case "valueRatio.numerator.unit":
|
|
2298
|
-
targetData["valueRatioNumeratorUnit"] = column.text?.div ?? "";
|
|
2640
|
+
targetData["valueRatioNumeratorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2299
2641
|
break;
|
|
2300
2642
|
case "valueRatio.denominator.value":
|
|
2301
|
-
targetData["valueRatioDenominatorValue"] = column.text?.div ?? "";
|
|
2643
|
+
targetData["valueRatioDenominatorValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2302
2644
|
break;
|
|
2303
2645
|
case "valueRatio.denominator.unit":
|
|
2304
|
-
targetData["valueRatioDenominatorUnit"] = column.text?.div ?? "";
|
|
2646
|
+
targetData["valueRatioDenominatorUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2305
2647
|
break;
|
|
2306
2648
|
// referenceRange
|
|
2307
2649
|
case "referenceRange.low.value":
|
|
2308
|
-
targetData["referenceRangeLow"] = column.text?.div ?? "";
|
|
2650
|
+
targetData["referenceRangeLow"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2309
2651
|
break;
|
|
2310
2652
|
case "referenceRange.low.unit":
|
|
2311
|
-
targetData["referenceRangeLowUnit"] = column.text?.div ?? "";
|
|
2653
|
+
targetData["referenceRangeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2312
2654
|
break;
|
|
2313
2655
|
case "referenceRange.high.value":
|
|
2314
|
-
targetData["referenceRangeHigh"] = column.text?.div ?? "";
|
|
2656
|
+
targetData["referenceRangeHigh"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2315
2657
|
break;
|
|
2316
2658
|
case "referenceRange.high.unit":
|
|
2317
|
-
targetData["referenceRangeHighUnit"] = column.text?.div ?? "";
|
|
2659
|
+
targetData["referenceRangeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2318
2660
|
break;
|
|
2319
2661
|
case "referenceRange.age.low.value":
|
|
2320
|
-
targetData["referenceRangeAgeLowValue"] = column.text?.div ?? "";
|
|
2662
|
+
targetData["referenceRangeAgeLowValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2321
2663
|
break;
|
|
2322
2664
|
case "referenceRange.age.low.unit":
|
|
2323
|
-
targetData["referenceRangeAgeLowUnit"] = column.text?.div ?? "";
|
|
2665
|
+
targetData["referenceRangeAgeLowUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2324
2666
|
break;
|
|
2325
2667
|
case "referenceRange.age.high.value":
|
|
2326
|
-
targetData["referenceRangeAgeHighValue"] = column.text?.div ?? "";
|
|
2668
|
+
targetData["referenceRangeAgeHighValue"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2327
2669
|
break;
|
|
2328
2670
|
case "referenceRange.age.high.unit":
|
|
2329
|
-
targetData["referenceRangeAgeHighUnit"] = column.text?.div ?? "";
|
|
2671
|
+
targetData["referenceRangeAgeHighUnit"] = templateUtilities.renderTextAsHtml(column.text?.div ?? "");
|
|
2330
2672
|
break;
|
|
2331
2673
|
default:
|
|
2332
2674
|
break;
|
|
@@ -2379,28 +2721,28 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2379
2721
|
for (const componentSection of columnData.section) {
|
|
2380
2722
|
const componentData = {};
|
|
2381
2723
|
for (const nestedColumn of componentSection.section ?? []) {
|
|
2382
|
-
this.extractSummaryObservationFields(nestedColumn, componentData);
|
|
2724
|
+
this.extractSummaryObservationFields(nestedColumn, componentData, templateUtilities);
|
|
2383
2725
|
}
|
|
2384
2726
|
if (Object.keys(componentData).length > 0) {
|
|
2385
2727
|
components.push(componentData);
|
|
2386
2728
|
}
|
|
2387
2729
|
}
|
|
2388
2730
|
} else {
|
|
2389
|
-
this.extractSummaryObservationFields(columnData, data);
|
|
2731
|
+
this.extractSummaryObservationFields(columnData, data, templateUtilities);
|
|
2390
2732
|
}
|
|
2391
2733
|
} else if (resourceItem.title === "DiagnosticReportLab Summary Grouped by DiagnosticReport|Lab Code") {
|
|
2392
2734
|
switch (columnData.title) {
|
|
2393
2735
|
case "Diagnostic Report Name":
|
|
2394
|
-
data["report"] = columnData.text?.div ?? "";
|
|
2736
|
+
data["report"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2395
2737
|
break;
|
|
2396
2738
|
case "Performer":
|
|
2397
|
-
data["performer"] = columnData.text?.div ?? "";
|
|
2739
|
+
data["performer"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2398
2740
|
break;
|
|
2399
2741
|
case "Issued Date":
|
|
2400
|
-
data["issued"] = columnData.text?.div ?? "";
|
|
2742
|
+
data["issued"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2401
2743
|
break;
|
|
2402
2744
|
case "Status":
|
|
2403
|
-
data["status"] = columnData.text?.div ?? "";
|
|
2745
|
+
data["status"] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2404
2746
|
break;
|
|
2405
2747
|
default:
|
|
2406
2748
|
break;
|
|
@@ -2425,8 +2767,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2425
2767
|
observationhtml += `
|
|
2426
2768
|
<tr>
|
|
2427
2769
|
<td>${componentCode}</td>
|
|
2428
|
-
<td>${component["formattedValue"] ?? "-"}</td>
|
|
2429
|
-
<td>${component["referenceRange"]?.trim() ?? "-"}</td>
|
|
2770
|
+
<td>${templateUtilities.renderTextAsHtml(component["formattedValue"]) ?? "-"}</td>
|
|
2771
|
+
<td>${templateUtilities.renderTextAsHtml(component["referenceRange"])?.trim() ?? "-"}</td>
|
|
2430
2772
|
<td>${date ?? "-"}</td>
|
|
2431
2773
|
</tr>`;
|
|
2432
2774
|
}
|
|
@@ -2439,8 +2781,8 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2439
2781
|
observationhtml += `
|
|
2440
2782
|
<tr>
|
|
2441
2783
|
<td>${data["code"] ?? "-"}</td>
|
|
2442
|
-
<td>${data["formattedValue"] ?? "-"}</td>
|
|
2443
|
-
<td>${data["referenceRange"]?.trim() ?? "-"}</td>
|
|
2784
|
+
<td>${templateUtilities.renderTextAsHtml(data["formattedValue"]) ?? "-"}</td>
|
|
2785
|
+
<td>${templateUtilities.renderTextAsHtml(data["referenceRange"])?.trim() ?? "-"}</td>
|
|
2444
2786
|
<td>${date ?? "-"}</td>
|
|
2445
2787
|
</tr>`;
|
|
2446
2788
|
}
|
|
@@ -2491,30 +2833,80 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2491
2833
|
const observations = this.getObservations(resources);
|
|
2492
2834
|
if (observations.length > 0) {
|
|
2493
2835
|
observations.sort((a, b) => {
|
|
2494
|
-
const dateA =
|
|
2495
|
-
const dateB =
|
|
2496
|
-
return dateA && dateB ?
|
|
2836
|
+
const dateA = this.getObservationDate(a);
|
|
2837
|
+
const dateB = this.getObservationDate(b);
|
|
2838
|
+
return dateA && dateB ? dateB.getTime() - dateA.getTime() : 0;
|
|
2497
2839
|
});
|
|
2840
|
+
this.filterObservationForLoincCodes(observations);
|
|
2498
2841
|
html += this.renderObservations(templateUtilities, observations, timezone);
|
|
2499
2842
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
diagnosticReports.
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2843
|
+
if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
|
|
2844
|
+
const diagnosticReports = this.getDiagnosticReports(resources);
|
|
2845
|
+
if (diagnosticReports.length > 0) {
|
|
2846
|
+
diagnosticReports.sort((a, b) => {
|
|
2847
|
+
const dateA = a.issued;
|
|
2848
|
+
const dateB = b.issued;
|
|
2849
|
+
return dateA && dateB ? new Date(dateB).getTime() - new Date(dateA).getTime() : 0;
|
|
2850
|
+
});
|
|
2851
|
+
html += this.renderDiagnosticReports(templateUtilities, diagnosticReports, timezone);
|
|
2852
|
+
}
|
|
2508
2853
|
}
|
|
2509
2854
|
return html;
|
|
2510
2855
|
}
|
|
2856
|
+
static filterObservationForLoincCodes(observations) {
|
|
2857
|
+
const labsAdded = /* @__PURE__ */ new Set();
|
|
2858
|
+
const filteredObservations = [];
|
|
2859
|
+
for (const obs of observations) {
|
|
2860
|
+
const loincCode = this.getObservationLoincCode(obs);
|
|
2861
|
+
if (loincCode && loincToLabName[loincCode]) {
|
|
2862
|
+
const labName = loincToLabName[loincCode];
|
|
2863
|
+
if (!labsAdded.has(labName)) {
|
|
2864
|
+
labsAdded.add(labName);
|
|
2865
|
+
filteredObservations.push(obs);
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
observations.length = 0;
|
|
2870
|
+
observations.push(...filteredObservations);
|
|
2871
|
+
}
|
|
2872
|
+
static getObservationLoincCode(obs) {
|
|
2873
|
+
if (obs.code && obs.code.coding) {
|
|
2874
|
+
for (const coding of obs.code.coding) {
|
|
2875
|
+
if (coding.system && coding.system.toLowerCase().includes("loinc") && coding.code) {
|
|
2876
|
+
return coding.code;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
return void 0;
|
|
2881
|
+
}
|
|
2882
|
+
static getObservationDate(obs) {
|
|
2883
|
+
let obsDate = void 0;
|
|
2884
|
+
if (obs.effectiveDateTime) {
|
|
2885
|
+
obsDate = new Date(obs.effectiveDateTime);
|
|
2886
|
+
} else if (obs.effectivePeriod) {
|
|
2887
|
+
if (obs.effectivePeriod.start) {
|
|
2888
|
+
obsDate = new Date(obs.effectivePeriod.start);
|
|
2889
|
+
} else if (obs.effectivePeriod.end) {
|
|
2890
|
+
obsDate = new Date(obs.effectivePeriod.end);
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
return obsDate;
|
|
2894
|
+
}
|
|
2511
2895
|
/**
|
|
2512
2896
|
* Get all Observation resources from the resource array
|
|
2513
2897
|
* @param resources - FHIR resources array
|
|
2514
2898
|
* @returns Array of Observation resources
|
|
2515
2899
|
*/
|
|
2516
2900
|
static getObservations(resources) {
|
|
2517
|
-
return resources.filter((resourceItem) =>
|
|
2901
|
+
return resources.filter((resourceItem) => {
|
|
2902
|
+
if (resourceItem.resourceType === "Observation") {
|
|
2903
|
+
const obsDate = this.getObservationDate(resourceItem);
|
|
2904
|
+
if (obsDate && obsDate >= new Date(Date.now() - RESULT_SUMMARY_OBSERVATION_DATE_FILTER)) {
|
|
2905
|
+
return true;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
return false;
|
|
2909
|
+
}).map((resourceItem) => resourceItem);
|
|
2518
2910
|
}
|
|
2519
2911
|
/**
|
|
2520
2912
|
* Get all DiagnosticReport resources from the resource array
|
|
@@ -2532,34 +2924,32 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2532
2924
|
* @returns HTML string for rendering
|
|
2533
2925
|
*/
|
|
2534
2926
|
static renderObservations(templateUtilities, observations, timezone) {
|
|
2535
|
-
let html =
|
|
2536
|
-
|
|
2927
|
+
let html = "";
|
|
2928
|
+
if (process.env.ENABLE_DIAGNOSTIC_REPORTS_IN_SUMMARY === "true") {
|
|
2929
|
+
html += `
|
|
2930
|
+
<h3>Observations</h3>`;
|
|
2931
|
+
}
|
|
2932
|
+
html += `
|
|
2537
2933
|
<table>
|
|
2538
2934
|
<thead>
|
|
2539
2935
|
<tr>
|
|
2540
2936
|
<th>Code</th>
|
|
2541
2937
|
<th>Result</th>
|
|
2542
|
-
<th>Unit</th>
|
|
2543
|
-
<th>Interpretation</th>
|
|
2544
2938
|
<th>Reference Range</th>
|
|
2545
|
-
<th>Comments</th>
|
|
2546
2939
|
<th>Date</th>
|
|
2547
2940
|
</tr>
|
|
2548
2941
|
</thead>
|
|
2549
2942
|
<tbody>`;
|
|
2550
2943
|
const observationAdded = /* @__PURE__ */ new Set();
|
|
2551
2944
|
for (const obs of observations) {
|
|
2552
|
-
const obsCode = templateUtilities.codeableConcept(obs.code);
|
|
2945
|
+
const obsCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code));
|
|
2553
2946
|
if (!observationAdded.has(obsCode)) {
|
|
2554
2947
|
observationAdded.add(obsCode);
|
|
2555
2948
|
html += `
|
|
2556
2949
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2557
2950
|
<td>${obsCode}</td>
|
|
2558
2951
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2559
|
-
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
2560
|
-
<td>${templateUtilities.firstFromCodeableConceptList(obs.interpretation)}</td>
|
|
2561
2952
|
<td>${templateUtilities.concatReferenceRange(obs.referenceRange)}</td>
|
|
2562
|
-
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
2563
2953
|
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
2564
2954
|
</tr>`;
|
|
2565
2955
|
}
|
|
@@ -2591,7 +2981,7 @@ var DiagnosticResultsTemplate = class _DiagnosticResultsTemplate {
|
|
|
2591
2981
|
<tbody>`;
|
|
2592
2982
|
const diagnosticReportAdded = /* @__PURE__ */ new Set();
|
|
2593
2983
|
for (const report of reports) {
|
|
2594
|
-
const reportName = templateUtilities.codeableConcept(report.code);
|
|
2984
|
+
const reportName = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(report.code));
|
|
2595
2985
|
if (!diagnosticReportAdded.has(reportName)) {
|
|
2596
2986
|
diagnosticReportAdded.add(reportName);
|
|
2597
2987
|
let resultCount = "";
|
|
@@ -2705,7 +3095,7 @@ var HistoryOfProceduresTemplate = class _HistoryOfProceduresTemplate {
|
|
|
2705
3095
|
const proc = resourceItem;
|
|
2706
3096
|
html += `
|
|
2707
3097
|
<tr id="${templateUtilities.narrativeLinkId(proc)}">
|
|
2708
|
-
<td>${templateUtilities.codeableConcept(proc.code, "display")}</td>
|
|
3098
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(proc.code, "display"))}</td>
|
|
2709
3099
|
<td>${templateUtilities.renderNotes(proc.note, timezone)}</td>
|
|
2710
3100
|
<td>${proc.performedDateTime ? templateUtilities.renderTime(proc.performedDateTime, timezone) : proc.performedPeriod ? templateUtilities.renderPeriod(proc.performedPeriod, timezone) : ""}</td>
|
|
2711
3101
|
</tr>`;
|
|
@@ -2757,7 +3147,7 @@ var SocialHistoryTemplate = class _SocialHistoryTemplate {
|
|
|
2757
3147
|
for (const obs of observations) {
|
|
2758
3148
|
html += `
|
|
2759
3149
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
2760
|
-
<td>${templateUtilities.codeableConcept(obs.code)}</td>
|
|
3150
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(obs.code))}</td>
|
|
2761
3151
|
<td>${templateUtilities.extractObservationValue(obs)}</td>
|
|
2762
3152
|
<td>${templateUtilities.extractObservationValueUnit(obs)}</td>
|
|
2763
3153
|
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
@@ -2802,7 +3192,7 @@ var PastHistoryOfIllnessTemplate = class {
|
|
|
2802
3192
|
<tbody>`;
|
|
2803
3193
|
const addedConditionCodes = /* @__PURE__ */ new Set();
|
|
2804
3194
|
for (const cond of resolvedConditions) {
|
|
2805
|
-
const conditionCode = templateUtilities.codeableConcept(cond.code);
|
|
3195
|
+
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
|
|
2806
3196
|
if (!addedConditionCodes.has(conditionCode)) {
|
|
2807
3197
|
addedConditionCodes.add(conditionCode);
|
|
2808
3198
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
@@ -2888,7 +3278,7 @@ var PlanOfCareTemplate = class {
|
|
|
2888
3278
|
const data = {};
|
|
2889
3279
|
for (const columnData of rowData.section ?? []) {
|
|
2890
3280
|
if (columnData.title) {
|
|
2891
|
-
data[columnData.title] = columnData.text?.div ?? "";
|
|
3281
|
+
data[columnData.title] = templateUtilities.renderTextAsHtml(columnData.text?.div ?? "");
|
|
2892
3282
|
}
|
|
2893
3283
|
}
|
|
2894
3284
|
if (data["status"] !== "active") {
|
|
@@ -2964,7 +3354,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
2964
3354
|
<tbody>`;
|
|
2965
3355
|
const addedConditionCodes = /* @__PURE__ */ new Set();
|
|
2966
3356
|
for (const cond of activeConditions) {
|
|
2967
|
-
const conditionCode = templateUtilities.codeableConcept(cond.code);
|
|
3357
|
+
const conditionCode = templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(cond.code));
|
|
2968
3358
|
if (!addedConditionCodes.has(conditionCode)) {
|
|
2969
3359
|
addedConditionCodes.add(conditionCode);
|
|
2970
3360
|
html += `<tr id="${templateUtilities.narrativeLinkId(cond)}">
|
|
@@ -3009,7 +3399,7 @@ var FunctionalStatusTemplate = class _FunctionalStatusTemplate {
|
|
|
3009
3399
|
if (impression.finding && impression.finding.length > 0) {
|
|
3010
3400
|
findingsHtml = "<ul>";
|
|
3011
3401
|
for (const finding of impression.finding) {
|
|
3012
|
-
const findingText = finding.itemCodeableConcept ? templateUtilities.codeableConcept(finding.itemCodeableConcept) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
|
|
3402
|
+
const findingText = finding.itemCodeableConcept ? templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(finding.itemCodeableConcept)) : finding.itemReference ? templateUtilities.renderReference(finding.itemReference) : "";
|
|
3013
3403
|
const cause = finding.basis || "";
|
|
3014
3404
|
findingsHtml += `<li>${findingText}${cause ? ` - ${cause}` : ""}</li>`;
|
|
3015
3405
|
}
|
|
@@ -3070,9 +3460,9 @@ var PregnancyTemplate = class _PregnancyTemplate {
|
|
|
3070
3460
|
const obs = resource;
|
|
3071
3461
|
html += `
|
|
3072
3462
|
<tr id="${templateUtilities.narrativeLinkId(obs)}">
|
|
3073
|
-
<td>${templateUtilities.extractPregnancyStatus(obs)}</td>
|
|
3463
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.extractPregnancyStatus(obs))}</td>
|
|
3074
3464
|
<td>${templateUtilities.renderNotes(obs.note, timezone)}</td>
|
|
3075
|
-
<td>${obs.effectiveDateTime ? templateUtilities.renderTime(obs.effectiveDateTime, timezone) : obs.effectivePeriod ? templateUtilities.renderPeriod(obs.effectivePeriod, timezone) : ""}</td>
|
|
3465
|
+
<td>${obs.effectiveDateTime ? templateUtilities.renderTextAsHtml(templateUtilities.renderTime(obs.effectiveDateTime, timezone)) : obs.effectivePeriod ? templateUtilities.renderTextAsHtml(templateUtilities.renderPeriod(obs.effectivePeriod, timezone)) : ""}</td>
|
|
3076
3466
|
</tr>`;
|
|
3077
3467
|
}
|
|
3078
3468
|
html += `
|
|
@@ -3122,7 +3512,7 @@ var AdvanceDirectivesTemplate = class _AdvanceDirectivesTemplate {
|
|
|
3122
3512
|
const consent = resourceItem;
|
|
3123
3513
|
html += `
|
|
3124
3514
|
<tr id="${templateUtilities.narrativeLinkId(consent)}">
|
|
3125
|
-
<td>${templateUtilities.codeableConcept(consent.scope, "display")}</td>
|
|
3515
|
+
<td>${templateUtilities.renderTextAsHtml(templateUtilities.codeableConcept(consent.scope, "display"))}</td>
|
|
3126
3516
|
<td>${consent.status || ""}</td>
|
|
3127
3517
|
<td>${consent.provision?.action ? templateUtilities.concatCodeableConcept(consent.provision.action) : ""}</td>
|
|
3128
3518
|
<td>${consent.dateTime || ""}</td>
|
|
@@ -3143,16 +3533,18 @@ var TypeScriptTemplateMapper = class {
|
|
|
3143
3533
|
* @param resources - FHIR resources
|
|
3144
3534
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3145
3535
|
* @param useSectionSummary - Whether to use the section summary for narrative generation
|
|
3536
|
+
* @param now - Optional current date to use for generating relative dates in the narrative
|
|
3146
3537
|
* @returns HTML string for rendering
|
|
3147
3538
|
*/
|
|
3148
|
-
static generateNarrative(section, resources, timezone, useSectionSummary = false) {
|
|
3539
|
+
static generateNarrative(section, resources, timezone, useSectionSummary = false, now) {
|
|
3149
3540
|
const templateClass = this.sectionToTemplate[section];
|
|
3150
3541
|
if (!templateClass) {
|
|
3151
3542
|
throw new Error(`No template found for section: ${section}`);
|
|
3152
3543
|
}
|
|
3153
3544
|
return useSectionSummary ? templateClass.generateSummaryNarrative(
|
|
3154
3545
|
resources,
|
|
3155
|
-
timezone
|
|
3546
|
+
timezone,
|
|
3547
|
+
now
|
|
3156
3548
|
) : templateClass.generateNarrative(resources, timezone);
|
|
3157
3549
|
}
|
|
3158
3550
|
};
|
|
@@ -3210,14 +3602,15 @@ var NarrativeGenerator = class {
|
|
|
3210
3602
|
* @param resources - Array of domain resources
|
|
3211
3603
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3212
3604
|
* @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
|
|
3605
|
+
* @param now - Optional date parameter
|
|
3213
3606
|
* @returns Generated HTML content or undefined if no resources
|
|
3214
3607
|
*/
|
|
3215
|
-
static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false) {
|
|
3608
|
+
static async generateNarrativeContentAsync(section, resources, timezone, useSectionSummary = false, now) {
|
|
3216
3609
|
if (!resources || resources.length === 0) {
|
|
3217
3610
|
return void 0;
|
|
3218
3611
|
}
|
|
3219
3612
|
try {
|
|
3220
|
-
const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary);
|
|
3613
|
+
const content = TypeScriptTemplateMapper.generateNarrative(section, resources, timezone, useSectionSummary, now);
|
|
3221
3614
|
if (!content) {
|
|
3222
3615
|
return void 0;
|
|
3223
3616
|
}
|
|
@@ -3239,8 +3632,8 @@ var NarrativeGenerator = class {
|
|
|
3239
3632
|
const options = aggressive ? AGGRESSIVE_MINIFY_OPTIONS : DEFAULT_MINIFY_OPTIONS;
|
|
3240
3633
|
return await htmlMinify(html, options);
|
|
3241
3634
|
} catch (error) {
|
|
3242
|
-
console.warn("HTML minification failed", error);
|
|
3243
|
-
return
|
|
3635
|
+
console.warn("HTML minification failed", error, html);
|
|
3636
|
+
return `${error instanceof Error ? error.message : String(error)}`;
|
|
3244
3637
|
}
|
|
3245
3638
|
}
|
|
3246
3639
|
/**
|
|
@@ -3269,10 +3662,11 @@ var NarrativeGenerator = class {
|
|
|
3269
3662
|
* @param timezone - Optional timezone to use for date formatting
|
|
3270
3663
|
* @param minify - Whether to minify the HTML content (default: true)
|
|
3271
3664
|
* @param useSectionSummary - Whether to use section summary for narrative generation (default: false)
|
|
3665
|
+
* @param now - Optional date parameter
|
|
3272
3666
|
* @returns Promise that resolves to a FHIR Narrative object or undefined if no resources
|
|
3273
3667
|
*/
|
|
3274
|
-
static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false) {
|
|
3275
|
-
const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary);
|
|
3668
|
+
static async generateNarrativeAsync(section, resources, timezone, minify = true, useSectionSummary = false, now) {
|
|
3669
|
+
const content = await this.generateNarrativeContentAsync(section, resources, timezone, useSectionSummary, now);
|
|
3276
3670
|
if (!content) {
|
|
3277
3671
|
return void 0;
|
|
3278
3672
|
}
|
|
@@ -3449,8 +3843,9 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
3449
3843
|
* @param baseUrl - Base URL for the FHIR server (e.g., 'https://example.com/fhir')
|
|
3450
3844
|
* @param timezone - Optional timezone to use for date formatting (e.g., 'America/New_York', 'Europe/London')
|
|
3451
3845
|
* @param patientId - Optional patient ID to use as primary patient for composition reference
|
|
3846
|
+
* @param now - Optional current date to use for composition date (defaults to new Date())
|
|
3452
3847
|
*/
|
|
3453
|
-
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId) {
|
|
3848
|
+
async buildBundleAsync(authorOrganizationId, authorOrganizationName, baseUrl, timezone, patientId, now) {
|
|
3454
3849
|
if (baseUrl.endsWith("/")) {
|
|
3455
3850
|
baseUrl = baseUrl.slice(0, -1);
|
|
3456
3851
|
}
|
|
@@ -3477,20 +3872,22 @@ var ComprehensiveIPSCompositionBuilder = class {
|
|
|
3477
3872
|
// Assuming patient is also a practitioner for simplicity
|
|
3478
3873
|
display: authorOrganizationName
|
|
3479
3874
|
}],
|
|
3480
|
-
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3875
|
+
date: (now || /* @__PURE__ */ new Date()).toISOString(),
|
|
3481
3876
|
title: "International Patient Summary",
|
|
3482
3877
|
section: this.sections,
|
|
3483
3878
|
text: await NarrativeGenerator.generateNarrativeAsync(
|
|
3484
3879
|
"Patient" /* PATIENT */,
|
|
3485
3880
|
this.patients,
|
|
3486
3881
|
timezone,
|
|
3487
|
-
true
|
|
3882
|
+
true,
|
|
3883
|
+
false,
|
|
3884
|
+
now
|
|
3488
3885
|
)
|
|
3489
3886
|
};
|
|
3490
3887
|
const bundle = {
|
|
3491
3888
|
resourceType: "Bundle",
|
|
3492
3889
|
type: "document",
|
|
3493
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3890
|
+
timestamp: (now || /* @__PURE__ */ new Date()).toISOString(),
|
|
3494
3891
|
identifier: {
|
|
3495
3892
|
"system": "urn:ietf:rfc:3986",
|
|
3496
3893
|
"value": "urn:uuid:4dcfd353-49fd-4ab0-b521-c8d57ced74d6"
|