#!/usr/bin/env python3 # -*- coding: utf-8 -*- """作业 9 · 汇报数据:Langfuse 请求/Token + SLA + 模版占位(业务/成本/规划)。""" from __future__ import annotations import json import urllib.parse import urllib.request from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any, Optional import _bootstrap # noqa: F401 import _config as C TZ8 = timezone(timedelta(hours=8)) def repo_root() -> Path: here = Path(__file__).resolve().parent if here.parent.name == "scripts_cursor": return here.parents[1] return here.parent def data_dir() -> Path: import os if os.name != "nt" and Path("/opt/workbuddy").is_dir(): p = Path(getattr(C, "HOMEWORK9_DATA_DIR", "/opt/workbuddy/data/homework9")) else: p = repo_root() / "data" / "homework9" p.mkdir(parents=True, exist_ok=True) return p def template_path() -> Path: remote = Path(getattr(C, "HOMEWORK9_TEMPLATE", "")) local = repo_root() / "patches_cursor" / "作业9_汇报PPT模版" / "template_defaults.json" if remote.is_file(): return remote return local def load_template() -> dict[str, Any]: p = template_path() data = json.loads(p.read_text(encoding="utf-8")) ov = data_dir() / "overrides.json" if ov.is_file(): data.update(json.loads(ov.read_text(encoding="utf-8"))) # nested merge for known keys raw = json.loads(ov.read_text(encoding="utf-8")) for key in ("business", "cost", "next"): if isinstance(raw.get(key), dict) and isinstance(data.get(key), dict): merged = dict(data[key]) merged.update(raw[key]) data[key] = merged return data def curl_get(url: str, auth: tuple[str, str] | None = None, timeout: int = 25) -> str: req = urllib.request.Request(url) if auth: import base64 token = base64.b64encode(("%s:%s" % auth).encode()).decode() req.add_header("Authorization", "Basic %s" % token) with urllib.request.urlopen(req, timeout=timeout) as resp: return resp.read().decode("utf-8", "replace") def parse_langfuse_daily(raw: str, days: int = 14) -> dict[str, Any]: data = json.loads(raw) rows = data.get("data") if isinstance(data, dict) else [] daily: list[dict[str, Any]] = [] for row in rows or []: if not isinstance(row, dict): continue day_tokens = 0 for u in row.get("usage") or []: if isinstance(u, dict): day_tokens += int(u.get("totalUsage") or 0) daily.append( { "date": str(row.get("date") or "")[:10], "tokens": day_tokens, "traces": int(row.get("countTraces") or 0), } ) daily.sort(key=lambda x: x.get("date") or "") daily = daily[-days:] return { "daily": daily, "total_tokens": sum(d["tokens"] for d in daily), "total_traces": sum(d["traces"] for d in daily), "source": "langfuse", } def fetch_langfuse(days: int = 14) -> dict[str, Any]: url = "%s/api/public/metrics/daily?limit=%d" % (C.LANGFUSE_URL.rstrip("/"), max(1, days)) try: raw = curl_get(url, auth=(C.LANGFUSE_PK, C.LANGFUSE_SK)) return parse_langfuse_daily(raw, days=days) except Exception as exc: return simulated_langfuse(days=days, error=str(exc)) def simulated_langfuse(days: int = 14, error: str = "") -> dict[str, Any]: today = datetime.now(TZ8).date() daily = [] for i in range(days): d = today - timedelta(days=days - 1 - i) base = 18 + (i % 5) * 3 daily.append({"date": d.isoformat(), "tokens": base * 1200, "traces": base}) return { "daily": daily, "total_tokens": sum(x["tokens"] for x in daily), "total_traces": sum(x["traces"] for x in daily), "source": "simulated", "error": error, } def mom_from_daily(daily: list[dict[str, Any]]) -> dict[str, Any]: """近两周对比:后 7 天 vs 前 7 天。""" if len(daily) < 2: return { "traces_wow_pct": 12.0, "tokens_wow_pct": 9.0, "source": "simulated", } mid = max(1, len(daily) // 2) prev, curr = daily[:mid], daily[mid:] pt = sum(x["traces"] for x in prev) or 1 ct = sum(x["traces"] for x in curr) pk = sum(x["tokens"] for x in prev) or 1 ck = sum(x["tokens"] for x in curr) return { "traces_wow_pct": round((ct - pt) / pt * 100.0, 1), "tokens_wow_pct": round((ck - pk) / pk * 100.0, 1), "prev_traces": sum(x["traces"] for x in prev), "curr_traces": ct, "prev_tokens": sum(x["tokens"] for x in prev), "curr_tokens": ck, "source": "langfuse_split", } def fetch_sla() -> dict[str, Any]: out = { "availability_pct": 99.92, "ttft_p99": 1.35, "full_p99": 6.8, "source": "simulated", } try: base = C.HOMEWORK8_PROMETHEUS_URL.rstrip("/") raw = json.loads(curl_get("%s/api/v1/query?query=%s" % (base, urllib.parse.quote("ai_platform_availability_ratio*100")))) res = raw.get("data", {}).get("result") or [] if res: out["availability_pct"] = round(float(res[0]["value"][1]), 2) out["source"] = "prometheus" raw2 = json.loads(curl_get("%s/api/v1/query?query=%s" % (base, urllib.parse.quote("ai_sla_first_token_latency_p99_seconds")))) r2 = raw2.get("data", {}).get("result") or [] if r2: out["ttft_p99"] = round(float(r2[0]["value"][1]), 2) raw3 = json.loads(curl_get("%s/api/v1/query?query=%s" % (base, urllib.parse.quote("ai_sla_full_response_latency_p99_seconds")))) r3 = raw3.get("data", {}).get("result") or [] if r3: out["full_p99"] = round(float(r3[0]["value"][1]), 2) except Exception: state = Path(getattr(C, "HOMEWORK8_DATA_DIR", "/opt/workbuddy/data/homework8")) / "sla_state.json" alt = repo_root() / "data" / "homework8" / "sla_state.json" for p in (state, alt): if p.is_file(): snap = json.loads(p.read_text(encoding="utf-8")) out["availability_pct"] = round(float(snap.get("availability_ratio", 0.9992)) * 100, 2) out["ttft_p99"] = float(snap.get("first_token_p99_seconds", 1.35)) out["full_p99"] = float(snap.get("full_response_p99_seconds", 6.8)) out["source"] = "sla_state" break return out def collect_deck() -> dict[str, Any]: tpl = load_template() lf = fetch_langfuse(14) sla = fetch_sla() mom = mom_from_daily(list(lf.get("daily") or [])) deck = { "generated_at": datetime.now(TZ8).isoformat(), "title": tpl.get("title") or getattr(C, "HOMEWORK9_TITLE", "AI 平台经营汇报"), "org": tpl.get("org") or getattr(C, "HOMEWORK9_ORG", ""), "period": tpl.get("period") or getattr(C, "HOMEWORK9_PERIOD", ""), "presenter": tpl.get("presenter") or "平台项目组", "core": { "total_requests": int(lf.get("total_traces") or 0), "total_tokens": int(lf.get("total_tokens") or 0), "availability_pct": sla["availability_pct"], "ttft_p99": sla["ttft_p99"], "full_p99": sla["full_p99"], "langfuse_source": lf.get("source"), "sla_source": sla.get("source"), "daily": lf.get("daily") or [], "mom": mom, }, "business": tpl.get("business") or {}, "cost": tpl.get("cost") or {}, "next": tpl.get("next") or {}, } (data_dir() / "report.json").write_text( json.dumps(deck, ensure_ascii=False, indent=2), encoding="utf-8" ) return deck def fmt_int(n: int) -> str: return "{:,}".format(int(n)) def fmt_pct(n: float, signed: bool = False) -> str: s = "%.1f%%" % float(n) if signed and float(n) > 0: return "+" + s return s