v1.10.0 (web) : fix #68 — creux pré-injection dans la reco de prise de sang (miroir Android)

Le « creux » était le minimum de la fenêtre entière (créneau N−1 → N) :
pour un ester à montée lente (EEn, pic ~J+5 ≈ intervalle 7 j) il tombait
juste après l'injection PRÉCÉDENTE — date antérieure à la stabilisation
affichée sur la même carte, et « juste avant ton injection du … » faux de
plusieurs jours. Le creux est désormais le niveau PRÉ-INJECTION du créneau
(principe v1.8.0 restauré ; EV inchangé — le point pré-injection EST son
minimum de fenêtre). WEB_VERSION alignée sur 1.10.0.

180 tests + E2E verts (check.sh) ; suite lab-timing inchangée (sa
sémantique épinglée était celle du fix).
This commit is contained in:
Siphonight 2026-09-27 23:10:10 +02:00
parent ff4a1374db
commit b865b1606f
3 changed files with 37 additions and 7 deletions

View File

@ -5,6 +5,29 @@
> section la plus récente EN PREMIER ; ce fichier est lu par le dialog > section la plus récente EN PREMIER ; ce fichier est lu par le dialog
> « Nouveautés » de l'app web (fetch `docs/CHANGELOG.md`). > « Nouveautés » de l'app web (fetch `docs/CHANGELOG.md`).
## [1.10.0] — 2026-09-27
### Corrigé — la recommandation de prise de sang précédait la stabilisation (#68)
- Miroir de l'Android v1.10.0 (remontée + données réelles → 6ᵉ régression
épinglée côté Android) : le « creux » était le **minimum de la fenêtre
entière** (créneau N−1 → créneau N). Pour un ester à montée lente (EEn :
pic ~J+5 ≈ intervalle 7 j), ce minimum tombe **juste après l'injection
PRÉCÉDENTE** (creux d'absorption, ~3 h) — la date proposée était gouvernée
par l'injection N−1, antérieure à la stabilisation, et le texte « juste
avant ton injection du … » était faux de plusieurs jours.
- **Fix** : le creux = **niveau PRÉ-INJECTION du créneau** (dernier point de
courbe avant l'injection) — restaure le principe v1.8.0 documenté et rend
la carte cohérente. Pour les esters à t½ courte (EV), le point
pré-injection EST le minimum de fenêtre : comportement inchangé.
- Tests : la suite lab-timing (12) passe inchangée — sa sémantique épinglée
(« creux juste avant l'injection, écart ≤ 2 h ») était déjà celle du fix ;
180 tests + E2E verts (`check.sh`).
Les autres changements de la v1.10.0 Android (dialog fantôme #69, rappel
sauté si dose déjà loggée) n'ont pas d'équivalent web (notifications et
intents de notification sont spécifiques Android).
## [1.9.7] — 2026-09-20 ## [1.9.7] — 2026-09-20
### Corrigé — la suggestion de prise de sang « change tout le temps » à chaque injection ### Corrigé — la suggestion de prise de sang « change tout le temps » à chaque injection

View File

@ -115,18 +115,25 @@ export function nextBloodDrawRecommendation(treatments, doseLogs, labs, nowMs =
const minTroughMs = nowMs + MIN_HORIZON_HOURS * HOUR_MS; const minTroughMs = nowMs + MIN_HORIZON_HOURS * HOUR_MS;
let windowStart = nowMs; let windowStart = nowMs;
for (const slot of slots) { for (const slot of slots) {
// Creux du créneau = minimum de la courbe entre l'injection précédente // ⚠️ FIX #68 (v1.10.0, miroir du Kotlin) : le creux = niveau
// (ou maintenant) et ce créneau // PRÉ-INJECTION du créneau (dernier point de courbe avant l'injection —
const window = curve.filter((p) => p.timestamp > windowStart && p.timestamp < slot.timestamp); // principe 1, « le moment le plus comparable »). L'ancien MINIMUM DE LA
// FENÊTRE ENTIÈRE tombait au DÉBUT de la fenêtre pour les esters à
// montée lente (EEn : pic ~J+5 ≈ intervalle 7 j → le minimum local est
// le creux d'absorption, quelques heures après l'injection PRÉCÉDENTE) :
// la date proposée précédait la stabilisation affichée sur la MÊME
// carte, et le texte « juste avant ton injection du … » était faux de
// plusieurs jours (remontée v1.9.8). Pour les esters à t½ courte (EV),
// le point pré-injection EST le minimum de fenêtre : inchangé.
let trough = null; let trough = null;
for (const p of window) { for (const p of curve) {
if (trough === null || p.e2 < trough.e2) trough = p; if (p.timestamp > windowStart && p.timestamp < slot.timestamp) trough = p;
} }
if ( if (
trough !== null trough !== null
&& trough.timestamp >= minTroughMs && trough.timestamp >= minTroughMs
&& trough.timestamp > lastLabMs && trough.timestamp > lastLabMs
&& slot.timestamp >= stabilizedAtMs // sauter au premier creux stabilisé && trough.timestamp >= stabilizedAtMs // sauter au premier creux stabilisé
) { ) {
return { return {
troughMs: trough.timestamp, troughMs: trough.timestamp,

View File

@ -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`). * (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. * v1.4.10 = portage initial de l'Android v1.4.10.
*/ */
export const WEB_VERSION = '1.9.7'; export const WEB_VERSION = '1.10.0';
/** Parse un champ numérique FR (virgule → point) ; null si vide/invalide. */ /** Parse un champ numérique FR (virgule → point) ; null si vide/invalide. */
function parseNumOrNull(text) { function parseNumOrNull(text) {