diff --git a/frontend-v2/src/App.tsx b/frontend-v2/src/App.tsx index 2ac9667a..1c4a84d9 100644 --- a/frontend-v2/src/App.tsx +++ b/frontend-v2/src/App.tsx @@ -17,27 +17,15 @@ import "react-toastify/dist/ReactToastify.css"; import { SimulationContext } from "./contexts/SimulationContext"; import { SimulateResponse } from "./app/backendApi"; import { CollapsibleSidebarProvider } from "./shared/contexts/CollapsibleSidebarContext"; -import { useModelTimeIntervals } from "./hooks/useModelTimeIntervals"; - -type Threshold = { lower: number; upper: number }; -export type Thresholds = { [key: string]: Threshold }; - -const THRESHOLDS: Thresholds = {}; function App() { const dispatch = useAppDispatch(); const isAuth = useSelector(isAuthenticated); const error = useSelector((state: RootState) => state.login.error); - const [intervals, setIntervals] = useModelTimeIntervals(); const [simulations, setSimulations] = useState([]); - const [thresholds, setThresholds] = useState(THRESHOLDS); const simulationContext = { simulations, setSimulations, - intervals, - setIntervals, - thresholds, - setThresholds, }; const onLogin = (username: string, password: string) => { diff --git a/frontend-v2/src/app/backendApi.ts b/frontend-v2/src/app/backendApi.ts index 84e80b14..16a4ec19 100644 --- a/frontend-v2/src/app/backendApi.ts +++ b/frontend-v2/src/app/backendApi.ts @@ -3507,6 +3507,10 @@ export type Variable = { upper_bound?: number | null; /** default value for this variable */ default_value?: number; + /** lower threshold for this variable */ + lower_threshold?: number | null; + /** upper threshold for this variable */ + upper_threshold?: number | null; /** True if default_value is stored as the log of this value */ is_log?: boolean; /** name of the variable */ @@ -3553,6 +3557,10 @@ export type VariableRead = { upper_bound?: number | null; /** default value for this variable */ default_value?: number; + /** lower threshold for this variable */ + lower_threshold?: number | null; + /** upper threshold for this variable */ + upper_threshold?: number | null; /** True if default_value is stored as the log of this value */ is_log?: boolean; /** name of the variable */ @@ -3598,6 +3606,10 @@ export type PatchedVariable = { upper_bound?: number | null; /** default value for this variable */ default_value?: number; + /** lower threshold for this variable */ + lower_threshold?: number | null; + /** upper threshold for this variable */ + upper_threshold?: number | null; /** True if default_value is stored as the log of this value */ is_log?: boolean; /** name of the variable */ @@ -3644,6 +3656,10 @@ export type PatchedVariableRead = { upper_bound?: number | null; /** default value for this variable */ default_value?: number; + /** lower threshold for this variable */ + lower_threshold?: number | null; + /** upper threshold for this variable */ + upper_threshold?: number | null; /** True if default_value is stored as the log of this value */ is_log?: boolean; /** name of the variable */ diff --git a/frontend-v2/src/contexts/SimulationContext.ts b/frontend-v2/src/contexts/SimulationContext.ts index 8c098528..b889322e 100644 --- a/frontend-v2/src/contexts/SimulationContext.ts +++ b/frontend-v2/src/contexts/SimulationContext.ts @@ -1,10 +1,7 @@ import { createContext } from "react"; -import { SimulateResponse, TimeIntervalRead } from "../app/backendApi"; -import { Thresholds } from "../App"; +import { SimulateResponse } from "../app/backendApi"; export const SimulationContext = createContext({ simulations: [] as SimulateResponse[], setSimulations: (simulations: SimulateResponse[]) => {}, - thresholds: {} as Thresholds, - setThresholds: (thresholds: Thresholds) => {}, }); diff --git a/frontend-v2/src/features/model/VariableRow.tsx b/frontend-v2/src/features/model/VariableRow.tsx index cc59e0e6..6aee9d53 100644 --- a/frontend-v2/src/features/model/VariableRow.tsx +++ b/frontend-v2/src/features/model/VariableRow.tsx @@ -1,5 +1,5 @@ // src/components/ProjectTable.tsx -import { FC, useEffect, useContext } from "react"; +import { FC, useEffect } from "react"; import { Control, useFieldArray, useForm } from "react-hook-form"; import { TableCell, @@ -25,7 +25,6 @@ import { useSelector } from "react-redux"; import { RootState } from "../../app/store"; import { selectIsProjectShared } from "../login/loginSlice"; import useEditProtocol from "./useEditProtocol"; -import { SimulationContext } from "../../contexts/SimulationContext"; interface Props { project: ProjectRead; @@ -80,7 +79,6 @@ const VariableRow: FC = ({ control, name: "model.derived_variables", }); - const { thresholds, setThresholds } = useContext(SimulationContext); const { handleSubmit, @@ -246,16 +244,6 @@ const VariableRow: FC = ({ pkpd_model: model.id, type, }); - - if (type === "AUC") { - setThresholds({ - ...thresholds, - [variable.name]: { - lower: 0, - upper: Infinity, - }, - }); - } }; const removeDerived = (index: number | number[]) => { diff --git a/frontend-v2/src/features/model/secondary/ThresholdsTable.tsx b/frontend-v2/src/features/model/secondary/ThresholdsTable.tsx index 8f170c62..97ce7a79 100644 --- a/frontend-v2/src/features/model/secondary/ThresholdsTable.tsx +++ b/frontend-v2/src/features/model/secondary/ThresholdsTable.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, FC, useContext, useState } from "react"; +import { ChangeEvent, FC, useState } from "react"; import { MenuItem, Select, @@ -21,9 +21,9 @@ import { useProjectRetrieveQuery, useUnitListQuery, useVariableListQuery, + useVariableUpdateMutation, VariableRead, } from "../../../app/backendApi"; -import { SimulationContext } from "../../../contexts/SimulationContext"; function VariableRow({ variable, @@ -32,32 +32,30 @@ function VariableRow({ variable: VariableRead; unit: UnitRead | undefined; }) { - const { thresholds, setThresholds } = useContext(SimulationContext); + const [updateVariable] = useVariableUpdateMutation(); const [unitSymbol, setUnitSymbol] = useState( unit?.symbol, ); function onChangeLowerThreshold(event: ChangeEvent) { const newValue = parseFloat(event.target.value); - const oldThresholds = thresholds[variable.name]; if (!isNaN(newValue)) { - setThresholds({ - ...thresholds, - [variable.name]: { - ...oldThresholds, - lower: newValue, + updateVariable({ + id: variable.id, + variable: { + ...variable, + lower_threshold: newValue, }, }); } } function onChangeUpperThreshold(event: ChangeEvent) { const newValue = parseFloat(event.target.value); - const oldThresholds = thresholds[variable.name]; if (!isNaN(newValue)) { - setThresholds({ - ...thresholds, - [variable.name]: { - ...oldThresholds, - upper: newValue, + updateVariable({ + id: variable.id, + variable: { + ...variable, + upper_threshold: newValue, }, }); } @@ -76,14 +74,14 @@ function VariableRow({ diff --git a/frontend-v2/src/features/results/useParameters.tsx b/frontend-v2/src/features/results/useParameters.tsx index 9851e2a9..c9f62d2e 100644 --- a/frontend-v2/src/features/results/useParameters.tsx +++ b/frontend-v2/src/features/results/useParameters.tsx @@ -1,5 +1,3 @@ -import { useContext } from "react"; - import { SimulateResponse, TimeIntervalRead, @@ -11,9 +9,7 @@ import { timesPerInterval, valuesPerInterval, } from "./utils"; -import { SimulationContext } from "../../contexts/SimulationContext"; import { useVariables } from "./useVariables"; -import { Thresholds } from "../../App"; import { useModelTimeIntervals } from "../../hooks/useModelTimeIntervals"; import { useUnits } from "./useUnits"; @@ -66,32 +62,21 @@ const timeOverLowerThresholdPerInterval = ( intervalValues: number[], intervalTimes: number[], variable: VariableRead, - thresholds: Thresholds, ) => { - const threshold = variable && thresholds[variable.name]; - return timeOverThreshold( - intervalTimes, - intervalValues, - threshold?.lower || 0, - ); + const threshold = variable?.lower_threshold || 0; + return timeOverThreshold(intervalTimes, intervalValues, threshold); }; const timeOverUpperThresholdPerInterval = ( intervalValues: number[], intervalTimes: number[], variable: VariableRead, - thresholds: Thresholds, ) => { - const threshold = variable && thresholds[variable.name]; - return timeOverThreshold( - intervalTimes, - intervalValues, - threshold?.upper || Infinity, - ); + const threshold = variable?.upper_threshold || Infinity; + return timeOverThreshold(intervalTimes, intervalValues, threshold); }; export function useParameters() { - const { thresholds } = useContext(SimulationContext); const [baseIntervals] = useModelTimeIntervals(); const variables = useVariables(); const intervals = useNormalisedIntervals(baseIntervals); @@ -176,7 +161,6 @@ export function useParameters() { intervalValues, intervalTimes, variable, - thresholds, ), ) : 0; @@ -205,7 +189,6 @@ export function useParameters() { intervalValues, intervalTimes, variable, - thresholds, ), ) : 0; @@ -234,13 +217,11 @@ export function useParameters() { intervalValues, intervalTimes, variable, - thresholds, ) - timeOverUpperThresholdPerInterval( intervalValues, intervalTimes, variable, - thresholds, ), ) : 0; diff --git a/pkpdapp/pkpdapp/migrations/0017_auto_20241125_1415.py b/pkpdapp/pkpdapp/migrations/0017_auto_20241125_1415.py new file mode 100644 index 00000000..cc224287 --- /dev/null +++ b/pkpdapp/pkpdapp/migrations/0017_auto_20241125_1415.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.25 on 2024-11-25 14:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pkpdapp', '0016_auto_20241122_1523'), + ] + + operations = [ + migrations.AddField( + model_name='variable', + name='lower_threshold', + field=models.FloatField(blank=True, help_text='lower threshold for this variable', null=True), + ), + migrations.AddField( + model_name='variable', + name='upper_threshold', + field=models.FloatField(blank=True, help_text='upper threshold for this variable', null=True), + ), + ] diff --git a/pkpdapp/pkpdapp/models/variable.py b/pkpdapp/pkpdapp/models/variable.py index 8e3b10ec..cff0eb1c 100644 --- a/pkpdapp/pkpdapp/models/variable.py +++ b/pkpdapp/pkpdapp/models/variable.py @@ -34,6 +34,12 @@ class Variable(StoredModel): default_value = models.FloatField( default=1, help_text="default value for this variable" ) + lower_threshold = models.FloatField( + blank=True, null=True, help_text="lower threshold for this variable" + ) + upper_threshold = models.FloatField( + blank=True, null=True, help_text="upper threshold for this variable" + ) is_log = models.BooleanField( default=False, @@ -411,4 +417,6 @@ def copy(self, variable, new_project): self.unit_symbol = variable.unit_symbol self.constant = variable.constant self.protocol = new_protocol + self.lower_threshold = variable.lower_threshold + self.upper_threshold = variable.upper_threshold self.save() diff --git a/pkpdapp/schema.yml b/pkpdapp/schema.yml index 845bb868..341e9fd6 100644 --- a/pkpdapp/schema.yml +++ b/pkpdapp/schema.yml @@ -4847,6 +4847,16 @@ components: type: number format: double description: default value for this variable + lower_threshold: + type: number + format: double + nullable: true + description: lower threshold for this variable + upper_threshold: + type: number + format: double + nullable: true + description: upper threshold for this variable is_log: type: boolean description: True if default_value is stored as the log of this value @@ -5656,6 +5666,16 @@ components: type: number format: double description: default value for this variable + lower_threshold: + type: number + format: double + nullable: true + description: lower threshold for this variable + upper_threshold: + type: number + format: double + nullable: true + description: upper threshold for this variable is_log: type: boolean description: True if default_value is stored as the log of this value