ed2532b991
scripts/web/ — motore in JavaScript (engine.js) sui ritorni VERI del book esportati da export_series.py, pagine assemblate da build.py. Da "sistema dinamico dove imposti valore iniziale, mensile e durata, e calcola la best curva fissando il tempo": la simulazione gira nel browser, quindi il motore va riscritto e VA PROVATO che dia la stessa risposta. test_engine.js confronta mediane e probabilita' con r0807_growth_yearly.py su due configurazioni, esige che il versato (deterministico) coincida al centesimo, e include un controllo positivo — un motore col fisco spento DEVE risultare fuori tolleranza, perche' un test che non sa fallire non e' un test. Il risolutore e' validato contro dep_necessario di r0727_tasse.py: -0.4 / -0.6 / -1.1%. smoke.js ESEGUE le pagine con un DOM finto. Serve perche' node --check valida solo la sintassi: ho pubblicato una pagina che lo passava e moriva alla prima riga utile (chiavi Python "0.0" ricostruite in JS come String(0) = "0"; i pesi intermedi funzionavano per caso). Altri due errori che questi strumenti hanno intercettato: - due anni di dati di un grafico scritti A MEMORIA perche' tail aveva troncato l'output -> ora i dati si INIETTANO da JSON (build.py), il passaggio manuale non esiste piu'; - una "distorsione sistematica" del motore JS (+0.75%, 8 semi tutti positivi) che erano 8 estrazioni contro UN punto Python rumoroso. Misurato bene, 8 semi per parte: -0.01%, t = -0.04; ed entrambi i campionatori cadono entro 1.7 SE dall'atteso ANALITICO della media di blocco. Scelta guidata da una misura: la banda del versamento suggerito resta +-1% da 1.200 a 3.000 percorsi -> non domina il Monte Carlo ma la granularita' della bisezione (~5 EUR). Percorsi tenuti bassi e incertezza dichiarata, invece di pagare tempo per una precisione che non arriva. Nessun impatto sulla produzione: non tocca book, pesi, cron o config. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
85 lines
3.3 KiB
JavaScript
85 lines
3.3 KiB
JavaScript
/* smoke.js — esegue DAVVERO lo script di una pagina, con un DOM finto.
|
|
*
|
|
* Perche' serve: `node --check` valida la SINTASSI e non tocca l'esecuzione. Ho pubblicato una
|
|
* pagina che passava il controllo di sintassi e moriva alla prima riga utile, perche' le chiavi
|
|
* dei pesi arrivavano da Python come "0.0"/"1.0" e la pagina le ricostruiva con String(0) -> "0".
|
|
* Un test che non esegue non vede niente di tutto cio'.
|
|
*
|
|
* node smoke.js pagina.html [...]
|
|
*/
|
|
const fs = require("fs");
|
|
|
|
function makeEl(tag) {
|
|
const el = {
|
|
tagName: tag, _html: "", textContent: "", value: "", dataset: {},
|
|
style: new Proxy({}, { get: () => "", set: () => true }),
|
|
children: [], attributes: {}, min: "0", max: "1000000",
|
|
setAttribute(k, v) { this.attributes[k] = v; },
|
|
getAttribute(k) { return this.attributes[k]; },
|
|
appendChild(c) { this.children.push(c); return c; },
|
|
addEventListener() {},
|
|
removeEventListener() {},
|
|
getBoundingClientRect() { return { left: 0, top: 0, width: 100, height: 40, right: 100, bottom: 40 }; },
|
|
insertAdjacentHTML(pos, html) { this._html += html; },
|
|
querySelectorAll() { return { forEach() {} }; },
|
|
querySelector() { return makeEl("div"); },
|
|
contains() { return false; },
|
|
focus() {}
|
|
};
|
|
Object.defineProperty(el, "innerHTML", {
|
|
get() { return this._html; },
|
|
set(v) { if (v === undefined || String(v).includes("undefined")) el._undef = true; this._html = String(v); }
|
|
});
|
|
return el;
|
|
}
|
|
|
|
const known = new Map();
|
|
global.document = {
|
|
_byId: known,
|
|
getElementById(id) {
|
|
if (!known.has(id)) known.set(id, makeEl("div"));
|
|
return known.get(id);
|
|
},
|
|
createElementNS(ns, tag) { return makeEl(tag); },
|
|
createElement(tag) { return makeEl(tag); },
|
|
querySelectorAll() { return { forEach() {} }; },
|
|
addEventListener() {}
|
|
};
|
|
global.window = { innerWidth: 1400, innerHeight: 900, addEventListener() {} };
|
|
global.requestAnimationFrame = fn => { /* una sola passata: basta per vedere se esplode */ };
|
|
global.setTimeout = (fn, ms) => { try { fn(); } catch (e) { throw e; } return 0; };
|
|
global.clearTimeout = () => {};
|
|
|
|
let bad = 0;
|
|
for (const file of process.argv.slice(2)) {
|
|
known.clear();
|
|
const html = fs.readFileSync(file, "utf8");
|
|
const blocchi = [...html.matchAll(/<script(?:\s[^>]*)?>([\s\S]*?)<\/script>/g)];
|
|
/* i blocchi type="application/json" sono DATI: il DOM finto deve restituirli */
|
|
for (const m of blocchi) {
|
|
const tagAttr = m[0].slice(0, m[0].indexOf(">"));
|
|
const idm = tagAttr.match(/id="([^"]+)"/);
|
|
if (tagAttr.includes("application/json") && idm) {
|
|
const el = document.getElementById(idm[1]);
|
|
el.textContent = m[1];
|
|
}
|
|
}
|
|
const code = blocchi.filter(m => !m[0].includes("application/json")).map(m => m[1]).join("\n");
|
|
try {
|
|
new Function(code)();
|
|
/* un id mai riempito, o riempito con "undefined", e' un difetto silenzioso */
|
|
const vuoti = [...known.entries()].filter(([, e]) => e._undef);
|
|
if (vuoti.length) {
|
|
console.log("⚠ " + file + " — gira, ma scrive 'undefined' in: " +
|
|
vuoti.map(([k]) => k).join(", "));
|
|
bad++;
|
|
} else {
|
|
console.log("✓ " + file + " — esegue senza errori (" + known.size + " elementi toccati)");
|
|
}
|
|
} catch (e) {
|
|
console.log("✗ " + file + " — " + e.constructor.name + ": " + e.message);
|
|
bad++;
|
|
}
|
|
}
|
|
process.exit(bad ? 1 : 0);
|