mirror of
https://github.com/Jeuners/Logpy-AgentOne.git
synced 2026-09-09 23:12:32 +02:00
Audit auf Optimierungspotenzial ergab: - /api/anrufe las bei jeder Anfrage ALLE meta.json unter ablage/ neu (auch bereits archivierte, erst danach rausgefiltert) - waechst mit der Gesamtzahl je empfangener Anrufe, jetzt von bis zu 3 Logins alle 3s parallel gepollt. - /api/verlauf las bei jeder Anfrage bis zu 300 KB aus dem FreeSWITCH-Log und scannte 4000 Zeilen per Regex, ebenfalls ungecacht. Beide bekommen jetzt einen kurzen 2s-Cache (gleiches Muster wie dienste.status()). Maskierung bei Testzugang arbeitet jetzt auf einer Kopie (dict(a) je Eintrag) statt die gecachten Objekte in-place zu veraendern - sonst haetten maskierte Rufnummern in die naechste, nicht maskierte Antwort durchsickern koennen. Dringlichkeit-Farben (rot/orange/gruen/grau) und der Sortierrang standen in drei Dateien dupliziert (dashboard.py, deck.py, server.py-JS) - bei der letzten Farbaenderung mussten alle drei von Hand synchron gehalten werden. Jetzt eine Quelle in pipe/kategorien.py (DRINGLICHKEIT_FARBE, DRINGLICHKEIT_RANG - Rang aus DRINGLICHKEITEN abgeleitet statt separat gepflegt), server.py injiziert die Farben serverseitig wie schon die Kategorie-Labels. Getestet: RANG/FARBE identisch zum alten Stand, /api/anrufe und /api/verlauf liefern bei zwei Anfragen innerhalb 2s dasselbe (gecachte) Ergebnis, Maskierung veraendert nachweislich nur die Kopie. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbdWYnDtyMgxsq4eN1WnrQ
101 lines
4.4 KiB
Python
101 lines
4.4 KiB
Python
"""Erzeugt aus der lokalen Ablage eine HTML-Triage-Übersicht.
|
|
|
|
python3 -m pipe.dashboard [ausgabe.html]
|
|
|
|
Rein lokal, liest nur die meta.json-Dateien. Notfälle stehen oben.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import html
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from . import config, kategorien
|
|
|
|
RANG = kategorien.DRINGLICHKEIT_RANG
|
|
FARBE = {k: f"#{v}" for k, v in kategorien.DRINGLICHKEIT_FARBE.items()}
|
|
KAT_LABEL = kategorien.KATEGORIE_LABEL
|
|
|
|
|
|
def _anrufe() -> list[dict]:
|
|
daten = []
|
|
if not config.ABLAGE_LOKAL.is_dir():
|
|
return daten
|
|
for meta in config.ABLAGE_LOKAL.glob("*/*/meta.json"):
|
|
try:
|
|
daten.append(json.loads(meta.read_text(encoding="utf-8")))
|
|
except Exception:
|
|
continue
|
|
daten.sort(key=lambda d: (RANG.get(d["auswertung"]["dringlichkeit"], 9), d["empfangen"]))
|
|
return daten
|
|
|
|
|
|
def _karte(d: dict) -> str:
|
|
e = d["auswertung"]
|
|
farbe = FARBE.get(e["dringlichkeit"], "#888")
|
|
wer = html.escape(e.get("anrufer_name") or "—")
|
|
nummer = html.escape(d.get("anrufer_nummer") or e.get("rueckrufnummer") or "—")
|
|
stich = " ".join(f"<span class='tag'>{html.escape(s)}</span>" for s in (e.get("stichworte") or []))
|
|
return f"""
|
|
<article class="karte" style="--akzent:{farbe}">
|
|
<div class="kopf">
|
|
<span class="kat">{html.escape(KAT_LABEL.get(e['kategorie'], e['kategorie']))}</span>
|
|
<span class="dring" style="background:{farbe}">{html.escape(e['dringlichkeit'])}</span>
|
|
<span class="zeit">{html.escape(d['empfangen'].replace('T', ' '))}</span>
|
|
</div>
|
|
<div class="wer"><strong>{wer}</strong> · <span class="nr">{nummer}</span>
|
|
{"· 📞 Rückruf" if e.get('rueckruf_gewuenscht') else ""}</div>
|
|
<p class="anliegen">{html.escape(e.get('anliegen_kurz') or '')}</p>
|
|
<div class="tags">{stich}</div>
|
|
<details><summary>Transkript</summary><p class="tr">{html.escape(d.get('transkript') or '')}</p></details>
|
|
</article>"""
|
|
|
|
|
|
def baue(ausgabe: Path) -> Path:
|
|
anrufe = _anrufe()
|
|
zahl = {k: sum(1 for d in anrufe if d["auswertung"]["dringlichkeit"] == k) for k in RANG}
|
|
zusammenfassung = " · ".join(
|
|
f"<b style='color:{FARBE[k]}'>{zahl[k]}</b> {k}" for k in RANG if zahl[k]
|
|
)
|
|
karten = "\n".join(_karte(d) for d in anrufe)
|
|
doc = f"""<!doctype html><html lang="de"><head><meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Anruf-Übersicht · {html.escape(kategorien.PROFIL_NAME)}</title>
|
|
<style>
|
|
:root{{--bg:#f6f7f9;--fg:#1a2230;--karte:#fff;--rand:#e4e8ee;--muted:#6b7688}}
|
|
@media(prefers-color-scheme:dark){{:root{{--bg:#12151b;--fg:#e7ecf3;--karte:#1b2029;--rand:#2a313d;--muted:#93a0b4}}}}
|
|
*{{box-sizing:border-box}}body{{margin:0;background:var(--bg);color:var(--fg);
|
|
font:15px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}}
|
|
.wrap{{max-width:840px;margin:0 auto;padding:28px 18px}}
|
|
h1{{font-size:22px;margin:0 0 4px}}.sub{{color:var(--muted);margin:0 0 20px;font-size:14px}}
|
|
.karte{{background:var(--karte);border:1px solid var(--rand);border-left:5px solid var(--akzent);
|
|
border-radius:12px;padding:14px 16px;margin:12px 0}}
|
|
.kopf{{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:6px}}
|
|
.kat{{font-weight:700}}.dring{{color:#fff;font-size:11px;font-weight:700;text-transform:uppercase;
|
|
padding:2px 8px;border-radius:999px;letter-spacing:.03em}}
|
|
.zeit{{margin-left:auto;color:var(--muted);font-size:13px;font-variant-numeric:tabular-nums}}
|
|
.wer{{margin:2px 0 6px}}.nr{{color:var(--muted)}}
|
|
.anliegen{{margin:6px 0}}.tags{{margin:6px 0}}
|
|
.tag{{display:inline-block;background:var(--bg);border:1px solid var(--rand);border-radius:6px;
|
|
padding:1px 7px;font-size:12px;color:var(--muted);margin:2px 4px 2px 0}}
|
|
details{{margin-top:6px}}summary{{cursor:pointer;color:var(--muted);font-size:13px}}
|
|
.tr{{color:var(--muted);font-size:13px;margin:6px 0 0}}
|
|
</style></head><body><div class="wrap">
|
|
<h1>Anruf-Übersicht · {html.escape(kategorien.PROFIL_NAME)}</h1>
|
|
<p class="sub">{len(anrufe)} Anrufe · {zusammenfassung or '—'} · lokal erzeugt aus der Ablage</p>
|
|
{karten}
|
|
</div></body></html>"""
|
|
ausgabe.write_text(doc, encoding="utf-8")
|
|
return ausgabe
|
|
|
|
|
|
def main() -> int:
|
|
ziel = Path(sys.argv[1]) if len(sys.argv) > 1 else config.WURZEL / "dashboard.html"
|
|
baue(ziel)
|
|
print(f"Dashboard: {ziel}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|