Update new unified dose management, style/other improvements

This commit is contained in:
2025-12-02 21:39:17 +00:00
parent 3fa5bf8360
commit bbdfc5f894
15 changed files with 830 additions and 495 deletions

View File

@@ -11,84 +11,80 @@
import { timeToMinutes } from './timeUtils';
import { calculateSingleDoseConcentration } from './pharmacokinetics';
import type { Dose, Deviation, SteadyStateConfig, PkParams, ConcentrationPoint } from '../constants/defaults';
import type { DayGroup, SteadyStateConfig, PkParams, ConcentrationPoint } from '../constants/defaults';
interface DoseWithTime extends Omit<Dose, 'time'> {
time: number;
isPlan?: boolean;
interface ProcessedDose {
timeMinutes: number;
ldx: number;
damph: number;
}
export const calculateCombinedProfile = (
doseSchedule: Dose[],
deviationList: Deviation[] = [],
correction: Deviation | null = null,
days: DayGroup[],
steadyStateConfig: SteadyStateConfig,
simulationDays: string,
pkParams: PkParams
): ConcentrationPoint[] => {
const dataPoints: ConcentrationPoint[] = [];
const timeStepHours = 0.25;
const totalHours = (parseInt(simulationDays, 10) || 3) * 24;
const totalDays = days.length;
const totalHours = totalDays * 24;
const daysToSimulate = Math.min(parseInt(steadyStateConfig.daysOnMedication, 10) || 0, 5);
// Convert days to processed doses with absolute time
const allDoses: ProcessedDose[] = [];
// Add steady-state doses (days before simulation period)
// Use template day (first day) for steady state
const templateDay = days[0];
if (templateDay) {
for (let steadyDay = -daysToSimulate; steadyDay < 0; steadyDay++) {
const dayOffsetMinutes = steadyDay * 24 * 60;
templateDay.doses.forEach(dose => {
const ldxNum = parseFloat(dose.ldx);
if (dose.time && !isNaN(ldxNum) && ldxNum > 0) {
allDoses.push({
timeMinutes: timeToMinutes(dose.time) + dayOffsetMinutes,
ldx: ldxNum,
damph: 0 // d-amph is calculated from LDX conversion, not administered directly
});
}
});
}
}
// Add doses from each day in sequence
days.forEach((day, dayIndex) => {
const dayOffsetMinutes = dayIndex * 24 * 60;
day.doses.forEach(dose => {
const ldxNum = parseFloat(dose.ldx);
if (dose.time && !isNaN(ldxNum) && ldxNum > 0) {
allDoses.push({
timeMinutes: timeToMinutes(dose.time) + dayOffsetMinutes,
ldx: ldxNum,
damph: 0 // d-amph is calculated from LDX conversion, not administered directly
});
}
});
});
// Calculate concentrations at each time point
for (let t = 0; t <= totalHours; t += timeStepHours) {
let totalLdx = 0;
let totalDamph = 0;
const allDoses: DoseWithTime[] = [];
const maxDayOffset = (parseInt(simulationDays, 10) || 3) - 1;
allDoses.forEach(dose => {
const timeSinceDoseHours = t - dose.timeMinutes / 60;
for (let day = -daysToSimulate; day <= maxDayOffset; day++) {
const dayOffset = day * 24 * 60;
doseSchedule.forEach(d => {
// Skip doses with empty or invalid time values
const timeStr = String(d.time || '').trim();
const doseStr = String(d.dose || '').trim();
const doseNum = parseFloat(doseStr);
if (!timeStr || timeStr === '' || !doseStr || doseStr === '' || doseNum === 0 || isNaN(doseNum)) {
return;
}
allDoses.push({ ...d, time: timeToMinutes(d.time) + dayOffset, isPlan: true });
});
}
const currentDeviations = [...deviationList];
if (correction) {
currentDeviations.push({ ...correction, isAdditional: true });
}
currentDeviations.forEach(dev => {
// Skip deviations with empty or invalid time values
const timeStr = String(dev.time || '').trim();
const doseStr = String(dev.dose || '').trim();
const doseNum = parseFloat(doseStr);
if (!timeStr || timeStr === '' || !doseStr || doseStr === '' || doseNum === 0 || isNaN(doseNum)) {
return;
if (timeSinceDoseHours >= 0) {
// Calculate LDX contribution
const ldxConcentrations = calculateSingleDoseConcentration(
String(dose.ldx),
timeSinceDoseHours,
pkParams
);
totalLdx += ldxConcentrations.ldx;
totalDamph += ldxConcentrations.damph;
}
const devTime = timeToMinutes(dev.time) + (dev.dayOffset || 0) * 24 * 60;
if (!dev.isAdditional) {
const closestDoseIndex = allDoses.reduce((closest, dose, index) => {
if (!dose.isPlan) return closest;
const diff = Math.abs(dose.time - devTime);
if (diff <= 60 && diff < closest.minDiff) {
return { index, minDiff: diff };
}
return closest;
}, { index: -1, minDiff: 61 }).index;
if (closestDoseIndex !== -1) {
allDoses.splice(closestDoseIndex, 1);
}
}
allDoses.push({ ...dev, time: devTime });
});
allDoses.forEach(doseInfo => {
const timeSinceDoseHours = t - doseInfo.time / 60;
const concentrations = calculateSingleDoseConcentration(doseInfo.dose, timeSinceDoseHours, pkParams);
totalLdx += concentrations.ldx;
totalDamph += concentrations.damph;
});
dataPoints.push({ timeHours: t, ldx: totalLdx, damph: totalDamph });