fix: recalibrate Neapolitan yeast for v1.1

This commit is contained in:
root
2026-07-26 08:52:58 +00:00
parent c0640068fc
commit 768102473c
3 changed files with 92 additions and 9 deletions
+78
View File
@@ -0,0 +1,78 @@
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const html = fs.readFileSync(path.join(__dirname, '..', 'index.html'), 'utf8');
function between(start, end) {
const from = html.indexOf(start);
const to = html.indexOf(end, from);
assert.ok(from >= 0 && to > from, `Quellbereich fehlt: ${start}${end}`);
return html.slice(from, to);
}
const core = between('const STYLES =', '/* ============================================================\n ZEITPLAN');
const simple = between('function applySimpleFermentation()', 'function initUI()');
const warnings = between('function renderWarnings(', 'function renderRecipe(');
const fixedNow = 1_700_000_000_000;
class FixedDate extends Date {
static now() { return fixedNow; }
}
const warningHost = { innerHTML: '' };
const sandbox = {
Date: FixedDate,
console,
$: id => id === 'warns' ? warningHost : {},
pct: x => `${x} %`,
n1: x => String(x),
};
vm.createContext(sandbox);
vm.runInContext(`${core}\n${simple}\n${warnings}\nglobalThis.api={STYLES,DEFAULT,calc,applySimpleFermentation,renderWarnings,setState:s=>S=s,getState:()=>S};`, sandbox);
const { STYLES, DEFAULT, calc, applySimpleFermentation, renderWarnings, setState, getState } = sandbox.api;
function state(overrides = {}) {
return { ...DEFAULT, pf: 'none', yeast: 'fresh', balls: 4, ball: 250, mode: 'w', ...overrides };
}
// Napoletana und Contemporanea werden bewusst niedriger kalibriert.
assert.equal(STYLES.napo.yeastK, 1.5);
assert.equal(STYLES.contemp.yeastK, 1.7);
// 8 h warme Napoletana bei 20 °C: rund 0,19 % Frischhefe statt bisher 0,76 %.
const napo8 = calc(state({ style: 'napo', seq: 'room', roomH: 8, roomT: 20, fridgeH: 0 }));
assert.ok(Math.abs(napo8.yPct - 0.190875) < 1e-9, `unerwartet: ${napo8.yPct}%`);
assert.ok(napo8.main.yeast > 1.1 && napo8.main.yeast < 1.2, `unerwartet: ${napo8.main.yeast} g`);
// Nicht neu kalibrierte Stile behalten den bisherigen Referenzfaktor 6.
const ny8 = calc(state({ style: 'ny', salt: 2, seq: 'room', roomH: 8, roomT: 20, fridgeH: 0 }));
assert.equal(ny8.yPct, 0.75);
// Lange Führung im Einfach-Modus endet mit 4 h warmer Stückgare.
setState(state({ uxMode: 'simple', bake: fixedNow + 24.75 * 3600e3 }));
applySimpleFermentation();
assert.equal(getState().seq, 'coldwarm');
assert.equal(getState().roomH, 4);
assert.equal(getState().fridgeH, 20);
setState(state({ uxMode: 'simple', bake: fixedNow + 48.75 * 3600e3 }));
applySimpleFermentation();
assert.equal(getState().roomH, 4);
assert.equal(getState().fridgeH, 44);
// Unter 4 h wird Napoletana ausdrücklich als Schnellteig gekennzeichnet.
const napo3State = state({ style: 'napo', seq: 'room', roomH: 3, roomT: 20, fridgeH: 0 });
setState(napo3State);
const napo3 = calc(napo3State);
renderWarnings({ n: 'Testmehl', reife: [0, 120], hyd: [50, 90], vt: true }, napo3);
assert.match(warningHost.innerHTML, /Schnellteig/);
// Kleine absolute Hefemengen nennen die Feinwaage, auch wenn der Prozentwert über 0,03 % liegt.
const napo24State = state({ style: 'napo', seq: 'room', roomH: 24, roomT: 20, fridgeH: 0 });
setState(napo24State);
const napo24 = calc(napo24State);
assert.ok(napo24.main.yeast < 0.5 && napo24.yPct > 0.03);
renderWarnings({ n: 'Testmehl', reife: [0, 120], hyd: [50, 90], vt: true }, napo24);
assert.match(warningHost.innerHTML, /Feinwaage/);
console.log('yeast calibration regression tests: OK');