Fix isFed state for regular plan comparison line, simplified urin ph selection

This commit is contained in:
2026-02-02 11:51:39 +00:00
parent 8e74fe576f
commit 90b0806cec
8 changed files with 71 additions and 60 deletions

View File

@@ -46,7 +46,6 @@ const getDefaultsForTranslation = (pkParams: any, therapeuticRange: any, uiSetti
standardVdPreset: defaults.pkParams.advanced.standardVd?.preset || 'adult', standardVdPreset: defaults.pkParams.advanced.standardVd?.preset || 'adult',
bodyWeight: defaults.pkParams.advanced.weightBasedVd.bodyWeight, bodyWeight: defaults.pkParams.advanced.weightBasedVd.bodyWeight,
tmaxDelay: defaults.pkParams.advanced.foodEffect.tmaxDelay, tmaxDelay: defaults.pkParams.advanced.foodEffect.tmaxDelay,
phTendency: defaults.pkParams.advanced.urinePh.phTendency,
fOral: defaults.pkParams.advanced.fOral, fOral: defaults.pkParams.advanced.fOral,
fOralPercent: String((parseFloat(defaults.pkParams.advanced.fOral) * 100).toFixed(1)), fOralPercent: String((parseFloat(defaults.pkParams.advanced.fOral) * 100).toFixed(1)),
steadyStateDays: defaults.pkParams.advanced.steadyStateDays, steadyStateDays: defaults.pkParams.advanced.steadyStateDays,
@@ -924,12 +923,7 @@ const Settings = ({
{/* Urine pH */} {/* Urine pH */}
<div className="space-y-3"> <div className="space-y-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Switch <Label htmlFor="urinePHMode" className="font-medium">
id="urinePHEnabled"
checked={pkParams.advanced.urinePh.enabled}
onCheckedChange={checked => updateAdvanced('urinePh', 'enabled', checked)}
/>
<Label htmlFor="urinePHEnabled" className="font-medium">
{t('urinePHTendency')} {t('urinePHTendency')}
</Label> </Label>
<Tooltip open={openTooltipId === 'urinePH'} onOpenChange={(open) => setOpenTooltipId(open ? 'urinePH' : null)}> <Tooltip open={openTooltipId === 'urinePH'} onOpenChange={(open) => setOpenTooltipId(open ? 'urinePH' : null)}>
@@ -949,38 +943,23 @@ const Settings = ({
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
{pkParams.advanced.urinePh.enabled && ( <div>
<div className="ml-8 space-y-2"> <Select
<div className="flex items-center gap-2"> value={pkParams.advanced.urinePh.mode}
<Label className="text-sm font-medium">{t('urinePHValue')}</Label> onValueChange={(value: 'normal' | 'acidic' | 'alkaline') =>
<Tooltip open={openTooltipId === 'urinePHValue'} onOpenChange={(open) => setOpenTooltipId(open ? 'urinePHValue' : null)}> updateAdvanced('urinePh', 'mode', value)
<TooltipTrigger asChild> }
<button
type="button"
onClick={handleTooltipToggle('urinePHValue')}
onTouchStart={handleTooltipToggle('urinePHValue')}
className="inline-flex items-center justify-center rounded-sm text-muted-foreground hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
aria-label={t('urinePHValueTooltip')}
> >
<Info className="h-4 w-4" /> <SelectTrigger id="urinePHMode">
</button> <SelectValue />
</TooltipTrigger> </SelectTrigger>
<TooltipContent side={tooltipSide}> <SelectContent>
<p className="text-xs max-w-xs">{tWithDefaults(t, 'urinePHValueTooltip', defaultsForT)}</p> <SelectItem value="normal">{t('urinePHModeNormal')}</SelectItem>
</TooltipContent> <SelectItem value="acidic">{t('urinePHModeAcidic')}</SelectItem>
</Tooltip> <SelectItem value="alkaline">{t('urinePHModeAlkaline')}</SelectItem>
</SelectContent>
</Select>
</div> </div>
<FormNumericInput
value={pkParams.advanced.urinePh.phTendency}
onChange={val => updateAdvanced('urinePh', 'phTendency', val)}
increment={0.1}
min={5.5}
max={8.0}
unit={t('phUnit')}
required={true}
/>
</div>
)}
</div> </div>
<Separator className="my-4" /> <Separator className="my-4" />

View File

@@ -26,7 +26,7 @@ const versionInfo = versionJsonDefault && Object.keys(versionJsonDefault).length
gitDate: 'unknown', gitDate: 'unknown',
}; };
export const LOCAL_STORAGE_KEY = 'medPlanAssistantState_v8'; // Incremented for ageGroup + renalFunction fields export const LOCAL_STORAGE_KEY = 'medPlanAssistantState_v9'; // Incremented for urinePh mode structure change
export const PROJECT_REPOSITORY_URL = 'https://git.11001001.org/cbaoth/med-plan-assistant'; export const PROJECT_REPOSITORY_URL = 'https://git.11001001.org/cbaoth/med-plan-assistant';
export const APP_VERSION = versionInfo.version; export const APP_VERSION = versionInfo.version;
export const BUILD_INFO = versionInfo; export const BUILD_INFO = versionInfo;
@@ -42,7 +42,7 @@ export interface AdvancedSettings {
standardVd: { preset: 'adult' | 'child' | 'custom'; customValue: string }; // Volume of distribution (L) standardVd: { preset: 'adult' | 'child' | 'custom'; customValue: string }; // Volume of distribution (L)
weightBasedVd: { enabled: boolean; bodyWeight: string }; // kg weightBasedVd: { enabled: boolean; bodyWeight: string }; // kg
foodEffect: { enabled: boolean; tmaxDelay: string }; // hours foodEffect: { enabled: boolean; tmaxDelay: string }; // hours
urinePh: { enabled: boolean; phTendency: string }; // 5.5-8.0 range urinePh: { mode: 'normal' | 'acidic' | 'alkaline' }; // pH effect on elimination
fOral: string; // bioavailability fraction fOral: string; // bioavailability fraction
steadyStateDays: string; // days of medication history to simulate steadyStateDays: string; // days of medication history to simulate
// Age-specific pharmacokinetics (Research Section 5.2) // Age-specific pharmacokinetics (Research Section 5.2)
@@ -141,7 +141,7 @@ export const getDefaultState = (): AppState => ({
standardVd: { preset: 'adult', customValue: '377' }, // Adult: 377L (Roberts 2015), Child: ~150-200L standardVd: { preset: 'adult', customValue: '377' }, // Adult: 377L (Roberts 2015), Child: ~150-200L
weightBasedVd: { enabled: false, bodyWeight: '70' }, // kg, adult average weightBasedVd: { enabled: false, bodyWeight: '70' }, // kg, adult average
foodEffect: { enabled: false, tmaxDelay: '1.0' }, // hours delay foodEffect: { enabled: false, tmaxDelay: '1.0' }, // hours delay
urinePh: { enabled: false, phTendency: '6.0' }, // pH scale (5.5-8.0) urinePh: { mode: 'normal' }, // 'normal' (6-7.5), 'acidic' (<6), 'alkaline' (>7.5)
fOral: String(DEFAULT_F_ORAL), // 0.96 bioavailability fOral: String(DEFAULT_F_ORAL), // 0.96 bioavailability
steadyStateDays: '7' // days of prior medication history steadyStateDays: '7' // days of prior medication history
} }

View File

@@ -29,10 +29,35 @@ export const useAppState = () => {
migratedUiSettings.showDayTimeOnXAxis = migratedUiSettings.showDayTimeOnXAxis ? '24h' : 'continuous'; migratedUiSettings.showDayTimeOnXAxis = migratedUiSettings.showDayTimeOnXAxis ? '24h' : 'continuous';
} }
// Migrate urinePh from old {enabled, phTendency} to new {mode} structure
let migratedPkParams = {...defaults.pkParams, ...parsedState.pkParams};
if (migratedPkParams.advanced) {
const oldUrinePh = migratedPkParams.advanced.urinePh as any;
if (oldUrinePh && typeof oldUrinePh === 'object' && 'enabled' in oldUrinePh) {
// Old format detected: {enabled: boolean, phTendency: string}
if (!oldUrinePh.enabled) {
migratedPkParams.advanced.urinePh = { mode: 'normal' };
} else {
const phValue = parseFloat(oldUrinePh.phTendency);
if (!isNaN(phValue)) {
if (phValue < 6.0) {
migratedPkParams.advanced.urinePh = { mode: 'acidic' };
} else if (phValue > 7.5) {
migratedPkParams.advanced.urinePh = { mode: 'alkaline' };
} else {
migratedPkParams.advanced.urinePh = { mode: 'normal' };
}
} else {
migratedPkParams.advanced.urinePh = { mode: 'normal' };
}
}
}
}
setAppState({ setAppState({
...defaults, ...defaults,
...parsedState, ...parsedState,
pkParams: {...defaults.pkParams, ...parsedState.pkParams}, pkParams: migratedPkParams,
days: parsedState.days || defaults.days, days: parsedState.days || defaults.days,
uiSettings: migratedUiSettings, uiSettings: migratedUiSettings,
}); });

View File

@@ -72,7 +72,8 @@ export const useSimulation = (appState: AppState) => {
doses: templateDay.doses.map(d => ({ doses: templateDay.doses.map(d => ({
id: `${d.id}-template-${i}`, id: `${d.id}-template-${i}`,
time: d.time, time: d.time,
ldx: d.ldx ldx: d.ldx,
isFed: d.isFed // Preserve food-timing flag for proper absorption delay modeling
})) }))
})); }));

View File

@@ -135,7 +135,11 @@ export const de = {
tmaxDelayUnit: "h", tmaxDelayUnit: "h",
urinePHTendency: "Urin-pH-Effekte", urinePHTendency: "Urin-pH-Effekte",
urinePHTooltip: "Urin-pH beeinflusst Nierenrückresorption von Amphetamin. Ermöglicht pH-abhängige Halbwertszeit-Variation (7-15h Bereich). Bei Deaktivierung: neutraler pH (~11h HWZ).", urinePHTooltip: "Urin-pH beeinflusst Nierenrückresorption von Amphetamin. Saurer Urin (<6) erhöht Elimination (schnellere Ausscheidung, t½ ~7-9h). Normaler pH (6-7,5) hält Basis-Elimination (~11h). Alkalischer Urin (>7,5) reduziert Elimination (langsamere Ausscheidung, t½ ~13-15h). Typischer Bereich: 5,5-8,0. Standard: Normaler pH (6-7,5).",
urinePHMode: "pH-Effekt",
urinePHModeNormal: "Normal (pH 6-7,5, t½ 11h)",
urinePHModeAcidic: "Sauer (pH <6, schnellere Elimination)",
urinePHModeAlkaline: "Alkalisch (pH >7,5, langsamere Elimination)",
urinePHValue: "pH-Wert", urinePHValue: "pH-Wert",
urinePHValueTooltip: "Dein typischer Urin-pH (sauer=schnellere Ausscheidung, alkalisch=langsamer). Standard: {{phTendency}}. Bereich: 5,5-8,0.", urinePHValueTooltip: "Dein typischer Urin-pH (sauer=schnellere Ausscheidung, alkalisch=langsamer). Standard: {{phTendency}}. Bereich: 5,5-8,0.",
phValue: "pH-Wert", phValue: "pH-Wert",

View File

@@ -133,7 +133,11 @@ export const en = {
tmaxDelayUnit: "h", tmaxDelayUnit: "h",
urinePHTendency: "Urine pH Effects", urinePHTendency: "Urine pH Effects",
urinePHTooltip: "Urine pH affects kidney reabsorption of amphetamine. Enables pH-dependent half-life variation (7-15h range). When disabled, assumes neutral pH (~11h HL).", urinePHTooltip: "Urine pH affects kidney reabsorption of amphetamine. Acidic urine (<6) increases elimination (faster clearance, t½ ~7-9h). Normal pH (6-7.5) maintains baseline elimination (~11h). Alkaline urine (>7.5) reduces elimination (slower clearance, t½ ~13-15h). Typical range: 5.5-8.0. Default: Normal pH (6-7.5).",
urinePHMode: "pH Effect",
urinePHModeNormal: "Normal (pH 6-7.5, t½ 11h)",
urinePHModeAcidic: "Acidic (pH <6, faster elimination)",
urinePHModeAlkaline: "Alkaline (pH >7.5, slower elimination)",
urinePHValue: "pH Value", urinePHValue: "pH Value",
urinePHValueTooltip: "Your typical urine pH (acidic=faster clearance, alkaline=slower). Default: {{phTendency}}. Range: 5.5-8.0.", urinePHValueTooltip: "Your typical urine pH (acidic=faster clearance, alkaline=slower). Default: {{phTendency}}. Range: 5.5-8.0.",
phValue: "pH Value", phValue: "pH Value",

View File

@@ -250,6 +250,7 @@ export const importSettings = (
time: dose.time || '12:00', time: dose.time || '12:00',
ldx: dose.ldx || '0', ldx: dose.ldx || '0',
damph: dose.damph, damph: dose.damph,
isFed: dose.isFed, // Explicitly preserve food-timing flag
})) }))
})); }));
} }

View File

@@ -108,8 +108,7 @@ export const calculateSingleDoseConcentration = (
// Per-dose food effect takes precedence over global setting // Per-dose food effect takes precedence over global setting
const foodEnabled = isFed !== undefined ? isFed : pkParams.advanced.foodEffect.enabled; const foodEnabled = isFed !== undefined ? isFed : pkParams.advanced.foodEffect.enabled;
const tmaxDelay = foodEnabled ? parseFloat(pkParams.advanced.foodEffect.tmaxDelay) : 0; const tmaxDelay = foodEnabled ? parseFloat(pkParams.advanced.foodEffect.tmaxDelay) : 0;
const urinePHEnabled = pkParams.advanced.urinePh.enabled; const urinePHMode = pkParams.advanced.urinePh.mode;
const phTendency = urinePHEnabled ? parseFloat(pkParams.advanced.urinePh.phTendency) : 6.0;
// Validate base parameters // Validate base parameters
if (isNaN(absorptionHalfLife) || absorptionHalfLife <= 0 || if (isNaN(absorptionHalfLife) || absorptionHalfLife <= 0 ||
@@ -125,20 +124,18 @@ export const calculateSingleDoseConcentration = (
const calculationTime = adjustedTime; // Use delayed time for all kinetic calculations const calculationTime = adjustedTime; // Use delayed time for all kinetic calculations
// Apply urine pH effect on elimination half-life // Apply urine pH effect on elimination half-life
// pH < 6: acidic (faster elimination, HL ~7-9h) // Acidic: pH < 6 (faster elimination, HL ~7-9h)
// pH 6-7: normal (HL ~10-12h) // Normal: pH 6-7.5 (baseline elimination, HL ~10-12h)
// pH > 7: alkaline (slower elimination, HL ~13-15h up to 34h extreme) // Alkaline: pH > 7.5 (slower elimination, HL ~13-15h up to 34h extreme)
let adjustedDamphHL = damphHalfLife; let adjustedDamphHL = damphHalfLife;
if (urinePHEnabled) { if (urinePHMode === 'acidic') {
if (phTendency < 6.0) {
// Acidic: reduce HL by ~30% // Acidic: reduce HL by ~30%
adjustedDamphHL = damphHalfLife * 0.7; adjustedDamphHL = damphHalfLife * 0.7;
} else if (phTendency > 7.5) { } else if (urinePHMode === 'alkaline') {
// Alkaline: increase HL by ~30-40% // Alkaline: increase HL by ~30-40%
adjustedDamphHL = damphHalfLife * 1.35; adjustedDamphHL = damphHalfLife * 1.35;
} }
// else: normal pH 6-7.5, no adjustment // else: normal mode, no adjustment
}
// Calculate rate constants // Calculate rate constants
const ka_ldx = Math.log(2) / absorptionHalfLife; const ka_ldx = Math.log(2) / absorptionHalfLife;