From de0e699ca546e16f4dc01b9a1cde1cc349904da1 Mon Sep 17 00:00:00 2001 From: niestrow Date: Thu, 28 Nov 2024 15:34:00 +0100 Subject: [PATCH 01/16] PKDPX-33 Trial Design page --- frontend-v2/src/features/drug/Drug.tsx | 416 ++++++++++++++++++------- 1 file changed, 296 insertions(+), 120 deletions(-) diff --git a/frontend-v2/src/features/drug/Drug.tsx b/frontend-v2/src/features/drug/Drug.tsx index 83a47870b..3e5e9a1f6 100644 --- a/frontend-v2/src/features/drug/Drug.tsx +++ b/frontend-v2/src/features/drug/Drug.tsx @@ -1,15 +1,20 @@ import { + Box, Button, Grid, IconButton, - List, - ListItem, Radio, Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, Tooltip, Typography, } from "@mui/material"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { RootState } from "../../app/store"; import { Compound, @@ -19,17 +24,52 @@ import { useProjectRetrieveQuery, useUnitListQuery, } from "../../app/backendApi"; -import { useFieldArray, useForm, useFormState } from "react-hook-form"; +import { useFieldArray, useForm } from "react-hook-form"; import FloatField from "../../components/FloatField"; import UnitField from "../../components/UnitField"; import { FC, useEffect, useMemo, useState } from "react"; -import DeleteIcon from "@mui/icons-material/Delete"; import TextField from "../../components/TextField"; -import useDirty from "../../hooks/useDirty"; -import useInterval from "../../hooks/useInterval"; -import ConfirmationDialog from "../../components/ConfirmationDialog"; import SelectField from "../../components/SelectField"; import { selectIsProjectShared } from "../login/loginSlice"; +import { TableHeader } from "../../components/TableHeader"; +import EditIcon from "@mui/icons-material/Edit"; +import CheckIcon from "@mui/icons-material/Check"; +import CloseIcon from "@mui/icons-material/Close"; +import Delete from "@mui/icons-material/Delete"; +import { decrementDirtyCount, incrementDirtyCount } from "../main/mainSlice"; +import AddCircleOutlineOutlinedIcon from "@mui/icons-material/AddCircleOutlineOutlined"; +import { getTableHeight } from "../../shared/calculateTableHeights"; + +export const DOUBLE_TABLE_SECOND_BREAKPOINTS = [ + { + minHeight: 1100, + tableHeight: "50vh", + }, + { + minHeight: 1000, + tableHeight: "47vh", + }, + { + minHeight: 900, + tableHeight: "45vh", + }, + { + minHeight: 800, + tableHeight: "40vh", + }, + { + minHeight: 700, + tableHeight: "37vh", + }, + { + minHeight: 600, + tableHeight: "30vh", + }, + { + minHeight: 500, + tableHeight: "28vh", + }, +]; const Drug: FC = () => { const projectId = useSelector( @@ -51,21 +91,18 @@ const Drug: FC = () => { const isSharedWithMe = useSelector((state: RootState) => selectIsProjectShared(state, project), ); - - const [showConfirmDelete, setShowConfirmDelete] = useState(false); + const [isEditIndex, setIsEditIndex] = useState(null); // create a form for the compound data using react-hook-form - const { reset, handleSubmit, control, setValue } = useForm({ - defaultValues: compound || { - name: "", - description: "", - compound_type: "SM", - efficacy_experiments: [], - }, - }); - const { isDirty } = useFormState({ control }); - - useDirty(isDirty); + const { reset, handleSubmit, control, setValue, getValues } = + useForm({ + defaultValues: compound || { + name: "", + description: "", + compound_type: "SM", + efficacy_experiments: [], + }, + }); const { fields: efficacy_experiments, @@ -81,6 +118,8 @@ const Drug: FC = () => { reset(compound); }, [compound, reset]); + const dispatch = useDispatch(); + const submit = useMemo( () => handleSubmit((data) => { @@ -93,33 +132,34 @@ const Drug: FC = () => { data.efficacy_experiments = data.efficacy_experiments.filter( (efficacy_experiment) => efficacy_experiment.compound !== undefined, ); - updateCompound({ id: compound.id, compound: data }).then((result) => { - // if the compound has no efficacy experiments, but the result has, then set the first one as the use_efficacy - if (result?.data) { - if ( - compound.efficacy_experiments.length === 0 && - result.data.efficacy_experiments.length > 0 - ) { - updateCompound({ - id: compound.id, - compound: { - ...data, - use_efficacy: result.data.efficacy_experiments[0].id, - }, - }); + dispatch(incrementDirtyCount()); + updateCompound({ id: compound.id, compound: data }) + .then((result) => { + // if the compound has no efficacy experiments, but the result has, then set the first one as the use_efficacy + if (result?.data) { + if ( + compound.efficacy_experiments.length === 0 && + result.data.efficacy_experiments.length > 0 + ) { + updateCompound({ + id: compound.id, + compound: { + ...data, + use_efficacy: result.data.efficacy_experiments[0].id, + }, + }); + } } - } - }); + }) + .then(() => dispatch(decrementDirtyCount())); } }), [compound, handleSubmit, updateCompound], ); - useInterval({ - callback: submit, - delay: 1000, - isDirty, - }); + useEffect(() => { + submit(); + }, [efficacy_experiments?.length]); const addNewEfficacyExperiment = () => { append([ @@ -134,7 +174,13 @@ const Drug: FC = () => { }; const deleteEfficacyExperiment = (index: number) => { - remove(index); + if ( + window.confirm( + "Are you sure you want to permanently delete this efficacy-safety data?", + ) + ) { + remove(index); + } }; if (isProjectLoading || isCompoundLoading || isLoadingUnits) { @@ -174,16 +220,24 @@ const Drug: FC = () => { const defaultProps = { disabled: isSharedWithMe }; + const handleOnEdit = (index: number, isEdit: boolean) => { + setIsEditIndex(isEdit ? index : null); + }; + return (
-
+ +
- + Drug Properties { textFieldProps={defaultProps} /> { /> - + + + Target Properties { textFieldProps={defaultProps} /> {
- - - Efficacy-Safety Data - - - + + + + Efficacy-Safety Data + + + + + + + + + + + + + Select + Name + C50 + Unit + Hill-coefficient + Actions + + {efficacy_experiments.map((efficacy_experiment, index) => ( - - -
+ + - -
- + + handleSelectEfficacy( efficacy_experiment as unknown as EfficacyRead, - )} - onClick={() => - handleSelectEfficacy( - efficacy_experiment as unknown as EfficacyRead, - ) - } - disabled={isSharedWithMe} - /> - Select -
-
- - setShowConfirmDelete(true)} + ) + } disabled={isSharedWithMe} - > - - - - { - deleteEfficacyExperiment(index); - setShowConfirmDelete(false); - }} - onCancel={() => setShowConfirmDelete(false)} + /> +
+ + + + {isEditIndex === index ? ( + - - - + ) : ( + + {getValues(`efficacy_experiments.${index}.name`) || "-"} + + )} + + + {isEditIndex === index ? ( + ) : ( + + {getValues(`efficacy_experiments.${index}.c50`) || "-"} + + )} + + + {isEditIndex === index ? ( { compound={compound} selectProps={defaultProps} /> -
- - -
+ ) : ( + + {getValues(`efficacy_experiments.${index}.c50_unit`) || + "-"} + + )} + + + {isEditIndex === index ? ( + + ) : ( + + {getValues( + `efficacy_experiments.${index}.hill_coefficient`, + ) || "-"} + + )} + + + {isEditIndex === index ? ( + + + { + submit(); + handleOnEdit(index, false); + }} + > + + + + + { + reset(); + handleOnEdit(index, false); + }} + > + + + + + ) : ( + + + + handleOnEdit(index, true)} + > + + + + + + + deleteEfficacyExperiment(index)} + > + + + + + + )} + + ))} - - - +
+
+
); }; From eeffde64d1ff2a584a57ea1ea364e765620e0910 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Thu, 28 Nov 2024 14:45:46 +0000 Subject: [PATCH 02/16] fix(a11y): missing labels for project radio buttons (#604) Make sure that each radio button in the Projects list has a clickable label. --- frontend-v2/src/features/projects/Project.tsx | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend-v2/src/features/projects/Project.tsx b/frontend-v2/src/features/projects/Project.tsx index cefd8b476..4a90759cf 100644 --- a/frontend-v2/src/features/projects/Project.tsx +++ b/frontend-v2/src/features/projects/Project.tsx @@ -31,7 +31,11 @@ import { useProjectAccessDestroyMutation, } from "../../app/backendApi"; import UserAccess from "./UserAccess"; -import { decrementDirtyCount, incrementDirtyCount, setProject } from "../main/mainSlice"; +import { + decrementDirtyCount, + incrementDirtyCount, + setProject, +} from "../main/mainSlice"; import TextField from "../../components/TextField"; import ConfirmationDialog from "../../components/ConfirmationDialog"; import { selectCurrentUser, selectIsProjectShared } from "../login/loginSlice"; @@ -149,12 +153,16 @@ const ProjectRow: FC = ({ handleSubmit((data: FormData) => { if (compound && project) { if (JSON.stringify(compound) !== JSON.stringify(data.compound)) { - dispatch(incrementDirtyCount()) - updateCompound({ id: compound.id, compound: data.compound }).then(() => dispatch(decrementDirtyCount()));; + dispatch(incrementDirtyCount()); + updateCompound({ id: compound.id, compound: data.compound }).then( + () => dispatch(decrementDirtyCount()), + ); } if (JSON.stringify(project) !== JSON.stringify(data.project)) { - dispatch(incrementDirtyCount()) - updateProject({ id: project.id, project: data.project }).then(() => dispatch(decrementDirtyCount())); + dispatch(incrementDirtyCount()); + updateProject({ id: project.id, project: data.project }).then(() => + dispatch(decrementDirtyCount()), + ); } } }), @@ -249,6 +257,7 @@ const ProjectRow: FC = ({ checked={isSelected} onClick={handleSelectProject} size="small" + id={`project-${project.id}`} /> @@ -257,12 +266,14 @@ const ProjectRow: FC = ({ name="project.name" control={control} textFieldProps={defaultProps} - size='small' + size="small" rules={{ required: true, validate: validateName }} sx={{ ...defaultSx }} /> ) : ( - {project?.name} + )} @@ -271,7 +282,7 @@ const ProjectRow: FC = ({ name="compound.name" control={control} textFieldProps={defaultProps} - size='small' + size="small" rules={{ required: true }} sx={defaultSx} /> @@ -416,4 +427,4 @@ const ProjectRow: FC = ({ ); }; -export default ProjectRow; \ No newline at end of file +export default ProjectRow; From 5b812ef23142ea17ce6cba525d28b159a73bf4c3 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Thu, 28 Nov 2024 15:37:25 +0000 Subject: [PATCH 03/16] fix(frontend): undefined values crash the app (#605) Catch undefined values in a couple of places, where they could crash the whole app if either: - there are no time intervals defined for a model. - a selected variable doesn't exist in `simulation.outputs`. --- frontend-v2/src/features/results/useParameters.tsx | 2 +- frontend-v2/src/features/results/utils.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend-v2/src/features/results/useParameters.tsx b/frontend-v2/src/features/results/useParameters.tsx index fe1b32797..9d3beddf6 100644 --- a/frontend-v2/src/features/results/useParameters.tsx +++ b/frontend-v2/src/features/results/useParameters.tsx @@ -137,7 +137,7 @@ export function useParameters() { } function timeConversionFactor(interval: TimeIntervalRead) { - const displayUnit = units?.find((unit) => unit.id === interval.unit); + const displayUnit = units?.find((unit) => unit.id === interval?.unit); const modelUnit = displayUnit?.compatible_units.find( (u) => u.symbol === "h", ); diff --git a/frontend-v2/src/features/results/utils.ts b/frontend-v2/src/features/results/utils.ts index e1b9900a7..23b5e45d7 100644 --- a/frontend-v2/src/features/results/utils.ts +++ b/frontend-v2/src/features/results/utils.ts @@ -33,7 +33,13 @@ export function valuesPerInterval( ) { const times = simulation?.time || []; const tMax = times[times.length - 1]; - const values = variable && simulation ? simulation.outputs[variable.id] : []; + if (!variable || !simulation) { + return timeIntervals.map(() => []); + } + if (!simulation.outputs[variable.id]) { + return timeIntervals.map(() => []); + } + const values = simulation.outputs[variable.id]; return timeIntervals.map((interval) => { if (values.length === 0) { return []; From 520f82d216f6b239897036417d9b76d434e1a63f Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Thu, 28 Nov 2024 16:03:10 +0000 Subject: [PATCH 04/16] bug: fix simulate not running when protocols different (#606) --- frontend-v2/src/features/simulation/useSimulation.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend-v2/src/features/simulation/useSimulation.ts b/frontend-v2/src/features/simulation/useSimulation.ts index fdf150694..4aaaa3ea8 100644 --- a/frontend-v2/src/features/simulation/useSimulation.ts +++ b/frontend-v2/src/features/simulation/useSimulation.ts @@ -26,7 +26,6 @@ export default function useSimulation( const { setSimulations } = useContext(SimulationContext); const [loadingSimulate, setLoadingSimulate] = useState(false); const [data, setData] = useState([]); - const [lastSimulate, setLastSimulate] = useState(); const [simulate, { error: simulateErrorBase }] = useCombinedModelSimulateCreateMutation(); const simulateError: ErrorObject | undefined = simulateErrorBase @@ -38,8 +37,6 @@ export default function useSimulation( useEffect(() => { let ignore = false; - const simulateInputsDifferent = - JSON.stringify(simInputs) !== JSON.stringify(lastSimulate); if ( runSimulation && simInputs.outputs?.length > 1 && @@ -47,8 +44,7 @@ export default function useSimulation( model && protocols && compound && - SIMULATION_PAGES.includes(page) && - simulateInputsDifferent + SIMULATION_PAGES.includes(page) ) { setLoadingSimulate(true); console.log("Simulating with params", simInputs.variables); @@ -62,7 +58,6 @@ export default function useSimulation( const responseData = response.data as SimulateResponse[]; setData(responseData); setSimulations(responseData); - setLastSimulate(simInputs); } } }); From f1383fd1cbd218c65ad625b4486d6a8b99f5fa0f Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Thu, 28 Nov 2024 16:06:02 +0000 Subject: [PATCH 05/16] feat(frontend): add start and end as secondary parameters (#607) Two new parameters: the values of each variable at the start and end of each interval. --- frontend-v2/src/features/results/columns.tsx | 3 +- .../src/features/results/useParameters.tsx | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/frontend-v2/src/features/results/columns.tsx b/frontend-v2/src/features/results/columns.tsx index 866c65903..abb75b783 100644 --- a/frontend-v2/src/features/results/columns.tsx +++ b/frontend-v2/src/features/results/columns.tsx @@ -65,8 +65,7 @@ export function columns({ return parameters.map((parameter) => { return { header: parameter.name, - value: (interval: TimeIntervalRead) => - parameter.value(interval, simulation, variable, aucVariable), + value: parameter.value, }; }); } diff --git a/frontend-v2/src/features/results/useParameters.tsx b/frontend-v2/src/features/results/useParameters.tsx index 9d3beddf6..5b9103cb0 100644 --- a/frontend-v2/src/features/results/useParameters.tsx +++ b/frontend-v2/src/features/results/useParameters.tsx @@ -146,6 +146,42 @@ export function useParameters() { } return [ + { + name: "Start", + value( + interval: TimeIntervalRead, + simulation: SimulateResponse, + variable: VariableRead, + ) { + const [intervalValues] = variablePerInterval( + intervals, + variable, + simulation, + interval, + ); + const start = intervalValues ? intervalValues[0] : 0; + return formattedNumber(start / variableConversionFactor(variable)); + }, + }, + { + name: "End", + value( + interval: TimeIntervalRead, + simulation: SimulateResponse, + variable: VariableRead, + ) { + const [intervalValues] = variablePerInterval( + intervals, + variable, + simulation, + interval, + ); + const end = intervalValues + ? intervalValues[intervalValues.length - 1] + : 0; + return formattedNumber(end / variableConversionFactor(variable)); + }, + }, { name: "Min", value( From 8e7b72a591f660d58df445cff43481886a058fa9 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Thu, 28 Nov 2024 16:43:50 +0000 Subject: [PATCH 06/16] fix(frontend): app crashes when intervals is empty (#608) Fix another crash that can happen when the current time interval is undefined. --- frontend-v2/src/features/results/useParameters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend-v2/src/features/results/useParameters.tsx b/frontend-v2/src/features/results/useParameters.tsx index 5b9103cb0..5556c02f4 100644 --- a/frontend-v2/src/features/results/useParameters.tsx +++ b/frontend-v2/src/features/results/useParameters.tsx @@ -77,7 +77,7 @@ const variablePerInterval = ( variable, simulation, ); - const intervalIndex = intervals.findIndex((i) => i.id === interval.id); + const intervalIndex = intervals.findIndex((i) => i.id === interval?.id); const intervalValues = variableValuesPerInterval[intervalIndex]; const timePerInterval = timesPerInterval(simulation.time, intervals); const intervalTimes = timePerInterval[intervalIndex]; From fd68bc67399f14402e81546eb3b4b3125a9cd9fa Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Thu, 28 Nov 2024 17:39:01 +0000 Subject: [PATCH 07/16] fix(frontend): interpolation errors for secondary parameters (#609) - Set an error tolerance of 1e-8 and treat smaller values as zero. - Add exceptions to handle the case where an interval has a duration of zero. --- frontend-v2/src/features/results/utils.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend-v2/src/features/results/utils.ts b/frontend-v2/src/features/results/utils.ts index 23b5e45d7..aca1e377d 100644 --- a/frontend-v2/src/features/results/utils.ts +++ b/frontend-v2/src/features/results/utils.ts @@ -8,6 +8,8 @@ import { import { Parameter } from "./useParameters"; import { columns } from "./columns"; +const TOLERANCE = 1e-8; + /** * Given x0 in the range [x[0], x[1]], return the linearly interpolated value y0 in the range [y[0], y[1]]. * @param x @@ -48,6 +50,9 @@ export function valuesPerInterval( const endTime = Math.min(tMax, interval.end_time); const startIndex = times.findIndex((t) => t >= startTime); const endIndex = times.findIndex((t) => t >= endTime); + if (startIndex === endIndex) { + return [values[startIndex], values[endIndex]]; + } const start = startIndex > 0 ? interpolate( @@ -89,6 +94,9 @@ export function timesPerInterval( const endTime = Math.min(tMax, interval.end_time); const startIndex = times.findIndex((t) => t >= startTime); const endIndex = times.findIndex((t) => t >= endTime); + if (startIndex === endIndex) { + return [times[startIndex], times[endIndex]]; + } const intervalTimes = [ startTime, ...times.slice(startIndex + 1, endIndex - 1), @@ -178,6 +186,9 @@ export function formattedNumber( if (value === 0) { return "0.000"; } + if (Math.abs(value) < TOLERANCE) { + return "0.000"; + } return value > upperThreshold || value < lowerThreshold ? value.toExponential(4) : value.toFixed(3); From 003f67b502e3f26f97a21b105a1abcc2f1490c15 Mon Sep 17 00:00:00 2001 From: niestrow Date: Fri, 29 Nov 2024 09:13:04 +0100 Subject: [PATCH 08/16] Added label for efficacy experiment name --- frontend-v2/src/features/drug/Drug.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/frontend-v2/src/features/drug/Drug.tsx b/frontend-v2/src/features/drug/Drug.tsx index 3e5e9a1f6..20169d318 100644 --- a/frontend-v2/src/features/drug/Drug.tsx +++ b/frontend-v2/src/features/drug/Drug.tsx @@ -224,6 +224,8 @@ const Drug: FC = () => { setIsEditIndex(isEdit ? index : null); }; + console.log("efficacy_experiments", efficacy_experiments); + return (
@@ -291,7 +293,7 @@ const Drug: FC = () => { Efficacy-Safety Data - { {efficacy_experiments.map((efficacy_experiment, index) => ( - { ) } disabled={isSharedWithMe} + id={`efficacy_experiment-${efficacy_experiment?.id}`} />
@@ -365,9 +368,13 @@ const Drug: FC = () => { textFieldProps={defaultProps} /> ) : ( - - {getValues(`efficacy_experiments.${index}.name`) || "-"} - + )}
From bba4cfcf4bb7c72422b9fe71f09aab2b165197e0 Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Fri, 29 Nov 2024 09:02:57 +0000 Subject: [PATCH 09/16] refactor(frontend): error tolerance in results (#610) - increase the tolerance to from 1.e-8 to 5.e-7. - apply the tolerance to `simulation.outputs[variable]`, before applying unit conversion factors. --- frontend-v2/src/features/results/utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend-v2/src/features/results/utils.ts b/frontend-v2/src/features/results/utils.ts index aca1e377d..705f3cb2d 100644 --- a/frontend-v2/src/features/results/utils.ts +++ b/frontend-v2/src/features/results/utils.ts @@ -8,7 +8,7 @@ import { import { Parameter } from "./useParameters"; import { columns } from "./columns"; -const TOLERANCE = 1e-8; +const TOLERANCE = 5e-7; /** * Given x0 in the range [x[0], x[1]], return the linearly interpolated value y0 in the range [y[0], y[1]]. @@ -41,7 +41,9 @@ export function valuesPerInterval( if (!simulation.outputs[variable.id]) { return timeIntervals.map(() => []); } - const values = simulation.outputs[variable.id]; + const values = simulation.outputs[variable.id].map((v) => + Math.abs(v) < TOLERANCE ? 0 : v, + ); return timeIntervals.map((interval) => { if (values.length === 0) { return []; @@ -189,6 +191,7 @@ export function formattedNumber( if (Math.abs(value) < TOLERANCE) { return "0.000"; } + return value > upperThreshold || value < lowerThreshold ? value.toExponential(4) : value.toFixed(3); From 102da5ffe58001fb7e2d9ebd5973a832d2a253fc Mon Sep 17 00:00:00 2001 From: Jim O'Donnell Date: Fri, 29 Nov 2024 09:06:28 +0000 Subject: [PATCH 10/16] refactor(frontend): invert secondary parameter unit conversion (#611) Invert unit conversion so that we calculate the conversion factor from simulation units to display units, rather then vice versa. Then multiply displayed values by the conversion factor rather than dividing. Move helper functions into the module scope. --- .../src/features/results/useParameters.tsx | 71 +++++++++++-------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/frontend-v2/src/features/results/useParameters.tsx b/frontend-v2/src/features/results/useParameters.tsx index 5556c02f4..090f825f8 100644 --- a/frontend-v2/src/features/results/useParameters.tsx +++ b/frontend-v2/src/features/results/useParameters.tsx @@ -1,6 +1,7 @@ import { SimulateResponse, TimeIntervalRead, + UnitListApiResponse, VariableListApiResponse, VariableRead, } from "../../app/backendApi"; @@ -93,6 +94,18 @@ const timeOverLowerThresholdPerInterval = ( return timeOverThreshold(intervalTimes, intervalValues, threshold); }; +function variableConversionFactor( + variable: VariableRead, + units: UnitListApiResponse, +) { + const modelUnit = units?.find((unit) => unit.id === variable.unit); + const displayUnit = modelUnit?.compatible_units.find( + (u) => +u.id === variable.threshold_unit, + ); + const conversionFactor = parseFloat(displayUnit?.conversion_factor || "1"); + return conversionFactor; +} + const timeOverUpperThresholdPerInterval = ( intervalValues: number[], intervalTimes: number[], @@ -102,6 +115,18 @@ const timeOverUpperThresholdPerInterval = ( return timeOverThreshold(intervalTimes, intervalValues, threshold); }; +function timeConversionFactor( + interval: TimeIntervalRead, + units: UnitListApiResponse, +) { + const modelUnit = units?.find((unit) => unit.symbol === "h"); + const displayUnit = modelUnit?.compatible_units.find( + (u) => +u.id === interval.unit, + ); + const conversionFactor = parseFloat(displayUnit?.conversion_factor || "1"); + return conversionFactor; +} + /** * Generate a list of secondary parameters to calculate for the current model. * Each parameter consists of a name and a function to calculate the formatted value of the parameter @@ -125,26 +150,6 @@ export function useParameters() { const intervals = useNormalisedIntervals(baseIntervals); const variables = useNormalisedVariables(baseVariables); - function variableConversionFactor(variable: VariableRead) { - const displayUnit = units?.find( - (unit) => unit.id === variable.threshold_unit, - ); - const modelUnit = displayUnit?.compatible_units.find( - (u) => +u.id === variable.unit, - ); - const conversionFactor = parseFloat(modelUnit?.conversion_factor || "1"); - return conversionFactor; - } - - function timeConversionFactor(interval: TimeIntervalRead) { - const displayUnit = units?.find((unit) => unit.id === interval?.unit); - const modelUnit = displayUnit?.compatible_units.find( - (u) => u.symbol === "h", - ); - const conversionFactor = parseFloat(modelUnit?.conversion_factor || "1"); - return conversionFactor; - } - return [ { name: "Start", @@ -160,7 +165,9 @@ export function useParameters() { interval, ); const start = intervalValues ? intervalValues[0] : 0; - return formattedNumber(start / variableConversionFactor(variable)); + return formattedNumber( + start * variableConversionFactor(variable, units), + ); }, }, { @@ -179,7 +186,7 @@ export function useParameters() { const end = intervalValues ? intervalValues[intervalValues.length - 1] : 0; - return formattedNumber(end / variableConversionFactor(variable)); + return formattedNumber(end * variableConversionFactor(variable, units)); }, }, { @@ -197,7 +204,7 @@ export function useParameters() { ); const min = intervalValues ? Math.min(...intervalValues) : 0; - return formattedNumber(min / variableConversionFactor(variable)); + return formattedNumber(min * variableConversionFactor(variable, units)); }, }, { @@ -214,7 +221,7 @@ export function useParameters() { interval, ); const max = intervalValues ? Math.max(...intervalValues) : 0; - return formattedNumber(max / variableConversionFactor(variable)); + return formattedNumber(max * variableConversionFactor(variable, units)); }, }, { @@ -232,9 +239,9 @@ export function useParameters() { : []; const difference = auc ? auc[auc.length - 1] - auc[0] : 0; return formattedNumber( - difference / - (variableConversionFactor(variable) * - timeConversionFactor(interval)), + difference * + variableConversionFactor(variable, units) * + timeConversionFactor(interval, units), ); }, }, @@ -264,7 +271,9 @@ export function useParameters() { variable, ) : 0; - return formattedNumber(tLower / timeConversionFactor(interval)); + return formattedNumber( + tLower * timeConversionFactor(interval, units), + ); } }, }, @@ -294,7 +303,9 @@ export function useParameters() { variable, ) : 0; - return formattedNumber(tUpper / timeConversionFactor(interval)); + return formattedNumber( + tUpper * timeConversionFactor(interval, units), + ); } }, }, @@ -332,7 +343,7 @@ export function useParameters() { ) : 0; return formattedNumber( - (tLower - tUpper) / timeConversionFactor(interval), + (tLower - tUpper) * timeConversionFactor(interval, units), ); } }, From 53a86434fa6394e03e36444680ede0348af580b1 Mon Sep 17 00:00:00 2001 From: wniestroj <152595753+wniestroj@users.noreply.github.com> Date: Fri, 29 Nov 2024 14:35:00 +0100 Subject: [PATCH 11/16] Fix build typescript issue (#612) --- frontend-v2/src/features/drug/Drug.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend-v2/src/features/drug/Drug.tsx b/frontend-v2/src/features/drug/Drug.tsx index 20169d318..24b8eaa8e 100644 --- a/frontend-v2/src/features/drug/Drug.tsx +++ b/frontend-v2/src/features/drug/Drug.tsx @@ -352,7 +352,7 @@ const Drug: FC = () => { ) } disabled={isSharedWithMe} - id={`efficacy_experiment-${efficacy_experiment?.id}`} + id={`efficacy_experiment-${efficacy_experiment?.theKey}`} />
@@ -369,7 +369,7 @@ const Drug: FC = () => { /> ) : (