/** * ───────────────────────────────────────────────────────────────────────────── * Modèles pharmacocinétiques « Transfem Science » — méta-analyse des esters * d'estradiol injectables (transfemscience.org/articles/injectable-e2-meta-analysis/). * * PORTAGE WEB du fichier Android `app/src/main/java/com/hormonetrack/pk/ * TransfemScienceModels.kt` (v1.4.0) — les paramètres sont copiés À * L'IDENTIQUE : toute retouche doit être faite dans les DEUX codebases et * épinglée par les tests (web/tests/tfs-models.test.js côté web, * TransfemScienceModelsTest.kt côté Android). * * SOURCE DES PARAMÈTRES : les coefficients du modèle à 3 compartiments (V3C) * publiés par le simulateur officiel de Transfem Science * (https://transfemscience.org/misc/injectable-e2-simulator/ — valeurs = * DONNÉES SCIENTIFIQUES issues des études agrégées, Tableaux 9–10 de * l'article). * * Forme close exacte (t en JOURS, Cp en pg/mL pour la dose de fit 5 mg) : * * Cp(t) = D·k1·k2 · [ e^(−k1·t)/((k1−k2)(k1−k3)) * + e^(−k3·t)/((k1−k3)(k2−k3)) * + e^(−k2·t)·(k3−k1)/((k1−k2)(k1−k3)(k2−k3)) ] * * La courbe est NORMALISÉE PAR MG : `sample()` retourne Cp(t) / fitDose — * le moteur PK multiplie déjà par la dose réelle (dose_mg), exactement comme * avec les tables Estrannaise (ODS). * * Sanity checks épinglés (web/tests/tfs-models.test.js, fidélité à l'article * reproduite à ~1–2 %) : * EV : Cmax 295 pg/mL @ 2,1 j, t½ 3,0 j | EEn : 160 @ 6,5 j, t½ 4,6 j * EB : 971 @ 0,65 j, t½ 1,2 j | EC (huile) : 155 @ 4,3 j, t½ 6,7 j * EC susp. : 241 @ 1,2 j, t½ 5,1 j | PEP : ~34 pg/mL @ 32,5 mg @ ~18 j, t½ 28,4 j * * Remarques de l'article conservées : l'undécylate (EU) a des données d'étude * limitées (ajustement moins fiable) ; PEP a une biodisponibilité réduite * (~15–20 % — d'où des doses ~6,5× plus élevées). * ───────────────────────────────────────────────────────────────────────────── */ /** Modèle à 3 compartiments (V3C) d'un ester, tel que publié par TFS. */ export class V3C { /** * @param {string} ester Clé ester de l'app ("EV", "EB", …) * @param {number} D Constante d'amplitude (pg/mL pour la dose de fit, unités TFS) * @param {number} k1 Constante de vitesse j⁻¹ (absorption dépôt) * @param {number} k2 Constante de vitesse j⁻¹ (phase rapide) * @param {number} k3 Constante de vitesse j⁻¹ (phase lente / terminale) * @param {number} [fitDoseMg=5] Dose à laquelle les paramètres ont été ajustés (5 mg dans l'article) */ constructor(ester, D, k1, k2, k3, fitDoseMg = 5.0) { this.ester = ester; this.D = D; this.k1 = k1; this.k2 = k2; this.k3 = k3; this.fitDoseMg = fitDoseMg; } /** Demi-vie TERMINALE (jours) = la phase la plus lente des trois. */ get terminalHalfLifeDays() { return Math.log(2.0) / Math.min(this.k1, this.k2, this.k3); } } /** * Table des modèles V3C par clé ester (identique au Kotlin — NE PAS éditer * les valeurs sans re-épingler les tests de fidélité à l'article). */ const MODELS = new Map([ // estradiol valerate (solution huileuse) ['EV', new V3C('EV', 2596.05956, 2.38229125, 0.23345814, 1.37642769)], // estradiol enanthate (solution huileuse) ['EEN', new V3C('EEN', 333.874181, 0.42412968, 0.43452980, 0.15291485)], // estradiol undecylate — ⚠️ données d'étude limitées (cf article, §Limites) ['EU', new V3C('EU', 65.9493374, 0.29634323, 4799337.57, 0.03141554)], // estradiol benzoate (solution huileuse) ['EB', new V3C('EB', 1.7050e+08, 3.22397192, 0.58870148, 70721.4018)], // estradiol cypionate — SOLUTION HUILEUSE ['EC', new V3C('EC', 1920.89671, 0.10321089, 0.89854779, 0.89359759)], // estradiol cypionate — SUSPENSION AQUEUSE (microcristaux) ; clé dédiée ['ECS', new V3C('ECS', 1.5669e+08, 0.13586726, 2.51772731, 74768.1493)], // polyestradiol phosphate — biodispo réduite, doses ~6,5× (cf article) ['PEP', new V3C('PEP', 34.46836875, 0.02456035, 135643.711, 0.10582368)], ]); /** * @param {string} ester Clé ester ("EV", "EEN", …) — lookup insensible à la casse * @returns {boolean} true si un modèle V3C existe pour cet ester */ export function hasModel(ester) { return MODELS.has(String(ester).toUpperCase()); } /** * @param {string} ester Clé ester (lookup insensible à la casse) * @returns {V3C|null} le modèle V3C de l'ester, ou null */ export function model(ester) { return MODELS.get(String(ester).toUpperCase()) || null; } /** * Réponse normalisée (pg/mL PAR mg) à dtHours après une injection de 1 mg, * modèle V3C de la méta-analyse TFS. * * @param {string} ester Clé ester ("EV", "EB", …) * @param {number} dtHours Heures écoulées depuis l'injection (≤ 0 → 0 : rien avant la dose) * @returns {number} pg/mL par mg */ export function sample(ester, dtHours) { if (dtHours <= 0.0) return 0.0; const m = model(ester); if (!m) return 0.0; const t = dtHours / 24.0; // la formule TFS travaille en jours return rawConcentrationDays(m, t) / m.fitDoseMg; } /** * Cp(t) en pg/mL pour la dose de fit (formule V3C exacte du simulateur). * * ⚠️ Le compartiment k2 peut être « ultra-rapide » (EU, PEP : k2 ~10⁵–10⁶ j⁻¹) : * son terme décroît instantanément et n'affecte que t≈0 — la double précision * JS (Float64, comme Kotlin Double) est suffisante (vérifié par les tests de * pics de l'article). * * @param {V3C} m Modèle de l'ester * @param {number} tDays Temps en JOURS depuis l'injection * @returns {number} pg/mL pour la dose de fit */ function rawConcentrationDays(m, tDays) { const D = m.D, k1 = m.k1, k2 = m.k2, k3 = m.k3; return D * k1 * k2 * ( Math.exp(-k1 * tDays) / ((k1 - k2) * (k1 - k3)) + Math.exp(-k3 * tDays) / ((k1 - k3) * (k2 - k3)) + Math.exp(-k2 * tDays) * (k3 - k1) / ((k1 - k2) * (k1 - k3) * (k2 - k3)) ); }