Entwicklung: der Mensch sieht, was seine Aufgabe ist

VanVan im Support: „wenn ich … Aufgaben an einen Modi zutrage und
auch bewerte, dann sieht der Modi zwar die Auswertung im Bereich
Entwicklung, aber bei ihm taucht nichts im Bereich eure Aufgaben
unten in der Karte auf."

IHR FALL NACHGESTELLT (server/mess-vanvan-karte.mjs): VanVan traegt
einem Modi zwei von 68 Punkten zu und bewertet sie, Filipe bewertet
nichts -- genau wie bei ihr.

  vorher   seine Karte: fertig=0 von 68, alles „offen",
           Punktzeilen sichtbar: 0
           Kennt sie das Wort „zugetragen"?  NEIN
  nachher  Abschnitt „Deine Aufgaben (2)" mit beiden Titeln

ZWEI DINGE WAREN EINS, DIE GETRENNT GEHOEREN

  Was er TUN soll   die Zuteilung. Die gibt es, bevor jemand etwas
                    bewertet, und sie gehoert ihm.
  Was man SIEHT     die Bewertung. Die erscheint erst, wenn alle
                    hingesehen haben.

`meine-karte` kannte nur das Zweite. War die Bewertung noch nicht
vollstaendig, stieg die Anzeige mit `return` aus -- und damit sah er
gar nichts, obwohl ihm zwei Aufgaben zugetragen waren. Das ist die
falsche Reihenfolge: Wer seine Aufgabe erst sieht, wenn sich zwei
Leute ueber ihre Ausfuehrung einig sind, kann sie nicht angehen.

Die Regel fuer die Bewertung bleibt unveraendert. Eine einzelne
Meinung soll bei einem Menschen nicht als Urteil des Teams ankommen.

UND DIE ANDERE HAELFTE: NIEMAND SAGTE ES IHR

VanVan hatte gesetzt, es kam nicht an, und nichts auf ihrem
Bildschirm erklaerte das. Ein Mensch, der das zweimal erlebt, hoert
auf zu setzen. An einem Punkt, den sie bewertet hat, steht jetzt
leise: „Er sieht das noch nicht -- es fehlt noch eine Einschaetzung."
Ohne Namen: Wer fehlt, waere eine Aufforderung, jemanden
anzutreiben.

IHRE ZWEITE BEOBACHTUNG, EBENFALLS GEMESSEN

„bei jedem Punkt läuft obwohl auch wenn die Aufgaben darüber gar
nicht zugetragen wurde." Stimmt: Die vier Bewertungsknoepfe („✓
Läuft ↗ Wächst ! Da hakt es – Kann ich nicht sagen") stehen an allen
68 Punkten, auch an den nicht zugetragenen. Beim Durchscrollen liest
man deshalb ueberall „Läuft".

DIE 68 BLEIBEN. Filipe am 25.09.2026 ausdruecklich: „dogfather und
die rechte hand sollen immer noch die 68 sachen sehen wie vorher …
unsere sicht soll sich nicht aendern." Also kein Ausblenden und
keine neue Vorgabe, sondern ein Knopf mehr in der Leiste, die es
schon gibt: „Zugetragen · 2". „Alle" bleibt, was beim Aufmachen
gilt. Gemessen grenzt er auf 2 Punkte ein, Kopf „2 / 2".

NEBENBEI ZUSAMMENGEFUEHRT
* `beurteilerZahl()` an einer Stelle -- beide Enden stellen dieselbe
  Frage, zwei Abschriften saegten irgendwann Verschiedenes ueber
  denselben Punkt.
* `antwortReihe()` und `punkteGefiltert()` ebenso.
* Die Abfrage stand zuerst je Punkt in der Schleife: 68
  Datenbankfragen fuer eine Zahl, die sich nicht aendert.

GEPRUEFT
pruef-entwicklung 79/0 (9 neue, mit Gegenproben in beide Richtungen:
ein nicht zugetragener Punkt traegt die Marke nicht, und sobald der
zweite Beurteiler setzt, geht es durch). mess-vanvan-karte ohne
ACHTUNG. pruef-werdegang 103/0, pruef-entwicklung-kacheln 34/0,
pruef-deutsche-texte, pruef-css-klassen, pruef-zwischenspeicher 34/0.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-30 16:48:24 +02:00
co-authored by Claude Opus 5
parent a3ec558127
commit 51c47e1c07
50 changed files with 1391 additions and 701 deletions
+334
View File
@@ -0,0 +1,334 @@
/* =====================================================================
VANVANS MELDUNG AUS DEM SUPPORT (30.09.2026)
„Wenn ich im Bereich eure Aufgaben runterscrolle und dort bei den 68
Aufgaben … Aufgaben an einen Modi zutrage und auch bewerte, dann
sieht der Modi zwar die Auswertung im Bereich Entwicklung, aber bei
ihm taucht nichts im Bereich eure Aufgaben unten in der Karte auf.
Bei mir zum Beispiel steht in der Karte im Bereich eure Aufgaben
bei jedem Punkt läuft obwohl auch wenn die Aufgaben darüber gar
nicht zugetragen wurde."
---------------------------------------------------------------------
ZWEI FRAGEN, UND BEIDE LASSEN SICH NUR AM BILDSCHIRM BEANTWORTEN
1. Was sieht der MODI unten in seiner eigenen Karte, nachdem ihm
etwas zugetragen und bewertet wurde?
2. Was steht bei der LEITUNG an einem Punkt, der gar nicht
zugetragen ist?
Der Quelltext allein sagt das nicht. Er sagt, was gerendert wird --
nicht, was ein Mensch daraus liest. Und genau darum geht es in ihrer
Meldung.
---------------------------------------------------------------------
DER AUFBAU IST IHR FALL, NICHT EIN AEHNLICHER
Filipe (admin) und VanVan (hand) sind die beiden Beurteiler --
genauso wie im Haus. Diene ist ein Modi. VanVan traegt ihm ZWEI von
68 Punkten zu und bewertet sie; einen DRITTEN bewertet sie, ohne ihn
zuzutragen. Filipe bewertet ABSICHTLICH NICHTS -- denn genau so war
es bei ihr, und wenn das der Grund ist, muss er hier sichtbar
werden.
Sie prueft nichts und meldet keinen Fehlschlag. Sie sagt, was auf
dem Bildschirm steht.
Aufrufen mit: node server/mess-vanvan-karte.mjs
===================================================================== */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { portMussFreiSein } from "./helfer-port.mjs";
const PORT = await portMussFreiSein(5493, "mess-vanvan-karte");
const ordner = mkdtempSync(join(tmpdir(), "ws-vanvan-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = String(PORT);
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) {
return ec.call(this, n, w, { ...(o || {}), secure: false });
};
import { notbremse } from "./helfer-notbremse.mjs";
await import("./index.js");
notbremse(180_000, "mess-vanvan-karte");
await new Promise((r) => setTimeout(r, 800));
const BASIS = `http://127.0.0.1:${PORT}`;
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f?.stack || f); process.exit(7); });
process.on("unhandledRejection", (f) => { console.error("ABSTURZ:", f?.stack || f); process.exit(7); });
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
const { ENTWICKLUNG_FREMD } = await import("./workspace-entwicklungs-punkte.js");
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
const geheim = randomBytes(32).toString("hex");
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
.run("code_kennung_schluessel", geheim, jetzt);
function anlegen(name, rolle, code) {
const salz = randomBytes(16).toString("hex");
const hash = scryptSync(code, salz, 64,
{ N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen"
+ " (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)"
+ " VALUES (?,?,?,?,?,?,1,?)")
.run(name, rolle, hash, salz, 32768,
createHmac("sha256", geheim).update(code).digest("hex"), jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
const idVan = anlegen("VanVan", "hand", "CODE-VANVAN-01");
const idModi = anlegen("Diene", "modi", "CODE-DIENE-001");
d.close();
/* ---- Anmelden ---------------------------------------------------- */
async function anmelden(rolle, code) {
const a = await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle, code, alter_ok: true }),
});
return (a.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; ");
}
const kVan = await anmelden("hand", "CODE-VANVAN-01");
const kModi = await anmelden("modi", "CODE-DIENE-001");
const kDogi = await anmelden("admin", "CODE-DOGI-0001");
if (!kVan || !kModi || !kDogi) {
console.error("KONNTE NICHT NACHSEHEN: die Anmeldung hat nicht geklappt.");
process.exit(1);
}
const ruf = (keks, weg, rumpf, art = "GET") => fetch(BASIS + weg, {
method: art,
headers: { "Content-Type": "application/json", Cookie: keks },
body: rumpf === undefined ? undefined : JSON.stringify(rumpf),
});
/* ---- VanVans Handgriffe ------------------------------------------ */
const P1 = ENTWICKLUNG_FREMD[0].schluessel;
const P2 = ENTWICKLUNG_FREMD[1].schluessel;
const P3 = ENTWICKLUNG_FREMD[2].schluessel; // bewertet, NICHT zugetragen
console.log("");
console.log("=== Was VanVan getan hat ===");
for (const [p, wort] of [[P1, "zugetragen"], [P2, "zugetragen"]]) {
const a = await ruf(kVan, `/workspace/api/entwicklung/person/${idModi}/punkt/${p}`,
{ an: true }, "PUT");
console.log(` ${wort}: ${p} (HTTP ${a.status})`);
}
for (const p of [P1, P2, P3]) {
const a = await ruf(kVan, `/workspace/api/entwicklung/person/${idModi}/${p}`,
{ stufe: "laeuft", anlass: "Im Stream gesehen, passt." }, "PUT");
console.log(` bewertet „Läuft": ${p} (HTTP ${a.status})`);
}
console.log(` Filipe bewertet NICHTS -- genau wie in ihrem Fall.`);
console.log(` Von ${ENTWICKLUNG_FREMD.length} Punkten sind 2 zugetragen und 3 bewertet.`);
/* =====================================================================
FRAGE 1: WAS SIEHT DER MODI UNTEN IN SEINER KARTE?
===================================================================== */
console.log("");
console.log("=== 1. Die eigene Karte des Modi (API) ===");
{
const k = await (await ruf(kModi, "/workspace/api/entwicklung/meine-karte")).json();
console.log(` hat_karte=${k.hat_karte} Beurteiler=${k.beurteiler} `
+ `fertig=${k.fertig} von ${k.gesamt}`);
const alle = (k.bloecke || []).flatMap((b) => b.punkte);
const offen = alle.filter((p) => p.offen).length;
console.log(` ${offen} von ${alle.length} Punkten stehen auf „offen"`);
for (const s of [P1, P2, P3]) {
const p = alle.find((x) => x.schluessel === s);
console.log(` ${s.padEnd(26)} offen=${p?.offen} `
+ `zugetragen=${"zugetragen" in (p || {}) ? p.zugetragen : "(gibt es nicht)"} `
+ `antworten=${JSON.stringify(p?.antworten ?? null)}`);
}
/* DIE KERNFRAGE: Steht in seiner Karte ueberhaupt irgendwo, WELCHE
Punkte ihm als Aufgabe zugetragen wurden? */
const kennt = alle.some((p) => "zugetragen" in p);
console.log(` Kennt seine Karte das Wort „zugetragen"? ${kennt ? "JA" : "NEIN"}`);
}
/* =====================================================================
FRAGE 2: WAS STEHT BEI VANVAN AN EINEM PUNKT?
===================================================================== */
console.log("");
console.log("=== 2. VanVans Karte ueber Diene (API) ===");
{
const k = await (await ruf(kVan, `/workspace/api/entwicklung/person/${idModi}`)).json();
const alle = (k.bloecke || []).flatMap((b) => b.punkte);
console.log(` ${alle.length} Punkte insgesamt, `
+ `${alle.filter((p) => p.zugetragen).length} davon zugetragen, `
+ `${alle.filter((p) => p.meins).length} bewertet, `
+ `${alle.filter((p) => p.schritt).length} mit einem Schritt`);
for (const s of [P1, P3]) {
const p = alle.find((x) => x.schluessel === s);
console.log(` ${s.padEnd(26)} zugetragen=${p?.zugetragen} `
+ `meins=${p?.meins?.stufe ?? "-"} schritt=${p?.schritt ? p.schritt.status : "-"}`);
}
}
/* =====================================================================
UND JETZT DER BILDSCHIRM
===================================================================== */
const { chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await chromium.launch();
async function alsWer(keks, breite = 1280, hoehe = 1000) {
const kontext = await browser.newContext({ viewport: { width: breite, height: hoehe } });
await kontext.addCookies(keks.split("; ").filter(Boolean).map((z) => {
const [n, ...r] = z.split("=");
return { name: n, value: r.join("="), domain: "127.0.0.1", path: "/" };
}));
const seite = await kontext.newPage();
const konsole = [];
seite.on("console", (e) => { if (e.type() === "error") konsole.push(e.text()); });
seite.on("pageerror", (f) => konsole.push(String(f?.message || f)));
return { kontext, seite, konsole };
}
console.log("");
console.log("=== 3. Was der Modi auf der Seite Eure Aufgaben WIRKLICH sieht ===");
{
const { kontext, seite, konsole } = await alsWer(kModi);
await seite.goto(`${BASIS}/workspace/entwicklung.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(1500);
const w = await seite.evaluate(() => {
/* GEMESSEN WIRD IN SEINER KARTE, nicht auf der ganzen Seite. Das
Wort „Aufgabe" steht oben in der Kopfleiste und in der
Vorlagen-Kachel -- eine Suche ueber die ganze Seite haette
„ja" gesagt, ohne dass in der Karte irgendetwas steht. Genau
die Sorte gruener Haken, die nichts geprueft hat. */
const karte = document.getElementById("karte-block");
const auf = karte?.querySelector(".e-block--auftrag") || null;
return {
ueberschriften: [...document.querySelectorAll("h1, h2")]
.map((x) => x.textContent.trim()).filter(Boolean).slice(0, 12),
karteDa: !!karte && !karte.hidden,
auftragsblock: auf ? auf.querySelector(".e-block__titel")?.textContent.trim() : null,
aufgabenTitel: auf
? [...auf.querySelectorAll(".e-punkt__titel")].map((x) => x.textContent.trim())
: [],
aufgabenLage: auf
? [...auf.querySelectorAll(".e-punkt__wann")].map((x) => x.textContent.trim())
: [],
satz: (document.getElementById("karte-satz")?.textContent || "").trim().slice(0, 160),
punkte: karte ? karte.querySelectorAll(".e-punkt").length : 0,
};
});
console.log(` Ueberschriften: ${w.ueberschriften.join(" | ")}`);
console.log(` Karte sichtbar: ${w.karteDa}, Punktzeilen darin: ${w.punkte}`);
console.log(` Abschnitt: ${JSON.stringify(w.auftragsblock)}`);
console.log(` Seine Aufgaben: ${JSON.stringify(w.aufgabenTitel)}`);
console.log(` Lage dazu: ${JSON.stringify(w.aufgabenLage)}`);
console.log(` Satz: ${w.satz}`);
if (!w.aufgabenTitel.length) {
console.log(" ACHTUNG: In seiner Karte steht nicht, welche Punkte seine "
+ "Aufgaben sind -- genau VanVans Meldung.");
}
await seite.screenshot({ path: "server/mess-vanvan-karte-modi.png", fullPage: true });
console.log(" Bild: server/mess-vanvan-karte-modi.png");
if (konsole.length) console.log(" KONSOLE:", konsole.slice(0, 3).join(" | "));
await kontext.close();
}
console.log("");
console.log("=== 4. Was VanVan an einem Punkt sieht ===");
{
const { kontext, seite, konsole } = await alsWer(kVan);
await seite.goto(`${BASIS}/workspace/entwicklung.html`, { waitUntil: "networkidle" });
await seite.waitForTimeout(1200);
/* Dienes Kachel aufmachen. */
const auf = await seite.evaluate((name) => {
const k = [...document.querySelectorAll("#personen .e-person")]
.find((x) => (x.querySelector(".e-person__name")?.textContent || "").includes(name));
if (!k) return false;
(k.querySelector("button, a") || k).click();
return true;
}, "Diene");
console.log(` Dienes Kachel geoeffnet: ${auf}`);
await seite.waitForTimeout(2000);
const z = await seite.evaluate(() => {
const zeilen = [...document.querySelectorAll(".e-punkt")].slice(0, 6).map((p) => ({
titel: (p.querySelector(".e-punkt__titel")?.textContent || "").trim().slice(0, 34),
zugetragen: p.dataset.zugetragen,
auftrag: (p.querySelector(".e-punkt__auftrag")?.textContent || "").trim(),
/* WAS WIRKLICH AN DER ZEILE STEHT -- ohne die Knopfbeschriftungen
vorher zu kennen. Genau das ist die Frage: Was liest ein
Mensch dort? */
woerter: (p.innerText || "").replace(/\s+/g, " ").trim().slice(0, 150),
gedrueckt: [...p.querySelectorAll("[aria-pressed='true']")]
.map((b) => b.textContent.trim()),
}));
return {
anzahl: document.querySelectorAll(".e-punkt").length,
zeilen,
/* Sieht SIE, dass es bei ihm noch nicht ankommt? */
wartet: [...document.querySelectorAll(".e-punkt__wartet")]
.map((x) => x.textContent.trim()),
/* Und gibt es den Knopf, mit dem sie auf das Zugetragene
eingrenzen kann? */
knoepfe: [...document.querySelectorAll(".e-stufen__knopf")]
.map((k) => `${k.querySelector(".e-stufen__name")?.textContent.trim()}`
+ `=${k.querySelector(".e-stufen__zahl")?.textContent.trim()}`),
};
});
console.log(` ${z.anzahl} Punktzeilen sichtbar. Die ersten sechs:`);
for (const r of z.zeilen) {
console.log(` · ${r.titel.padEnd(36)} zugetragen=${r.zugetragen} `
+ `Knopf=„${r.auftrag}" gedrueckt=${JSON.stringify(r.gedrueckt)}`);
console.log(` „${r.woerter}"`);
}
console.log(` „Er sieht das noch nicht" steht an ${z.wartet.length} Punkten`);
if (z.wartet[0]) console.log(` „${z.wartet[0]}"`);
console.log(` Filterknoepfe: ${z.knoepfe.join(" ")}`);
if (!z.wartet.length) {
console.log(" ACHTUNG: Sie hat bewertet, es kommt beim Modi nicht an, und "
+ "nichts auf ihrem Bildschirm sagt ihr das.");
}
await seite.screenshot({ path: "server/mess-vanvan-karte-leitung.png", fullPage: true });
console.log(" Bild: server/mess-vanvan-karte-leitung.png");
/* ---- Und der Filter: grenzt er wirklich ein? ------------------
Ohne diesen Griff hiesse „der Knopf ist da" nur, dass ein Knopf
da ist. */
const nachFilter = await seite.evaluate(() => {
const k = [...document.querySelectorAll(".e-stufen__knopf")]
.find((x) => x.querySelector(".e-stufen__name")?.textContent.trim() === "Zugetragen");
if (!k) return null;
k.click();
return true;
});
if (nachFilter) {
await seite.waitForTimeout(1200);
const n = await seite.evaluate(() => ({
punkte: document.querySelectorAll(".e-punkt").length,
alleZugetragen: [...document.querySelectorAll(".e-punkt")]
.every((p) => p.dataset.zugetragen === "ja"),
}));
console.log(` Nach „Zugetragen": ${n.punkte} Punkte, `
+ `alle zugetragen=${n.alleZugetragen}`);
if (n.punkte !== 2 || !n.alleZugetragen) {
console.log(" ACHTUNG: Der Filter grenzt nicht auf das Zugetragene ein.");
}
await seite.screenshot({ path: "server/mess-vanvan-karte-filter.png", fullPage: true });
console.log(" Bild: server/mess-vanvan-karte-filter.png");
} else {
console.log(` ACHTUNG: Den Knopf „Zugetragen“ gibt es nicht.`);
}
if (konsole.length) console.log(" KONSOLE:", konsole.slice(0, 3).join(" | "));
await kontext.close();
}
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Rest im Temp */ }
console.log("");
console.log("Fertig.");
process.exit(0);
+96
View File
@@ -551,6 +551,102 @@ console.log("\n=== Punkte je Person aussuchen ===");
`und alles wieder wegnehmen geht ebenfalls (${danach?.gesamt})`);
}
/* =======================================================================
VANVANS MELDUNG AUS DEM SUPPORT (30.09.2026)
„Wenn ich … Aufgaben an einen Modi zutrage und auch bewerte, dann
sieht der Modi zwar die Auswertung im Bereich Entwicklung, aber bei
ihm taucht nichts im Bereich eure Aufgaben unten in der Karte auf."
Nachgestellt im Browser (mess-vanvan-karte) war es genau so: Seine
Karte lieferte alle 68 Punkte des Katalogs und kein Wort darueber,
welche davon ihm zugetragen wurden -- und weil noch nicht alle
Beurteiler gesetzt hatten, stieg die Anzeige ganz aus.
ZWEI GETRENNTE DINGE, die vorher eins waren:
* WAS ER TUN SOLL -- die Zuteilung. Die gibt es, bevor jemand
etwas bewertet, und sie gehoert ihm.
* WAS MAN SIEHT -- die Bewertung. Die erscheint erst, wenn
alle hingesehen haben, und das bleibt so.
======================================================================= */
console.log("");
console.log("=== VanVans Meldung: die eigene Karte kennt die Aufgaben ===");
{
const PA = "e-chat-neue-begruesst";
const PB = "e-chat-fragen-beantwortet";
/* Sauberer Ausgangspunkt -- die Abschnitte davor haben gesetzt. */
await hole(`/workspace/api/entwicklung/person/${idModi}/punkte`,
{ host: CREW, keks: dogi.keks, methode: "PUT",
rumpf: JSON.stringify({ punkte: [PA, PB] }) });
const meine = async () => (await hole("/workspace/api/entwicklung/meine-karte",
{ host: CREW, keks: ayla.keks })).daten;
const m0 = await meine();
const alle0 = (m0?.bloecke || []).flatMap((b) => b.punkte);
ok(alle0.some((p) => p.schluessel === PA && p.zugetragen === true),
"die eigene Karte weiss, welche Punkte zugetragen sind");
ok(m0?.meine_aufgaben === 2,
`und sagt, wie viele es sind (${m0?.meine_aufgaben})`);
/* DER KERN DER MELDUNG: Er sieht seine Aufgabe, BEVOR irgendjemand
sie bewertet hat. Vorher war das umgekehrt -- ohne vollstaendige
Bewertung sah er gar nichts. */
ok(alle0.find((p) => p.schluessel === PA)?.offen === true,
"sie steht da, obwohl noch niemand etwas dazu gesetzt hat");
/* GEGENPROBE: Ein Punkt, der NICHT zugetragen ist, traegt die Marke
auch nicht. Ohne sie hiesse „zugetragen=true" womoeglich nur,
dass dort immer `true` steht. */
const fremd = alle0.find((p) => p.schluessel !== PA && p.schluessel !== PB);
ok(fremd && fremd.zugetragen === false,
`ein nicht zugetragener Punkt traegt die Marke nicht (${fremd?.schluessel})`);
/* ---- Und die andere Haelfte: sieht die Leitung, dass es bei ihm
noch nicht ankommt? ------------------------------------- */
await hole(`/workspace/api/entwicklung/person/${idModi}/${PA}`,
{ host: CREW, keks: hand.keks, methode: "PUT",
rumpf: JSON.stringify({ stufe: "laeuft", anlass: "Im Stream gesehen." }) });
const sicht = async (keks) => {
const k = (await hole(`/workspace/api/entwicklung/person/${idModi}`,
{ host: CREW, keks })).daten;
return (k?.bloecke || []).flatMap((b) => b.punkte).find((p) => p.schluessel === PA);
};
const beiVan = await sicht(hand.keks);
ok(beiVan?.sieht_er && beiVan.sieht_er.da === 1 && beiVan.sieht_er.noetig === 2,
`VanVan sieht: eine von zwei Einschaetzungen liegt vor `
+ `(${JSON.stringify(beiVan?.sieht_er)})`);
const m1 = await meine();
ok((m1?.bloecke || []).flatMap((b) => b.punkte)
.find((p) => p.schluessel === PA)?.offen === true,
"und bei ihm ist der Punkt weiterhin offen -- eine halbe Karte waere schlimmer");
/* GEGENPROBE, OHNE DIE DIE ZWEI ZEILEN OBEN NICHTS BEWEISEN:
Setzt der zweite Beurteiler auch, geht es durch -- und die
Leitung sieht das ebenfalls. */
await hole(`/workspace/api/entwicklung/person/${idModi}/${PA}`,
{ host: CREW, keks: dogi.keks, methode: "PUT",
rumpf: JSON.stringify({ stufe: "laeuft", anlass: "Sehe ich auch so." }) });
const beiVan2 = await sicht(hand.keks);
ok(beiVan2?.sieht_er?.da === 2,
`nach der zweiten Einschaetzung steht es auf 2 von 2 `
+ `(${JSON.stringify(beiVan2?.sieht_er)})`);
const m2 = await meine();
const pa2 = (m2?.bloecke || []).flatMap((b) => b.punkte)
.find((p) => p.schluessel === PA);
ok(pa2?.offen === false && pa2?.zugetragen === true,
`jetzt sieht er die Einschaetzung -- und es ist weiterhin seine Aufgabe `
+ `(offen=${pa2?.offen}, zugetragen=${pa2?.zugetragen})`);
ok(Array.isArray(pa2?.antworten) && pa2.antworten[0]?.anzahl === 2,
`mit beiden Antworten (${JSON.stringify(pa2?.antworten)})`);
}
d.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log("");
+78 -2
View File
@@ -731,6 +731,10 @@ entwicklungRouter.get("/workspace/api/entwicklung/person/:id", nurLeitung, (req,
`gesetzt`/`gesamt`): „0 von 68" war die Zahl, die ihn gestoert
hat, und sie sagt bei jemandem mit drei Aufgaben nichts. */
const zugeteilt = zugeteiltFuer(id);
/* EINMAL, NICHT JE PUNKT. Beim ersten Anlauf stand der Aufruf
drinnen in der Schleife -- 68 Datenbankfragen fuer eine Zahl,
die sich waehrend eines Kartenaufrufs nicht aendert. */
const noetigeStimmen = beurteilerZahl(p.id);
const bloecke = katalogFuer(req.person, false).map((b) => ({
...b,
punkte: b.punkte.map((pt) => {
@@ -749,6 +753,27 @@ entwicklungRouter.get("/workspace/api/entwicklung/person/:id", nurLeitung, (req,
nicht" dasselbe Bild, und das erste ist eine Auskunft,
das zweite eine Taeuschung. */
andere_offen: m ? 0 : fremde.filter((f) => f.schluessel === pt.schluessel).length,
/* ==== SIEHT ER ES SCHON? (30.09.2026) =====================
VanVan aus dem Support: Sie hatte bewertet, und beim Modi
kam nichts an. Der Grund ist die Regel unten in
`meine-karte`: Ein Punkt erscheint bei ihm erst, wenn
ALLE Beurteiler gesetzt haben.
Die Regel bleibt -- eine einzelne Meinung soll bei einem
Menschen nicht als Urteil des Teams ankommen. Was fehlte,
war die Rueckmeldung an die, die schon gesetzt haben:
VanVan hat getan, was sie tun sollte, und nichts auf
ihrem Bildschirm sagte ihr, dass es liegen bleibt.
GEZAEHLT WIRD GEGEN DIESELBE LISTE wie dort, nicht gegen
eine eigene -- sonst sagen die beiden Seiten irgendwann
Verschiedenes ueber denselben Punkt. */
sieht_er: (() => {
const wieViele = (m ? 1 : 0)
+ fremde.filter((f) => f.schluessel === pt.schluessel).length;
return { da: wieViele, noetig: noetigeStimmen };
})(),
/* DER SCHRITT ZU DIESEM PUNKT. Er steht an der Karte und
nicht auf einer eigenen Liste: Wer die Beobachtung sieht,
soll im selben Blick sehen, ob daraus etwas geworden ist. */
@@ -893,6 +918,22 @@ entwicklungRouter.get("/workspace/api/entwicklung/person/:id", nurLeitung, (req,
* erinnert, und weit genug, dass sich etwas ändern kann. */
export const SCHRITT_TAGE = 14;
/** Wie viele Menschen urteilen ueber diese Person?
*
* AN EINER STELLE (30.09.2026). Dieselbe Frage wird an zwei Enden
* gestellt: `meine-karte` entscheidet damit, ob ein Punkt bei dem
* Menschen ueberhaupt erscheint, und die Karte der Leitung sagt
* damit, ob er ihn schon sieht. Zwei Abschriften waeren die, die
* irgendwann Verschiedenes ueber denselben Punkt sagen.
*
* ALLE AUSSER IHM SELBST: Wer sich selbst einschaetzt, ist keine
* zweite Meinung. */
function beurteilerZahl(personId) {
return db().prepare(
"SELECT COUNT(*) AS n FROM personen WHERE rolle IN ('admin','hand') AND aktiv = 1 AND id <> ?")
.get(personId)?.n || 0;
}
/** Die Schritte zu den Punkten einer Person — einer je Punkt.
*
* ES IST IMMER DER JÜNGSTE. Wer denselben Punkt ein zweites Mal
@@ -1278,7 +1319,10 @@ entwicklungRouter.get("/workspace/api/entwicklung/meine-karte", (req, res) => {
return res.json({ hat_karte: false });
}
/* Wer ueber diese Person urteilt -- alle ausser ihr selbst. */
/* Wer ueber diese Person urteilt -- alle ausser ihr selbst.
DIE ZAHL KOMMT AUS `beurteilerZahl()`, die Liste von hier: Die
Karte der Leitung stellt dieselbe Frage, und zwei Abschriften
waeren die, die irgendwann Verschiedenes sagen. */
const beurteiler = db().prepare(
"SELECT id FROM personen WHERE rolle IN ('admin','hand') AND aktiv = 1 AND id <> ?")
.all(req.person.id).map((z) => z.id);
@@ -1293,6 +1337,20 @@ entwicklungRouter.get("/workspace/api/entwicklung/meine-karte", (req, res) => {
nachPunkt.get(z.schluessel).push(z);
}
/* ==== WELCHE PUNKTE SIND SEINE AUFGABE? (30.09.2026) =========
VanVan aus dem Support: „dann sieht der Modi zwar die
Auswertung im Bereich Entwicklung, aber bei ihm taucht nichts
im Bereich eure Aufgaben unten in der Karte auf."
Nachgemessen war es genau so: Seine Karte lieferte alle 68
Punkte des Katalogs und kein Wort darueber, welche davon ihm
zugetragen wurden. Dabei ist das die eigentliche Auskunft --
die Bewertung ist die Folge, nicht der Gegenstand. Wer nicht
weiss, was seine Aufgaben sind, kann sie auch nicht
angehen. */
const meineAufgaben = zugeteiltFuer(req.person.id);
const bloecke = ENTWICKLUNG_BLOECKE
.filter((b) => !b.nurSelbst)
.map((b) => ({
@@ -1301,13 +1359,27 @@ entwicklungRouter.get("/workspace/api/entwicklung/meine-karte", (req, res) => {
const liste = nachPunkt.get(pt.schluessel) || [];
/* VOLLSTAENDIG heisst: jeder Beurteiler hat gesetzt. */
const fertig = beurteiler.length > 0 && liste.length >= beurteiler.length;
/* DIE ZUTEILUNG HAENGT NICHT AN DER BEWERTUNG. Sie geht
auch dann mit, wenn noch niemand etwas gesetzt hat --
sonst saehe er seine Aufgabe erst, wenn sie schon
beurteilt ist, und das ist die falsche Reihenfolge. */
const zugetragen = meineAufgaben.has(pt.schluessel);
if (!fertig) {
return { schluessel: pt.schluessel, titel: pt.titel, stufe: pt.stufe, offen: true };
return {
schluessel: pt.schluessel, titel: pt.titel, stufe: pt.stufe,
offen: true, zugetragen,
/* WIE VIELE NOCH FEHLEN. „Noch nichts fertig" allein
sieht aus wie ein Fehler; „einer von zwei hat
gesetzt" ist eine Auskunft. */
davon_da: liste.length,
};
}
const zaehler = {};
for (const z of liste) zaehler[z.stufe] = (zaehler[z.stufe] || 0) + 1;
return {
schluessel: pt.schluessel, titel: pt.titel, stufe: pt.stufe, offen: false,
zugetragen,
davon_da: liste.length,
/* Nach der Reihenfolge des Katalogs, nicht nach Haeufigkeit
-- sonst stuende bei einer Abweichung mal das eine, mal
das andere zuerst, und das liest sich wie eine Wertung. */
@@ -1326,6 +1398,10 @@ entwicklungRouter.get("/workspace/api/entwicklung/meine-karte", (req, res) => {
beurteiler: beurteiler.length,
fertig: alle.filter((x) => !x.offen).length,
gesamt: alle.length,
/* WIE VIELE PUNKTE SEINE AUFGABE SIND. Die Zahl steht oben an
der Karte -- „2 Aufgaben" ist etwas anderes als „0 von 68",
und das zweite hat Filipe schon einmal zu Recht gestoert. */
meine_aufgaben: alle.filter((x) => x.zugetragen).length,
stufen: ENTWICKLUNG_STUFEN,
erwartung: ENTWICKLUNG_ERWARTUNG,
bloecke,