Files
dogfather-universe/server/pruef-resuemee.mjs
T
DogFatherGitandClaude Opus 5 2719bd9b88 Wie geht es dir: achtzehn Fragen in sechs Feldern, eine Zusammenfassung
Sechs Fragen mehr (jetzt 18), und alle in sechs Feldern gegliedert:
Last & Erholung, Miteinander, Sinn & Anerkennung, Klarheit &
Rueckhalt, Grenzen & Sicherheit, Weiterkommen & Ausblick. Die Felder
sind nicht ausgedacht -- es sind die Gebiete, die die Forschung zu
belasteten Rollen durchgehend abfragt.

Die drei neuen Luecken, die sie schliessen: Schlaf (zeigt Erschoepfung
frueher als jede andere Frage), Widersprechen (der Kern dessen, was
psychologische Sicherheit heisst) und Mitreden (viel Anforderung bei
wenig Einfluss ist die Kombination, aus der Erschoepfung entsteht).
Jede neue Frage hat ihre belegte Einordnung -- eine Frage ohne
Grundlage ist eine Frage, die jemand gut fand.

DIE ROTATION MUSSTE NEU. Der Katalog ist nach Themen sortiert; wer
daraus drei aufeinanderfolgende nimmt, bekommt drei aus demselben
Feld. Jetzt wird der Ring vorher verzahnt -- erst je eine Frage aus
jedem Feld, dann je eine zweite. Ein Fenster trifft damit von selbst
verschiedene Felder.

Ein erster Anlauf reservierte feste Plaetze fuer die kernlosen
Felder. Begruendet mit einer Auswertung je Feld, die es gar nicht
gibt -- und er kostete: zwoelf Runden, bis jede Frage einmal gestellt
war, fast ein halbes Jahr. Gefunden hat das pruef-befinden.mjs, nicht
das Nachdenken. Mit dem verzahnten Ring und vier Plaetzen je Runde
sind es vier Runden.

DIE EINE ZUSAMMENFASSUNG AM ENDE, nicht eine je Kategorie. Sie sagt
in dieser Reihenfolge: die Lage in einem Satz, was traegt (benannt,
nicht als Zahl), hoechstens EINE Sache zum Hinschauen, ein Schluss,
der zur Lage passt. Umgekehrt waere es eine Maengelliste mit
Trostpflaster, und so etwas beantwortet man beim naechsten Mal nicht
mehr ehrlich.

ZUR FRAGE NACH DER BESTEN KOSTENLOSEN MOEGLICHKEIT: bewusst KEINE
KI. Das sind Gesundheitsdaten (wer nachts wach liegt, wer angefeindet
wird, wer ans Aufhoeren denkt) -- deshalb sind sie im ganzen Haus als
nurSelbst markiert. Kostenlose KI-Angebote bezahlt man mit den Daten.
Ein Sprachmodell erfindet ausserdem, und zwar ueberzeugend. Und ein
fremder Dienst faellt aus. Jeder Grund reicht einzeln. Die
Begruendung steht ausfuehrlich in workspace-befinden-resuemee.js.

Nebenbefund behoben: Die Zusammenfassung blieb direkt NACH einer
abgeschlossenen Runde leer -- da ist keine Antwort mehr "frisch".
Genau der Moment, in dem man sie sehen will.

Auch eine Pruefung korrigiert: Sie verbot den Text "entwicklung.js"
irgendwo in der Datei und schlug an einem Kommentar an, der die
Serverdatei benennt. Sie misst jetzt die Skript-Quellen.

pruef-befinden.mjs 112/0, pruef-resuemee.mjs 35/0 (neu).

Offener Befund, aelter als diese Aenderung: pruef-entwicklung.mjs
meldet "ein Modi: keine Kachel fuehrt mehr ersatzweise auf die
Entwicklungsseite" -- auch ohne diese Commits.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-20 19:45:14 +02:00

182 lines
9.2 KiB
JavaScript

/* =====================================================================
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);