Files
ketapk/pkCore.js
T
2026-08-01 17:34:23 +02:00

135 lines
5.2 KiB
JavaScript

/**
* ============================================================================
* KetaPK - Moteur Pharmacocinétique (PK/PD)
* Modèles : Domino (1982), Clements (1981), Kamp (2020)
* ============================================================================
*/
export const MODES = {
ESKETAMINE: 'ESKETAMINE',
RACEMIQUE: 'RACEMIQUE'
};
// 1. Modèles Pharmacocinétiques (Multi-exponentiels)
// Cp(t) = (Dose / Poids) * [ A * e^(-alpha * t) + B * e^(-beta * t) + C * e^(-gamma * t) ]
export const PK_MODELS = {
DOMINO: {
name: "Domino (1982)",
refDose: 2.0, // mg/kg
A: 14300, alpha: 1.35167,
B: 2340, beta: 0.09517,
C: 197, gamma: 0.00378,
color: '#ff9933' // Orange
},
CLEMENTS: {
name: "Clements (1981)",
refDose: 0.25, // mg/kg
A: 108, alpha: 0.03940,
B: 39, beta: 0.00380,
C: 0, gamma: 0,
color: '#6ec46c' // Vert
},
KAMP: {
name: "Kamp (2020)",
refDose: 0.5, // mg/kg
A: 1332, alpha: 0.24658,
B: 155, beta: 0.03845,
C: 69, gamma: 0.00409,
color: '#4fafe3' // Bleu
}
};
// 2. Seuils d'effets cliniques (ng/mL)
export const CLINICAL_THRESHOLDS = {
[MODES.ESKETAMINE]: {
maxScale: 500,
defaultConcentration: 5, // mg/mL
zones: [
{ name: "Inactif", min: 0, max: 10, color: "rgba(249, 252, 126, 0.4)", textHex: "#8a8d00" },
{ name: "Anti-Hyperalgésique", min: 10, max: 50, color: "rgba(218, 247, 166, 0.4)", textHex: "#417505" },
{ name: "Analgésie", min: 50, max: 150, color: "rgba(255, 195, 0, 0.4)", textHex: "#b78100" },
{ name: "Psychédélique", min: 150, max: 350, color: "rgba(242, 160, 223, 0.4)", textHex: "#a2137f" },
{ name: "Émergence", min: 350, max: 500, color: "rgba(89, 196, 240, 0.4)", textHex: "#0c6796" },
{ name: "Narcose", min: 500, max: 1000, color: "rgba(43, 122, 244, 0.4)", textHex: "#0a3c8a" },
{ name: "Excessif", min: 1000, max: Infinity, color: "rgba(255, 87, 51, 0.4)", textHex: "#a81300" }
]
},
[MODES.RACEMIQUE]: {
maxScale: 1000,
defaultConcentration: 10, // mg/mL
zones: [
{ name: "Inactif", min: 0, max: 20, color: "rgba(249, 252, 126, 0.4)", textHex: "#8a8d00" },
{ name: "Anti-Hyperalgésique", min: 20, max: 100, color: "rgba(218, 247, 166, 0.4)", textHex: "#417505" },
{ name: "Analgésie", min: 100, max: 300, color: "rgba(255, 195, 0, 0.4)", textHex: "#b78100" },
{ name: "Psychédélique", min: 300, max: 700, color: "rgba(242, 160, 223, 0.4)", textHex: "#a2137f" },
{ name: "Émergence", min: 700, max: 1000, color: "rgba(89, 196, 240, 0.4)", textHex: "#0c6796" },
{ name: "Narcose", min: 1000, max: 2000, color: "rgba(43, 122, 244, 0.4)", textHex: "#0a3c8a" },
{ name: "Excessif", min: 2000, max: Infinity, color: "rgba(255, 87, 51, 0.4)", textHex: "#a81300" }
]
}
};
// 3. Équation Fondamentale du Calcul de la Concentration Plasmatique Cp(t)
/**
* Calcule la réponse impulsionnelle pour un bolus unique à un instant t.
* $C_p(t) = \frac{\text{dose}}{\text{poids}} \times \left( A \cdot e^{-\alpha t} + B \cdot e^{-\beta t} + C \cdot e^{-\gamma t} \right)$
*/
export function calculateBolusCp(model, doseMg, weightKg, timeElapsedMin) {
if (timeElapsedMin < 0 || weightKg <= 0) return 0;
const dosePerKg = doseMg / weightKg;
const { A, alpha, B, beta, C, gamma } = model;
const termA = A * Math.exp(-alpha * timeElapsedMin);
const termB = B * Math.exp(-beta * timeElapsedMin);
const termC = C * (gamma ? Math.exp(-gamma * timeElapsedMin) : 0);
return dosePerKg * (termA + termB + termC);
}
/**
* Superposition Linéaire des effets (Bolus multiples + Perfusions) sur 300 minutes (pas de 5 min)
*/
export function generateSimulationData({ model, weightKg, timelineInputs }) {
// timelineInputs : tableau de 61 pas de temps (0 à 300 min par pas de 5 min)
// { time: 0..300, bolusMg: number, perfRateMgKgH: number }
const timeGrid = Array.from({ length: 61 }, (_, i) => i * 5);
const cpResults = new Array(61).fill(0);
timeGrid.forEach((targetTime, targetIndex) => {
let totalCp = 0;
for (let i = 0; i <= targetIndex; i++) {
const inputTime = timeGrid[i];
const deltaTime = targetTime - inputTime;
const input = timelineInputs[i] || { bolusMg: 0, perfRateMgKgH: 0 };
// Contribution du Bolus
if (input.bolusMg > 0) {
totalCp += calculateBolusCp(model, input.bolusMg, weightKg, deltaTime);
}
// Contribution de la perfusion continue (discrétisée sur le pas de 5 min)
if (input.perfRateMgKgH > 0) {
// mg/kg/h converti en mg sur l'intervalle de 5 min (5/60 heure)
const perfDoseMg = (input.perfRateMgKgH * weightKg) * (5 / 60);
totalCp += calculateBolusCp(model, perfDoseMg, weightKg, deltaTime);
}
}
cpResults[targetIndex] = Math.round(totalCp * 100) / 100;
});
return cpResults;
}
// 4. Fonctions de Calculateur Médical (Conversions d'unités)
export const CalculatorEngine = {
mgToMgKg: (mg, weight) => (weight > 0 ? mg / weight : 0),
mgKgToMg: (mgKg, weight) => mgKg * weight,
mgKgHToMicrogKgMin: (mgKgH) => (mgKgH * 1000) / 60,
microgKgMinToMgKgH: (microg) => (microg * 60) / 1000,
mgToMl: (mg, concMgMl) => (concMgMl > 0 ? mg / concMgMl : 0),
mlToMg: (ml, concMgMl) => ml * concMgMl
};