simbiot-harness/scripts/export_dashboard_snapshot.py

133 lines
4.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Снимок состояния харнеса Simbiot → публичный дашборд (t-261018220236-7d5851).
Запуск из корня simbiot-harness:
./venv/bin/python scripts/export_dashboard_snapshot.py /path/to/agent-dashboard/site/
Что делает:
- читает проекции стора (та же проекция, что у scheduler/CLI — консистентно);
- выгружает site/data.json: задачи, лестница, эскалации, дайджесты,
AUM-историю, траты по уровням, uptime;
- НЕ выносит секреты: только агрегаты и открытые тексты (публичная страница).
Дашборд — статика (nginx), обновление снимка = повторный запуск скрипта +
push в репо + deploy_app на портале. Автообновление живых данных — roadmap.
"""
import json
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from harness import es # noqa: E402
LEVELS = ["sec", "min", "hour", "day"]
LEVEL_META = {
"sec": {"interval": "1 с", "ctx": "0 (без LLM)", "job": "рефлексы: телеметрия, алерты"},
"min": {"interval": "1 мин", "ctx": "256K", "job": "S-триаж: входящие, быстрые фиксы"},
"hour": {"interval": "1 ч", "ctx": "512K", "job": "S/M-задачи, дайджест часа"},
"day": {"interval": "1 д", "ctx": "1M", "job": "M/L-задачи, манифест дня"},
}
DIGEST_LEVELS = {"day", "hour"} # публично показываем только содержательные уровни
def build() -> dict:
st = es.run_projections()
tasks = sorted(
st["tasks"].values(),
key=lambda t: t.get("created", ""),
)
tasks_public = [
{
"id": t.get("id"),
"title": t.get("title"),
"size": t.get("size"),
"status": t.get("status"),
"level": t.get("level"),
"created": t.get("created"),
}
for t in tasks
]
ladder = {}
for lv in LEVELS:
c = st["cycle_state"].get(lv, {})
ladder[lv] = {
"busy": bool(c.get("busy")),
"ticks": c.get("ticks", 0),
"last_tick": c.get("last_tick"),
**LEVEL_META[lv],
}
escalations = [
{
"id": e.get("id"),
"severity": e.get("severity"),
"status": e.get("status"),
"from_level": e.get("from_level"),
"to_level": e.get("to_level"),
"reason": e.get("reason"),
"ts": e.get("ts"),
}
for e in st.get("escalations", {}).values()
if e.get("status") == "open"
]
digests = [
{
"level": d.get("level"),
"period": d.get("period"),
"text": (d.get("text") or "").strip(),
"ts": d.get("ts"),
}
for d in st.get("digests", {}).values()
if d.get("level") in DIGEST_LEVELS
]
digests.sort(key=lambda d: d.get("period") or "", reverse=True)
spend = {}
for s in st.get("spend", []):
lv = s.get("level", "?")
agg = spend.setdefault(lv, {"calls": 0, "prompt_tokens": 0, "completion_tokens": 0})
agg["calls"] += 1
agg["prompt_tokens"] += s.get("prompt_tokens", 0) or 0
agg["completion_tokens"] += s.get("completion_tokens", 0) or 0
import datetime
snapshot = {
"generated_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
"harness": "simbiot-harness",
"agent": "alex-shkor-6af5",
"portal": "https://cil.by",
"ladder": ladder,
"tasks": tasks_public,
"tasks_stats": {
"total": len(tasks_public),
"by_status": {
s: sum(1 for t in tasks_public if t["status"] == s)
for s in ("ready", "running", "blocked", "failed", "done", "closed")
},
},
"escalations_open": escalations,
"digests": digests[:20],
"aum_history": st.get("aum_history", []),
"spend_by_level": spend,
}
return snapshot
def main() -> int:
dest = sys.argv[1] if len(sys.argv) > 1 else "site"
os.makedirs(dest, exist_ok=True)
snap = build()
path = os.path.join(dest, "data.json")
with open(path, "w", encoding="utf-8") as f:
json.dump(snap, f, ensure_ascii=False, indent=1)
print(f"snapshot → {path} (tasks={snap['tasks_stats']['total']}, "
f"escalations_open={len(snap['escalations_open'])}, digests={len(snap['digests'])})")
return 0
if __name__ == "__main__":
raise SystemExit(main())