Update pharmacokinetic parameters/calculations, add advanced settings, add disclaimer/citations, many improvements

This commit is contained in:
2026-01-09 19:50:15 +00:00
parent abd8e790b8
commit b396caa67a
14 changed files with 2222 additions and 192 deletions

View File

@@ -28,7 +28,12 @@ export const calculateCombinedProfile = (
const timeStepHours = 0.25;
const totalDays = days.length;
const totalHours = totalDays * 24;
const daysToSimulate = Math.min(parseInt(steadyStateConfig.daysOnMedication, 10) || 0, 5);
// 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[] = [];
@@ -36,7 +41,7 @@ export const calculateCombinedProfile = (
// Add steady-state doses (days before simulation period)
// Use template day (first day) for steady state
const templateDay = days[0];
if (templateDay) {
if (templateDay && daysToSimulate > 0) {
for (let steadyDay = -daysToSimulate; steadyDay < 0; steadyDay++) {
const dayOffsetMinutes = steadyDay * 24 * 60;
templateDay.doses.forEach(dose => {

View File

@@ -3,19 +3,22 @@
*
* Implements single-dose concentration calculations for lisdexamfetamine (LDX)
* and its active metabolite dextroamphetamine (d-amph). Uses first-order
* absorption and elimination kinetics.
* absorption and elimination kinetics with optional advanced modifiers.
*
* @author Andreas Weyer
* @license MIT
*/
import { LDX_TO_DAMPH_CONVERSION_FACTOR, type PkParams } from '../constants/defaults';
import { LDX_TO_DAMPH_SALT_FACTOR, DEFAULT_F_ORAL, type PkParams } from '../constants/defaults';
interface ConcentrationResult {
ldx: number;
damph: number;
}
// Standard adult volume of distribution (Roberts et al. 2015): 377 L
const STANDARD_VD_ADULT = 377.0;
// Pharmacokinetic calculations
export const calculateSingleDoseConcentration = (
dose: string,
@@ -25,27 +28,61 @@ export const calculateSingleDoseConcentration = (
const numDose = parseFloat(dose) || 0;
if (timeSinceDoseHours < 0 || numDose <= 0) return { ldx: 0, damph: 0 };
const absorptionRate = parseFloat(pkParams.ldx.absorptionRate);
// Extract base parameters
const absorptionHalfLife = parseFloat(pkParams.ldx.absorptionHalfLife);
const conversionHalfLife = parseFloat(pkParams.ldx.halfLife);
const damphHalfLife = parseFloat(pkParams.damph.halfLife);
// Validate parameters to avoid division by zero or invalid calculations
if (isNaN(absorptionRate) || absorptionRate <= 0 ||
// Extract advanced parameters
const fOral = parseFloat(pkParams.advanced.fOral) || DEFAULT_F_ORAL;
const foodEnabled = pkParams.advanced.foodEffect.enabled;
const tmaxDelay = foodEnabled ? parseFloat(pkParams.advanced.foodEffect.tmaxDelay) : 0;
const urinePHEnabled = pkParams.advanced.urinePh.enabled;
const phTendency = urinePHEnabled ? parseFloat(pkParams.advanced.urinePh.phTendency) : 6.0;
// Validate base parameters
if (isNaN(absorptionHalfLife) || absorptionHalfLife <= 0 ||
isNaN(conversionHalfLife) || conversionHalfLife <= 0 ||
isNaN(damphHalfLife) || damphHalfLife <= 0) {
return { ldx: 0, damph: 0 };
}
const ka_ldx = Math.log(2) / absorptionRate;
const k_conv = Math.log(2) / conversionHalfLife;
const ke_damph = Math.log(2) / damphHalfLife;
// Apply food effect: high-fat meal delays absorption by slowing rate (~+1h to Tmax)
// Approximate by increasing absorption half-life proportionally
const adjustedAbsorptionHL = absorptionHalfLife * (1 + (tmaxDelay / 1.5));
// Apply urine pH effect on elimination half-life
// pH < 6: acidic (faster elimination, HL ~7-9h)
// pH 6-7: normal (HL ~10-12h)
// pH > 7: alkaline (slower elimination, HL ~13-15h up to 34h extreme)
let adjustedDamphHL = damphHalfLife;
if (urinePHEnabled) {
if (phTendency < 6.0) {
// Acidic: reduce HL by ~30%
adjustedDamphHL = damphHalfLife * 0.7;
} else if (phTendency > 7.5) {
// Alkaline: increase HL by ~30-40%
adjustedDamphHL = damphHalfLife * 1.35;
}
// else: normal pH 6-7.5, no adjustment
}
// Calculate rate constants
const ka_ldx = Math.log(2) / adjustedAbsorptionHL;
const k_conv = Math.log(2) / conversionHalfLife;
const ke_damph = Math.log(2) / adjustedDamphHL;
// Apply stoichiometric conversion and bioavailability
const effectiveDose = numDose * LDX_TO_DAMPH_SALT_FACTOR * fOral;
// Calculate LDX concentration (prodrug)
let ldxConcentration = 0;
if (Math.abs(ka_ldx - k_conv) > 0.0001) {
ldxConcentration = (numDose * ka_ldx / (ka_ldx - k_conv)) *
(Math.exp(-k_conv * timeSinceDoseHours) - Math.exp(-ka_ldx * timeSinceDoseHours));
}
// Calculate d-amphetamine concentration (active metabolite)
let damphConcentration = 0;
if (Math.abs(ka_ldx - ke_damph) > 0.0001 &&
Math.abs(k_conv - ke_damph) > 0.0001 &&
@@ -53,7 +90,20 @@ export const calculateSingleDoseConcentration = (
const term1 = Math.exp(-ke_damph * timeSinceDoseHours) / ((ka_ldx - ke_damph) * (k_conv - ke_damph));
const term2 = Math.exp(-k_conv * timeSinceDoseHours) / ((ka_ldx - k_conv) * (ke_damph - k_conv));
const term3 = Math.exp(-ka_ldx * timeSinceDoseHours) / ((k_conv - ka_ldx) * (ke_damph - ka_ldx));
damphConcentration = LDX_TO_DAMPH_CONVERSION_FACTOR * numDose * ka_ldx * k_conv * (term1 + term2 + term3);
damphConcentration = effectiveDose * ka_ldx * k_conv * (term1 + term2 + term3);
}
// Apply weight-based Vd scaling if enabled
// Standard adult Vd = 377 L; weight-normalized ~5.4 L/kg
// Concentration inversely proportional to Vd: C = Amount / Vd
if (pkParams.advanced.weightBasedVd.enabled) {
const bodyWeight = parseFloat(pkParams.advanced.weightBasedVd.bodyWeight);
if (!isNaN(bodyWeight) && bodyWeight > 0) {
const weightBasedVd = bodyWeight * 5.4; // L/kg factor from literature
const scalingFactor = STANDARD_VD_ADULT / weightBasedVd;
damphConcentration *= scalingFactor;
ldxConcentration *= scalingFactor;
}
}
return { ldx: Math.max(0, ldxConcentration), damph: Math.max(0, damphConcentration) };