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
This commit is contained in:
36
tools/claude-glm.sh
Executable file
36
tools/claude-glm.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sesiune Claude Code separata pe GLM (Z.AI). NU consuma cota Claude Max.
|
||||
#
|
||||
# tools/claude-glm.sh task "implementeaza T3 din TODO.md" # one-shot, JSON pe stdout
|
||||
# tools/claude-glm.sh resume <session_id> "continua" # predare / continuare
|
||||
# tools/claude-glm.sh shell # sesiune interactiva
|
||||
# tools/claude-glm.sh -- <argumente claude brute>
|
||||
#
|
||||
# Env optional: GLM_MODEL (default glm-5.3-flash[1m]), GLM_CONTEXT (default 250000)
|
||||
set -euo pipefail
|
||||
|
||||
KEY_FILE="${ZAI_KEY_FILE:-$HOME/.config/zai/key}"
|
||||
[[ -r "$KEY_FILE" ]] || { echo "Lipseste cheia Z.AI in $KEY_FILE (chmod 600)" >&2; exit 1; }
|
||||
|
||||
export ANTHROPIC_BASE_URL="https://api.z.ai/api/anthropic"
|
||||
export ANTHROPIC_AUTH_TOKEN="$(tr -d '\r\n' < "$KEY_FILE")"
|
||||
# [1m] = fereastra reala de 1M; fara el Claude Code presupune 200k si compacteaza devreme
|
||||
MODEL="${GLM_MODEL:-glm-5.3-flash[1m]}"
|
||||
export ANTHROPIC_DEFAULT_SONNET_MODEL="$MODEL"
|
||||
export ANTHROPIC_DEFAULT_OPUS_MODEL="$MODEL"
|
||||
export ANTHROPIC_DEFAULT_HAIKU_MODEL="glm-5.3-flash[1m]"
|
||||
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
||||
export API_TIMEOUT_MS=3000000
|
||||
# config separat: transcripturile GLM nu se amesteca cu cele Anthropic in statistica de consum
|
||||
export CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR_GLM:-$HOME/.claude-glm}"
|
||||
|
||||
CTX="${GLM_CONTEXT:-250000}"
|
||||
COMMON=(--model sonnet --autocompact "$CTX" --output-format json)
|
||||
|
||||
case "${1:-shell}" in
|
||||
task) shift; exec claude -p "$*" "${COMMON[@]}" < /dev/null ;;
|
||||
resume) sid="$2"; shift 2; exec claude -p "$*" --resume "$sid" "${COMMON[@]}" < /dev/null ;;
|
||||
shell) shift || true; exec claude --model sonnet --autocompact "$CTX" "$@" ;;
|
||||
--) shift; exec claude "$@" ;;
|
||||
*) exec claude "$@" ;;
|
||||
esac
|
||||
112
tools/claude_usage_extract.py
Normal file
112
tools/claude_usage_extract.py
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Extrage token usage real din transcripturile Claude Code (~/.claude/projects/**/*.jsonl).
|
||||
|
||||
READ-ONLY: nu scrie, nu mută, nu șterge nimic. Emite JSON agregat pe stdout.
|
||||
Poate fi rulat remote fără a atinge discul: ssh host 'python3 -' < acest_fisier
|
||||
|
||||
Deduplicare: cheia (message.id, requestId) — aceeași cerere API poate apărea în
|
||||
mai multe fișiere (resume, fork de sesiune, backup-uri).
|
||||
"""
|
||||
import json, os, sys, glob, argparse, hashlib
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--root", default=os.path.expanduser("~/.claude/projects"))
|
||||
ap.add_argument("--machine", default=os.uname().nodename)
|
||||
a = ap.parse_args()
|
||||
|
||||
rows = {} # (session, date, model) -> tokens
|
||||
sessions = {} # session -> meta
|
||||
seen = set() # chei de deduplicare
|
||||
dup = 0
|
||||
total_assistant = 0
|
||||
files = sorted(glob.glob(os.path.join(a.root, "**", "*.jsonl"), recursive=True))
|
||||
bad_lines = 0
|
||||
|
||||
for fp in files:
|
||||
project = os.path.basename(os.path.dirname(fp))
|
||||
try:
|
||||
fh = open(fp, "r", encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
with fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if not line or not line.startswith("{"):
|
||||
continue
|
||||
try:
|
||||
d = json.loads(line)
|
||||
except Exception:
|
||||
bad_lines += 1
|
||||
continue
|
||||
t = d.get("type")
|
||||
sid = d.get("sessionId")
|
||||
if not sid:
|
||||
continue
|
||||
ts = d.get("timestamp") or ""
|
||||
s = sessions.setdefault(sid, {
|
||||
"machine": a.machine, "project": project, "first_ts": ts, "last_ts": ts,
|
||||
"user_msgs": 0, "assistant_msgs": 0, "models": set(),
|
||||
"version": d.get("version"), "entrypoint": d.get("entrypoint"),
|
||||
"cwd": d.get("cwd"), "files": set(),
|
||||
})
|
||||
s["files"].add(os.path.basename(fp))
|
||||
if ts:
|
||||
if not s["first_ts"] or ts < s["first_ts"]: s["first_ts"] = ts
|
||||
if ts > s["last_ts"]: s["last_ts"] = ts
|
||||
if t == "user" and not d.get("isSidechain"):
|
||||
s["user_msgs"] += 1
|
||||
if t != "assistant":
|
||||
continue
|
||||
msg = d.get("message") or {}
|
||||
u = msg.get("usage")
|
||||
if not isinstance(u, dict):
|
||||
continue
|
||||
total_assistant += 1
|
||||
# cheie de deduplicare
|
||||
mid = msg.get("id"); rid = d.get("requestId")
|
||||
if mid or rid:
|
||||
key = f"{mid}|{rid}"
|
||||
else:
|
||||
key = "h:" + hashlib.sha1((sid + ts + str(u.get("output_tokens"))).encode()).hexdigest()
|
||||
if key in seen:
|
||||
dup += 1
|
||||
continue
|
||||
seen.add(key)
|
||||
|
||||
model = msg.get("model") or "unknown"
|
||||
if model.startswith("<") or model == "unknown":
|
||||
continue # synthetic / mesaje locale fără cost
|
||||
s["assistant_msgs"] += 1
|
||||
s["models"].add(model)
|
||||
date = ts[:10] if ts else "unknown"
|
||||
k = (sid, date, model)
|
||||
r = rows.setdefault(k, {"input": 0, "output": 0, "cache_creation": 0,
|
||||
"cache_read": 0, "thinking": 0, "turns": 0,
|
||||
"web_search": 0, "web_fetch": 0})
|
||||
r["input"] += u.get("input_tokens") or 0
|
||||
r["output"] += u.get("output_tokens") or 0
|
||||
r["cache_creation"] += u.get("cache_creation_input_tokens") or 0
|
||||
r["cache_read"] += u.get("cache_read_input_tokens") or 0
|
||||
otd = u.get("output_tokens_details") or {}
|
||||
r["thinking"] += otd.get("thinking_tokens") or 0
|
||||
stu = u.get("server_tool_use") or {}
|
||||
r["web_search"] += stu.get("web_search_requests") or 0
|
||||
r["web_fetch"] += stu.get("web_fetch_requests") or 0
|
||||
r["turns"] += 1
|
||||
|
||||
out = {
|
||||
"machine": a.machine,
|
||||
"root": a.root,
|
||||
"files_scanned": len(files),
|
||||
"assistant_records_with_usage": total_assistant,
|
||||
"duplicates_removed": dup,
|
||||
"malformed_lines": bad_lines,
|
||||
"rows": [{"session": k[0], "date": k[1], "model": k[2], **v} for k, v in rows.items()],
|
||||
"sessions": {sid: {**{kk: vv for kk, vv in m.items() if kk not in ("models", "files")},
|
||||
"models": sorted(m["models"]), "files": len(m["files"])}
|
||||
for sid, m in sessions.items()},
|
||||
}
|
||||
json.dump(out, sys.stdout)
|
||||
|
||||
main()
|
||||
492
tools/claude_usage_md.py
Normal file
492
tools/claude_usage_md.py
Normal file
@@ -0,0 +1,492 @@
|
||||
#!/usr/bin/env python3
|
||||
import json, sys
|
||||
SP, OUT = sys.argv[1], sys.argv[2]
|
||||
d = json.load(open(SP + "/_agg.json")); T = d["TOT"]
|
||||
def tt(x): return x["input"]+x["output"]+x["cache_creation"]+x["cache_read"]
|
||||
P = {"GLM-5.3":{"in":1.40,"cr":0.26,"out":4.40},
|
||||
"GLM-5.3-Flash (promo)":{"in":0.075,"cr":0.015,"out":0.25},
|
||||
"GLM-5.3-Flash (listă)":{"in":0.15,"cr":0.03,"out":0.50},
|
||||
"Zen Claude Opus 5":{"in":5.0,"cr":0.5,"cw":6.25,"out":25.0},
|
||||
"Zen Claude Sonnet 5":{"in":2.0,"cr":0.2,"cw":2.50,"out":10.0}}
|
||||
def cA(t,n):
|
||||
p=P[n]; cw=p.get("cw",p["in"])
|
||||
return {"input":t["input"]/1e6*p["in"],"cache_read":t["cache_read"]/1e6*p["cr"],
|
||||
"cache_write":t["cache_creation"]/1e6*cw,"output":t["output"]/1e6*p["out"]}
|
||||
def cB(t,n):
|
||||
p=P[n]; return (t["input"]+t["cache_read"]+t["cache_creation"])/1e6*p["in"]+t["output"]/1e6*p["out"]
|
||||
def S(t,n): return sum(cA(t,n).values())
|
||||
N=lambda v: f"{v:,}"
|
||||
days=d["days"]; nd=d["span"]; ns=d["n_sessions"]
|
||||
g53,gfp,gfl = S(T,"GLM-5.3"),S(T,"GLM-5.3-Flash (promo)"),S(T,"GLM-5.3-Flash (listă)")
|
||||
zs = d["zen_self"]
|
||||
COV={"claude-agent":("2026-08-17","2026-09-01",5,"`~/.claude/projects` (Linux, LXC 171)"),
|
||||
"moltbot":("2026-08-02","2026-09-01",31,"`/home/moltbot/.claude/projects` (Linux, LXC 110)"),
|
||||
"LENOVO-AIO-BIRO":("2026-07-29","2026-09-01",29,"`C:\\Users\\<user>\\.claude\\projects` (Windows)")}
|
||||
L=[];A=L.append
|
||||
A("# Analiză consum Claude Code — Claude Max 5x vs. OpenCode Zen (GLM-5.3)\n")
|
||||
A(f"**Generat:** 2026-09-01 · **Perioadă:** {days[0]} → {days[-1]} ({nd} zile) · "
|
||||
f"**3 mașini · {N(ns)} sesiuni · {tt(T)/1e9:.2f}ial miliarde tokeni**\n".replace("ial",""))
|
||||
A("## 0. Ce a fost scanat\n")
|
||||
A("| Mașină | Locație | Fișiere | Înregistrări | Duplicate | Sesiuni | Prima zi | Ultima zi | Zile active |")
|
||||
A("|---|---|---:|---:|---:|---:|---|---|---:|")
|
||||
for m in d["meta"]:
|
||||
mm=m["machine"]; c=COV[mm]
|
||||
A(f"| `{mm}` | {c[3]} | {N(m['files_scanned'])} | {N(m['assistant_records_with_usage'])} | "
|
||||
f"{N(m['duplicates_removed'])} | {d['sess_per_machine'][mm]} | {c[0]} | {c[1]} | {c[2]} |")
|
||||
A("")
|
||||
A("Windows a fost extras de o sesiune Claude Code rulată manual pe acea mașină, cu **exact același "
|
||||
"script și aceeași cheie de deduplicare** — altfel cifrele nu s-ar putea aduna. Datele au trecut "
|
||||
"printr-un pas de tabel (separator TAB, date în format M/D/YYYY); au fost renormalizate la ISO, "
|
||||
"iar toate cele 218 UUID-uri de sesiune au fost validate. Ceasul din zi s-a pierdut la conversie, "
|
||||
"dar granularitatea folosită în raport e ziua.\n")
|
||||
A("### Corecție de premisă\n")
|
||||
A("Sesiunea care a condus analiza **nu rulează pe Windows** — rulează pe LXC 171 `claude-agent` "
|
||||
"(`Linux claude-agent 6.8.12-15-pve`). Windows a fost acoperit separat, prin sesiunea deschisă acolo.\n")
|
||||
A("### Surse de token usage și care e autoritativă\n")
|
||||
A("| Sursă | Conține | Verdict |")
|
||||
A("|---|---|---|")
|
||||
A("| `~/.claude/projects/**/*.jsonl`, câmpul `message.usage` din înregistrările `assistant` | `input_tokens`, `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `thinking_tokens`, `model`, `requestId` | **AUTORITATIVĂ** — valori raportate de API |")
|
||||
A("| `~/.claude/stats-cache.json` | mesaje/sesiuni/tool-calls pe zi, din 2025-12-31 | secundară — **zero tokeni**; dovadă că istoricul e mai lung decât transcripturile |")
|
||||
A("| `~/.claude/history.jsonl` | 6.400 de prompturi, din 2025-12-31 | secundară — fără tokeni |")
|
||||
A("\nLungimea transcriptului nu a fost folosită ca proxy pentru tokeni nicăieri.\n")
|
||||
A("## 1. Perioada\n")
|
||||
A(f"- **Început:** {days[0]}\n- **Sfârșit:** {days[-1]}\n- **Zile calendaristice:** {nd}\n"
|
||||
f"- **Zile cu activitate:** {len(days)}\n- **Zile lipsă:** {', '.join(d['missing']) or 'niciuna'}\n")
|
||||
A("Acoperirea e inegală: Windows și moltbot acoperă luna întreagă, dar **`claude-agent` are doar "
|
||||
"5 zile** cu transcripturi păstrate (retenția implicită de 30 de zile plus o recreare a "
|
||||
"directorului pe 2026-08-30). `stats-cache.json` arată activitate continuă din **2025-12-31**. "
|
||||
"Consumul istoric real e mai mare decât cel de aici; **nu am extrapolat nimic** — toate cifrele "
|
||||
"sunt tokeni observați efectiv.\n")
|
||||
A("## 2. Deduplicare\n")
|
||||
tr=sum(m["assistant_records_with_usage"] for m in d["meta"])
|
||||
td=sum(m["duplicates_removed"] for m in d["meta"])
|
||||
A("Cheie: `message.id` + `requestId`. Aceeași cerere API reapare în mai multe fișiere după "
|
||||
"`--resume`, fork de sesiune sau compactare.\n")
|
||||
A(f"- Înregistrări `assistant` cu `usage` întâlnite: **{N(tr)}**")
|
||||
A(f"- Duplicate eliminate: **{N(td)}** ({td/tr*100:.1f}%)")
|
||||
A(f"- Cereri API unice numărate: **{N(tr-td)}**")
|
||||
A(f"- Sesiuni prezente pe mai multe mașini: **{len(d['cross_dupes'])}** (verificat pe toate cele trei perechi)")
|
||||
A(f"- Linii malformate: **{sum(m['malformed_lines'] for m in d['meta'])}**\n")
|
||||
A("## 3. Total\n")
|
||||
A("| Metrică | Valoare |")
|
||||
A("|---|---:|")
|
||||
A(f"| Sesiuni | {N(ns)} |")
|
||||
A(f"| Turn-uri asistent (cereri API) | {N(T['turns'])} |")
|
||||
A(f"| Input tokens (necache) | {N(T['input'])} |")
|
||||
A(f"| Output tokens | {N(T['output'])} |")
|
||||
A(f"| Cache creation (write) | {N(T['cache_creation'])} |")
|
||||
A(f"| Cache read | {N(T['cache_read'])} |")
|
||||
A(f"| **TOTAL** | **{N(tt(T))}** |\n")
|
||||
A(f"Cache read = **{T['cache_read']/tt(T)*100:.1f}%** din volum. Output-ul real e doar "
|
||||
f"**{T['output']/tt(T)*100:.2f}%**. Asta e semnătura unui workload agentic: contextul se "
|
||||
"retrimite la fiecare turn, iar tarifarea cache-ului decide costul.\n")
|
||||
A(f"Medii: **{N(round(tt(T)/ns))}** tokeni/sesiune ({T['turns']/ns:.0f} turn-uri) · "
|
||||
f"**{N(round(tt(T)/nd))}** tokeni/zi · **{ns/nd:.1f}** sesiuni/zi.\n")
|
||||
def tbl(title,dic,keyname,sort=None):
|
||||
A(f"### {title}\n")
|
||||
A(f"| {keyname} | Sesiuni | Turn-uri | Input | Output | Cache write | Cache read | Total | % |")
|
||||
A("|---|---:|---:|---:|---:|---:|---:|---:|---:|")
|
||||
for k,v in (sorted(dic.items(),key=sort) if sort else sorted(dic.items(),key=lambda kv:-tt(kv[1]))):
|
||||
se=d["sess_per_machine"].get(k,"") if keyname=="Mașină" else ""
|
||||
A(f"| {k} | {se} | {N(v['turns'])} | {N(v['input'])} | {N(v['output'])} | {N(v['cache_creation'])} | "
|
||||
f"{N(v['cache_read'])} | {N(tt(v))} | {tt(v)/tt(T)*100:.1f}% |")
|
||||
A("")
|
||||
tbl("Breakdown pe calculator", d["by_machine"], "Mașină")
|
||||
tbl("Breakdown pe model", d["by_model"], "Model")
|
||||
tbl("Breakdown pe lună", d["by_month"], "Lună", sort=lambda kv:kv[0])
|
||||
tbl("Breakdown pe zi", d["by_day"], "Zi", sort=lambda kv:kv[0])
|
||||
A("## 4. Prețuri — verificate 2026-09-01\n")
|
||||
A("Pagina oficială **OpenCode Zen (`opencode.ai/docs/zen`) nu listează încă GLM-5.3 sau "
|
||||
"GLM-5.3-Flash.** Cele mai noi GLM de acolo:\n")
|
||||
A("| Model pe Zen | Input | Output | Cache read | Cache write |")
|
||||
A("|---|---:|---:|---:|---:|")
|
||||
for r in [("GLM 5.2","$1.40","$4.40","$0.26","—"),("GLM 5.1","$1.40","$4.40","$0.26","—"),
|
||||
("GLM 5","$1.00","$3.20","$0.20","—"),("Claude Opus 5","$5.00","$25.00","$0.50","$6.25"),
|
||||
("Claude Sonnet 5","$2.00","$10.00","$0.20","$2.50"),
|
||||
("Claude Fable 5","$10.00","$50.00","$1.00","$12.50"),
|
||||
("Claude Haiku 4.5","$1.00","$5.00","$0.10","$1.25")]:
|
||||
A("| "+" | ".join(r)+" |")
|
||||
A("")
|
||||
A("Pentru GLM-5.3 am folosit **prețurile oficiale Z.AI** (furnizorul modelului). Zen revinde de "
|
||||
"regulă la același tarif — GLM 5.2 pe Zen e identic cu tariful Z.AI, ceea ce validează proxy-ul.\n")
|
||||
A("| Model | Input | Cache read | Cache write | Output |")
|
||||
A("|---|---:|---:|---:|---:|")
|
||||
A("| **GLM-5.3** | $1.40 | $0.26 | *fără tarif separat* | $4.40 |")
|
||||
A("| **GLM-5.3-Flash** (promo, până 2026-09-09 24:00 UTC+8) | $0.075 | $0.015 | *fără tarif separat* | $0.25 |")
|
||||
A("| **GLM-5.3-Flash** (preț de listă) | $0.15 | $0.03 | *fără tarif separat* | $0.50 |\n")
|
||||
A("Surse: [opencode.ai/docs/zen](https://opencode.ai/docs/zen/) · "
|
||||
"[docs.z.ai/guides/overview/pricing](https://docs.z.ai/guides/overview/pricing) · "
|
||||
"[openrouter.ai/z-ai/glm-5.3-flash](https://openrouter.ai/z-ai/glm-5.3-flash)\n")
|
||||
A("> **Cache write:** niciun model GLM nu are tarif separat de cache creation (spre deosebire de "
|
||||
"modelele Claude de pe Zen). `cache_creation_input_tokens` se facturează deci la tariful de "
|
||||
"**input normal**. `cache_read_input_tokens` **nu** a fost tratat ca input normal — are tariful "
|
||||
"lui redus. Vezi Scenariul B pentru varianta fără cache.\n")
|
||||
A("## 5. Cost echivalent Zen — Scenariul A (tokeni reali, tarife de cache)\n")
|
||||
A("| Model | Input | Cache read | Cache write | Output | **TOTAL** |")
|
||||
A("|---|---:|---:|---:|---:|---:|")
|
||||
for n in ["GLM-5.3","GLM-5.3-Flash (promo)","GLM-5.3-Flash (listă)"]:
|
||||
c=cA(T,n)
|
||||
A(f"| {n} | ${c['input']:.2f} | ${c['cache_read']:.2f} | ${c['cache_write']:.2f} | ${c['output']:.2f} | **${sum(c.values()):.2f}** |")
|
||||
A("")
|
||||
A("### Medii\n")
|
||||
A("| Model | / sesiune | / zi | / lună (30 zile) |")
|
||||
A("|---|---:|---:|---:|")
|
||||
for n in ["GLM-5.3","GLM-5.3-Flash (promo)","GLM-5.3-Flash (listă)"]:
|
||||
s=S(T,n); A(f"| {n} | ${s/ns:.3f} | ${s/nd:.2f} | ${s/nd*30:.2f} |")
|
||||
A("")
|
||||
A("## 6. Scenariul B — fără avantajul cache-ului\n")
|
||||
A("Tot `cache_read` + `cache_creation` facturați ca input normal.\n")
|
||||
A("| Model | A (cu cache) | B (fără cache) | Factor |")
|
||||
A("|---|---:|---:|---:|")
|
||||
for n in ["GLM-5.3","GLM-5.3-Flash (promo)","GLM-5.3-Flash (listă)"]:
|
||||
a,b=S(T,n),cB(T,n); A(f"| {n} | ${a:.2f} | ${b:.2f} | **{b/a:.1f}×** |")
|
||||
A("")
|
||||
A("## 7. Comparație cu Claude Max 5x\n")
|
||||
A("Nu există conversie oficială Anthropic din quota Max în tokeni, deci nu o inventez. Compar ce "
|
||||
"ai plătit cu ce ar fi costat exact aceiași tokeni la tarif per-token.\n")
|
||||
A("| | Claude Max 5x | Zen GLM-5.3 | Zen GLM-5.3-Flash (promo) | Zen GLM-5.3-Flash (listă) |")
|
||||
A("|---|---|---|---|---|")
|
||||
A(f"| Cost pentru perioada măsurată | **$100/lună** | ${g53:,.2f} | ${gfp:,.2f} | ${gfl:,.2f} |")
|
||||
A(f"| Tokeni | {N(tt(T))} | identic | identic | identic |")
|
||||
A(f"| Sesiuni | {N(ns)} | identic | identic | identic |")
|
||||
A("| Modele | Opus 5, Sonnet 5, Fable 5, Haiku 4.5 | GLM-5.3 | GLM-5.3-Flash | GLM-5.3-Flash |")
|
||||
A("")
|
||||
A("### Cât valorează de fapt abonamentul Max\n")
|
||||
A(f"Aceiași {N(tt(T))} tokeni, **fiecare model la propriul lui tarif pe Zen** "
|
||||
"(Opus 5 la preț de Opus 5, Sonnet 5 la preț de Sonnet 5 etc.):\n")
|
||||
A(f"# **${zs:,.2f}**\n")
|
||||
A(f"Abonamentul Max 5x de $100 a livrat consum care la tarif per-token ar fi costat **${zs:,.0f}** — "
|
||||
f"adică Max e de **{zs/100:.0f}× mai ieftin** decât plata la token pentru exact aceleași modele. "
|
||||
"Asta e cifra cea mai importantă din tot raportul.\n")
|
||||
A("### Acoperirea celor $20 Zen\n")
|
||||
A("| Model | Cost pentru consumul observat | % din $20 | De câte ori acoperă $20 consumul |")
|
||||
A("|---|---:|---:|---:|")
|
||||
for n in ["GLM-5.3","GLM-5.3-Flash (promo)","GLM-5.3-Flash (listă)"]:
|
||||
s=S(T,n); A(f"| {n} | ${s:,.2f} | {s/20*100:,.0f}% | **{20/s:.3f}×** |")
|
||||
A("")
|
||||
A("Scenariul B, fără cache:\n")
|
||||
A("| Model | Cost | % din $20 | Acoperire |")
|
||||
A("|---|---:|---:|---:|")
|
||||
for n in ["GLM-5.3","GLM-5.3-Flash (promo)","GLM-5.3-Flash (listă)"]:
|
||||
b=cB(T,n); A(f"| {n} | ${b:,.2f} | {b/20*100:,.0f}% | {20/b:.3f}× |")
|
||||
A("")
|
||||
A("### Cât consum încap în $20\n")
|
||||
A("| Model | Tokeni pentru $20 | % din consumul lunar | Zile la ritmul actual | Sesiuni |")
|
||||
A("|---|---:|---:|---:|---:|")
|
||||
for n in ["GLM-5.3","GLM-5.3-Flash (promo)","GLM-5.3-Flash (listă)"]:
|
||||
s=S(T,n); f=20/s
|
||||
A(f"| {n} | {N(round(tt(T)*f))} | {f*100:.1f}% | {nd*f:.2f} | {ns*f:.1f} |")
|
||||
A("")
|
||||
|
||||
# ---- Sectiunea abonamentelor Z.AI Coding Plan ----
|
||||
PROMPTS_LINUX = 245 + 309 # numarate exact (claude-agent + moltbot)
|
||||
REQ_LINUX = 2143 + 1810
|
||||
RATIO = REQ_LINUX / PROMPTS_LINUX
|
||||
REQ_WIN = tt_turns_win = d["by_machine"]["LENOVO-AIO-BIRO"]["turns"]
|
||||
PROMPTS_WIN = REQ_WIN / RATIO
|
||||
PROMPTS_TOT = PROMPTS_LINUX + PROMPTS_WIN
|
||||
WEEKS = nd / 7
|
||||
PPW = PROMPTS_TOT / WEEKS
|
||||
PLANS = [("Lite", 18.00, 151.20, 400, 10000),
|
||||
("Pro", 72.00, 604.80, 2000, 60000),
|
||||
("Max", 160.00, 1344.00, 8000, 140000)]
|
||||
son=tt(d["by_model"]["claude-sonnet-5"])/tt(T)*100
|
||||
op=tt(d["by_model"]["claude-opus-5"])/tt(T)*100
|
||||
A("## 8. Abonamentele Z.AI GLM Coding Plan\n")
|
||||
A("Planurile Z.AI nu se facturează pe tokeni, ci pe **prompturi** (turnuri umane) contorizate în "
|
||||
"credite, cu două limite simultane: una la 5 ore și una săptămânală. Documentația Z.AI notează "
|
||||
"că *„un prompt poate invoca modelul de 15–20 de ori\"* — deci comparația corectă nu se face pe "
|
||||
"cererile API, ci pe prompturile tale reale.\n")
|
||||
A("### Prețuri (verificate 2026-09-01)\n")
|
||||
A("| Plan | Lunar | Anual | Efectiv/lună anual | Economie | Prompturi / 5h | Prompturi / săpt. | Credite / săpt. |")
|
||||
A("|---|---:|---:|---:|---:|---:|---:|---:|")
|
||||
A("| Lite | $18.00 | **$151.20** | $12.60 | 30% | ~80 | ~400 | 10.000 |")
|
||||
A("| Pro | $72.00 | **$604.80** | $50.40 | 30% | ~400 | ~2.000 | 60.000 |")
|
||||
A("| Max | $160.00 | **$1.344,00** | $112.00 | 30% | ~1.600 | ~8.000 | 140.000 |")
|
||||
A("")
|
||||
A("Toate planurile includ **GLM-5.3 și GLM-5.3-Flash**. Surse: "
|
||||
"[docs.z.ai/devpack/overview](https://docs.z.ai/devpack/overview) · "
|
||||
"[aipricing.guru](https://www.aipricing.guru/z-ai-subscription-pricing/)\n")
|
||||
A("### Câte prompturi dai de fapt\n")
|
||||
A(f"Am numărat prompturile umane reale (excluzând `tool_result`, care în transcript apar tot ca "
|
||||
f"mesaje de tip `user`):\n")
|
||||
A("| Mașină | Prompturi umane | Cereri API | Cereri / prompt |")
|
||||
A("|---|---:|---:|---:|")
|
||||
A(f"| `claude-agent` | 245 | 2.143 | 8,75 |")
|
||||
A(f"| `moltbot` | 309 | 1.810 | 5,86 |")
|
||||
A(f"| `LENOVO-AIO-BIRO` | **~{PROMPTS_WIN:,.0f}** *(estimat)* | {REQ_WIN:,} | {RATIO:.2f} *(aplicat)* |")
|
||||
A(f"| **TOTAL** | **~{PROMPTS_TOT:,.0f}** | {T['turns']:,} | {RATIO:.2f} |")
|
||||
A("")
|
||||
A(f"> **Estimare marcată:** scriptul rulat pe Windows a raportat doar cererile API, nu și "
|
||||
f"prompturile umane. Cifra pentru Windows e derivată din raportul măsurat pe cele două mașini "
|
||||
f"Linux ({RATIO:.2f} cereri API per prompt). E singura extrapolare din tot raportul și e "
|
||||
f"marcată ca atare. Raportul real pe Windows poate diferi — sesiunile de acolo par mai lungi, "
|
||||
f"deci numărul de prompturi ar putea fi mai mic (și acoperirea planurilor, mai bună).\n")
|
||||
A(f"Ritm rezultat: **~{PPW:,.0f} prompturi/săptămână** (~{PROMPTS_TOT/nd:,.0f}/zi) pe {nd} zile.\n")
|
||||
A("### Acoperire — la multiplicator 1×\n")
|
||||
A("| Plan | Anual | Efectiv/lună | Prompturi/săpt. | Acoperă din nevoia ta | Verdict |")
|
||||
A("|---|---:|---:|---:|---:|---|")
|
||||
for nm,mo,yr,ppw,cr in PLANS:
|
||||
cov = ppw/PPW*100
|
||||
v = "**ACOPERĂ**" if cov>=100 else ("aproape" if cov>=70 else "insuficient")
|
||||
A(f"| {nm} | ${yr:,.2f} | ${yr/12:.2f} | ~{ppw:,} | **{cov:.0f}%** | {v} |")
|
||||
A("")
|
||||
A("### Acoperire — dacă GLM-5.3 consumă credite multiplicat\n")
|
||||
A("Z.AI aplică multiplicatori de credite modelelor mari (documentat 3× în orele de vârf și 2× "
|
||||
"off-peak pentru GLM-5.2 / GLM-5-Turbo; pentru GLM-5.3 multiplicatorul nu e publicat explicit). "
|
||||
"Dacă GLM-5.3 se contorizează la fel, acoperirea scade proporțional:\n")
|
||||
A("| Plan | 1× (Flash / off-peak promo) | 2× (off-peak) | 3× (peak) |")
|
||||
A("|---|---:|---:|---:|")
|
||||
for nm,mo,yr,ppw,cr in PLANS:
|
||||
A(f"| {nm} | {ppw/PPW*100:.0f}% | {ppw/2/PPW*100:.0f}% | {ppw/3/PPW*100:.0f}% |")
|
||||
A("")
|
||||
A("### Comparație completă a opțiunilor\n")
|
||||
A("| Opțiune | Cost efectiv/lună | Acoperă volumul tău? | Observație |")
|
||||
A("|---|---:|---|---|")
|
||||
A("| **Claude Max 5x** (actual) | $100.00 | **DA, 100%** | modelele Claude; "
|
||||
f"${zs:,.0f} valoare la tarif per-token |")
|
||||
A(f"| OpenCode Zen, GLM-5.3-Flash | ${gfp:,.2f} *(pay-per-token)* | da, dar costă {gfp/100:.1f}× cât Max | fără plafon, dar fără reducere de abonament |")
|
||||
A(f"| OpenCode Zen, GLM-5.3 | ${g53:,.2f} *(pay-per-token)* | da, dar costă {g53/100:.0f}× cât Max | prohibitiv |")
|
||||
A("| OpenCode Zen $20 (buget fix) | $20.00 | **NU — 10,5%** | vezi secțiunea 7 |")
|
||||
A(f"| **Z.AI Coding Lite anual** | **$12.60** | NU — {400/PPW*100:.0f}% la 1× | cel mai ieftin plan cu GLM-5.3 |")
|
||||
A(f"| **Z.AI Coding Pro anual** | **$50.40** | **DA la 1× ({2000/PPW*100:.0f}%)**, nu la 2–3× | cel mai bun raport dacă multiplicatorul e 1× |")
|
||||
A(f"| **Z.AI Coding Max anual** | **$112.00** | **DA, cu marjă ({8000/PPW*100:.0f}% la 1×, {8000/3/PPW*100:.0f}% la 3×)** | singurul sigur indiferent de multiplicator |")
|
||||
A("")
|
||||
A("### Ce înseamnă asta concret\n")
|
||||
A(f"**Z.AI Coding Pro anual ($604,80/an = $50,40/lună) e de departe cea mai interesantă opțiune "
|
||||
f"din tot setul** — la ritmul tău de ~{PPW:,.0f} prompturi/săptămână, cota lui de ~2.000/săptămână "
|
||||
f"acoperă {2000/PPW*100:.0f}% din volum, la **jumătate din prețul Max 5x**.\n")
|
||||
A("Dar cu trei rezerve serioase:\n")
|
||||
A(f"1. **Marja e subțire.** {2000/PPW*100:.0f}% nu e mult peste 100%. O săptămână mai aglomerată "
|
||||
"decât media te scoate din cotă, și atunci aștepți resetarea.")
|
||||
A("2. **Multiplicatorul de credite e necunoscut pentru GLM-5.3.** Dacă e 2× sau 3× ca la GLM-5.2, "
|
||||
f"Pro acoperă doar {2000/2/PPW*100:.0f}%–{2000/3/PPW*100:.0f}% și ai nevoie de Max ($112/lună).")
|
||||
A("3. **Limita de 5 ore contează separat.** Media ta zilnică încape în cota de 5h a planului Pro "
|
||||
"(~400), dar zilele de vârf pot să nu încapă. Cotele sunt simultane: săptămânală ȘI la 5 ore.\n")
|
||||
A("**Diferența de calitate rămâne argumentul decisiv.** Un plan Z.AI îți dă GLM-5.3, nu Opus 5. "
|
||||
f"La tine {op:.0f}% din volum rulează deja pe Opus 5 — muncă unde ai ales conștient modelul mare.\n")
|
||||
|
||||
|
||||
# ---- Sectiunea 9: scenariul real Opus planifica / Flash executa ----
|
||||
SON = d["by_model"]["claude-sonnet-5"]; OPU = d["by_model"]["claude-opus-5"]
|
||||
son_turn_pct = SON["turns"]/T["turns"]*100
|
||||
opu_turn_pct = OPU["turns"]/T["turns"]*100
|
||||
PPW_SON = PPW * SON["turns"]/T["turns"]
|
||||
PPW_OPU = PPW * OPU["turns"]/T["turns"]
|
||||
son_flash_promo = S(SON,"GLM-5.3-Flash (promo)")
|
||||
son_flash_list = S(SON,"GLM-5.3-Flash (listă)")
|
||||
A("## 9. Scenariul real: Opus planifică, GLM-5.3-Flash execută\n")
|
||||
A("Fluxul vizat înlocuiește **doar treapta de execuție** (azi Sonnet 5) cu GLM-5.3-Flash, "
|
||||
"păstrând Opus 5 pe Max pentru planificare. Asta nu e o felie arbitrară din volum — e exact "
|
||||
"împărțirea care se vede deja în date.\n")
|
||||
A("| Treaptă | Model azi | Tokeni/lună | % volum | Turn-uri | Prompturi/săpt. *(est.)* |")
|
||||
A("|---|---|---:|---:|---:|---:|")
|
||||
A(f"| Planificare | Opus 5 | {tt(OPU)/1e9:.2f}B | {tt(OPU)/tt(T)*100:.1f}% | {N(OPU['turns'])} | ~{PPW_OPU:,.0f} |")
|
||||
A(f"| Execuție | Sonnet 5 | {tt(SON)/1e9:.2f}B | {tt(SON)/tt(T)*100:.1f}% | {N(SON['turns'])} | ~{PPW_SON:,.0f} |")
|
||||
A(f"| Alte | Fable 5 + Haiku | {(tt(d['by_model']['claude-fable-5'])+tt(d['by_model']['claude-haiku-4-5-20251001']))/1e6:.0f}M | 1.5% | {N(d['by_model']['claude-fable-5']['turns']+d['by_model']['claude-haiku-4-5-20251001']['turns'])} | ~{PPW-PPW_SON-PPW_OPU:,.0f} |")
|
||||
A("")
|
||||
A("### Cât costă mutarea treptei de execuție\n")
|
||||
A("| Variantă | Cost/lună | Observație |")
|
||||
A("|---|---:|---|")
|
||||
A(f"| Zen pay-per-token, Flash promo | ${son_flash_promo:,.2f} | tarif promoțional, expiră 2026-09-09 |")
|
||||
A(f"| Zen pay-per-token, Flash listă | ${son_flash_list:,.2f} | tariful de după |")
|
||||
A(f"| Z.AI Coding **Lite** anual | $12.60 | cotă ~400 prompturi/săpt. = **{400/PPW_SON*100:.0f}%** din nevoie |")
|
||||
A(f"| Z.AI Coding **Pro** anual | $50.40 | cotă ~2.000/săpt. = **{2000/PPW_SON*100:.0f}%** din nevoie |")
|
||||
A(f"| Z.AI Coding **Max** anual | $112.00 | cotă ~8.000/săpt. = {8000/PPW_SON*100:.0f}% |")
|
||||
A("")
|
||||
A(f"Treapta de execuție are nevoie de **~{PPW_SON:,.0f} prompturi/săptămână**. "
|
||||
f"**Lite nu ajunge** ({400/PPW_SON*100:.0f}%). **Pro ajunge cu marjă de {2000/PPW_SON/1:.1f}×** — "
|
||||
"și, spre deosebire de comparația pe volumul total, aici marja e confortabilă.\n")
|
||||
A("> **Detaliu în favoarea ta:** multiplicatorul de credite Z.AI (3× peak / 2× off-peak) e "
|
||||
"documentat pentru modelele mari, nu pentru cele „de rutină\". GLM-5.3-**Flash** e modelul mic, "
|
||||
f"deci probabil se contorizează la 1×. Dacă e așa, Pro acoperă treapta de execuție cu "
|
||||
f"{2000/PPW_SON*100:.0f}% și rămâne loc de creștere. Confirmă asta înainte să plătești anual.\n")
|
||||
A("### Calibrare cu OpenCode Go — datele tale reale\n")
|
||||
A("OpenCode Go costă **$10/lună** și include limite exprimate în valoare de consum, nu în tokeni:\n")
|
||||
A("| Limită | Valoare |")
|
||||
A("|---|---:|")
|
||||
A("| La 5 ore | $12 de consum |")
|
||||
A("| Săptămânal | $30 de consum |")
|
||||
A("| Lunar | $60 de consum |")
|
||||
A("")
|
||||
A(f"Abonamentul s-a consumat în 1–2 zile. Asta **confirmă independent** analiza: la volumul tău, "
|
||||
f"consumul săptămânal echivalent e ${gfp/(nd/7):,.0f} pe GLM-5.3-Flash promo și "
|
||||
f"${g53/(nd/7):,.0f} pe GLM-5.3 — deci plafonul săptămânal de $30 al lui Go se evaporă în "
|
||||
f"{30/(g53/(nd/7))*7:.1f} zile pe modelul mare și în {30/(gfp/(nd/7))*7:.1f} zile pe Flash. "
|
||||
"Un plan plafonat în dolari nu rezistă la acest volum, indiferent de furnizor.\n")
|
||||
A("**De asta planurile Z.AI sunt altceva:** cota lor e în prompturi, nu în dolari de consum. "
|
||||
"Un prompt care declanșează 20 de invocări de model costă la fel ca unul care declanșează 2 — "
|
||||
"iar sesiunile tale au în medie "
|
||||
f"{RATIO:.1f} cereri API per prompt. Exact tipul de workload pe care tarifarea per-prompt îl "
|
||||
"avantajează, și pe care cea per-dolar îl penalizează.\n")
|
||||
|
||||
|
||||
# ---- Sectiunea 10: plafonul saptamanal Max 5x ----
|
||||
import statistics as _st, datetime as _dt, collections as _co
|
||||
_days={k:tt(v) for k,v in d["by_day"].items()}
|
||||
_THR=50e6
|
||||
_blk=[k for k,v in _days.items() if v<_THR]
|
||||
_act=[v for v in _days.values() if v>=_THR]
|
||||
_med=_st.median(_act); _deliv=sum(_days.values())/(nd/7); _want=_med*7
|
||||
_short=_want-_deliv; _shortp=PPW*_short/_deliv
|
||||
_wk=_co.defaultdict(lambda:[0,[]])
|
||||
for k in sorted(_days):
|
||||
_d0=_dt.date.fromisoformat(k); _w=_d0.isocalendar()[:2]
|
||||
_wk[_w][0]+=_days[k]; _wk[_w][1].append((_d0.strftime("%a")[:2],_days[k]))
|
||||
A("## 10. Problema reală: plafonul general săptămânal Max 5x\n")
|
||||
A("Max 5x are **două plafoane săptămânale** — unul general, pe toate modelele, și unul separat "
|
||||
"pentru Opus. Limita atinsă e cea **generală**, ceea ce înseamnă că un supliment pe GLM-5.3-Flash "
|
||||
"chiar poate ajuta: fiecare prompt mutat în altă parte eliberează cotă de Max.\n")
|
||||
A("### Tiparul se vede în date\n")
|
||||
A("| Săptămână | Total | Profil zilnic (M tokeni, Lu→Du) |")
|
||||
A("|---|---:|---|")
|
||||
for _w in sorted(_wk):
|
||||
_tok,_prof=_wk[_w]
|
||||
A(f"| {_w[0]}-W{_w[1]:02d} | {_tok/1e9:.2f}B | " + " · ".join(f"{a} **{b/1e6:.0f}**" for a,b in _prof) + " |")
|
||||
A("")
|
||||
A("**Săptămâna W33 e semnătura manualului pentru un plafon atins:** 655M luni, 229M marți, apoi "
|
||||
"40M, 8M, 1M, 1M, 1M. Cota s-a consumat în două zile, urmate de cinci zile moarte. Exact ce "
|
||||
"descrii. Prin contrast, W32 și W34 au mers 7 zile din 7, la 2,93–3,40B.\n")
|
||||
A(f"- **Zile efectiv blocate** (sub 50M tokeni): **{len(_blk)} din {len(_days)}** = {len(_blk)/len(_days)*100:.0f}%")
|
||||
A(f"- **Zi activă mediană:** {_med/1e6:.0f}M tokeni")
|
||||
A(f"- **Livrat efectiv de Max 5x:** {_deliv/1e9:.2f}B/săptămână")
|
||||
A(f"- **Cerere dacă ai lucra 7 zile din 7 la ritmul median:** {_want/1e9:.2f}B/săptămână")
|
||||
A(f"- **Deficit: {_short/1e9:.2f}B/săptămână = +{_short/_deliv*100:.0f}%** ≈ **~{_shortp:,.0f} prompturi/săptămână**\n")
|
||||
A("> Deficitul e o estimare **conservatoare**: presupune că în zilele blocate ai lucra la ritm "
|
||||
f"median ({_med/1e6:.0f}M), nu la ritmul zilelor bune (peste 850M). Cererea reală suprimată e "
|
||||
"probabil mai mare. Nu se poate măsura direct — Claude Code nu persistă evenimentele de limită "
|
||||
"în transcript, apar doar în stream-ul SDK.\n")
|
||||
A("### Ce plan acoperă deficitul\n")
|
||||
A("Nu trebuie mutat tot volumul — doar atât cât să elibereze cota lipsă.\n")
|
||||
A("| Plan | Lunar | Anual (efectiv/lună) | Cotă/săpt. | Acoperă deficitul de ~%d | Zile blocate rămase |" % round(_shortp))
|
||||
A("|---|---:|---:|---:|---:|---:|")
|
||||
for _nm,_mo,_yr,_ppw,_cr in PLANS:
|
||||
_cov=_ppw/_shortp
|
||||
_rem=max(0.0, len(_blk)/(nd/7) * (1-min(_cov,1.0)))
|
||||
A(f"| {_nm} | ${_mo:.2f} | ${_yr/12:.2f} | ~{_ppw:,} | **{_cov*100:.0f}%** | ~{_rem:.1f}/săpt. |")
|
||||
A("")
|
||||
A(f"Azi ai ~{len(_blk)/(nd/7):.1f} zile blocate pe săptămână. **Lite ar duce-o la ~"
|
||||
f"{len(_blk)/(nd/7)*(1-min(400/_shortp,1.0)):.1f}**, **Pro la zero**, cu marjă de "
|
||||
f"{2000/_shortp:.1f}× pentru creștere.\n")
|
||||
A("### Verificare din a doua direcție\n")
|
||||
A(f"Dacă muți **toată** treapta de execuție (Sonnet 5, {SON['turns']/T['turns']*100:.0f}% din "
|
||||
f"turn-uri, ~{PPW_SON:,.0f} prompturi/săpt.) pe GLM-5.3-Flash, eliberezi "
|
||||
f"{SON['turns']/T['turns']*100:.0f}% din cota Max — care ar ține atunci de "
|
||||
f"**{1/(1-SON['turns']/T['turns']):.1f}× mai mult**, adică toată săptămâna în loc de două zile.\n")
|
||||
A(f"Pentru asta ai nevoie de ~{PPW_SON:,.0f} prompturi/săpt.: **Lite ({400/PPW_SON*100:.0f}%) nu "
|
||||
f"ajunge, Pro ({2000/PPW_SON*100:.0f}%) ajunge cu marjă.**\n")
|
||||
A("Cele două calcule dau răspunsuri diferite pentru Lite pentru că măsoară lucruri diferite: "
|
||||
"primul, cât lipsește ca să nu mai fii blocat; al doilea, cât ar costa mutarea completă a "
|
||||
"treptei de execuție. **Nu ai nevoie de mutarea completă** — ai nevoie doar de deficit.\n")
|
||||
|
||||
A("## 11. Fișiere generate\n")
|
||||
A("| Fișier | Conținut |")
|
||||
A("|---|---|")
|
||||
A("| `claude_usage_report.md` | acest raport |")
|
||||
A(f"| `claude_usage_sessions.csv` | {N(ns)} rânduri, una per sesiune |")
|
||||
A("| `claude_usage_summary.csv` | agregate: TOTAL, mașină, model, lună, zi |")
|
||||
A("| `tools/claude_usage_extract.py` | extractorul read-only (rulabil remote: `ssh host 'python3 -' < script`) |")
|
||||
A("| `tools/claude_usage_report.py` | agregator + CSV |")
|
||||
A("| `tools/claude_usage_md.py` | generatorul acestui raport |")
|
||||
A("")
|
||||
A("## 12. CONCLUZIE\n")
|
||||
A("### „Dacă păstrez Claude Max 5x $100 și adaug OpenCode Zen $20, cât coding suplimentar obțin?\"\n")
|
||||
A(f"**Foarte puțin — aproximativ {nd*(20/gfp):.1f} zile pe lună, în cel mai bun caz.**\n")
|
||||
A(f"Consumul real pe {nd} zile, pe trei mașini: **{tt(T)/1e9:.2f} miliarde de tokeni**, {N(ns)} sesiuni, "
|
||||
f"{N(T['turns'])} cereri API. La acest volum, $20 nu sunt un supliment — sunt o rotunjire.\n")
|
||||
A("| | GLM-5.3 | GLM-5.3-Flash (promo) | GLM-5.3-Flash (listă) |")
|
||||
A("|---|---:|---:|---:|")
|
||||
A(f"| Cost pentru volumul tău lunar | ${g53:,.0f} | ${gfp:,.0f} | ${gfl:,.0f} |")
|
||||
A(f"| Cât din el acoperă $20 | {20/g53*100:.1f}% | **{20/gfp*100:.1f}%** | {20/gfl*100:.1f}% |")
|
||||
A(f"| Zile de lucru din $20 | {nd*20/g53:.2f} | **{nd*20/gfp:.1f}** | {nd*20/gfl:.1f} |")
|
||||
A("")
|
||||
A("### Punct cu punct\n")
|
||||
A("**1. Ar fi fost $20 Zen suficienți pentru consumul observat?** \n")
|
||||
A(f"**NU, pe niciun model.**\n")
|
||||
A(f"- GLM-5.3-Flash (promo): ${gfp:,.2f} — de **{gfp/20:.1f}× peste buget**. Cel mai bun caz posibil.")
|
||||
A(f"- GLM-5.3-Flash (listă, după 9 sept.): ${gfl:,.2f} — de **{gfl/20:.0f}× peste buget**.")
|
||||
A(f"- GLM-5.3: ${g53:,.2f} — de **{g53/20:.0f}× peste buget**.")
|
||||
A(f"- Fără cache (Scenariul B), chiar și Flash promo ajunge la ${cB(T,'GLM-5.3-Flash (promo)'):,.2f} — de {cB(T,'GLM-5.3-Flash (promo)')/20:.0f}× peste.\n")
|
||||
A("**2. Câte sesiuni/zi înseamnă?** \n")
|
||||
A(f"Ritmul real: **{ns/nd:.1f} sesiuni/zi**, {tt(T)/nd/1e6:.0f}M tokeni/zi. Cu $20 obții:")
|
||||
A(f"- GLM-5.3-Flash promo: **{nd*20/gfp:.1f} zile** (~{ns*20/gfp:.0f} sesiuni)")
|
||||
A(f"- GLM-5.3-Flash listă: **{nd*20/gfl:.1f} zile** (~{ns*20/gfl:.0f} sesiuni)")
|
||||
A(f"- GLM-5.3: **{nd*20/g53:.2f} zile** (~{ns*20/g53:.0f} sesiuni) — sub o zi\n")
|
||||
A("**3. Ce model e mai avantajos?** \n")
|
||||
A(f"**GLM-5.3-Flash**, de **{g53/gfp:.1f}× mai ieftin** decât GLM-5.3. Diferența absolută pe "
|
||||
f"consumul tău: **${g53-gfp:,.2f}** (${g53:,.2f} vs ${gfp:,.2f}). La preț de listă diferența "
|
||||
f"rămâne **${g53-gfl:,.2f}**. Compromisul e de calitate — Flash e modelul mic.\n")
|
||||
A("**4. Diferența de cost GLM-5.3 vs GLM-5.3-Flash** \n")
|
||||
A(f"| | GLM-5.3 | Flash (promo) | Flash (listă) |")
|
||||
A("|---|---:|---:|---:|")
|
||||
A(f"| Total | ${g53:,.2f} | ${gfp:,.2f} | ${gfl:,.2f} |")
|
||||
A(f"| Pe zi | ${g53/nd:.2f} | ${gfp/nd:.2f} | ${gfl/nd:.2f} |")
|
||||
A(f"| Pe sesiune | ${g53/ns:.3f} | ${gfp/ns:.3f} | ${gfl/ns:.3f} |")
|
||||
A(f"| Raport față de GLM-5.3 | 1× | {g53/gfp:.1f}× mai ieftin | {g53/gfl:.1f}× mai ieftin |")
|
||||
A("")
|
||||
A("**5. Ce procent din utilizarea Claude poate fi mutat realist pe Zen?** \n")
|
||||
A(f"Structura reală: **{son:.0f}%** Sonnet 5, **{op:.0f}%** Opus 5, "
|
||||
f"{tt(d['by_model']['claude-fable-5'])/tt(T)*100:.1f}% Fable 5, restul Haiku.\n")
|
||||
A("Întrebarea corectă nu mai e „ce procent muți\", ci **„cât încape în $20\"** — și răspunsul e "
|
||||
f"**{20/gfp*100:.1f}%** din volum, la cel mai ieftin tarif posibil. Chiar dacă ai muta pe Zen "
|
||||
"absolut tot ce rulează azi pe Sonnet, ai depăși bugetul de câteva ori.\n")
|
||||
A(f"Cu $20/lună pe GLM-5.3-Flash poți acoperi realist **~{20/gfp*100:.0f}% din volum** — echivalentul "
|
||||
f"a ~{tt(T)*20/gfp/1e6:.0f}M tokeni. Ținta rezonabilă: **un singur flux automat**, mic și bine "
|
||||
"delimitat (un bot, un job de noapte, un agent în buclă), nu o felie procentuală din tot ce faci.\n")
|
||||
A("**6. Dar abonamentele Z.AI?** \n")
|
||||
A(f"Aici răspunsul e cu totul altul decât la Zen $20. La ~{PPW:,.0f} prompturi/săptămână:\n")
|
||||
A("| | Efectiv/lună (anual) | Acoperire la 1× | Acoperire la 3× |")
|
||||
A("|---|---:|---:|---:|")
|
||||
A(f"| Z.AI Coding **Lite** | $12.60 | {400/PPW*100:.0f}% | {400/3/PPW*100:.0f}% |")
|
||||
A(f"| Z.AI Coding **Pro** | $50.40 | **{2000/PPW*100:.0f}%** | {2000/3/PPW*100:.0f}% |")
|
||||
A(f"| Z.AI Coding **Max** | $112.00 | {8000/PPW*100:.0f}% | **{8000/3/PPW*100:.0f}%** |")
|
||||
A("")
|
||||
A("**Lite ($12,60/lună) e opțiunea potrivită pentru rolul pe care îl căutai la Zen $20** — cost "
|
||||
f"similar, dar acoperă {400/PPW*100:.0f}% din volum în loc de {20/gfp*100:.1f}%, adică de "
|
||||
f"**{(400/PPW)/(20/gfp):.0f}× mai mult** pentru aceiași bani. Modelul de abonament bate plata "
|
||||
"la token, exact cum o face și Max față de tarifele Zen.\n")
|
||||
A("### Recomandare\n")
|
||||
A(f"**Păstrează Max 5x — la ${zs:,.0f} valoare per-token pentru $100 plătiți, e cea mai bună afacere "
|
||||
"din tot setul de date.** Nu e nimic de optimizat acolo.\n")
|
||||
A("**Nu lua OpenCode Zen $20.** La volumul tău e o rotunjire (10,5%), iar pentru bani similari "
|
||||
f"Z.AI Coding Lite anual ($12,60/lună) îți dă de {(400/PPW)/(20/gfp):.0f}× mai mult. Singurul "
|
||||
"motiv real pentru Zen ar fi accesul la multe modele diferite dintr-un singur cont, nu volumul.\n")
|
||||
A("**Dacă vrei capacitate suplimentară serioasă**, ordinea corectă e:\n")
|
||||
A(f"1. **Z.AI Coding Lite anual, $12,60/lună** — ca supliment: preia un flux automat întreg "
|
||||
f"(tot `moltbot` = {tt(d['by_machine']['moltbot'])/1e6:.0f}M tokeni/lună) și îți lasă Max liber "
|
||||
"pentru munca grea. Risc mic, cost mic. **Aici aș începe.**")
|
||||
A(f"2. **Z.AI Coding Pro anual, $50,40/lună** — dacă vrei să muți o felie mare din volum. "
|
||||
f"Acoperă {2000/PPW*100:.0f}% la 1×, dar doar {2000/3/PPW*100:.0f}% dacă GLM-5.3 se contorizează "
|
||||
"la 3×. Verifică multiplicatorul înainte, e diferența dintre „ajunge\" și „nu ajunge\".")
|
||||
A("3. **Z.AI Coding Max anual, $112/lună** — singurul care acoperă volumul indiferent de "
|
||||
"multiplicator, dar la acel preț compari direct cu un al doilea Max 5x sau cu Max 20x, unde "
|
||||
"primești Opus 5 în loc de GLM-5.3.\n")
|
||||
A("**Testul decisiv nu e de preț, e de calitate.** Toate variantele Z.AI îți dau GLM-5.3, nu "
|
||||
f"Opus 5, iar {op:.0f}% din volumul tău rulează pe Opus 5 pentru că ai ales asta. Ia Lite anual "
|
||||
"o lună, rutează pe el fluxurile automate, și vezi dacă rezultatul e acceptabil înainte să "
|
||||
"muți ceva important.\n")
|
||||
A("**Rolul rămas pentru Zen $20** (dacă îl iei totuși) e restrâns:\n")
|
||||
A("- **buget de experiment** — să testezi dacă GLM-5.3-Flash e destul de bun pe un flux anume, "
|
||||
"înainte să iei o decizie mai mare;")
|
||||
A("- **overflow de urgență** — dacă atingi limita Max într-o zi aglomerată, ai un backup care "
|
||||
"costă cenți pe oră;")
|
||||
A(f"- **un singur job automat mic** — de exemplu tot ce rulează pe `moltbot` "
|
||||
f"({tt(d['by_machine']['moltbot'])/1e6:.0f}M tokeni = ${S(d['by_machine']['moltbot'],'GLM-5.3-Flash (promo)'):.2f}/lună "
|
||||
f"pe Flash promo, {S(d['by_machine']['moltbot'],'GLM-5.3-Flash (promo)')/20*100:.0f}% din buget).\n")
|
||||
|
||||
A("### Rezerve\n")
|
||||
A(f"1. **`claude-agent` are doar 5 zile din {nd}** cu transcripturi păstrate; consumul lui real e "
|
||||
f"mai mare decât cei {tt(d['by_machine']['claude-agent'])/1e6:.0f}M măsurați. Nu am extrapolat. "
|
||||
"Efectul e mic la scara totalului (2,7%), dar direcția e clară: totalul real e mai mare, nu mai mic.")
|
||||
A("2. **Alte mașini Windows** din tailnet (`marius-hx90g`, `lenovo-aio-h1`, `lg17`, `roa-carapetru2`) "
|
||||
"nu au fost scanate — erau offline. Dacă rulezi Claude Code și pe ele, totalul crește.")
|
||||
A(f"3. **Cache hit rate-ul e ipoteza critică:** {T['cache_read']/tt(T)*100:.1f}% din volum e cache read. "
|
||||
"OpenCode cu GLM nu garantează același comportament de caching ca Claude Code — Scenariul B "
|
||||
f"(${cB(T,'GLM-5.3-Flash (promo)'):,.0f} în loc de ${gfp:,.0f}) e cazul rău, și nu e improbabil.")
|
||||
A("4. **Prețul Flash e promoțional** până pe 2026-09-09; se dublează după.")
|
||||
A("5. **GLM-5.3 nu e listat oficial pe OpenCode Zen** — am folosit tariful Z.AI ca proxy, validat "
|
||||
"prin faptul că GLM 5.2 are pe Zen exact prețul Z.AI.")
|
||||
A("6. **Datele Windows au trecut printr-o conversie de tabel** (TAB + date M/D/YYYY). UUID-urile și "
|
||||
"valorile numerice au fost validate, dar nu am putut verifica independent fișierele sursă.")
|
||||
open(OUT+"/claude_usage_report.md","w").write("\n".join(L)+"\n")
|
||||
print("scris")
|
||||
119
tools/claude_usage_report.py
Normal file
119
tools/claude_usage_report.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user