/* ===================================================================== aether-police / Ansicht: Statistik Zahlen über die Behörde, nicht über einzelne Beamte. Es gibt hier bewusst keine Rangliste — jede Kennzahl beschreibt einen Vorgang, keine Leistung. ===================================================================== */ "use strict"; (() => { const { sicher, aufruf, meldung, euro } = App; const z = { reiter: "uebersicht", // uebersicht | abdeckung | katalog tage: 30, daten: null, }; /* ---- Bausteine ------------------------------------------------------ */ /** Runde Zahl mit Tausenderpunkt */ function zahl(wert) { return Math.round(wert || 0).toLocaleString("de-DE"); } /** Kennzahlkachel */ function kachel(wert, text, unter) { return `
${wert} ${sicher(text)} ${unter ? `${sicher(unter)}` : ""}
`; } /** Waagerechtes Balkendiagramm aus einer Verteilung */ function verteilung(daten, leer) { if (!daten || !daten.eintraege.length) { return `
${sicher(leer || "Keine Daten im Zeitraum.")}
`; } return `
${daten.eintraege.map((e) => `
${sicher(e.name)}
${zahl(e.anzahl)} ${e.anteil} %
`).join("")}
`; } /** Säulendiagramm des Tagesverlaufs */ function verlauf(zeilen) { if (!zeilen || !zeilen.length) { return `
Im Zeitraum wurde nichts erfasst.
`; } // Höchstwert bestimmt die Skalierung beider Reihen let hoechst = 1; zeilen.forEach((t) => { hoechst = Math.max(hoechst, +t.einsaetze || 0, +t.anzeigen || 0); }); return `
Einsätze Anzeigen Höchstwert: ${hoechst}
${zeilen.map((t) => { const einsaetze = +t.einsaetze || 0; const anzeigen = +t.anzeigen || 0; const tag = String(t.tag).slice(0, 10); return `
${sicher(tag.slice(8))}
`; }).join("")}
`; } function reiterleiste() { const eintraege = [ { id: "uebersicht", text: "Überblick" }, { id: "abdeckung", text: "Dienstabdeckung" }, { id: "katalog", text: "Katalogauswertung" }, ]; const zeitraeume = (z.daten && z.daten.zeitraeume) || [7, 30, 90]; return `
${eintraege.map((e) => ` `).join("")}
${zeitraeume.map((t) => ` `).join("")}
`; } /* ---- Überblick -------------------------------------------------------- */ function uebersicht(behaelter) { const d = z.daten; const e = d.einsaetze, a = d.anzeigen, g = d.gewahrsam, v = d.vorgaenge, p = d.personal; behaelter.innerHTML = `

Statistik

Auswertung der letzten ${d.tage} Tage. Alle Zahlen beschreiben Vorgänge der Behörde — es gibt hier keine Bewertung einzelner Beamter.

${reiterleiste()}
${kachel(zahl(e.gesamt), "Einsätze", e.offen + " offen")} ${kachel(zahl(a.gesamt), "Anzeigen", a.bestaetigt + " bestätigt")} ${kachel(zahl(g.einweisungen), "Einweisungen", g.laufend + " laufend")} ${kachel(zahl(p.dienstStunden), "Dienststunden", p.bestand + " Beamte im Bestand")}
Verlauf
${verlauf(d.verlauf)}
Einsätze nach Kategorie
${verteilung(e.nachKategorie)}
Ein Einsatz bleibt im Schnitt ${zahl(e.laufzeitMinuten)} Minuten offen.
Einsätze nach Priorität
${verteilung(e.nachPrioritaet)}
Anzeigen
Bestätigt${zahl(a.bestaetigt)}
Noch Entwurf${zahl(a.entwurf)}
Ø festgelegte Haft${zahl(a.haftDurchschnitt)} Min
Ø festgelegte Geldstrafe${euro(a.geldDurchschnitt)}
In ${zahl(a.abweichungen)} von ${zahl(a.bestaetigt)} bestätigten Anzeigen wich die Entscheidung vom Katalogvorschlag ab. Das ist eine Aussage über den Katalog — nicht über die Beamten.
Gewahrsam
Einweisungen${zahl(g.einweisungen)}
Laufend${zahl(g.laufend)}
Ø festgelegte Dauer${zahl(g.haftDurchschnitt)} Min
Vorzeitig entlassen${zahl(g.vorzeitig)}
Zwischen Fristablauf und tatsächlicher Entlassung lagen im Schnitt ${zahl(g.verzugMinuten)} Minuten. Das System entlässt niemanden von selbst — der Wert zeigt, wie schnell jemand erreichbar war.
Vorgänge
Akten angelegt${zahl(v.akten)}
Akten offen${zahl(v.aktenOffen)}
Berichte${zahl(v.berichte)}
Berichte im Entwurf${zahl(v.berichteEntwurf)}
Fahndungen${zahl(v.fahndungen)}
Fahndungen aktiv${zahl(v.fahndungenAktiv)}
Beweise erfasst${zahl(v.beweise)}
Asservate eingelagert${zahl(v.asservate)}
Akten nach Art
${verteilung(v.aktenNachTyp)}
Personal nach Abteilung ${p.imDienst} gerade im Dienst
${verteilung(p.nachAbteilung)}
`; verdrahte(behaelter); } /* ---- Dienstabdeckung --------------------------------------------------- */ function abdeckung(behaelter) { const d = z.daten; const hoechst = Math.max(d.hoechstwert, 0.1); // Wo ist die Besetzung am dünnsten? const schwach = [...d.stunden].sort((a, b) => a.mittel - b.mittel).slice(0, 3); behaelter.innerHTML = `

Dienstabdeckung

Mittlere Besetzung je Tageszeit über ${d.tage} Tage — ein Planungswert. Er sagt nichts darüber, wer Dienst hatte.

${reiterleiste()}
${kachel(zahl(d.schichten), "Schichten im Zeitraum")} ${kachel(d.hoechstwert.toFixed(2), "stärkste Stunde", "gleichzeitig im Dienst")} ${kachel(schwach.map((s) => s.stunde + " Uhr").join(", "), "dünnste Stunden")}
Besetzung nach Tageszeit Durchschnittlich gleichzeitig im Dienst
${d.stunden.map((s) => `
${String(s.stunde).padStart(2, "0")}
`).join("")}
Schichten nach Abteilung
${verteilung(d.nachAbteilung)}
`; verdrahte(behaelter); } /* ---- Katalogauswertung -------------------------------------------------- */ function katalog(behaelter) { const d = z.daten; behaelter.innerHTML = `

Katalogauswertung

Welche Delikte kommen vor und wie oft weicht die Entscheidung vom Vorschlag ab. Eine hohe Abweichungsquote heißt: der Katalogwert passt nicht — nicht, dass jemand falsch entschieden hat.

${reiterleiste()}
${kachel(zahl(d.anzeigen), "ausgewertete Anzeigen", "letzte " + d.tage + " Tage")} ${kachel(zahl(d.delikte.length), "verwendete Delikte")} ${kachel(zahl(d.ungenutzt.length), "nie verwendet", "von " + d.katalogGesamt + " im Katalog")}
Häufigste Delikte
${d.delikte.length ? `
${d.delikte.map((delikt) => `
${sicher(delikt.kennung)}
${sicher(delikt.name)}
${sicher(delikt.paragraph || "")}
${zahl(delikt.anzahl)}× 0 ? "info" : "neutral"}" title="Anteil der Anzeigen mit diesem Delikt, in denen vom Vorschlag abgewichen wurde"> ${delikt.abweichungsAnteil} % abweichend
`).join("")}
` : `
Im Zeitraum wurde keine Anzeige bestätigt.
`}
Im Zeitraum nie verwendet Kandidaten zum Aufräumen des Katalogs
${d.ungenutzt.length ? `
${d.ungenutzt.map((delikt) => ` ${sicher(delikt.kennung)} · ${sicher(delikt.bezeichnung)}`).join("")}
` : `
Jedes Delikt des Katalogs kam vor.
`}
`; verdrahte(behaelter); } /* ---- Verdrahtung --------------------------------------------------------- */ function verdrahte(behaelter) { behaelter.querySelectorAll("[data-reiter]").forEach((el) => { el.addEventListener("click", () => { z.reiter = el.dataset.reiter; rendern(behaelter); }); }); behaelter.querySelectorAll("[data-tage]").forEach((el) => { el.addEventListener("click", () => { z.tage = +el.dataset.tage; rendern(behaelter); }); }); } /* ---- Laden ---------------------------------------------------------------- */ async function rendern(behaelter) { const aktionen = { uebersicht: "uebersicht", abdeckung: "abdeckung", katalog: "katalog", }; try { z.daten = await aufruf("statistik", aktionen[z.reiter], { tage: z.tage }); // Der Server bestimmt den gültigen Zeitraum z.tage = z.daten.tage; if (z.reiter === "abdeckung") abdeckung(behaelter); else if (z.reiter === "katalog") katalog(behaelter); else uebersicht(behaelter); } catch (f) { behaelter.innerHTML = `

Statistik

Konnte nicht geladen werden: ${sicher(f.message)}

`; } } App.registriereAnsicht("statistik", { rendern }); })();