Files
med-plan-assistant/docs/pharmacokinetics.test.ts.example

300 lines
10 KiB
Plaintext

/**
* Pharmacokinetic Model Tests
*
* Validates LDX/d-amphetamine concentration calculations against clinical data
* from research literature. Tests cover:
* - Clinical validation targets (Research Section 5.1)
* - Age-specific elimination kinetics (Research Section 5.2)
* - Renal function effects (Research Section 8.2)
* - Edge cases and boundary conditions
*
* REFERENCES:
* - AI Research Document (2026-01-17): Sections 3.2, 5.1, 5.2, 8.2
* - PMC4823324: Ermer et al. meta-analysis of LDX pharmacokinetics
* - FDA NDA 021-977: Clinical pharmacology label
*
* @author Andreas Weyer
* @license MIT
*/
import { calculateSingleDoseConcentration } from '../pharmacokinetics';
import { getDefaultState } from '../../constants/defaults';
import type { PkParams } from '../../constants/defaults';
// Helper: Get default PK parameters
const getDefaultPkParams = (): PkParams => {
return getDefaultState().pkParams;
};
describe('Pharmacokinetic Model - Clinical Validation', () => {
describe('70mg Reference Case (Research Section 5.1)', () => {
test('LDX peak concentration should be ~55-65 ng/mL at 1h', () => {
const pkParams = getDefaultPkParams();
const result = calculateSingleDoseConcentration('70', 1.0, pkParams);
// Research target: ~58 ng/mL (±10%)
expect(result.ldx).toBeGreaterThan(55);
expect(result.ldx).toBeLessThan(65);
});
test('d-Amphetamine peak concentration should be ~75-85 ng/mL at 4h', () => {
const pkParams = getDefaultPkParams();
const result = calculateSingleDoseConcentration('70', 4.0, pkParams);
// Research target: ~80 ng/mL (±10%)
expect(result.damph).toBeGreaterThan(75);
expect(result.damph).toBeLessThan(85);
});
test('Crossover phenomenon: LDX peak < d-Amph peak', () => {
const pkParams = getDefaultPkParams();
const ldxPeak = calculateSingleDoseConcentration('70', 1.0, pkParams);
const damphPeak = calculateSingleDoseConcentration('70', 4.0, pkParams);
// Characteristic prodrug behavior: prodrug peaks early but lower
expect(ldxPeak.ldx).toBeLessThan(damphPeak.damph);
});
test('LDX near-zero by 12h (rapid conversion)', () => {
const pkParams = getDefaultPkParams();
const result = calculateSingleDoseConcentration('70', 12.0, pkParams);
// LDX should be essentially eliminated (< 1 ng/mL)
expect(result.ldx).toBeLessThan(1.0);
});
test('d-Amphetamine persists at 12h (~25-35 ng/mL)', () => {
const pkParams = getDefaultPkParams();
const result = calculateSingleDoseConcentration('70', 12.0, pkParams);
// d-amph has 11h half-life, should still be measurable
// ~80 ng/mL at 4h → ~30 ng/mL at 12h (roughly 1 half-life)
expect(result.damph).toBeGreaterThan(25);
expect(result.damph).toBeLessThan(35);
});
});
describe('Age-Specific Elimination (Research Section 5.2)', () => {
test('Child elimination: faster than adult (9h vs 11h half-life)', () => {
const adultParams = getDefaultPkParams();
const childParams = {
...adultParams,
advanced: {
...adultParams.advanced,
ageGroup: { preset: 'child' as const }
}
};
const adultResult = calculateSingleDoseConcentration('70', 12.0, adultParams);
const childResult = calculateSingleDoseConcentration('70', 12.0, childParams);
// At 12h, child should have ~68% of adult concentration
// exp(-ln(2)*12/9) / exp(-ln(2)*12/11) ≈ 0.68
const ratio = childResult.damph / adultResult.damph;
expect(ratio).toBeGreaterThan(0.60);
expect(ratio).toBeLessThan(0.75);
});
test('Adult preset uses 11h half-life', () => {
const pkParams = {
...getDefaultPkParams(),
advanced: {
...getDefaultPkParams().advanced,
ageGroup: { preset: 'adult' as const }
}
};
const result4h = calculateSingleDoseConcentration('70', 4.0, pkParams);
const result15h = calculateSingleDoseConcentration('70', 15.0, pkParams);
// At 15h (4h + 11h), concentration should be ~half of 4h peak
// Allows some tolerance for absorption/distribution phase
const ratio = result15h.damph / result4h.damph;
expect(ratio).toBeGreaterThan(0.40);
expect(ratio).toBeLessThan(0.60);
});
test('Custom preset uses base half-life from config', () => {
const customParams = {
...getDefaultPkParams(),
damph: { halfLife: '13' }, // Custom 13h half-life
advanced: {
...getDefaultPkParams().advanced,
ageGroup: { preset: 'custom' as const }
}
};
const result4h = calculateSingleDoseConcentration('70', 4.0, customParams);
const result17h = calculateSingleDoseConcentration('70', 17.0, customParams);
// At 17h (4h + 13h), should be ~half of 4h peak
const ratio = result17h.damph / result4h.damph;
expect(ratio).toBeGreaterThan(0.40);
expect(ratio).toBeLessThan(0.60);
});
});
describe('Renal Function Effects (Research Section 8.2)', () => {
test('Severe renal impairment: ~50% slower elimination', () => {
const normalParams = getDefaultPkParams();
const renalParams = {
...normalParams,
advanced: {
...normalParams.advanced,
renalFunction: {
enabled: true,
severity: 'severe' as const
}
}
};
const normalResult = calculateSingleDoseConcentration('70', 18.0, normalParams);
const renalResult = calculateSingleDoseConcentration('70', 18.0, renalParams);
// Severe renal: half-life 11h → 16.5h (1.5x factor)
// At 18h, renal patient should have ~1.5x concentration vs normal
const ratio = renalResult.damph / normalResult.damph;
expect(ratio).toBeGreaterThan(1.3);
expect(ratio).toBeLessThan(1.7);
});
test('Normal/mild severity: no adjustment', () => {
const baseParams = getDefaultPkParams();
const normalResult = calculateSingleDoseConcentration('70', 8.0, baseParams);
const mildParams = {
...baseParams,
advanced: {
...baseParams.advanced,
renalFunction: {
enabled: true,
severity: 'mild' as const
}
}
};
const mildResult = calculateSingleDoseConcentration('70', 8.0, mildParams);
// Mild impairment should not affect elimination in this model
expect(mildResult.damph).toBeCloseTo(normalResult.damph, 1);
});
test('Renal function disabled: no effect', () => {
const baseParams = getDefaultPkParams();
const disabledParams = {
...baseParams,
advanced: {
...baseParams.advanced,
renalFunction: {
enabled: false,
severity: 'severe' as const // Should be ignored when disabled
}
}
};
const baseResult = calculateSingleDoseConcentration('70', 12.0, baseParams);
const disabledResult = calculateSingleDoseConcentration('70', 12.0, disabledParams);
expect(disabledResult.damph).toBeCloseTo(baseResult.damph, 1);
});
});
describe('Edge Cases and Boundary Conditions', () => {
test('Zero dose returns zero concentrations', () => {
const pkParams = getDefaultPkParams();
const result = calculateSingleDoseConcentration('0', 4.0, pkParams);
expect(result.ldx).toBe(0);
expect(result.damph).toBe(0);
});
test('Negative time returns zero concentrations', () => {
const pkParams = getDefaultPkParams();
const result = calculateSingleDoseConcentration('70', -1.0, pkParams);
expect(result.ldx).toBe(0);
expect(result.damph).toBe(0);
});
test('Very high dose scales proportionally', () => {
const pkParams = getDefaultPkParams();
const result70 = calculateSingleDoseConcentration('70', 4.0, pkParams);
const result140 = calculateSingleDoseConcentration('140', 4.0, pkParams);
// Linear pharmacokinetics: 2x dose → 2x concentration
expect(result140.damph).toBeCloseTo(result70.damph * 2, 0);
});
test('Food effect delays absorption without changing AUC', () => {
const pkParams = getDefaultPkParams();
const fedParams = {
...pkParams,
advanced: {
...pkParams.advanced,
foodEffect: {
enabled: true,
tmaxDelay: '1.0' // 1h delay
}
}
};
// Peak should be later for fed state
const fastedPeak1h = calculateSingleDoseConcentration('70', 1.0, pkParams);
const fedPeak1h = calculateSingleDoseConcentration('70', 1.0, fedParams, true);
const fedPeak2h = calculateSingleDoseConcentration('70', 2.0, fedParams, true);
// Fed state at 1h should be lower than fasted (absorption delayed)
expect(fedPeak1h.ldx).toBeLessThan(fastedPeak1h.ldx);
// Fed peak should occur later (around 2h instead of 1h)
expect(fedPeak2h.ldx).toBeGreaterThan(fedPeak1h.ldx);
});
});
describe('Weight-Based Volume of Distribution', () => {
test('Lower body weight increases concentrations', () => {
const standardParams = getDefaultPkParams();
const lightweightParams = {
...standardParams,
advanced: {
...standardParams.advanced,
weightBasedVd: {
enabled: true,
bodyWeight: '50' // 50kg vs default ~70kg
}
}
};
const standardResult = calculateSingleDoseConcentration('70', 4.0, standardParams);
const lightweightResult = calculateSingleDoseConcentration('70', 4.0, lightweightParams);
// Smaller Vd → higher concentration
// 50kg: Vd ~270L, 70kg: Vd ~377L, ratio ~1.4x
const ratio = lightweightResult.damph / standardResult.damph;
expect(ratio).toBeGreaterThan(1.2);
expect(ratio).toBeLessThan(1.6);
});
test('Higher body weight decreases concentrations', () => {
const standardParams = getDefaultPkParams();
const heavyweightParams = {
...standardParams,
advanced: {
...standardParams.advanced,
weightBasedVd: {
enabled: true,
bodyWeight: '100' // 100kg
}
}
};
const standardResult = calculateSingleDoseConcentration('70', 4.0, standardParams);
const heavyweightResult = calculateSingleDoseConcentration('70', 4.0, heavyweightParams);
// Larger Vd → lower concentration
const ratio = heavyweightResult.damph / standardResult.damph;
expect(ratio).toBeLessThan(0.8);
});
});
});