testsAndMisc/python_pkg/token_dashboard/finalize.py
Krzysztof kuhy Rudnicki 1ba202c567
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (mypy, mypy) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (pylint, pylint) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (rest, ) (push) Waiting to run
Pre-commit checks / pre-commit (${{ matrix.group }}) (shellcheck, shellcheck) (push) Waiting to run
Python tests / test (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / lib-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
token_dashboard: ingester core + detail tables (calls, marks, turn_detail, ab_rows) for the control-panel dashboard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JThjboXTeZ3yJPBzxMx5wS
2026-10-18 14:45:06 +02:00

218 lines
7.7 KiB
Python

"""Derive per-session, per-prompt and per-subagent summaries from raw rows.
Run for every main session touched by an ingest pass. A subagent file may be
ingested in a different pass than its parent, so everything here is computed
from the store, never from the in-memory walk.
Linking a subagent to the prompt that spawned it: the ``toolUseId`` in its
``.meta.json`` (or the ``agentId`` the ``Agent`` result reported) names an
``Agent`` call; that call's prompt is the answer. A depth-2 agent's call sits
in its parent agent's file, so the chain is followed upward. An agent with no
resolvable spawn -- one that outlived ``/clear`` -- stays ``prompt_idx NULL``
("unattributed") rather than being dropped, so session cost always reconciles.
"""
from __future__ import annotations
from collections import defaultdict
import json
from typing import TYPE_CHECKING, Any
from python_pkg.token_dashboard import active
if TYPE_CHECKING:
import sqlite3
_MAX_DEPTH = 8
def _rows(conn: sqlite3.Connection, sql: str, *args: object) -> list[Any]:
"""Run a query and return all rows."""
return conn.execute(sql, args).fetchall()
class _Session:
"""Everything stored for one main session and its subagents."""
def __init__(self, conn: sqlite3.Connection, main: str) -> None:
self.main = main
self.sessions = {
r[0]: r
for r in _rows(
conn,
"SELECT key, cwd, agent_type, description, spawn_tool_use_id,"
" agent_model, first_ts, last_ts, final_tag"
" FROM sessions WHERE main_key = ?",
main,
)
}
self.turns = _rows(
conn,
"SELECT key, prompt_idx, usd, model FROM turns WHERE main_key = ?",
main,
)
keys = json.dumps(list(self.sessions))
self.calls = _rows(
conn,
"SELECT key, tool_use_id, agent_id, prompt_idx, description,"
" subagent_type FROM agent_calls WHERE key IN"
" (SELECT value FROM json_each(?))",
keys,
)
self.spans_by_key: dict[str, list[active.Span]] = defaultdict(list)
for key, start, end in _rows(
conn,
"SELECT key, start, end FROM activity WHERE key IN"
" (SELECT value FROM json_each(?))",
keys,
):
self.spans_by_key[key].append((start, end))
self.spans = [s for spans in self.spans_by_key.values() for s in spans]
self.prompts = _rows(
conn,
"SELECT idx, ts, text FROM prompts WHERE key = ? ORDER BY idx",
main,
)
self.usd: dict[str, float] = defaultdict(float)
self.ncalls: dict[str, int] = defaultdict(int)
self.models: dict[str, dict[str, float]] = defaultdict(
lambda: defaultdict(float)
)
for key, _, usd, model in self.turns:
self.usd[key] += usd
self.ncalls[key] += 1
self.models[key][model] += usd
def spawn(self, sub: str) -> tuple[Any, ...] | None:
"""The ``Agent`` call that spawned ``sub``, if any is stored."""
tool_use_id = self.sessions[sub][4]
agent_id = sub.rsplit("/agent-", 1)[-1]
for call in self.calls:
if (tool_use_id and call[1] == tool_use_id) or call[2] == agent_id:
return tuple(call)
return None
def resolve(self, sub: str) -> tuple[int | None, int, tuple[Any, ...] | None]:
"""``(prompt_idx, depth, spawning call)`` for a subagent."""
first = self.spawn(sub)
call, depth = first, 1
while call is not None and call[0] != self.main and depth < _MAX_DEPTH:
call = self.spawn(call[0]) if call[0] in self.sessions else None
depth += 1
return (call[3] if call is not None else None), depth, first
def _sub_rows(state: _Session) -> list[tuple[Any, ...]]:
"""One ``subagent_link`` row per subagent of the session."""
out = []
for key, row in state.sessions.items():
if key == state.main:
continue
prompt_idx, depth, call = state.resolve(key)
models = state.models.get(key) or {}
model = max(models, key=models.__getitem__) if models else row[5]
spans = state.spans_by_key.get(key, [])
out.append(
(
key,
state.main,
prompt_idx,
depth,
row[3] or (call[4] if call else "") or key,
row[2] or (call[5] if call else "") or "unknown",
model,
state.usd.get(key, 0.0),
state.ncalls.get(key, 0),
row[6],
row[7],
row[7] - row[6],
active.measure(spans),
)
)
return out
def refresh(conn: sqlite3.Connection, main: str) -> None:
"""Rebuild every summary row for one main session."""
state = _Session(conn, main)
conn.execute("DELETE FROM subagent_link WHERE main_key = ?", (main,))
conn.execute("DELETE FROM prompt_summary WHERE key = ?", (main,))
conn.execute("DELETE FROM session_summary WHERE key = ?", (main,))
if not state.sessions:
return
subs = _sub_rows(state)
conn.executemany(
"INSERT INTO subagent_link VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", subs
)
first = min((r[6] for r in state.sessions.values() if r[6]), default=0.0)
last = max(r[7] for r in state.sessions.values())
_prompt_rows(conn, state, subs, last)
own = state.sessions.get(main)
usd_main = state.usd.get(main, 0.0)
usd_sub = sum(r[7] for r in subs)
merged: dict[str, float] = defaultdict(float)
for models in state.models.values():
for model, usd in models.items():
merged[model] += usd
conn.execute(
"INSERT INTO session_summary VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
main,
own[1] if own else next(iter(state.sessions.values()))[1],
state.prompts[0][2] if state.prompts else (subs[0][4] if subs else ""),
first,
last,
last - first,
active.measure(state.spans),
usd_main + usd_sub,
usd_main,
usd_sub,
sum(state.ncalls.values()),
len(state.prompts),
len(subs),
json.dumps(dict(merged)),
own[8] if own else None,
),
)
def _prompt_rows(
conn: sqlite3.Connection,
state: _Session,
subs: list[tuple[Any, ...]],
last: float,
) -> None:
"""One ``prompt_summary`` row per prompt, plus one for pre-prompt turns."""
own: dict[int, float] = defaultdict(float)
calls: dict[int, int] = defaultdict(int)
for key, idx, usd, _ in state.turns:
if key == state.main:
own[idx] += usd
calls[idx] += 1
sub_usd: dict[int, float] = defaultdict(float)
for row in subs:
if row[2] is not None:
sub_usd[row[2]] += row[7]
prompts = list(state.prompts)
if own.get(-1) or sub_usd.get(-1):
start = min((s for s, _ in state.spans), default=0.0)
prompts.insert(0, (-1, start, "(before the first prompt)"))
bounds = [ts for _, ts, _ in prompts[1:]] + ([last + 1] if prompts else [])
conn.executemany(
"INSERT INTO prompt_summary VALUES (?,?,?,?,?,?,?,?,?)",
[
(
state.main,
idx,
ts,
text,
own.get(idx, 0.0),
sub_usd.get(idx, 0.0),
active.measure(state.spans, ts, end),
min(end, last) - ts,
calls.get(idx, 0),
)
for (idx, ts, text), end in zip(prompts, bounds, strict=True)
],
)