From 64904a8b430d0dfe12a8ff0901b434c2ba9dbeec Mon Sep 17 00:00:00 2001 From: Zeke Critchlow Date: Tue, 6 Feb 2024 16:56:16 -0800 Subject: [PATCH] medical dashboard now calculates length of stay instead of using mock data (#89) * fixed the bug in the authentication.ts file Problem The getUserGroupKey() function was checking the stored user group key against the object's keys instead of the object's values. This caused the function to always return null and broke the protected navbar. Solution - Changed the function to check the object's values instead of the keys. Ticket N/A Documentation N/A Testing - Tested the protected navbar and it is now working as expected. * medical dashboard now calculates length of stay instead of using mock data Problem The medical dashboard was using mock data for length of stay. Solution - Created a new function in `postfestivalDashboard.ts` to calculate length of stay - Integrated the new function into the `postevent-summary.tsx` page Ticket URL N/A Documentation N/A Tests Run - Locally executed `yarn build` and built with no errors. - Checked local dev server and confirmed that the medical dashboard was displaying calculated data. + localhost:3000/medical/dashboards/postevent-summary --- .../dashboard/LengthOfStayCountsTable.tsx | 10 +- app/web/constants/medicalForm.ts | 7 + .../LengthOfStayCountsTableProps.tsx | 0 .../medical/dashboards/postevent-summary.tsx | 20 +- app/web/utils/postfestivalDashboard.ts | 208 ++++++++++++++---- 5 files changed, 193 insertions(+), 52 deletions(-) rename app/web/{components/dashboard => interfaces}/LengthOfStayCountsTableProps.tsx (100%) diff --git a/app/web/components/dashboard/LengthOfStayCountsTable.tsx b/app/web/components/dashboard/LengthOfStayCountsTable.tsx index b55fa92..61b0bd7 100644 --- a/app/web/components/dashboard/LengthOfStayCountsTable.tsx +++ b/app/web/components/dashboard/LengthOfStayCountsTable.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper } from "@mui/material"; -import { LengthOfStayCountsTableProps } from "./LengthOfStayCountsTableProps"; +import { LengthOfStayCountsTableProps } from "../../interfaces/LengthOfStayCountsTableProps"; /** @@ -28,11 +28,11 @@ export const LengthOfStayCountsTable: React.FC = ( - {rows.map((row, index) => ( + {rows.map((row: (string | number)[], index: number) => ( {row.map((cell, cellIndex) => ( - {cell} + {typeof cell === "number" ? cell.toFixed(0) : cell} ))} @@ -42,11 +42,11 @@ export const LengthOfStayCountsTable: React.FC = ( Quartiles {/* Quantiles, Mean, etc. */} - {summaryRows.map((row, index) => ( + {summaryRows.map((row: (string | number)[], index: number) => ( {row.map((cell, cellIndex) => ( - {cell} + {row[0] === "Mean" && typeof cell === "number" ? cell.toFixed(2) : cell} ))} diff --git a/app/web/constants/medicalForm.ts b/app/web/constants/medicalForm.ts index e280018..e44f11f 100644 --- a/app/web/constants/medicalForm.ts +++ b/app/web/constants/medicalForm.ts @@ -42,3 +42,10 @@ export const chiefComplaints = [ "Shortness of Breath", "Trauma", ]; + +export const TriageAcuities = { + Red: "red", + Yellow: "yellow", + Green: "green", + White: "white", +}; diff --git a/app/web/components/dashboard/LengthOfStayCountsTableProps.tsx b/app/web/interfaces/LengthOfStayCountsTableProps.tsx similarity index 100% rename from app/web/components/dashboard/LengthOfStayCountsTableProps.tsx rename to app/web/interfaces/LengthOfStayCountsTableProps.tsx diff --git a/app/web/pages/medical/dashboards/postevent-summary.tsx b/app/web/pages/medical/dashboards/postevent-summary.tsx index 2ea0b9b..5c6f60e 100644 --- a/app/web/pages/medical/dashboards/postevent-summary.tsx +++ b/app/web/pages/medical/dashboards/postevent-summary.tsx @@ -6,7 +6,7 @@ import { ChiefComplaintCountsTable } from "../../../components/dashboard/ChiefCo import { YearSelectionField } from "../../../components/dashboard/YearSelectionField"; import { PatientEncounterAcuityBarChart } from "../../../components/dashboard/PatientEncounterAcuityChart"; import { ChiefComplaintEncounterCountsTable } from "../../../components/dashboard/ChiefComplaintEncounterCountsTable"; -// import { LengthOfStayCountsTable } from "../../../components/dashboard/LengthOfStayCountsTable"; +import { LengthOfStayCountsTable } from "../../../components/dashboard/LengthOfStayCountsTable"; import { calculateChiefComplaintEncounterCountsData, calculateChiefComplaintEncounterCountsSummary } from "../../../utils/postfestivalDashboard"; // import { CommonPresentationsAndTransportsTables } from "../../../components/dashboard/TopTenCommonPresentationsTable"; import { fetchPatientEncountersData } from "../../../utils/postfestivalDashboard"; @@ -18,6 +18,8 @@ import { SubmitAlert } from "../../../interfaces/SubmitAlert"; import { PatientEncounterRow } from "../../../interfaces/PatientEncounterRow"; import { ChiefComplaintCountsTableRowData } from "../../../interfaces/ChiefComplaintCountsTableProps"; // import { ChiefComplaintEncounterCountsTableProps } from "../../../interfaces/ChiefComplaintEncounterCountsTableProps"; +import { calculatePostFestivalLengthOfStayData } from "../../../utils/postfestivalDashboard"; +import { LengthOfStayCountsTableProps } from "../../../interfaces/LengthOfStayCountsTableProps"; /** @@ -40,9 +42,9 @@ const MedicalPostEventSummaryDashboard = () => { const selectedYear = watch("selectedYear"); // Calculated data - const [chiefComplaintEncounterCountsData, setChiefComplaintEncounterCountsData] = useState([]); // TODO: Replace with actual data when API integration is complete - const [chiefComplaintCountRows, setChiefComplaintCountRows] = useState([]); // TODO: Replace with actual data when API integration is complete - // const [lengthOfStayData, setLengthOfStayData] = useState([]); + const [chiefComplaintEncounterCountsData, setChiefComplaintEncounterCountsData] = useState([]); + const [chiefComplaintCountRows, setChiefComplaintCountRows] = useState([]); + const [lengthOfStayData, setLengthOfStayData] = useState({ rows: [], summaryRows: [] }); // const [commonPresentationData, setCommonPresentationData] = useState([]); // When the year is selected, fetch the patient encounters for that year @@ -64,8 +66,8 @@ const MedicalPostEventSummaryDashboard = () => { const chiefComplaintCountRows = calculateChiefComplaintEncounterCountsSummary(patientEncounters); setChiefComplaintCountRows(chiefComplaintCountRows); - // const lengthOfStayData = generatePostFestivalLengthOfStayData(patientEncounters); - // setLengthOfStayData(lengthOfStayData); + const lengthOfStayData = calculatePostFestivalLengthOfStayData(patientEncounters); + setLengthOfStayData(lengthOfStayData); // const commonPresentationData = generatePostFestivalCommonPresentationsData(patientEncounters); // setCommonPresentationData(commonPresentationData); @@ -103,10 +105,10 @@ const MedicalPostEventSummaryDashboard = () => { - {/* - + + - + {/* { - // TODO: Implement actual generation logic - console.log("TODO: Implement actual generation logic:", data); - - const rowsDataCCCount = [ - ["Unknown", 0, 0, 0, 0, 0], - ["0 - 15 mins", 0, 0, 0, 0, 0], - ["16 - 30 mins", 0, 0, 0, 0, 0], - ["31 - 45 mins", 0, 0, 0, 0, 0], - ["46 - 60 mins", 0, 0, 0, 0, 0], - ["61 - 75 mins", 0, 0, 0, 0, 0], - ["76 - 90 mins", 0, 0, 0, 0, 0], - ["91 - 105 mins", 0, 0, 0, 0, 0], - ["106 - 120 mins", 0, 0, 0, 0, 0], - ["121 - 135 mins", 0, 0, 0, 0, 0], - ["136 - 150 mins", 0, 0, 0, 0, 0], - [">150 mins", 0, 0, 0, 0, 0], - ]; - - const summaryRowsCCCount = [ - ["Q1", 0, 0, 0, 0, 0], - ["Mean", 0, 0, 0, 0, 0], - ["Q3", 0, 0, 0, 0, 0], - ["Max", 0, 0, 0, 0, 0], - ]; - - return { rowsDataCCCount, summaryRowsCCCount }; -}; +import { TriageAcuities } from "../constants/medicalForm"; +import { LengthOfStayCountsTableProps } from "../interfaces/LengthOfStayCountsTableProps"; /** * Generates data for the Post Festival Common Presentations table @@ -275,3 +239,171 @@ export function calculateChiefComplaintEncounterCountsSummary( return summaryData; } + +export function calculatePostFestivalLengthOfStayData( + patientEncounters: PatientEncounterRow[] +): LengthOfStayCountsTableProps { + const rowsDataCCCount = [ + ["Unknown", 0, 0, 0, 0, 0], + ["0 - 15 mins", 0, 0, 0, 0, 0], + ["16 - 30 mins", 0, 0, 0, 0, 0], + ["31 - 45 mins", 0, 0, 0, 0, 0], + ["46 - 60 mins", 0, 0, 0, 0, 0], + ["61 - 75 mins", 0, 0, 0, 0, 0], + ["76 - 90 mins", 0, 0, 0, 0, 0], + ["91 - 105 mins", 0, 0, 0, 0, 0], + ["106 - 120 mins", 0, 0, 0, 0, 0], + ["121 - 135 mins", 0, 0, 0, 0, 0], + ["136 - 150 mins", 0, 0, 0, 0, 0], + [">150 mins", 0, 0, 0, 0, 0], + ]; + + const summaryRowsCCCount = [ + ["Q1", 0, 0, 0, 0, 0], + ["Mean", 0, 0, 0, 0, 0], + ["Q3", 0, 0, 0, 0, 0], + ["Max", 0, 0, 0, 0, 0], + ]; + + const losDurations = patientEncounters.map((encounter) => { + const arrivalDateTime = new Date(encounter.arrival_date); + arrivalDateTime.setHours(encounter.arrival_time.getHours()); + arrivalDateTime.setMinutes(encounter.arrival_time.getMinutes()); + arrivalDateTime.setSeconds(encounter.arrival_time.getSeconds()); + + const departureDateTime = new Date(encounter.departure_date); + departureDateTime.setHours(encounter.departure_time.getHours()); + departureDateTime.setMinutes(encounter.departure_time.getMinutes()); + departureDateTime.setSeconds(encounter.departure_time.getSeconds()); + + const durationMinutes = + (departureDateTime.getTime() - arrivalDateTime.getTime()) / (1000 * 60); + + return { durationMinutes, acuity: encounter.triage_acuity }; + }); + + losDurations.forEach(({ durationMinutes, acuity }) => { + let rowIndex; + if (durationMinutes <= 15) { + rowIndex = 1; + } else if (durationMinutes <= 30) { + rowIndex = 2; + } else if (durationMinutes <= 45) { + rowIndex = 3; + } else if (durationMinutes <= 60) { + rowIndex = 4; + } else if (durationMinutes <= 75) { + rowIndex = 5; + } else if (durationMinutes <= 90) { + rowIndex = 6; + } else if (durationMinutes <= 105) { + rowIndex = 7; + } else if (durationMinutes <= 120) { + rowIndex = 8; + } else if (durationMinutes <= 135) { + rowIndex = 9; + } else if (durationMinutes <= 150) { + rowIndex = 10; + } else { + rowIndex = 11; + } + + (rowsDataCCCount[rowIndex][1] as number)++; + + console.log("acuity", acuity); + // Increment the count for the specific triage acuity + switch (acuity) { + case TriageAcuities.Red: + (rowsDataCCCount[rowIndex][2] as number)++; + break; + case TriageAcuities.Yellow: + (rowsDataCCCount[rowIndex][3] as number)++; + break; + case TriageAcuities.Green: + (rowsDataCCCount[rowIndex][4] as number)++; + break; + case TriageAcuities.White: + (rowsDataCCCount[rowIndex][5] as number)++; + break; + } + }); + + // Step 1: Aggregate duration minutes into separate arrays + const totalDurations: number[] = []; + const redDurations: number[] = []; + const yellowDurations: number[] = []; + const greenDurations: number[] = []; + const whiteDurations: number[] = []; + + losDurations.forEach(({ durationMinutes, acuity }) => { + totalDurations.push(durationMinutes); + switch (acuity) { + case TriageAcuities.Red: + redDurations.push(durationMinutes); + break; + case TriageAcuities.Yellow: + yellowDurations.push(durationMinutes); + break; + case TriageAcuities.Green: + greenDurations.push(durationMinutes); + break; + case TriageAcuities.White: + whiteDurations.push(durationMinutes); + break; + } + }); + + // Helper function to calculate mean, quartiles, and max + const calculateStatistics = (durations: number[]) => { + const sorted = durations.sort((a, b) => a - b); + const mean = sorted.reduce((acc, val) => acc + val, 0) / sorted.length; + const Q1 = sorted[Math.floor(sorted.length / 4)]; + const Q3 = sorted[Math.floor(sorted.length * (3 / 4))]; + const max = sorted[sorted.length - 1]; + + return { mean, Q1, Q3, max }; + }; + + // Step 2, 3, 4, 5: Calculate statistics for each category + const totalStats = calculateStatistics(totalDurations); + const redStats = calculateStatistics(redDurations); + const yellowStats = calculateStatistics(yellowDurations); + const greenStats = calculateStatistics(greenDurations); + const whiteStats = calculateStatistics(whiteDurations); + + // Populate summaryRowsCCCount with calculated statistics + summaryRowsCCCount[0] = [ + "Q1", + totalStats.Q1, + redStats.Q1, + yellowStats.Q1, + greenStats.Q1, + whiteStats.Q1, + ]; + summaryRowsCCCount[1] = [ + "Mean", + totalStats.mean, + redStats.mean, + yellowStats.mean, + greenStats.mean, + whiteStats.mean, + ]; + summaryRowsCCCount[2] = [ + "Q3", + totalStats.Q3, + redStats.Q3, + yellowStats.Q3, + greenStats.Q3, + whiteStats.Q3, + ]; + summaryRowsCCCount[3] = [ + "Max", + totalStats.max, + redStats.max, + yellowStats.max, + greenStats.max, + whiteStats.max, + ]; + + return { rows: rowsDataCCCount, summaryRows: summaryRowsCCCount }; +}