HormoneTrack-web/js/pk/transfem-science-models.js
Siphonight 491efef8eb v1.4.10 — portage initial de l'app Android v1.4.10
Portage navigateur complet de l'app Android HormoneTrack v1.4.10 :
- moteur PK fidèle (Estrannaise tables ODS, Transfem Science V3C,
  WHSAH fit Mona, Bateman) — paramètres identiques, invariants Android
  préservés (fixes #19-#23, #35, #52-#62), 121 tests Node épinglés
- calibration par période d'ester et par modèle affiché (#60/#61)
- UI 6 écrans + éditeur, CurveChart Canvas (zoom/pan fractionnaire,
  prévision, pics/creux, fuseau configurable), rappels web
  (Notification API), seuils d'alerte, i18n FR/EN
- sauvegarde JSON v2 compatible Android bidirectionnelle (rétrocompat v1)
- 100 % local : localStorage, aucun serveur applicatif, aucune télémétrie
- processus : scripts/check.sh (syntaxe, i18n, tests, E2E navigateur,
  smoke HTTP), docs séparées (README + DEVELOPPEMENT + CHANGELOG)
- dépôt GIT SÉPARÉ de l'Android : historique 100 % propre, versions
  alignées sur l'Android porté, changelogs indépendants

Non porté (impossible dans un navigateur, documenté §12) : agenda
récurrent, notifications onglet fermé, montre.
2026-09-08 14:34:58 +02:00

138 lines
6.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ─────────────────────────────────────────────────────────────────────────────
* 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))
);
}