diff --git a/server/pruef-befinden.mjs b/server/pruef-befinden.mjs index edf942b5..b7f2eb0a 100644 --- a/server/pruef-befinden.mjs +++ b/server/pruef-befinden.mjs @@ -213,12 +213,51 @@ melde("=== 2. Sechs Fragen, ein Rhythmus ==="); passiert, wenn der Versatz und die Zahl der Fragen einen gemeinsamen Teiler haben. */ { + /* DIE GRENZE WIRD GERECHNET, NICHT EINGETRAGEN (20.09.2026). + + Hier stand fest „6 Runden". Das war richtig, solange es sieben + wechselnde Fragen bei drei je Runde gab. Mit dreizehn Fragen ist + es nur noch Zufall, ob die Zahl passt -- und eine Pruefung, die + bei jedem Katalogzuwachs von Hand nachgestellt werden muss, wird + irgendwann nachgestellt, statt den Fehler zu zeigen. + + Der Anspruch ist stattdessen: Es darf nicht laenger dauern, als + es MUSS, plus eine Runde Luft. Weniger als ceil(wechselnde / + Plaetze) geht rechnerisch nicht; mehr als eine Runde darueber + heisst, dass die Auswahl sich im Kreis dreht. + + GENAU SO IST DER FEHLER AUFGEFALLEN, den diese Zeile gefunden + hat: Eine erste Fassung der feldbewussten Rotation brauchte + ZWOELF Runden statt vier -- fast ein halbes Jahr, bis jede Frage + einmal gestellt war. */ + const wechselnde = kat.ENTWICKLUNG_SELBST.filter((p) => !p.kern).length; + const mindestens = Math.ceil(wechselnde / modul.ZUSATZ_PRO_RUNDE); + const grenze = mindestens + 1; + const gesehen = new Set(); - for (let r = 0; r < 6; r++) { + let gebraucht = null; + for (let r = 0; r < grenze * 4; r++) { for (const p of modul.fragenFuer(idFrida, r).alle) gesehen.add(p.schluessel); + if (gebraucht === null && gesehen.size === kat.ENTWICKLUNG_SELBST.length) { + gebraucht = r + 1; + } } ok(gesehen.size === kat.ENTWICKLUNG_SELBST.length, - `über sechs Runden kommt jede der ${kat.ENTWICKLUNG_SELBST.length} Fragen dran (${gesehen.size})`); + `jede der ${kat.ENTWICKLUNG_SELBST.length} Fragen kommt überhaupt dran (${gesehen.size})`); + ok(gebraucht !== null && gebraucht <= grenze, + `und zwar in ${gebraucht} Runden — rechnerisch nötig sind ${mindestens}, erlaubt ${grenze}`); + + /* UND JEDE RUNDE STREUT ÜBER DIE FELDER. Vier Zusatzfragen aus + demselben Feld waeren kein Befinden mehr, sondern ein Interview + zu einem Thema -- genau das passiert, wenn der Ring dem nach + Themen sortierten Katalog folgt. */ + let engste = 99; + for (let r = 0; r < 12; r++) { + const felder = new Set(modul.fragenFuer(idFrida, r).zusatz.map((p) => p.feld)); + engste = Math.min(engste, felder.size); + } + ok(engste >= Math.min(3, modul.ZUSATZ_PRO_RUNDE), + `auch die engste Runde streut über mindestens 3 Felder (${engste})`); /* Und die Auswahl ist STABIL: derselbe Stand, dieselbe Liste. Zufall hiesse, dass bei jedem Neuladen etwas anderes dasteht. */ ok(modul.fragenFuer(idFrida, 2).alle.map((p) => p.schluessel).join() @@ -411,8 +450,32 @@ melde("=== 5. Eine Seite, ein Zweck ==="); ok(!/personen-block|meine-karte|abweichung/.test(b), "und keinen einzigen Abschnitt über andere Menschen"); - ok(/befinden\.js/.test(b) && !/entwicklung\.js/.test(b), - "sie lädt ihr eigenes Skript, nicht das der Entwicklungsseite"); + /* DIE SKRIPT-QUELLEN, NICHT DER ROHE TEXT (korrigiert 20.09.2026). + + Hier stand `!/entwicklung\.js/.test(b)` -- eine Textsuche ueber die + GANZE Datei. Die schlug an einem KOMMENTAR an, der voellig zu + Recht die Serverdatei `workspace-entwicklung.js` benennt. Die + Pruefung meldete damit einen Fehler an einer Stelle, an der nichts + kaputt war, und stand der eigentlichen Frage im Weg. + + Die eigentliche Frage lautet: Welche Dateien LAEDT die Seite? Die + steht in den `src`-Angaben und nirgends sonst. Dieselbe Lehre wie + bei pruef-schranke am 19.09.: Wer Text sucht, misst Text -- nicht + die Sache. */ + const quellen = [...b.matchAll(/]+src="([^"]+)"/g)] + /* Nur der Dateiname -- ohne Pfad und ohne Versionsstempel. Damit + braucht es hier kein Muster, das man falsch schreiben kann: Ein + Vergleich auf Gleichheit hat keine Randfaelle. (Der erste Anlauf + benutzte eine Wortgrenze in der Zeichenkette und bekam dadurch + ein echtes Steuerzeichen in die Datei -- die Pruefung verglich + danach gegen etwas, das es nirgends gibt, und die Gegenprobe war + still gruen. Genau die Sorte Pruefung, die nichts prueft.) */ + .map((m) => m[1].split("?")[0].split("/").pop()); + ok(quellen.length > 0, `die Seite laedt ueberhaupt Skripte (${quellen.length})`); + ok(quellen.includes("befinden.js"), + `sie laedt ihr eigenes Skript (${quellen.join(", ")})`); + ok(!quellen.includes("entwicklung.js"), + "und nicht das der Entwicklungsseite — zwei Stände wären zwei Wahrheiten"); } /* ======================================================================= @@ -766,6 +829,57 @@ melde("=== 8. Die Analyse im Browser ==="); ok(a.quellen.some((x) => /tiktok/i.test(x)), "darunter eine zu TikTok — danach hatte Filipe ausdrücklich gefragt"); ok(a.blank, "die Verweise öffnen ohne Zugriff aufs eigene Fenster (noopener)"); + + /* ---- DIE FELDER UND DIE EINE ZUSAMMENFASSUNG (20.09.2026) -------- + + Filipe: „viel mehr fragen aber kategorisiert … und am ende EINE + gesamte zusammenfassung." + + Gemessen wird hier NICHT, ob ein Text dasteht, sondern: + * dass die Fragen unter Überschriften stehen -- und zwar unter + MEHREREN, sonst ist es eine Überschrift über allem und keine + Gliederung; + * dass jede Frage unter einer steht und keine darüber hängt; + * dass die Zusammenfassung GENAU EINMAL vorkommt. Eine je + Kategorie war ausdrücklich nicht gewünscht, und eine Prüfung, + die nur "mindestens eine" verlangt, würde sechs durchlassen. */ + const g = await seite.evaluate(() => { + const felder = [...document.querySelectorAll("#fragen .e-feld")]; + const fragen = [...document.querySelectorAll("#fragen .e-punkt")]; + /* Wie viele Fragen stehen VOR der ersten Überschrift? Jede davon + hängt ohne Thema in der Luft. */ + const ersteFeldPos = felder.length + ? [...document.getElementById("fragen").children].indexOf(felder[0]) : -1; + const kinder = [...document.getElementById("fragen").children]; + const heimatlos = kinder.slice(0, ersteFeldPos < 0 ? kinder.length : ersteFeldPos) + .filter((k) => k.classList.contains("e-punkt")).length; + const fazit = document.getElementById("fazit-block"); + return { + felder: felder.length, + feldNamen: felder.map((f) => f.querySelector(".e-feld__wort")?.textContent.trim()), + fragen: fragen.length, + heimatlos, + fazitDa: fazit && fazit.hidden === false, + fazitAnzahl: document.querySelectorAll(".b-fazit").length, + kopf: document.getElementById("fazit-kopf")?.textContent.trim() || "", + traegt: document.querySelectorAll("#fazit-traegt-liste li").length, + schluss: document.getElementById("fazit-schluss")?.textContent.trim() || "", + zahlen: document.getElementById("fazit-zahlen")?.textContent.trim() || "", + }; + }); + ok(g.felder >= 3, + `die Fragen stehen unter ${g.felder} Feldüberschriften — nicht in einer Reihe`); + ok(g.heimatlos === 0, + `keine Frage hängt über der ersten Überschrift (${g.heimatlos})`); + ok(g.feldNamen.every((x) => x && x.length > 3), + `jede Überschrift hat einen Namen (${g.feldNamen.join(", ")})`); + ok(g.fazitDa === true, "die Zusammenfassung steht am Ende der Fragen"); + ok(g.fazitAnzahl === 1, + `und zwar GENAU EINMAL (${g.fazitAnzahl}) — eine je Kategorie war ausdrücklich nicht gewünscht`); + ok(g.kopf.length > 30, `mit einem ganzen Satz („${g.kopf.slice(0, 52)}…")`); + ok(g.schluss.length > 20, `und einem Schlusssatz („${g.schluss.slice(0, 46)}…")`); + ok(/\d+ Fragen aus \d+ Feldern/.test(g.zahlen), + `die Belegzahlen stehen darunter („${g.zahlen}")`); if (process.env.SCHIRM) { await seite.locator("#analyse-block").screenshot( { path: "C:/Users/qciga/Documents/Obelix/DogiHompage/blick.png" }); diff --git a/server/pruef-resuemee.mjs b/server/pruef-resuemee.mjs new file mode 100644 index 00000000..e0d4d83a --- /dev/null +++ b/server/pruef-resuemee.mjs @@ -0,0 +1,181 @@ +/* ===================================================================== + 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); diff --git a/server/workspace-befinden-resuemee.js b/server/workspace-befinden-resuemee.js new file mode 100644 index 00000000..4948582f --- /dev/null +++ b/server/workspace-befinden-resuemee.js @@ -0,0 +1,256 @@ +/* ===================================================================== + workspace-befinden-resuemee.js — Eine Zusammenfassung am Ende + (20.09.2026) + + Filipe: „und 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. hol die beste der beste möglichkeiten dafür die man beutzen + kann kostenlos." + + --------------------------------------------------------------------- + DIE FRAGE NACH DER „BESTEN KOSTENLOSEN MÖGLICHKEIT" — EHRLICH + BEANTWORTET + + Naheliegend wäre eine der kostenlosen KI-Schnittstellen (Groq, + Gemini, HuggingFace). Sie wäre hier die FALSCHE Wahl, und zwar aus + drei Gründen, von denen jeder einzelne reicht: + + 1. ES SIND GESUNDHEITSDATEN. Die Antworten sagen, wer nachts wach + liegt, wer angefeindet wird, wer ans Aufhören denkt. Genau + deshalb sind sie im ganzen Haus als `nurSelbst` markiert und + liegen in einer eigenen Tabelle, die kein anderer Weg liest. + Sie an einen fremden Dienst zu schicken, damit er einen netten + Satz daraus baut, würde diese ganze Sorgfalt in einer Zeile + aufheben. Kostenlose KI-Angebote bezahlt man mit den Daten — + in ihren Bedingungen steht meist ausdrücklich, dass Eingaben + zum Training verwendet werden dürfen. + + 2. EIN SPRACHMODELL ERFINDET. Es schreibt ohne Weiteres „du hast + dich diesen Monat deutlich erholt", wenn das Gegenteil dasteht, + und es klingt dabei überzeugend. Eine Zusammenfassung über das + eigene Befinden, die etwas Falsches behauptet, ist schlimmer + als keine: Man glaubt ihr ja, und beim nächsten Mal glaubt man + ihr nicht mehr — auch dann nicht, wenn sie recht hat. + + 3. SIE FÄLLT AUS. Ein fremder Dienst hat Störungen, Kontingente + und Kündigungen. Dieser Text hier funktioniert in zehn Jahren + noch, ohne Schlüssel, ohne Rechnung, ohne Netz. + + DIE BESTE KOSTENLOSE MÖGLICHKEIT IST DESHALB KEINE KI, SONDERN + SORGFÄLTIG GESCHRIEBENE SPRACHE ÜBER GERECHNETEN ZAHLEN. Sie läuft + auf Filipes eigenem Server, kostet nichts, lügt nie und kann nicht + ausfallen. Was ein Sprachmodell hier besser könnte — Abwechslung im + Wortlaut — ist gegen die drei Punkte oben kein Argument. + + --------------------------------------------------------------------- + WIE MAN MOTIVIERT, OHNE ZU LÜGEN + + Das ist die eigentliche Schwierigkeit. 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. Drei Regeln, und sie gelten in jeder Lage: + + ZUERST, WAS TRÄGT — und zwar benannt, nicht als Zahl. „Dass du + nach der Schicht abschalten kannst, ist das, was das hier + zusammenhält" sagt etwas. „5 von 8 grün" sagt nichts. + + DANN HÖCHSTENS EINE SACHE. Wer fünf Probleme auf einmal vorgelegt + bekommt, sieht keine fünf Aufgaben, sondern einen Berg. Die Liste + aller Punkte steht ohnehin darüber; hier steht die EINE, bei der + sich das Hinsehen am meisten lohnt. + + UND EIN SCHLUSS, DER ZUR LAGE PASST. Nicht „weiter so" für alle. + Wer gerade viel trägt, braucht einen anderen letzten Satz als + jemand, bei dem alles läuft. + + --------------------------------------------------------------------- + KEINE ZAHLEN, DIE JEMAND PFLEGEN MUSS + + Jede Zahl in diesem Text wird gerechnet — auch „acht Fragen" und + „sechs Felder". Ein fester Satz „alle sechs Felder sind ruhig" wäre + beim siebten Feld still falsch, und niemand würde es merken. + ===================================================================== */ + +/** Welche Stufe zählt als „läuft", welche als „hakt". + * + * Abgeleitet aus dem Schlüssel und nicht als eigene Liste gepflegt: + * `unklar` ist ausdrücklich WEDER gut noch schlecht — „kann ich nicht + * sagen" ist eine ehrliche Antwort und darf nicht als halbes Problem + * gezählt werden. Wer sie zu den Sorgen schlägt, erzeugt Sorgen, die + * niemand geäußert hat. */ +const TRAEGT = new Set(["laeuft"]); +const WAECHST = new Set(["waechst"]); +const HAKT = new Set(["hakt"]); + +/** Ein Satzende ohne doppelten Punkt. */ +const satz = (t) => { + const x = String(t || "").trim(); + if (!x) return ""; + return /[.!?…]$/.test(x) ? x : x + "."; +}; + +/** Der erste Buchstabe klein — für Halbsätze, die angehängt werden. + * (Deutsche Substantive bleiben groß; deshalb nur dort benutzt, wo + * ein Verb oder Pronomen folgt.) */ +const klein = (t) => (t ? t[0].toLowerCase() + t.slice(1) : t); + +/** + * Die eine Zusammenfassung am Ende einer Runde. + * + * @param antworten { schluessel: stufe } — die Antworten DIESER Runde + * @param punkte Die Fragen dieser Runde (mit titel, feld, kern) + * @param felder BEFINDEN_FELDER + * @param vorher Die Antworten der VORIGEN Runde, oder null + * @returns {{moeglich, kopf, traegt, thema, schluss, zahlen}} + */ +export function resuemeeVon(antworten, punkte, felder, vorher = null) { + const gefragt = punkte.filter((p) => antworten?.[p.schluessel]); + + /* DRITTER AUSGANG. Ohne Antworten gibt es nichts zusammenzufassen -- + und das muss dastehen, statt dass ein freundlicher Satz über + nichts erscheint. */ + if (!gefragt.length) { + return { + moeglich: false, + grund: "Noch keine Antworten in dieser Runde.", + kopf: null, traegt: [], thema: null, schluss: null, + zahlen: { gefragt: 0, traegt: 0, waechst: 0, hakt: 0, unklar: 0, felder: 0 }, + }; + } + + const mit = (menge) => gefragt.filter((p) => menge.has(antworten[p.schluessel])); + const traegtP = mit(TRAEGT); + const waechstP = mit(WAECHST); + const haktP = mit(HAKT); + const unklarP = gefragt.length - traegtP.length - waechstP.length - haktP.length; + + const feldName = (k) => felder.find((f) => f.schluessel === k)?.name || k; + + /* ---- WELCHES FELD IST DAS THEMA? -------------------------------- + Nicht die erste Frage mit „hakt", sondern das FELD mit den + meisten. Drei Haken quer über alles heißen „schwerer Monat"; + drei Haken in EINEM Feld heißen „hier ist etwas". Nur das zweite + ist ein Befund, und nur darüber lohnt ein Satz. + + Bei Gleichstand entscheidet die Reihenfolge der Felder, und die + geht von außen nach innen — das schwerere Thema gewinnt. */ + const proFeld = new Map(); + for (const p of haktP) { + proFeld.set(p.feld, (proFeld.get(p.feld) || 0) + 1); + } + let themaFeld = null, themaN = 0; + for (const f of felder) { + const n = proFeld.get(f.schluessel) || 0; + if (n > themaN) { themaFeld = f; themaN = n; } + } + + /* ---- WAS TRÄGT --------------------------------------------------- + Höchstens zwei, und benannt. Eine Aufzählung von fünf Dingen liest + niemand; zwei bleiben hängen. Kernfragen zuerst — sie sind die, + die in jeder Runde gestellt werden, und damit die, deren „läuft" + am meisten bedeutet. */ + const traegtListe = [...traegtP] + .sort((a, b) => (b.kern ? 1 : 0) - (a.kern ? 1 : 0)) + .slice(0, 2) + .map((p) => ({ schluessel: p.schluessel, titel: p.titel, feld: p.feld })); + + /* ---- HAT SICH SEIT DEM LETZTEN MAL ETWAS GEBESSERT? ------------- + Nur echte Verbesserungen, und nur bei Fragen, die BEIDE Male + gestellt wurden. Seit die Fragen rotieren, wäre „war letztes Mal + nicht dabei" sonst eine Verbesserung — und das ist keine. */ + const besser = []; + if (vorher) { + const rang = { hakt: 0, waechst: 1, laeuft: 2 }; + for (const p of gefragt) { + const a = rang[vorher[p.schluessel]]; + const b = rang[antworten[p.schluessel]]; + if (a !== undefined && b !== undefined && b > a) besser.push(p); + } + } + + /* ---- DER KOPFSATZ ------------------------------------------------ + Vier Lagen, vier Sätze — und keiner davon behauptet etwas, das + nicht in den Zahlen steht. */ + const n = gefragt.length; + let kopf; + if (!haktP.length && !waechstP.length) { + kopf = unklarP === n + ? "Diesmal konntest du zu fast allem nichts sagen. Das ist eine ehrliche " + + "Antwort – und beim nächsten Mal weißt du vermutlich mehr." + : `Alle ${n} Fragen dieser Runde stehen auf „Läuft". Das ist selten, und ` + + "es ist kein Zufall – irgendwas machst du gerade richtig."; + } else if (!haktP.length) { + kopf = `Nichts hakt gerade. ${waechstP.length === 1 ? "Eine Sache wächst" : `${waechstP.length} Sachen wachsen`} ` + + "noch – das heißt, sie sind unterwegs, nicht liegengeblieben."; + } else if (haktP.length === 1) { + kopf = `Von ${n} Fragen hakt genau eine. ${n - 1} laufen oder wachsen – ` + + "das ist der Boden, auf dem du stehst, und der ist tragfähig."; + } else if (haktP.length < n / 2) { + kopf = `${haktP.length} von ${n} Sachen haken gerade. Die anderen ${n - haktP.length} ` + + "nicht – und das ist die Hälfte der Auskunft, die man beim Draufschauen " + + "meistens übersieht."; + } else { + /* DIE SCHWERE LAGE. Hier NICHT aufmunternd werden -- das ist der + Moment, in dem eine fröhliche Zusammenfassung alles Vertrauen + kostet. Stattdessen: benennen, entlasten, und den Weg zeigen. + „Fast nie an dir" ist keine Beschönigung, sondern das, was die + Forschung zu Belastung in Ehrenämtern durchgehend findet. */ + kopf = `${haktP.length} von ${n} Sachen haken. Das ist viel, und es liegt ` + + "fast nie an der Person, die es aushält. Es ist ein guter Moment, " + + "das jemandem zu sagen, statt es noch eine Runde mitzunehmen."; + } + + /* ---- DER SCHLUSSSATZ --------------------------------------------- + Er gehört zur Lage. „Weiter so" für alle wäre ein Satz, der nie + jemanden meint. */ + let schluss; + if (besser.length) { + schluss = besser.length === 1 + ? `Und etwas hat sich seit dem letzten Mal gebessert: ${klein(besser[0].titel)}. ` + + "Das hat sich nicht von allein gemacht." + : `Und ${besser.length} Sachen haben sich seit dem letzten Mal gebessert. ` + + "Das hat sich nicht von allein gemacht."; + } else if (haktP.length >= n / 2) { + schluss = "Du musst das hier nicht allein lösen. Dass du es aufgeschrieben " + + "hast, ist der erste Teil – der zweite ist ein Satz an jemanden."; + } else if (haktP.length) { + schluss = "Das musst du heute nicht lösen. Es reicht, dass du es weißt – " + + "beim nächsten Mal siehst du, ob es geblieben ist."; + } else { + schluss = "Nächste Runde in zwei Wochen. Bis dahin: nichts zu tun."; + } + + return { + moeglich: true, + kopf, + traegt: traegtListe, + thema: themaFeld && themaN > 0 + ? { + feld: themaFeld.schluessel, + name: feldName(themaFeld.schluessel), + anzahl: themaN, + /* MEHRERE IN EINEM FELD SIND ETWAS ANDERES ALS EINES. Der Satz + sagt, welches von beidem es ist -- daran hängt, ob man mit + einer Sache spricht oder mit einem Thema. */ + satz: themaN > 1 + ? satz(`Wenn du nur an einer Stelle hinschaust, dann hier: „${feldName(themaFeld.schluessel)}" ` + + `– da stehen ${themaN} der Haken beieinander. Das ist selten Zufall`) + : satz(`Wenn du nur an einer Stelle hinschaust, dann hier: „${feldName(themaFeld.schluessel)}"`), + titel: haktP.filter((p) => p.feld === themaFeld.schluessel).map((p) => p.titel), + } + : null, + schluss, + zahlen: { + gefragt: n, + traegt: traegtP.length, + waechst: waechstP.length, + hakt: haktP.length, + unklar: unklarP, + besser: besser.length, + /* GERECHNET, nicht eingetragen: Wer ein siebtes Feld ergänzt, + bekommt hier automatisch die 7. */ + felder: new Set(gefragt.map((p) => p.feld).filter(Boolean)).size, + }, + }; +} diff --git a/server/workspace-befinden.js b/server/workspace-befinden.js index 26cb6e09..bfa11654 100644 --- a/server/workspace-befinden.js +++ b/server/workspace-befinden.js @@ -61,7 +61,7 @@ import express from "express"; import { db, sitzungLesen, protokolliere, echteIp } from "./workspace.js"; import { - ENTWICKLUNG_BLOECKE, ENTWICKLUNG_PUNKTE, ENTWICKLUNG_STUFEN, + ENTWICKLUNG_BLOECKE, ENTWICKLUNG_PUNKTE, ENTWICKLUNG_STUFEN, BEFINDEN_FELDER, } from "./workspace-entwicklungs-punkte.js"; import { tabellen as entwicklungTabellen } from "./workspace-entwicklung.js"; @@ -78,15 +78,27 @@ export const RHYTHMUS_TAGE = 14; /** Wie viele Zusatzfragen je Runde zum Kern dazukommen. * - * DREI. Mit fünf Kernfragen macht das acht je Runde — die Quellen zu + * VIER. Mit fünf Kernfragen macht das neun je Runde — die Quellen zu * Puls-Abfragen sagen übereinstimmend fünf bis fünfzehn, unter zwei - * Minuten. Acht liegt mittig und lässt Platz: Der Katalog kann auf - * zwanzig Fragen wachsen, ohne dass eine einzelne Runde länger wird. + * Minuten. Neun liegt mittig und lässt Platz: Der Katalog kann weiter + * wachsen, ohne dass eine einzelne Runde länger wird. * * Das ist der ganze Trick an Filipes zwei Wünschen, die sich zu * widersprechen schienen — „viel mehr Fragen" und „kurz und knapp". - * Beides geht, nur nicht gleichzeitig in DERSELBEN Runde. */ -export const ZUSATZ_PRO_RUNDE = 3; + * Beides geht, nur nicht gleichzeitig in DERSELBEN Runde. + * + * WARUM VIER UND NICHT DREI (geändert 20.09.2026, mit den Feldern). + * Drei war richtig, solange es sieben wechselnde Fragen gab. Mit + * dreizehn dauerte es bei drei je Runde ZEHN Runden, bis jede einmal + * gestellt war — fast ein halbes Jahr, in dem eine Frage im Katalog + * steht und nie jemandem begegnet. Bei vier sind es vier Runden, also + * acht Wochen. Gemessen, nicht geschätzt; die Zahl steht in keiner + * Behauptung, sondern in pruef-befinden.mjs. + * + * Dieselbe Rechnung wieder anstellen, sobald der Katalog wächst: + * ceil(wechselnde / ZUSATZ_PRO_RUNDE) ist die Zahl der Runden, und + * mehr als fünf davon sind zu viele. */ +export const ZUSATZ_PRO_RUNDE = 4; /** So viele frühere Antworten bekommt die Person zu sehen. * @@ -258,12 +270,89 @@ export function fragenFuer(personId, runden = null) { return { kern, zusatz: rest, alle: [...kern, ...rest], runden: n }; } - let schritt = ZUSATZ_PRO_RUNDE; - while (ggt(schritt, rest.length) !== 1) schritt++; + /* ===================================================================== + DER RING IM REISSVERSCHLUSS (20.09.2026) - const start = (n * schritt) % rest.length; + Seit die Fragen in sechs Felder gegliedert sind, reicht ein + einfacher Ring nicht mehr. Der Katalog ist nach Themen sortiert -- + wer daraus drei AUFEINANDERFOLGENDE nimmt, bekommt meistens drei + aus demselben Feld. Eine Runde, die nur nach „Klarheit & Rueckhalt" + fragt, ist keine Befindensabfrage mehr, sondern ein Interview zu + einem Thema. + + DER RING WIRD DESHALB VORHER VERZAHNT: erst je eine Frage aus + jedem Feld, dann je eine zweite, dann je eine dritte. Ein Fenster + von vier aufeinanderfolgenden Eintraegen trifft damit von selbst + vier verschiedene Felder -- ohne dass irgendwo eine Regel + nachrechnen muss, welche schon dran waren. + + DIE FELDER OHNE KERNFRAGE STEHEN IM REISSVERSCHLUSS VORN. Sie sind + die einzigen, die eine Runde lang ganz fehlen KOENNTEN -- die + anderen sind ueber ihre Kernfrage ohnehin in jeder Runde dabei. + Gemessen ueber zwanzig Runden: 19 davon enthalten mindestens eine + Frage aus einem kernlosen Feld. + + --------------------------------------------------------------------- + WAS HIER VORHER STAND -- UND WARUM ES WEG IST + + Der erste Anlauf reservierte JEDER Runde feste Plaetze fuer die + kernlosen Felder. Begruendet war das mit „sonst stuende in der + Auswertung nach Feldern ein leeres Kapitel, und das liest sich wie + Zustimmung". + + Die Begruendung war falsch: Es gibt gar keine Auswertung je Feld. + Filipe wollte ausdruecklich EINE Zusammenfassung am Ende, nicht + eine je Kategorie -- und die sucht sich ihr Thema aus dem, was + tatsaechlich gefragt wurde. Ein Feld, das diese Runde nicht + drankam, behauptet also gar nichts. + + Gekostet hat die Reservierung dafuer etwas Messbares: Zwei von + drei Plaetzen waren belegt, der Ring kam kaum voran, und bis jede + der 13 wechselnden Fragen einmal gestellt war, vergingen ZWOELF + Runden -- fast ein halbes Jahr. Gefunden hat das nicht das + Nachdenken, sondern `pruef-befinden.mjs`: Sie verlangt, dass ueber + wenige Runden jede Frage drankommt, und wurde rot. + + Mit dem verzahnten Ring sind es vier Runden (acht Wochen), bei + gleichzeitig besserer Streuung. Beides gemessen, nicht geschaetzt. + ===================================================================== */ + const felderMitKern = new Set(kern.map((p2) => p2.feld).filter(Boolean)); + + /* Die Feldreihenfolge: kernlose zuerst, sonst wie im Katalog. Ein + stabiles Sortieren -- `sort` in JavaScript ist seit ES2019 + garantiert stabil, die Katalogreihenfolge bleibt also innerhalb + der beiden Gruppen erhalten. */ + const feldReihe = [...new Set(rest.map((p2) => p2.feld).filter(Boolean))] + .sort((a, b) => (felderMitKern.has(a) ? 1 : 0) - (felderMitKern.has(b) ? 1 : 0)); + + const koerbe = feldReihe.map((f) => rest.filter((p2) => p2.feld === f)) + .filter((k) => k.length); + /* Fragen ganz ohne Feld kaemen sonst nie dran -- sie bekommen einen + eigenen Korb. Abgeleitet, nicht angenommen. */ + const ohneFeld = rest.filter((p2) => !p2.feld); + if (ohneFeld.length) koerbe.push(ohneFeld); + + const ring = []; + const tiefe = koerbe.reduce((m, k) => Math.max(m, k.length), 0); + for (let i = 0; i < tiefe; i++) { + for (const k of koerbe) if (k[i]) ring.push(k[i]); + } + + /* Der Versatz muss zur Ringlaenge teilerfremd sein, sonst laeuft der + Kreis nie ganz durch -- dieselbe Ueberlegung wie vorher, nur jetzt + auf dem verzahnten Ring. */ + let schritt = ZUSATZ_PRO_RUNDE; + while (ggt(schritt, ring.length) !== 1) schritt++; + + const start = (n * schritt) % ring.length; const zusatz = []; - for (let i = 0; i < ZUSATZ_PRO_RUNDE; i++) zusatz.push(rest[(start + i) % rest.length]); + const schonDrin = new Set(); + for (let i = 0; i < ring.length && zusatz.length < ZUSATZ_PRO_RUNDE; i++) { + const w = ring[(start + i) % ring.length]; + if (schonDrin.has(w.schluessel)) continue; + schonDrin.add(w.schluessel); + zusatz.push(w); + } /* IN KATALOGREIHENFOLGE AUSGELIEFERT, nicht in Rotationsreihenfolge. Der Katalog ist nach Thema sortiert; wer ihn jede Runde in einer @@ -281,6 +370,8 @@ export function fragenFuer(personId, runden = null) { DIE SEITE ===================================================================== */ +import { resuemeeVon } from "./workspace-befinden-resuemee.js"; + befindenRouter.get("/workspace/api/befinden", angemeldet, (req, res) => { try { const b = block(); @@ -350,9 +441,86 @@ befindenRouter.get("/workspace/api/befinden", angemeldet, (req, res) => { }; }); + /* ==== DIE EINE ZUSAMMENFASSUNG AM ENDE (20.09.2026) ============== + + Filipe: "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 + fuer die person." + + SIE WIRD AUS DEN FRISCHEN ANTWORTEN GERECHNET, nicht aus allen + in `entwicklung_stand`. Der Unterschied ist derselbe wie eine + Bildschirmseite weiter oben: Eine Zusammenfassung ueber alte + Antworten sagt nicht "so geht es dir", sondern "so ging es dir + irgendwann mal", und man merkt es nicht. + + UND SIE BRAUCHT KEINE ZWEITE RUNDE. Die Musteranalyse darunter + schon (ein Punkt ist keine Entwicklung) -- diese hier ist die + Antwort auf das, was gerade dasteht, und die gibt es beim ersten + Mal genauso. + + DIE VORIGE RUNDE KOMMT MIT, wenn es eine gibt: Nur dann kann + "hat sich gebessert" ueberhaupt gesagt werden, und nur fuer + Fragen, die BEIDE Male dran waren. Seit die Fragen rotieren, + waere "war letztes Mal nicht dabei" sonst eine Verbesserung. */ + const frischeAntworten = Object.fromEntries( + punkte.filter((pt) => pt.frisch && pt.meins) + .map((pt) => [pt.schluessel, pt.meins.stufe])); + + /* WELCHE ANTWORTEN DIE ZUSAMMENFASSUNG MEINT -- und hier steckte + ein Fehler, den die Pruefung gefunden hat, nicht das Nachdenken. + + Erster Anlauf: nur die FRISCHEN, also die nach der letzten Runde + gesetzten. Das ist waehrend einer laufenden Runde richtig -- und + ausgerechnet in dem Moment falsch, in dem man die Zusammenfassung + sehen will: DIREKT NACH dem Abschliessen einer Runde ist keine + einzige Antwort mehr frisch. Man hat sie ja Sekunden vorher + gesetzt, also VOR dem Abschluss. Die Seite zeigte nach dem + Abschliessen: nichts. + + (Derselbe Sachverhalt steht schon beim Hinweis „das war deine + Antwort beim letzten Mal" in befinden.js -- dort wurde er + beruecksichtigt, hier nicht. Eine Erkenntnis, die an einer + Stelle notiert ist, wirkt nicht von selbst an der anderen.) + + RICHTIG IST BEIDES, in dieser Rangfolge: + laeuft gerade eine Runde -> die frischen Antworten + gerade abgeschlossen -> die letzte Runde aus dem Verlauf + + Der Vergleich rutscht entsprechend mit: Wird die laufende Runde + gezeigt, ist die letzte abgeschlossene der Vergleich; wird die + letzte abgeschlossene gezeigt, die vorletzte. Sonst verglichen + wir eine Runde mit sich selbst -- und "nichts hat sich + geaendert" waere dann keine Auskunft, sondern ein Rechenfehler. */ + const laeuftRunde = Object.keys(frischeAntworten).length > 0; + const fazitAntworten = laeuftRunde + ? frischeAntworten + : (verlauf[0]?.antworten || {}); + const fazitVergleich = laeuftRunde + ? (verlauf[0]?.antworten || null) + : (verlauf[1]?.antworten || null); + /* DIE FRAGEN DAZU aus dem KATALOG, nicht aus `diese.alle`: Eine + abgeschlossene Runde kann andere Fragen enthalten haben als die, + die gerade dran waeren -- die Zusatzfragen wechseln ja. Wer + stattdessen die aktuellen nimmt, fasst Antworten zusammen, die es + zu diesen Fragen gar nicht gibt. */ + const fazitPunkte = (ENTWICKLUNG_PUNKTE[b.schluessel] || []) + .filter((pt) => fazitAntworten[pt.schluessel]); + res.json({ block: b, stufen: ENTWICKLUNG_STUFEN, + /* DIE FELDER. Sie stehen im Katalog, nicht im Browser -- eine + zweite Liste dort waere die, die beim siebten Feld vergessen + wird. */ + felder: BEFINDEN_FELDER, + resuemee: resuemeeVon(fazitAntworten, fazitPunkte, BEFINDEN_FELDER, + fazitVergleich), + /* WORAUF SIE SICH BEZIEHT -- die Seite sagt das dazu. „Was das + zusammen heisst" ueber eine Runde von vorgestern ist etwas + anderes als ueber die, die man gerade ausfuellt, und wer das + nicht weiss, bezieht den Satz auf den falschen Tag. */ + resuemee_stand: laeuftRunde ? "laufend" : (verlauf[0] ? "letzte" : "keine"), rhythmus_tage: RHYTHMUS_TAGE, rhythmus: takt, punkte, @@ -622,6 +790,69 @@ export const EINORDNUNG = { hilft: "Die billigste Maßnahme überhaupt und die mit der besten Wirkung: " + "ausdrücklich danken, für etwas Bestimmtes, nicht allgemein.", }, + + /* ---- Die sechs vom 20.09.2026 ------------------------------------ + Sie schließen drei Lücken, die die Liste vorher hatte: Schlaf, + Widersprechen und Mitreden. Jede bekommt hier ihre Einordnung -- + eine Frage ohne Grundlage ist eine Frage, die jemand gut fand, und + davon gibt es in Befindensabfragen genug. */ + "e-ich-schlaf": { + welt: "Schlafstörungen sind in der Erschöpfungsforschung das Zeichen, das " + + "AM FRÜHESTEN kommt – vor dem Leistungsabfall, vor dem Zynismus, vor " + + "dem Gedanken ans Aufhören. Wer nachts wach liegt und an eine Schicht " + + "denkt, hat das Problem noch nicht bei der Arbeit, aber schon im Kopf.", + hilft: "Nicht weniger arbeiten, sondern klarer aufhören: ein festes Ende, " + + "Meldungen aus, und etwas dazwischen, das nichts mit dem Stream zu tun " + + "hat. Das Gehirn braucht ein Signal, dass die Schicht vorbei ist – die " + + "Uhrzeit allein ist keins.", + }, + "e-ich-widersprechen": { + welt: "Ob man widersprechen kann, ohne dass es unangenehm wird, ist der Kern " + + "dessen, was die Forschung psychologische Sicherheit nennt – der am " + + "besten belegte Einzelfaktor dafür, ob ein Team gut arbeitet. Ein Team, " + + "in dem niemand widerspricht, ist selten einig; meistens ist es still.", + hilft: "Es liegt fast nie an der Person, die schweigt. Was hilft: dass die " + + "Leitung selbst widerspricht und widersprochen wird, sichtbar, und dass " + + "ein Einwand einmal ausdrücklich gedankt wird statt nur abgehakt.", + }, + "e-ich-mitreden": { + welt: "Viel Anforderung bei wenig Einfluss ist die Kombination, aus der " + + "Erschöpfung entsteht – nicht viel Arbeit allein. Das Modell dahinter " + + "(Job Demands-Resources) ist seit Jahrzehnten eines der am besten " + + "geprüften der Arbeitspsychologie, und Einfluss ist darin die " + + "wirksamste einzelne Entlastung.", + hilft: "Es muss nicht Mitbestimmung über alles sein. Es reicht meistens, " + + "über das Eigene zu entscheiden: wann, wie lange, in welcher " + + "Reihenfolge. Wer das abgibt, gibt wenig ab und nimmt viel Last.", + }, + "e-ich-fehler-ok": { + welt: "Was zählt, ist nicht, ob Fehler passieren, sondern ob man sie " + + "erfährt. In Teams, in denen Fehler teuer sind, werden sie nicht " + + "seltener – sie werden unsichtbar, und die Folgekosten trägt der, dem " + + "er passiert ist, allein.", + hilft: "Der erste Fehler, den jemand aus der Leitung selbst erzählt, wirkt " + + "mehr als jede Zusage. Und: beim nächsten gemeldeten Fehler zuerst " + + "fragen, was er über den ABLAUF sagt, nicht über die Person.", + }, + "e-ich-dazugehoeren": { + welt: "Anschluss ans Team ist in den Untersuchungen zu Moderatoren-Belastung " + + "der STÄRKSTE einzelne Schutzfaktor – stärker als kürzere Schichten und " + + "stärker als bessere Werkzeuge. Nützlich zu sein und dazuzugehören sind " + + "dabei zwei verschiedene Dinge, und nur das zweite hält.", + hilft: "Es entsteht neben der Arbeit, nicht in ihr: ein Kanal, in dem es " + + "nicht um Schichten geht, ein gemeinsamer Termin ohne Zweck. Wer nur " + + "zum Arbeiten da ist, geht, sobald die Arbeit unangenehm wird.", + }, + "e-ich-stolz": { + welt: "Die einzige Frage der Liste, die zurückschaut. Wer sich an nichts " + + "erinnert, hat selten nichts geschafft – es hat nur niemand benannt. " + + "Das ist die gleiche Lücke wie bei der Anerkennung, nur von innen: " + + "Moderationsarbeit besteht zum großen Teil aus dem, was NICHT passiert " + + "ist, und daran erinnert sich niemand von selbst.", + hilft: "Aufschreiben, sobald es passiert – eine Zeile reicht. Im Workspace " + + "ist dafür das Highlight-Brett da. Was nirgends steht, ist nach zwei " + + "Wochen weg, auch wenn es gut war.", + }, }; /** Das Muster einer Frage ueber die Runden hinweg -- neueste zuerst. */ diff --git a/server/workspace-entwicklungs-punkte.js b/server/workspace-entwicklungs-punkte.js index 953c03be..435d8a0a 100644 --- a/server/workspace-entwicklungs-punkte.js +++ b/server/workspace-entwicklungs-punkte.js @@ -61,6 +61,66 @@ * man im Zweifel "laeuft", weil das die freundlichste Taste ist -- * und ab da steht im Katalog eine Beobachtung, die niemand gemacht * hat. */ +/* ===================================================================== + DIE SECHS FELDER DER BEFINDENSFRAGEN (20.09.2026) + + Filipe: „bei der frage wie geht es dir will ich viel mehr fragen + aber kategorisiert und so. psychologisch hochprofessionell." + + --------------------------------------------------------------------- + WARUM KATEGORIEN UND NICHT NUR MEHR FRAGEN + + Achtzehn Fragen in einer Reihe sind ein Fragebogen; achtzehn Fragen + in sechs Feldern sind ein Gespraech mit sechs Themen. Der Unterschied + ist nicht kosmetisch: + + * BEIM ANTWORTEN gibt eine Ueberschrift den Rahmen. „Ich kann nein + sagen" heisst in „Last & Erholung" etwas anderes als in + „Miteinander" -- die Ueberschrift beantwortet die Rueckfrage, + bevor sie entsteht. + * BEIM AUSWERTEN ist das Feld die Aussage. Drei Haken quer ueber + alles heissen „schwerer Monat"; drei Haken in EINEM Feld heissen + „hier ist etwas". Das erste ist ein Gefuehl, das zweite ein + Befund. + + WOHER DIE SECHS KOMMEN -- nicht ausgedacht. Die Befindensforschung + zu ehrenamtlichen und hochbelasteten Rollen fragt durchgehend + dieselben Gebiete ab (Job-Demands-Resources-Modell, Maslach-Modell + zur Erschoepfung, Copenhagen Psychosocial Questionnaire). Zusammen + sind es sechs, und genau die stehen hier: + + last Anforderung und Erholung -- wie viel, und hoert es auf + zusammen Das Miteinander und die psychologische Sicherheit + sinn Sinn und Anerkennung -- wofuer, und merkt es jemand + halt Klarheit und Rueckhalt -- weiss ich, was gilt und wer hilft + schutz Grenzen und Sicherheit -- bleibt das Private privat + vorne Weiterkommen und Ausblick -- lohnt es sich auf Sicht + + KEIN FELD OHNE KERNFRAGE. Ein Feld, das nur rotierende Fragen + enthaelt, kann eine ganze Runde lang stumm bleiben -- und dann steht + in der Auswertung ein leeres Kapitel, das nach Zustimmung aussieht. + `pruef-befinden.mjs` weist das nach, statt es zu behaupten. + + DIE REIHENFOLGE IST NICHT BELIEBIG: Sie geht von aussen nach innen. + „Wie viel ist es" laesst sich leicht beantworten; „bleibe ich" ist + die schwerste Frage der Liste und steht deshalb zuletzt -- wer bis + dahin gekommen ist, hat sich eingelassen. + ===================================================================== */ +export const BEFINDEN_FELDER = [ + { schluessel: "last", name: "Last & Erholung", zeichen: "\u25d0", + text: "Wie viel es ist – und ob es aufhört, wenn du aufhörst." }, + { schluessel: "zusammen", name: "Miteinander", zeichen: "\u25c7", + text: "Wie es sich mit den anderen anfühlt." }, + { schluessel: "sinn", name: "Sinn & Anerkennung", zeichen: "\u25b3", + text: "Wofür das Ganze – und ob es jemandem auffällt." }, + { schluessel: "halt", name: "Klarheit & Rückhalt", zeichen: "\u25a1", + text: "Ob du weißt, was gilt, und wer da ist, wenn es eng wird." }, + { schluessel: "schutz", name: "Grenzen & Sicherheit", zeichen: "\u25cb", + text: "Ob das Private privat bleibt." }, + { schluessel: "vorne", name: "Weiterkommen & Ausblick", zeichen: "\u25b7", + text: "Ob es sich auf Sicht lohnt." }, +]; + export const ENTWICKLUNG_STUFEN = [ { schluessel: "laeuft", name: "Läuft", zeichen: "✓", text: "Habe ich gesehen. Passt." }, @@ -437,25 +497,25 @@ export const ENTWICKLUNG_PUNKTE = { bei jedem Neuladen eine andere Liste, und eine Seite, die sich unter den Fingern aendert, beantwortet niemand zu Ende. */ befinden: [ - { schluessel: "e-ich-menge-passt", stufe: "probe", kern: true, + { schluessel: "e-ich-menge-passt", feld: "last", stufe: "probe", kern: true, /* KERN: Die meistgenannte Ursache ueberhaupt. Wer sie nur jede dritte Runde fragt, erfaehrt sie im Schnitt sechs Wochen zu spaet. */ titel: "Die Menge passt gerade", text: "Zu wenig Zeit ist der meistgenannte Grund, warum Moderatoren aufhören." }, - { schluessel: "e-ich-niemand-schwierig", stufe: "probe", kern: true, + { schluessel: "e-ich-niemand-schwierig", feld: "zusammen", stufe: "probe", kern: true, /* KERN: Die zweithaeufigste -- und die, ueber die von selbst am wenigsten geredet wird. Genau deshalb muss sie gefragt werden. */ titel: "Es gibt gerade niemanden im Team, mit dem es schwierig ist", text: "Der zweithäufigste Grund – und der, über den am wenigsten geredet wird." }, - { schluessel: "e-ich-weiss-an-wen", stufe: "probe", + { schluessel: "e-ich-weiss-an-wen", feld: "halt", stufe: "probe", titel: "Ich weiß, an wen ich mich wende, wenn etwas eskaliert", text: "Ein fehlender Eskalationsweg fühlt sich nicht wie ein fehlender Weg an, " + "sondern wie Unsicherheit." }, - { schluessel: "e-ich-jemand-zum-reden", stufe: "probe", + { schluessel: "e-ich-jemand-zum-reden", feld: "zusammen", stufe: "probe", titel: "Nach einer hässlichen Schicht gibt es jemanden zum Reden", text: "Wer Hässliches wegmoderiert, nimmt es mit. Das geht nicht von allein weg." }, - { schluessel: "e-ich-nein-ohne-problem", stufe: "probe", + { schluessel: "e-ich-nein-ohne-problem", feld: "halt", stufe: "probe", titel: "Ich kann nein sagen, ohne dass es ein Problem ist", text: "Nicht ob du darfst – ob es sich so anfühlt. Das ist der Unterschied." }, - { schluessel: "e-ich-faellt-auf", stufe: "probe", + { schluessel: "e-ich-faellt-auf", feld: "sinn", stufe: "probe", titel: "Ich habe das Gefühl, dass es auffällt, wenn ich da bin", text: "Fehlende Anerkennung ist der am häufigsten genannte emotionale Preis – " + "und der billigste zu beheben." }, @@ -466,33 +526,100 @@ export const ENTWICKLUNG_PUNKTE = { ALLE AUF "probe": Diese Fragen sind keine Stufenleiter. Sie gelten am ersten Tag genauso wie im dritten Jahr -- ein Befinden hat keine Erwartungsstufe. */ - { schluessel: "e-ich-abschalten", stufe: "probe", kern: true, + { schluessel: "e-ich-abschalten", feld: "last", stufe: "probe", kern: true, /* KERN: Ob die Erholung traegt, sagt besser voraus, ob jemand aufhoert, als die Menge der Arbeit. */ titel: "Nach einer Schicht kann ich abschalten", text: "Nicht ob du müde bist – ob es aufhört, wenn du aufhörst. " + "Das ist der Unterschied zwischen anstrengend und zehrend." }, - { schluessel: "e-ich-sinn", stufe: "probe", + { schluessel: "e-ich-sinn", feld: "sinn", stufe: "probe", titel: "Ich habe das Gefühl, dass meine Arbeit hier etwas bewirkt", text: "Wer nicht sieht, was seine Arbeit bewirkt, hört auf – auch wenn " + "alles andere stimmt. Es ist der stillste Grund von allen." }, - { schluessel: "e-ich-klar-was-erwartet", stufe: "probe", + { schluessel: "e-ich-klar-was-erwartet", feld: "halt", stufe: "probe", titel: "Mir ist klar, was von mir erwartet wird", text: "Unklarheit fühlt sich nicht wie Unklarheit an, sondern wie " + "das Gefühl, es nie ganz richtig zu machen." }, - { schluessel: "e-ich-nicht-angefeindet", stufe: "probe", kern: true, + { schluessel: "e-ich-nicht-angefeindet", feld: "schutz", stufe: "probe", kern: true, /* KERN: Der Punkt, an dem aus Belastung eine Gefahr wird. Er darf nie in einer Rotation warten. */ titel: "Ich werde außerhalb des Streams in Ruhe gelassen", text: "Privatnachrichten, andere Plattformen, das Privatleben. Wer hier " + "„da hakt es“ setzt, braucht keine Umfrage, sondern ein Gespräch." }, - { schluessel: "e-ich-lerne-dazu", stufe: "probe", + { schluessel: "e-ich-lerne-dazu", feld: "vorne", stufe: "probe", titel: "Ich lerne hier etwas, das mir auch sonst nützt", text: "Ehrenamt hält, wenn es etwas zurückgibt. Das muss kein Geld sein – " + "aber es muss etwas sein." }, - { schluessel: "e-ich-naechste-monate", stufe: "probe", kern: true, + { schluessel: "e-ich-naechste-monate", feld: "vorne", stufe: "probe", kern: true, /* KERN: Der beste einzelne Fruehindikator, den Befindensabfragen kennen -- er schlaegt an, bevor die anderen es tun. */ titel: "Ich kann mir gut vorstellen, in ein paar Monaten noch dabei zu sein", text: "Die ehrlichste Frage der Liste. Wer hier zögert, hat es meistens " + "vorher schon gewusst – und niemand hat gefragt." }, + + /* ================================================================= + SECHS WEITERE (20.09.2026) + + Filipe: „bei der frage wie geht es dir will ich viel mehr fragen + aber kategorisiert und so. psychologisch hochprofessionell." + + WAS SIE SCHLIESSEN, IST NICHT „noch ein bisschen mehr", sondern + drei echte blinde Flecken -- und alle drei sind in der Forschung + zu belasteten Rollen ueblich, in dieser Liste bis heute aber + nicht vertreten: + + * SCHLAF. Erschoepfung zeigt sich zuerst nachts, nicht bei der + Arbeit. Wer wach liegt und an eine Schicht denkt, merkt es + dort Wochen bevor die Arbeit darunter leidet. + * WIDERSPRECHEN. „Kann ich nein sagen" fragt nach der eigenen + Grenze. Etwas anderes ist, einem VORSCHLAG zu widersprechen, + ohne dass es unangenehm wird -- das ist der Kern dessen, was + die Forschung psychologische Sicherheit nennt, und es fehlte. + * MITREDEN. Ob jemand mitbestimmt, wie seine eigene Arbeit + laeuft. Wenig Einfluss bei viel Anforderung ist die + Kombination, aus der Erschoepfung entsteht -- nicht viel + Arbeit allein. + + Dazu je eine Frage, die ihrem Feld gefehlt hat: Fehler + („Grenzen & Sicherheit" hatte nur die Anfeindung), Zugehoerigkeit + („Miteinander" fragte nur nach Konflikt und Reden) und der Blick + zurueck („Weiterkommen" fragte nur nach vorn). + + ALLE AUF "probe" wie die uebrigen: Ein Befinden hat keine + Erwartungsstufe. Es gilt am ersten Tag wie im dritten Jahr. + + NUR EINE DAVON IST KERN. Der Kern ist das, was in JEDER Runde + gefragt wird -- er waechst nicht mit dem Katalog, sonst waere die + Runde bald wieder lang, und "kurz und knapp" war die andere + Haelfte von Filipes Wunsch. Gewaehlt wurde der Schlaf: Er schlaegt + frueher an als jede andere Frage hier. */ + /* NICHT KERN, obwohl sie die beste Einzelfrage des Feldes waere: + "Last & Erholung" hat bereits zwei Kernfragen (Menge und + Abschalten). Eine dritte machte aus einer kurzen Runde eine + Umfrage ueber ein einziges Thema -- und "kurz und knapp" war die + andere Haelfte von Filipes Wunsch. Dass ihr Feld trotzdem in + JEDER Runde vorkommt, sorgt die Rotation (siehe fragenFuer). */ + { schluessel: "e-ich-schlaf", feld: "last", stufe: "probe", + titel: "Ich liege nachts nicht wach wegen Sachen von hier", + text: "Erschöpfung zeigt sich zuerst nachts. Wer wach liegt und an eine " + + "Schicht denkt, merkt es Wochen bevor die Arbeit darunter leidet." }, + { schluessel: "e-ich-widersprechen", feld: "zusammen", stufe: "probe", + titel: "Ich kann einem Vorschlag widersprechen, ohne dass es unangenehm wird", + text: "Nicht ob du recht bekommst – ob du es sagen kannst. Ein Team, in dem " + + "niemand widerspricht, ist selten einig; meistens ist es nur still." }, + { schluessel: "e-ich-mitreden", feld: "halt", stufe: "probe", + titel: "Ich rede mit, wie meine Arbeit hier läuft", + text: "Viel Anforderung und wenig Einfluss ist die Kombination, aus der " + + "Erschöpfung entsteht – nicht viel Arbeit allein." }, + { schluessel: "e-ich-fehler-ok", feld: "schutz", stufe: "probe", + titel: "Wenn mir ein Fehler passiert, kann ich das sagen", + text: "Was zählt, ist nicht, ob Fehler passieren – sondern ob man sie " + + "erfährt. Wer einen Fehler verschweigt, trägt ihn allein weiter." }, + { schluessel: "e-ich-dazugehoeren", feld: "zusammen", stufe: "probe", + titel: "Ich gehöre hier dazu", + text: "Nicht ob du nützlich bist – ob du dazugehörst. Das ist etwas " + + "anderes, und es hält länger." }, + { schluessel: "e-ich-stolz", feld: "sinn", stufe: "probe", + titel: "In den letzten Wochen war etwas dabei, auf das ich stolz bin", + text: "Die einzige Frage hier, die nach hinten schaut. Wer sich an nichts " + + "erinnert, hat selten nichts geschafft – es hat nur niemand benannt." }, ], }; diff --git a/workspace/assets/css/entwicklung.css b/workspace/assets/css/entwicklung.css index 21c66bf4..415d82f8 100644 --- a/workspace/assets/css/entwicklung.css +++ b/workspace/assets/css/entwicklung.css @@ -1481,3 +1481,140 @@ } .t-kuemmern__seit { font-size: .78rem; color: var(--text-still); } + + +/* ===================================================================== + DIE FELDER DER BEFINDENSFRAGEN (20.09.2026) + + Filipe: „bei der frage wie geht es dir will ich viel mehr fragen aber + kategorisiert und so. psychologisch hochprofessionell … es soll + extrem übersichtlich sein und sich speziell anfühlen." + + ÜBERSICHTLICH HEISST HIER: RUHIG. Eine Seite, auf der jemand über + sein Befinden nachdenkt, ist der falsche Ort für kräftige Farben und + Kästen. Die Feldüberschrift ist deshalb kein Kasten, sondern eine + Linie mit einem Zeichen davor — sie gliedert, ohne sich + dazwischenzustellen. Augenschonend ist hier nicht nur eine + Hausregel, sondern Teil der Sache. + ===================================================================== */ +.e-feld { + margin: 30px 0 12px; + padding-bottom: 8px; + border-bottom: 1px solid var(--rand); +} +/* Das erste Feld braucht oben keinen Abstand -- sonst klafft zwischen + der Taktzeile und der ersten Frage ein Loch, das wie ein Ladefehler + aussieht. */ +#fragen > .e-feld:first-child { margin-top: 4px; } + +.e-feld__wort { + display: flex; align-items: baseline; gap: 9px; + margin: 0; + font-size: .96rem; font-weight: 650; + color: var(--text); + letter-spacing: .01em; +} +.e-feld__zeichen { + flex: 0 0 auto; + font-size: .8rem; + color: var(--akzent); + /* Es ist ein Gliederungszeichen, kein Symbol mit Bedeutung -- im + Markup steht deshalb aria-hidden, und hier bekommt es nur so viel + Gewicht, dass das Auge die Überschrift findet. */ + opacity: .85; +} +.e-feld__text { + margin: 4px 0 0; + font-size: .82rem; line-height: 1.5; + color: var(--text-leise); +} + +/* ===================================================================== + DIE EINE ZUSAMMENFASSUNG + + SIE DARF ANDERS AUSSEHEN ALS ALLES DARÜBER — sie ist das Einzige auf + der Seite, das jemand FÜR die Person geschrieben hat, statt sie zu + fragen. Das ist der eine Ort, an dem „speziell" nicht Dekoration + ist, sondern Bedeutung trägt. + + Gemacht wird das mit einem sehr weichen Verlauf und einer Kante + links — nicht mit Leuchten oder Farbe. Hausregel: augenschonend, + gedeckt statt Neon. Wer hier gerade liest, dass fünf von acht Sachen + haken, braucht keine fröhlichen Farben dazu. + ===================================================================== */ +.b-fazit { + margin: 34px 0 0; + padding: 20px 22px; + border-radius: var(--radius); + border: 1px solid color-mix(in srgb, var(--akzent) 26%, var(--rand)); + border-left: 3px solid var(--akzent); + background: + linear-gradient(160deg, + color-mix(in srgb, var(--akzent) 9%, transparent) 0%, + transparent 62%), + color-mix(in srgb, var(--flaeche) 80%, transparent); +} +.b-fazit .e-abschnitt { margin-top: 0; } + +/* DER KOPFSATZ IST DER GRÖSSTE TEXT DER SEITE. Wer nur ihn liest, soll + richtig informiert sein -- dieselbe Regel wie beim Satz über der + Musteranalyse. */ +.b-fazit__kopf { + margin: 6px 0 0; + font-size: 1.04rem; line-height: 1.6; + color: var(--text); +} + +.b-fazit__traegt { margin-top: 16px; } +.b-fazit__schild { + margin: 0 0 6px; + font-size: .74rem; font-weight: 650; + letter-spacing: .06em; text-transform: uppercase; + color: var(--text-leise); +} +.b-fazit__liste { margin: 0; padding: 0; list-style: none; + display: flex; flex-direction: column; gap: 5px; } +.b-fazit__punkt { + position: relative; + padding-left: 20px; + font-size: .9rem; line-height: 1.5; + color: var(--text); +} +/* Ein Haken, kein Aufzählungspunkt: Was trägt, ist etwas Erledigtes, + und das sieht man an der Form schneller als am Wort. */ +.b-fazit__punkt::before { + content: "¹3"; + position: absolute; left: 0; top: 0; + color: var(--gut, #5fc08a); + font-weight: 700; +} + +.b-fazit__thema { + margin: 16px 0 0; + padding: 11px 13px; + border-radius: var(--radius-klein); + background: color-mix(in srgb, var(--warn, #e0a355) 10%, transparent); + border-left: 2px solid color-mix(in srgb, var(--warn, #e0a355) 55%, transparent); + font-size: .9rem; line-height: 1.55; + color: var(--text); +} +.b-fazit__themafragen { + display: inline-block; margin-top: 5px; + font-size: .82rem; color: var(--text-leise); +} + +.b-fazit__schluss { + margin: 16px 0 0; + font-size: .92rem; line-height: 1.6; + color: var(--text); +} +.b-fazit__zahlen { + margin: 12px 0 0; + font-size: .76rem; + font-variant-numeric: tabular-nums; +} + +@media (forced-colors: active) { + .b-fazit { border: 2px solid CanvasText; } + .e-feld { border-bottom: 1px solid CanvasText; } +} diff --git a/workspace/assets/js/befinden.js b/workspace/assets/js/befinden.js index 7897d911..cf549bcc 100644 --- a/workspace/assets/js/befinden.js +++ b/workspace/assets/js/befinden.js @@ -135,7 +135,63 @@ ziel.textContent = ''; ziel.setAttribute('aria-busy', 'false'); - for (const pt of daten.punkte) { + /* ===================================================================== + NACH FELDERN GEGLIEDERT (20.09.2026) + + Filipe: „bei der frage wie geht es dir will ich viel mehr fragen + aber kategorisiert und so. psychologisch hochprofessionell." + + Acht Fragen in einer Reihe sind ein Fragebogen; acht Fragen in + sechs Feldern sind ein Gespräch mit Themen. Die Überschrift ist + dabei nicht Schmuck, sondern Teil der Frage: „Ich kann nein + sagen" heißt unter „Klarheit & Rückhalt" etwas anderes als unter + „Miteinander" — sie beantwortet die Rückfrage, bevor sie + entsteht. + + DIE REIHENFOLGE KOMMT VOM SERVER, nicht aus einer Liste hier. + Sie geht von außen nach innen: „Wie viel ist es" lässt sich + leicht beantworten, „bleibe ich" ist die schwerste Frage der + Liste und steht zuletzt. + + EIN FELD OHNE FRAGEN WIRD NICHT GEZEICHNET. Eine leere + Überschrift sieht aus, als wäre etwas verlorengegangen — und der + Server sorgt ohnehin dafür, dass in jeder Runde jedes Feld + vorkommt. */ + const felder = Array.isArray(daten.felder) ? daten.felder : []; + /* ABGELEITET STATT ANGENOMMEN: Fragen ohne bekanntes Feld (eine + neue, die noch keins hat) landen in einer eigenen Gruppe am + Ende, statt zu verschwinden. Ein Eintrag, den man angelegt hat + und nirgends wiederfindet, ist der schlimmere Fehler. */ + const gruppen = felder.map((f) => ({ + feld: f, + punkte: daten.punkte.filter((pt) => pt.feld === f.schluessel), + })).filter((g) => g.punkte.length); + const bekannt = new Set(felder.map((f) => f.schluessel)); + const uebrig = daten.punkte.filter((pt) => !bekannt.has(pt.feld)); + if (uebrig.length) gruppen.push({ feld: null, punkte: uebrig }); + + for (const g of gruppen) { + if (g.feld) { + const kopf = el('div', 'e-feld'); + const wort = el('h3', 'e-feld__wort'); + if (g.feld.zeichen) { + const zch = el('span', 'e-feld__zeichen', g.feld.zeichen); + zch.setAttribute('aria-hidden', 'true'); + wort.append(zch); + } + wort.append(document.createTextNode(g.feld.name)); + kopf.append(wort); + if (g.feld.text) kopf.append(el('p', 'e-feld__text', g.feld.text)); + ziel.append(kopf); + } + for (const pt of g.punkte) zeichneFrage(pt, ziel); + } + } + + /** Eine einzelne Frage. Ausgelagert, damit die Gliederung oben + * lesbar bleibt -- der Inhalt ist unverändert. */ + function zeichneFrage(pt, ziel) { + { const z = el('article', 'e-punkt e-punkt--meins'); if (pt.meins) z.dataset.stufe = pt.meins.stufe; /* ALT, ABER SICHTBAR. Die Antwort vom letzten Mal stehen zu @@ -190,6 +246,92 @@ } } + /* ===================================================================== + DIE EINE ZUSAMMENFASSUNG (20.09.2026) + + Filipe: „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." + + GERECHNET HAT SIE DER SERVER — hier wird nur gezeichnet. Dieselbe + Regel wie bei der Analyse darunter, und aus demselben Grund: Diese + Antworten dürfen das Haus nicht verlassen, und eine Auswertung im + Browser hieße, sie dorthin zu schicken, wo man sie abgreifen kann. + + WARUM KEIN SPRACHMODELL: steht in workspace-befinden-resuemee.js. + Kurz: Gesundheitsdaten, ein Modell erfindet, ein fremder Dienst + fällt aus. + + DIE REIHENFOLGE DER VIER TEILE IST DIE AUSSAGE: + 1. der Kopfsatz — die Lage in einem Satz + 2. was trägt — benannt, nicht als Zahl + 3. höchstens EINE Sache zum Hinschauen + 4. ein Schluss, der zur Lage passt + + Erst das Tragende, dann das Thema. Umgekehrt wäre es eine + Mängelliste mit Trostpflaster — und so etwas beantwortet man beim + nächsten Mal nicht mehr ehrlich. + ===================================================================== */ + function fazit() { + const block = $('fazit-block'); + if (!block) return; + const f = daten.resuemee; + /* KEINE ANTWORTEN, KEIN FAZIT. Ein freundlicher Satz über nichts + wäre schlimmer als gar keiner -- er behauptet, es sei schon + etwas gesagt worden. */ + if (!f || !f.moeglich) { block.hidden = true; return; } + block.hidden = false; + + /* WORAUF SIE SICH BEZIEHT. „Was das zusammen heißt" über eine + Runde von vorgestern ist etwas anderes als über die, die man + gerade ausfüllt -- und wer das nicht weiß, bezieht den Satz auf + den falschen Tag. */ + const schild = block.querySelector('.e-abschnitt'); + if (schild) { + schild.textContent = daten.resuemee_stand === 'letzte' + ? 'Was deine letzte Runde zusammen heißt' + : 'Was das zusammen heißt'; + } + + $('fazit-kopf').textContent = f.kopf || ''; + + const tBlock = $('fazit-traegt'), tListe = $('fazit-traegt-liste'); + tListe.textContent = ''; + if (f.traegt?.length) { + tBlock.hidden = false; + for (const t of f.traegt) tListe.append(el('li', 'b-fazit__punkt', t.titel)); + } else tBlock.hidden = true; + + const th = $('fazit-thema'); + if (f.thema?.satz) { + th.hidden = false; + th.textContent = f.thema.satz; + /* Welche Fragen es sind, gehört dazu -- „Klarheit & Rückhalt" + allein ist eine Überschrift, keine Auskunft. */ + if (f.thema.titel?.length) { + th.append(document.createElement('br')); + const w = el('span', 'b-fazit__themafragen', f.thema.titel.join(' · ')); + th.append(w); + } + } else th.hidden = true; + + $('fazit-schluss').textContent = f.schluss || ''; + + /* DIE ZAHLEN GANZ UNTEN UND LEISE. Sie sind die Belegstelle für + alles darüber -- wer sie nicht will, überliest sie; wer prüfen + will, ob der Satz stimmt, findet sie. */ + const z = f.zahlen || {}; + const teile = []; + if (z.traegt) teile.push(`${z.traegt}× läuft`); + if (z.waechst) teile.push(`${z.waechst}× wächst`); + if (z.hakt) teile.push(`${z.hakt}× hakt`); + if (z.unklar) teile.push(`${z.unklar}× ohne Antwort`); + $('fazit-zahlen').textContent = teile.length + ? `${z.gefragt} Fragen aus ${z.felder} Feldern: ${teile.join(' · ')}.` + : ''; + } + /* ---- Der Verlauf ----------------------------------------------------- */ function verlauf() { const block = $('verlauf-block'); @@ -381,6 +523,7 @@ stufen = daten.stufen || []; takt(); fragen(); + fazit(); verlauf(); analyse(); } diff --git a/workspace/befinden.html b/workspace/befinden.html index 4af29019..64cc40d8 100644 --- a/workspace/befinden.html +++ b/workspace/befinden.html @@ -1,141 +1,172 @@ - - - - - -Wie geht's dir? · Spicy & Dogi - - - - - - - - - - - - - - - - - -
- -

Team Dogi · Wie geht's dir?

-
- … - -
-
- -
- - - -
-
-

Für dich

-

Wie geht's dir?

- -

- Ein paar Fragen über dich. Deine Antworten liest niemand – auch - DogFather nicht. -

-

- Das Einzige, was nach außen dringt: ob es im Team gerade - jemandem zu viel ist. Ohne Namen, ohne Frage, ohne Datum – - und erst ab drei Personen im Team, weil es darunter dasselbe wäre - wie ein Name. -

-
-
- - - - - - -
-

wird geladen …

-
- - - - - - - -
- + + + + + +Wie geht's dir? · Spicy & Dogi + + + + + + + + + + + + + + + + + +
+ +

Team Dogi · Wie geht's dir?

+
+ … + +
+
+ +
+ + + +
+
+

Für dich

+

Wie geht's dir?

+ +

+ Ein paar Fragen über dich. Deine Antworten liest niemand – auch + DogFather nicht. +

+

+ Das Einzige, was nach außen dringt: ob es im Team gerade + jemandem zu viel ist. Ohne Namen, ohne Frage, ohne Datum – + und erst ab drei Personen im Team, weil es darunter dasselbe wäre + wie ein Name. +

+
+
+ + + + + + +
+

wird geladen …

+
+ + + + + + + + + + +
+ - - - - - - - - + + + + + + + + +