Auf der Startseite von Creator, rechter Hand und Modis ist der Ring das
groesste Element, ganz oben. Er rechnete:
prozent = (aktuelle Stunde - 6) / 18
...und nannte das "Tag geschafft". Um 9 Uhr also 17 %, um 23 Uhr 94 %
-- unabhaengig davon, ob jemand etwas getan hatte.
Aufgefallen auf einem Bildschirmfoto: Bei einer Modi stand "0 % TAG
GESCHAFFT", waehrend sie ueberhaupt nichts offen hatte. Beide Angaben
waren fuer sich richtig und zusammen Unsinn -- und es war das Erste,
was sie sah.
JETZT ZAEHLT ER ARBEIT. Nenner ist, was heute auf dem Tisch liegt:
heute faellig ODER laenger offen. Die naheliegende Rechnung ueber "nur
heute faellig" waere derselbe Fehler noch einmal gewesen -- bei drei
ueberfaelligen Sachen haette der Ring "nichts faellig" gemeldet.
Zaehler ist, was HEUTE fertig wurde (erledigt_am), nicht was irgendwann
abgehakt wurde. Liegt nichts an: voller Ring, und der Titel sagt es in
Worten statt nur eine 100 zu zeigen.
Gemessen danach, dieselbe Seite: "33 % Heute geschafft", darunter
"2 Sachen sind ueberfaellig -- das zuerst". Die Seite erzaehlt jetzt
eine Geschichte statt zweier.
NEU: pruef-zentrale-ring (10 Pruefungen)
Drei Lagen, drei Menschen: etwas anliegend und teils fertig / nichts
anliegend / drei ueberfaellige und nichts fertig. Dazu zwei
Abgrenzungen (morgen faellig und vorgestern erledigt duerfen nicht
mitzaehlen) und die Gegenprobe: zwischen zwei Abfragen wird eine
Aufgabe fertig gemacht, der Ring MUSS springen -- er tut es, 0 % -> 33 %.
Der letzte Abschnitt rechnet die alte Uhrzeit-Formel mit und meldet,
falls der Ring je wieder auf ihren Wert faellt.
NEU: mess-startseite -- ein Messwerkzeug, kein Pruefwerkzeug
Es sagt nicht richtig/falsch, sondern wie viel da ist: Seitenlaenge in
Handy-Bildschirmen, Bedienelemente, Woerter, Ring, je Rolle, mit
echten Aufgaben statt im leeren Zustand. Gemessener Stand:
admin 7,0 Schirme / 58 Bedienelemente hand 5,7 / 43
modi 5,0 / 37 manager 4,9 / 38
scout 4,5 / 34 creator 4,3 / 32
pruef-sicht, pruef-verborgen, pruef-modi-verborgen und
pruef-haus-trennung laufen unveraendert durch -- die neue Abfrage
folgt der fremden Sicht ueber dieselbe Variable wie der Rest.
Co-Authored-By: Claude Opus 5 <[email protected]>
221 lines
9.9 KiB
JavaScript
221 lines
9.9 KiB
JavaScript
/* =====================================================================
|
|
DER RING OBEN ZEIGT ARBEIT, NICHT DIE UHRZEIT (17.09.2026)
|
|
|
|
Auf der Startseite von Creator, rechter Hand und Modis steht das
|
|
groesste Element ganz oben: ein Ring mit einer Prozentzahl und dem
|
|
Wort darunter. Bis heute rechnete er:
|
|
|
|
prozent = (aktuelle Stunde - 6) / 18
|
|
|
|
...und nannte das "Tag geschafft". Um 9 Uhr also 17 %, um 23 Uhr
|
|
94 % -- voellig unabhaengig davon, ob jemand etwas getan hatte.
|
|
|
|
AUFGEFALLEN IST ES AUF EINEM BILDSCHIRMFOTO: Bei einem Modi stand
|
|
"0 % TAG GESCHAFFT", waehrend sie ueberhaupt nichts offen hatte.
|
|
Jede der beiden Angaben war fuer sich richtig. Zusammen waren sie
|
|
Unsinn -- und es war das Erste, was sie morgens sah.
|
|
|
|
---------------------------------------------------------------------
|
|
WAS DIESE PRUEFUNG SICHERT
|
|
|
|
Drei Faelle, und der dritte ist der, an dem so etwas wieder
|
|
kaputtgeht:
|
|
|
|
1. Es liegt etwas an, ein Teil ist fertig -> ein Anteil
|
|
2. Es liegt NICHTS an -> voll, und es steht da
|
|
3. Es liegt etwas an, NICHTS ist fertig -> null
|
|
|
|
Fall 3 ist der wichtige. Die naheliegende Rechnung waere "was ist
|
|
heute faellig" -- dann meldete der Ring bei drei UEBERFAELLIGEN
|
|
Sachen "nichts faellig, alles gut". Das waere derselbe Fehler noch
|
|
einmal, nur mit anderen Zahlen. Gezaehlt wird deshalb, was heute auf
|
|
dem Tisch liegt: faellig ODER laenger offen.
|
|
|
|
UND DIE GEGENPROBE: Zwischen zwei Abfragen wird eine Aufgabe fertig
|
|
gemacht. Der Ring MUSS sich bewegen. Ohne diesen Teil koennte er
|
|
dauerhaft dieselbe Zahl liefern und alle Faelle oben bestehen.
|
|
|
|
Aufrufen mit: node server/pruef-zentrale-ring.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { portMussFreiSein } from "./helfer-port.mjs";
|
|
import { heuteLokal, tagLokal } from "./helfer-tag.mjs";
|
|
|
|
const PORT = await portMussFreiSein(4473, "pruef-zentrale-ring");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-ring-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
process.env.PORT = String(PORT);
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
|
|
|
const express = (await import("express")).default;
|
|
const ec = express.response.cookie;
|
|
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
await import("./index.js");
|
|
notbremse(180_000, "pruef-zentrale-ring");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
const BASIS = `http://127.0.0.1:${PORT}`;
|
|
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
|
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const melde = (t) => console.log(t);
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
const schluessel = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", schluessel, jetzt);
|
|
|
|
function anlegen(name, rolle, code) {
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync(code, salt, 64,
|
|
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
|
|
d.prepare("INSERT INTO personen"
|
|
+ " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)"
|
|
+ " VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768,
|
|
createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
|
|
/* Drei Menschen mit drei Lagen -- getrennt, damit sich die Faelle nicht
|
|
gegenseitig beeinflussen. Ein einziger Mensch, bei dem zwischendurch
|
|
Zeilen geloescht werden, misst am Ende den Loeschvorgang mit. */
|
|
const idTut = anlegen("Marina", "modi", "CODE-TUT-00001");
|
|
const idLeer = anlegen("Nane", "modi", "CODE-LEER-0001");
|
|
const idSaeu = anlegen("Rieke", "modi", "CODE-SAEU-0001");
|
|
|
|
const setzen = (personId, status, frist, erledigtAm = null) =>
|
|
d.prepare(`INSERT INTO aufgaben (titel, status, verantwortlich_id, frist, erledigt_am, erstellt)
|
|
VALUES (?,?,?,?,?,?)`)
|
|
.run("Sache", status, personId, frist, erledigtAm, jetzt);
|
|
|
|
/* 1. Etwas liegt an, ein Teil ist fertig: 2 offen (eins ueberfaellig,
|
|
eins heute) + 1 heute erledigt -> 1 von 3 -> 33 % */
|
|
setzen(idTut, "offen", tagLokal(-2));
|
|
setzen(idTut, "offen", heuteLokal());
|
|
setzen(idTut, "erledigt", heuteLokal(), jetzt);
|
|
/* Und Dinge, die NICHT mitzaehlen duerfen: morgen faellig, und etwas,
|
|
das vorgestern fertig wurde. Ohne sie waere nicht geprueft, dass der
|
|
Ring den TAG meint und nicht die Gesamtlage. */
|
|
setzen(idTut, "offen", tagLokal(3));
|
|
setzen(idTut, "erledigt", tagLokal(-9), new Date(Date.now() - 2 * 86400_000).toISOString());
|
|
|
|
/* 2. Es liegt nichts an -- nur etwas in der Zukunft. */
|
|
setzen(idLeer, "offen", tagLokal(5));
|
|
|
|
/* 3. Drei UEBERFAELLIGE, nichts fertig. Der Fall, an dem eine Rechnung
|
|
ueber "heute faellig" faelschlich "alles gut" melden wuerde. */
|
|
setzen(idSaeu, "offen", tagLokal(-4));
|
|
setzen(idSaeu, "offen", tagLokal(-3));
|
|
setzen(idSaeu, "arbeit", tagLokal(-1));
|
|
d.close();
|
|
|
|
async function ring(code) {
|
|
const an = await fetch(BASIS + "/workspace/api/anmelden", {
|
|
method: "POST", headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ rolle: "creator", code }),
|
|
});
|
|
const keks = (an.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
|
|
const a = await fetch(BASIS + "/workspace/api/zentrale", { headers: { cookie: keks } });
|
|
return a.json();
|
|
}
|
|
|
|
/* =======================================================================
|
|
1. ES LIEGT ETWAS AN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Etwas liegt an, ein Teil ist fertig ===");
|
|
{
|
|
const r = await ring("CODE-TUT-00001");
|
|
ok(r.titel === "Heute geschafft", `der Ring heisst "${r.titel}"`);
|
|
ok(r.prozent === 33, `und steht auf ${r.prozent} % (1 von 3)`);
|
|
/* DIE ENTSCHEIDENDE ABGRENZUNG: Ohne diese Zeile koennte der Ring
|
|
alle fuenf Zeilen zaehlen und zufaellig auch auf eine plausible
|
|
Zahl kommen. */
|
|
ok(r.prozent !== 20 && r.prozent !== 40,
|
|
"morgen und vorgestern zaehlen nicht mit");
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. ES LIEGT NICHTS AN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Es liegt nichts an ===");
|
|
{
|
|
const r = await ring("CODE-LEER-0001");
|
|
ok(r.prozent === 100, `voller Ring (${r.prozent} %)`);
|
|
/* UND ES STEHT IN WORTEN DA. Ein voller Ring ohne Erklaerung waere
|
|
wieder eine Zahl, bei der man raten muss -- genau der Fehler, der
|
|
hier repariert wurde. */
|
|
ok(r.titel === "Heute nichts offen", `und er sagt es: "${r.titel}"`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. ÜBERFÄLLIG IST NICHT "NICHTS"
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Drei ueberfaellige Sachen ===");
|
|
{
|
|
const r = await ring("CODE-SAEU-0001");
|
|
ok(r.prozent === 0, `leerer Ring (${r.prozent} %)`);
|
|
ok(r.titel === "Heute geschafft",
|
|
`und NICHT "nichts offen" -- ueberfaellig liegt an ("${r.titel}")`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. GEGENPROBE: BEWEGT ER SICH?
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Die Gegenprobe ===");
|
|
{
|
|
const vorher = await ring("CODE-SAEU-0001");
|
|
const e = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
/* SQLite kennt ORDER BY im UPDATE nicht (ohne eigenes Uebersetzen).
|
|
Deshalb die Zeile ueber eine Unterabfrage auswaehlen. */
|
|
e.prepare(`UPDATE aufgaben SET status = 'erledigt', erledigt_am = ?
|
|
WHERE id = (SELECT id FROM aufgaben
|
|
WHERE verantwortlich_id = ? AND status = 'offen'
|
|
ORDER BY id LIMIT 1)`).run(jetzt, idSaeu);
|
|
e.close();
|
|
const nachher = await ring("CODE-SAEU-0001");
|
|
/* OHNE DIESE ZEILE beweisen die drei Abschnitte darueber nichts: Ein
|
|
Ring, der immer dieselbe Zahl liefert, koennte sie alle bestehen,
|
|
solange die Zahl zufaellig passt. */
|
|
ok(nachher.prozent > vorher.prozent,
|
|
`eine Aufgabe fertig gemacht: ${vorher.prozent} % -> ${nachher.prozent} %`);
|
|
ok(nachher.prozent === 33, `und zwar auf 1 von 3 (${nachher.prozent} %)`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. DIE UHRZEIT SPIELT KEINE ROLLE MEHR
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 5. Was der Ring NICHT mehr ist ===");
|
|
{
|
|
/* Die alte Rechnung, wortwoertlich, mit der jetzigen Stunde. Sie
|
|
steht hier, damit auffaellt, wenn jemand sie zurueckholt: Der Ring
|
|
darf nicht zufaellig wieder auf ihren Wert fallen. */
|
|
const alt = Math.round((Math.min(Math.max(new Date().getHours() - 6, 0), 18) / 18) * 100);
|
|
const r = await ring("CODE-LEER-0001");
|
|
ok(r.prozent === 100 && (alt !== 100 || new Date().getHours() >= 24),
|
|
`die alte Uhrzeit-Rechnung ergaebe jetzt ${alt} %, der Ring zeigt ${r.prozent} %`);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
melde("");
|
|
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
|
process.exit(fehler ? 1 : 0);
|