diff --git a/README.md b/README.md index 4da46b7..1fb18fc 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.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 && 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 ``` diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 25ceaa8..5618b61 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 = 40 - versionName = "1.9.2" + versionCode = 41 + versionName = "1.9.4" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/com/hormonetrack/data/model/PKPresets.kt b/app/src/main/java/com/hormonetrack/data/model/PKPresets.kt index 2fdb7f1..bb846ea 100644 --- a/app/src/main/java/com/hormonetrack/data/model/PKPresets.kt +++ b/app/src/main/java/com/hormonetrack/data/model/PKPresets.kt @@ -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, diff --git a/app/src/main/java/com/hormonetrack/ui/screens/TreatmentEditorScreen.kt b/app/src/main/java/com/hormonetrack/ui/screens/TreatmentEditorScreen.kt index 40a5866..4ea818c 100644 --- a/app/src/main/java/com/hormonetrack/ui/screens/TreatmentEditorScreen.kt +++ b/app/src/main/java/com/hormonetrack/ui/screens/TreatmentEditorScreen.kt @@ -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)) { diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index cc4e61b..1ba528a 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -238,6 +238,9 @@ Injection EV — Estrannaise + Injection EC (huile) — Estrannaise + Injection EB — Estrannaise + Injection EUCS (suspension cristalline) — Estrannaise Injection EU — Estrannaise Injection EEn — Estrannaise Injection EV — Transfem Science diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0bc8454..fabcf62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -238,6 +238,9 @@ EV injection — Estrannaise + EC injection (oil) — Estrannaise + EB injection — Estrannaise + EUCS injection (crystal suspension) — Estrannaise EU injection — Estrannaise EEn injection — Estrannaise EV injection — Transfem Science diff --git a/app/src/test/java/com/hormonetrack/data/model/PKPresetsTest.kt b/app/src/test/java/com/hormonetrack/data/model/PKPresetsTest.kt new file mode 100644 index 0000000..acc5433 --- /dev/null +++ b/app/src/test/java/com/hormonetrack/data/model/PKPresetsTest.kt @@ -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) + } +} diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cb68db9..10b2c5b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -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 diff --git a/docs/DEVELOPPEMENT.md b/docs/DEVELOPPEMENT.md index e143b72..c014ef2 100644 --- a/docs/DEVELOPPEMENT.md +++ b/docs/DEVELOPPEMENT.md @@ -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 → diff --git a/docs/GUIDE_INSTALLATION.md b/docs/GUIDE_INSTALLATION.md index ad13147..46a93e4 100644 --- a/docs/GUIDE_INSTALLATION.md +++ b/docs/GUIDE_INSTALLATION.md @@ -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 —