Refactor exporter into modules

This commit is contained in:
2026-07-09 10:21:37 +00:00
parent dc8aa26893
commit 43be027b5d
9 changed files with 821 additions and 720 deletions
+135
View File
@@ -0,0 +1,135 @@
from __future__ import annotations
from collections import Counter, defaultdict
from dataclasses import dataclass, field
from .utils import as_float, as_int
COST_FIELDS = ("input", "output", "cacheRead", "cacheWrite", "total")
@dataclass
class Totals:
total: int = 0
input: int = 0
output: int = 0
cache_read: int = 0
cache_write: int = 0
reasoning: int = 0
entries: int = 0
cost_input: float = 0.0
cost_output: float = 0.0
cost_cache_read: float = 0.0
cost_cache_write: float = 0.0
cost_total: float = 0.0
def add_usage(self, usage: dict) -> tuple[int, int, int, int, int, int, dict[str, float]]:
total_tokens = as_int(usage.get("totalTokens"))
input_tokens = as_int(usage.get("input"))
output_tokens = as_int(usage.get("output"))
cache_read_tokens = as_int(usage.get("cacheRead"))
cache_write_tokens = as_int(usage.get("cacheWrite"))
reasoning_tokens = as_int(usage.get("reasoningTokens"))
self.entries += 1
self.total += total_tokens
self.input += input_tokens
self.output += output_tokens
self.cache_read += cache_read_tokens
self.cache_write += cache_write_tokens
self.reasoning += reasoning_tokens
skipped_negative_costs: dict[str, float] = {}
cost = usage.get("cost") or {}
if isinstance(cost, dict):
input_cost = as_float(cost.get("input"))
output_cost = as_float(cost.get("output"))
cache_read_cost = as_float(cost.get("cacheRead"))
cache_write_cost = as_float(cost.get("cacheWrite"))
total_cost = as_float(cost.get("total"))
if input_cost < 0:
skipped_negative_costs["input"] = input_cost
input_cost = 0.0
if output_cost < 0:
skipped_negative_costs["output"] = output_cost
output_cost = 0.0
if cache_read_cost < 0:
skipped_negative_costs["cacheRead"] = cache_read_cost
cache_read_cost = 0.0
if cache_write_cost < 0:
skipped_negative_costs["cacheWrite"] = cache_write_cost
cache_write_cost = 0.0
if total_cost < 0:
skipped_negative_costs["total"] = total_cost
total_cost = 0.0
self.cost_input += input_cost
self.cost_output += output_cost
self.cost_cache_read += cache_read_cost
self.cost_cache_write += cache_write_cost
self.cost_total += total_cost
return (
total_tokens,
input_tokens,
output_tokens,
cache_read_tokens,
cache_write_tokens,
reasoning_tokens,
skipped_negative_costs,
)
@dataclass
class UsageReport:
today: Totals = field(default_factory=Totals)
all_time: Totals = field(default_factory=Totals)
files_scanned: int = 0
lines_scanned: int = 0
parse_errors: int = 0
negative_cost_entries_skipped: int = 0
negative_cost_fields_skipped: Counter[str] = field(default_factory=Counter)
negative_cost_amounts_skipped: defaultdict[str, float] = field(default_factory=lambda: defaultdict(float))
message_records_today: int = 0
message_records_all_time: int = 0
non_message_records_today: int = 0
non_message_records_all_time: int = 0
unique_session_ids_today: set[str] = field(default_factory=set)
unique_models_today: set[str] = field(default_factory=set)
unique_providers_today: set[str] = field(default_factory=set)
role_counts_today: Counter[str] = field(default_factory=Counter)
stop_reason_counts_today: Counter[tuple[str, str, str]] = field(default_factory=Counter)
tool_call_counts_today: Counter[str] = field(default_factory=Counter)
error_type_counts_today: Counter[str] = field(default_factory=Counter)
session_message_counts_today: Counter[tuple[str, str]] = field(default_factory=Counter)
session_error_counts_today: Counter[str] = field(default_factory=Counter)
session_tool_call_counts_today: Counter[tuple[str, str]] = field(default_factory=Counter)
tokens_by_model_today: defaultdict[tuple[str, str], int] = field(default_factory=lambda: defaultdict(int))
input_tokens_by_model_today: defaultdict[tuple[str, str], int] = field(default_factory=lambda: defaultdict(int))
output_tokens_by_model_today: defaultdict[tuple[str, str], int] = field(default_factory=lambda: defaultdict(int))
reasoning_tokens_by_model_today: defaultdict[tuple[str, str], int] = field(default_factory=lambda: defaultdict(int))
session_tokens_today: defaultdict[str, int] = field(default_factory=lambda: defaultdict(int))
session_input_tokens_today: defaultdict[str, int] = field(default_factory=lambda: defaultdict(int))
session_output_tokens_today: defaultdict[str, int] = field(default_factory=lambda: defaultdict(int))
tokens_by_model_all_time: defaultdict[tuple[str, str], int] = field(default_factory=lambda: defaultdict(int))
input_tokens_by_model_all_time: defaultdict[tuple[str, str], int] = field(default_factory=lambda: defaultdict(int))
output_tokens_by_model_all_time: defaultdict[tuple[str, str], int] = field(default_factory=lambda: defaultdict(int))
session_tokens_all_time: defaultdict[str, int] = field(default_factory=lambda: defaultdict(int))
day_buckets: defaultdict[str, Totals] = field(default_factory=lambda: defaultdict(Totals))
hour_buckets: defaultdict[str, Totals] = field(default_factory=lambda: defaultdict(Totals))
@dataclass
class SessionIndexReport:
visible_count: int = 0
total_count: int = 0
has_more: bool = False
sessions: list[dict] = field(default_factory=list)
@dataclass
class UsageCostCacheReport:
version: int | None = None
updated_at_ms: int | None = None
files_tracked: int = 0