HormoneTrack/docs/CHANGELOG.md

44 KiB
Raw Blame History

Changelog — HormoneTrack

Format : Keep a Changelog. Versionnage : SemVer. 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)