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.
138 lines
6.2 KiB
JavaScript
138 lines
6.2 KiB
JavaScript
/**
|
||
* ─────────────────────────────────────────────────────────────────────────────
|
||
* 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))
|
||
);
|
||
}
|