133 lines
4.7 KiB
Python
133 lines
4.7 KiB
Python
#!/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())
|