feat(statistik): Hauptreiter Statistik mit kompletter Betriebsstatistik

Neuer Reiter zwischen Audit-Log und Verbrauchsrechner, Anlass war die
Dashboard-Kachel API-Kosten gesamt, die nirgendwo hinführte (sie springt
jetzt hierher). Kennzahlen-Set nach Betreiber-Best-Practices für
nutzungsbasierte KI-Produkte (Kostenverlauf, Marge je Kunde, Betrieb).

- GET /api/statistik?months=N liefert alles in einem JSON. Kosten und
  Läufe je Monat und Lagentyp (führende Quelle refresh_log mit
  lückenlosem incidents-Join), Quellen-Split aus token_usage_monthly,
  Erfolgsquote, normalisiertes Fehler-Ranking, Dauer je Typ, Wachstum,
  MAU/DAU aus user_activity_days und je Organisation Ist-Kosten plus
  Margen-Schätzung zu heutigen Sätzen (Recherche-Anlage zählt dreifach,
  weil der Multi-Pass nur eine Log-Zeile erzeugt). Aggregation als pure,
  unit-getestete Funktionen, Monatsachse lückenlos gefüllt, Mediane nur
  über Läufe mit Kosten (Erfassung begann erst 03/2026).
- statistik.js injiziert sich selbst nach dem rechner.js-Muster,
  Platzierung doppelt gesichert (Ladereihenfolge mit async=false plus
  insertBefore). Charts als Inline-SVG ohne Bibliothek, eine Achse je
  Chart, Hover-Titel mit großen Trefferflächen, Legenden, Serienfarben
  je Theme mit dem dataviz-Palette-Validator geprüft (alle Checks PASS).
  Zeitraum 6/12/Alles, Datenstand-Hinweis, ehrliche Fußnote unter der
  Margen-Tabelle, Leerzustand für die junge Aktivitäts-Erfassung.
- Migration user_activity_days für die Portal-Staging-DB (auf Live legt
  sie der Monitor an, dort läuft die Erfassung in get_current_user).
- 10 neue Unit-Tests plus Auth-Smoke für den Endpoint, 96 Tests grün
  (die zwei bekannten shared-Import-Fehlschläge bestehen unverändert).

Gegenstück im Monitor: Commit d367c60 (Aktivitäts-Erfassung).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-07-25 15:09:34 +00:00
Ursprung 5fd4e64b4c
Commit 25ec80c8e4
9 geänderte Dateien mit 1020 neuen und 7 gelöschten Zeilen

Datei anzeigen

@@ -1180,11 +1180,22 @@ async function loadDashboardTokenStats() {
};
}, { cost: 0, refreshes: 0 });
// Sprung-Kachel zum Statistik-Tab. Handler direkt anhaengen, denn die
// Kachel entsteht NACH wireStatCardLinks() (ein erneuter Aufruf dort
// wuerde die anderen Kacheln doppelt binden).
const card = document.createElement('div');
card.className = 'stat-card';
card.className = 'stat-card stat-card-link';
card.setAttribute('role', 'link');
card.setAttribute('tabindex', '0');
card.title = 'Zur Statistik';
card.innerHTML = '<div class="stat-label">API-Kosten gesamt</div>' +
'<div class="stat-value">$' + totals.cost.toFixed(2) + '</div>' +
'<div class="stat-sub">' + totals.refreshes + ' Refreshes</div>';
const springe = () => gotoSection('statistik');
card.addEventListener('click', springe);
card.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); springe(); }
});
grid.appendChild(card);
}
} catch (err) {
@@ -1304,9 +1315,14 @@ document.addEventListener("DOMContentLoaded", () => {
}
});
// --- Verbrauchsrechner nachladen (eigenstaendiges Modul, siehe rechner.js) ---
// --- Statistik und Verbrauchsrechner nachladen (eigenstaendige Module) ---
// async=false erzwingt die Ausfuehrungs-Reihenfolge (dynamische Scripts sind
// sonst async), damit der Statistik-Tab links vom Verbrauchsrechner landet.
(function () {
var s = document.createElement("script");
s.src = "/static/js/rechner.js?v=20260725b";
document.head.appendChild(s);
["/static/js/statistik.js?v=20260725a", "/static/js/rechner.js?v=20260725b"].forEach(function (src) {
var s = document.createElement("script");
s.src = src;
s.async = false;
document.head.appendChild(s);
});
})();