Filipe, zum Bildschirmfoto der Kachel: "ich will dass du diese seite
perfektionnierst den gerade wenn ich drauf druecke geht die
entwicklungsseite auf."
Der Fehler war schlimmer als ein falscher Verweis. Auf der Kachel
steht "die Antworten sieht nur du" -- und sie oeffnete eine Seite
voller Namen und Beobachtungen ueber ANDERE Menschen. Wer ihr glaubt
und drauftippt, sieht im ersten Moment das Gegenteil dessen, was
draufsteht.
Jetzt: eine Seite, ein Zweck.
workspace/befinden.html + assets/js/befinden.js die eigene Seite
server/workspace-befinden.js Rhythmus + Verlauf
entwicklung.html nur noch ein Verweis
Was die Recherche zu Puls-Abfragen ergeben hat (Quellen im Kopf des
Moduls): kurz halten (fuenf bis fuenfzehn Fragen -- sechs bleiben
sechs), WIEDERHOLEN (zweiwoechentlich), und der VERLAUF ist die
Auskunft, nicht der Einzelwert. Bisher beantwortete man die sechs
Fragen einmal, und die Antwort stand fuer immer -- ein Befinden von
vor drei Monaten ist keine Auskunft mehr, sondern ein Andenken.
Der Verlauf gehoert ihr allein: eigene Tabelle, kein von_id, kein
anderer Weg im Haus liest sie. Im Protokoll steht nur, DASS eine
Runde war, nie was darin stand. Die Ampel zaehlt weiterhin nur den
aktuellen Stand und kennt keine Namen.
Zwei Dinge, die erst die Pruefung gefunden hat:
- Wer die neue Seite oeffnete, ohne dass vorher jemand die
Entwicklungsseite besucht hatte, bekam "no such table" und eine
503. entwicklung_stand wird beim ersten Aufruf angelegt, nicht
beim Start. Die Tabelle wird jetzt angefordert, nicht ein zweites
Mal abgeschrieben.
- pruef-entwicklung suchte die Kachel ueber ziel ===
"entwicklung.html" und hat den gemeldeten Fehler damit
mitgetragen. Sie sucht jetzt die eigene Seite -- und prueft
zusaetzlich, dass keine Kachel mehr ersatzweise dorthin fuehrt.
Ausserdem weg: die tote Funktion meins() in entwicklung.js (sie
zeichnete in drei Stellen, die es nicht mehr gibt) und der Titel
"Wie geht's dir?", den diese Seite fuer einen Modi trug -- er
versprach etwas anderes als die Seite zeigt, also derselbe Fehler
wie an der Kachel.
server/pruef-befinden.mjs 48 Pruefungen, 0 Fehler (Port 4419)
pruef-entwicklung 43 (vorher 40), 0 Fehler
pruef-nachwuchs 123, pruef-rechtetafel 19, pruef-rechte-umstellen 46,
pruef-css-klassen -- alle 0 Fehler
Co-Authored-By: Claude Opus 5 <[email protected]>
256 lines
10 KiB
JavaScript
256 lines
10 KiB
JavaScript
/* =====================================================================
|
||
Wie geht's dir? (15.09.2026)
|
||
|
||
Filipe, mit dem Bildschirmfoto der Kachel: *„ich will dass du diese
|
||
seite perfektionnierst, denn gerade wenn ich drauf drücke geht die
|
||
entwicklungsseite auf."*
|
||
|
||
Er hat recht, und es war schlimmer als ein falscher Verweis: Die
|
||
Kachel verspricht „sechs Fragen – die Antworten sieht nur du" und
|
||
öffnete eine Seite mit Namen und Beobachtungen über andere Menschen.
|
||
Wer ihr glaubt und draufdrückt, sieht im ersten Moment das Gegenteil
|
||
dessen, was draufsteht.
|
||
|
||
---------------------------------------------------------------------
|
||
EINE SEITE, EIN ZWECK
|
||
|
||
`entwicklung.html` bediente zwei Fragen: „wie geht es dem Team?" und
|
||
„wie geht es mir?". Das ist eine Seite zu wenig. Die sechs Fragen
|
||
haben jetzt ihre eigene — und `entwicklung.html` wird dadurch selbst
|
||
einfacher, weil sie nur noch eine Sache tut.
|
||
|
||
---------------------------------------------------------------------
|
||
WAS DIE RECHERCHE ERGEBEN HAT (15.09.2026)
|
||
|
||
Zu Puls-/Befindensabfragen in kleinen Teams sagen die Quellen
|
||
übereinstimmend dreierlei:
|
||
|
||
1. KURZ. Fünf bis fünfzehn Fragen, unter zwei Minuten. Sechs sind
|
||
also richtig — daran ändert sich nichts.
|
||
|
||
2. WIEDERHOLT. Monatlich oder zweiwöchentlich. Genau das fehlte:
|
||
Bisher beantwortete man die sechs Fragen EINMAL, und die Antwort
|
||
stand für immer. Ein Befinden von vor drei Monaten ist keine
|
||
Auskunft mehr, sondern ein Andenken.
|
||
|
||
3. DER VERLAUF IST DIE AUSKUNFT, nicht der Einzelwert. „Niedrige
|
||
Energie über mehrere Wochen" ist das Warnzeichen — ein einzelnes
|
||
„da hakt es" ist ein schlechter Tag.
|
||
|
||
Deshalb zwei Dinge neu: ein RHYTHMUS (nach 14 Tagen wird wieder
|
||
gefragt) und ein VERLAUF, den nur die Person selbst sieht.
|
||
|
||
Quellen:
|
||
https://www.specific.app/blog/pulse-survey-questions-best-questions-for-burnout-to-detect-and-address-employee-engagement-risk
|
||
https://formbricks.com/blog/pulse-survey-questions
|
||
https://www.vantagecircle.com/en/blog/pulse-surveys/
|
||
|
||
---------------------------------------------------------------------
|
||
DER VERLAUF GEHÖRT IHR ALLEIN
|
||
|
||
Er wird in einer eigenen Tabelle geführt, und kein anderer Weg im
|
||
Haus liest sie. Die Ampel (wie viele es gerade schwer haben) zählt
|
||
weiterhin nur den AKTUELLEN Stand und kennt keine Namen — daran
|
||
ändert der Verlauf nichts.
|
||
|
||
Das ist kein Nebensatz: Eine Verlaufsakte über das Befinden eines
|
||
Menschen ist das Empfindlichste, was dieses Haus speichern kann. Sie
|
||
existiert nur, weil sie IHM hilft, sein eigenes Muster zu sehen.
|
||
===================================================================== */
|
||
|
||
import express from "express";
|
||
import { db, sitzungLesen, protokolliere, echteIp } from "./workspace.js";
|
||
import {
|
||
ENTWICKLUNG_BLOECKE, ENTWICKLUNG_PUNKTE, ENTWICKLUNG_STUFEN,
|
||
} from "./workspace-entwicklungs-punkte.js";
|
||
import { tabellen as entwicklungTabellen } from "./workspace-entwicklung.js";
|
||
|
||
export const befindenRouter = express.Router();
|
||
|
||
/** Nach wie vielen Tagen wieder gefragt wird.
|
||
*
|
||
* 14 Tage — zweiwöchentlich, das untere Ende dessen, was die Quellen
|
||
* empfehlen (monatlich bis zweiwöchentlich). Häufiger wäre
|
||
* Umfragemüdigkeit: Wer jede Woche gefragt wird, klickt irgendwann
|
||
* dasselbe an wie beim letzten Mal, ohne nachzudenken — und dann misst
|
||
* die Abfrage nur noch sich selbst. */
|
||
export const RHYTHMUS_TAGE = 14;
|
||
|
||
/** So viele frühere Antworten bekommt die Person zu sehen.
|
||
*
|
||
* Sechs Stände sind drei Monate — lang genug, um ein Muster zu sehen,
|
||
* kurz genug, dass es kein Archiv wird. */
|
||
export const VERLAUF_MAX = 6;
|
||
|
||
const jetzt = () => new Date().toISOString();
|
||
|
||
let bereit = false;
|
||
function tabellen() {
|
||
if (bereit) return;
|
||
db().exec(`
|
||
/* DER VERLAUF DES EIGENEN BEFINDENS.
|
||
|
||
Eigene Tabelle und nicht „entwicklung_stand“: Dort steht der
|
||
AKTUELLE Stand, einer je Punkt und Beurteiler, und der wird
|
||
ueberschrieben. Genau das ist dort richtig -- eine Beobachtung
|
||
ueber einen Menschen soll aktuell sein, nicht historisch.
|
||
|
||
Hier ist es umgekehrt: Der Verlauf IST die Auskunft. Zwei
|
||
verschiedene Beduerfnisse, zwei Tabellen. Sie in eine zu zwingen
|
||
hiesse, eines von beiden falsch zu machen.
|
||
|
||
KEIN von_id: Was hier steht, hat die Person ueber sich selbst
|
||
gesagt. Eine Spalte fuer "wer hat das eingetragen" waere die
|
||
Einladung, eines Tages auch jemand anderen eintragen zu lassen. */
|
||
CREATE TABLE IF NOT EXISTS befinden_verlauf (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
person_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE,
|
||
runde TEXT NOT NULL, -- Zeitpunkt der Runde, alle sechs teilen ihn
|
||
schluessel TEXT NOT NULL,
|
||
stufe TEXT NOT NULL,
|
||
am TEXT NOT NULL
|
||
);
|
||
CREATE INDEX IF NOT EXISTS idx_befinden_person
|
||
ON befinden_verlauf (person_id, runde);
|
||
`);
|
||
/* UND DIE TABELLE, AUS DER DIE ANTWORTEN KOMMEN.
|
||
|
||
Sie wird hier nicht angelegt, sondern angefordert: Die sechs
|
||
Antworten stehen in `entwicklung_stand`, und die legt die
|
||
Entwicklungsseite an -- beim ersten Aufruf, nicht beim Start.
|
||
Wer die neue Seite oeffnete, bevor irgendjemand die
|
||
Entwicklungsseite besucht hatte, bekam deshalb "no such table"
|
||
und eine 503. Gefunden hat das die Pruefung, nicht das Lesen. */
|
||
entwicklungTabellen();
|
||
bereit = true;
|
||
}
|
||
|
||
function angemeldet(req, res, next) {
|
||
const person = sitzungLesen(req);
|
||
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
|
||
req.person = person;
|
||
try { tabellen(); } catch (f) {
|
||
console.error("[befinden] Tabellen:", f?.message);
|
||
return res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
next();
|
||
}
|
||
|
||
/** Der Block mit den sechs Fragen. Er ist als einziger `nurSelbst`. */
|
||
function block() {
|
||
return ENTWICKLUNG_BLOECKE.find((b) => b.nurSelbst) || null;
|
||
}
|
||
|
||
/** Wann war die letzte vollständige Runde — und ist wieder Zeit? */
|
||
function rhythmus(personId) {
|
||
const b = block();
|
||
const gesamt = (ENTWICKLUNG_PUNKTE[b.schluessel] || []).length;
|
||
const letzte = db().prepare(
|
||
`SELECT runde, COUNT(*) AS n FROM befinden_verlauf
|
||
WHERE person_id = ? GROUP BY runde ORDER BY runde DESC LIMIT 1`).get(personId);
|
||
if (!letzte) return { letzte: null, faellig: true, tage: null, vollstaendig: false };
|
||
const tage = Math.floor((Date.now() - Date.parse(letzte.runde)) / 86400000);
|
||
return {
|
||
letzte: letzte.runde,
|
||
tage,
|
||
vollstaendig: letzte.n >= gesamt,
|
||
/* FAELLIG heisst: die letzte Runde ist alt genug -- ODER sie war nie
|
||
fertig. Eine halb beantwortete Runde ist keine Runde. */
|
||
faellig: tage >= RHYTHMUS_TAGE || letzte.n < gesamt,
|
||
};
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE SEITE
|
||
===================================================================== */
|
||
|
||
befindenRouter.get("/workspace/api/befinden", angemeldet, (req, res) => {
|
||
try {
|
||
const b = block();
|
||
if (!b) return res.status(503).json({ fehler: "kein_block" });
|
||
|
||
const meine = new Map(db().prepare(
|
||
`SELECT schluessel, stufe, anlass, am FROM entwicklung_stand
|
||
WHERE person_id = ? AND von_id = ?`)
|
||
.all(req.person.id, req.person.id).map((z) => [z.schluessel, z]));
|
||
|
||
/* DER VERLAUF -- nach Runden gebuendelt, neueste zuerst. */
|
||
const runden = db().prepare(
|
||
`SELECT runde FROM befinden_verlauf WHERE person_id = ?
|
||
GROUP BY runde ORDER BY runde DESC LIMIT ?`).all(req.person.id, VERLAUF_MAX)
|
||
.map((z) => z.runde);
|
||
const zeilen = runden.length
|
||
? db().prepare(
|
||
`SELECT runde, schluessel, stufe FROM befinden_verlauf
|
||
WHERE person_id = ? AND runde IN (${runden.map(() => "?").join(",")})`)
|
||
.all(req.person.id, ...runden)
|
||
: [];
|
||
const verlauf = runden.map((r) => ({
|
||
runde: r,
|
||
antworten: Object.fromEntries(
|
||
zeilen.filter((z) => z.runde === r).map((z) => [z.schluessel, z.stufe])),
|
||
}));
|
||
|
||
res.json({
|
||
block: b,
|
||
stufen: ENTWICKLUNG_STUFEN,
|
||
rhythmus_tage: RHYTHMUS_TAGE,
|
||
rhythmus: rhythmus(req.person.id),
|
||
punkte: (ENTWICKLUNG_PUNKTE[b.schluessel] || []).map((pt) => ({
|
||
...pt, meins: meine.get(pt.schluessel) || null,
|
||
})),
|
||
verlauf,
|
||
});
|
||
} catch (f) {
|
||
console.error("[befinden] Lesen:", f?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|
||
|
||
/** Eine Runde abschließen: alle sechs Antworten wandern in den Verlauf.
|
||
*
|
||
* ALS RUNDE UND NICHT JE KLICK: Ein Verlauf, der jeden einzelnen Klick
|
||
* festhält, zeigt kein Muster, sondern Rauschen — und er würde jedes
|
||
* Zögern mitschreiben. Was zählt, ist der Stand, wenn jemand fertig
|
||
* ist. */
|
||
befindenRouter.post("/workspace/api/befinden/runde", express.json({ limit: "8kb" }),
|
||
angemeldet, (req, res) => {
|
||
try {
|
||
const b = block();
|
||
const punkte = ENTWICKLUNG_PUNKTE[b.schluessel] || [];
|
||
const meine = new Map(db().prepare(
|
||
`SELECT schluessel, stufe FROM entwicklung_stand
|
||
WHERE person_id = ? AND von_id = ?`)
|
||
.all(req.person.id, req.person.id).map((z) => [z.schluessel, z.stufe]));
|
||
|
||
const fehlt = punkte.filter((p) => !meine.has(p.schluessel));
|
||
if (fehlt.length) {
|
||
/* EINE HALBE RUNDE IST KEINE RUNDE. Sie wuerde im Verlauf wie
|
||
ein vollstaendiger Stand aussehen und das Muster verfaelschen. */
|
||
return res.status(400).json({
|
||
fehler: "unvollstaendig", offen: fehlt.length, gesamt: punkte.length,
|
||
});
|
||
}
|
||
|
||
const runde = jetzt();
|
||
const rein = db().prepare(
|
||
`INSERT INTO befinden_verlauf (person_id, runde, schluessel, stufe, am)
|
||
VALUES (?,?,?,?,?)`);
|
||
for (const p of punkte) rein.run(req.person.id, runde, p.schluessel, meine.get(p.schluessel), runde);
|
||
|
||
/* IM PROTOKOLL STEHT NUR, DASS es passiert ist -- nicht was.
|
||
Das Protokoll liest DogFather; die Antworten gehen ihn nichts
|
||
an, und ein "detail" mit Stufen darin waere genau die
|
||
Hintertuer, die es hier nicht geben darf. */
|
||
protokolliere("befinden_runde", {
|
||
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
|
||
detail: `${punkte.length} Fragen`,
|
||
});
|
||
|
||
res.status(201).json({ runde, anzahl: punkte.length, rhythmus: rhythmus(req.person.id) });
|
||
} catch (f) {
|
||
console.error("[befinden] Runde:", f?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|