760 lines
44 KiB
Markdown
760 lines
44 KiB
Markdown
# 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.9`, …).
|
||
|
||
## [1.4.9] — 2026-09-07 (versionCode 29)
|
||
|
||
### Corrigé
|
||
- **La calibration tombait à ×2,21 (auto ET manuelle) sur un traitement
|
||
basculé sous un autre modèle** (#61, remontée : « le bouton Calibrer avec
|
||
les analyses du traitement EEn WHSAH me met toujours à ×2,21 ») :
|
||
diagnostic par reproduction sur données réelles — l'export contient une
|
||
dose EEn de test logguée en **janvier** ; les labs de janvier-mars
|
||
tombaient 15–76 j après cette dose, quand la prédiction WHSAH est
|
||
RÉSIDUELLE (184 → 1 pg/mL) → ratios aberrants (3,3 / 15,9 / 89,6 / 383)
|
||
→ la MÉDIANE tombait à ×2,21, **dans les deux pipelines** (manuel ET
|
||
auto — même défaut de frontière). FIX : **garde de significativité**
|
||
(`labIsSignificant`) — un lab ne calibre que si la prédiction reste
|
||
≥ 15 % du max déjà observé (l'injection « gouverne » encore le taux) ;
|
||
appliquée aux 3 pipelines (manuel, échelles par ester, k T), labs
|
||
traités par timestamp croissant. Résultat : le facteur WHSAH retombe à
|
||
**0,55** (physiologique).
|
||
- **Hint UX** (demande) : dans l'éditeur de traitement, quand l'auto-
|
||
calibration est ON, la carte Calibration affiche que le facteur manuel
|
||
est ignoré à l'affichage (l'auto recouvre les courbes).
|
||
|
||
### Ajouté
|
||
- **4 nouveaux tests** (168 au total / 146 sans les données locales) :
|
||
reproduction épinglée du ×2,21 (facteur manuel WHSAH 0,55, plus jamais
|
||
2,21), garde de significativité (séquence réelle + cas aux frontières),
|
||
cohérence auto↔manuel.
|
||
|
||
## [1.4.8] — 2026-09-07 (versionCode 28)
|
||
|
||
## [1.4.8] — 2026-09-07 (versionCode 28)
|
||
|
||
### Corrigé
|
||
- **La calibration automatique était fausse pour les modèles autres que le
|
||
modèle stocké du traitement** (#60, remontée : « la calibration auto sur
|
||
WHSAH met ×2,21 alors que l'idéal serait < 1 ») : un SEUL jeu de facteurs
|
||
était calculé depuis la prédiction du modèle STOCKÉ puis partagé par
|
||
toutes les courbes — en basculant un traitement (ex. EEn TFS → WHSAH),
|
||
les facteurs devenaient absurdes (les prédictions WHSAH diffèrent de TFS :
|
||
creux plus profonds, montée plus rapide). FIX (demande : « la calibration
|
||
doit être active sur tous les modèles mais s'adapter à chaque profil ») :
|
||
**la calibration est désormais PAR MODÈLE** — le graphique calcule un jeu
|
||
de facteurs (et de k T) PAR modèle affiché (ESE/TFS/WHS), chacun calibré
|
||
avec la prédiction DE SON modèle → chaque courbe calibrée passe par tes
|
||
labs, quel que soit le modèle superposé. Home conserve la calibration du
|
||
modèle stocké (inchangé). Épinglé par test : chaque courbe calibrée passe
|
||
par le lab (±2 %) et les échelles TFS/WHS diffèrent.
|
||
|
||
### Ajouté
|
||
- **5 nouveaux tests** (164 au total / 142 sans les données locales) :
|
||
`CalibrationPerModelTest` — les 3 modèles calibrés atterrissent sur le
|
||
lab (ESE inclus), échelles distinctes (ESE↔TFS ~2 %, WHS ~11 %),
|
||
délégation `autoCalibrated(modelOverride)` → `computeEsterScaleFactors`
|
||
(implémentation unique), **k T par modèle** (k ∝ 1/E2 calibrée — et les
|
||
k post-calibration CONVERGENT entre modèles : même E2 cible, comportement
|
||
voulu), labs antérieurs à la 1ʳᵉ dose → échelles vides (par modèle).
|
||
|
||
## [1.4.7] — 2026-09-07 (versionCode 27)
|
||
|
||
### Modifié
|
||
- **Toggles de modèles pré-cochés selon TES traitements** (#58, demande :
|
||
« les seules courbes activées par défaut sont celles qui se rapportent au
|
||
modèle PK des traitements en cours ou passés ») : au chargement du
|
||
graphique, les chips Estrannaise/Transfem/WHSAH sont alignés sur les
|
||
modèles RÉELLEMENT utilisés par des traitements à PROFIL PK
|
||
(`usesProfileModel` — le traitement inactif compte, l'oral/anti-androgène
|
||
non) ; les autres restent activables au tap. Cas de l'utilisatrice :
|
||
EV historique TFS + EEn actuel (qu'elle passe sous WHSAH) → TFS + WHSAH
|
||
ON, Estrannaise OFF. Garde-fou testé : aucun modèle profil utilisé → TFS
|
||
ON (les traitements Bateman sont tracés identiquement dans chaque série).
|
||
Une seule initialisation : les toggles ensuite modifiés ne sont pas
|
||
re-rencontrés. (`defaultModelToggles`, helper pur testé.)
|
||
|
||
### Corrigé
|
||
- **La carte de traitement affichait « EEN Estrannaise » pour un traitement
|
||
sous WHSAH** (#59, remontée) : le ternaire à 2 branches du chip
|
||
`TreatmentsScreen` tombait WHSAH dans le `else` → label « Estrannaise ».
|
||
FIX : helper centralisé **`modelLabelRes`** (unique source de vérité du
|
||
label : ESE/TFS/WHS) utilisé par la carte — le dropdown de l'éditeur et
|
||
le chip du graphique étaient déjà corrects. Règle : TOUT nouvel affichage
|
||
de modèle passe par le helper, jamais un ternaire.
|
||
|
||
### Ajouté
|
||
- **Épinglage : la calibration s'applique à TOUTES les courbes** (2ᵉ partie
|
||
de la demande — vérification) : le facteur par ESTER (`scalePerEster`)
|
||
est appliqué par DOSE (`doseEster`), indépendamment du modelOverride →
|
||
les 3 courbes (ESE/TFS/WHS) d'un même traitement sont scalées
|
||
IDENTIQUEMENT (2× le facteur = 2× la courbe, test `calibration applies
|
||
to ALL model curves equally`). C'était déjà le comportement ; il est
|
||
désormais garanti par test. Subtilité documentée (§7.6) : le facteur est
|
||
calculé depuis la prédiction du modèle STOCKÉ du traitement — les
|
||
courbes des autres modèles partagent ce facteur (un facteur par
|
||
ester/période, pas par modèle).
|
||
- **3 nouveaux tests** (158 au total / 136 sans les données locales) :
|
||
`defaultModelToggles` (2) + calibration universelle (1).
|
||
|
||
## [1.4.6] — 2026-09-07 (versionCode 26)
|
||
|
||
### Ajouté
|
||
- **3ᵉ modèle PK superposable : WHSAH** (demande : « ajouter le nouveau
|
||
modèle à côté de tous les autres comme un 3ᵉ choix possible, au même
|
||
niveau ») : le fit « license-free » du **WHSAH Collective** publié dans
|
||
l'app open-source **Mona** (libre d'usage par leurs auteurs — crédit :
|
||
alix / WHSAH Collective via Mona). Même famille mathématique (tri-
|
||
exponentielle en forme close) mais paramètres refittés avec une
|
||
**biodisponibilité explicite F < 1** : montée plus rapide à J+1 (EEn
|
||
~70 pg/mL vs ~22 chez TFS pour 5 mg) et t½ terminale plus longue
|
||
(EEn 7,3 j vs 4,5 j). **PEP non couvert** par ce fit (comme chez Mona).
|
||
- **6 presets WHSAH** (EV/EU/EEn/EB/EC/ECS) + dropdown « PK model » à 3
|
||
choix dans l'éditeur ; l'override d'ester par dose suit la couverture du
|
||
modèle (helper `Esters.choicesForModel`, testé : TFS=7 / WHS=6 / ESE=3).
|
||
- **Écran Graphiques : 3 toggles de modèles indépendants** (Estrannaise /
|
||
Transfem Science / WHSAH superposables simultanément — légende en 3
|
||
couleurs : bleu / turquoise / vert) + **l'écran défile verticalement**
|
||
(la rangée des toggles était coupée sous le pli, #57).
|
||
- **7 nouveaux tests** (155 au total / 133 sans les données locales) :
|
||
`WhsahModelsTest` (fidélité au comportement de Mona : EV 367,5 @ 1,69 j,
|
||
EEn 187,9 @ 4,99 j, t½ 7,34 j), `WhsahEngineTest` (dispatch, coupure
|
||
10×t½, PEP → 0, 3 courbes distinctes), `choicesForModel`.
|
||
|
||
### Corrigé
|
||
- **Écran Graphiques : rangée des toggles de modèles coupée sous le pli**
|
||
(#57, découvert en validant sur émulateur) : le Column ne défilait pas —
|
||
le chip WHSAH était littéralement hors écran sur un téléphone standard.
|
||
FIX : écran à défilement vertical.
|
||
|
||
## [1.4.5] — 2026-09-07 (versionCode 25)
|
||
|
||
### Corrigé
|
||
- **Boucle de recomposition sur le graphique** (#56, découvert en validant
|
||
#54) : `nowMs` était relu frais à CHAQUE recomposition → `endMs` dérivait
|
||
→ les keys du producteur de courbes changeaient à chaque frame →
|
||
re-calcul perpétuel → main thread saturé (« Skipped 52 frames ») → les
|
||
taps sur les chips étaient perdus. FIX : `nowMs` mémoïsé sur le tick
|
||
minute ; le calcul des créneaux prévisionnels mémoïsé aussi.
|
||
- **La projection paraissait décalée d'un jour** (#55, remontée : « la
|
||
prévision simule mon injection le 13 au lieu du 12 ») : diagnostic sur
|
||
données réelles — la GÉNÉRATION est exacte (créneau = dernière dose
|
||
loguée + intervalle exact ; samedi 12 à 18:54) ; l'illusion venait de
|
||
(a) l'ABSENCE de marqueur des doses simulées, (b) les labels X alignés
|
||
sur minuit **UTC** (= 02:00 du matin FR), (c) la courbe qui continue de
|
||
descendre après le créneau (physiologie EEn — contribution à J+0,5
|
||
~4 pg/mL). ⚠️ Subtilité documentée : la dose d'hier avait été LOGUÉE le
|
||
lendemain de l'injection réelle → le rythme 6 j décale d'un jour par
|
||
cycle ; l'app suit les LOGS (édition de la dose pour recadrer).
|
||
|
||
### Ajouté
|
||
- **Marqueurs de doses sur le graphique** : les doses PRÉVISIONNELLES
|
||
(ligne verticale pointillée + triangle + heure, dans le fuseau de
|
||
lecture) et les doses RÉELLES (petits triangles discrets au bas) — la
|
||
projection devient non ambiguë.
|
||
- **Labels X alignés sur minuit LOCAL** (`xLabelTicks`, testé) au lieu de
|
||
multiples UTC.
|
||
- **Fuseau horaire du graphique configurable** : Paramètres → « Fuseau
|
||
horaire du graphique » (vide = fuseau du téléphone) ; s'applique à
|
||
l'alignement des jours et à l'heure des marqueurs.
|
||
- **6 nouveaux tests** (145 au total / 123 sans les données locales) :
|
||
ticks minuit local, fuseau choisi (America/New_York), cas dégénérés,
|
||
créneau prévisionnel = dernière dose + intervalle exact (régression #4).
|
||
|
||
## [1.4.4] — 2026-09-07 (versionCode 24)
|
||
|
||
### Corrigé
|
||
- **La prévision était inaccessible depuis la v1.4.1** (#54, remontée : « en
|
||
1.4.3 je ne peux même pas drag le graphique vers la gauche pour voir les
|
||
prévisions ; le toggle activé ne génère pas les prévisions ») : **deux
|
||
causes emboîtées** —
|
||
1. **Captures FIGÉES dans le gestionnaire de gestes** : la closure de
|
||
`pointerInput(Unit)` est créée une seule fois et les `val` calculées
|
||
(`futurePanHorizon`…) y étaient figées à leur valeur de la PREMIÈRE
|
||
composition (0, chip désactivé) → le clamp interdisait DÉFINITIVEMENT
|
||
le pan vers le futur. FIX : les vals de fenêtre sont exposées au geste
|
||
via `rememberUpdatedState` (alias `gesture*`, lectures à jour).
|
||
**Garde de non-régression** : `ChartScreenSourceGuardTest` fait échouer
|
||
toute réintroduction de lecture figée dans la closure (le lint Compose
|
||
ne détecte pas ce pattern).
|
||
2. **Toggle sans effet visible** : à l'activation, `endMs = now` faisait
|
||
que `generateForecastDoses(..., toMs = now, ...)` ne générait AUCUNE
|
||
dose. FIX : à l'activation, la fenêtre s'ÉTEND à droite (sans déplacer
|
||
son début — pas de saut) jusqu'au premier créneau projeté (+12 h de
|
||
contexte), sinon 1/3 de la plage, borné par l'horizon
|
||
(`forecastExtensionHours`, testé). Désactiver → retour à la fenêtre
|
||
standard. Le drag vers la gauche continue au-delà (jusqu'à 1 an).
|
||
|
||
### Ajouté
|
||
- **3 nouveaux tests** (140 au total / 118 sans les données locales) :
|
||
extension de fenêtre (bornes, garde-fous) et garde de source anti-captures
|
||
figées. Validation émulateur : drag gauche → panHours < 0 (« Revenir à
|
||
maintenant » apparaît) et la courbe est dessinée au-delà de la ligne
|
||
« maintenant » (analyse du screencap).
|
||
|
||
## [1.4.3] — 2026-09-07 (versionCode 23)
|
||
|
||
### Corrigé
|
||
- **Le delta « vs il y a 6 h » de la carte d'accueil comparait en réalité
|
||
il y a ~24 h** (#53, remontée : « 238 pg/mL avec −11 vs 6 h malgré mon
|
||
injection d'hier ») : `curve.firstOrNull { écart ≥ 6 h }` sur une fenêtre
|
||
de 24 h retournait le PREMIER point de la liste (il y a 24 h), pas un
|
||
point à 6 h. FIX : nouveau helper pur **`pointHoursBefore`** (le point
|
||
dont l'écart au dernier est le plus petit au-dessus de l'offset —
|
||
testé : grille 1 h, grille 30 min, points irréguliers, courbe courte).
|
||
Le delta affiché est désormais VRAIMENT un delta 6 h.
|
||
|
||
### Ajouté
|
||
- **Régression n°4** épinglée sur le 4ᵉ export réel (HORS dépôt, gitignoré)
|
||
: le scénario « injection EEn hier, l'estimation ne remonte pas » —
|
||
export v1.4.2 (3 traitements, 52 doses, 26 labs, settings v2 avec seuils
|
||
et auto-calibration). Le test épine, avec le VRAI moteur : la
|
||
**contribution d'une dose EEn à J+1 est marginale** (< 12 % du total —
|
||
Tmax 6,5 j), le **plateau d'équilibre** (swing 48 h ≤ 25 % du niveau),
|
||
l'absence de fausse alerte à l'observation, et le parsing des settings
|
||
v2. Réponse à la question posée : **comportement pharmacocinétique
|
||
normal** de l'énanthate (courbe plate en équilibre), pas un bug — le
|
||
seul bug était l'étiquette du delta (ci-dessus).
|
||
- **8 nouveaux tests** (137 au total / 115 sans les données locales) :
|
||
`RegressionUserCase4Test` (4) + `pointHoursBefore` (4, dans ChartZoomTest).
|
||
|
||
## [1.4.2] — 2026-09-07 (versionCode 22)
|
||
|
||
### Ajouté
|
||
- **Seuils d'alerte configurables** (E2 et T) : dans Paramètres, une limite
|
||
HAUTE et une limite BASSE par marqueur (E2 en pg/mL, T en ng/mL), chacune
|
||
optionnelle (champ vide = pas d'alerte), validées (haut > bas requis) et
|
||
**évaluées contre le taux ESTIMÉ actuel** (le même que la carte « niveau
|
||
actuel », calibration incluse — pas une mesure de labo). En cas de
|
||
dépassement : carte d'avertissement rouge sur l'accueil (« ▲ E2 estimé
|
||
≈ 259 pg/mL — au-dessus de ta limite haute (200) ») + disclaimer «
|
||
estimation, pas une mesure ». Comparaison STRICTE (valeur == limite → rien).
|
||
- **Notification d'alerte** : la vérification tourne **toutes les 15 min
|
||
même app fermée** (worker WorkManager — la dépendance déclarée depuis v1.0
|
||
est enfin utilisée) + **check immédiat au save des seuils**. Anti-spam :
|
||
l'état des alertes déjà notifiées est mémorisé — pas de re-notification
|
||
du même franchissement ; le retour à la normale ré-arme la prochaine
|
||
alerte ; pas de notification de « retour à la normale ». Canal de
|
||
notification dédié (réglable séparément des rappels, remonte sur la
|
||
Watch GT 3). Journalisation AppLog complète (debug à distance).
|
||
- **9 nouveaux tests** (129 au total / 111 sans les données locales) :
|
||
`AlertsTest` (12 : logique pure des seuils, codec d'état anti-spam) et
|
||
`AlertsEngineTest` (4 : la notification évalue EXACTEMENT le niveau de la
|
||
carte accueil ; déclenchement sur niveau réel).
|
||
- **Backup JSON avec les paramètres utilisateur** : l'export embarque
|
||
désormais langue, auto-calibration et seuils d'alerte (`"settings"`,
|
||
format v2) ; l'import les restaure (la langue est appliquée au retour).
|
||
**Rétrocompatible** : les anciens exports (sans settings) restent
|
||
importables ; 2 tests round-trip + compat v1.
|
||
|
||
## [1.4.1] — 2026-09-06 (versionCode 21)
|
||
|
||
### Ajouté
|
||
- **Prévision étendue ET sans saut temporel** (remontées utilisateur) :
|
||
- activer le chip **Prévision** ne déplace PLUS le graphique « tout à
|
||
droite » dans le futur — la fenêtre reste exactement où elle était ;
|
||
la courbe projetée s'étend au-delà et on la parcourt en **tirant vers
|
||
la gauche** (« scroller à l'infini » = jusqu'à 1 an de projection) ;
|
||
« Revenir à maintenant » ramène depuis le passé **ou** le futur ;
|
||
- l'horizon passe de « 2 × Posologie, borné 7–30 j » à **12 × Posologie,
|
||
borné 30 j – 1 an** (`forecastHorizonHours`, épinglé par `ChartZoomTest`).
|
||
- désactiver la prévision pendant qu'on est dans le futur → retour
|
||
automatique à « maintenant » (sinon fenêtre sans données).
|
||
- **Delta en JOURS sur la carte « Prochaine dose »** (remontée : les heures
|
||
deviennent illisibles au-delà de 24 h) : à partir d'un jour, la carte
|
||
affiche « 5 j 2 h · sam. 6 18:00 (traitement) » avec le jour de la
|
||
semaine ; en dessous, format heures/minutes inchangé
|
||
(`HrtDuration.daysAndHours` + string `next_dose_days` FR/EN).
|
||
- **5 nouveaux tests** (112 au total / 94 sans les données locales) :
|
||
bornes d'horizon et clamp de panoramique (`ChartZoomTest` ×4 — le clamp
|
||
unique drag/zoom est centralisé dans `clampPanHours`),
|
||
conversion heures→jours (`HrtDurationTest` ×1).
|
||
|
||
## [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)
|