/* ===================================================================== pruef-resuemee.mjs — Die eine Zusammenfassung am Ende Filipe, 20.09.2026: „am ende von allen fragen will ich eine gesamte zusammenfassung, nicht bei jeder frage oder kategorie eine, sondern EINE gesamte am ende. eine motivierende zusammenfassung für die person." --------------------------------------------------------------------- WAS HIER GEPRÜFT WIRD, IST NICHT „steht ein Text da" Sondern die eine Eigenschaft, an der so ein Text scheitert: ER DARF NICHT LÜGEN, UM ZU MOTIVIEREN. Eine Zusammenfassung, die bei sechs von acht „da hakt es" fröhlich bleibt, ist keine Ermutigung, sondern Hohn — und beim nächsten Mal antwortet man ihr nicht mehr ehrlich. Genau das ist mit einem Sprachmodell die wahrscheinlichste Fehlerart, und genau deshalb rechnet das hier ein Programm. Die Prüfungen gehen deshalb paarweise: einmal die gute Lage, einmal die schwere — und der Text MUSS sich unterscheiden. Ein Text, der in beiden Fällen gleich klingt, besteht jede Prüfung, die nur nach „ist ein Satz da" fragt. AUFRUF: node server/pruef-resuemee.mjs ===================================================================== */ import { resuemeeVon } from "./workspace-befinden-resuemee.js"; import { ENTWICKLUNG_PUNKTE, BEFINDEN_FELDER } from "./workspace-entwicklungs-punkte.js"; let fehler = 0, geprueft = 0; const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; console.log("\n=== Die eine Zusammenfassung ===\n"); const punkte = ENTWICKLUNG_PUNKTE.befinden; ok(punkte.length >= 12, `es gibt genug Fragen zum Zusammenfassen (${punkte.length})`); ok(punkte.every((p) => !!p.feld), `jede Frage gehört zu einem Feld (${punkte.filter((p) => !p.feld).length} ohne)`); /* JEDES FELD KOMMT VOR. Ein Feld ohne Fragen wäre eine Überschrift über nichts -- und in der Zusammenfassung ein Thema, das nie gewählt werden kann. */ for (const f of BEFINDEN_FELDER) { const n = punkte.filter((p) => p.feld === f.schluessel).length; ok(n > 0, ` Feld '${f.name}' hat Fragen (${n})`); } /* GEGENPROBE IN DIE ANDERE RICHTUNG: kein verwaistes Feld an einer Frage, das es in der Liste nicht gibt. Ohne sie bliebe eine vertippte Feldkennung unbemerkt -- die Frage stünde dann unter „ohne Feld" am Ende, und niemand wüsste warum. */ const bekannt = new Set(BEFINDEN_FELDER.map((f) => f.schluessel)); const verwaist = punkte.filter((p) => p.feld && !bekannt.has(p.feld)); ok(verwaist.length === 0, `Gegenprobe: keine Frage zeigt auf ein Feld, das es nicht gibt (${verwaist.map((p) => p.feld).join(", ") || "keine"})`); const bau = (stufen) => { const a = {}; punkte.slice(0, stufen.length).forEach((p, i) => { a[p.schluessel] = stufen[i]; }); return a; }; const teil = (n, stufe) => Array(n).fill(stufe); /* ---- 1. DIE GUTE LAGE -------------------------------------------- */ const gut = resuemeeVon(bau(teil(8, "laeuft")), punkte.slice(0, 8), BEFINDEN_FELDER); ok(gut.moeglich === true, "bei acht Mal 'Läuft' gibt es eine Zusammenfassung"); ok(gut.thema === null, " und kein Thema zum Hinschauen — es gibt keins"); ok(gut.traegt.length > 0, ` aber etwas, das trägt (${gut.traegt.length})`); ok(gut.zahlen.hakt === 0 && gut.zahlen.traegt === 8, ` die Zahlen stimmen (${gut.zahlen.traegt} läuft, ${gut.zahlen.hakt} hakt)`); /* ---- 2. DIE SCHWERE LAGE ----------------------------------------- */ const schwer = resuemeeVon(bau(teil(6, "hakt").concat(teil(2, "laeuft"))), punkte.slice(0, 8), BEFINDEN_FELDER); ok(schwer.moeglich === true, "bei sechs von acht 'Da hakt es' auch"); ok(schwer.zahlen.hakt === 6, ` und sie zählt richtig (${schwer.zahlen.hakt})`); /* DIE ENTSCHEIDENDE PRÜFUNG. */ ok(gut.kopf !== schwer.kopf, "der Kopfsatz ist ein ANDERER als bei der guten Lage — er ist nicht für alle gleich"); ok(gut.schluss !== schwer.schluss, "und der Schlusssatz auch"); ok(/6 von 8/.test(schwer.kopf), `die schwere Lage wird BEIM NAMEN genannt ('${schwer.kopf.slice(0, 44)}…')`); ok(!/super|toll|prima|großartig|weiter so/i.test(schwer.kopf + schwer.schluss), "und nicht schöngeredet — kein 'super', kein 'weiter so'"); ok(/jemandem|allein|sagen/i.test(schwer.schluss), `der Schluss zeigt einen Weg statt Trost ('${schwer.schluss.slice(0, 50)}…')`); /* UND TROTZDEM NICHT NIEDERSCHMETTERND: Auch in der schweren Lage steht das, was trägt — sonst ist es eine Mängelliste. */ ok(schwer.traegt.length > 0, `auch hier steht, was trägt (${schwer.traegt.length}) — sonst wäre es eine Mängelliste`); /* ---- 3. HÖCHSTENS EIN THEMA -------------------------------------- */ ok(schwer.thema !== null, "bei sechs Haken gibt es ein Thema"); ok(typeof schwer.thema.name === "string" && schwer.thema.name.length > 2, ` und es hat einen Namen ('${schwer.thema?.name}')`); /* Wer fünf Probleme auf einmal vorgelegt bekommt, sieht keine fünf Aufgaben, sondern einen Berg. */ ok(!Array.isArray(schwer.thema.felder), "es ist EIN Feld, keine Liste — fünf Probleme auf einmal sind ein Berg"); /* DAS THEMA IST DAS FELD MIT DEN MEISTEN HAKEN, nicht das erste. */ { /* Drei Haken in EINEM Feld gegen einen in einem anderen. */ const zielFeld = BEFINDEN_FELDER.find((f) => punkte.filter((p) => p.feld === f.schluessel).length >= 3); const imFeld = punkte.filter((p) => p.feld === zielFeld.schluessel).slice(0, 3); const andere = punkte.find((p) => p.feld !== zielFeld.schluessel); const a = {}; for (const p of imFeld) a[p.schluessel] = "hakt"; a[andere.schluessel] = "hakt"; const r = resuemeeVon(a, [...imFeld, andere], BEFINDEN_FELDER); ok(r.thema?.feld === zielFeld.schluessel, `das Thema ist das Feld mit den meisten Haken ('${r.thema?.name}', ${r.thema?.anzahl})`); ok(r.thema?.anzahl === 3, ` und nennt, wie viele es dort sind (${r.thema?.anzahl})`); } /* ---- 4. „KANN ICH NICHT SAGEN" IST KEIN PROBLEM ------------------ */ /* Wer sie zu den Sorgen schlägt, erzeugt Sorgen, die niemand geäußert hat. */ const unklar = resuemeeVon(bau(teil(8, "unklar")), punkte.slice(0, 8), BEFINDEN_FELDER); ok(unklar.zahlen.hakt === 0 && unklar.zahlen.unklar === 8, `acht Mal 'Kann ich nicht sagen' ergibt null Haken (${unklar.zahlen.hakt})`); ok(unklar.thema === null, " und kein Thema — es wurde ja nichts gesagt"); ok(/ehrliche Antwort/i.test(unklar.kopf), ` der Satz nennt es eine ehrliche Antwort ('${unklar.kopf.slice(0, 40)}…')`); /* ---- 5. „HAT SICH GEBESSERT" NUR, WENN ES STIMMT ----------------- */ { const p3 = punkte.slice(0, 3); const vorher = { [p3[0].schluessel]: "hakt", [p3[1].schluessel]: "laeuft" }; const jetzt = { [p3[0].schluessel]: "laeuft", [p3[1].schluessel]: "laeuft", [p3[2].schluessel]: "laeuft" }; const r = resuemeeVon(jetzt, p3, BEFINDEN_FELDER, vorher); ok(r.zahlen.besser === 1, `genau eine echte Verbesserung wird gezählt (${r.zahlen.besser})`); /* DIE FALLE: Die dritte Frage war letztes Mal gar nicht dran. „War nicht dabei" ist KEINE Verbesserung -- seit die Fragen rotieren, wäre das sonst bei jeder frisch dazugekommenen Frage der Fall, und der Satz „drei Sachen haben sich gebessert" wäre erfunden. */ ok(!/2 Sachen|3 Sachen/.test(r.schluss), ` eine Frage, die letztes Mal nicht dran war, zählt nicht als Verbesserung ("${r.schluss.slice(0, 46)}…")`); /* GEGENPROBE: Wird es schlechter, steht dort KEINE Verbesserung. */ const rueck = resuemeeVon({ [p3[0].schluessel]: "hakt" }, [p3[0]], BEFINDEN_FELDER, { [p3[0].schluessel]: "laeuft" }); ok(rueck.zahlen.besser === 0, `Gegenprobe: eine Verschlechterung wird nicht als Besserung gezählt (${rueck.zahlen.besser})`); } /* ---- 6. DER DRITTE AUSGANG --------------------------------------- */ const leer = resuemeeVon({}, punkte.slice(0, 5), BEFINDEN_FELDER); ok(leer.moeglich === false, "ohne Antworten sagt sie, dass sie nichts sagen kann — statt freundlich über nichts zu reden"); ok(typeof leer.grund === "string" && leer.grund.length > 5, ` und nennt den Grund ('${leer.grund}')`); /* ---- 7. KEINE ERFUNDENEN ZAHLEN ---------------------------------- */ /* Jede Zahl im Text muss in `zahlen` wiederzufinden sein. Ein Satz, der „12 Fragen" sagt, während acht gestellt wurden, ist genau der Fehler, wegen dem hier kein Sprachmodell hängt. */ { const r = resuemeeVon(bau(teil(3, "hakt").concat(teil(5, "laeuft"))), punkte.slice(0, 8), BEFINDEN_FELDER); const text = [r.kopf, r.schluss, r.thema?.satz].filter(Boolean).join(" "); const zahlenImText = [...text.matchAll(/\b(\d+)\b/g)].map((m) => Number(m[1])); const erlaubt = new Set(Object.values(r.zahlen).filter((x) => typeof x === "number")); /* Auch Differenzen sind erlaubt -- „5 von 8 laufen" nennt 8 minus 3. */ for (const z of [...erlaubt]) for (const y of [...erlaubt]) erlaubt.add(Math.abs(z - y)); const erfunden = zahlenImText.filter((z) => !erlaubt.has(z)); ok(erfunden.length === 0, `jede Zahl im Text steht auch in den Zahlen (${erfunden.length} erfunden)`); console.log(` Text: '${r.kopf}'`); console.log(` Zahlen: ${JSON.stringify(r.zahlen)}`); } console.log(`\n${fehler ? fehler + " FEHLER" : "Alles in Ordnung"} — ${geprueft} Prüfungen\n`); process.exit(fehler ? 1 : 0);