Miroir fidèle du Kotlin (doc Android §7.12) :
- estrannaise-models.js : forme close 3C de estrannaise.js (paramètres
publiés EV/EU/EEn/EC/EB/EUCS), posterior MCMC (313 échantillons/ester,
asset mcmc_samples.json ≈ 48 Ko fetché au démarrage — remplace le fetch
des tables ODS, 550 Ko), t½ terminale analytique, mapping en objets
{d,k1,k2,k3} (miroir du Param Kotlin).
- estrannaise-cloud.js : nuage d'incertitude — 32 courbes du posterior
(échelonnées, déterministes) superposant les doses des porteurs ESE ;
exclusif ESE (TFS/WHSAH sans posterior publié), gardes (sans dose,
nbCurves<2, MCMC non chargé → vide).
- pk-engine.js : dispatch ESE → forme close (plus de fallback PKProfileStore
au runtime) ; cutoffHours ESE → 10 × t½ analytique ; lab-timing.js t½ ESE
analytique ; app.js fetch MCMC remplace le fetch des tables ODS
(démarrage plus rapide).
- chart.js/chart-canvas.js : chip « Nuage » (off, activable si ESE affiché,
coupure auto si ESE off), clé 'CLOUD' skippée des légendes (leçon #63),
nuage dessiné en alpha faible, légende dédiée ; seed démo + traitement
EV/ESE (2 doses) pour démontrer le nuage ; choicesForModel ESE=6.
- Tests : estrannaise-models.test.js (6 : fidélité RMS/pics, dégénérés,
MCMC 313×6, t½ analytique, gardes) + estrannaise-cloud.test.js (5 :
32 courbes à dispersion réelle, fenêtre, exclusivité ESE, gardes)
→ 166 verts + E2E (chip/légende/exclusivité nuage) + check --release.
- WEB_VERSION 1.9.0 (versions sync Android). CHANGELOG [1.9.0] web.
70 lines
2.7 KiB
JavaScript
70 lines
2.7 KiB
JavaScript
/**
|
|
* Tests du NUAGE D'INCERTITUDE (v1.9.0 — miroir de `EstrannaiseCloudTest.kt`,
|
|
* `js/pk/estrannaise-cloud.js`) : 32 courbes par défaut, dispersion réelle
|
|
* entre courbes, fenêtre respectée, exclusivité ESE (TFS/oral → vide).
|
|
*/
|
|
|
|
import { test, describe, before } from 'node:test';
|
|
import { join } from 'node:path';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { initProfiles, makeTreatment, makeDose, DAY_MS, WEB_ROOT } from './helpers.js';
|
|
import { compute } from '../js/pk/estrannaise-cloud.js';
|
|
import { initWithJson as initMcmc } from '../js/pk/estrannaise-models.js';
|
|
|
|
const NOW = 1_790_000_000_000;
|
|
|
|
function ese() {
|
|
return makeTreatment({
|
|
name: 'EEn ESE', type: 'ESTRADIOL', route: 'INJECTION_IM',
|
|
doseAmount: 5.0, doseUnit: 'mg', esterType: 'EEN', pkModel: 'ESE',
|
|
forecastIntervalDays: 7.0, isActive: true,
|
|
});
|
|
}
|
|
|
|
function doses() {
|
|
return [4, 3, 2, 1].map((k) => makeDose(NOW - k * DAY_MS, 5.0));
|
|
}
|
|
|
|
describe('EstrannaiseCloud web (miroir Android v1.9.0)', () => {
|
|
before(() => {
|
|
initProfiles();
|
|
// Le nuage exige le posterior MCMC chargé (313 échantillons/ester)
|
|
initMcmc(readFileSync(join(WEB_ROOT, 'assets/mcmc_samples.json'), 'utf8'));
|
|
});
|
|
|
|
test('32 courbes avec dispersion réelle entre elles', () => {
|
|
const cloud = compute([ese()], doses(), NOW - 30 * DAY_MS, NOW, 3_600_000, NOW);
|
|
assert.equal(cloud.length, 32);
|
|
assert.ok(cloud.every((c) => c.length >= 2));
|
|
const peaks = [...new Set(cloud.map((c) => Math.max(...c.map((p) => p.e2))))];
|
|
assert.ok(peaks.length >= 8, `les courbes divergent (${peaks.length} maxima distincts)`);
|
|
});
|
|
|
|
test('le nuage reste dans la fenêtre demandée', () => {
|
|
const start = NOW - 10 * DAY_MS;
|
|
const end = NOW;
|
|
const cloud = compute([ese()], doses(), start, end, 3_600_000, NOW);
|
|
assert.ok(cloud.every((c) => c.every((p) => p.timestamp >= start && p.timestamp <= end)));
|
|
});
|
|
|
|
test('exclusivité ESE : TFS et oral produisent un nuage vide', () => {
|
|
const tfs = { ...ese(), pkModel: 'TFS' };
|
|
assert.equal(compute([tfs], doses(), NOW - 30 * DAY_MS, NOW, 3_600_000, NOW).length, 0);
|
|
const oral = makeTreatment({
|
|
name: 'CPA', type: 'ANTI_ANDROGEN', route: 'ORAL',
|
|
doseAmount: 12.5, doseUnit: 'mg', esterType: 'NONE',
|
|
forecastIntervalDays: 2.0, isActive: true,
|
|
});
|
|
assert.equal(compute([oral], doses(), NOW - 30 * DAY_MS, NOW, 3_600_000, NOW).length, 0);
|
|
});
|
|
|
|
test('aucune dose sur le porteur → nuage vide', () => {
|
|
assert.equal(compute([ese()], [], NOW - 30 * DAY_MS, NOW, 3_600_000, NOW).length, 0);
|
|
});
|
|
|
|
test('nbCurves < 2 → nuage vide', () => {
|
|
assert.equal(compute([ese()], doses(), NOW - 30 * DAY_MS, NOW, 3_600_000, NOW, 1).length, 0);
|
|
});
|
|
});
|