#!/usr/bin/env python3 """Run derive-workspace analyze locally and print LLM token usage from summary.llm.""" from __future__ import annotations import argparse import asyncio import json import os import sys from datetime import date from pathlib import Path _BACKEND = Path(__file__).resolve().parents[1] if str(_BACKEND) not in sys.path: sys.path.insert(0, str(_BACKEND)) from dotenv import load_dotenv load_dotenv(_BACKEND / ".env") load_dotenv(_BACKEND / ".env.local", override=True) from investibook.core import derive_workspace as dw from investibook.db import queries as dbq def _load_env_data_dir() -> None: data = (os.environ.get("DATA_DIR") or "").strip() if data: dbq.DB_PATH = Path(data) / "db" / "investibook.db" elif not Path(dbq.DB_PATH).is_file(): dbq.DB_PATH = _BACKEND / "data" / "db" / "investibook.db" async def main() -> int: parser = argparse.ArgumentParser(description="Measure Analyze LLM token usage") parser.add_argument("--day", default="2026-05-04", help="UTC+8 bucket start (YYYY-MM-DD)") parser.add_argument( "--metric", default="dossiers_created", choices=["dossiers_created", "dossiers_workspace_linked_new"], ) parser.add_argument("--project", default="ALL") parser.add_argument("--granularity", default="week", choices=["day", "week", "month"]) parser.add_argument("--limit", type=int, default=0, help="Max dossiers to analyze (0 = all in bucket)") args = parser.parse_args() _load_env_data_dir() if not Path(dbq.DB_PATH).is_file(): print(f"DB not found: {dbq.DB_PATH}", file=sys.stderr) return 1 day = date.fromisoformat(args.day) dd = dbq._db_admin_dashboard_ticket_trends_drill_down( day=day, metric=args.metric, project=args.project, granularity=args.granularity, ) dossier_ids = [ str(x.get("dossier_id") or x.get("id") or "").strip() for x in (dd.get("items") or []) if str(x.get("dossier_id") or x.get("id") or "").strip() ] if args.limit > 0: dossier_ids = dossier_ids[: args.limit] print(f"DB: {dbq.DB_PATH}") print(f"Bucket: {args.metric} · {args.granularity} of {args.day} · project={args.project}") print(f"Dossiers in drill-down: {len(dd.get('items') or [])}, analyzing: {len(dossier_ids)}") scope = dw.AnalyzeScope( dossier_ids=dossier_ids, metric=args.metric, day=args.day, project=args.project, granularity=args.granularity, range_from=None, range_to=None, ) out = await dw.analyze(scope) summary = out.get("summary") or {} llm = summary.get("llm") or {} print("\n--- Analyze summary ---") print(json.dumps(summary, indent=2, ensure_ascii=False)) print("\n--- Token usage ---") print(f"LLM calls (with usage): {llm.get('calls', 0)}") print(f"Input tokens: {llm.get('input_tokens', 0):,}") print(f"Output tokens: {llm.get('output_tokens', 0):,}") print(f"Total tokens: {llm.get('total_tokens', 0):,}") scanned = int(summary.get("dossiers_scanned") or 0) skipped = int(summary.get("dossiers_skipped_prior_state") or 0) if scanned: denom = scanned - skipped if denom > 0 and llm.get("calls"): print( f"Per analyzed dossier (excl. skipped): " f"~{int(llm.get('input_tokens', 0)) // denom:,} in / " f"~{int(llm.get('output_tokens', 0)) // denom:,} out" ) return 0 if __name__ == "__main__": raise SystemExit(asyncio.run(main()))