v1.9.4 : presets du modèle ESE étendus à ses 6 esters injectables + éditeur dynamique

- PKPresets.kt (25 entrées) : EC huile (5 mg IM), EB (1 mg IM), EUCS
  suspension cristalline (10 mg SC) — champs Bateman informatifs = t½
  terminales des fits (pas utilisés au calcul)
- TreatmentEditorScreen.kt : liste des esters = Esters.choicesForModel
  (même helper que l'override du DoseDialog) ; au changement de modèle,
  l'ester rebascule sur EV si non couvert (garde anti 0 pg/mL)
- PKPresetsTest (nouveau, 6 tests) : couverture des 6 esters ESE,
  unicité, TFS 7 / WHSAH 6 inchangés, garde anti hors couverture,
  plausibilité des t½ informatives — 243 verts + lint
- versionCode 41 / versionName 1.9.4 + CHANGELOG/§2/§7.12/§8/README/GUIDE
- Doc : doublon de la section §7.12 supprimé (collé deux fois)
This commit is contained in:
Siphonight 2026-09-19 23:30:23 +02:00
parent 8fc6d07772
commit e4f52bc6da
10 changed files with 199 additions and 50 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.9.2 — build Android ✅, **lint vert** ✅, **237 tests unitaires** ✅ (207 sans les données de test locales ; 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.9.4 — build Android ✅, **lint vert** ✅, **243 tests unitaires** ✅ (213 sans les données de test locales ; 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)
@ -123,7 +123,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 # 237 tests (207 sans les données locales)
./gradlew testDebugUnitTest # 243 tests (213 sans les données locales)
./gradlew lint # lint vert obligatoire avant release
```

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 = 40
versionName = "1.9.2"
versionCode = 41
versionName = "1.9.4"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@ -46,6 +46,39 @@ object PKPresets {
absorptionHours = 152f, eliminationHalfLifeHours = 150f, bioavailabilityFraction = 1f,
defaultDoseUnit = "mg", defaultDoseAmount = 10.0
),
// --- v1.9.4 : le modèle ESE est ANALYTIQUE (estrannaise.js, cf
// pk/EstrannaiseModels.kt) et couvre 6 esters injectables —
// presets ajoutés pour EC (huile), EB et EUCS (undécylate
// suspension cristalline, exclusif à ce fit). Champs Bateman =
// t½ terminales des fits (informatifs, pas utilisés au calcul —
// le dispatch passe par la forme close).
PKPreset(
nameRes = R.string.preset_ec_ese,
type = TreatmentType.ESTRADIOL,
route = AdministrationRoute.INJECTION_IM,
esterType = Esters.EC,
pkModel = PKModels.ESTRANNAISE,
absorptionHours = 7f, eliminationHalfLifeHours = 202f, bioavailabilityFraction = 1f,
defaultDoseUnit = "mg", defaultDoseAmount = 5.0
),
PKPreset(
nameRes = R.string.preset_eb_ese,
type = TreatmentType.ESTRADIOL,
route = AdministrationRoute.INJECTION_IM,
esterType = Esters.EB,
pkModel = PKModels.ESTRANNAISE,
absorptionHours = 1f, eliminationHalfLifeHours = 4f, bioavailabilityFraction = 1f,
defaultDoseUnit = "mg", defaultDoseAmount = 1.0
),
PKPreset(
nameRes = R.string.preset_eucs_ese,
type = TreatmentType.ESTRADIOL,
route = AdministrationRoute.INJECTION_SUBCUT,
esterType = Esters.EUCS,
pkModel = PKModels.ESTRANNAISE,
absorptionHours = 22f, eliminationHalfLifeHours = 756f, bioavailabilityFraction = 1f,
defaultDoseUnit = "mg", defaultDoseAmount = 10.0
),
// --- Injections: Transfem Science models ---
PKPreset(
nameRes = R.string.preset_ev_tfs,

View File

@ -271,10 +271,17 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
Spacer(Modifier.height(8.dp))
if (PharmacokineticEngine.isInjectionRoute(route)) {
// v1.9.4 : la liste des esters suit la couverture RÉELLE du
// modèle choisi (Esters.choicesForModel — miroir du DoseDialog
// et du treatment-editor web) : le modèle ESE ANALYTIQUE offre
// ses 6 esters injectables (EV/EU/EEn/EC/EB/EUCS) directement
// dans l'éditeur, au lieu des seuls EV/EU/EEn de l'époque des
// tables ODS. Chaque ester listé est dispatché par le moteur —
// aucun choix possible ne produit 0 pg/mL.
DropdownField(
label = stringResource(R.string.ester),
selectedLabel = ester,
options = listOf(Esters.EV, Esters.EU, Esters.EEN).map { it to it },
options = Esters.choicesForModel(model).map { it to it },
onSelect = { ester = it }
)
Spacer(Modifier.height(8.dp))
@ -292,7 +299,17 @@ fun TreatmentEditorScreen(treatmentId: Long, onDone: () -> Unit) {
PKModels.TRANSFEM_SCIENCE to stringResource(R.string.model_tfs),
PKModels.WHSAH to stringResource(R.string.model_whs)
),
onSelect = { model = it }
onSelect = { model = it
// v1.9.4 : l'ester courant peut sortir de la couverture
// du nouveau modèle (ex. EUCS + TFS : le V3C n'a pas de
// paramètres EUCS → le dispatcher donnerait 0) — on
// rebascule sur le premier ester couvert (EV dans les
// trois listes). Le recompose reconstruit ensuite la
// liste des esters (options = choicesForModel(model)).
if (ester !in Esters.choicesForModel(it)) {
ester = Esters.choicesForModel(it).first()
}
}
)
} else {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {

View File

@ -238,6 +238,9 @@
<!-- Presets -->
<string name="preset_ev_ese">Injection EV — Estrannaise</string>
<string name="preset_ec_ese">Injection EC (huile) — Estrannaise</string>
<string name="preset_eb_ese">Injection EB — Estrannaise</string>
<string name="preset_eucs_ese">Injection EUCS (suspension cristalline) — Estrannaise</string>
<string name="preset_eu_ese">Injection EU — Estrannaise</string>
<string name="preset_een_ese">Injection EEn — Estrannaise</string>
<string name="preset_ev_tfs">Injection EV — Transfem Science</string>

View File

@ -238,6 +238,9 @@
<!-- Presets -->
<string name="preset_ev_ese">EV injection — Estrannaise</string>
<string name="preset_ec_ese">EC injection (oil) — Estrannaise</string>
<string name="preset_eb_ese">EB injection — Estrannaise</string>
<string name="preset_eucs_ese">EUCS injection (crystal suspension) — Estrannaise</string>
<string name="preset_eu_ese">EU injection — Estrannaise</string>
<string name="preset_een_ese">EEn injection — Estrannaise</string>
<string name="preset_ev_tfs">EV injection — Transfem Science</string>

View File

@ -0,0 +1,84 @@
package com.hormonetrack.data.model
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
/**
* Tests des PRESETS de traitements (v1.9.4 — miroir de `tests/presets.test.js`
* côté web) : le modèle ESE ANALYTIQUE couvre 6 esters injectables — les
* presets de création doivent offrir les 6 (comme TFS offre les 7 du V3C et
* WHSAH les 6 du fit Mona). Avant v1.9.4, les presets ESE n'offraient que
* EV/EU/EEn (l'héritage des tables ODS) — remontée : « les presets n'ont pas
* été mis à jour lors de l'ajout du modèle source ESE ».
*
* Garde centrale : **aucun preset ne peut créer un traitement non couvert par
* son propre modèle** (ester ∈ choicesForModel(pkModel)) — un preset hors
* couverture produirait 0 pg/mL sans aucun avertissement (leçon §12).
*/
class PKPresetsTest {
/** Presets injectables d'un modèle (ester réel — le NONE Bateman sort). */
private fun injectables(model: String) =
PKPresets.all.filter { it.pkModel == model && it.esterType != Esters.NONE }
@Test
fun `les presets ESE couvrent les 6 esters du fit analytique`() {
val esters = injectables(PKModels.ESTRANNAISE).map { it.esterType }.sorted()
// Même ensemble que Esters.choicesForModel(ESE) — l'éditeur et les
// presets doivent proposer exactement la couverture du dispatch.
assertEquals(Esters.choicesForModel(PKModels.ESTRANNAISE).sorted(), esters)
}
@Test
fun `chaque ester couvert a EXACTEMENT un preset ESE (pas de doublon)`() {
val names = injectables(PKModels.ESTRANNAISE).map { it.esterType }
assertEquals(names.size, names.toSet().size)
}
@Test
fun `les presets TFS (7) et WHSAH (6) restent inchangés`() {
assertEquals(7, injectables(PKModels.TRANSFEM_SCIENCE).size)
assertEquals(6, injectables(PKModels.WHSAH).size)
}
@Test
fun `aucun preset injectable ne peut créer un traitement non couvert`() {
// La garde ne vise que les presets à ester RÉEL (profil PK) : un
// preset Bateman (esterType NONE, ex. gel/patch/oral/anti-androgène)
// ne passe pas par le dispatch par ester — sa couverture n'a pas de
// sens. Un preset injectable hors couverture produirait 0 pg/mL sans
// aucun avertissement (leçon §12).
for (preset in PKPresets.all.filter { it.esterType != Esters.NONE }) {
assertTrue(
"preset " + preset.nameRes + " : ester " + preset.esterType +
" hors couverture de " + preset.pkModel + " → 0 pg/mL garanti",
preset.esterType in Esters.choicesForModel(preset.pkModel)
)
}
}
@Test
fun `les presets ESE non injectables (gel patch oral) restent en Bateman`() {
val nonInjectable = PKPresets.all.filter {
it.pkModel == PKModels.ESTRANNAISE && it.esterType == Esters.NONE
}
// 3E2 (gel/patch/oral) + 3 anti-androgènes (CPA/spiro/bica)
assertTrue("gel, patch, oral + anti-androgènes toujours présents", nonInjectable.size >= 6)
assertTrue(nonInjectable.all { it.bioavailabilityFraction in 0.01f..1f })
}
@Test
fun `les champs Bateman informatifs des nouveaux presets ESE restent plausibles`() {
// Les champs Bateman des presets ESE reflètent les t½ terminales des
// fits (informatifs, pas utilisés au calcul) : EC ≈ 202 h (k3 0,669/j),
// EB ≈ 4 h (k3 4,34/j), EUCS ≈ 756 h (k2 0,022/j) — on garde des
// bornes larges pour ne pas figer les valeurs des fits.
val ec = PKPresets.all.first { it.esterType == Esters.EC && it.pkModel == PKModels.ESTRANNAISE }
val eb = PKPresets.all.first { it.esterType == Esters.EB && it.pkModel == PKModels.ESTRANNAISE }
val eucs = PKPresets.all.first { it.esterType == Esters.EUCS && it.pkModel == PKModels.ESTRANNAISE }
assertTrue("t½ EC ESE ≈ 8,4 j (fit 0,669/j)", ec.eliminationHalfLifeHours in 100f..300f)
assertTrue("t½ EB ESE courte (fit 4,34/j)", eb.eliminationHalfLifeHours in 1f..24f)
assertTrue("t½ EUCS ESE ≈ 31,5 j (suspension cristalline)", eucs.eliminationHalfLifeHours in 400f..1200f)
}
}

View File

@ -3,6 +3,31 @@
Format : [Keep a Changelog](https://keepachangelog.com/fr-FR/1.1.0/).
Versionnage : [SemVer](https://semver.org/). Releases taguées dans git (`v1.4.10`, …).
## [1.9.4] — 2026-09-19 (versionCode 41)
### Ajouté — les presets du modèle ESE couvrent ses 6 esters injectables
- **Remontée** : depuis l'ajout du modèle source Estrannaise (v1.9.0), les
presets de création n'offraient toujours que EV/EU/EEn — « les presets
n'ont pas été mis à jour lors de l'ajout du modèle source pour ESE ».
- **Presets (`PKPresets.kt`, 25 entrées)** : 3 presets ESE ajoutés au
même niveau que les autres — EC huile (5 mg IM), EB (1 mg IM), EUCS
undécylate suspension cristalline (10 mg SC, ester exclusif à ce fit).
Champs Bateman informatifs = t½ terminales des fits (pas utilisés au
calcul — le dispatch passe par la forme close).
- **Éditeur (`TreatmentEditorScreen.kt`)** : la liste des esters suit la
couverture RÉELLE du modèle choisi (`Esters.choicesForModel` — même
liste que l'override du DoseDialog) : ESE liste ses 6 esters
injectables directement à la création. Au changement de modèle, si
l'ester courant sort de la couverture (ex. EUCS + TFS), il rebascule
sur EV — garde anti « 0 pg/mL ».
- **Tests** : `PKPresetsTest` (nouveau, 6 tests — miroir
`tests/presets.test.js` web) : couverture des 6 esters ESE, unicité,
TFS (7) / WHSAH (6) inchangés, garde « aucun preset injectable hors
couverture de son modèle », plausibilité des t½ informatives.
243 tests verts + lint.
- **Doc** : doublon de la section §7.12 supprimé (collé deux fois).
## [1.9.2] — 2026-09-19 (versionCode 40)
### Corrigé — le nuage d'incertitude exigeait un traitement STOCKÉ en ESE

View File

@ -63,6 +63,7 @@ Fonctionnalités v1 :
| Date | Événement |
|---|---|
| 19 sept. 2026 (session v1.9.4) | **Presets du modèle ESE étendus à ses 6 esters injectables** (remontée : « depuis l'ajout du modèle source ESE, les presets n'ont pas été mis à jour ») : 3 presets ajoutés (EC huile 5 mg IM, EB 1 mg IM, EUCS suspension cristalline 10 mg SC — 25 entrées), champs Bateman informatifs = t½ des fits ; **éditeur** : la liste des esters suit `Esters.choicesForModel` (parité DoseDialog/web) et rebascule sur EV si l'ester sort de la couverture au changement de modèle ; **web** : 2 trous du portage v1.9.0 corrigés (`Esters.EUCS` manquant dans pk-engine.js — preset référençant `undefined` — et `choicesForModel` resté sur la couverture ODS à 3 esters) + `tests/presets.test.js` (6 tests, miroir `PKPresetsTest`). 243 verts / 213 sans données locales + lint + check web (175). Doc : doublon §7.12 supprimé. Web v1.9.4 sync. |
| 19 sept. 2026 (session v1.9.2) | **Fix nuage vide sans traitement stocké ESE** (remontée : « il ne s'active que autour du tracé émulé, pas autour du modèle Estrannaise ») : le filtre du nuage testait le pkModel STOCKÉ — or la courbe ESE redessine TOUTES les doses E2 (modelOverride ESE) quel que soit le modèle stocké. FIX : le nuage couvre les doses à ester effectif couvert par ESE (peu importe le stocké) ; l'exclusivité reste portée par le chip ; l'oral Bateman reste hors nuage. 2 tests réécrits (237 verts / 207 sans). Web miroir v1.9.2. |
| 19 sept. 2026 (session v1.9.1) | **Fix nuage calibré + suggestion sur l'Accueil + explication +4 pg/mL** : (a) remontée « le nuage ne s'active que autour du tracé, pas autour du modèle Estrannaise » : la courbe ESE est CALIBRÉE (scalePerEster) mais le nuage était BRUT → échelles différentes. FIX : EstrannaiseCloud.compute reçoit le MÊME scalePerEster que la courbe ESE (Android + web miroir) ; test nuage calibré ×2 (Android). (b) Le +4 pg/mL constaté depuis v1.8.2 : l'ARRONDI de la médiane (fix v1.8.2, plus juste que la troncature) a déplacé une échelle auto-calibrée d'un cran — le niveau actuel est le CORRIGÉ, documenté CHANGELOG 1.9.1. (c) Émulateur : nuage visible autour de la courbe ESE calibrée (1363 px rose pâle, seed réel + traitement EV ESE de test), 0 crash. Web v1.9.1 sync (harmonisation de l'arrondi — les niveaux web/android convergent). 235 verts / 205 sans données locales. |
| 17 sept. 2026 (session v1.9.0) | **Modèle Estrannaise ANALYTIQUE (abandon de l'ODS) + nuage d'incertitude MCMC (Android + web sync)** (demande) : découverte des sources originelles (github.com/WHSAH/estrannaise.js — forme close 3C + paramètres publiés + posterior MCMC 313/ester). Vérification numérique AVANT débranchement : **RMS 0,00 vs tables ODS** (l'ODS était l'échantillonnage de ces formules). ESE → analytique (dispatch, cutoff 10×t½, t½ LabTiming), PKProfileStore retiré du runtime (asset ODS → src/test/assets, −550 Ko d'APK), ESE couvre 6 esters (+EUCS), TFS = liste explicite (EUCS sans V3C TFS). **Nuage MCMC exclusif ESE** : chip `Nuage` (off, activable si ESE affiché, coupure auto si ESE off), 32 courbes du posterior déterministes, alpha faible sous les courbes, hors échelles/labels/extrema. 11 nouveaux tests (234 verts / 204 sans données locales) + lint + check web (166). Validé émulateur §16.ter : courbe ESE analytique affichée, unités, chips Extend/Cloud, 0 crash. |
@ -701,50 +702,25 @@ devient à son tour**.
miroirs ; le fetch des tables ODS au démarrage web est remplacé par le
fetch MCMC (démarrage plus rapide) ; nuage dessiné par `chart-canvas.js`.
### 7.12 Modèle Estrannaise ANALYTIQUE + nuage MCMC (v1.9.0) — `pk/EstrannaiseModels.kt`, `pk/EstrannaiseCloud.kt`
#### Presets & éditeur — les 6 esters sélectionnables à la création (v1.9.4)
**DÉCISION v1.9.0** (utilisatrice) : « tous les modèles ont maintenant leurs
sources originelles — on abandonne complètement les liens avec le fichier
ODS, qui lui-même était une extrapolation de ces mêmes sources ». TFS
(v1.4.0) et WHSAH (v1.4.6) étaient déjà analytiques ; **l'Estrannaise le
devient à son tour**.
- **SOURCE** : https://github.com/WHSAH/estrannaise.js/ — modèle 3
compartiments en forme close : `C(t) = dose·d·k1·k2·[e^(−k1t)/((k1−k2)(k1−k3))
− e^(−k2t)/((k1−k2)(k2−k3)) + e^(−k3t)/((k1−k3)(k2−k3))]` (t en JOURS).
Paramètres publiés par ester : EV [478, 0,236, 4,85, 1,24], EU [471,5,
0,01729, 6,528, 2,285], EEn [191,4, 0,119, 0,601, 0,402], EC [246,
0,0825, 3,57, 0,669], EB [1893,1, 0,67, 61,5, 4,34], EUCS [16,15,
0,046, 0,022, 0,101] (nouveau code ester v1.9.0 : « EUn casubq »).
- **FIDÉLITÉ (vérifiée AVANT débranchement)** : la forme close reproduit
les tables ODS **RMS 0,00** sur 0→200 j (EV/EU/EEn), écart de pic
≤ 0,1 % — l'ODS n'était que l'échantillonnage horaire de ces formules.
Épinglé par `EstrannaiseModelsTest` (Android) et
`estrannaise-models.test.js` (web).
- **Débranchement runtime** : `concentrationOfDose` dispatche ESE →
`EstrannaiseModels.sample` (plus de fallback `PKProfileStore.sample`) ;
`cutoffHours` ESE → 10 × t½ terminale analytique (plus la longueur de
table) ; `LabTiming` t½ ESE analytique ; `PKProfileStore.init` retirée
du démarrage. **L'asset `pk_profiles.json` quitte `src/main/assets`
(−550 Ko d'APK) pour `src/test/assets/`** : l'ODS survit UNIQUEMENT
comme référence des tests de fidélité. `PKProfileStore` est conservé
(lecteur pur) pour ces tests.
- **Couverture étendue** : ESE passe de 3 à **6 esters injectables**
(EV/EU/EEn/EC/EB/**EUCS**) — `Esters.choicesForModel` mis à jour (TFS
reste 7, WHSAH 6 ; ⚠️ TFS = liste EXPLICITE : EUCS n'a pas de V3C TFS).
- **NUAGE D'INCERTITUDE (exclusif ESE, demande v1.9.0)** : Estrannaise
publie le **posterior MCMC** de ses paramètres (313 échantillons par
ester — asset `mcmc_samples.json` ≈ 48 Ko, chargé au démarrage). Chip
`Nuage` du graphique (off par défaut, activable à volonté, ACTIVABLE
SEULEMENT si ESE est affiché — éteindre ESE coupe le nuage) →
`EstrannaiseCloud.compute` trace 32 courbes du posterior (échelonnées,
déterministes) superposant les doses des porteurs ESE. Dessin en alpha
faible SOUS les courbes ; hors échelles/labels/extrema. Limites : les
contributions TFS/WHSAH ne sont pas dans le nuage (pas de posterior
publié) ; les patchs du repo (tw/ow) ne sont pas portés.
- **Web sync** : `js/pk/estrannaise-models.js` + `js/pk/estrannaise-cloud.js`
miroirs ; le fetch des tables ODS au démarrage web est remplacé par le
fetch MCMC (démarrage plus rapide) ; nuage dessiné par `chart-canvas.js`.
- **`PKPresets.kt` (25 entrées)** : 3 presets ESE ajoutés — EC huile
(5 mg IM), EB (1 mg IM), EUCS suspension cristalline (10 mg SC, ester
exclusif à ce fit). Champs Bateman **informatifs** (t½ terminales des
fits : EC ≈ 202 h pour k3 0,669/j, EB ≈ 4 h pour k3 4,34/j, EUCS
≈ 756 h pour k2 0,022/j) — pas utilisés au calcul, le dispatch passe
par la forme close.
- **`TreatmentEditorScreen.kt`** : la liste des esters =
`Esters.choicesForModel(model)` (même helper que l'override du
DoseDialog — plus aucune divergence des listes). Au changement de
modèle, si l'ester courant sort de la couverture (EUCS + TFS par ex.),
il rebascule sur EV : aucun choix possible ne produit 0 pg/mL.
- **Garde épinglée** : `PKPresetsTest` (miroir web
`tests/presets.test.js`) — « aucun preset injectable hors couverture de
son propre modèle » ; ESE 6 / TFS 7 explicite (sans EUCS) / WHSAH 6.
- **Web sync v1.9.4** : mêmes presets + i18n ; 2 trous du portage v1.9.0
corrigés au passage (`Esters.EUCS` manquant dans `pk-engine.js`,
`choicesForModel` resté à 3 esters).
## 8. Tests unitaires
@ -918,6 +894,11 @@ Emplacement : `app/src/test/java/com/hormonetrack/`. Répertoire de travail d'ex
- **`EstrannaiseCloudTest`** (5) : v1.9.0 — nuage d'incertitude : 32 courbes
à dispersion réelle (les posterior couvrent une plage), fenêtre respectée,
exclusivité ESE (TFS/oral → vide), sans dose → vide, nbCurves < 2 → vide.
- **`PKPresetsTest`** (6) : v1.9.4 — presets de création : les presets ESE
couvrent les 6 esters du fit (unicité, même ensemble que
`choicesForModel`), TFS (7) / WHSAH (6) inchangés, **garde anti
« 0 pg/mL »** (aucun preset injectable hors couverture de son modèle),
champs Bateman informatifs plausibles (t½ des fits).
- **`LabTimingTest`** (11) : v1.8.0/v1.8.1 — recommandation de prochaine
prise de sang : creux juste avant le créneau, saut au premier creux
STABILISÉ (5 × t½ ; régime non stabilisé → creux suivant), prise récente →

View File

@ -88,7 +88,10 @@ Les notifications de l'app remontent automatiquement sur la montre via **Huawei
## 6. Premiers pas dans l'app
1. **Traitements → +** → choisis un preset (ex : *Injection EV — Estrannaise*)
1. **Traitements → +** → choisis un preset (ex : *Injection EV —
Estrannaise*). Le modèle Estrannaise propose ses 6 esters injectables
(EV, EU, EEn, EC huile, EB, EUCS suspension cristalline) ; le menu
déroulant Ester liste toujours les esters couverts par le modèle choisi.
- Tu as changé d'ester ? Crée (ou garde) le traitement de l'ancien ester et
passe-le **inactif** (switch « Actif ») : il disparaît de la saisie et de tes
rappels, mais **son historique reste simulé et calibré** sur les graphiques —