v1.9.1 : fix nuage calibré (il entoure la courbe ESE calibrée) + harmonisation arrondi (miroir Android)
- estrannaise-cloud.js : paramètre scalePerEster — le nuage reçoit la
MÊME calibration que la courbe ESE (autoByModel ESE). Avant : courbe ESE
calibrée mais nuage BRUT → échelles différentes (remontée v1.9.1 :
« le nuage ne s'active que autour du tracé, pas autour du modèle
Estrannaise »).
- chart.js : autoByModel.get('ESE')?.esterScales passé au nuage ;
DEFAULT_NB_CURVES importé.
- +1 test : nuage calibré ×2 = nuage brut ×2 (167 verts au total).
- CHANGELOG [1.9.1] : fix nuage calibré + HARMONISATION de l'arrondi de
la médiane de calibration (le web avait la troncature historique — les
niveaux web/android convergent ; le niveau correct = l'ARRONDI).
- WEB_VERSION 1.9.1.
This commit is contained in:
parent
174928bb6b
commit
fc1b6e1700
@ -5,6 +5,19 @@
|
||||
> 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.1] — 2026-09-19
|
||||
|
||||
### Corrigé — le nuage d'incertitude entoure maintenant la courbe Estrannaise calibrée
|
||||
|
||||
- Miroir du fix Android v1.9.1 : la courbe ESE affichée est calibrée
|
||||
(scalePerEster des labs) mais le nuage était tracé brut → échelles
|
||||
différentes. Le nuage reçoit maintenant le MÊME scalePerEster que la
|
||||
courbe ESE.
|
||||
- Harmonisation de l'arrondi de la médiane de calibration (le web avait
|
||||
la troncature historique) : les niveaux estimés web et Android
|
||||
convergent. Le niveau correct = l'ARRONDI (plus juste que la
|
||||
troncature, cf CHANGELOG Android 1.9.1).
|
||||
|
||||
## [1.9.0] — 2026-09-18
|
||||
|
||||
### Modèle Estrannaise ANALYTIQUE + nuage d'incertitude (portage Android v1.9.0, versions sync)
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
import * as EstrannaiseModels from './estrannaise-models.js';
|
||||
import { usesProfileModel } from './pk-engine.js';
|
||||
import { usesProfileModel, doseEster } from './pk-engine.js';
|
||||
|
||||
|
||||
/** Nombre de courbes du nuage (compromis étendue visuelle / coût ×32). */
|
||||
@ -45,7 +45,15 @@ export const MIN_HORIZON_HOURS = 6;
|
||||
* ESE couvert, aucune dose, ou MCMC non chargé.
|
||||
*/
|
||||
export function compute(
|
||||
treatments, doseLogs, startMs, endMs, stepMs, nowMs, nbCurves = DEFAULT_NB_CURVES,
|
||||
treatments, doseLogs, startMs, endMs, stepMs, nowMs,
|
||||
nbCurves = DEFAULT_NB_CURVES,
|
||||
/**
|
||||
* Calibration à appliquer au nuage (v1.9.1 — fix « le nuage ne s'active
|
||||
* que autour du tracé, pas autour du modèle Estrannaise ») : passer le
|
||||
* MÊME scalePerEster que la courbe ESE pour que le nuage ENTOURE la
|
||||
* courbe calibrée.
|
||||
*/
|
||||
scalePerEster = null,
|
||||
) {
|
||||
if (startMs >= endMs || stepMs <= 0 || nbCurves < 2) return [];
|
||||
|
||||
@ -91,9 +99,11 @@ export function compute(
|
||||
if (dose.timestamp > t) continue;
|
||||
const dtH = (t - dose.timestamp) / 3600000;
|
||||
const tr = carriers.find((x) => x.id === dose.treatmentId);
|
||||
// Même calibration que la courbe ESE (fix v1.9.1)
|
||||
const scale = scalePerEster?.[doseEster(tr, dose)] ?? tr.scaleFactor;
|
||||
e2 += EstrannaiseModels.sampleWithParams(
|
||||
tr.esterType, dtH, paramsByEster.get(tr.esterType),
|
||||
) * dose.doseAmount;
|
||||
) * dose.doseAmount * scale;
|
||||
}
|
||||
curve.push({ timestamp: t, e2, t: 0.0 });
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
|
||||
import {
|
||||
computeCurve, generateForecastDoses, autoCalibrated, TConfig,
|
||||
computeLabAnchoredCurve, computeEstrannaiseCloud,
|
||||
computeLabAnchoredCurve, computeEstrannaiseCloud, DEFAULT_NB_CURVES,
|
||||
} from '../pk/index.js';
|
||||
import {
|
||||
stepForRange, defaultModelToggles, panDeltaHours, clampPanHours,
|
||||
@ -314,6 +314,7 @@ export function renderChart(container, ctx, { preserveState = false } = {}) {
|
||||
if (state.showCloud) {
|
||||
const cloud = computeEstrannaiseCloud(
|
||||
treatments, doseLogs, startMs, endMs, step, nowMs,
|
||||
DEFAULT_NB_CURVES, autoByModel.get('ESE')?.esterScales ?? null,
|
||||
);
|
||||
if (cloud.length > 0) curves.push(['CLOUD', cloud.flat()]);
|
||||
}
|
||||
|
||||
@ -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.0';
|
||||
export const WEB_VERSION = '1.9.1';
|
||||
|
||||
/** Parse un champ numérique FR (virgule → point) ; null si vide/invalide. */
|
||||
function parseNumOrNull(text) {
|
||||
|
||||
@ -131,7 +131,7 @@ async function main() {
|
||||
ok(dialog !== null, 'dialog « Nouveautés » au premier lancement (markdown rendu)');
|
||||
if (dialog) {
|
||||
const dlgText = await dialog.textContent();
|
||||
ok(dlgText.includes('Version 1.9.0'), 'dialog : section de la version courante');
|
||||
ok(dlgText.includes('Version 1.9.1'), 'dialog : section de la version courante');
|
||||
const hasRenderedMd = await dialog.$('.md-body h3, .md-body strong');
|
||||
ok(hasRenderedMd !== null, 'dialog : markdown RENDU (titres/gras), pas de brut');
|
||||
await page.click('.dialog .actions button');
|
||||
@ -334,7 +334,7 @@ async function main() {
|
||||
await page.waitForTimeout(300);
|
||||
text = await page.textContent('main.content');
|
||||
ok(text.includes('Langue'), 'Paramètres : carte langue');
|
||||
ok(text.includes('Version 1.9.0 (web)'), 'Paramètres : version affichée');
|
||||
ok(text.includes('Version 1.9.1 (web)'), 'Paramètres : version affichée');
|
||||
ok(text.includes('100 % local'), 'Paramètres : note vie privée');
|
||||
await page.screenshot({ path: join(SHOTS, 'e2e-settings.png') });
|
||||
|
||||
|
||||
@ -66,4 +66,20 @@ describe('EstrannaiseCloud web (miroir Android v1.9.0)', () => {
|
||||
test('nbCurves < 2 → nuage vide', () => {
|
||||
assert.equal(compute([ese()], doses(), NOW - 30 * DAY_MS, NOW, 3_600_000, NOW, 1).length, 0);
|
||||
});
|
||||
|
||||
test('le nuage suit la calibration (fix v1.9.1, miroir Android)', () => {
|
||||
// scalePerEster ×2 → nuage calibré = nuage brut ×2 (le nuage ENTOURE
|
||||
// la courbe ESE calibrée au lieu de flotter à une autre échelle)
|
||||
const scaled = { EEN: 2.0 };
|
||||
const calibrated = compute([ese()], doses(), NOW - 30 * DAY_MS, NOW, 3_600_000, NOW, 32, scaled);
|
||||
const raw = compute([ese()], doses(), NOW - 30 * DAY_MS, NOW, 3_600_000, NOW);
|
||||
assert.equal(calibrated.length, raw.length);
|
||||
assert.ok(raw[raw.length - 1].length >= 2);
|
||||
const rawLast = raw[raw.length - 1][raw[raw.length - 1].length - 1].e2;
|
||||
const calLast = calibrated[calibrated.length - 1][calibrated[calibrated.length - 1].length - 1].e2;
|
||||
assert.ok(
|
||||
Math.abs(calLast - rawLast * 2) <= rawLast * 0.01,
|
||||
`calibré (${calLast}) ≈ 2× brut (${rawLast})`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user