HormoneTrack-web/tests/whsah-models.test.js
Siphonight 491efef8eb v1.4.10 — portage initial de l'app Android v1.4.10
Portage navigateur complet de l'app Android HormoneTrack v1.4.10 :
- moteur PK fidèle (Estrannaise tables ODS, Transfem Science V3C,
  WHSAH fit Mona, Bateman) — paramètres identiques, invariants Android
  préservés (fixes #19-#23, #35, #52-#62), 121 tests Node épinglés
- calibration par période d'ester et par modèle affiché (#60/#61)
- UI 6 écrans + éditeur, CurveChart Canvas (zoom/pan fractionnaire,
  prévision, pics/creux, fuseau configurable), rappels web
  (Notification API), seuils d'alerte, i18n FR/EN
- sauvegarde JSON v2 compatible Android bidirectionnelle (rétrocompat v1)
- 100 % local : localStorage, aucun serveur applicatif, aucune télémétrie
- processus : scripts/check.sh (syntaxe, i18n, tests, E2E navigateur,
  smoke HTTP), docs séparées (README + DEVELOPPEMENT + CHANGELOG)
- dépôt GIT SÉPARÉ de l'Android : historique 100 % propre, versions
  alignées sur l'Android porté, changelogs indépendants

Non porté (impossible dans un navigateur, documenté §12) : agenda
récurrent, notifications onglet fermé, montre.
2026-09-08 14:34:58 +02:00

66 lines
2.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Fidélité du modèle WHSAH au comportement de Mona
* (miroir de WhsahModelsTest.kt — fit « license-free » du WHSAH Collective).
*
* Valeurs épinglées (±2 %) : EV Cmax 367,5 @ 1,69 j · EEn 187,9 @ 4,99 j ·
* t½ EEn 7,34 j · PEP NON COUVERT · linéarité par mg.
*/
import { test, describe } from 'node:test';
import assert from 'node:assert/strict';
import { assertClose } from './helpers.js';
import * as WHS from '../js/pk/whsah-models.js';
function findPeak(ester, doseMg, maxDays) {
let best = { v: 0, tDays: 0 };
for (let h = 0; h <= maxDays * 24; h += 0.25) {
const v = WHS.sample(ester, h) * doseMg;
if (v > best.v) best = { v, tDays: h / 24 };
}
return best;
}
describe('WhsahModels (fit Mona, license-free)', () => {
test('pics Mona : EV 367,5 @ 1,69 j (5 mg) · EEn 187,9 @ 4,99 j (5 mg)', () => {
const ev = findPeak('EV', 5, 10);
assertClose(ev.v, 367.5, 0.02, 'pic EV WHSAH 5 mg');
assertClose(ev.tDays, 1.69, 0.1, 'Tmax EV WHSAH');
const een = findPeak('EEN', 5, 20);
assertClose(een.v, 187.9, 0.02, 'pic EEn WHSAH 5 mg');
assertClose(een.tDays, 4.99, 0.15, 'Tmax EEn WHSAH');
});
test('t½ terminale EEn ≈ 7,34 j (plus longue que TFS 4,5 j)', () => {
assertClose(WHS.model('EEN').terminalHalfLifeDays, 7.34, 0.05);
});
test('EEn à J+1 ~3× plus haut que TFS (70 vs 22 pg/mL) — la « montée rapide » WHSAH', () => {
// Sémantique épinglée côté Android (différence visible entre les apps)
const eenJ1 = WHS.sample('EEN', 24) * 5;
assertClose(eenJ1, 70, 0.15, 'EEn WHSAH à J+1 (5 mg)');
});
test('PEP non couvert → 0 (retiré du fit WHSAH)', () => {
assert.equal(WHS.sample('PEP', 100), 0);
assert.equal(WHS.hasModel('PEP'), false);
});
test('linéarité par mg', () => {
const a = WHS.sample('EV', 40);
assertClose(a * 3, WHS.sample('EV', 40) * 3, 1e-12);
});
test('6 esters couverts (sans PEP) — choicesForModel en dépend', () => {
for (const e of ['EV', 'EU', 'EEN', 'EB', 'EC', 'ECS']) {
assert.ok(WHS.hasModel(e), `${e} devrait être couvert`);
}
});
test('dt ≤ 0 → 0 ; ester inconnu → 0', () => {
assert.equal(WHS.sample('EV', 0), 0);
assert.equal(WHS.sample('EV', -3), 0);
assert.equal(WHS.sample('XXX', 40), 0);
});
});