HormoneTrack/docs/CHANGELOG.md

492 lines
29 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
Format : [Keep a Changelog](https://keepachangelog.com/fr-FR/1.1.0/).
Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.4.0`, …).
## [1.4.0] — 2026-09-06 (versionCode 20)
### Ajouté
- **Modèle Transfem Science reconstruit sur la méta-analyse officielle**
(https://transfemscience.org/articles/injectable-e2-meta-analysis/) :
la courbe TFS n'est plus dérivée des tables horaires du tableur — elle
est désormais la **forme close exacte du modèle à 3 compartiments (V3C)**
publié par le simulateur TFS, avec les paramètres D/k1/k2/k3 des 7 esters
(EV, EEn, EU, EB, EC huile, EC suspension, PEP). PICS DE RÉFÉRENCE
REPRODUITS À ~1 % (épinglés par `TransfemScienceModelsTest`) : EV 295
pg/mL @ 2,1 j ; EEn 160 @ 6,5 j ; EB 971 @ 0,65 j ; EC 155 @ 4,3 j ;
EC susp. 241 @ 1,2 j ; PEP 34 @ 32,5 mg @ 18 j ; AUC et état d'équilibre
EV 5 mg/7 j (Cmax 384 / Cmin 142 / Cavg 269) conformes à la Figure 11.
Avantages : plus de plancher d'arrondi 0,01 en queue de table, pas de
coupure à 8001 h (extrapolation exacte), coupure moteur à 10 demi-vies
terminales.
- **Nouveaux esters** : benzoate (EB), cypionate huile (EC), cypionate
suspension aqueuse (ECS) et polyestradiol phosphate (PEP) — 4 presets
TFS, override d'ester par dose étendu (la liste suit le modèle du
traitement : TFS = les 7 esters, Estrannaise = EV/EU/EEn faute de tables
ODS). PEP = biodisponibilité réduite, doses ~6,5× (cf article).
- **Rappels qui suivent la Posologie** (fix #52, Corrigé ci-dessous) :
la carte « Prochaine dose » de l'accueil passe par le même moteur que
les alarmes.
- **12 nouveaux tests** (102 au total / 84 sans les données locales) :
`TransfemScienceModelsTest` (pics, t½, AUC, équilibre de la Figure 11)
et `ReminderScheduleTest` (grille Posologie, créneau manqué, fallback
quotidien).
### Corrigé
- **Les rappels se déclenchaient TOUS LES JOURS** sans tenir compte de
l'intervalle entre prises (#52, remontée utilisateur : « je m'injecte
tous les samedis à 18 h, je dois recevoir le rappel uniquement le
samedi à 18 h ») : le calcul du prochain déclenchement ignorait doses et
Posologie. Désormais, un traitement avec Posologie + historique sonne
le jour du créneau à l'heure choisie (dernière dose + k × intervalle,
créneaux passés sautés comme la prévision) ; sans Posologie ou sans
historique → comportement quotidien inchangé. De plus, l'alarme suivante
est reprogrammée **après chaque notification** et **après « Pris »**
(les alarmes sont one-shot : la chaîne n'existait qu'au boot/au save).
## [1.3.5] — 2026-09-06 (versionCode 19)
### Corrigé
- **Création du calendrier d'agenda impossible** (remontée via les logs de
diagnostic — le mécanisme v1.3.1 a tout de suite nourri le diagnostic
à distance : `Sync adapters must specify an account and account type`) :
l'URI d'insertion `CALLER_IS_SYNCADAPTER=true` du calendrier local
« HormoneTrack » n'embarquait pas `ACCOUNT_NAME` + `ACCOUNT_TYPE` —
exigé par le CalendarProvider pour toute opération sync-adapter
(`CalendarEvents.ensureCalendar`, §14 #50). Ces mêmes logs prouvent aussi
que **la permission agenda est désormais accordée** (fix manifest v1.3.3 ✓)
et que **l'export des logs fonctionne sur le téléphone** (fix #48 ✓) —
la chaîne de diagnostic est opérationnelle.
- **Suppression d'événement d'agenda silencieusement manquée** (#50 bis) :
l'URI de DELETE avait le même défaut (`CALLER_IS_SYNCADAPTER` sans params
account) → « switch OFF » + save loguait « supprimé » mais l'événement
restait dans le provider. Fix : mêmes paramètres sur l'URI de delete.
Validé émulateur : cycle complet ON→save→OFF→save = événement créé puis
réellement supprimé (`content query` vide).
- **Événement d'agenda orphelin après tout re-save** (#51) :
`TreatmentEditorScreen.buildTreatment()` ne transmettait pas
`calendarEventId` → l'id revenait à `null` à chaque sauvegarde : après le
premier save, l'événement devenait définitivement orphelin (plus jamais
supprimable/désactivable par l'app). L'id existant est désormais chargé à
l'ouverture et conservé par le save.
- **Log « événement agenda supprimé » mensonger** : imprimé aussi quand rien
n'a été supprimé (switch désactivé, posologie absente, permission absente).
Désormais trois messages distincts : « créé/mis à jour (id=…) »,
« supprimé », « non activé (switch/posologie/permission) ».
### Ajouté
- Validation émulateur de l'agenda end-to-end (recette §16.ter, permissions
accordées via `pm grant`) : calendrier + événement récurrent créés et
visibles dans le provider (`content query`).
## [1.3.4] — 2026-09-06 (versionCode 18)
### Corrigé
- **L'écran Doses faisait crasher l'app** (remontée) : `MissingFormatArgumentException:
Format specifier '%3$d'` — la string `hrt_duration` a TROIS placeholders
(`%1$d mois, %2$d jours, %3$d total`) mais l'appel transmettait 2 arguments.
Crash dès le premier affichage de l'écran Doses avec une donnée antérieure à
aujourd'hui. **Le bug est né en v1.3.1** (string + appel dans le même commit,
jamais testés ensemble) et a survécu aux releases v1.3.1→v1.3.3. Fix : passer
totalDays en 3ᵉ argument (cf §14 #47).
- **L'export des logs de diagnostic faisait crasher l'app** (remontée répétée ;
les « fixes » IO de v1.3.2 et v1.3.3 étaient corrects mais à côté du vrai
problème) : le nom de fichier était construit via
`LocalDate.now().format(ofPattern("yyyyMMdd-HHmm"))` — un `LocalDate` n'a pas
de champ horaire → `UnsupportedTemporalTypeException: Unsupported field:
HourOfDay`, levée SYNCHRONEMENT dans le onClick (thread UI) au tap sur
« Exporter », avant même l'ouverture du gestionnaire de fichiers. L'export
JSON fonctionnait car son pattern `yyyyMMdd` est valide pour un LocalDate.
FIX : utilitaire `ExportFileNames` (pur, testé) — `diagnosticLogFileName()`
utilise un `LocalDateTime` ; `backupFileName()` reste sur `LocalDate`.
Cf §14 #48.
- **Lint `StringFormatMatches`** (détecté en implantant le lint — nouveau filet
de la v1.3.4) : la notification de rappel passait un Double à une `%s`.
Fix : `dose.toString()` (comportement d'affichage inchangé, #49).
### Ajouté
- **Lint en filet bloquant** : `./gradlew lint` est désormais vert (it aurait
attrapé dès v1.3.1 les deux crashs ci-dessus via `StringFormatMatches`).
Les deux checks Compose 1.12+ théoriques (`NonObservableLocale`,
`LocalContextGetResourceValueCall`) sont rétrogradés en warning via
`app/lint.xml` (staleness de config, refonte i18n plus tard, cf §19/§20).
- **Recette reproductible : test manuel sur émulateur** (documentée §16.ter) :
émulateur + image ATD ou google_apis, seed de la DB du debug avec un backup
JSON via le nouveau script **`scripts/seed-emulator.py`** (générique, sans
aucune donnée embarquée), installation du APK release par-dessus (données
conservées, même signature), pilotage par `uiautomator dump` + `input tap`,
crashs via `adb logcat -b crash`. C'est cette recette qui a reproduit et
confirmé les deux crashs ci-dessus ainsi que LEUR correction (flux SAF
complet : picker → SAVE → fichier écrit → message de confirmation, sur
l'APK release données réelles).
- **3 nouveaux tests** (`ExportFileNamesTest`, 90 au total / 72 sans les
données locales) : épinglent le format des noms de fichiers d'export —
auraient attrapé le bug #48 le jour même.
## [1.3.3] — 2026-09-06 (versionCode 17)
### Corrigé
- **Export des logs plantait TOUJOURS l'app** (remontée ; le « fix » v1.3.2
était insuffisant) : l'écriture était réimplémentée en inline au lieu de
réutiliser l'IO éprouvée de l'export JSON, et les appels `AppLog.log` du
callback étaient hors try/catch — une exception dans une coroutine à scope
racine (CoroutineScope standalone) remonte au handler de la thread =
crash du process. FIX : l'export passe désormais par
**`BackupManager.writeBackup` tel quel** (le même code path que l'export
JSON qui fonctionne sur le téléphone), tout le callback est gardé, et
`AppLog.log` ne lève plus jamais d'exception (IO fichier avalée en interne,
on perd au pire une ligne de journal). Feedback visible succès/échec
(comme l'export JSON) + compteur de lignes rafraîchi après l'export.
- **Événements d'agenda inopérants depuis v1.3.0** : les permissions
WRITE_CALENDAR / READ_CALENDAR n'étaient **pas déclarées dans le manifest**
(jamais présentes dans tout l'historique git, bien que documentées §9) —
la demande à l'exécution était donc refusée d'office et
`CalendarEvents` lève SecurityException. Cause racine manquante des bugs
« toggle agenda » « corrigés » en v1.3.1/v1.3.2 : la logique UI était
correcte, il ne venait à l'esprit de personne que la déclaration manquait.
- **Métadonnées de version fausses** : le bump de version n'avait jamais
été commité — les tags v1.3.0, v1.3.1 **et** v1.3.2 contenaient tous
`versionCode = 14 / versionName = "1.3.0"`, donc les APK publiés
affichaient « 1.3.0 » dans Paramètres et en titre du dialog « Nouveautés ».
Désormais bumpé + commité avant chaque tag (checklist §16 étape 1).
### Technique
- Doc de développement mise à jour (DB v3, targetSdk 36, fichiers de
données de régression, bugs #43–#45) ; téléphone de test documenté :
**Google Pixel 9 /e/OS** (AOSP, DocumentsUI standard — l'export JSON y
fonctionne, l'export logs réutilise ce même path).
- Compteur de tests inchangé : 87 (44 sans les données locales, gitignorées).
## [1.3.2] — 2026-09-06 (versionCode 16)
### Corrigé
- **Toggle de l'agenda** : le switch ne s'activait pas (le résultat de la
permission arrive ASYNCHRONE — le test synchrone juste après `launch()`
était toujours faux). FIX : le callback active le switch ; Posologie
désormais **requise** pour l'agenda (refus + message si absente) ;
permission vérifiée RÉELLEMENT via `ContextCompat` au save (le flag
remembered pouvait être faux après recomposition) ; journalisation
AppLog à chaque étape.
- **Export des logs** plantait l'app : réécrit avec le pattern éprouvé de
l'export JSON (BackupManager) ; chaque étape journalisée dans AppLog.
- **Dialog « Nouveautés » réapparaissait à chaque réouverture** : la version
vue est désormais mémorisée AVANT l'affichage (et non après) + AppLog
pour diagnostiquer si ça se reproduit.
- **Titre du dialog « Nouveautés »** : affiche la version de l'APP
(`BuildConfig.VERSION_NAME`) — si l'APK installé est 1.3.0, le titre
montre 1.3.0 même si le contenu parle d'une version plus récente
(l'asset changelog.md peut contenir des sections plus récentes si
l'APK a été reconstruit après coup). Installer le dernier APK
(`HormoneTrack-v1.3.2-release.apk`) pour avoir la version correcte.
## [1.3.1] — 2026-09-06 (versionCode 15)
### Ajouté
- **En-tête « temps sous THS »** sur la page Doses : depuis la 1re prise
enregistrée, en jours totaux et décomposition mois/jours (mois = 30 j,
estimation documentée).
- **Logs de diagnostic** (v1.3.1) : journal horodaté des événements notables
(agenda, rappels, import/export, erreurs), buffer circulaire de 500 lignes,
persisté dans `filesDir/debug-log.txt` ; export (SAF) et effacement depuis
Paramètres. Objectif : debug à distance via les logs joints aux retours.
- 9 nouveaux tests (87 au total) : AppLog (formatage, buffer circulaire) et
HrtDuration (jours, décomposition mois/jours).
### Corrigé
- **Toggle de l'agenda ne s'activait pas** (bug remonté) : le résultat de la
permission arrive ASYNCHRONE — le test synchrone juste après `launch()`
était toujours faux → le switch rebondissait sans s'activer et sans
feedback. FIX : le callback active le switch ; feedback visible (hint) et
journalisation (AppLog).
## [1.3.0] — 2026-09-06 (versionCode 14)
### Ajouté
- **Dialog « Nouveautés »** : après chaque mise à jour (et à la première
installation), l'app affiche le changelog des versions non vues dans une
fenêtre fermable — ne réapparaît pas avant la prochaine mise à jour
(version vue mémorisée). Contenu = `docs/CHANGELOG.md` embarqué en asset,
synchronisé automatiquement à chaque build (tâche Gradle `copyChangelog`).
- **Événements d'agenda récurrents** : dans l'éditeur de traitement, sous
« Rappels », un switch « Événement d'agenda récurrent » crée un événement
dans un calendrier local dédié « HormoneTrack » (visible dans l'agenda du
téléphone) avec répétition RRULE dérivée de la **Posologie** (tous les N
jours, arrondi demi-supérieur). Permission agenda demandée à l'activation ;
suppression/recréation automatique au save.
- **Paramètres : version installée + lien cliquable** vers les releases Gitea
(télécharger les APK sans attendre le rappel de mise à jour).
- 11 nouveaux tests (78 au total) : ChangelogHelper (8 : comparaison SemVer
numérique 1.2.9 < 1.2.10 — lexicographique aurait tort, extraction de
sections, première installation), CalendarRruleTest (3 : RRULE, arrondi
demi-supérieur — **kotlin.math.round arrondit les ties vers l'entier PAIR
(6,5 → 6)**, piège documenté).
## [1.2.10] — 2026-09-05 (versionCode 13)
### Corrigé
- **Sens des boutons de zoom inversé** (retour utilisateur) : convention
carte — « **+** » = zoom **avant** (fenêtre plus courte, plus de détail) et
« **−** » = zoom **arrière** (fenêtre plus longue, plus de temps). Avant,
le « + » dézoomait (montrait plus de temps) — pas intuitif. Le pinch
(écarter = zoom avant) était déjà correct.
## [1.2.9] — 2026-09-05 (versionCode 12)
### Ajouté
- **Zoom du graphique de simulation** : pinch à 2 doigts **et** boutons − / +
(accessibilité), plage bornée **6 h → 300 j** ; le point focal du pinch reste
fixe dans le temps ; échantillonnage adaptatif (15 min ≤ 12 h, 30 min ≤
24 h, sinon 1 h — courbes lisses à fort zoom) ; labels X adaptatifs
(1 h / 3 h). Un seul gestionnaire de gestes gère pan + zoom (pas de
conflit de consommation).
- **README : disclaimer IA** en en-tête — projet développé avec l'IA,
contribution humaine essentielle (feedback, retours utilisateur, suggestions) ;
mention factuelle reprise en §1/§2 de la doc de développement.
- 3 tests `stepForRange` (67 au total).
## [1.2.8] — 2026-09-05 (versionCode 11)
### Modifié
- **« Fréquence d'injection » renommé « Posologie »** (section Traitements) :
le terme « fréquence d'injection » n'avait pas de sens pour les prises
d'hormones non injectables (gel, patch, oral). L'astuce d'intervalle est
inchangée ; les textes d'aide disent désormais « dernière **prise**
enregistrée » au lieu de « dernière injection » (EN : « Dosage »).
## [1.2.7] — 2026-09-05 (versionCode 10)
### Corrigé
- **Prévision cassée après un oubli d'injection** : le premier créneau projeté
tombait dans le PASSÉ (dernière prise + intervalle = jour de l'injection
manquée) → la courbe peignait un **faux pic dans l'historique** et tout le
rythme projeté était décalé. Désormais les créneaux déjà passés ne sont
jamais simulés : la prévision avance au premier créneau **strictement
futur** au rythme configuré. Un simple **retard**, lui, décale naturellement
toute la prévision (elle part de la dernière prise réelle — comportement
voulu, désormais testé).
- 2 nouveaux tests (64 au total) : oubli (créneaux passés sautés, espacement
conservé) et retard (décalage complet de la prévision).
## [1.2.6] — 2026-09-05 (versionCode 9)
### Ajouté
- **Valeurs estimées sur les pics/creux** : chaque triangle ▲▼ du graphique est
accompagné de la valeur simulée de l'extremum (au-dessus d'un pic, en dessous
d'un creux), dans la couleur de la courbe.
### Corrigé
- **Import JSON en mode ÉCRASEMENT** : l'import échouait dès que l'app
contenait déjà des données (conflit d'IDs du backup avec les IDs existants).
Désormais l'import efface d'abord les données actuelles (ordre enfants →
parents), restaure le backup (IDs conservés → FK valides), restaure le
réglage T du backup et **reprogramme les rappels**. Le dialog d'import
prévient clairement : « Effacer & restaurer ».
## [1.2.5] — 2026-09-05 (versionCode 8)
### Corrigé
- **Espace vide en haut de l'écran** (contenu trop bas) : double consommation des
insets — la fenêtre poussait déjà le contenu sous la barre de statut (thème
AppCompat) ET les `TopAppBar` Material 3 rajoutaient leur padding de barre de
statut interne. Passage en **edge-to-edge propre** (`enableEdgeToEdge()`) :
- les `TopAppBar` consomment la barre de statut (une seule fois) ;
- les `Scaffold` (racine + imbriqués Doses/Analyses) ont
`contentWindowInsets = 0` pour ne pas cumuler ;
- la `NavigationBar` consomme la barre de navigation système.
Le contenu remonte, les listes défilent visuellement sous la barre de statut
(comportement Material You standard).
- **Builds release optimisés** (R8 + shrink des ressources, signés avec la clé
debug → installables par-dessus sans perte de données) : l'APK passe de
**20 Mo à 2,4 Mo** et gagne en fluidité. Depuis cette release, chaque version
est publiée en deux fichiers :
- `HormoneTrack-vX.Y.Z-release.apk` ← **recommandé** (optimisé R8) ;
- `HormoneTrack-vX.Y.Z-debug.apk` (comme avant, plus lourd, pour diagnostic).
## [1.2.4] — 2026-09-05 (versionCode 7)
### Corrigé
- **Traitement inactif = drapeau administratif, pas filtre de données** (bug
remonté) : passer un traitement à inactif faisait disparaître sa simulation du
graphique **et** sa contribution à la calibration, tout en permettant encore
d'y loger des doses (incohérence). Désormais :
- Home et Graphiques simulent avec **TOUS les traitements** (actifs + inactifs)
→ l'historique EV d'un traitement passé à inactif reste simulé et calibré ;
- l'inactivation retire uniquement : les chips « Log rapide », le traitement
dans le dropdown des **nouvelles** doses (l'édition d'une dose existante
garde son traitement même inactif), et les **rappels** (alarme annulée au
save, au boot et dans le calcul de la prochaine dose) ;
- la suppression d'une dose reste possible depuis l'historique.
### Ajouté
- **Régression n°3** épinglée sur le 3ᵉ export réel (HORS dépôt, gitignoré) :
le scénario complet de transition — 1 traitement **EV inactif** (29 doses,
2–8 mg, janvier→juillet) + 1 traitement **EEn actif** (9 doses), 22 labs
couvrant les deux périodes. Vérifie : simulation de l'inactif, calibration
par période sur les DEUX esters (EV **et** EEN), k T par ester, continuité
de la courbe pendant la transition, niveau actuel. 6 nouveaux tests (62 au
total, tous verts).
## [1.2.3] — 2026-09-05 (versionCode 6)
### Ajouté
- **Pics et creux estimés sur le graphique** : chip « Pics / creux » (désactivé par
défaut) qui affiche des triangles ▲▼ aux extrema locaux de CHAQUE courbe
(E2 et T, pour les deux modèles), colorés comme leur série. Détection dans le
moteur (`detectExtrema`) : extrema locaux stricts, plateaux fusionnés,
alternance pic/creux imposée, oscillations d'amplitude < seuil ignorées
(2 pg/mL pour l'E2, 0,02 ng/mL pour la T — sémantique zigzag : une oscillation
sous le seuil produit un seul pivot).
- **Calibration T PAR PÉRIODE D'ESTER** : la suppression de la testostérone n'est
pas la même selon l'ester (valerate = pics hauts et courts, enanthate = plateau
doux) → le k du modèle T est désormais calibré **par période** comme le facteur
d'échelle E2 (`computeTKPerEster`) : labs T valerate → k des doses valerate,
labs T enanthate → k des doses enanthate. La courbe T utilise à chaque instant
le k de l'ester **actif** (dernière dose ≤ t, `activeEsterAt`), fallback = k stocké.
Le k T est calibré contre l'E2 **déjà calibrée** (scalePerEster appliqué).
- **Données de test hors dépôt (vie privée)** : les tests de régression épinglés
sur les exports réels chargent désormais leurs données depuis `local-test-data/`
(**gitignoré**) et s'ignorent proprement si le fichier est absent — **aucune
donnée de santé personnelle dans le dépôt ni dans son historique** (réécriture
d'historique effectuée avant le premier push).
- 14 nouveaux tests (56 au total) : détection d'extrema (6) + attribution du k T
par période et courbe T utilisant le k de l'ester actif (2), regroupement des
prises de sang (4), régression n°2 en version data-driven (6).
### Corrigé
- La calibration automatique de la T utilisait l'E2 **non calibrée** → les k
étaient faussés ; elle utilise maintenant l'E2 calibrée par période d'ester.
## [1.2.2] — 2026-09-05 (versionCode 5)
### Ajouté
- **Prise de sang E2 + T en UNE entrée** : le dialog d'ajout propose les deux
marqueurs (chacun **optionnel** — on peut ne saisir que E2 ou que T), avec
date/heure et notes partagées ; les entrées sont insérées au même timestamp et
s'affichent **côte à côte** dans l'écran Analyses (« E2 306 pg/mL · T 44 ng/dL »).
- **Tap sur une paire → sélecteur** (« Quelle entrée veux-tu modifier ? » E2 / T)
puis édition pré-remplie de l'entrée choisie.
- **Suppression par prise de sang** : la corbeille supprime la prise entière
(confirmation listant les valeurs).
- **Toggle T du graphique** : masque désormais aussi les **labs T** (avant : seule
la courbe T disparaissait, les points restaient).
- 4 tests de regroupement (48 au total).
## [1.2.1] — 2026-09-05 (versionCode 4)
### Corrigé
- **Calibration par PÉRIODE D'ESTER** (le fond du problème « courbes à 250–375 ») :
la calibration automatique calculait UN facteur par traitement, mélangeant les labs
de toutes les périodes — un lab valerate comparé à une prédiction enanthate donne un
ratio aberrant qui gonfle la courbe. Désormais chaque lab est **attribué à la période
d'injection dans laquelle il tombe** (dernière dose E2 ≤ lab → son ester) et le
facteur est la médiane des ratios de CETTE période : labs valerate → doses
valerate, labs enanthate → doses enanthate (`computeEsterScaleFactors` +
paramètre `scalePerEster` dans `e2At`/`computeCurve`).
Vérifié sur les données réelles : l'état d'équilibre EEn (t½ ≈ 6,7 j, doses tous
les 6–7 j → accumulation ×2) donne ~270 pg/mL calibré — cohérent avec les labs
300/250 ; les « 375 » rapportés = le même calcul **non calibré** (268/0,72 ≈ 367).
- **Lab T saisi en "pg/mL"** (38 "pg/mL" — faute de frappe) : renvoyé brut, il
écrasait l'axe T (~40) et **rendait la courbe T invisible** (écrasée en bas).
Conversion défensive ajoutée (`pg` → ÷1000, `µg/L` → ÷1000).
### Ajouté
- **Édition des résultats de labo** (comme les doses) : appuyer sur une ligne de
l'écran Analyses ouvre le dialog pré-rempli (marqueur, valeur, unité, date/heure,
notes). Nouveau formateur `formatLabValue` (préserve les décimales : 0,44 reste
0,44, contrairement à formatDose).
- **Tap sur le graphique de l'accueil → écran Graphiques** + mini-légende (E2 bleu /
T rose) + indication.
- **Calibration automatique fusionnée** : UNE seule option dans Paramètres qui couvre
**E2 (par période d'ester) ET T** — plus de réglage T isolé ; le bouton manuel
« Calibrer avec les analyses » (T) reste comme action ponctuelle.
- **8 nouveaux tests** (44 au total) dont `RegressionUserCase2Test` épinglé sur le
2ᵉ export réel (9 doses EEn/TFS, 8 labs, fréquence 6 j) — inclut la vérification
de l'état d'équilibre et du lab T en unité aberrante.
### Modifié
- `AutoCalibrated` : `treatments` inchangés (plus de copies avec SF modifié), nouveau
champ `esterScales: Map<String, Double>` + `calibratedEsters`.
## [1.2.0] — 2026-09-05 (versionCode 3)
### Ajouté
- **Panoramique du graphique** : fais glisser le graphique vers la droite pour
remonter dans le passé (fenêtre glissante sur tout l'historique) ; bouton
« Revenir à maintenant ».
- **Les deux modèles PK superposables** : Estrannaise (bleu) et Transfem Science
(turquoise) tracées simultanément sur le même graphique, avec toggles
indépendants (override de modèle dans le moteur, indépendant du modèle stocké
par traitement).
- **Simulation prévisionnelle** : nouvelle section « Fréquence d'injection » dans
l'éditeur de traitement (intervalle en jours) → chip « Prévision » du graphique
qui projette les doses à venir (2× l'intervalle, borné 7–30 j) à partir de la
dernière injection réelle ; les doses projetées ne sont jamais sauvegardées et
conservent l'ester override de la dernière injection.
- **Calibration automatique (optionnelle)** : dans les Paramètres, option
« Calibration automatique » — ajuste à la volée (affichage seul) les facteurs
d'échelle et le modèle T depuis les labs ; désactivée par défaut, les valeurs
stockées ne sont jamais modifiées.
- **Intervalle entre dosages** : l'écran Doses affiche Δ (jours) entre chaque dose
et la précédente du même traitement (colonne « Interval (d) » du `.ods`).
- 6 nouveaux tests (`V120FeaturesTest`) → **36 tests**.
### Modifié
- **Montée des toolchains** : Gradle 9.7.1, **AGP 9.4.0** (Kotlin intégré — plus de
plugin `org.jetbrains.kotlin.android`, `kotlinOptions` supprimé), Kotlin 2.3.21,
KSP 2.3.11, **Compose BOM 2026.08.00** (Compose 1.12), Room 2.8.4,
Navigation 2.10.0, Activity 1.13.0, AppCompat 1.8.0, DataStore 1.2.1, Gson 2.14.0,
compileSdk/targetSdk **37**.
- Note Material You / Expressive : la BOM récente apporte composants et comportements
Material You ; `MaterialExpressiveTheme` reste **internal** dans la ligne material3
pinnée (vérifié par compilation) → `MaterialTheme` standard conservé, à basculer
dès que l'API devient publique (commenté dans `ui/theme/Theme.kt`).
### Corrigé
- **Horloge du TimePicker décentrée** (partait vers la gauche dans les dialogs de
saisie) : centrage explicite (Box + Center) dans `DateTimeField` et l'éditeur de
traitement.
- Migration Room **v1 → v2** propre (colonne `forecastIntervalDays`) avec
suppression du `fallbackToDestructiveMigration()` — les données réelles sont
protégées : une migration manquante plante bruyamment au lieu d'effacer.
## [1.1.0] — 2026-09-05 (versionCode 2)
### Corrigé
- **Courbes EEn plates à zéro** (le bug rapporté : « les graphiques ne se génèrent pas »).
Cause : casse des clés de profils — l'asset JSON (verbatim du `.ods`) contient
`"EEn_ese"`/`"EEn_tfs"` (n minuscule) alors que la constante `Esters.EEN = "EEN"` ;
le lookup exact échouait silencieusement → `sample() = 0` pour **tous** les traitements
EEn (EV et EU marchaient, ce qui masquait le bug). Fix : lookup **insensible à la casse**
dans `PKProfileStore` (`lookup()`), test de régression épinglé sur les données réelles
de l'utilisatrice (`RegressionUserCaseTest`, export v1.0.0 fourni).
- **Unités de testostérone** : les labs T pouvaient être saisis en ng/dL (cas réel :
33 et 44 ng/dL). L'axe T du chart mélangeait alors des ng/dL avec des estimations en
ng/mL (erreur ×100, courbe T invisible) et la calibration T aurait été faussée d'un
facteur 100. Fix : `PharmacokineticEngine.convertTToNgMl()` (ng/dL → ×0,01 ;
ng/L → ×0,001 ; nmol/L → ×0,2884 ; ng/mL inchangé), appliqué à la calibration **et
au rendu du chart**.
### Ajouté
- **Édition des doses** (le bug rapporté : « on ne peut pas modifier les entrées
d'injection ») : appuyer sur une ligne de l'écran Doses ouvre le dialog en mode
édition (traitement, dose, date/heure, notes) → enregistre sans recréer.
- **Choix de l'ester par injection** dans le dialog de dose (comme dans le `.ods`) :
« Défaut (EEn) », EV, EU, EEn — override stocké par dose, utilisé par le moteur PK.
- 6 tests de régression sur les données réelles de l'utilisatrice (30 tests au total).
### Technique
- `versionCode 2`, `versionName 1.1.0` ; APK debug régénérée.
## [1.0.0] — 2026-09-05 (versionCode 1)
Première version fonctionnelle :
- Courbes estimées E2/T heure par heure (24 h / 7 j / 30 j), modèles **Estrannaise** et
**Transfem Science** (tables horaires extraites de `Estrogen.ods` : EV/EU/EEn)
- Modèle Bateman paramétrable (gel/patch/oral)
- Log des doses, analyses de sang, calibration (facteur d'échelle médian lab÷prédiction)
- Estimation T empirique calibrable
- Rappels quotidiens (alarmes exactes) avec actions « Pris » / « Reporter 1 h »,
notifications remontant sur Huawei Watch GT 3 (Gadgetbridge ou Huawei Health)
- Sauvegarde/Restauration JSON (SAF), FR/EN (langue par app), 100 % local
- 24 tests unitaires (moteur PK, profils ODS, round-trip backup)