HormoneTrack-web/docs/CHANGELOG.md
Siphonight 902d521094 v1.13.0 (web) : reco prédictive — valeur E2 au creux, régime poolé, cible de creux — miroir Android
- predictedE2 : valeur brute au creux × facteur du ester (auto-calibration
  ON requise — sinon rien, une valeur brute serait trompeuse) ; rendu
  coloré face à la cible dans labs.js + home.js.
- pooledRegimeDoses : les doses de tous les traitements partageant (ester
  effectif, mg) forment UNE séquence — le re-parenting d'historique
  (« 6d-old ») ne redémarre plus la stabilisation. Garde-fous : ester ≠
  jamais poolé, dose ≠ exclue.
- Cible de creux E2 (opt-in) : store + carte settings + backup
  (rétrocompatible — un backup ancien n'efface pas la cible locale) +
  i18n FR/EN. ⚠️ DISTINCT des seuils d'alerte : jamais de notification.
- 15 tests (pooling ×3, statut cible, round-trip backup) ; leçon E2E :
  bump WEB_VERSION sans section CHANGELOG = dialog vide.

199 tests + E2E verts (check.sh). WEB_VERSION alignée sur 1.13.0.
2026-10-07 09:58:52 +02:00

467 lines
25 KiB
Markdown
Raw 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.

# CHANGELOG — HormoneTrack Web
> Journal des versions de l'**app web** (séparé du CHANGELOG Android —
> `docs/CHANGELOG.md` à la racine du dépôt). Convention Keep a Changelog :
> section la plus récente EN PREMIER ; ce fichier est lu par le dialog
> « Nouveautés » de l'app web (fetch `docs/CHANGELOG.md`).
## [1.13.0] — 2026-09-30
### Ajouté — recommandation de prise de sang : valeur prédite, régime poolé, cible de creux
- **Valeur E2 attendue au creux recommandé** : la carte affiche désormais
« ≈ X pg/mL » quand l'auto-calibration fournit ses facteurs (sinon rien —
une valeur brute non calibrée serait trompeuse). Miroir de
`LabRecommendation.predictedE2`.
- **Régime poolé** (`pooledRegimeDoses`, miroir du Kotlin) : les doses de
tous les traitements partageant (ester effectif, mg) forment UNE séquence
— un re-parenting d'historique (« … - old » créé a posteriori) ne
redémarre plus la stabilisation. Garde-fous : ester ≠ → jamais poolé ;
dose ≠ → exclue (le trou qui suit casse la séquence, conservateur).
- **Cible de creux E2** (opt-in, Paramètres) : référence personnelle pour la
prochaine prise de sang — la valeur prédite est colorée dans/hors cible.
⚠️ DISTINCT des seuils d'alerte (surveillance continue + notifications) :
la cible ne notifie jamais. Voyage dans le backup (champs optionnels,
rétrocompatibles).
Vérifié : 198 tests + E2E verts (`check.sh`).
## [1.12.0] — 2026-09-29
### Corrigé — un traitement inactif à Posologie projetait encore ses injections (#70)
- Miroir de l'Android v1.12.0 (remontée : « un traitement mis en inactif ne
devrait plus être pris en compte pour les futures prédictions ») :
`generateForecastDoses` (js/pk/pk-engine.js) garde `!isActive` — et
l'horizon du chip Prévision du graphique (js/ui/chart.js) ne considère
que les Posologies ACTIVES.
- §6.bis inchangé : l'historique d'un inactif reste simulé — seul le FUTUR
s'arrête. 3 tests miroirs (inactif vide, actif inchangé, avant/après
archivage).
### Ajouté — page Doses : glyphes de forme de prise + marqueurs « prise de sang le même jour »
- Chaque ligne de dose porte le glyphe de sa forme (💊 oral, 💉 injection
IM/SC, 💧 gel, 🩹 patch, 🧴 autre — helper pur `routeGlyph` dans
js/data/models.js, miroir de `routeIcon`).
- Une analyse de sang LE MÊME JOUR qu'une dose affiche un encart latéral
🧪↑ (avant la dose) et/ou 🧪↓ (après) — helper pur `labMarkersForDose`
(js/util/format.js, jour calendaire LOCAL ; lab à l'heure exacte =
APRÈS, convention documentée).
- 10 tests (`tests/doses-extras.test.js`, miroirs de DosesExtrasTest.kt).
Vérifié : 194 tests verts + E2E (`check.sh`).
## [1.11.0] — 2026-09-29
### Ajouté — les traitements inactifs regroupés en bas de la page Traitements
- Miroir de l'Android v1.11.0 (demande : « les traitements inactifs sont
peu discernables — les placer à la fin, et peut-être les rendre plus
distincts ») : les ACTIFS d'abord, les INACTIFS regroupés **tout en bas**
sous un en-tête « Traitements inactifs » (seulement s'il en existe),
cartes **atténuées** (opacity 0,55) en plus du badge.
- Tri STABLE : l'ordre relatif à l'intérieur de chaque groupe est conservé.
Helper pur `treatmentsForDisplay` dans `js/data/models.js` (miroir du
Kotlin, node-safe) — 4 tests (`tests/treatments-display.test.js`,
miroir de TreatmentsDisplayTest.kt).
Vérifié : 184 tests verts + E2E (`check.sh`).
## [1.10.0] — 2026-09-27
### Corrigé — la recommandation de prise de sang précédait la stabilisation (#68)
- Miroir de l'Android v1.10.0 (remontée + données réelles → 6ᵉ régression
épinglée côté Android) : le « creux » était le **minimum de la fenêtre
entière** (créneau N−1 → créneau N). Pour un ester à montée lente (EEn :
pic ~J+5 ≈ intervalle 7 j), ce minimum tombe **juste après l'injection
PRÉCÉDENTE** (creux d'absorption, ~3 h) — la date proposée était gouvernée
par l'injection N−1, antérieure à la stabilisation, et le texte « juste
avant ton injection du … » était faux de plusieurs jours.
- **Fix** : le creux = **niveau PRÉ-INJECTION du créneau** (dernier point de
courbe avant l'injection) — restaure le principe v1.8.0 documenté et rend
la carte cohérente. Pour les esters à t½ courte (EV), le point
pré-injection EST le minimum de fenêtre : comportement inchangé.
- Tests : la suite lab-timing (12) passe inchangée — sa sémantique épinglée
(« creux juste avant l'injection, écart ≤ 2 h ») était déjà celle du fix ;
180 tests + E2E verts (`check.sh`).
Les autres changements de la v1.10.0 Android (dialog fantôme #69, rappel
sauté si dose déjà loggée) n'ont pas d'équivalent web (notifications et
intents de notification sont spécifiques Android).
## [1.9.7] — 2026-09-20
### Corrigé — la suggestion de prise de sang « change tout le temps » à chaque injection
- Miroir de l'Android v1.9.7 (remontée + données réelles) : la règle de
stabilisation comparait l'écart inter-doses **EXACTEMENT** à l'écart
précédent — un log 30 min plus tard cassait le régime et repoussait la
stabilisation de 5 × t½ à chaque injection (creux recommandé fuyant).
- **Fix (tolérance)** : chaque écart doit rester dans **l'intervalle de
Posologie ± 24 h** (`GAP_TOLERANCE_MS`, fenêtre vs l'intervalle
THÉORIQUE). Un vrai changement de créneau (2 j au lieu de 7 j) reste
hors fenêtre. Tests : « interval change » ré-épinglé (+21 j) + 2 tests
de tolérance miroirs (12 verts lab-timing).
## [1.9.6] — 2026-09-20
### Modifié — la carte de l'accueil liste TOUTES les prochaines prises
- Miroir de l'Android v1.9.6 (remontée : le rappel quotidien CPA masquait
le rappel hebdomadaire EEn — « je ne le vois quasiment pas ») : la carte
« Prochaine dose » devient « **Prochaines doses** » (titre pluriel dès 2
lignes) — **une ligne par traitement, TRIÉES par prochaine prise** ;
la 1ʳᵉ ligne (la plus proche) reste mise en avant. Format de ligne
inchangé (`Xj Yh · sam. 26 10:00 (nom)`, `XhMM · HH:mm`, `N min`).
- Moteur : helper PUR `nextDoseLineText` dans `format.js` (miroir du
Kotlin `nextDoseLine`) — 3 tests miroirs dans misc.test.js.
- Les NOTIFICATIONS de rappel sont inchangées.
## [1.9.5] — 2026-09-20
### Corrigé — revue de code post-v1.9.4 (audit lecture-seule des deux dépôts)
- **Garde de `cutoffHours` pour ESE** (parité du Kotlin) : la branche ESE ne
vérifiait pas `hasModel(ester)` — un traitement IMPORTÉ (le backup accepte
toute string de pkModel/ester) avec un ester non couvert (ex. ECS/PEP en
ESE) levait `TypeError` et laissait un écran vide. Retombe maintenant sur
la garde Bateman (≥ 24 h), comme TFS/WHS le faisaient déjà.
- **min/max en UNE passe** : les spreads `Math.min(...allPoints)` (≈ 5 par
frame) lèvent `RangeError` au-delà de ~10⁵ arguments — atteignable avec
le Nuage actif × zoom max (≈ 230 000 points) — et allouaient un tableau
intermédiaire par appel. Remplacés par une boucle (chart.js +
chart-canvas.js).
- **Init MCMC non bloquante** : le démarrage n'attend plus le fetch de
l'asset (seul le nuage lit le posterior, qui renvoie `[]` proprement) ;
l'init re-rend à la fin et pose `data-ready` — la garantie E2E
« data-ready ⇒ asset chargé » est maintenant RÉELLE (le commentaire
antérieur était faux : le marqueur était posé avant le fetch).
- **Portage des optimisations Android v1.8.2** (la dérive de miroir était
réelle) : contexte moteur pré-calculé (`prepareE2Context`/`e2AtCtx` —
doses groupées, cutoffs par traitement, Bateman paresseux),
`computeCurve` ne recalcule plus `cutoffHours` PAR POINT × traitement ni
le scan complet de doseLogs. Résultats identiques (175 tests verts).
- **E2E version paramétrée** : les asserts lisent `data-version` posé par
l'app au lieu de strings codées en dur à bump à chaque release (l'oubli a
produit le bug v1.9.3).
### Documenté — miroir nettoyé
- En-tête de `pk-engine.js` à jour (ESE analytique v1.9.0, plus de tables
ODS) ; JSDoc « troncature » de `pk-calibration.js` alignée sur
`Math.round` (fix v1.9.2) ; `dialogs.js` (ESE=6) ; import mort et JSDoc
orpheline de `lab-timing.js` retirés ; couleurs de l'accueil via
`COLORS` (source unique avec le canvas).
- **`pk_profiles.json` exclu du zip de release** (`.gitattributes`
export-ignore, −548 Ko livrés) : plus fetché au runtime depuis v1.9.0 —
l'asset ne survit que pour le test de fidélité RMS (miroir Android),
`pk-profile-store.js` documenté test-only.
## [1.9.4] — 2026-09-19
### Ajouté — les presets du modèle ESE couvrent ses 6 esters injectables
- **Remontée** : depuis l'ajout du modèle source Estrannaise (v1.9.0), les
presets de création n'offraient toujours que EV/EU/EEn — « les presets
n'ont pas été mis à jour lors de l'ajout du modèle source pour ESE ».
- **Presets** : 3 presets ESE ajoutés (25 entrées, miroir de
`PKPresets.kt`) — EC huile (5 mg IM), EB (1 mg IM), EUCS undécylate
suspension cristalline (10 mg SC, ester exclusif à ce fit). Champs
Bateman informatifs = t½ terminales des fits (pas utilisés au calcul).
- **Éditeur** : la liste des esters suit la couverture RÉELLE du modèle
choisi (`choicesForModel`, parité Android v1.9.0) — ESE liste ses 6
esters injectables ; au changement de modèle, si l'ester courant sort de
la couverture (ex. EUCS + TFS), il rebascule sur EV (garde anti
« 0 pg/mL ») et le formulaire se re-rend.
- **Corrigé (portage v1.9.0 incomplet)** : `Esters.EUCS` manquait dans
`pk-engine.js` (le preset référençait `undefined`) et `choicesForModel`
(`js/data/models.js`) était resté sur la couverture des tables ODS
(3 esters) avec sa KDoc obsolète « produirait 0 pg/mL ».
- **Tests** : `tests/presets.test.js` (nouveau, 6 tests) — couverture des
6 esters ESE, unicité, clés i18n EN/FR, TFS (7) / WHSAH (6) inchangés,
`choicesForModel` aligné sur les dispatchs. Suite web : 175 tests.
## [1.9.3] — 2026-09-19
### Corrigé — le numéro de version annoncé
- Le dialog « Nouveautés » et Paramètres annonçaient **1.9.1** au lieu de
1.9.2 (le commit de fix du nuage n'incluait pas la mise à jour de
`WEB_VERSION`) — méta-correction de version, **aucune autre différence
avec 1.9.2** (mêmes fonctionnalités que l'Android 1.9.2).
## [1.9.2] — 2026-09-19
### Corrigé — le nuage d'incertitude exigeait un traitement STOCKÉ en ESE
- Miroir du fix Android v1.9.2 : le filtre du nuage testait le `pkModel`
**STOCKÉ** du traitement, alors que la courbe ESE affichée redessine
toutes les doses E2 avec `modelOverride = "ESE"` — le nuage restait vide
dès qu'aucun traitement n'était stocké en ESE.
- **Fix** : le nuage couvre **toutes les doses E2 à profil injectable dont
l'ester effectif (override compris) est couvert par le fit
Estrannaise** — indépendamment du modèle stocké. L'exclusivité ESE
reste portée par le chip. L'oral Bateman reste hors nuage.
- Test réécrit : « les doses d'un traitement TFS sont couvertes quand ESE
est affiché » (épinglé des deux plateformes) ; oral seul → vide.
## [1.9.1] — 2026-09-19
### Corrigé — le nuage d'incertitude entoure maintenant la courbe Estrannaise calibrée
- Miroir du fix Android v1.9.1 : la courbe ESE affichée est calibrée
(scalePerEster des labs) mais le nuage était tracé brut → échelles
différentes. Le nuage reçoit maintenant le MÊME scalePerEster que la
courbe ESE.
- Harmonisation de l'arrondi de la médiane de calibration (le web avait
la troncature historique) : les niveaux estimés web et Android
convergent. Le niveau correct = l'ARRONDI (plus juste que la
troncature, cf CHANGELOG Android 1.9.1).
## [1.9.0] — 2026-09-18
### Modèle Estrannaise ANALYTIQUE + nuage d'incertitude (portage Android v1.9.0, versions sync)
- **Le modèle ESE n'est plus les tables horaires ODS** : il est désormais la
**forme close 3C publiée par estrannaise.js** (les sources originelles
dont l'ODS n'était que l'échantillonnage horaire) — fidélité épinglée :
RMS 0,00 vs les tables sur 0→200 j, écart de pic ≤ 0,1 %. Les tables ne
sont plus fetchées au démarrage (−550 Ko, démarrage plus rapide) ;
`pk_profiles.json` reste uniquement comme référence des tests.
- **6 esters injectables couverts** (EV, EU, EEn, EC, EB + **EUCS**, undécylate
suspension cristalline, exclusif à ce fit) — l'ancien ESE n'en couvrait
que 3.
- **NUAGE D'INCERTITUDE (exclusif ESE)** : Estrannaise publie le posterior
MCMC de ses paramètres (313 échantillons/ester) — chip `Nuage`
(off par défaut, activable à volonté, actif SEULEMENT si ESE est affiché)
→ nuage diffus de courbes montrant la plage d'imprécision du modèle,
comme sur le site estrannaise. TFS/WHSAH ne publient pas de posterior →
pas de nuage pour eux.
- t½ terminale ESE désormais **analytique** (ln2/min(k1,k2,k3)) — la
recommandation de prise de sang n'estime plus la t½ par pente de table.
- **Tests** : fidélité (RMS, pics), cas dégénérés, MCMC (313 × 6), nuage
(32 courbes, dispersion, exclusivité ESE), t½ analytique.
## [1.8.1] — 2026-09-17
### Corrigé — la stabilisation ignorait les changements récents (dose/ester/intervalle)
- **Bug remonté** : la recommandation disait « stabilisée depuis février »
alors que l'ester, le dosage ET la posologie venaient de changer —
l'ancien calcul partait de la **1ʳᵉ dose du traitement** (proxy aveugle
aux changements).
- **Fix** : le régime courant = la **séquence terminale de doses** à
(ester effectif, dose mg, **écart inter-doses**) constants — tout
changement récent réinitialise la stabilisation : la recommandation saute
au premier creux **après** la stabilisation du nouveau régime (règle des
~5 demi-vies conservée). L'encart porte l'ester **effectif** de la
dernière dose (override compris).
- **Nouveau : encart sur l'Accueil** (demande) — la suggestion de
prochaine prise de sang est désormais aussi visible directement sur la
page d'accueil (creux daté + créneau associé, mention de stabilisation
si le régime n'était pas déjà stable).
- 3 nouveaux tests miroirs (155 au total) : changement de dose / de
posologie (intervalle) / d'ester → réinitialisation épinglée.
## [1.8.0] — 2026-09-17
### Prochaine prise de sang recommandée — portage de l'Android v1.8.0 (versions sync)
- **Nouvel encart sur la page Analyses** : « Prochaine prise de sang
(suggestion) » — quand un traitement E2 **injectable actif a une
Posologie**, l'app déduit de ses courbes **le creux estimé juste avant
l'injection suivante** (heure exacte + créneau associé + mention « ou
simplement la veille »), **au premier creux où l'ester est stabilisé**
(~5 demi-vies terminales après le dernier changement — on saute les
creux « de transition »).
- `js/pk/lab-timing.js` : miroir fidèle du Kotlin (créneaux
generateForecastDoses, courbe E2 fine sans calibration, filtres
maintenant/+6 h/dernière prise/stabilisation). Le modèle Estrannaise lit
sa demi-vie dans les tables (`pkProfileStore.terminalHalfLifeDays`,
refactor miroir), TFS/WHSAH analytiquement.
- **Sans Posologie** sur l'injectable actif : encart remplacé par l'invite
« renseigne une Posologie… » (demande v1.8.0) — le lien est découvrable.
- 7 tests miroirs (152 au total) + **scénario E2E** (encart rendu sur #labs
avec la démo EEn 7 j : creux daté, créneau, stabilisation).
## [1.7.1] — 2026-09-16
### Corrigé — une seule note sur deux s'affichait dans les Analyses
- **Bug remonté** (identique Android) : dans une prise de sang E2+T, quand
les deux entrées portent des notes **distinctes** (ex. une note clinique
sur l'E2, une note « DHT » sur la T), une seule s'affichait — l'autre
était bien conservée mais perdue à l'affichage.
- **Cause** : l'ancien code ne prenait que la première note non vide du
groupe (hypothèse historique « toutes identiques » — vraie à l'époque du
dialog de création, fausse depuis l'édition unitaire).
- **Fix** : `labNotesForDisplay` (helper PUR dans `format.js`, miroir du
Kotlin) — notes distinctes → une ligne par note préfixée du marqueur
(« E2 : … » / « T : … ») ; note identique → une seule ligne ; vides ignorées.
- Épinglé par : 5 tests unitaires (miroir du Kotlin) + **scénario E2E** (la
dernière paire de démo porte des notes distinctes, les deux s'affichent
sur #labs) + régression data-driven Android sur l'export réel.
## [1.7.0] — 2026-09-13
### Unités des axes du graphique — portage de l'Android v1.7.0 (versions sync)
- **pg/mL** (E2, axe gauche) et **ng/mL** (T, axe droit) affichées au sommet
des colonnes de labels du graphique de simulation — miroir exact du fix
Android (padTop élargi 12 → 26 px, texte gris dans la couleur des labels).
- Universelles, PAS des clés i18n (déjà en dur dans les légendes).
> Note de parité : la **sauvegarde automatique quotidienne** de l'Android
> v1.7.0 n'est PAS portée — le navigateur ne peut pas écrire périodiquement
> dans un dossier arbitraire sans intervention (voir §12 Limites) ;
> l'export/import manuel du backup couvre la même donnée.
## [1.6.1] — 2026-09-13
### Corrigé — les options du graphique se réinitialisaient « toutes seules »
- **Bug remonté** : sur l'écran Graphiques, les options sélectionnées
(modèles affichés, Tracé labs, Prolonger, zoom, panoramique…) revenaient
d'elles-mêmes aux défauts « alors qu'on n'a pas changé de menu ».
- **Cause** (divergence de portage) : le shell web re-crée l'écran ENTIER
à chaque **tick minute** (60 s), **mutation du store** ou **resize** —
et l'état des options vivait dans une variable LOCALE de la fonction de
rendu, donc retombait aux défauts à chaque re-crétion. Côté Android,
l'équivalent vit dans des `remember { mutableStateOf }` qui survivent à
toutes les recompositions : le bug est structurellement impossible côté
APK — c'était un piège du portage, pas de la logique.
- **Fix** : l'état d'interaction du graphique vit au niveau MODULE et le
shell signale les re-rendus du **même** écran (`preserveState: true`)
→ options préservées pendant qu'on regarde le graphique ; à l'entrée
depuis la navigation (quitter puis revenir), retour aux défauts —
comportement identique à l'Android.
- **Épinglé par l'E2E** : re-render déclenché (resize — même code path
que le tick) → chip et légende toujours en place ; navigation → reset.
> Note de parité : fix **web-only** (pas de changement Android) — les
> fonctionnalités portées restent celles de l'Android v1.6.0.
## [1.6.0] — 2026-09-12
### « Tracé labs » prolongé au-delà du dernier lab — portage de l'Android v1.6.0
- **Nouveau chip `Prolonger`** (à côté de `Tracé labs`, **off par défaut**,
désactivé tant que Tracé labs est off) : la courbe ancrée sur tes
prises de sang se PROLONGE au-delà du dernier lab — la limite v1.5.0
rendait invisible justement la période la plus récente.
- **Recette** : au-delà du dernier lab significatif, `courbe(t) = M(t) ×
ρ_dernier lab` — le modèle PK (dosages, esters injectés depuis, pics et
descentes classiques) × le ratio mesuré au dernier lab, supposé constant.
Les doses loguées APRÈS le dernier lab font repartir la courbe en pic
(override d'ester compris).
- **Horizon honnête** : la prolongation s'arrête quand le MODÈLE lui-même
s'éteint (dernière dose + cutoff moteur : 10 t½ terminales V3C/WHS, fin
de table ODS, 30 t½ Bateman) — jamais de ligne à zéro inventée.
- **Lisibilité** : partie prolongée dessinée SÉPARÉMENT (même rose,
atténué) avec sa légende dédiée + **AVERTISSEMENT visible** sous la
légende : simple simulation sans garantie de correspondre au réel,
extrapolée à partir de tes résultats de laboratoire — qui peuvent
eux-mêmes être erronés. Fie-toi à ta prochaine prise de sang.
- Garde anti-ratés inchangée (#61) : un lab hors fenêtre d'action ne
prolonge pas la courbe et n'est jamais traversé.
- Portage fidèle : `extensionHorizonEndMs`/`lastAnchorMs` (miroir du
Kotlin), 8 nouveaux tests unitaires miroirs + **scénario E2E navigateur**
(activation des chips, légende et avertissement rendus).
## [1.5.0] — 2026-09-11
### « Tracé labs » — portage de la version Android v1.5.0 (versions synchronisées)
- **Nouvelle courbe superposable** (chip `Tracé labs`, **off par défaut**)
: `courbe(t) = M(t) × ρ(t)` — le modèle PK garde la forme biologique
(Tmax, queues, accumulation), l'amplitude est recalée sur CHAQUE
prise de sang — **passage EXACT garanti** sur chaque lab, transition
log-linéaire d'un lab au suivant (jamais de saut vertical).
- Garde anti-ratés (#61) : un lab hors fenêtre d'action n'est pas un
ancrage ; E2 uniquement ; fenêtre bornée [1er ; dernier lab] — la
courbe s'arrête au dernier lab (pas d'extrapolation).
- Indépendante de la calibration (sa propre référence) ; hors
pics/creux ; n'entre JAMAIS dans niveau accueil/alertes.
- La rangée de chips du graphique défile horizontalement (4ᵉ chip —
miroir du fix Android v1.5.0).
- 11 tests miroirs de `LabTrajectoryModelTest.kt` (Android).
## [1.4.10] — 2026-09-08
Version initiale de la version web — portage navigateur de l'app Android
v1.4.10, 100 % locale (données dans le localStorage, aucun serveur
applicatif, aucune télémétrie).
### Moteur PK (fidèle à l'Android, testé)
- **Trois modèles superposables** : Estrannaise (tables ODS de
`pk_profiles.json`, même asset que l'app Android), Transfem Science (forme
close V3C de la méta-analyse, 7 esters), WHSAH (fit license-free de Mona,
6 esters — PEP non couvert) + Bateman paramétrable (gel/patch/oral).
- Pics de l'article TFS épinglés (±2 %) ; équilibre EV 5 mg/7 j = Figure 11
(Cmax 384 / Cmin 142 / Cavg 269) ; fidélité Mona WHSAH (EV 367,5 @ 1,69 j,
EEn 187,9 @ 4,99 j, t½ 7,34 j).
- Invariants Android préservés : bisection `computeKa` dans le bon sens
(#19), modèle strict + extrapolation terminale ≥ 1 % du pic (#20/#21),
lookup insensible à la casse (#22), conversion des labs T (#23/#26),
garde de significativité de calibration (#61), créneaux de prévision
passés sautés (#35), rappels sur la grille Posologie (#52).
- Calibration **par période d'ester** et **par modèle affiché** (#60) :
auto-calibration optionnelle (désactivée par défaut) + calibration
manuelle par traitement ; k T par période d'ester.
### Fonctionnalités
- Accueil : niveau actuel estimé (E2 pg/mL + T ng/mL) avec delta « vs il y a
6 h » réel (fix #53), cartes d'alerte de seuil, carte prochaine dose
(jours au-delà de 24 h, fix v1.4.1), log rapide, mini-graphique 24 h.
- Graphiques : 24 h/7 j/30 j, zoom 6 h → 300 j (pinch, molette, boutons,
focal du pinch conservée), panoramique avec **cumul fractionnaire** des
deltas (fix #62), prévision avec extension de fenêtre sans saut (fix #54)
et horizon borné 30 j–1 an, toggles 3 modèles alignés sur les traitements
(fix #58), pics/creux avec valeurs, marqueurs de doses réelles et
projetées (fix #55), labels X à minuit local dans le fuseau choisi
(v1.4.5), labs T convertis en ng/mL au rendu.
- Doses : historique groupé par jour, Δ jours depuis la dose précédente du
même traitement, édition (override d'ester par injection selon le modèle),
en-tête « temps sous THS » (3 placeholders, bug #47 épinglé).
- Analyses : prise de sang E2 + T en une entrée, groupement par timestamp,
suppression par prise entière, unités T multiples (ng/mL, ng/dL, ng/L,
nmol/L).
- Traitements : 22 presets, éditeur complet (ester/modèle/Bateman,
calibration, Posologie, rappel), traitement inactif = drapeau
administratif (la simulation reste, bug v1.2.4).
- Rappels web : notifications navigateur tant que la page est ouverte
(bannière in-app + actions « Loguer maintenant » / « Reporter 1 h »).
Limite documentée : pas de notification onglet fermé.
- Seuils d'alerte : évaluation du niveau estimé, notification web, anti-spam
par état persisté (même codec que l'app Android).
- Sauvegarde JSON **compatible Android dans les deux sens** (BackupData v2,
rétrocompat v1, import en mode écrasement avec IDs conservés) ; paramètres
utilisateur transportés (langue, auto-cal, seuils).
- Logs de diagnostic exportables (buffer 500 lignes, même format que
l'app Android).
- FR + EN (langue par app, indépendante du système), dialog « Nouveautés »
après mise à jour.
### Processus
- 121 tests Node (`node --test`) : profils ODS (pins ODS identiques à
l'Android), fidélité TFS/WHSAH, moteur, calibration, backup (dont import
des exports Android réels de `local-test-data/` si présents), helpers du
graphique, alertes, rappels, changelog, store.
- `scripts/check.sh` : syntaxe de tous les modules + cohérence i18n FR/EN +
tests + smoke HTTP optionnel.
- **Dépôt GIT SÉPARÉ** de l'app Android (HormoneTrack-web) — historique
100 % propre (aucune dette de confidentialité), versions alignées sur
l'Android, changelogs totalement indépendants.
### Non porté (impossible ou hors périmètre navigateur)
- Événements d'agenda récurrents (pas de CalendarProvider dans un
navigateur) — l'éditeur affiche une note explicative.
- Notifications quand l'onglet est fermé (aucun scheduler système côté web).
- Verrou biométrique, widget — non implémentés non plus côté Android v1.