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