From 1c9d2e4e6a0c186e77a19a48e55d92e437f08bca Mon Sep 17 00:00:00 2001 From: Siphonight Date: Mon, 7 Sep 2026 17:45:44 +0200 Subject: [PATCH] =?UTF-8?q?v1.4.7=20:=20toggles=20de=20mod=C3=A8les=20alig?= =?UTF-8?q?n=C3=A9s=20sur=20les=20traitements=20=C3=A0=20profil=20(#58)=20?= =?UTF-8?q?+=20calibration=20universelle=20=C3=A9pingl=C3=A9e=20(par=20est?= =?UTF-8?q?er,=20toutes=20les=20courbes)=20+=20fix=20#59=20:=20chip=20de?= =?UTF-8?q?=20traitement=20affichait=20'Estrannaise'=20pour=20WHSAH=20(ter?= =?UTF-8?q?naire=20=E2=86=92=20helper=20modelLabelRes=20centralis=C3=A9)?= =?UTF-8?q?=20;=20158=20tests=20verts=20+=20lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +-- app/build.gradle.kts | 4 +- .../com/hormonetrack/data/model/Treatment.kt | 14 +++++ .../hormonetrack/ui/components/CurveChart.kt | 24 ++++++++ .../hormonetrack/ui/screens/ChartScreen.kt | 20 ++++++ .../ui/screens/TreatmentsScreen.kt | 7 ++- .../com/hormonetrack/pk/WhsahEngineTest.kt | 26 ++++++++ .../java/com/hormonetrack/ui/ChartZoomTest.kt | 35 +++++++++++ docs/CHANGELOG.md | 43 ++++++++++++- docs/DEVELOPPEMENT.md | 61 ++++++++++++++++--- docs/GUIDE_INSTALLATION.md | 4 ++ 11 files changed, 231 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 479b985..8e6ced3 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ sauvegarde JSON. **100 % local, aucun compte, aucun serveur.** > à titre informatif — ce ne sont pas des mesures. Fie-toi toujours à tes analyses de > sang et aux consignes de ton endocrinologue. -- **Statut** : v1.4.6 — build Android ✅, **lint vert** ✅, **155 tests unitaires** ✅ (133 sans les données de test locales ; 3 régressions épinglées sur données réelles **non versionnées**), intégration montre = notifications ✅, **dépôt Gitea privé + releases avec APK** ✅ +- **Statut** : v1.4.7 — build Android ✅, **lint vert** ✅, **158 tests unitaires** ✅ (136 sans les données de test locales ; 3 régressions épinglées sur données réelles **non versionnées**), intégration montre = notifications ✅, **dépôt Gitea privé + releases avec APK** ✅ - **Journal des versions** : [docs/CHANGELOG.md](docs/CHANGELOG.md) - **Guide utilisateur** : [docs/GUIDE_INSTALLATION.md](docs/GUIDE_INSTALLATION.md) - **Doc de développement** (architecture, maths, décisions, bugs) : [docs/DEVELOPPEMENT.md](docs/DEVELOPPEMENT.md) @@ -36,7 +36,8 @@ sauvegarde JSON. **100 % local, aucun compte, aucun serveur.** [simulateur TFS](https://transfemscience.org/misc/injectable-e2-simulator/), [article](https://transfemscience.org/articles/injectable-e2-meta-analysis/)), avec les **7 esters** (EV, EU, EEn, EB, EC huile, EC suspension, PEP) — et WHSAH - en couvre 6 (sans PEP). Affichés côte à côte avec toggles indépendants (3 couleurs) + en couvre 6 (sans PEP). Affichés côte à côte avec toggles indépendants (3 couleurs) — **pré-cochés + selon les modèles de tes traitements** (v1.4.7) et **tous calibrés** par tes labs - **Modèle Bateman** paramétrable (temps au pic, demi-vie, biodisponibilité) pour gel, patch et voie orale - **Simulation prévisionnelle** : configurer la **posologie** (intervalle en jours) sur un @@ -91,7 +92,7 @@ par AGP si les licences sont signées). Le wrapper télécharge Gradle 9.7.1. git clone && cd HormoneTrack echo "sdk.dir=/chemin/vers/android-sdk" > local.properties # ou ANDROID_HOME ./gradlew assembleDebug # APK : app/build/outputs/apk/debug/app-debug.apk -./gradlew testDebugUnitTest # 155 tests (133 sans les données locales) +./gradlew testDebugUnitTest # 158 tests (136 sans les données locales) ./gradlew lint # lint vert obligatoire avant release ``` @@ -103,7 +104,7 @@ Pas de Play Store : l'app est sideloadée. Détails pas-à-pas : [docs/GUIDE_INS Dépôts : **gitea.cloudyfy.fr** et **gitea.farewell.dev** (miroir) — `Siphonight/HormoneTrack` sur les deux (privé), avec -**releases taguées** (`v1.1.0` → `v1.4.6`) et **deux APK par release** (depuis v1.2.5) +**releases taguées** (`v1.1.0` → `v1.4.7`) et **deux APK par release** (depuis v1.2.5) (téléchargeables sans compiler, cf [docs/DEVELOPPEMENT.md §16.bis](docs/DEVELOPPEMENT.md)) : `-release.apk` (**recommandé**, optimisé R8, 2,4 Mo) et `-debug.apk` (20 Mo) : diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 65ac2be..dc8c0ae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,8 +17,8 @@ android { // tag : v1.3.0→v1.3.2 contenaient tous versionCode 14 / "1.3.0" // (bump jamais commité) → BuildConfig.VERSION_NAME était faux dans // les APK publiés (Paramètres + titre du dialog « Nouveautés »). - versionCode = 26 - versionName = "1.4.6" + versionCode = 27 + versionName = "1.4.7" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/hormonetrack/data/model/Treatment.kt b/app/src/main/java/com/hormonetrack/data/model/Treatment.kt index 8e66979..ef6cf8a 100644 --- a/app/src/main/java/com/hormonetrack/data/model/Treatment.kt +++ b/app/src/main/java/com/hormonetrack/data/model/Treatment.kt @@ -43,6 +43,20 @@ object PKModels { const val WHSAH = "WHS" } +/** + * Ressource de LABEL d'un modèle PK (v1.4.6/1.4.7, fix #59) — UNIQUE source + * de vérité pour les affichages : chips de `TreatmentsScreen`, dropdown de + * `TreatmentEditorScreen`, chip du graphique. ⚠️ L'ancien ternaire à 2 + * branches affichait « Estrannaise » pour WHSAH (WHSAH tombait dans le + * `else`), remonté par l'utilisatrice — TOUTE nouvelle utilisation doit + * passer par ce helper, pas par un ternaire (bug #59). + */ +fun modelLabelRes(pkModel: String): Int = when (pkModel) { + PKModels.TRANSFEM_SCIENCE -> com.hormonetrack.R.string.model_tfs + PKModels.WHSAH -> com.hormonetrack.R.string.model_whs + else -> com.hormonetrack.R.string.model_ese +} + enum class TreatmentType { ESTRADIOL, ANTI_ANDROGEN, diff --git a/app/src/main/java/com/hormonetrack/ui/components/CurveChart.kt b/app/src/main/java/com/hormonetrack/ui/components/CurveChart.kt index d3df750..0c580e4 100644 --- a/app/src/main/java/com/hormonetrack/ui/components/CurveChart.kt +++ b/app/src/main/java/com/hormonetrack/ui/components/CurveChart.kt @@ -113,6 +113,30 @@ fun niceCeil(value: Double): Double { return nice * exp } +/** + * Toggles de MODÈLES PK par défaut (v1.4.7, fix #58) : au chargement du + * graphique, seuls les modèles RÉELLEMENT utilisés par les traitements (en + * cours ou passés — `isActive` n'est pas un filtre de simulation, cf §6.bis) + * sont affichés ; les autres restent disponibles (chip off, activables au + * tap). Ex. EV historique sous TFS + EEn actuel sous WHSAH → TFS ✓ WHSAH ✓ + * Estrannaise ✗. + * + * @param pkModelsUsed ensemble des `pkModel` des traitements de l'utilisatrice. + * @return map modèle → toggle initial. GARDE-FOU : si AUCUN modèle profil + * n'est utilisé (traitements gel/oral uniquement), TFS est forcé à ON — + * les traitements Bateman sont tracés IDENTIQUEMENT dans chaque série + * (cf §7.3b) : sans garde-fou, le graphique serait vide. + */ +fun defaultModelToggles(pkModelsUsed: Set): Map { + val toggles = mapOf( + "ESE" to pkModelsUsed.contains("ESE"), + "TFS" to pkModelsUsed.contains("TFS"), + "WHS" to pkModelsUsed.contains("WHS") + ) + // Garde-fou : aucune courbe sinon (Bateman tracé dans chaque série) + return if (toggles.values.none { it }) toggles + ("TFS" to true) else toggles +} + /** * Pas d'échantillonnage de la courbe selon la plage affichée (v1.2.9) : * plus on zoome, plus le pas finit pour des courbes lisses diff --git a/app/src/main/java/com/hormonetrack/ui/screens/ChartScreen.kt b/app/src/main/java/com/hormonetrack/ui/screens/ChartScreen.kt index b24f13d..5158f8b 100644 --- a/app/src/main/java/com/hormonetrack/ui/screens/ChartScreen.kt +++ b/app/src/main/java/com/hormonetrack/ui/screens/ChartScreen.kt @@ -111,6 +111,26 @@ fun ChartScreen() { var showTfs by remember { mutableStateOf(true) } // v1.4.6 : 3ᵉ modèle PK superposable (WHSAH — fit license-free de Mona) var showWhs by remember { mutableStateOf(false) } + // v1.4.7 (fix #58) : au CHARGEMENT des traitements, les toggles sont + // alignés sur les modèles réellement UTILISÉS par des traitements à + // PROFIL PK (injection + ester, `usesProfileModel`) — en cours ou passés + // (cf §6.bis : EV inactif reste simulé) ; les autres modèles restent + // activables au tap. ⚠️ FILTRER par usesProfileModel : sinon un + // traitement oral/anti-androgène (pkModel stocké = ESE par défaut, sans + // courbe profil) activerait Estrannaise à tort (vu sur l'émulateur avec + // le CPA seedé). Une seule initialisation. + var modelTogglesInitialized by remember { mutableStateOf(false) } + LaunchedEffect(treatments) { + if (!modelTogglesInitialized && treatments.isNotEmpty()) { + val toggles = com.hormonetrack.ui.components.defaultModelToggles( + treatments.filter { it.usesProfileModel }.map { it.pkModel }.toSet() + ) + showEse = toggles.getValue("ESE") + showTfs = toggles.getValue("TFS") + showWhs = toggles.getValue("WHS") + modelTogglesInitialized = true + } + } var showForecast by remember { mutableStateOf(false) } var showExtrema by remember { mutableStateOf(false) } var chartWidthPx by remember { mutableIntStateOf(0) } diff --git a/app/src/main/java/com/hormonetrack/ui/screens/TreatmentsScreen.kt b/app/src/main/java/com/hormonetrack/ui/screens/TreatmentsScreen.kt index 65f6dbd..c7aa4d7 100644 --- a/app/src/main/java/com/hormonetrack/ui/screens/TreatmentsScreen.kt +++ b/app/src/main/java/com/hormonetrack/ui/screens/TreatmentsScreen.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp import com.hormonetrack.R import com.hormonetrack.data.model.PKModels import com.hormonetrack.data.model.Treatment +import com.hormonetrack.data.model.modelLabelRes import com.hormonetrack.ui.LocalAppContainer import com.hormonetrack.ui.components.formatDose @@ -99,10 +100,12 @@ private fun TreatmentCard(tr: Treatment, onClick: () -> Unit) { AssistChip( onClick = {}, label = { + // v1.4.7 (fix #59) : label via modelLabelRes — + // l'ancien ternaire à 2 branches affichait + // « Estrannaise » pour le modèle WHSAH. Text( tr.esterType + " · " + stringResource( - if (tr.pkModel == PKModels.TRANSFEM_SCIENCE) R.string.model_tfs - else R.string.model_ese + modelLabelRes(tr.pkModel) ) ) } diff --git a/app/src/test/java/com/hormonetrack/pk/WhsahEngineTest.kt b/app/src/test/java/com/hormonetrack/pk/WhsahEngineTest.kt index b1de3f7..7fb93f5 100644 --- a/app/src/test/java/com/hormonetrack/pk/WhsahEngineTest.kt +++ b/app/src/test/java/com/hormonetrack/pk/WhsahEngineTest.kt @@ -103,6 +103,32 @@ class WhsahEngineTest { assertTrue(curve.all { it.e2 == 0.0 }) } + @Test + fun `calibration applies to ALL model curves equally (fix 58)`() { + // Demande v1.4.7 : « TOUTES les courbes doivent s'adapter au calibrage » + // — épinglé : 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. + val tr = treatment(Esters.EEN, PKModels.WHSAH) + val d = dose(mg = 5.0) + val t = BASE + 2 * DAY_MS + val scales = mapOf("EEN" to 2.0) + for (model in listOf("ESE", "TFS", "WHS")) { + val calibrated = PharmacokineticEngine.computeCurve( + listOf(tr), listOf(d), BASE, t, tConfig = TConfig(), + modelOverride = model, scalePerEster = scales + ).last().e2 + val raw = PharmacokineticEngine.computeCurve( + listOf(tr), listOf(d), BASE, t, tConfig = TConfig(), + modelOverride = model + ).last().e2 + assertTrue( + "$model : calibrée=$calibrated ≠ 2× brut=$raw", + calibrated in raw * 2.0 * 0.99..raw * 2.0 * 1.01 + ) + } + } + @Test fun `three models give three distinct EV curves (no toggle collapse)`() { val tr = treatment(Esters.EV, PKModels.ESTRANNAISE) diff --git a/app/src/test/java/com/hormonetrack/ui/ChartZoomTest.kt b/app/src/test/java/com/hormonetrack/ui/ChartZoomTest.kt index cdebdf5..04db19c 100644 --- a/app/src/test/java/com/hormonetrack/ui/ChartZoomTest.kt +++ b/app/src/test/java/com/hormonetrack/ui/ChartZoomTest.kt @@ -152,6 +152,41 @@ class ChartZoomTest { rangeHours = 6L, horizonHours = 24L, firstSlotGapHours = 0L)) } + // ── v1.4.7 (fix #58) : toggles de modèles alignés sur les traitements ── + + @Test + fun `default toggles follow the models actually used by treatments`() { + // Cas de l'utilisatrice : EV historique sous TFS + EEn actuel sous WHSAH + // → Estrannaise OFF (jamais utilisée), les deux autres ON + assertEquals( + mapOf("ESE" to false, "TFS" to true, "WHS" to true), + com.hormonetrack.ui.components.defaultModelToggles(setOf("TFS", "WHS")) + ) + // les 3 modèles utilisés → tous ON + assertEquals( + mapOf("ESE" to true, "TFS" to true, "WHS" to true), + com.hormonetrack.ui.components.defaultModelToggles(setOf("ESE", "TFS", "WHS")) + ) + // un seul modèle → lui seul ON + assertEquals( + mapOf("ESE" to true, "TFS" to false, "WHS" to false), + com.hormonetrack.ui.components.defaultModelToggles(setOf("ESE")) + ) + } + + @Test + fun `default toggles - no profile model used falls back to TFS (Bateman still drawn)`() { + // Traitements gel/oral uniquement (pkModel stocké = ESE mais PAS de + // modèle profil au sens usesProfileModel)... le set passé par + // ChartScreen contient les pkModel BRUTS : un traitement gel sous + // ESE produit setOf("ESE") → ESE ON. Un set vide (aucun traitement) + // → garde-fou TFS ON (la courbe ne doit jamais être vide par défaut). + assertEquals( + mapOf("ESE" to false, "TFS" to true, "WHS" to false), + com.hormonetrack.ui.components.defaultModelToggles(emptySet()) + ) + } + // ── v1.4.5 (fix #55) : labels X alignés sur minuit LOCAL ── @Test diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4f1d27f..a9e2654 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,7 +1,48 @@ # 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.6`, …). +Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.4.7`, …). + +## [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) ## [1.4.6] — 2026-09-07 (versionCode 26) diff --git a/docs/DEVELOPPEMENT.md b/docs/DEVELOPPEMENT.md index 33dd176..e447de0 100644 --- a/docs/DEVELOPPEMENT.md +++ b/docs/DEVELOPPEMENT.md @@ -89,6 +89,7 @@ Fonctionnalités v1 : | 7 sept. 2026 (session v1.4.4) | **Prévision inaccessible depuis v1.4.1** (#54, remontée) : DEUX causes emboîtées — (a) captures FIGÉES dans `pointerInput(Unit)` (`futurePanHorizon` lu à 0 pour toujours → pan futur interdit) → `rememberUpdatedState` + **garde de source** (`ChartScreenSourceGuardTest`, le lint Compose ne voit pas ce pattern) ; (b) toggle sans effet visible (`endMs = now` → 0 dose générée) → extension contrôlée à droite (`forecastExtensionHours`, jusqu'au 1ᵉʳ créneau, sans déplacer le début). Validé émulateur : drag gauche → panHours < 0, courbe dessinée au-delà de « maintenant » (screencap). 140 verts. Publication v1.4.4. | | 7 sept. 2026 (session v1.4.5) | **« La prévision simule le 13 au lieu du 12 »** (#55) : diagnostic sur données réelles — GÉNÉRATION EXACTE (créneau = dernière dose loguée + 6 j ; subtilité : la dose avait été loguée le LENDENDE de l'injection réelle → dérive d'un jour par cycle, l'app suit les logs) ; illusion causée par l'absence de marqueur + labels X à minuit UTC. FIX : marqueurs de doses (prévisionnels pointillés + réels discrets), labels à minuit LOCAL, fuseau de graphique configurable (Paramètres). **+ #56** : boucle de recomposition (`nowMs` relu à chaque frame → endMs dérive → saturation main thread → taps perdus) → mémoïsé sur le tick. 145 verts. Publication v1.4.5. | | 7 sept. 2026 (session v1.4.6) | **3ᵉ modèle PK « WHSAH »** (demande) : le fit « license-free » du WHSAH Collective publié dans **Mona** (l'app concurrente — son git montre qu'elle utilisait NOS paramètres TFS avant mars 2026) intégré au même niveau : dispatch parallèle (aucun remplacement), 6 presets, dropdown 3 choix, 3ᵉ toggle superposable (vert/violet), PEP non couvert. Comparaison chiffrée TFS vs WHSAF épinglée (EV pic 367 vs 295 ; EEn à J+1 ×3). **+ #57** : l'écran Graphiques défile désormais (rangée de toggles coupée sous le pli, trouvé en émulateur). 155 verts. Publication v1.4.6. | +| 7 sept. 2026 (session v1.4.7) | **Toggles alignés sur les traitements** (#58, demande) : au chargement, seuls les modèles utilisés par des traitements à PROFIL PK (`usesProfileModel`) sont ON (l'oral/AA polluait le set — vu en émulateur avec le CPA) ; garde-fou TFS si aucun profil ; une seule init. **Épinglé : la calibration s'applique à TOUTES les courbes** (par ESTER via doseEster, indépendant du modelOverride — test). 158 verts. Publication v1.4.7. | + **#59** : le chip de la carte de traitement affichait « Estrannaise » pour WHSAH (ternaire à 2 branches → `modelLabelRes` centralisé). | 6 sept. 2026 (session v1.3.0) | Dialog « Nouveautés » après mise à jour (CHANGELOG embarqué en asset, tâche `copyChangelog`, version vue en DataStore) ; **événements d'agenda récurrents** (calendrier local HormoneTrack, RRULE posologie, permission runtime, Room v3 `calendarEventId`) ; Paramètres : version + lien releases ; **78 tests verts**, APK v1.3.0 + releases. | | 5 sept. 2026 (session v1.2.10) | Sens des boutons de zoom inversé (+ = zoom avant, convention carte — retour utilisateur) ; 67 tests verts, APK v1.2.10 + releases. | | 5 sept. 2026 (session v1.2.9) | **Zoom du graphique** (pinch + boutons, 6 h → 300 j, focal stable, échantillonnage adaptatif `stepForRange`, labels X 1 h/3 h) ; **README : disclaimer IA-assisté** en en-tête ; 3 tests ; **67 tests verts**, APK v1.2.9 + releases. | @@ -345,6 +346,14 @@ T est faux d'un facteur 100, bug réel remonté par l'utilisatrice : labs 33/44 ### 7.6 Calibration (v1.2.1 : PAR PÉRIODE D'ESTER pour l'auto) +**PORTÉE (épinglé v1.4.7, test `calibration applies to ALL model curves +equally`)** : le facteur s'applique par ESTER de la DOSE (`doseEster`), +indépendamment du modelOverride → TOUTES les courbes affichées (ESE/TFS/ +WHS) d'un même traitement sont scalées identiquement. Subtilité : 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 : les comparaisons inter-modèles reflètent +la FORME, pas des calibrations distinctes). **Automatique** (`computeEsterScaleFactors` + `scalePerEster`, option « Auto-calibration ») : - chaque lab est **attribué à la période d'injection dans laquelle il tombe** = dernière dose E2 ≤ lab (une prise de sang reflète d'abord l'injection qui précède) ; @@ -448,7 +457,7 @@ niveau que les deux autres, pas de remplacement »). ## 8. Tests unitaires -**155 tests JVM, tous verts** (`./gradlew testDebugUnitTest`) — **133 sans +**158 tests JVM, tous verts** (`./gradlew testDebugUnitTest`) — **136 sans les données de test locales** (cf §8.bis : les 3 classes de régression, 6 tests chacune, sont skippées via `Assume`). Dépendance : JUnit 4.13.2. Emplacement : `app/src/test/java/com/hormonetrack/`. Répertoire de travail d'exécution = @@ -574,11 +583,18 @@ dépôt (ni maintenant, ni si le repo devient public un jour). - **`WhsahModelsTest`** (6) : v1.4.6 — fidélité au comportement **Mona** (fit WHSAH) : pics EV 367,5 @ 1,69 j / EEn 187,9 @ 4,99 j (±2 %), t½ par ester, linéarité par mg, PEP non couvert, `choicesForModel` ×3 modèles. -- **`WhsahEngineTest`** (4) : v1.4.6 — dispatch WHSAH (override depuis un +- **`WhsahEngineTest`** (5) : v1.4.6/v1.4.7 — dispatch WHSAH (override depuis un + identiques au pic par construction) ; + **calibration universelle épinglée** + (le facteur par ester s'applique à TOUTES les courbes, indépendamment du + modelOverride — demande v1.4.7). traitement ESE, `pkModel = WHS`, coupure 10 × t½, PEP → 0) et **3 courbes EV distinctes** (ESE/TFS/WHS — seuil 2 % : ESE et TFS sont quasi identiques au pic par construction). -- **`ChartZoomTest`** (13) : v1.2.9/v1.4.1/v1.4.5 — pas d'échantillonnage, +- **`ChartZoomTest`** (15) : v1.2.9 (pas d'échantillonnage adaptatif) / + v1.4.1 (horizon + clamp panoramique) / v1.4.5 (`pointHoursBefore`, + `xLabelTicks` minuit local) / v1.4.7 — **`defaultModelToggles`** (toggles + alignés sur les modèles utilisés par les traitements ; garde-fou TFS si + aucun profil), horizon + clamp de panoramique, `pointHoursBefore`, extension de prévision, **`xLabelTicks`** (minuit LOCAL, fuseau CHOISI America/New_York, heures rondes, cas dégénérés). @@ -615,7 +631,6 @@ dépôt (ni maintenant, ni si le repo devient public un jour). et 0 au-delà de 10 × t½ (par design) ; `computeCurve(modelOverride=TFS)` sur un traitement stocké ESE reproduit le pic V3C EV 295 — c'est le test du dispatch `concentrationOfDose` + de `cutoffHours`. -- **`ChartZoomTest`** (7) : v1.2.9 (pas d'échantillonnage) + v1.4.1 — **horizon de prévision** 12 × Posologie borné 30 j–1 an (`forecastHorizonHours`, null sans Posologie) et **clamp du panoramique** (`clampPanHours` : panHours négatif = futur, borné par l'horizon ; prévision OFF → futur interdit) — la logique de fenêtre du graphique est centralisée et testée. - **`ReminderScheduleTest`** (8) : v1.4.0 — fix #52 : rappel sur la GRILLE Posologie (dose il y a 2 j + intervalle 7 j → alarme dans 5 j à l'heure choisie, PAS demain), heure déjà passée le jour du créneau → créneau @@ -712,6 +727,11 @@ Sur la montre : remontée par Gadgetbridge **ou** Huawei Health (cf §17). (+ FAB), mini-chart 24 h (multi-séries via `ChartSeries`) **cliquable → écran Graphiques** (v1.2.1) avec mini-légende E2/T ; données auto-calibrées si l'option est active (`scalePerEster = effectiveAuto?.esterScales`) ; rafraîchissement `tick` 60 s +- `ChartScreen` (v1.2, le plus riche) : **toggles de modèles initialement + alignés sur les traitements** (v1.4.7, fix #58 : `defaultModelToggles` sur + les pkModel des traitements à PROFIL PK — init unique au chargement ; + garde-fou TFS si aucun profil — les traitements Bateman sont tracés dans + chaque série) ; - `ChartScreen` (v1.2, le plus riche) : **fenêtre temporelle v1.4.1** — `endMs = now − panHours` avec `panHours` signé : > 0 = passé (tirer vers la droite), **< 0 = futur** (tirer vers la gauche, uniquement avec la @@ -1125,6 +1145,18 @@ l'exemple) :** **Session v1.4.0 (modèle TFS sur la méta-analyse + fix #52) :** +59. **« EEN Estrannaise » pour un traitement sous WHSAH** (v1.4.7, + remontée) : le chip de `TreatmentsScreen` affichait le modèle via un + ternaire à 2 branches (`if (pkModel == TFS) model_tfs else model_ese`) + → WHSAH tombait dans le `else`. Fix : helper centralisé + `modelLabelRes(pkModel)` (data/model/Treatment.kt) — UNIQUE source du + label. Leçon : quand un 3ᵉ choix est ajouté à un champ, GREPPER tous + les points d'affichage existants (l'éditeur et le chart avaient été + fixés en v1.4.6, la carte de traitement avait été ratée) — l'audit de + `grep -rn "model_tfs else"` est passé à côté du pattern retourné + multi-ligne ; le vrai filet : chercher les USAGES du champ + (`tr.pkModel`) et vérifier chaque site. + 57. **Écran Graphiques : rangée des toggles coupée sous le pli** (v1.4.6, découvert en validant WHSAH sur émulateur : le chip était absent du dump UI) : le Column du ChartScreen ne défilait pas — les toggles de @@ -1340,8 +1372,8 @@ vérifie pas en tests JVM). 1. **Bumper la version** dans `app/build.gradle.kts` : `versionCode = N+1`, `versionName = "X.Y.Z+1"` (SemVer : fix = Z, feature = Y). -2. **Tests verts obligatoires** : `./gradlew testDebugUnitTest` — 155 au - total, 133 si `local-test-data/` est absent (les 3 classes de régression +2. **Tests verts obligatoires** : `./gradlew testDebugUnitTest` — 158 au + total, 136 si `local-test-data/` est absent (les 3 classes de régression réelles sont skippées via `Assume`, 6 tests chacune) ; **lint vert obligatoire** : `./gradlew lint` (v1.3.4 — `StringFormatMatches` aurait attrapé les crashs #47/#48 dès v1.3.1). Paliers de test du projet : @@ -1633,9 +1665,24 @@ Sur le téléphone de test (à compléter par l'utilisatrice) : RÉELLEMENT (bug #53 : avant, la comparaison portait sur 24 h et le delta pouvait être trompeusement négatif) — vérifier la cohérence avec la courbe 24 h du graphique +- [ ] **v1.4.4** : **prévision sans saut** : activer le chip ne déplace pas le + graphique (il s'étend jusqu'à la prochaine dose) ; drag gauche = futur + jusqu'à 1 an ; « Revenir à maintenant » ; désactivation en plein futur = + retour auto (fix #54 : avant, le pan futur était figé à 0) +- [ ] **v1.4.5** : **marqueurs de doses** : ligne pointillée + heure pour les + doses projetées, triangles discrets pour les prises réelles ; labels X + à minuit LOCAL du fuseau CHOISI (Paramètres, vide = téléphone) ; la + prévision ne devait rien à un décalage de génération (créneau exact) +- [ ] **v1.4.6** : **3ᵉ modèle WHSAH** : preset WHSAH → courbe verte + superposable aux 2 autres (montée plus rapide à J+1, t½ plus longues + que TFS) ; PEP non couvert ; 6 presets ; dropdown modèle à 3 choix +- [ ] **v1.4.7** : **toggles alignés sur les traitements** : au 1ᵉʳ chargement + du graphique, seuls les modèles des traitements à profil sont ON + (ex. EV TFS + EEn WHSAH → Estrannaise OFF) ; les autres activables au + tap ; et TOUTES les courbes affichées reflètent la calibration (test) --- -*Doc mise à jour le 7 sept. 2026 (v1.4.6) — build OK, lint vert, 155/155 tests verts (133 sans +*Doc mise à jour le 7 sept. 2026 (v1.4.7) — build OK, lint vert, 158/158 tests verts (136 sans les données locales), dépôts Gitea (cloudyfy + farewell) avec releases APK, aucune donnée de santé dans le dépôt ni l'historique. Fil des corrections : v1.3.3 = reprise de maintenance (permissions agenda, export logs IO, bump de version) ; v1.3.4 = 2 crashs diff --git a/docs/GUIDE_INSTALLATION.md b/docs/GUIDE_INSTALLATION.md index 2675d06..457f43c 100644 --- a/docs/GUIDE_INSTALLATION.md +++ b/docs/GUIDE_INSTALLATION.md @@ -187,6 +187,10 @@ Pics de référence (dose unique de 5 mg, IM) : pour parcourir la projection dans le futur (jusqu'à 1 an selon la Posologie) ; « Revenir à maintenant » pour revenir. Si tu désactives le chip alors que tu es dans le futur, l'app revient automatiquement à maintenant +- **Toggles pré-cochés selon tes traitements** (v1.4.7) : au chargement du + graphique, seuls les modèles utilisés par tes traitements (injections, en + cours ou passés) sont affichés — les autres s'activent au tap ; et TOUTES + les courbes affichées suivent ta calibration (labs), quel que soit le modèle - **Marqueurs de doses** (v1.4.5) : les doses projetées sont marquées par une ligne verticale pointillée avec leur heure ; tes prises enregistrées par un petit triangle discret au bas du graphique — plus d'ambiguïté sur « où » la