91 lines
4 KiB
Python
91 lines
4 KiB
Python
#!/usr/bin/env python3
|
||
"""$0 сводка расхода сессии ДВУМЯ независимыми путями.
|
||
|
||
Путь 1 — то, что каждый харнесс записал сам (`cost_usd` в своих артефактах).
|
||
Путь 2 — пере-счёт из СЫРОГО `usage` по прайсу `models.yaml` (кэш-токены по своей ставке).
|
||
Расхождение двух путей и есть проверка: одинаковые числа, полученные одним способом, — не проверка.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import sqlite3
|
||
from pathlib import Path
|
||
|
||
import yaml
|
||
|
||
REPO = Path("/home/ubuntu/projects/textmachine")
|
||
PL = Path.home() / "books" / "gu-zhenren" / "promptlang"
|
||
CUTOFF = "2026-07-31 18:40" # первая трата сессии
|
||
|
||
prices = yaml.safe_load((REPO / "backend/configs/models.yaml").read_text(encoding="utf-8"))["models"]
|
||
|
||
|
||
def px(model: str) -> tuple[float, float, float]:
|
||
p = prices[model]["price"]
|
||
return p["input_per_m"], p["output_per_m"], p.get("cached_per_m", 0.0)
|
||
|
||
|
||
rows = []
|
||
|
||
# — движковые прогоны (задача A арм en; задача B арм a0 в боевой форме до стоп-гейта)
|
||
for proj in ("A-ru", "A-en", "B-a0", "B-a1", "B-a2"):
|
||
db = PL / proj / "db.sqlite"
|
||
if not db.exists():
|
||
continue
|
||
con = sqlite3.connect(db)
|
||
for r in con.execute("select model_actual, prompt_tokens, cached_tokens, completion_tokens,"
|
||
" cost_usd from request_log where ts > ?", (CUTOFF,)):
|
||
m, pt, cch, ct, self_cost = r
|
||
if not m:
|
||
continue
|
||
pin, pout, pcached = px(m)
|
||
recomputed = (pt - cch) / 1e6 * pin + cch / 1e6 * pcached + ct / 1e6 * pout
|
||
rows.append(("движок " + proj, self_cost, recomputed))
|
||
con.close()
|
||
|
||
# — сырые пробы провода и армы
|
||
for d, label in ((PL / "wire", "wire-пробы"), (PL / "armsraw", "армы задачи B/A")):
|
||
for f in sorted(d.glob("*.json")):
|
||
if f.name == "fragments.json":
|
||
continue
|
||
j = json.loads(f.read_text(encoding="utf-8"))
|
||
if "error" in j:
|
||
continue
|
||
pin, pout, pcached = px("deepseek-v4-flash")
|
||
pt, cch, ct = j.get("prompt_tokens", 0), j.get("cached_tokens", 0), j.get("completion_tokens", 0)
|
||
recomputed = (pt - cch) / 1e6 * pin + cch / 1e6 * pcached + ct / 1e6 * pout
|
||
rows.append((label, j.get("cost_usd", 0.0), recomputed))
|
||
|
||
# — судьи (свои леджеры, свои модели; в judge_p4 их ДВА, маршрутизация по семейству пары)
|
||
for sub, label in (("judge", "судья армов"), ("judge_p4", "судья пар P4")):
|
||
led = PL / sub / "ledger.jsonl"
|
||
if not led.exists():
|
||
continue
|
||
for ln in led.read_text().splitlines():
|
||
j = json.loads(ln)
|
||
if "error" in j:
|
||
continue
|
||
model = j.get("judge", "grok-4.3")
|
||
pin, pout, _ = px(model)
|
||
billed = j.get("out_billed")
|
||
if billed is None: # judge_p4: reasoning у deepseek subset, у xai additive
|
||
ct, rt = j.get("completion_tok", 0), j.get("reasoning_tok", 0)
|
||
billed = ct + rt if model.startswith("grok") and rt > ct else ct
|
||
recomputed = j.get("prompt_tok", 0) / 1e6 * pin + billed / 1e6 * pout
|
||
rows.append((f"{label} {model}", j.get("cost_usd", 0.0), recomputed))
|
||
|
||
agg: dict[str, list[float]] = {}
|
||
for label, a, b in rows:
|
||
v = agg.setdefault(label, [0.0, 0.0, 0])
|
||
v[0] += a
|
||
v[1] += b
|
||
v[2] += 1
|
||
|
||
print(f"{'источник':<22}{'вызовов':>9}{'путь 1 ($)':>13}{'путь 2 ($)':>13}{'Δ':>12}")
|
||
t1 = t2 = 0.0
|
||
for label, (a, b, n) in sorted(agg.items()):
|
||
print(f"{label:<22}{n:>9}{a:>13.6f}{b:>13.6f}{a-b:>12.2e}")
|
||
t1 += a
|
||
t2 += b
|
||
print(f"{'ИТОГО':<22}{sum(v[2] for v in agg.values()):>9}{t1:>13.6f}{t2:>13.6f}{t1-t2:>12.2e}")
|
||
print(f"\nпотолок сессии $0.15 · использовано {t1/0.15*100:.1f}% · остаток ${0.15-t1:.6f}")
|