"""Tests für die puren Aggregationsfunktionen der Betriebsstatistik.""" from routers.statistik import ( baue_monatsachse, normalisiere_fehler, aggregiere_refresh, berechne_credits, aggregiere_orgs, fuelle_monatsserie, ) TARIF = {"monitor_adhoc": 45, "monitor_research": 40, "chat": 1, "enhance": 1, "globe": 1} def test_monatsachse_lueckenlos_ueber_jahresgrenze(): assert baue_monatsachse("2025-11", "2026-02") == ["2025-11", "2025-12", "2026-01", "2026-02"] def test_monatsachse_leer_bei_unsinn(): assert baue_monatsachse("2026-05", "2026-02") == [] assert baue_monatsachse("", "2026-02") == [] def test_fehler_normalisierung(): assert normalisiere_fehler("Endgültig fehlgeschlagen nach 3 Versuchen: Claude CLI Timeout nach 420s") == "Claude CLI Timeout nach 420s" assert normalisiere_fehler("Verwaist (Neustart, x)") == "Verwaist (Neustart, automatisch bereinigt)" assert normalisiere_fehler(None) == "Unbekannt" def _lauf(monat="2026-03", typ="adhoc", status="completed", kosten=5.0, **kw): return {"monat": monat, "typ": typ, "status": status, "trigger_type": "auto", "total_cost_usd": kosten, "input_tokens": 100, "output_tokens": 50, "articles_found": 3, "error_message": None, "dauer_s": 600.0, **kw} def test_median_ignoriert_nullkosten(): """Läufe mit 0 $ (Kostenerfassung fehlte) dürfen den Median nicht verzerren.""" rows = [_lauf(kosten=0.0), _lauf(kosten=4.0), _lauf(kosten=6.0)] agg = aggregiere_refresh(rows, ["2026-03"]) assert agg["kpi"]["median_kosten_lauf"] == 5.0 assert agg["kpi"]["laeufe_ok"] == 3 def test_betrieb_und_fehler(): rows = [_lauf(), _lauf(status="error", kosten=0.0, error_message="Endgültig fehlgeschlagen nach 3 Versuchen: Timeout X"), _lauf(status="error", kosten=0.0, error_message="Timeout X")] agg = aggregiere_refresh(rows, ["2026-03"]) b = agg["monat_betrieb"][0] assert b["completed"] == 1 and b["error"] == 2 assert agg["fehler_top"][0] == {"grund": "Timeout X", "n": 2} assert agg["kpi"]["erfolgsquote"] == 33.3 def test_aggregiere_refresh_leer(): agg = aggregiere_refresh([], []) assert agg["kpi"]["laeufe_gesamt"] == 0 assert agg["kpi"]["erfolgsquote"] is None assert agg["monat_typ"] == [] def test_credits_formel_erstlauf_dreifach(): """Anlage einer Recherche = 3 Durchläufe, erscheint aber als 1 Log-Zeile.""" credits = berechne_credits(adhoc_ok=2, research_ok=3, research_erstlaeufe=1, neben_aufrufe={"chat": 4}, tarif=TARIF) # 2*45 + (3 + 2*1)*40 + 4*1 = 90 + 200 + 4 assert credits == 294 def test_credits_formel_ohne_erstlauf(): """Grenzfall, der chronologisch erste Lauf war ein error. Dann zählt kein Erstlauf-Zuschlag (die Heuristik greift erst beim ersten completed Lauf, was die SQL-Seite über rn=1 auf completed-Zeilen abbildet).""" assert berechne_credits(0, 2, 0, {}, TARIF) == 80 def test_orgs_marge(): rows_orgs = [{"tenant_id": 1, "ist_kosten": 10.0, "laeufe_ok": 2, "laeufe_gesamt": 3, "adhoc_ok": 2, "research_ok": 0, "research_erstlaeufe": 0}] rows_neben = [{"organization_id": 1, "source": "chat", "api_calls": 10, "kosten": 0.5}] orgs = aggregiere_orgs(rows_orgs, rows_neben, {1: "Test-Org"}, TARIF, 0.20) o = orgs[0] assert o["credits_geschaetzt"] == 100 # 2*45 + 10*1 assert o["gegenwert_usd"] == 20.0 assert o["kosten_usd"] == 10.5 assert o["marge_usd"] == 9.5 def test_orgs_ohne_laeufe_taucht_trotzdem_auf(): orgs = aggregiere_orgs([], [], {5: "Leer-Org"}, TARIF, 0.20) assert orgs[0]["org_id"] == 5 assert orgs[0]["credits_geschaetzt"] == 0 def test_fuelle_monatsserie(): rows = [{"monat": "2026-03", "n": 2}] out = fuelle_monatsserie(rows, ["2026-02", "2026-03"], "monat", ["n"]) assert out == [{"monat": "2026-02", "n": 0}, {"monat": "2026-03", "n": 2}]