mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 23:07:03 +01:00
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
Links sessions to todo notes (note_claim.sh, todo-images prompts, fuzzy first prompt >= 0.5, handoff chains), dates completions from the status HLC in the first Firebase dump, and adds usd_per_note, note_linked_usd_share, notes_done(_linked) to the snapshot, a WEEKLY section and notes.json. token_dashboard stores the build-class A/B (agent-ab-build.jsonl) as meta ab_build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWWMeapxnDcgk7HSw67
199 lines
6.7 KiB
Python
199 lines
6.7 KiB
Python
"""USD per completed todo note: per-note rows, window metrics and outputs.
|
|
|
|
``usd_per_note`` = lifetime linked USD of notes done or abandoned in the window
|
|
over the notes done in the window that have a linked session (lower is
|
|
better). ``note_linked_usd_share`` is the share of window spend linked to any
|
|
note: how far to trust the first number.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from dataclasses import asdict, dataclass
|
|
from datetime import UTC, datetime
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
import time
|
|
from typing import TYPE_CHECKING
|
|
|
|
from python_pkg.token_audit import history, notes_collect, pricing
|
|
from python_pkg.token_audit.notes_backlog import TERMINAL
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Mapping, Sequence
|
|
|
|
from python_pkg.token_audit.model import Session
|
|
from python_pkg.token_audit.notes_backlog import Note
|
|
from python_pkg.token_audit.notes_cost import Spend
|
|
from python_pkg.token_audit.notes_link import Link
|
|
|
|
JSON_NAME = "notes.json"
|
|
TOP = 15
|
|
TITLE_CHARS = 60
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class NoteRow:
|
|
"""One note with the spend of every session linked to it."""
|
|
|
|
id: str
|
|
title: str
|
|
status: str
|
|
priority: str
|
|
done_at: float | None
|
|
sessions: list[str]
|
|
usd: float
|
|
tokens: int
|
|
|
|
|
|
def backlog_path_for(home: Path) -> Path:
|
|
"""BACKLOG.md under a given home directory."""
|
|
return home / "src" / "todo" / "BACKLOG.md"
|
|
|
|
|
|
def note_rows(
|
|
notes: list[Note], links: Mapping[str, Link], spend: Mapping[str, Spend]
|
|
) -> list[NoteRow]:
|
|
"""Notes with >=1 linked session; a session's spend splits equally."""
|
|
usd: dict[str, float] = {}
|
|
toks: dict[str, float] = {}
|
|
sessions: dict[str, list[str]] = {}
|
|
for sid in sorted(links):
|
|
share = len(links[sid].notes)
|
|
cost = spend.get(sid)
|
|
for note_id in links[sid].notes:
|
|
sessions.setdefault(note_id, []).append(sid)
|
|
usd[note_id] = usd.get(note_id, 0.0) + (cost.usd / share if cost else 0.0)
|
|
toks[note_id] = toks.get(note_id, 0) + (cost.tokens / share if cost else 0)
|
|
rows = [
|
|
NoteRow(
|
|
n.id,
|
|
n.title,
|
|
n.status,
|
|
n.priority,
|
|
n.done_at,
|
|
sessions[n.id],
|
|
round(usd[n.id], 4),
|
|
round(toks[n.id]),
|
|
)
|
|
for n in notes
|
|
if n.id in sessions
|
|
]
|
|
return sorted(rows, key=lambda r: -r.usd)
|
|
|
|
|
|
def _in(stamp: float | None, window: tuple[float, float]) -> bool:
|
|
return stamp is not None and window[0] <= stamp <= window[1]
|
|
|
|
|
|
def metrics(
|
|
notes: list[Note],
|
|
rows: list[NoteRow],
|
|
window: tuple[float, float],
|
|
window_usd: Mapping[str, float],
|
|
linked: set[str],
|
|
) -> dict[str, float]:
|
|
"""The snapshot metrics; ``usd_per_note`` is absent when unmeasurable."""
|
|
done = {n.id for n in notes if n.status == "done" and _in(n.done_at, window)}
|
|
closed = {n.id for n in notes if n.status == "abandoned" and _in(n.done_at, window)}
|
|
linked_done = [r for r in rows if r.id in done]
|
|
spent = sum(r.usd for r in rows if r.id in done | closed)
|
|
total = sum(window_usd.values())
|
|
linked_usd = sum(v for k, v in window_usd.items() if k in linked)
|
|
out: dict[str, float] = {
|
|
"note_linked_usd_share": round(linked_usd / total, 4) if total else 0.0,
|
|
"notes_done": len(done),
|
|
"notes_done_linked": len(linked_done),
|
|
}
|
|
if linked_done:
|
|
out = {"usd_per_note": round(spent / len(linked_done), 2)} | out
|
|
return out
|
|
|
|
|
|
def _day(stamp: float | None) -> str:
|
|
if not stamp:
|
|
return "-"
|
|
return datetime.fromtimestamp(stamp, tz=UTC).astimezone().strftime("%Y-%m-%d")
|
|
|
|
|
|
def _title(text: str) -> str:
|
|
text = text.replace("|", "\\|")
|
|
return text if len(text) <= TITLE_CHARS else text[: TITLE_CHARS - 1] + "…"
|
|
|
|
|
|
def render(
|
|
rows: list[NoteRow], found: Mapping[str, float], rules: Mapping[str, int]
|
|
) -> list[str]:
|
|
"""The WEEKLY.md section: metrics, link sources, top closed notes by USD."""
|
|
per_note = found.get("usd_per_note")
|
|
lines = [
|
|
"## Cost per todo note",
|
|
"",
|
|
f"- usd_per_note: {'n/a' if per_note is None else f'${per_note:,.2f}'}"
|
|
" (lower is better)",
|
|
f"- note_linked_usd_share: {found['note_linked_usd_share'] * 100:.1f}%",
|
|
f"- notes_done: {found['notes_done']} "
|
|
f"({found.get('notes_done_linked', 0)} with a linked session)",
|
|
"- links by rule: "
|
|
+ " ".join(f"{k}={rules.get(k, 0)}" for k in ("a", "b", "c", "d")),
|
|
"",
|
|
"| note | status | done | sessions | USD | tokens |",
|
|
"|---|---|---|---|---|---|",
|
|
]
|
|
lines += [
|
|
f"| {_title(r.title)} | {r.status} | {_day(r.done_at)} | "
|
|
f"{len(r.sessions)} | ${r.usd:,.2f} | {r.tokens:,} |"
|
|
for r in rows[:TOP]
|
|
]
|
|
return lines
|
|
|
|
|
|
def write_json(rows: list[NoteRow], out: Path) -> Path:
|
|
"""Write every linked note for the dashboard."""
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
path = out / JSON_NAME
|
|
data = {"generated": time.time(), "notes": [asdict(r) for r in rows]}
|
|
path.write_text(json.dumps(data, indent=1), encoding="utf-8")
|
|
return path
|
|
|
|
|
|
def section(
|
|
root: Path,
|
|
book: pricing.PriceBook,
|
|
sessions: Sequence[Session],
|
|
window: tuple[float, float],
|
|
) -> tuple[dict[str, float], list[str], list[NoteRow]] | None:
|
|
"""Metrics, WEEKLY.md lines and rows for one audit window."""
|
|
started = time.monotonic()
|
|
found = notes_collect.collect(root, book, sessions, window[0])
|
|
if found is None:
|
|
return None
|
|
rows = note_rows(found.notes, found.links, found.spend)
|
|
spent = notes_collect.window_usd(sessions, book)
|
|
got = metrics(found.notes, rows, window, spent, set(found.links))
|
|
rules = " ".join(f"{k}={v}" for k, v in sorted(found.rules.items()))
|
|
sys.stderr.write(
|
|
f"notes: {len(found.links)} sessions linked ({rules}) "
|
|
f"in {time.monotonic() - started:.2f}s\n"
|
|
)
|
|
closed = [r for r in rows if r.status in TERMINAL and _in(r.done_at, window)]
|
|
return got, ["", *render(closed, got, found.rules)], rows
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
"""Rewrite notes.json from all transcripts, independent of any window."""
|
|
parser = argparse.ArgumentParser(prog="token_audit.notes")
|
|
parser.add_argument("--root", type=Path, default=notes_collect.TRANSCRIPTS)
|
|
parser.add_argument("--out", type=Path, default=history.REPORT_DIR)
|
|
args = parser.parse_args(argv)
|
|
found = notes_collect.collect(args.root, pricing.load(), [], float("inf"))
|
|
if found is not None:
|
|
written = write_json(note_rows(found.notes, found.links, found.spend), args.out)
|
|
sys.stderr.write(f"Wrote {written}\n")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|