Files
ROMFASTSQL/tools/claude_usage_report.py
Claude Agent a01cac0657 feat(analiza): consum real Claude Code si dimensionarea suplimentului GLM
Plafonul general saptamanal Max 5x se consuma in ~2 zile, urmate de ~5 zile
blocate. Masurat pe 3 masini (Windows, claude-agent, moltbot), 2026-07-29 ->
2026-09-01: 11,15 miliarde tokeni, 435 sesiuni, 64.211 cereri API, dedup pe
message.id + requestId. Deficit +32% (~570 prompturi/saptamana).

Concluzia nu e cea asteptata: bugetele plafonate in dolari (OpenCode Zen $20,
OpenCode Go) se evapora la acest volum -- Go a tinut 1-2 zile in practica.
Planurile Z.AI se contorizeaza in prompturi, nu in dolari, si rezista la un
workload cu 7,1 cereri API per prompt. Z.AI Coding Lite ($18/luna) acopera 70%
din deficit; Pro ($72) il acopera integral.

Verificat empiric ca subagentii nu pot rula pe GLM (in proces, moștenesc auth),
dar sesiunile separate pot: endpoint fals local a primit cererea cu tokenul
alternativ, fara sa atinga OAuth-ul Max. Masurat: ~186MB RSS si 20-60s pornire
per sesiune -- de aici regulile de orchestrare (sarcini mari, max 4 procese,
fan-out in interiorul unei sesiuni GLM).

Ramane neverificat multiplicatorul de credite pentru GLM-5.3-Flash; la 3x in
loc de 1x, Lite pica si decizia se muta pe Pro.

- docs/supliment-glm-zai.md: rezumat de implementare (indexat in CLAUDE.md)
- claude_usage_report.md + CSV-uri: analiza completa si datele brute
- tools/claude_usage_*.py: pipeline de masurare read-only, re-rulabil remote
- tools/claude-glm.sh: wrapper de sesiune GLM (task/resume/shell)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-09-01 11:32:56 +00:00

120 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""Agregă rezultatele claude_usage_extract.py și generează raportul + CSV-urile."""
import json, csv, sys, os
from collections import defaultdict
OUT = sys.argv[1] if len(sys.argv) > 1 else "."
INPUTS = sys.argv[2:]
# Preturi USD / 1M tokeni. Verificate 2026-09-01.
PRICES = {
"GLM-5.3": {"in": 1.40, "cache_read": 0.26, "cache_write": None, "out": 4.40},
"GLM-5.3-Flash (promo)":{"in": 0.075, "cache_read": 0.015, "cache_write": None, "out": 0.25},
"GLM-5.3-Flash (list)": {"in": 0.15, "cache_read": 0.03, "cache_write": None, "out": 0.50},
# referinta: aceleasi modele Claude, dar platite la bucata pe Zen
"Zen Claude Opus 5": {"in": 5.00, "cache_read": 0.50, "cache_write": 6.25, "out": 25.00},
"Zen Claude Sonnet 5": {"in": 2.00, "cache_read": 0.20, "cache_write": 2.50, "out": 10.00},
"Zen Claude Haiku 4.5": {"in": 1.00, "cache_read": 0.10, "cache_write": 1.25, "out": 5.00},
"Zen Claude Fable 5": {"in": 10.00, "cache_read": 1.00, "cache_write": 12.50,"out": 50.00},
}
# mapare model observat -> tarif Zen pentru acelasi model (referinta "cat ar costa la bucata")
ZEN_SELF = {"claude-opus-5":"Zen Claude Opus 5", "claude-sonnet-5":"Zen Claude Sonnet 5",
"claude-haiku-4-5-20251001":"Zen Claude Haiku 4.5", "claude-fable-5":"Zen Claude Fable 5"}
rows, sessions, meta = [], {}, []
for p in INPUTS:
d = json.load(open(p))
meta.append({k: v for k, v in d.items() if k not in ("rows", "sessions")})
m = d["machine"]
for r in d["rows"]:
rows.append({**r, "machine": m})
for sid, s in d["sessions"].items():
sessions[(m, sid)] = s
# --- deduplicare cross-masina pe session id ---
by_sid = defaultdict(set)
for (m, sid) in sessions: by_sid[sid].add(m)
cross_dupes = {sid for sid, ms in by_sid.items() if len(ms) > 1}
def cost(tok, price, no_cache=False):
"""tok: dict cu input/output/cache_creation/cache_read. Returneaza breakdown USD."""
p = PRICES[price]
cw = p["cache_write"] if p["cache_write"] is not None else p["in"]
if no_cache:
# Scenariul B: fara avantajul cache-ului -> tot contextul reincarcat ca input normal
return {"input": (tok["input"] + tok["cache_read"] + tok["cache_creation"]) / 1e6 * p["in"],
"cache_read": 0.0, "cache_write": 0.0,
"output": tok["output"] / 1e6 * p["out"]}
return {"input": tok["input"] / 1e6 * p["in"],
"cache_read": tok["cache_read"] / 1e6 * p["cache_read"],
"cache_write": tok["cache_creation"] / 1e6 * cw,
"output": tok["output"] / 1e6 * p["out"]}
def zero(): return {"input": 0, "output": 0, "cache_creation": 0, "cache_read": 0, "turns": 0, "thinking": 0}
def add(a, b):
for k in a: a[k] += b.get(k, 0)
return a
TOT = zero()
by_model, by_machine, by_month, by_day, by_session = (defaultdict(zero) for _ in range(5))
sess_machine, sess_models = {}, defaultdict(set)
for r in rows:
add(TOT, r)
add(by_model[r["model"]], r); add(by_machine[r["machine"]], r)
add(by_month[r["date"][:7]], r); add(by_day[r["date"]], r)
add(by_session[r["session"]], r)
sess_machine[r["session"]] = r["machine"]; sess_models[r["session"]].add(r["model"])
def tot(t): return t["input"] + t["output"] + t["cache_creation"] + t["cache_read"]
days = sorted(by_day)
import datetime
d0 = datetime.date.fromisoformat(days[0]); d1 = datetime.date.fromisoformat(days[-1])
span = (d1 - d0).days + 1
missing = [ (d0 + datetime.timedelta(i)).isoformat() for i in range(span)
if (d0 + datetime.timedelta(i)).isoformat() not in by_day ]
# ---------------- CSV sesiuni ----------------
with open(os.path.join(OUT, "claude_usage_sessions.csv"), "w", newline="") as f:
w = csv.writer(f)
w.writerow(["session_id","machine","project","models","first_ts","last_ts","user_turns",
"assistant_turns","input_tokens","output_tokens","cache_creation_tokens",
"cache_read_tokens","total_tokens","cost_glm53_usd","cost_glm53flash_promo_usd"])
for sid, t in sorted(by_session.items(), key=lambda kv: -tot(kv[1])):
m = sess_machine[sid]; s = sessions.get((m, sid), {})
w.writerow([sid, m, s.get("project",""), "|".join(sorted(sess_models[sid])),
s.get("first_ts",""), s.get("last_ts",""), s.get("user_msgs",""), t["turns"],
t["input"], t["output"], t["cache_creation"], t["cache_read"], tot(t),
round(sum(cost(t,"GLM-5.3").values()),6),
round(sum(cost(t,"GLM-5.3-Flash (promo)").values()),6)])
# ---------------- CSV summary ----------------
with open(os.path.join(OUT, "claude_usage_summary.csv"), "w", newline="") as f:
w = csv.writer(f)
w.writerow(["dimension","key","sessions","turns","input_tokens","output_tokens",
"cache_creation_tokens","cache_read_tokens","total_tokens",
"cost_glm53_usd","cost_glm53flash_promo_usd","cost_glm53flash_list_usd"])
def emit(dim, key, t, nsess=""):
w.writerow([dim, key, nsess, t["turns"], t["input"], t["output"], t["cache_creation"],
t["cache_read"], tot(t),
round(sum(cost(t,"GLM-5.3").values()),4),
round(sum(cost(t,"GLM-5.3-Flash (promo)").values()),4),
round(sum(cost(t,"GLM-5.3-Flash (list)").values()),4)])
emit("TOTAL","ALL",TOT,len(by_session))
for k,v in sorted(by_machine.items()): emit("machine",k,v,sum(1 for s in sess_machine.values() if s==k))
for k,v in sorted(by_model.items()): emit("model",k,v)
for k,v in sorted(by_month.items()): emit("month",k,v)
for k,v in sorted(by_day.items()): emit("day",k,v)
zen_self = 0.0; zen_self_missing = []
for _m,_t in by_model.items():
if _m in ZEN_SELF: zen_self += sum(cost(_t, ZEN_SELF[_m]).values())
else: zen_self_missing.append(_m)
json.dump({"zen_self":zen_self,"zen_self_missing":zen_self_missing,"TOT":TOT,"by_model":dict(by_model),"by_machine":dict(by_machine),
"by_month":dict(by_month),"by_day":dict(by_day),"meta":meta,
"days":days,"span":span,"missing":missing,"cross_dupes":sorted(cross_dupes),
"n_sessions":len(by_session),
"sess_per_machine":{k:sum(1 for s in sess_machine.values() if s==k) for k in by_machine}},
open(os.path.join(OUT,"_agg.json"),"w"), indent=1)
print("OK", len(by_session), "sesiuni,", len(days), "zile")