Skip to content

Commit

Permalink
feat(frontend): units for variable thresholds (#588)
Browse files Browse the repository at this point in the history
- Store display units for variable thresholds separately from variable units.
- Normalise threshold units to `pmol/L` in the Results tables.
  • Loading branch information
eatyourgreens authored Nov 25, 2024
1 parent ced28aa commit 3faa1ec
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 52 deletions.
8 changes: 8 additions & 0 deletions frontend-v2/src/app/backendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3533,6 +3533,8 @@ export type Variable = {
display?: boolean;
/** False/True if biomarker type displayed on LHS/RHS axis */
axis?: boolean;
/** unit for the threshold values */
threshold_unit?: number | null;
/** variable values are in this unit (note this might be different from the unit in the stored sbml) */
unit?: number | null;
/** pharmacodynamic model */
Expand Down Expand Up @@ -3583,6 +3585,8 @@ export type VariableRead = {
display?: boolean;
/** False/True if biomarker type displayed on LHS/RHS axis */
axis?: boolean;
/** unit for the threshold values */
threshold_unit?: number | null;
/** variable values are in this unit (note this might be different from the unit in the stored sbml) */
unit?: number | null;
/** pharmacodynamic model */
Expand Down Expand Up @@ -3632,6 +3636,8 @@ export type PatchedVariable = {
display?: boolean;
/** False/True if biomarker type displayed on LHS/RHS axis */
axis?: boolean;
/** unit for the threshold values */
threshold_unit?: number | null;
/** variable values are in this unit (note this might be different from the unit in the stored sbml) */
unit?: number | null;
/** pharmacodynamic model */
Expand Down Expand Up @@ -3682,6 +3688,8 @@ export type PatchedVariableRead = {
display?: boolean;
/** False/True if biomarker type displayed on LHS/RHS axis */
axis?: boolean;
/** unit for the threshold values */
threshold_unit?: number | null;
/** variable values are in this unit (note this might be different from the unit in the stored sbml) */
unit?: number | null;
/** pharmacodynamic model */
Expand Down
15 changes: 14 additions & 1 deletion frontend-v2/src/features/model/secondary/ThresholdsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function VariableRow({
variable: VariableRead;
unit: UnitRead | undefined;
}) {
const units = useUnits();
const [updateVariable] = useVariableUpdateMutation();
const [unitSymbol, setUnitSymbol] = useState<string | undefined>(
unit?.symbol,
Expand Down Expand Up @@ -99,6 +100,16 @@ function VariableRow({
}
function onChangeUnit(event: SelectChangeEvent) {
setUnitSymbol(event.target.value as string);
const unit = units?.find((unit) => unit.symbol === event.target.value);
if (unit) {
updateVariable({
id: variable.id,
variable: {
...variable,
threshold_unit: unit.id,
},
});
}
}

return (
Expand Down Expand Up @@ -158,7 +169,9 @@ const ThresholdsTable: FC<TableProps> = (props) => {
<VariableRow
key={variable.id}
variable={variable}
unit={units?.find((unit) => unit.id === variable.unit)}
unit={units?.find(
(unit) => unit.id === (variable.threshold_unit || variable.unit),
)}
/>
))}
</TableBody>
Expand Down
137 changes: 86 additions & 51 deletions frontend-v2/src/features/results/useParameters.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
SimulateResponse,
TimeIntervalRead,
VariableListApiResponse,
VariableRead,
} from "../../app/backendApi";
import {
Expand Down Expand Up @@ -41,6 +42,30 @@ function useNormalisedIntervals(intervals: TimeIntervalRead[]) {
});
}

function useNormalisedVariables(variables: VariableListApiResponse) {
const units = useUnits();
return variables.map((variable) => {
const variableUnit = units?.find(
(unit) => unit.id === variable.threshold_unit,
);
const defaultUnit = variableUnit?.compatible_units.find(
(u) => u.symbol === "pmol/L",
);
const conversionFactor = parseFloat(defaultUnit?.conversion_factor || "1");
const lower_threshold = variable.lower_threshold
? variable.lower_threshold * conversionFactor
: null;
const upper_threshold = variable.upper_threshold
? variable.upper_threshold * conversionFactor
: null;
return {
...variable,
lower_threshold,
upper_threshold,
};
});
}

const variablePerInterval = (
intervals: TimeIntervalRead[],
variable: VariableRead,
Expand Down Expand Up @@ -78,8 +103,9 @@ const timeOverUpperThresholdPerInterval = (

export function useParameters() {
const [baseIntervals] = useModelTimeIntervals();
const variables = useVariables();
const baseVariables = useVariables();
const intervals = useNormalisedIntervals(baseIntervals);
const variables = useNormalisedVariables(baseVariables || []);
return [
{
name: "Min",
Expand Down Expand Up @@ -147,23 +173,26 @@ export function useParameters() {
value(
intervalIndex: number,
simulation: SimulateResponse,
variable: VariableRead,
baseVariable: VariableRead,
) {
const [intervalValues, intervalTimes] = variablePerInterval(
intervals,
variable,
simulation,
intervalIndex,
);
return intervalValues
? formattedNumber(
timeOverLowerThresholdPerInterval(
intervalValues,
intervalTimes,
variable,
),
)
: 0;
const variable = variables.find((v) => v.id === baseVariable.id);
if (variable) {
const [intervalValues, intervalTimes] = variablePerInterval(
intervals,
variable,
simulation,
intervalIndex,
);
return intervalValues
? formattedNumber(
timeOverLowerThresholdPerInterval(
intervalValues,
intervalTimes,
variable,
),
)
: 0;
}
},
},
{
Expand All @@ -175,23 +204,26 @@ export function useParameters() {
value(
intervalIndex: number,
simulation: SimulateResponse,
variable: VariableRead,
baseVariable: VariableRead,
) {
const [intervalValues, intervalTimes] = variablePerInterval(
intervals,
variable,
simulation,
intervalIndex,
);
return intervalValues
? formattedNumber(
timeOverUpperThresholdPerInterval(
intervalValues,
intervalTimes,
variable,
),
)
: 0;
const variable = variables.find((v) => v.id === baseVariable.id);
if (variable) {
const [intervalValues, intervalTimes] = variablePerInterval(
intervals,
variable,
simulation,
intervalIndex,
);
return intervalValues
? formattedNumber(
timeOverUpperThresholdPerInterval(
intervalValues,
intervalTimes,
variable,
),
)
: 0;
}
},
},
{
Expand All @@ -203,28 +235,31 @@ export function useParameters() {
value(
intervalIndex: number,
simulation: SimulateResponse,
variable: VariableRead,
baseVariable: VariableRead,
) {
const [intervalValues, intervalTimes] = variablePerInterval(
intervals,
variable,
simulation,
intervalIndex,
);
return intervalValues
? formattedNumber(
timeOverLowerThresholdPerInterval(
intervalValues,
intervalTimes,
variable,
) -
timeOverUpperThresholdPerInterval(
const variable = variables.find((v) => v.id === baseVariable.id);
if (variable) {
const [intervalValues, intervalTimes] = variablePerInterval(
intervals,
variable,
simulation,
intervalIndex,
);
return intervalValues
? formattedNumber(
timeOverLowerThresholdPerInterval(
intervalValues,
intervalTimes,
variable,
),
)
: 0;
) -
timeOverUpperThresholdPerInterval(
intervalValues,
intervalTimes,
variable,
),
)
: 0;
}
},
},
] as Parameter[];
Expand Down
31 changes: 31 additions & 0 deletions pkpdapp/pkpdapp/migrations/0018_variable_threshold_unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#
# This file is part of PKPDApp (https://github.com/pkpdapp-team/pkpdapp) which
# is released under the BSD 3-clause license. See accompanying LICENSE.md for
# copyright notice and full license details.
#
# Generated by Django 3.2.25 on 2024-11-25 16:37

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('pkpdapp', '0017_auto_20241125_1415'),
]

operations = [
migrations.AddField(
model_name='variable',
name='threshold_unit',
field=models.ForeignKey(
blank=True,
help_text='unit for the threshold values',
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='thresholds',
to='pkpdapp.unit'
),
),
]
8 changes: 8 additions & 0 deletions pkpdapp/pkpdapp/models/variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ class Variable(StoredModel):
upper_threshold = models.FloatField(
blank=True, null=True, help_text="upper threshold for this variable"
)
threshold_unit = models.ForeignKey(
Unit,
on_delete=models.PROTECT,
blank=True,
null=True,
related_name="thresholds",
help_text="unit for the threshold values",
)

is_log = models.BooleanField(
default=False,
Expand Down
8 changes: 8 additions & 0 deletions pkpdapp/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4902,6 +4902,10 @@ components:
axis:
type: boolean
description: False/True if biomarker type displayed on LHS/RHS axis
threshold_unit:
type: integer
nullable: true
description: unit for the threshold values
unit:
type: integer
nullable: true
Expand Down Expand Up @@ -5721,6 +5725,10 @@ components:
axis:
type: boolean
description: False/True if biomarker type displayed on LHS/RHS axis
threshold_unit:
type: integer
nullable: true
description: unit for the threshold values
unit:
type: integer
nullable: true
Expand Down

0 comments on commit 3faa1ec

Please sign in to comment.