v1.4.7 : toggles de modèles alignés sur les traitements à profil (#58) + calibration universelle épinglée (par ester, toutes les courbes) + fix #59 : chip de traitement affichait 'Estrannaise' pour WHSAH (ternaire → helper modelLabelRes centralisé) ; 158 tests verts + lint

This commit is contained in:
Siphonight 2026-09-07 17:45:44 +02:00
parent ca917ba772
commit 1c9d2e4e6a
11 changed files with 231 additions and 16 deletions

View File

@ -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 <repo> && 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) :

View File

@ -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 {

View File

@ -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,

View File

@ -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<String>): Map<String, Boolean> {
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

View File

@ -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) }

View File

@ -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)
)
)
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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