Files
med-plan-assistant/src/utils/calculations.ts

104 lines
3.3 KiB
TypeScript

/**
* Pharmacokinetic Calculation Utilities
*
* Combines multiple dose profiles over time to create complete concentration
* curves. Handles steady-state calculations, dose accumulation, and empty
* value filtering for robust simulation.
*
* @author Andreas Weyer
* @license MIT
*/
import { timeToMinutes } from './timeUtils';
import { calculateSingleDoseConcentration } from './pharmacokinetics';
import type { DayGroup, SteadyStateConfig, PkParams, ConcentrationPoint } from '../constants/defaults';
interface ProcessedDose {
timeMinutes: number;
ldx: number;
damph: number;
isFed?: boolean; // Optional: indicates if dose was taken with food
}
export const calculateCombinedProfile = (
days: DayGroup[],
steadyStateConfig: SteadyStateConfig,
pkParams: PkParams
): ConcentrationPoint[] => {
const dataPoints: ConcentrationPoint[] = [];
const timeStepHours = 0.25;
const totalDays = days.length;
const totalHours = totalDays * 24;
// Use steadyStateDays from advanced settings (allows 0 for "first day" simulation)
const daysToSimulate = Math.min(
parseInt(pkParams.advanced.steadyStateDays, 10) || 0,
7 // cap at 7 days for performance
);
// 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 && daysToSimulate > 0) {
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
isFed: dose.isFed // Pass through per-dose food effect flag
});
}
});
}
}
// 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
isFed: dose.isFed // Pass through per-dose food effect flag
});
}
});
});
// Calculate concentrations at each time point
for (let t = 0; t <= totalHours; t += timeStepHours) {
let totalLdx = 0;
let totalDamph = 0;
allDoses.forEach(dose => {
const timeSinceDoseHours = t - dose.timeMinutes / 60;
if (timeSinceDoseHours >= 0) {
// Calculate LDX contribution with per-dose food effect
const ldxConcentrations = calculateSingleDoseConcentration(
String(dose.ldx),
timeSinceDoseHours,
pkParams,
dose.isFed // Pass per-dose food flag
);
totalLdx += ldxConcentrations.ldx;
totalDamph += ldxConcentrations.damph;
}
});
dataPoints.push({ timeHours: t, ldx: totalLdx, damph: totalDamph });
}
return dataPoints;
};