v1.9.7 (web) : tolérance ± 24 h de l'écart inter-doses — miroir Android
La règle de stabilisation comparait l'écart inter-doses EXACTEMENT à l'écart précédent : un log 30 min plus tard cassait le régime et repoussait la stabilisation de 5 × t½ à chaque injection (creux fuyant). Fix : chaque écart doit rester dans l'intervalle de Posologie ± 24 h (GAP_TOLERANCE_MS, fenêtre vs l'intervalle THÉORIQUE) — des logs à 6,8 j puis 7,2 j ne se déstabilisent plus en cascade ; un vrai changement de créneau reste hors fenêtre. Tests : « interval change » ré-épinglé (+21 j) + 2 tests de tolérance miroirs (12 verts lab-timing). WEB_VERSION 1.9.7 + CHANGELOG/README.
This commit is contained in:
parent
048a76e540
commit
ab8926bf36
@ -17,12 +17,12 @@
|
||||
> Fie-toi toujours à tes analyses de sang et aux consignes de ton
|
||||
> endocrinologue.
|
||||
|
||||
- **Statut** : web v1.9.6 — parité fonctionnelle avec l'Android v1.9.6
|
||||
- **Statut** : web v1.9.7 — parité fonctionnelle avec l'Android v1.9.7
|
||||
(modèle Estrannaise analytique + nuage d'incertitude MCMC exclusif ESE ;
|
||||
presets des 6 esters injectables ESE ; l'auto-backup journalier Android
|
||||
reste structurellement non porté, cf §12 — l'export manuel couvre la
|
||||
donnée) ·
|
||||
**178 tests verts** · E2E navigateur ✅ · conteneur testé en
|
||||
**180 tests verts** · E2E navigateur ✅ · conteneur testé en
|
||||
configuration durcie ✅ · lint/i18n ✅
|
||||
- **Journal des versions web** : [docs/CHANGELOG.md](docs/CHANGELOG.md)
|
||||
- **Releases** : [gitea.cloudyfy.fr/Siphonight/HormoneTrack-web/releases](https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web/releases)
|
||||
|
||||
@ -5,6 +5,20 @@
|
||||
> section la plus récente EN PREMIER ; ce fichier est lu par le dialog
|
||||
> « Nouveautés » de l'app web (fetch `docs/CHANGELOG.md`).
|
||||
|
||||
## [1.9.7] — 2026-09-20
|
||||
|
||||
### Corrigé — la suggestion de prise de sang « change tout le temps » à chaque injection
|
||||
|
||||
- Miroir de l'Android v1.9.7 (remontée + données réelles) : la règle de
|
||||
stabilisation comparait l'écart inter-doses **EXACTEMENT** à l'écart
|
||||
précédent — un log 30 min plus tard cassait le régime et repoussait la
|
||||
stabilisation de 5 × t½ à chaque injection (creux recommandé fuyant).
|
||||
- **Fix (tolérance)** : chaque écart doit rester dans **l'intervalle de
|
||||
Posologie ± 24 h** (`GAP_TOLERANCE_MS`, fenêtre vs l'intervalle
|
||||
THÉORIQUE). Un vrai changement de créneau (2 j au lieu de 7 j) reste
|
||||
hors fenêtre. Tests : « interval change » ré-épinglé (+21 j) + 2 tests
|
||||
de tolérance miroirs (12 verts lab-timing).
|
||||
|
||||
## [1.9.6] — 2026-09-20
|
||||
|
||||
### Modifié — la carte de l'accueil liste TOUTES les prochaines prises
|
||||
|
||||
@ -159,8 +159,16 @@ export function nextBloodDrawRecommendation(treatments, doseLogs, labs, nowMs =
|
||||
* modifiée/irrégulière) change le creux — conservateur assumé : une série
|
||||
* d'intervalles chaotiques maintient la carte « non stabilisée », ce qui est
|
||||
* pharmacocinétiquement vrai (le trough n'est comparable que sur un
|
||||
* intervalle régulier). Comparaison EXACTE (au ms) : 7 j ± 1 h casse le
|
||||
* régime, reflétant un vrai changement de créneau.
|
||||
* intervalle régulier).
|
||||
*
|
||||
* v1.9.7 (miroir du Kotlin, remontée : « la suggestion change tout le temps
|
||||
* à chaque injection si l'injection n'est pas faite pile à la même heure ») :
|
||||
* la comparaison EXACTE cassait le régime pour un log à 12:30 au lieu de
|
||||
* 12:00 — chaque écart doit rester dans l'INTERVALLE DE POSOLOGIE ± 24 h
|
||||
* (« je m'injecte le même jour, à l'heure près »). La fenêtre se réfère à
|
||||
* l'intervalle THÉORIQUE, pas à l'écart précédent : des logs à 6,8 j puis
|
||||
* 7,2 j ne se déstabilisent plus en cascade. Un vrai changement (2 j au lieu
|
||||
* de 7 j) reste hors fenêtre.
|
||||
*
|
||||
* @private
|
||||
* @param {object} carrier traitement porteur
|
||||
@ -172,17 +180,22 @@ function computeRegimeStartMs(carrier, myDoses) {
|
||||
let idx = sorted.length - 1;
|
||||
let regimeStart = sorted[idx].timestamp;
|
||||
let current = sorted[idx];
|
||||
// Écart EXIGÉ pour remonter : celui des deux doses les plus récentes
|
||||
let gapExpected = -1;
|
||||
// Fenêtre d'écart = INTERVALLE DE POSOLOGIE ± 24 h (la Posologie est un
|
||||
// prérequis de la carte : absente → on s'arrête à la dernière dose).
|
||||
const intervalDays = carrier.forecastIntervalDays;
|
||||
if (!intervalDays || intervalDays <= 0) return regimeStart;
|
||||
const intervalMs = Math.round(intervalDays * 24 * HOUR_MS);
|
||||
const gapMin = intervalMs - GAP_TOLERANCE_MS;
|
||||
const gapMax = intervalMs + GAP_TOLERANCE_MS;
|
||||
const DOSE_EPS = 1e-6;
|
||||
while (idx > 0) {
|
||||
const prev = sorted[idx - 1];
|
||||
const gap = current.timestamp - prev.timestamp;
|
||||
const sameMarker = String(doseEster(carrier, current)) === String(doseEster(carrier, prev))
|
||||
&& Math.abs(current.doseAmount - prev.doseAmount) < DOSE_EPS;
|
||||
const sameGap = gapExpected < 0 || gap === gapExpected;
|
||||
// gap > 0 : un doublon de log n'est jamais un régime
|
||||
const sameGap = gap > 0 && gap >= gapMin && gap <= gapMax;
|
||||
if (!sameMarker || !sameGap) break;
|
||||
gapExpected = gap;
|
||||
regimeStart = prev.timestamp;
|
||||
current = prev;
|
||||
idx--;
|
||||
@ -190,6 +203,11 @@ function computeRegimeStartMs(carrier, myDoses) {
|
||||
return regimeStart;
|
||||
}
|
||||
|
||||
/** Tolérance de l'écart inter-doses (v1.9.7, miroir GAP_TOLERANCE_MS Kotlin) :
|
||||
* ± 24 h autour de l'intervalle de Posologie — un log à 12:30 au lieu de
|
||||
* 12:00 ne repousse plus la stabilisation de 5 × t½. */
|
||||
const GAP_TOLERANCE_MS = 24 * HOUR_MS;
|
||||
|
||||
/**
|
||||
* Invite d'AFFICHAGE (v1.8.0, demande explicite) : existe-t-il un traitement
|
||||
* E2 injectable ACTIF SANS Posologie ? Si oui, la page Analyses suggère de la
|
||||
|
||||
@ -31,7 +31,7 @@ import { el, showDialog, field, switchRow, filterChip, toast } from './component
|
||||
* (APK + web au MÊME numéro, tags appariés `vX.Y.Z` + `vX.Y.Z-web`).
|
||||
* v1.4.10 = portage initial de l'Android v1.4.10.
|
||||
*/
|
||||
export const WEB_VERSION = '1.9.6';
|
||||
export const WEB_VERSION = '1.9.7';
|
||||
|
||||
/** Parse un champ numérique FR (virgule → point) ; null si vide/invalide. */
|
||||
function parseNumOrNull(text) {
|
||||
|
||||
@ -113,12 +113,35 @@ describe('LabTiming (miroir Android v1.8.0)', () => {
|
||||
});
|
||||
|
||||
test('changement d\'intervalle récent réinitialise la stabilisation', () => {
|
||||
// 9 semaines à 7 j, puis une dose 2 j après la plus ancienne des deux
|
||||
// dernières : l'écart terminal devient 2 j → régime fondé sur cette dose
|
||||
// (celle à NOW−14 j avait un écart de 5 j ≠ 2 j → hors régime)
|
||||
// 9 semaines à 7 j, puis une dose 2 j après la précédente : l'écart de
|
||||
// 2 j (48 h) est HORS de la fenêtre Posologie ± 24 h (v1.9.7, [6 j ;
|
||||
// 8 j] pour 7 j) → le régime s'arrête à la dose NOW−7 j (un vrai
|
||||
// changement de créneau reste un changement — miroir Android)
|
||||
const doses = weeklyDoses(9).concat([makeDose(NOW - 9 * DAY_MS, 5.0)]);
|
||||
const rec = nextBloodDrawRecommendation([eenTfs()], doses, [], NOW);
|
||||
assert.equal(rec.injectionMs, NOW + 14 * DAY_MS);
|
||||
// régimeStart = NOW−7 j → stabilisation ≈ NOW+15,7 j → 1ᵉʳ créneau : +21 j
|
||||
assert.equal(rec.injectionMs, NOW + 21 * DAY_MS);
|
||||
assert.equal(rec.wasAlreadyStabilized, false);
|
||||
});
|
||||
|
||||
test('le flou d\'heure de log ne réinitialise PAS la stabilisation (v1.9.7)', () => {
|
||||
// REMONTÉE v1.9.7 (miroir Android) : « la suggestion change tout le
|
||||
// temps à chaque injection si l'injection n'est pas faite pile à la
|
||||
// même heure » — chaque écart reste dans Posologie ± 24 h → le régime
|
||||
// remonte la séquence entière (63 j) → déjà stabilisée.
|
||||
const jitterHours = [2, -3, 5, -1, 23, -23, 1, -0.5, 0.75];
|
||||
const doses = jitterHours.map((jh, i) =>
|
||||
makeDose(NOW - (63 - 7 * i) * DAY_MS + jh * HOUR_MS, 5.0));
|
||||
const rec = nextBloodDrawRecommendation([eenTfs()], doses, [], NOW);
|
||||
assert.equal(rec.wasAlreadyStabilized, true);
|
||||
});
|
||||
|
||||
test('un écart au-delà de 24 h de tolérance réinitialise (v1.9.7)', () => {
|
||||
// 8 écarts réguliers, puis un retard de 25 h sur le 7 j : hors fenêtre
|
||||
// → le régime ne remonte pas avant la dose en retard → non stabilisée
|
||||
const doses = weeklyDoses(9).map((d, i) =>
|
||||
i === 7 ? makeDose(NOW - 14 * DAY_MS + 25 * HOUR_MS, 5.0) : d);
|
||||
const rec = nextBloodDrawRecommendation([eenTfs()], doses, [], NOW);
|
||||
assert.equal(rec.wasAlreadyStabilized, false);
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user