Wie lange geht das schon so -- Eskalation statt eines Schalters

Stufe 9 aus dem Plan: "Eskalationsstufe sichtbar". Damit ist Stufe 9
bis auf einen Punkt durch.

DER MANGEL, GEMESSEN

"Ueberfaellig" war ein Schalter: an oder aus. Eine Aufgabe, deren Frist
gestern lief, sah genauso aus wie eine, die seit drei Wochen liegt --
dasselbe Wort, dieselbe Farbe, derselbe Platz.

Damit verliert das Wort seine Bedeutung. Stehen auf einem Brett zwanzig
Karten "ueberfaellig", sagt keine davon mehr etwas; man ueberliest sie
alle, auch die, die seit einem Monat blockiert.

ZWEI VERSCHIEDENE FRAGEN, DIE NICHT VERMISCHT WERDEN DUERFEN

  FRIST UEBERSCHRITTEN   Es war etwas versprochen, der Termin ist
                         vorbei. Eine Tatsache.
                         faellig (ab 1 Tag) -> liegt (ab 3) -> haengt (ab 8)

  NICHTS PASSIERT        Keine Frist, aber seit 14 Tagen hat niemand
                         die Karte angefasst. Eine Beobachtung.

Die erste ist haerter und steht vorn. Eine Aufgabe ohne Frist ist nicht
"zu spaet" -- sie ist nur still. Beides in einen Topf zu werfen hiesse,
jemandem einen gebrochenen Termin vorzuwerfen, den er nie zugesagt hat.
Deshalb ist "still" auch farblos gezeichnet, gestrichelt statt gefuellt.

DAS IST KEINE MAHNUNG

Dieselbe Haltung wie bei der Standzeit im Talente-Trichter: Die Zahl ist
eine Auskunft ueber UNS, kein Vorwurf an eine Person. Deshalb steht
nirgends ein Name, und die hoechste Stufe heisst "haengt", nicht
"versaeumt".

DIE SCHWELLEN SIND GESETZT, NICHT GEMESSEN -- und das steht so im Code:
unter 3 Tagen reicht ein Wochenende; ab 3 ist es ein Zustand; ab 8 (ueber
eine Woche) kommt es ohne Hilfe auch naechste Woche nicht dazu. Aendert
sich eine Zahl, aendert sie sich an einer Stelle, und die Oberflaeche
zieht mit -- sie bekommt das WORT vom Server.

AM SERVER GERECHNET. Startseite, Brett und Uebersicht lesen dieselbe
Liste. Drei Stellen, die dieselbe Frage selbst beantworten, geben
irgendwann drei Antworten -- genau der Fehler, der im Projekt schon bei
der Rollenreihenfolge und beim Wort "Agentur" passiert ist.

DIE FARBE IST NICHT DIE AUSKUNFT: In jedem Kaestchen steht ein Wort
("liegt seit 5 Tagen"), nicht nur ein roterer Punkt.

PRUEFUNGEN: pruef-eskalation neu mit 40, davon 6 im Browser. Gemessen
wird an den GRENZEN, nicht in der Mitte -- jede Schwelle dreimal: einen
Tag davor, genau darauf, einen Tag danach. Die wichtigste ist die erste:
am Tag der Frist ist nichts ueberfaellig. Wer bis zum 15. Zeit hat, ist
am 15. puenktlich; eine Eskalation, die einen Tag zu frueh anschlaegt,
verliert genau das Vertrauen, das sie braucht.

Die Pruefung benutzt einen FESTEN Zeitpunkt als Eingabe, keine
Wanderuhr -- sonst misst sie irgendwann das Gegenteil.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-15 13:22:55 +02:00
co-authored by Claude Opus 5
parent 1386534051
commit 2d5972f45f
34 changed files with 879 additions and 349 deletions
+151
View File
@@ -0,0 +1,151 @@
/* =====================================================================
Wie lange geht das schon so? (15.09.2026)
Stufe 9 aus dem Plan: „Eskalationsstufe sichtbar".
---------------------------------------------------------------------
DER MANGEL, GEMESSEN
Überfällig war bisher ein Schalter: an oder aus. Eine Aufgabe, deren
Frist gestern lief, sah genauso aus wie eine, die seit drei Wochen
liegt — dasselbe Wort, dieselbe Farbe, derselbe Platz.
Damit verliert das Wort seine Bedeutung. Wenn auf einem Brett zwanzig
Karten „überfällig" stehen, sagt keine davon mehr etwas; man
überliest sie alle, auch die, die seit einem Monat blockiert.
---------------------------------------------------------------------
ZWEI VERSCHIEDENE FRAGEN — UND SIE DÜRFEN NICHT VERMISCHT WERDEN
FRIST ÜBERSCHRITTEN Es war etwas versprochen, und der Termin ist
vorbei. Eine Tatsache.
NICHTS PASSIERT Es gibt keine Frist, aber seit Wochen hat
niemand die Karte angefasst. Eine Beobachtung.
Die erste ist härter als die zweite, und sie steht deshalb vorn. Eine
Aufgabe ohne Frist ist nicht „zu spät" — sie ist nur still. Beides in
einen Topf zu werfen hieße, jemandem einen gebrochenen Termin
vorzuwerfen, den er nie zugesagt hat.
---------------------------------------------------------------------
DAS IST KEINE MAHNUNG
Dieselbe Haltung wie bei der Standzeit im Talente-Trichter und bei
„Was auf dich wartet": Die Zahl ist eine Auskunft über UNS, nicht ein
Vorwurf an eine Person. Deshalb steht hier auch nirgends ein Name,
und deshalb heißt die höchste Stufe „hängt" und nicht „versäumt".
---------------------------------------------------------------------
DIE SCHWELLEN SIND GESETZT, NICHT GEMESSEN
Ehrlich benannt: 3 und 8 Tage sind ein begründeter Anfang, kein
Messergebnis. Die Überlegung dahinter:
unter 3 Tagen Ein Wochenende reicht dafür. Wer Freitag eine
Frist reißt und Montag liefert, hat nichts
versäumt — nur den Kalender nicht gelesen.
ab 3 Tagen Länger als ein Wochenende. Jetzt ist es keine
Verzögerung mehr, sondern ein Zustand.
ab 8 Tagen Über eine Woche. Wer so lange nicht dazu kommt,
kommt ohne Hilfe auch nächste Woche nicht dazu.
Wenn sich zeigt, dass die Stufen zu früh oder zu spät greifen, wird
hier eine Zahl geändert — an einer Stelle, und die Oberfläche zieht
mit, weil sie das Wort vom Server bekommt.
===================================================================== */
/** Die Stufen, von der mildesten zur härtesten.
*
* `ab` ist die Anzahl Tage, ab der die Stufe gilt. Die Liste wird von
* hinten durchsucht — die härteste zutreffende gewinnt. */
export const STUFEN = [
{ schluessel: "faellig", ab: 1, rang: 1 },
{ schluessel: "liegt", ab: 3, rang: 2 },
{ schluessel: "haengt", ab: 8, rang: 3 },
];
/** Ohne Frist: ab wie vielen Tagen ohne Bewegung es auffällt.
*
* 14 Tage — zwei Wochen. Kürzer wäre bei einer Aufgabe ohne Termin
* Panikmache: Nicht alles, was daliegt, ist liegengeblieben. */
export const STILL_TAGE = 14;
const TAG = 86400000;
/** Ganze Tage zwischen zwei Datumsangaben (a später als b). */
function tageSeit(zeitpunkt, jetzt) {
const t = Date.parse(zeitpunkt);
if (!Number.isFinite(t)) return null;
return Math.floor((jetzt - t) / TAG);
}
/** Wie lange geht das mit dieser Aufgabe schon so?
*
* @param {object} a eine Zeile aus `aufgaben`
* @param {number} [jetzt] Zeitpunkt in ms (für die Prüfung setzbar)
* @returns {{stufe: string, tage: number, grund: string, wort: string}|null}
* null, wenn nichts anliegt
*/
export function eskalation(a, jetzt = Date.now()) {
if (!a) return null;
/* Erledigtes und Abgebrochenes eskaliert nicht. Eine Karte, die fertig
ist, war vielleicht zu spät fertig -- aber sie liegt niemandem mehr
im Weg, und eine Warnung daran wäre nur noch ein Vorwurf. */
if (a.status === "erledigt" || a.status === "abgebrochen") return null;
/* ---- 1. Die Frist. Eine Tatsache, und sie steht vorn. ---- */
if (a.frist) {
/* Auf den ANFANG des Tages nach der Frist gerechnet: Wer bis zum
15. Zeit hat, ist am 15. nicht zu spät. Die Grenze ist Mitternacht
danach, nicht der Moment des Nachsehens. */
const ende = Date.parse(a.frist + "T23:59:59");
if (Number.isFinite(ende) && jetzt > ende) {
const tage = Math.max(1, Math.floor((jetzt - ende) / TAG) + 1);
const treffer = [...STUFEN].reverse().find((s) => tage >= s.ab);
if (treffer) {
return {
stufe: treffer.schluessel,
rang: treffer.rang,
tage,
grund: "frist",
wort: treffer.schluessel === "faellig"
? "überfällig"
: (treffer.schluessel === "liegt"
? `liegt seit ${tage} Tagen`
: `hängt seit ${tage} Tagen`),
};
}
}
/* MIT FRIST UND NOCH NICHT ÜBERSCHRITTEN: Hier endet es. Eine Karte,
deren Termin erst nächste Woche ist, ist nicht „still" -- sie ist
eingeplant. */
return null;
}
/* ---- 2. Ohne Frist: seit wann hat niemand sie angefasst? ---- */
const zuletzt = a.geaendert || a.erstellt;
const still = zuletzt ? tageSeit(zuletzt, jetzt) : null;
if (still !== null && still >= STILL_TAGE) {
return {
stufe: "still",
rang: 1,
tage: still,
grund: "keine_bewegung",
wort: `seit ${still} Tagen unberührt`,
};
}
return null;
}
/** Setzt `eskalation` an jede Zeile einer Liste.
*
* AM SERVER UND NICHT IM BROWSER: Startseite, Aufgabenbrett und
* Übersicht lesen dieselbe Liste. Drei Stellen, die dieselbe Frage
* selbst beantworten, geben irgendwann drei Antworten -- genau der
* Fehler, der im Projekt schon bei der Rollenreihenfolge und beim Wort
* „Agentur" passiert ist. */
export function eskalationSetzen(liste, jetzt = Date.now()) {
for (const a of liste || []) a.eskalation = eskalation(a, jetzt);
return liste;
}