Ausgeloest von pruef-modi-wortleck, die seit Tagen rot war: "24
Fundstellen -- der verborgene Zugang waere damit auffindbar." Die
erste Frage war, ob sie recht hat. Gemessen: ja, und schlimmer als
gedacht.
WER DIESE DATEIEN BEKOMMT: Nicht nur Kollegen. `curl` ohne jeden Keks
auf workspace.dogfather-universe.com liefert die Skripte und
Stilvorlagen des Arbeitsplatzes mit HTTP 200 -- fuer jeden im Netz
lesbar.
DARUNTER crew-haus.css, 27 806 Byte, das ganze Farbhaus von Team Dogi
samt seiner Kommentare. In deren Kopf stand seit dem 10.09. woertlich
"Diese Datei wird NUR auf crew.dogfather-universe.com ausgeliefert."
Das war eine Behauptung, keine Tatsache: Die Weiche biegt `haus.css`
auf diesen Namen um, wer den Namen direkt nannte, ging an ihr vorbei.
Ein Kommentar, der eine Absicht beschreibt, erfuellt sie nicht.
Zwei Stellen im Haus haben sich damit widersprochen -- die Datei sagte
"nur auf crew.", pruef-crew-adresse verlangte ausdruecklich das
Gegenteil ("sie ist kein Geheimnis"). Durchgesetzt war die offene
Fassung. Filipe hat am 21.09. entschieden: zusperren. Die alte
Begruendung dagegen war die Sorge, eine Sperre koenne die Pruefung
blind machen -- die ist jetzt beantwortet statt vermieden: Jeder
Eintrag der Tafel wird in BEIDE Richtungen gemessen, und dass die
Umbiegung von `haus.css` auf crew. weiterhin greift, ebenfalls.
DIE ADRESSE WAR DAS EIGENTLICHE GEHEIMNIS, nicht das Wort. Die
Rollennamen stehen auf der Zugangswand von Team Dogi ohnehin offen --
so gewollt. WO sie liegt, stand dagegen im Klartext in gate.css,
gate.js und crew-haus.css, alle drei oeffentlich abrufbar. Jetzt steht
sie in keiner ausgelieferten Datei mehr.
DIE PRUEFUNG SELBST HATTE DREI MAENGEL:
1. Sie kannte die Mehrzahl nicht. `\bmodi\b` fand "ein Modi", nicht
"die Modis" -- 23 weitere Stellen, darunter der Satz "Fuer die
rechte Hand und die Modis gibt es dort den stillen Zugang". Also
genau die Auskunft, die sie verhindern soll, in der Form, die sie
nicht sah.
2. Ihre Ausnahmeliste war abgeschrieben. Sie nannte eine Datei; die
zweite, die der Server auf crew. begrenzt, haette sie nie
erfahren. Ausgenommen ist jetzt genau das, was der Server auch
durchsetzt (GEHOERT_ZU_ADRESSE) -- kein Muster, sondern die
Durchsetzung selbst.
3. Sie mahnte 45 Kommentare an. Filipes Entscheidung: in Kommentaren
ja, in Code und sichtbaren Texten nein. Rot, das immer da ist,
wird ueberlesen -- und dann faengt sie auch den echten Fall nicht.
Dafuer neu: helfer-ohne-kommentar.mjs, ein Zerleger, der Kommentare
zeichengenau durch Leerzeichen ersetzt, ohne Zeichenketten,
Vorlagen oder Suchmuster zu beschaedigen. KEIN Suchausdruck -- genau
daran ist am 20.09. die Portumstellung gescheitert, mit gueltigem
JavaScript ohne das Feld `port`. Dreizehn Proben in beide Richtungen
beweisen, dass er weder zu viel noch zu wenig wegnimmt; ein Zerleger,
der zu viel nimmt, macht die ganze Pruefung STILL gruen.
Von 45 blieben damit drei echte, alle behoben:
- daten.modis -> daten.leute (Feldname in der Auskunft, also Code)
- zwei Saetze, die Menschen lesen, auf "die Moderation" umgestellt --
fuer jemanden, der neu im Treff ist, sogar klarer
Nebenbei, beim selben Durchgang gefunden und geschlossen:
- teamlage.js fiel auf einen Rollennamen zurueck. Nachgemessen: Die
Stilvorlage kennt zu dieser Karte gar keine solche Regel -- der
Rueckfall hat nie etwas bewirkt und nur das Wort ausgeliefert.
- werdegang.js fuehrte die Saetze ueber den Rollengruppen selbst,
mit den Rollennamen als Schluessel. Der Kommentar daneben
behauptete sogar, sie seien "abgeleitet, nicht abgeschrieben".
Sie kommen jetzt vom Server, und pruef-werdegang misst, dass
jede Gruppe ihren eigenen behaelt.
pruef-modi-wortleck 8/0 (war 5, davon 1 rot)
pruef-crew-adresse 144/0 (war 135)
pruef-werdegang 98 statt 95 Pruefungen, dieselben 3 alten Fehler
pruef-team-ampel 32/0, pruef-team-stufen 28/0, pruef-hilfe 84/0
Co-Authored-By: Claude Opus 5 <[email protected]>
804 lines
36 KiB
JavaScript
804 lines
36 KiB
JavaScript
/* =====================================================================
|
||
ENTWICKLUNG — die Auswertung je Person (19.09.2026)
|
||
|
||
Filipe, mit dem Bildschirmfoto der alten Kachel:
|
||
|
||
„diese kategorie ist ja die aufgaben die ich an die modis habe,
|
||
also eher aufgabe und dan perfektionierst du eine neue wo
|
||
diagramme texte und so zu jedem modi gemacht wird und diese
|
||
kategorie kannst du entwicklung nenen.“
|
||
|
||
Die Umbenennung war der erste Teil und ist seit dem 19.09. früh
|
||
erledigt: Die alte Kachel heißt „Eure Aufgaben“, weil sie das ist —
|
||
ein Katalog dessen, was ein Modi können soll. Hier kommt der zweite
|
||
Teil: die Auswertung, die es noch nie gab.
|
||
|
||
---------------------------------------------------------------------
|
||
WARUM DIESE DATEI NICHT „entwicklung.js“ HEISST
|
||
|
||
`workspace-entwicklung.js` gibt es seit dem 11.09. und es ist der
|
||
KATALOG. Diese Datei ist die AUSWERTUNG darüber. Zwei Dateien mit
|
||
fast demselben Namen wären die zuverlässigste Art, in einem halben
|
||
Jahr die falsche zu ändern.
|
||
|
||
Dasselbe gilt für die Seite: `entwicklung.html` ist der Katalog
|
||
(„Eure Aufgaben“), `werdegang.html` ist diese Auswertung
|
||
(„Entwicklung“). Dass die Kachel anders heißt als die Datei, ist
|
||
Absicht und geprüft — `pruef-werdegang` verlangt, dass die Kachel
|
||
„Entwicklung“ auf `werdegang.html` zeigt und nicht woanders hin.
|
||
Ein Umbenennen der alten Datei wäre die sauberere Endform, aber es
|
||
wären zwei Änderungen in einer: Wenn danach etwas klemmt, weiß
|
||
niemand, welche von beiden es war.
|
||
|
||
---------------------------------------------------------------------
|
||
DAS EIGENTLICHE PROBLEM: ES GAB KEINE VERGANGENHEIT
|
||
|
||
`entwicklung_stand` hat den Schlüssel (person, punkt, beurteiler)
|
||
und wird bei jeder Änderung ÜBERSCHRIEBEN. Das ist dort richtig —
|
||
eine Beobachtung über einen Menschen soll aktuell sein, nicht
|
||
historisch. Nur: Damit existiert nichts, woraus man einen Verlauf
|
||
zeichnen könnte. Am 19.09.2026 nachgemessen standen 43 Zeilen in der
|
||
Tabelle und NULL Zeilen Geschichte.
|
||
|
||
Ein Diagramm über einen einzigen Zeitpunkt ist kein Diagramm,
|
||
sondern ein Balken. Deshalb legt diese Datei als Erstes an, was
|
||
gefehlt hat: eine Spur, die mitschreibt, statt zu überschreiben.
|
||
|
||
---------------------------------------------------------------------
|
||
WAS HIER AUSDRÜCKLICH NICHT ENTSTEHT
|
||
|
||
Keine Punktzahl. Kein Prozentwert. Kein Durchschnitt. Keine
|
||
Rangliste. Kein Vergleich zwischen zwei Menschen — nirgends, auch
|
||
nicht versteckt als „Team-Mittelwert“.
|
||
|
||
Das ist keine Vorsicht, das ist die Recherche:
|
||
|
||
* ÜBERRECHTFERTIGUNG (Deci 1971, Selbstbestimmungstheorie). Wer
|
||
für etwas, das er gern tut, eine äußere Bewertung bekommt,
|
||
verliert die innere Motivation. Die Ausnahme — und die ist der
|
||
ganze Trick — ist Rückmeldung, die KOMPETENZ BESTÄTIGT, ohne eine
|
||
Zahl daneben.
|
||
* SOZIALER VERGLEICH. Beurteilungen, die Menschen nebeneinander
|
||
stellen, erzeugen Neid und senken die Leistung derer, die
|
||
schlechter dastehen. Ein Balken „Diene 71 %, VanVan 88 %“ wäre
|
||
genau das, und er wäre nie wieder aus den Köpfen zu bekommen.
|
||
* SCHWÄCHENFOKUS. Rückmeldung, die auf Defizite zeigt, senkt die
|
||
Leistung messbar. Deshalb steht hier IMMER zuerst, was sich
|
||
bewegt hat und was trägt — und erst danach, was liegt.
|
||
|
||
Quellen:
|
||
https://hbr.org/2011/05/the-power-of-small-wins
|
||
https://www.simplypsychology.org/self-determination-theory.html
|
||
https://www.profit.co/blog/performance-management/adverse-effects-of-employee-comparison-during-performance-appraisals/
|
||
https://www.cambridge.org/core/journals/industrial-and-organizational-psychology/article/abs/working-with-social-comparisons-in-the-appraisal-and-management-of-performance/2366D9737F8A543A16E6C884D1E8A5F8
|
||
|
||
---------------------------------------------------------------------
|
||
WAS STATTDESSEN GEZEIGT WIRD: BEWEGUNG
|
||
|
||
Amabile und Kramer haben rund 12 000 Tagebucheinträge von 238
|
||
Menschen ausgewertet. Das Ergebnis, das seitdem „progress principle“
|
||
heißt: Der stärkste einzelne Treiber guter Arbeit ist SICHTBARER
|
||
FORTSCHRITT in einer Sache, die einem wichtig ist. Nicht Lob, nicht
|
||
Belohnung, nicht Druck — und die Schritte dürfen klein sein.
|
||
|
||
Deshalb ist die erste Zahl auf dieser Seite nicht, WIE GUT jemand
|
||
ist, sondern WAS SICH BEWEGT HAT. Und die zweite sagt etwas über
|
||
UNS: wie alt die Einschätzungen sind. Eine Karte, die seit vier
|
||
Monaten niemand angefasst hat, ist keine Auskunft über den
|
||
Menschen — sie ist eine über die, die hinsehen sollten.
|
||
|
||
---------------------------------------------------------------------
|
||
DER ANLASS KOMMT NICHT MIT
|
||
|
||
In `entwicklung_stand` steht neben jedem „da hakt es“ ein Satz, woran
|
||
man es gemerkt hat. Der ist für ein Gespräch aufgeschrieben. Eine
|
||
CHRONIK dieser Sätze wäre das Belastendste, was dieses Haus
|
||
speichern könnte — jede alte Beobachtung bliebe auf Dauer lesbar,
|
||
auch die, die längst geklärt ist.
|
||
|
||
Die Spur hier merkt sich deshalb nur: welcher Punkt, welche Stufe,
|
||
wann, von wem. Kein Anlass, nie. Dasselbe Prinzip wie beim
|
||
Befinden-Verlauf — dort steht im Protokoll auch nur, DASS eine Runde
|
||
war.
|
||
===================================================================== */
|
||
|
||
import express from "express";
|
||
import {
|
||
db, sitzungLesen, fuehrtTeamDogi, ROLLEN_NAME, ROLLEN_SORTIERUNG,
|
||
} from "./workspace.js";
|
||
import {
|
||
ENTWICKLUNG_BLOECKE, ENTWICKLUNG_PUNKTE, ENTWICKLUNG_STUFEN,
|
||
ENTWICKLUNG_ERWARTUNG, ENTWICKLUNG_FREMD, ENTWICKLUNG_NACH_SCHLUESSEL,
|
||
} from "./workspace-entwicklungs-punkte.js";
|
||
/* NUR IN EINE RICHTUNG. Diese Datei liest aus `workspace-entwicklung.js`;
|
||
jene weiß von dieser nichts. Die Spur-Tabelle steht deshalb DORT, bei
|
||
`entwicklung_stand`, zu dem sie gehört — nicht hier.
|
||
|
||
Der bequeme Weg wäre gewesen, die Tabelle hier anzulegen und von dort
|
||
ein `spurLegen()` zu importieren. Das ergäbe einen Ring zwischen zwei
|
||
Modulen, und Ringe funktionieren in JavaScript so lange, bis jemand
|
||
eine Konstante auf oberster Ebene benutzt statt in einer Funktion.
|
||
Dann kommt ein Fehler, den niemand mit dem Import in Verbindung
|
||
bringt. */
|
||
import {
|
||
VERBLASST_NACH_TAGEN, tabellen as entwicklungTabellen,
|
||
} from "./workspace-entwicklung.js";
|
||
|
||
/* WAS UEBER EINER GRUPPE STEHT (hierher geholt am 21.09.2026).
|
||
|
||
Vorher stand das in werdegang.js, also in einer ausgelieferten
|
||
Datei. Hier steht es richtig: Der Server schickt jeder Person ihren
|
||
Satz mit, und wer die Rolle nicht sehen darf, bekommt die Person
|
||
ohnehin nicht.
|
||
|
||
NUR ZWEI EINTRAEGE, UND DAS IST ABSICHT. Manager, Scouts und
|
||
Creator stehen im Werdegang unter ihrem Namen, ohne Erklaerung --
|
||
dort ist die Ueberschrift selbsterklaerend. Ein Satz um des Satzes
|
||
willen waere Fuellung. */
|
||
const ROLLEN_SATZ = {
|
||
hand: "Koordiniert den Alltag und schaut mit dir zusammen auf das Team.",
|
||
modi: "Die Leute, die im Chat und im Live stehen.",
|
||
};
|
||
|
||
export const werdegangRouter = express.Router();
|
||
|
||
const TAG = 86400000;
|
||
|
||
/** Die Rollen, über die eine Karte geführt wird.
|
||
*
|
||
* ABGELEITET WÄRE HIER FALSCH, und das ist die Ausnahme: Es ist
|
||
* dieselbe Liste wie in `workspace-entwicklung.js`, und sie MUSS
|
||
* dieselbe sein — eine Auswertung über jemanden, der gar keine Karte
|
||
* hat, wäre leer. Sie steht trotzdem hier noch einmal, weil ein
|
||
* Import nur wegen zweier Zeichenketten die beiden Module
|
||
* aneinanderbindet; `pruef-werdegang` vergleicht sie und wird rot,
|
||
* wenn sie auseinanderlaufen. */
|
||
export const KARTEN_ROLLEN = ["hand", "modi"];
|
||
|
||
/** Wie viele Monatsspalten der Verlauf zeigt.
|
||
*
|
||
* SECHS. Genug, um ein halbes Jahr zu sehen — und wenig genug, dass
|
||
* die Spalten auf einem Handy noch lesbar sind. Mehr Spalten heißt
|
||
* nicht mehr Auskunft: Was vor einem Jahr war, ist für die Frage
|
||
* „bewegt sich etwas?“ ohne Belang. */
|
||
export const MONATE = 6;
|
||
|
||
/** Ab wann ein „läuft“ als getragen gilt und ausdrücklich gesagt wird.
|
||
*
|
||
* 60 TAGE. Anerkennung wirkt nur, wenn sie sich auf etwas Bestimmtes
|
||
* bezieht — „du machst das gut“ ist Höflichkeit, „das steht seit zwei
|
||
* Monaten und ist nie gekippt“ ist eine Beobachtung. Unter zwei
|
||
* Monaten wäre es ein Zwischenstand, kein Befund. */
|
||
export const TRAEGT_AB_TAGEN = 60;
|
||
|
||
/** Ab wann ein „da hakt es“ von selbst zur Sprache kommt.
|
||
*
|
||
* 30 TAGE OHNE BEWEGUNG. Die Hausregel dazu steht im Plan vom 11.09.:
|
||
* „Ein ‚Da hakt es', das nirgends hinführt, ist eine Notiz über einen
|
||
* Menschen — und die will hier niemand sammeln.“ */
|
||
export const LIEGT_AB_TAGEN = 30;
|
||
|
||
/** Ab wie vielen Monatsspalten mit Inhalt überhaupt etwas gezeichnet wird.
|
||
*
|
||
* ZWEI. Ein Punkt ist kein Verlauf. Darunter sagt die Seite das —
|
||
* sie zeigt keine Linie zwischen einem einzigen Wert und dem Rand.
|
||
* Das ist der dritte Ausgang: nicht in Ordnung / in Ordnung / KONNTE
|
||
* NOCH NICHT NACHSEHEN, mit Grund. */
|
||
export const VERLAUF_AB = 2;
|
||
|
||
/* ---------------------------------------------------------------------
|
||
DIE SPUR LIEGT NEBENAN
|
||
|
||
`entwicklung_lauf` wird in `workspace-entwicklung.js` angelegt und
|
||
dort auch geschrieben — bei `entwicklung_stand`, zu dem sie gehört.
|
||
Diese Datei liest sie nur. Ein `tabellen()` hier wäre eine zweite
|
||
Stelle, an der dieselbe Tabelle entstehen kann, und die zweite ist
|
||
immer die, die eine Spalte vergisst.
|
||
------------------------------------------------------------------- */
|
||
function tabellen() { entwicklungTabellen(); }
|
||
|
||
/* ---------------------------------------------------------------------
|
||
RECHNEN
|
||
------------------------------------------------------------------- */
|
||
|
||
/** Die Rangfolge der drei aussagekräftigen Stufen.
|
||
*
|
||
* „Kann ich nicht sagen“ hat KEINEN Rang und bekommt auch keinen. Es
|
||
* ist keine schlechtere Antwort als „läuft“, sondern eine andere Art
|
||
* von Antwort — sie sagt etwas über den Beurteiler, nicht über den
|
||
* Beurteilten. Ihr eine Zahl zu geben hieße, sie in dieselbe Reihe zu
|
||
* stellen, und ab da würde „ich habe es nicht gesehen“ wie ein
|
||
* halbes „da hakt es“ gelesen. */
|
||
const RANG = { hakt: 0, waechst: 1, laeuft: 2 };
|
||
|
||
/** Wie ein Übergang zu nennen ist. Gerechnet, nicht gespeichert. */
|
||
export function artVon(vorher, stufe, nachgetragen) {
|
||
if (nachgetragen) return "anfang";
|
||
if (!vorher) return "neu";
|
||
if (vorher === stufe) return "gleich";
|
||
if (vorher === "unklar") return "geklaert";
|
||
if (stufe === "unklar") return "offen";
|
||
const a = RANG[vorher], b = RANG[stufe];
|
||
if (a === undefined || b === undefined) return "gleich";
|
||
return b > a ? "vor" : "zurueck";
|
||
}
|
||
|
||
/** Der Monat eines Zeitpunkts in Ortszeit — „2026-09“.
|
||
*
|
||
* ÜBER Intl UND NICHT ÜBER slice(0,7). `toISOString()` rechnet in UTC;
|
||
* zwischen Mitternacht und zwei Uhr liefert das den VORMONAT, und zwar
|
||
* genau am Monatsersten. Ein Diagramm, das in den ersten Stunden des
|
||
* Monats eine Spalte zu wenig hat, fällt niemandem auf — es sieht
|
||
* einfach nach wenig Bewegung aus. Dieselbe Falle hat am 06.09. eine
|
||
* Prüfung zwei Stunden pro Tag rot gemacht. */
|
||
const MONATSFORM = new Intl.DateTimeFormat("sv-SE", {
|
||
timeZone: "Europe/Berlin", year: "numeric", month: "2-digit",
|
||
});
|
||
export function monatVon(iso) {
|
||
const t = Date.parse(iso || "");
|
||
if (!Number.isFinite(t)) return null;
|
||
return MONATSFORM.format(new Date(t)).slice(0, 7);
|
||
}
|
||
|
||
/** Die letzten N Monate, älteste zuerst — „2026-04“ … „2026-09“. */
|
||
export function monatsspalten(anzahl = MONATE, bis = new Date()) {
|
||
const spalten = [];
|
||
/* ÜBER DEN 15. GERECHNET und nicht über den heutigen Tag: Wer am
|
||
31. März einen Monat abzieht, landet in JavaScript im März (der
|
||
31. Februar existiert nicht und rollt über). Der 15. ist in jedem
|
||
Monat vorhanden. */
|
||
const roh = monatVon(bis.toISOString()) || monatVon(new Date().toISOString());
|
||
const [j, m] = roh.split("-").map(Number);
|
||
for (let i = anzahl - 1; i >= 0; i--) {
|
||
const d = new Date(Date.UTC(j, m - 1 - i, 15, 12));
|
||
spalten.push(`${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`);
|
||
}
|
||
return spalten;
|
||
}
|
||
|
||
const tageSeit = (iso) => {
|
||
const t = Date.parse(iso || "");
|
||
return Number.isFinite(t) ? Math.floor((Date.now() - t) / TAG) : null;
|
||
};
|
||
|
||
/** Alle Spur-Zeilen einer Person, älteste zuerst. */
|
||
function spurVon(personId) {
|
||
return db().prepare(
|
||
`SELECT l.schluessel, l.von_id, l.stufe, l.vorher, l.nachgetragen, l.am,
|
||
p.name AS von_name
|
||
FROM entwicklung_lauf l
|
||
LEFT JOIN personen p ON p.id = l.von_id
|
||
WHERE l.person_id = ?
|
||
ORDER BY l.am ASC, l.id ASC`).all(personId);
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIAGRAMM 1 — WAS SICH BEWEGT HAT
|
||
|
||
Je Monat: wie viele Punkte sind nach vorn gegangen, wie viele zurück,
|
||
wie viele wurden zum ersten Mal angesehen.
|
||
|
||
ES IST BEWUSST EIN DIAGRAMM MIT ZWEI RICHTUNGEN und nicht zwei
|
||
nebeneinander: Vorwärts und rückwärts sind dieselbe Größe mit
|
||
verschiedenem Vorzeichen. Zwei getrennte Diagramme würden dazu
|
||
verleiten, sie zu vergleichen, als wären es zwei Themen.
|
||
|
||
RÜCKWÄRTS IST NICHT ROT. Auf dieser Seite gibt es kein Rot — das ist
|
||
seit dem 11.09. die wichtigste Farbentscheidung des Bereichs. Ein
|
||
Rückschritt ist eine Beobachtung, kein Alarm, und oft genug heißt er
|
||
nur, dass jemand genauer hingesehen hat als beim letzten Mal.
|
||
===================================================================== */
|
||
export function bewegungVon(spur, spalten) {
|
||
const leer = () => ({ vor: 0, zurueck: 0, neu: 0, geklaert: 0, offen: 0 });
|
||
const nach = new Map(spalten.map((s) => [s, leer()]));
|
||
|
||
for (const z of spur) {
|
||
const m = monatVon(z.am);
|
||
if (!nach.has(m)) continue;
|
||
const art = artVon(z.vorher, z.stufe, z.nachgetragen);
|
||
/* „anfang“ und „gleich“ zählen nicht als Bewegung. Die
|
||
Startaufnahme ist keine Veränderung, sondern der Nullpunkt — und
|
||
wer denselben Wert noch einmal setzt, hat bestätigt, nicht
|
||
bewegt. Beides mitzuzählen wäre der bequemste Weg zu einem
|
||
Diagramm, das immer etwas zeigt. */
|
||
if (art === "anfang" || art === "gleich") continue;
|
||
nach.get(m)[art] += 1;
|
||
}
|
||
|
||
return spalten.map((m) => ({ monat: m, ...nach.get(m) }));
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIAGRAMM 2 — DER STREIFEN
|
||
|
||
Eine Zeile je Punkt, eine Spalte je Monat, jede Zelle die Stufe, die
|
||
AM ENDE DIESES MONATS galt.
|
||
|
||
FORTGESCHRIEBEN, NICHT LÜCKENHAFT: Wenn im Juli niemand etwas
|
||
geändert hat, galt im Juli weiter, was im Juni gesetzt wurde. Eine
|
||
leere Zelle würde „keine Einschätzung“ bedeuten, und das wäre falsch.
|
||
|
||
ABER SICHTBAR ALT: Eine fortgeschriebene Zelle, deren Ursprung länger
|
||
als 90 Tage zurückliegt, wird gedämpft gezeichnet. Sonst sähe eine
|
||
vier Monate alte Einschätzung genauso frisch aus wie die von gestern
|
||
— und das ist die Lüge mit Zeitstempel, vor der der Plan vom 11.09.
|
||
ausdrücklich warnt.
|
||
|
||
DIE FARBE IST NICHT DIE AUSKUNFT. Jede Zelle trägt das Zeichen ihrer
|
||
Stufe (✓ ↗ ! –) als Text. Wer Farben schlecht unterscheidet, liest
|
||
dasselbe. Das ist im Haus seit dem Befinden-Verlauf so und bleibt so.
|
||
===================================================================== */
|
||
export function streifenVon(spur, spalten, beurteilerId) {
|
||
/* NUR DER BLICK EINES BEURTEILERS je Streifen. Zwei Meinungen in
|
||
einer Zelle zu mischen hieße, einen Mittelwert zu bilden — und ein
|
||
Mittelwert aus „läuft“ und „da hakt es“ ist „wächst gerade“, was
|
||
niemand gesagt hat. Die Abweichung zwischen den beiden ist das
|
||
Ergebnis (so steht es im Plan) und hat ihren eigenen Platz. */
|
||
const meine = spur.filter((z) => z.von_id === beurteilerId);
|
||
if (!meine.length) return [];
|
||
|
||
/* Nur Punkte, zu denen es überhaupt etwas gibt — der ganze Katalog
|
||
wäre eine Wand aus Strichen. */
|
||
const punkte = [...new Set(meine.map((z) => z.schluessel))];
|
||
const grenze = Date.now() - VERBLASST_NACH_TAGEN * TAG;
|
||
|
||
return punkte.map((schluessel) => {
|
||
const pt = ENTWICKLUNG_NACH_SCHLUESSEL.get(schluessel);
|
||
const zeilen = meine.filter((z) => z.schluessel === schluessel);
|
||
let stufe = null, seit = null;
|
||
const zellen = spalten.map((m) => {
|
||
/* Alles, was BIS EINSCHLIESSLICH dieses Monats passiert ist. */
|
||
for (const z of zeilen) {
|
||
if (monatVon(z.am) <= m) { stufe = z.stufe; seit = z.am; }
|
||
}
|
||
if (!stufe) return { monat: m, stufe: null };
|
||
return {
|
||
monat: m, stufe, seit,
|
||
/* Nur die LETZTE Spalte kann verblassen — bei den früheren
|
||
sagt die Spalte selbst schon, wann es war. Ein gedämpfter
|
||
Märzwert im März wäre Unsinn. */
|
||
alt: m === spalten.at(-1) && Date.parse(seit) < grenze,
|
||
};
|
||
});
|
||
return {
|
||
schluessel,
|
||
titel: pt?.titel || schluessel,
|
||
block: pt?.block || null,
|
||
zellen,
|
||
};
|
||
});
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE TEXTE
|
||
|
||
Filipe wollte „diagramme texte und so“. Die Texte sind der Teil, den
|
||
ein Diagramm nicht kann: Es zeigt, DASS sich etwas bewegt hat — nicht
|
||
WAS, und schon gar nicht, was man damit macht.
|
||
|
||
DIE REIHENFOLGE IST DIE AUSSAGE, und sie ist aus der Forschung
|
||
abgeleitet, nicht aus Höflichkeit:
|
||
|
||
1. WAS SICH BEWEGT HAT — progress principle: sichtbarer
|
||
Fortschritt ist der stärkste Treiber.
|
||
2. WAS TRÄGT — Anerkennung wirkt nur konkret; deshalb
|
||
mit Punkt und Dauer, nicht als Lob.
|
||
3. WAS LIEGT — erst an dritter Stelle. Schwächenfokus
|
||
senkt die Leistung messbar.
|
||
4. WAS NIEMAND ANSIEHT — die einzige Zeile, die von UNS handelt.
|
||
5. WAS ALS NÄCHSTES KOMMT— der Weg nach vorn, nicht die Lücke.
|
||
|
||
JEDER ABSATZ NENNT DEN PUNKT, auf den er sich bezieht. Ein Satz wie
|
||
„es gibt Bewegung“ ist keine Auskunft, sondern eine Behauptung.
|
||
===================================================================== */
|
||
|
||
const zaehl = (n, eins, viele) => (n === 1 ? eins : viele.replace("%n", n));
|
||
|
||
export function texteVon({ spur, beurteilerId, stand, person }) {
|
||
const texte = [];
|
||
const meine = spur.filter((z) => z.von_id === beurteilerId);
|
||
const titel = (s) => ENTWICKLUNG_NACH_SCHLUESSEL.get(s)?.titel || s;
|
||
|
||
/* ---- 1. Was sich bewegt hat ------------------------------------- */
|
||
const bewegt = meine.filter((z) => {
|
||
const art = artVon(z.vorher, z.stufe, z.nachgetragen);
|
||
return art === "vor" || art === "zurueck" || art === "geklaert";
|
||
});
|
||
const vor = bewegt.filter((z) => artVon(z.vorher, z.stufe, z.nachgetragen) === "vor");
|
||
const zurueck = bewegt.filter((z) => artVon(z.vorher, z.stufe, z.nachgetragen) === "zurueck");
|
||
|
||
if (vor.length) {
|
||
texte.push({
|
||
art: "vor",
|
||
titel: "Was sich bewegt hat",
|
||
satz: zaehl(vor.length,
|
||
`Ein Punkt ist weitergekommen: „${titel(vor[0].schluessel)}“.`,
|
||
`%n Punkte sind weitergekommen.`)
|
||
+ " Das ist die Sorte Veränderung, die man aussprechen sollte –"
|
||
+ " sichtbarer Fortschritt hält Menschen bei der Sache, stärker"
|
||
+ " als Lob es je könnte.",
|
||
punkte: vor.slice(-5).reverse().map((z) => ({
|
||
titel: titel(z.schluessel), von: z.vorher, zu: z.stufe, am: z.am,
|
||
})),
|
||
});
|
||
}
|
||
if (zurueck.length) {
|
||
texte.push({
|
||
art: "zurueck",
|
||
titel: "Wo du es anders siehst als vorher",
|
||
satz: zaehl(zurueck.length,
|
||
"Bei einem Punkt hast du zurückgesetzt.",
|
||
"Bei %n Punkten hast du zurückgesetzt.")
|
||
+ " Das ist kein Rückschritt dieser Person – es heißt zuerst,"
|
||
+ " dass du genauer hingesehen hast als beim letzten Mal."
|
||
+ " Beides gehört ins Gespräch, und zwar in dieser Reihenfolge.",
|
||
punkte: zurueck.slice(-5).reverse().map((z) => ({
|
||
titel: titel(z.schluessel), von: z.vorher, zu: z.stufe, am: z.am,
|
||
})),
|
||
});
|
||
}
|
||
|
||
/* ---- 2. Was trägt ------------------------------------------------ */
|
||
const traegt = stand
|
||
.filter((s) => s.stufe === "laeuft" && (tageSeit(s.am) ?? 0) >= TRAEGT_AB_TAGEN)
|
||
.sort((a, b) => Date.parse(a.am) - Date.parse(b.am));
|
||
if (traegt.length) {
|
||
const t = traegt[0];
|
||
texte.push({
|
||
art: "traegt",
|
||
titel: "Was seit Längerem trägt",
|
||
satz: zaehl(traegt.length,
|
||
`„${titel(t.schluessel)}“ steht seit ${tageSeit(t.am)} Tagen auf „Läuft“ und ist nie gekippt.`,
|
||
`%n Punkte stehen seit über ${Math.round(TRAEGT_AB_TAGEN / 30)} Monaten auf „Läuft“ und sind nie gekippt.`)
|
||
+ " Genau so etwas wird nie gesagt, weil nichts passiert –"
|
||
+ " und fehlende Anerkennung ist der am häufigsten genannte"
|
||
+ " Grund, warum Leute in dieser Arbeit aufhören.",
|
||
punkte: traegt.slice(0, 5).map((s) => ({
|
||
titel: titel(s.schluessel), zu: "laeuft", am: s.am, tage: tageSeit(s.am),
|
||
})),
|
||
});
|
||
}
|
||
|
||
/* ---- 3. Was liegt ------------------------------------------------ */
|
||
const liegt = stand
|
||
.filter((s) => s.stufe === "hakt" && (tageSeit(s.am) ?? 0) >= LIEGT_AB_TAGEN)
|
||
.sort((a, b) => Date.parse(a.am) - Date.parse(b.am));
|
||
if (liegt.length) {
|
||
texte.push({
|
||
art: "liegt",
|
||
titel: "Was seit Längerem unverändert steht",
|
||
satz: zaehl(liegt.length,
|
||
`„${titel(liegt[0].schluessel)}“ steht seit ${tageSeit(liegt[0].am)} Tagen unverändert auf „Da hakt es“.`,
|
||
`%n Punkte stehen seit über ${LIEGT_AB_TAGEN} Tagen unverändert auf „Da hakt es“.`)
|
||
+ " Ein „da hakt es“, das nirgends hinführt, ist nach einer Weile"
|
||
+ " keine Beobachtung mehr, sondern eine Notiz über einen Menschen."
|
||
+ " Auf der Karte steht neben jedem Punkt „Daraus einen Schritt machen“.",
|
||
punkte: liegt.slice(0, 5).map((s) => ({
|
||
titel: titel(s.schluessel), zu: "hakt", am: s.am, tage: tageSeit(s.am),
|
||
})),
|
||
});
|
||
}
|
||
|
||
/* ---- 4. Was niemand mehr angesehen hat --------------------------- */
|
||
const alt = stand.filter((s) => (tageSeit(s.am) ?? 0) >= VERBLASST_NACH_TAGEN);
|
||
if (alt.length) {
|
||
texte.push({
|
||
art: "alt",
|
||
titel: "Das sagt etwas über uns, nicht über sie",
|
||
satz: zaehl(alt.length,
|
||
`Eine Einschätzung ist älter als ${VERBLASST_NACH_TAGEN} Tage.`,
|
||
`%n Einschätzungen sind älter als ${VERBLASST_NACH_TAGEN} Tage.`)
|
||
+ " Sie zählen in der Auswertung nicht mehr mit. Eine Einschätzung"
|
||
+ " vom Frühjahr, heute unverändert angezeigt, wäre eine Lüge mit"
|
||
+ " Zeitstempel – deshalb verblasst sie, statt zu verschwinden.",
|
||
punkte: alt.slice(0, 5).map((s) => ({
|
||
titel: titel(s.schluessel), zu: s.stufe, am: s.am, tage: tageSeit(s.am),
|
||
})),
|
||
});
|
||
}
|
||
|
||
/* ---- 5. Was als Nächstes kommt ----------------------------------- */
|
||
const gesetzt = new Set(stand.map((s) => s.schluessel));
|
||
const offen = ENTWICKLUNG_FREMD.filter((p) => !gesetzt.has(p.schluessel));
|
||
if (offen.length) {
|
||
/* NACH ERWARTUNGSSTUFE SORTIERT, niedrigste zuerst: Was ab Tag eins
|
||
gilt, ist die sinnvollere nächste Frage als das, wofür man
|
||
jemanden fragt. */
|
||
const rang = new Map(ENTWICKLUNG_ERWARTUNG.map((e) => [e.schluessel, e.rang]));
|
||
const sortiert = [...offen].sort(
|
||
(a, b) => (rang.get(a.stufe) ?? 9) - (rang.get(b.stufe) ?? 9));
|
||
texte.push({
|
||
art: "offen",
|
||
titel: "Wozu du noch nichts gesagt hast",
|
||
satz: zaehl(offen.length,
|
||
"Ein Punkt des Katalogs ist von dir noch unbeantwortet.",
|
||
"%n Punkte des Katalogs sind von dir noch unbeantwortet.")
|
||
+ " Das ist kein Versäumnis – „Kann ich nicht sagen“ ist eine"
|
||
+ " gültige Antwort und ehrlicher als zu raten. Es steht hier,"
|
||
+ " damit man weiß, worüber die Auswertung schweigt.",
|
||
punkte: sortiert.slice(0, 5).map((p) => ({
|
||
titel: p.titel,
|
||
/* DER NAME, NICHT DER SCHLUESSEL -- und schon gar nicht der
|
||
`rang` daneben. ENTWICKLUNG_ERWARTUNG traegt eine Zahl je
|
||
Stufe ("rang"), die es nur zum Sortieren gibt. Sie mitzuliefern
|
||
hiesse, eine Zahl ueber einen Menschen auszuliefern, die
|
||
irgendwann jemand addiert. Aufgefallen ist das der eigenen
|
||
Pruefung, nicht dem Lesen. */
|
||
erwartung: ENTWICKLUNG_ERWARTUNG.find((e) => e.schluessel === p.stufe)?.name
|
||
|| p.stufe,
|
||
})),
|
||
});
|
||
}
|
||
|
||
return texte;
|
||
}
|
||
|
||
/** Der eine Satz oben — die ganze Auswertung in einer Zeile.
|
||
*
|
||
* WER NUR IHN LIEST, SOLL RICHTIG INFORMIERT SEIN. Dieselbe Regel wie
|
||
* bei der Befinden-Analyse: Der Satz ist kein Aufhänger, er ist die
|
||
* Kurzfassung. */
|
||
export function satzVon({ bewegung, stand, texte, spalten }) {
|
||
const vor = bewegung.reduce((s, m) => s + m.vor, 0);
|
||
const zurueck = bewegung.reduce((s, m) => s + m.zurueck, 0);
|
||
const neu = bewegung.reduce((s, m) => s + m.neu + m.geklaert, 0);
|
||
|
||
if (!stand.length) {
|
||
return "Zu dieser Person hast du noch nichts eingetragen. Die Auswertung "
|
||
+ "beginnt mit der ersten Einschätzung – vorher stünde hier eine Grafik "
|
||
+ "über nichts.";
|
||
}
|
||
if (vor + zurueck + neu === 0) {
|
||
const alt = texte.find((t) => t.art === "alt");
|
||
return alt
|
||
? `In den letzten ${spalten.length} Monaten hat sich an dieser Karte nichts `
|
||
+ "bewegt – und ein Teil davon ist inzwischen älter als drei Monate. "
|
||
+ "Das ist eine Auskunft über uns, nicht über sie."
|
||
: `In den letzten ${spalten.length} Monaten hat sich an dieser Karte nichts `
|
||
+ "bewegt. Das kann heißen, dass alles ruhig läuft – oder dass "
|
||
+ "länger niemand hingesehen hat. Die Karte selbst kann das nicht "
|
||
+ "unterscheiden.";
|
||
}
|
||
const teile = [];
|
||
if (vor) teile.push(zaehl(vor, "ein Punkt nach vorn", "%n Punkte nach vorn"));
|
||
if (zurueck) teile.push(zaehl(zurueck, "einer zurück", "%n zurück"));
|
||
if (neu) teile.push(zaehl(neu, "einer zum ersten Mal beantwortet",
|
||
"%n zum ersten Mal beantwortet"));
|
||
return `In den letzten ${spalten.length} Monaten: ${teile.join(", ")}. `
|
||
+ (vor >= zurueck
|
||
? "Das Erste gehört ausgesprochen – es ist der Teil, den sonst niemand bemerkt."
|
||
: "Das ist kein schlechtes Zeichen: Wer zurücksetzt, hat hingesehen.");
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE WEGE
|
||
===================================================================== */
|
||
|
||
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("[werdegang] Tabellen:", f?.message);
|
||
return res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
next();
|
||
}
|
||
|
||
/** Nur DogFather und die rechte Hand. 404 statt 403 — wer „kein
|
||
* Zugriff“ liest, fragt als Nächstes, wer denn dann. */
|
||
function nurLeitung(req, res, next) {
|
||
if (fuehrtTeamDogi(req.person)) return next();
|
||
return res.status(404).json({ fehler: "nicht_gefunden" });
|
||
}
|
||
|
||
werdegangRouter.use("/workspace/api/werdegang", angemeldet);
|
||
|
||
/* ---- Die Liste ------------------------------------------------------
|
||
Wer eine Karte hat, und was sich dort zuletzt getan hat.
|
||
|
||
AUSDRÜCKLICH OHNE SORTIERUNG NACH „AKTIVITÄT“. Eine Liste, die die
|
||
mit der meisten Bewegung nach oben stellt, ist eine Rangliste mit
|
||
einem anderen Namen. Sortiert wird nach Namen.
|
||
------------------------------------------------------------------- */
|
||
werdegangRouter.get("/workspace/api/werdegang/liste", nurLeitung, (req, res) => {
|
||
try {
|
||
/* NACH ROLLE, DANN NACH NAMEN (19.09.2026).
|
||
|
||
Filipe: „die reihenfolge der listen soll immer angepasst sein
|
||
hatten wir doch schon. die rechte hand rolle immer zuerst und
|
||
dan die modis."
|
||
|
||
Vorher stand hier nur `ORDER BY name` mit der Begründung, eine
|
||
Sortierung nach Aktivität wäre eine Rangliste mit anderem Namen.
|
||
Das gilt weiter — aber eine Sortierung nach ROLLE ist keine:
|
||
Sie sagt nichts darüber, wie gut jemand ist, sondern nur, welche
|
||
Aufgabe er hat. Genau dieselbe Reihenfolge benutzt das Haus in
|
||
`ROLLEN_SORTIERUNG` schon überall sonst.
|
||
|
||
ABGELEITET, NICHT ABGESCHRIEBEN: Die Reihenfolge kommt aus
|
||
derselben Konstante wie in der Personenliste. Wer dort eine
|
||
Rolle verschiebt, verschiebt sie hier mit. */
|
||
const leute = db().prepare(
|
||
`SELECT id, name, rolle FROM personen
|
||
WHERE aktiv = 1 AND rolle IN (${KARTEN_ROLLEN.map(() => "?").join(",")})
|
||
ORDER BY ${ROLLEN_SORTIERUNG}, name COLLATE NOCASE`).all(...KARTEN_ROLLEN);
|
||
|
||
const spalten = monatsspalten();
|
||
const liste = leute.map((p) => {
|
||
const spur = spurVon(p.id);
|
||
const bewegung = bewegungVon(spur.filter((z) => z.von_id === req.person.id), spalten);
|
||
const stand = db().prepare(
|
||
"SELECT schluessel, stufe, am FROM entwicklung_stand WHERE person_id = ? AND von_id = ?")
|
||
.all(p.id, req.person.id)
|
||
.filter((s) => ENTWICKLUNG_FREMD.some((x) => x.schluessel === s.schluessel));
|
||
const juengste = stand.map((s) => s.am).sort().at(-1) || null;
|
||
return {
|
||
id: p.id,
|
||
name: p.name,
|
||
/* DIE ROLLE SELBST KOMMT MIT, nicht nur ihr Name: Die
|
||
Oberfläche gruppiert danach und zeichnet die Leitung anders.
|
||
Über den NAMEN zu gruppieren wäre die Stelle, die beim
|
||
nächsten Umbenennen auseinanderfällt. */
|
||
rolle: p.rolle,
|
||
rolle_name: ROLLEN_NAME[p.rolle] ?? p.rolle,
|
||
/* DER SATZ UNTER DER UEBERSCHRIFT (21.09.2026).
|
||
|
||
Er stand bis heute in werdegang.js -- also in einer Datei,
|
||
die JEDER herunterlaedt, der den Arbeitsplatz oeffnet. Mit
|
||
den Rollennamen als Schluessel, und einer davon gehoert der
|
||
Community. Dieselbe Ueberlegung wie bei ROLLENTEXT in
|
||
start.js: Was einen Rollennamen traegt, kommt vom Server.
|
||
|
||
EIN FEHLENDER SATZ IST EIN GUELTIGER ZUSTAND. Nicht jede
|
||
Rolle braucht eine Erklaerung, und ein erfundener Satz waere
|
||
schlechter als keiner -- die Oberflaeche laesst die Zeile
|
||
dann einfach weg. */
|
||
rolle_satz: ROLLEN_SATZ[p.rolle] ?? null,
|
||
gesetzt: stand.length,
|
||
gesamt: ENTWICKLUNG_FREMD.length,
|
||
bewegt: bewegung.reduce((s, m) => s + m.vor + m.zurueck + m.neu + m.geklaert, 0),
|
||
vor: bewegung.reduce((s, m) => s + m.vor, 0),
|
||
zuletzt: juengste,
|
||
zuletzt_tage: juengste ? tageSeit(juengste) : null,
|
||
};
|
||
});
|
||
|
||
res.json({
|
||
liste,
|
||
monate: spalten.length,
|
||
/* WAS DIESE SEITE NICHT TUT, steht in der Antwort und nicht nur
|
||
im Kopf des Entwicklers: Die Oberfläche zeigt den Satz. */
|
||
hinweis: "Keine Punktzahl, kein Vergleich zwischen Personen. "
|
||
+ "Jede Karte wird nur mit sich selbst über die Zeit verglichen.",
|
||
});
|
||
} catch (f) {
|
||
console.error("[werdegang] Liste:", f?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|
||
|
||
/* ---- Eine Person ---------------------------------------------------- */
|
||
werdegangRouter.get("/workspace/api/werdegang/person/:id", nurLeitung, (req, res) => {
|
||
try {
|
||
const id = Number(req.params.id);
|
||
const p = Number.isInteger(id) && id > 0
|
||
&& db().prepare("SELECT id, name, rolle FROM personen WHERE id = ?").get(id);
|
||
if (!p || !KARTEN_ROLLEN.includes(p.rolle)) {
|
||
return res.status(404).json({ fehler: "nicht_gefunden" });
|
||
}
|
||
|
||
const spur = spurVon(p.id);
|
||
const spalten = monatsspalten();
|
||
const meine = spur.filter((z) => z.von_id === req.person.id);
|
||
const bewegung = bewegungVon(meine, spalten);
|
||
const streifen = streifenVon(spur, spalten, req.person.id);
|
||
const stand = db().prepare(
|
||
"SELECT schluessel, stufe, am FROM entwicklung_stand WHERE person_id = ? AND von_id = ?")
|
||
.all(p.id, req.person.id)
|
||
.filter((s) => ENTWICKLUNG_FREMD.some((x) => x.schluessel === s.schluessel));
|
||
|
||
const texte = texteVon({ spur, beurteilerId: req.person.id, stand, person: p });
|
||
|
||
/* MONATE MIT INHALT, nicht Monate überhaupt. Sechs leere Spalten
|
||
sind kein Verlauf — sie sehen nur aus wie einer. */
|
||
const gefuellt = streifen.length
|
||
? spalten.filter((m) => streifen.some(
|
||
(z) => z.zellen.find((c) => c.monat === m)?.stufe)).length
|
||
: 0;
|
||
|
||
res.json({
|
||
person: { id: p.id, name: p.name, rolle_name: ROLLEN_NAME[p.rolle] ?? p.rolle },
|
||
stufen: ENTWICKLUNG_STUFEN,
|
||
/* ENTWICKLUNG_ERWARTUNG wird NICHT mitgeschickt. Die Seite braucht
|
||
die Liste nicht, und sie traegt ein Feld `rang` -- eine Zahl je
|
||
Erwartungsstufe, die es nur zum Sortieren gibt. Auf einer Seite
|
||
ueber Menschen hat keine Zahl etwas verloren, die man addieren
|
||
koennte. Der Name der Stufe steht dort, wo er gebraucht wird. */
|
||
bloecke: ENTWICKLUNG_BLOECKE.filter((b) => !b.nurSelbst)
|
||
.map((b) => ({ schluessel: b.schluessel, name: b.name })),
|
||
spalten,
|
||
bewegung,
|
||
streifen,
|
||
texte,
|
||
satz: satzVon({ bewegung, stand, texte, spalten }),
|
||
gesetzt: stand.length,
|
||
gesamt: ENTWICKLUNG_FREMD.length,
|
||
/* DER DRITTE AUSGANG. Nicht „in Ordnung“ und nicht „nicht in
|
||
Ordnung“, sondern: konnte noch nicht nachsehen — mit Grund.
|
||
Die Oberfläche zeichnet dann KEIN Diagramm, sondern schreibt
|
||
den Grund hin. */
|
||
verlauf_moeglich: gefuellt >= VERLAUF_AB,
|
||
verlauf_ab: VERLAUF_AB,
|
||
monate_mit_inhalt: gefuellt,
|
||
verblasst_nach_tagen: VERBLASST_NACH_TAGEN,
|
||
});
|
||
} catch (f) {
|
||
console.error("[werdegang] Person:", f?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|
||
|
||
/* ---- Die eigene Entwicklung -----------------------------------------
|
||
|
||
Der Teil, der den Menschen selbst angeht — und der nach der
|
||
Forschung der wirksamste ist: Wer seinen eigenen Fortschritt sieht,
|
||
bleibt dabei.
|
||
|
||
OHNE NAMEN, OHNE ANLASS, OHNE WER-HAT-WAS. Genau wie bei
|
||
`meine-karte`: Sie sieht, DASS ein Punkt sich bewegt hat und wann —
|
||
nicht, wer ihn gesetzt hat und was danebensteht. Der Anlass ist für
|
||
ein Gespräch aufgeschrieben, nicht zum Lesen ohne den Menschen dazu.
|
||
|
||
UND ERST, WENN ÜBERHAUPT JEMAND GESETZT HAT. Eine leere eigene Seite
|
||
würde wie ein Fehler aussehen; ein Satz ist eine Auskunft.
|
||
------------------------------------------------------------------- */
|
||
werdegangRouter.get("/workspace/api/werdegang/mich", (req, res) => {
|
||
try {
|
||
if (!KARTEN_ROLLEN.includes(req.person.rolle)) {
|
||
return res.json({ hat_karte: false });
|
||
}
|
||
const spur = spurVon(req.person.id).filter((z) => z.von_id !== req.person.id);
|
||
const spalten = monatsspalten();
|
||
|
||
/* ZUSAMMENGEFASST ÜBER ALLE BEURTEILER. Hier geht es nicht darum,
|
||
wer was gesagt hat — sondern darum, ob sich etwas bewegt.
|
||
Deshalb ist eine Bewegung eine Bewegung, egal von wem. */
|
||
const bewegung = bewegungVon(spur, spalten);
|
||
const vor = bewegung.reduce((s, m) => s + m.vor, 0);
|
||
const zurueck = bewegung.reduce((s, m) => s + m.zurueck, 0);
|
||
|
||
let satz;
|
||
if (!spur.length) {
|
||
satz = "Zu dir wurde noch nichts eingetragen. Sobald das passiert ist, "
|
||
+ "steht hier, was sich über die Zeit bewegt hat.";
|
||
} else if (!vor && !zurueck) {
|
||
satz = `In den letzten ${spalten.length} Monaten hat sich an den `
|
||
+ "Einschätzungen zu dir nichts geändert.";
|
||
} else if (vor) {
|
||
satz = vor === 1
|
||
? "Seit dem letzten Mal ist ein Punkt weitergekommen."
|
||
: `Seit dem letzten Mal sind ${vor} Punkte weitergekommen.`;
|
||
} else {
|
||
satz = "Seit dem letzten Mal wurde ein Punkt anders eingeschätzt als vorher. "
|
||
+ "Das ist kein Urteil – frag danach, dann weißt du, worum es geht.";
|
||
}
|
||
|
||
res.json({
|
||
hat_karte: true,
|
||
spalten,
|
||
bewegung,
|
||
satz,
|
||
/* KEIN STREIFEN. Er würde zeigen, WELCHER Punkt wann gewackelt
|
||
hat — und damit mittelbar, was jemand über sie gesagt hat,
|
||
ohne dass derjenige dabei ist. Die Zahl reicht; für alles
|
||
Weitere ist das Gespräch da, und der Satz sagt das auch. */
|
||
hinweis: "Welche Punkte das sind, steht nicht hier. Frag danach – "
|
||
+ "eine Einschätzung ohne den Menschen dazu ist eine Behauptung.",
|
||
});
|
||
} catch (f) {
|
||
console.error("[werdegang] Mich:", f?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|