- PKPresets.kt (25 entrées) : EC huile (5 mg IM), EB (1 mg IM), EUCS suspension cristalline (10 mg SC) — champs Bateman informatifs = t½ terminales des fits (pas utilisés au calcul) - TreatmentEditorScreen.kt : liste des esters = Esters.choicesForModel (même helper que l'override du DoseDialog) ; au changement de modèle, l'ester rebascule sur EV si non couvert (garde anti 0 pg/mL) - PKPresetsTest (nouveau, 6 tests) : couverture des 6 esters ESE, unicité, TFS 7 / WHSAH 6 inchangés, garde anti hors couverture, plausibilité des t½ informatives — 243 verts + lint - versionCode 41 / versionName 1.9.4 + CHANGELOG/§2/§7.12/§8/README/GUIDE - Doc : doublon de la section §7.12 supprimé (collé deux fois)
1128 lines
66 KiB
Markdown
1128 lines
66 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.10`, …).
|
||
|
||
## [1.9.4] — 2026-09-19 (versionCode 41)
|
||
|
||
### 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 (`PKPresets.kt`, 25 entrées)** : 3 presets ESE ajoutés au
|
||
même niveau que les autres — 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 — le dispatch passe par la forme close).
|
||
- **Éditeur (`TreatmentEditorScreen.kt`)** : la liste des esters suit la
|
||
couverture RÉELLE du modèle choisi (`Esters.choicesForModel` — même
|
||
liste que l'override du DoseDialog) : ESE liste ses 6 esters
|
||
injectables directement à la création. 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 ».
|
||
- **Tests** : `PKPresetsTest` (nouveau, 6 tests — miroir
|
||
`tests/presets.test.js` web) : couverture des 6 esters ESE, unicité,
|
||
TFS (7) / WHSAH (6) inchangés, garde « aucun preset injectable hors
|
||
couverture de son modèle », plausibilité des t½ informatives.
|
||
243 tests verts + lint.
|
||
- **Doc** : doublon de la section §7.12 supprimé (collé deux fois).
|
||
|
||
## [1.9.2] — 2026-09-19 (versionCode 40)
|
||
|
||
### Corrigé — le nuage d'incertitude exigeait un traitement STOCKÉ en ESE
|
||
|
||
- **Bug remonté** : activer Estrannaise dans le graphique puis le nuage
|
||
n'affichait rien tant qu'aucun traitement n'était **stocké** avec le
|
||
modèle ESE — « la seule méthode trouvée était de mettre un traitement en
|
||
cours sur le modèle ESE, mais ça ne devrait pas être un prérequis ».
|
||
- **Cause** : 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"` (peu importe le modèle stocké) — courbe
|
||
et nuage n'utilisaient pas la même définition de « quelles doses sont
|
||
tracées 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 (activable seulement si ESE est affiché). L'oral Bateman reste hors
|
||
nuage (pas d'ester échantillonnable). Les traitements inactifs sont
|
||
inclus (§6.bis : la courbe ESE les trace aussi).
|
||
- 2 tests réécrits (237 au total / 207 sans les données locales) :
|
||
« doses d'un traitement TFS couvertes quand ESE est affiché » (le fix,
|
||
épinglé des deux plateformes) ; oral seul → vide (conservé).
|
||
- Validé émulateur §16.ter sur le profil réel (EEn stocké TFS, ESE
|
||
activé) : nuage visible autour de la courbe ESE, 0 crash.
|
||
|
||
## [1.9.1] — 2026-09-19 (versionCode 39)
|
||
|
||
### Corrigé — le nuage d'incertitude entoure maintenant la courbe Estrannaise calibrée
|
||
|
||
- **Bug remonté** : le nuage ne s'activait « que autour du tracé, pas autour
|
||
du modèle Estrannaise » — la courbe ESE affichée est **calibrée**
|
||
(scalePerEster des labs) mais le nuage était tracé **brut** : les deux
|
||
flottaient à des échelles différentes.
|
||
- **Fix** : le nuage reçoit le MÊME `scalePerEster` que la courbe ESE
|
||
(autoByModel ESE) — il entoure désormais la courbe calibrée (Android +
|
||
web en miroir).
|
||
- 1 nouveau test : nuage calibré ×2 = nuage brut ×2 (le nuage suit la
|
||
calibration).
|
||
|
||
### Note — pourquoi le taux affiché a bougé depuis v1.8.2 (+4 pg/mL)
|
||
|
||
- v1.8.2 a corrigé l'**arrondi de la médiane de calibration** (troncature
|
||
flottante → arrondi au plus proche) : une échelle dont la médiane brute
|
||
tombait à 0,8999… était tronquée en 0,89 au lieu de 0,90. Avec le modèle
|
||
ESE analytique (v1.9.0) déplaçant ce dernier bit, le piège est devenu
|
||
visible : les échelles auto-calibrées ont bougé d'un cran d'affichage →
|
||
le niveau estimé a monté d'environ 1 % (+4 pg/mL sur ~320).
|
||
- **Le niveau actuel est le CORRIGÉ** (l'arrondi est plus juste que la
|
||
troncature). Le web applique la même règle depuis v1.9.1 (harmonisation
|
||
des niveaux entre plateformes).
|
||
|
||
## [1.9.0] — 2026-09-18 (versionCode 38)
|
||
|
||
### Modèle Estrannaise ANALYTIQUE (abandon de l'ODS) + nuage d'incertitude MCMC
|
||
|
||
- **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 %.
|
||
- **Décision** (utilisatrice) : « tous les modèles ont maintenant leurs
|
||
sources originelles — on abandonne complètement les liens avec le fichier
|
||
ODS ». Concrètement : plus aucune lecture des tables au runtime
|
||
(`concentrationOfDose` dispatche ESE → forme close ; `cutoffHours` ESE →
|
||
10 × t½ terminale analytique ; l'asset `pk_profiles.json` quitte l'APK,
|
||
−550 Ko, et ne sert plus qu'aux tests de fidélité).
|
||
- **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. TFS reste à 7 (liste explicite : EUCS n'a pas de
|
||
V3C TFS), WHSAH à 6.
|
||
- **NUAGE D'INCERTITUDE (exclusif ESE)** : Estrannaise publie le posterior
|
||
MCMC de ses paramètres (313 échantillons/ester, asset ≈ 48 Ko) — chip
|
||
`Nuage` (off par défaut, activable à volonté, actif SEULEMENT si ESE est
|
||
affiché, coupure automatique si ESE est éteint) → nuage diffus de 32
|
||
courbes du posterior montrant la plage d'imprécision du modèle, comme
|
||
sur le site estrannaise. Dessiné en alpha faible sous les courbes, hors
|
||
échelles/labels/extrema.
|
||
- 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.
|
||
- **11 nouveaux tests** (234 au total / 204 sans les données locales) :
|
||
fidélité (RMS, pics ±0,2 %), cas dégénérés (taux égaux → limites sans
|
||
NaN), MCMC 313 × 6, nuage (32 courbes à dispersion réelle, fenêtre,
|
||
exclusivité ESE, gardes), choicesForModel ESE=6, t½ analytique.
|
||
- **Porté web** (web v1.9.0, versions sync) : `estrannaise-models.js` +
|
||
`estrannaise-cloud.js` miroirs, nuage canvas, fetch MCMC au démarrage
|
||
(l'ODS web quitte aussi le runtime), 11 tests miroirs + E2E nuage.
|
||
- Validé émulateur §16.ter (APK release, seed réel v1.7.0) : courbe ESE
|
||
analytique affichée, chips Extend/Cloud présents, recommandation
|
||
de prise de sang fonctionnelle, 0 crash.
|
||
|
||
## [1.8.2] — 2026-09-17 (versionCode 37)
|
||
|
||
### Maintenance — performance du moteur + fraîcheur de l'interface
|
||
|
||
Aucune nouvelle fonctionnalité : session d'audit complet (code + doc), avec
|
||
les optimisations et corrections sûres qui en sont sorties.
|
||
|
||
### Performance du moteur PK
|
||
- `e2At`/`computeCurve` : `cutoffHours` et les paramètres Bateman
|
||
(bisection 50 itérations) ne sont plus recalculés à chaque (point ×
|
||
dose × traitement) — pré-calculés par traitement, et Bateman devient
|
||
**paresseux** (inutile pour les traitements à profil). Le chemin le plus
|
||
chaud (tracé labs : ~720 points × N × D appels de cutoff par refresh)
|
||
s'allège d'un facteur P × T. Doses pré-groupées par traitement (fin des
|
||
scans complets). **Résultat identique** (ordre de sommation préservé —
|
||
223 tests verts inchangés).
|
||
- `AppLog` : purge du buffer sans recopie à chaque ligne loggée.
|
||
- `ChangelogHelper` : Regex précompilées (dialog « Nouveautés »).
|
||
- `CurveChart` : SimpleDateFormat créé hors boucle de dessin.
|
||
|
||
### Corrigé — fraîcheur de l'interface
|
||
- **Accueil** : éditer un scaleFactor ou le modèle T rafraîchit la courbe
|
||
immédiatement (clés du producteur incomplètes — l'ancien trio attendait
|
||
le tick de 60 s).
|
||
- **Graphiques** : éditer la VALEUR d'une analyse rafraîchit les courbes
|
||
(la clé n'était que le NOMBRE d'analyses).
|
||
|
||
### Maintenance — code & documentation
|
||
- Code mort supprimé (Repository/DAO sans appelants, `currentLevel`,
|
||
variable et scheduler dupliqués), imports morts/redondants nettoyés,
|
||
médiane de calibration factorisée (4 copies → 1 helper),
|
||
qualifications raccourcies, en-têtes/KDoc obsolètes mis à jour.
|
||
- Documentation : ~35 corrections (compteurs de tests obsolètes, TOC et
|
||
ancres réparées, inventaire des suites de tests à jour et fusionné,
|
||
titres CHANGELOG doublés supprimés, versions « à jour v1.2.0 » /
|
||
« Gradle 8.9 » / « API 34 » corrigées, fautes).
|
||
|
||
## [1.8.1] — 2026-09-17 (versionCode 36)
|
||
|
||
### 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 récents).
|
||
- **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, intervalle comparé exactement). La carte porte
|
||
désormais l'**ester effectif** de la dernière dose (override compris).
|
||
- **Nouveau : suggestion aussi sur l'Accueil** (demande) — carte compacte
|
||
(creux daté + créneau associé), avec la mention de stabilisation
|
||
seulement si le régime n'était pas déjà stable.
|
||
- Validé émulateur sur l'export réel : la carte passe de « stabilisé depuis
|
||
le 06/02 » (v1.8.0) à « **EEN pas stabilisé avant le 29/09 — premier
|
||
creux fiable après ton dernier changement** », creux recommandé 27/09
|
||
22:43, 0 crash.
|
||
- 3 nouveaux tests (223 au total / 193 sans les données locales) :
|
||
changement de dose / d'intervalle / d'ester → réinitialisation épinglée.
|
||
- **Web** : même fix en miroir + encart Accueil (web v1.8.1, versions sync).
|
||
|
||
## [1.8.0] — 2026-09-17 (versionCode 35)
|
||
|
||
### Prochaine prise de sang recommandée (page Analyses)
|
||
|
||
- **Nouvelle carte sur la page Analyses** : « Prochaine prise de sang
|
||
(suggestion) » — l'app déduit de ses courbes quand refaire une analyse :
|
||
**au creux estimé, juste avant l'injection suivante** (le creux est le
|
||
moment le plus informatif et comparable ; l'heure exacte du creux calculé
|
||
est affichée, avec la mention « ou simplement la veille du créneau »).
|
||
- **Condition de stabilité** (choix v1.8.0) : un creux n'est interprétable
|
||
que si l'ester est stabilisé — règle des **~5 demi-vies terminales**
|
||
(≈ 97 % de l'équilibre). L'app **saute donc au premier creux stabilisé**
|
||
après un changement d'ester/dose/rythme, et la carte l'explique
|
||
(« stabilisé depuis le … (~5 demi-vies) » / « c'est le premier creux fiable
|
||
depuis ton dernier changement »). Le modèle Estrannaise lit sa demi-vie
|
||
dans les tables (nouveau `PKProfileStore.terminalHalfLifeDays`), TFS/WHSAH
|
||
la calculent analytiquement.
|
||
- **Périmètre honnête** : la carte n'apparaît que s'il existe un traitement
|
||
E2 **injectable actif avec une Posologie** (sinon pas de creux prévisible) ;
|
||
**et si un injectable actif est SANS Posologie, la page affiche une invite**
|
||
« renseigne une Posologie pour recevoir des recommandations » (demande
|
||
v1.8.0). Créneaux déjà mesurés jamais re-recommandés (prise en compte des
|
||
analyses existantes). Calcul hors UI thread + rafraîchi chaque minute ;
|
||
disclaimer estimation visible sur la carte.
|
||
- **11 nouveaux tests** (220 au total / 190 sans les données locales) :
|
||
LabTimingTest (8 : creux avant créneau, saut de stabilisation, stabilisé,
|
||
prise récente → creux suivant, sans Posologie → null, oral → null,
|
||
invite, Estrannaise lit sa t½ dans la table) + régression n°5 étendue
|
||
(recommandation cohérente sur l'export réel).
|
||
- **Porté web** (web v1.8.0, versions sync) : `js/pk/lab-timing.js` miroir +
|
||
encart + scénario E2E.
|
||
- Validé émulateur §16.ter (APK release, seed réel v1.7.0) : carte rendue —
|
||
« creux 20/09 10:37, juste avant l'injection EEN du 20/09 11:20, EEN
|
||
stabilisé depuis le 06/02 » — 0 crash.
|
||
|
||
## [1.7.1] — 2026-09-16 (versionCode 34)
|
||
|
||
### Corrigé — une seule note sur deux s'affichait dans les Analyses
|
||
|
||
- **Bug remonté** (identique côté web) : dans une prise de sang E2+T, quand
|
||
les deux entrées portent des notes **distinctes**, une seule s'affichait
|
||
dans l'écran Analyses — l'autre était bien **conservée** (export/import
|
||
l'avaient) mais perdue à l'affichage.
|
||
- **Cause** : l'affichage ne prenait que la première note non vide du groupe
|
||
(hypothèse historique « toutes identiques en pratique » — vraie à l'époque
|
||
du dialog de création, fausse depuis l'édition unitaire E2/T).
|
||
- **Fix** : `labNotesForDisplay` (helper pur, testé) — notes distinctes →
|
||
une ligne par note **préfixée du marqueur** (« E2 : … » / « T : … ») ;
|
||
note identique sur les deux entrées → une seule ligne (pas de doublon) ;
|
||
notes vides ignorées ; une seule note → brute (comportement v1.0 gardé).
|
||
- **Épinglé par** : 5 nouveaux tests LabsGroupingTest + **régression n°5
|
||
data-driven sur le nouvel export réel** (v1.7.0 : la dernière prise
|
||
porte exactement ce cas — deux notes distinctes E2/T, les deux
|
||
affichées). Validé émulateur sur l'APK release seedé avec cet export.
|
||
- **Web** : même fix en miroir (web v1.7.1, versions sync).
|
||
|
||
## [1.7.0] — 2026-09-13 (versionCode 33)
|
||
|
||
### Sauvegarde automatique quotidienne (opt-in)
|
||
|
||
- **Nouvelle option Paramètres → « Sauvegarde automatique quotidienne »**
|
||
(désactivée par défaut, en PLUS de l'export manuel qui reste inchangé) :
|
||
chaque jour, l'app écrit une sauvegarde JSON complète (MÊME format que
|
||
l'export manuel — importable tel quel) dans le **dossier que tu choisis**
|
||
(ex. un dossier Owncloud synchronisé → le backup atterrit dans ton cloud).
|
||
- **Aucune permission de stockage** : le dossier est choisi UNE FOIS via le
|
||
sélecteur système (SAF) — la permission persiste (survit au reboot), tu
|
||
peux la révoquer à tout moment dans les réglages système. 100 % local :
|
||
l'écriture va uniquement dans CE dossier.
|
||
- **Rétention configurable** (1–30 copies, défaut 7) : chaque run crée un
|
||
NOUVEAU fichier horodaté `hormonetrack-auto-20260913-1830.json` (jamais
|
||
d'écrasement — les N dernières copies saines restent disponibles) et les
|
||
plus vieilles sont supprimées. **Tes exports manuels, tes logs et tout
|
||
fichier étranger du dossier ne sont JAMAIS touchés** (rétention pure,
|
||
épinglée par tests).
|
||
- **Statut visible** : « Dernière sauvegarde : OK/ÉCHEC (date) » dans
|
||
Paramètres + journalisation AppLog complète (diagnostic à distance).
|
||
À l'activation, un **premier backup est écrit immédiatement** (pas
|
||
d'attente de 24 h) ; ensuite WorkManager (24 h, survit au reboot, gère
|
||
le Doze).
|
||
- 9 nouveaux tests (200 au total / 177 sans les données locales) : noms
|
||
horodatés (règle anti-#48 : signature LocalDateTime obligatoire), garde
|
||
de rétention (jamais de suppression hors auto-backups reconnaissables,
|
||
keep clampé ≥ 1 — `keep=0` ne supprime jamais la dernière copie).
|
||
- Validé sur émulateur (§16.ter, APK release + recette standard) : dossier
|
||
choisi via le sélecteur SAF, fichier `hormonetrack-auto-…json` écrit,
|
||
rétention appliquée, 0 crash.
|
||
|
||
### Unités des axes du graphique
|
||
|
||
- **pg/mL** (E2, axe gauche) et **ng/mL** (T, axe droit) affichées au sommet
|
||
des colonnes de labels du graphique de simulation — remontée : « on n'a
|
||
jamais ajouté les unités des mesures dans les axes » (des nombres nus
|
||
depuis la v1.0). Portées aussi côté web (v1.7.0 web, versions sync).
|
||
|
||
## [1.6.0] — 2026-09-12 (versionCode 32)
|
||
|
||
### « Tracé labs » prolongé au-delà du dernier lab
|
||
|
||
- **Nouveau chip `Prolonger`** (à côté de `Tracé labs`, **off par défaut**,
|
||
sans effet — donc désactivé — tant que Tracé labs est off) : la courbe
|
||
ancrée sur tes prises de sang se PROLONGE désormais au-delà du dernier lab,
|
||
au lieu de s'arrêter là (limite v1.5.0 : justement la période la plus
|
||
récente était invisible).
|
||
- **Recette de la prolongation** : `courbe(t) = M(t) × ρ_dernier lab` où
|
||
M(t) = le modèle PK brut (porte TOUTE la dynamique : Tmax, queues,
|
||
accumulation **et les doses loguées après le dernier lab** — une nouvelle
|
||
injection, même à un autre ester, fait repartir la courbe en pic) et
|
||
ρ = le ratio corps-réel ÷ modèle observé au dernier lab, supposé constant.
|
||
Demande couverte : simuler à partir des labs précédents, du dosage et du
|
||
type d'ester injecté, et de l'évolution classique de l'ester.
|
||
- **Horizon honnête** : la prolongation s'arrête quand le MODÈLE lui-même
|
||
s'éteint (dernière dose + coupure moteur = 10 t½ terminales V3C/WHS, fin
|
||
de table ODS, 30 t½ Bateman) — jamais de ligne à zéro inventée au-delà.
|
||
- **Lisibilité** : la partie prolongée est dessinée SÉPARÉMENT (même rose,
|
||
atténué) avec sa propre légende — « estimation, plus ancrée » : la partie
|
||
mesurée (qui passe par les labs) ne peut pas se confondre avec la partie
|
||
simulée.
|
||
- **Avertissement visible** (demande) : tant que la partie prolongée est
|
||
affichée, un texte sous la légende rappelle que c'est une **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**
|
||
— et qu'il faut se fier à la prochaine prise de sang, pas à cette courbe.
|
||
- **Garde anti-ratés inchangée** : seuls les labs SIGNIFICATIFS ancrent
|
||
(leçon #61) — un lab tardif hors fenêtre d'action ne prolonge pas la
|
||
courbe et n'est jamais traversé.
|
||
- **Limites documentées** (cf doc de développement §7.10.bis) : ρ est
|
||
constant (pas d'extrapolation de pente — elle divergerait sans base
|
||
physiologique) ; après un changement d'ester, l'amplitude reste calibrée
|
||
par le ρ de l'ancien ester (même limite que la calibration classique) ;
|
||
les doses PRÉVISIONNELLES ne participent pas (la projection reste la
|
||
série Prévision).
|
||
- **8 nouveaux tests** (191 au total / 168 sans les données locales) :
|
||
identité ρ constant + continuité de suture, horizon = cutoff du
|
||
traitement, montée après une dose EV loguée après le dernier lab,
|
||
flag off = comportement v1.5.0 bit-compatible, garde #61 sur la
|
||
prolongation, gardes du helper d'horizon, `lastAnchorMs`.
|
||
- **Validé sur émulateur** (recette §16.ter, APK release + données
|
||
réelles seedées) — et heureusement : la validation a TROUVÉ le bug
|
||
#64 (courbe vide en release seule, inlining destructeur de R8 full
|
||
mode sur `LabTrajectoryModel`, cf doc §14). FIX : garde R8 explicite ;
|
||
re-validé en release : ancrée, prolongée, avertissement, 0 crash.
|
||
|
||
## [1.5.0] — 2026-09-11 (versionCode 31)
|
||
|
||
### « Tracé labs » — courbe ancrée sur tes prises de sang
|
||
|
||
- **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) et l'amplitude est recalée sur CHAQUE prise
|
||
de sang : la trajectoire réelle que tes labs tracent, avec la dynamique
|
||
du modèle entre les points.
|
||
- **Passage exact garanti** sur chaque lab ; transition continue
|
||
(log-linéaire) d'un lab au suivant — jamais de saut vertical.
|
||
- Garde anti-ratés : un lab qui ne « gouverne » plus la prédiction du
|
||
modèle (< 15 % du max, cf leçon #61) n'est PAS forcé-traversé — une
|
||
dose de test de janvier ne crée pas de zigzag.
|
||
- **E2 uniquement** (unité pg/mL) ; fenêtre = entre le 1er et le dernier
|
||
lab significatif — rien avant/après : la courbe s'arrête à ta dernière
|
||
prise de sang (honnête, pas d'extrapolation).
|
||
- Indépendante de la calibration (elle est SA propre référence) et
|
||
n'entre JAMAIS dans l'accueil/les seuils d'alerte/les rappels — une
|
||
courbe de comparaison, pas une estimation d'action.
|
||
- Couleur dédiée rose foncé pointillé, hors pics/creux.
|
||
|
||
### Améliorations UI
|
||
|
||
- La rangée de chips du graphique défile horizontalement (le 4ᵉ chip
|
||
était coupé hors fenêtre sur écrans étroits — constat émulateur v1.5.0).
|
||
|
||
## [1.4.10] — 2026-09-07 (versionCode 30)
|
||
|
||
### Corrigé
|
||
- **Le pan du graphique ne fonctionnait pas sur la vue 24 h** (#62, remontée
|
||
par test utilisateur sur Pixel 9 : « le glisser horizontal ne marche pas
|
||
sur une échelle de 24 h, uniquement au-dessus ») : les deltas de mouvement
|
||
du doigt (~30 px ≈ 0,67 h à 24 h) étaient **tronqués à 0** par le cast
|
||
en heures entières à CHAQUE événement de mouvement → le pan ne bougeait
|
||
jamais sur 24 h (alors que sur 7 j le même delta ≈ 4,7 h). FIX : **cumul
|
||
fractionnaire** — le résiduel < 1 h est conservé d'un événement au
|
||
suivant (`panDeltaHours`, pur et testé) : le pan se déclenche dès que le
|
||
cumul franchit une heure entière. Aucun impact sur les vues larges.
|
||
- **Nettoyage** : instrumentation de diagnostic retirée ; 2 imports/
|
||
constantes morts supprimés.
|
||
|
||
### Ajouté
|
||
- **4 nouveaux tests** (172 au total / 150 sans les données locales) :
|
||
cumul fractionnaire à 24 h (2 deltas → 1 h), 7 j (delta immédiat),
|
||
deltas négatifs (troncature vers zéro), cas dégénérés.
|
||
- **Limitation de test documentée** (§16.ter) : l'émulateur headless ne
|
||
délivre pas les mouvements intermédiaires au graphique via `adb input
|
||
swipe` → le pan n'est PAS testable par adb sur cet environnement ; le
|
||
test humain sur Pixel 9 reste la référence pour les gestes.
|
||
|
||
## [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 / 391)
|
||
→ 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)
|
||
|
||
### 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)
|