cctally 1.74.0 → 1.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/bin/_cctally_cache.py +9 -0
- package/bin/_cctally_config.py +3 -3
- package/bin/_cctally_core.py +12 -3
- package/bin/_cctally_dashboard.py +33 -15
- package/bin/_cctally_dashboard_conversation.py +2 -2
- package/bin/_cctally_dashboard_sources.py +401 -18
- package/bin/_cctally_db.py +694 -1
- package/bin/_cctally_parser.py +100 -6
- package/bin/_cctally_percent_breakdown.py +59 -34
- package/bin/_cctally_quota.py +311 -45
- package/bin/_cctally_record.py +52 -1
- package/bin/_cctally_refresh.py +26 -0
- package/bin/_cctally_statusline.py +3 -3
- package/bin/_cctally_tui.py +3 -0
- package/bin/_lib_aggregators.py +7 -0
- package/bin/_lib_cache_report.py +25 -9
- package/bin/_lib_conversation_retention.py +22 -2
- package/bin/_lib_doctor.py +4 -4
- package/bin/cctally +27 -1
- package/dashboard/static/assets/index-fZRxsSf5.js +80 -0
- package/dashboard/static/assets/{index-ZiPO8veo.css → index-yftBNnLR.css} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +1 -1
- package/dashboard/static/assets/index-DeQjEMO6.js +0 -80
|
@@ -9,12 +9,14 @@ import sqlite3
|
|
|
9
9
|
import sys
|
|
10
10
|
from collections.abc import Mapping
|
|
11
11
|
from dataclasses import dataclass, replace
|
|
12
|
-
from types import MappingProxyType
|
|
12
|
+
from types import MappingProxyType, SimpleNamespace
|
|
13
13
|
from typing import Any, Iterable
|
|
14
14
|
from zoneinfo import ZoneInfo
|
|
15
15
|
|
|
16
16
|
from _cctally_core import get_week_start_name
|
|
17
17
|
from _cctally_quota import (
|
|
18
|
+
codex_five_hour_percent_at_crossing,
|
|
19
|
+
codex_quota_breakdown,
|
|
18
20
|
codex_physical_mutation_seq,
|
|
19
21
|
load_codex_quota_observations,
|
|
20
22
|
load_codex_quota_projection_certificate,
|
|
@@ -88,6 +90,7 @@ class CodexCycleBoundary:
|
|
|
88
90
|
resets_at: dt.datetime
|
|
89
91
|
# Root provenance is server-only accounting input, never public wire data.
|
|
90
92
|
source_root_keys: tuple[str, ...]
|
|
93
|
+
used_percent: float | None = None
|
|
91
94
|
|
|
92
95
|
|
|
93
96
|
@dataclass(frozen=True)
|
|
@@ -97,6 +100,7 @@ class CodexWeeklyPeriod:
|
|
|
97
100
|
start_at: dt.datetime
|
|
98
101
|
end_at: dt.datetime
|
|
99
102
|
source_root_keys: tuple[str, ...]
|
|
103
|
+
used_percent: float | None = None
|
|
100
104
|
|
|
101
105
|
|
|
102
106
|
def _resolve_codex_weekly_cycle(
|
|
@@ -104,7 +108,7 @@ def _resolve_codex_weekly_cycle(
|
|
|
104
108
|
now_utc: dt.datetime,
|
|
105
109
|
) -> CodexCycleBoundary:
|
|
106
110
|
"""Select exactly one active account-level 10,080-minute native cycle."""
|
|
107
|
-
boundaries: dict[tuple[int, dt.datetime],
|
|
111
|
+
boundaries: dict[tuple[int, dt.datetime], dict[str, object]] = {}
|
|
108
112
|
stale_weekly_evidence = False
|
|
109
113
|
for history in build_history(tuple(observations)):
|
|
110
114
|
if history.identity.window_minutes != 10_080:
|
|
@@ -116,17 +120,23 @@ def _resolve_codex_weekly_cycle(
|
|
|
116
120
|
stale_weekly_evidence = True
|
|
117
121
|
continue
|
|
118
122
|
boundary = (history.identity.window_minutes, baseline.resets_at)
|
|
119
|
-
boundaries.setdefault(boundary, set())
|
|
123
|
+
item = boundaries.setdefault(boundary, {"roots": set(), "used": []})
|
|
124
|
+
item["roots"].add(history.identity.source_root_key)
|
|
125
|
+
if baseline.used_percent is not None:
|
|
126
|
+
item["used"].append(float(baseline.used_percent))
|
|
120
127
|
if len(boundaries) != 1:
|
|
121
128
|
if not boundaries:
|
|
122
129
|
raise CodexCycleUnavailable("stale" if stale_weekly_evidence else "missing")
|
|
123
130
|
raise CodexCycleUnavailable("conflicting")
|
|
124
|
-
(window_minutes, resets_at),
|
|
131
|
+
(window_minutes, resets_at), boundary_data = next(iter(boundaries.items()))
|
|
132
|
+
root_keys = boundary_data["roots"]
|
|
133
|
+
used_values = boundary_data["used"]
|
|
125
134
|
return CodexCycleBoundary(
|
|
126
135
|
window_minutes=window_minutes,
|
|
127
136
|
start_at=resets_at - dt.timedelta(minutes=window_minutes),
|
|
128
137
|
resets_at=resets_at,
|
|
129
138
|
source_root_keys=tuple(sorted(root_keys)),
|
|
139
|
+
used_percent=max(used_values) if used_values else None,
|
|
130
140
|
)
|
|
131
141
|
|
|
132
142
|
|
|
@@ -151,7 +161,7 @@ def _codex_weekly_periods(
|
|
|
151
161
|
placeholders = ",".join("?" for _ in roots)
|
|
152
162
|
try:
|
|
153
163
|
rows = stats_conn.execute(
|
|
154
|
-
"SELECT source_root_key, resets_at_utc, nominal_start_at_utc "
|
|
164
|
+
"SELECT source_root_key, resets_at_utc, nominal_start_at_utc, current_percent "
|
|
155
165
|
"FROM quota_window_blocks "
|
|
156
166
|
"WHERE source='codex' AND window_minutes=10080 "
|
|
157
167
|
f"AND source_root_key IN ({placeholders}) AND orphaned_at IS NULL "
|
|
@@ -162,9 +172,9 @@ def _codex_weekly_periods(
|
|
|
162
172
|
except sqlite3.Error:
|
|
163
173
|
rows = ()
|
|
164
174
|
|
|
165
|
-
raw_boundaries: list[tuple[dt.datetime, dt.datetime, set[str]]] = []
|
|
175
|
+
raw_boundaries: list[tuple[dt.datetime, dt.datetime, set[str], list[float]]] = []
|
|
166
176
|
|
|
167
|
-
for root_key, resets_at_raw, start_at_raw in rows:
|
|
177
|
+
for root_key, resets_at_raw, start_at_raw, current_percent in rows:
|
|
168
178
|
try:
|
|
169
179
|
start_at = dt.datetime.fromisoformat(str(start_at_raw).replace("Z", "+00:00"))
|
|
170
180
|
resets_at = dt.datetime.fromisoformat(str(resets_at_raw).replace("Z", "+00:00"))
|
|
@@ -176,17 +186,21 @@ def _codex_weekly_periods(
|
|
|
176
186
|
resets_at = resets_at.astimezone(UTC)
|
|
177
187
|
if resets_at <= start_at:
|
|
178
188
|
continue
|
|
179
|
-
|
|
189
|
+
used_values = []
|
|
190
|
+
if isinstance(current_percent, (int, float)) and not isinstance(current_percent, bool):
|
|
191
|
+
used_values.append(float(current_percent))
|
|
192
|
+
raw_boundaries.append((start_at, resets_at, {str(root_key)}, used_values))
|
|
180
193
|
|
|
181
194
|
if active_cycle is not None:
|
|
182
195
|
raw_boundaries.append((
|
|
183
196
|
active_cycle.start_at.astimezone(UTC),
|
|
184
197
|
active_cycle.resets_at.astimezone(UTC),
|
|
185
198
|
set(active_cycle.source_root_keys),
|
|
199
|
+
[active_cycle.used_percent] if active_cycle.used_percent is not None else [],
|
|
186
200
|
))
|
|
187
201
|
|
|
188
|
-
ordered: list[tuple[dt.datetime, dt.datetime, set[str]]] = []
|
|
189
|
-
for start_at, resets_at, period_roots in sorted(
|
|
202
|
+
ordered: list[tuple[dt.datetime, dt.datetime, set[str], list[float]]] = []
|
|
203
|
+
for start_at, resets_at, period_roots, used_values in sorted(
|
|
190
204
|
raw_boundaries, key=lambda item: (item[0], item[1]),
|
|
191
205
|
):
|
|
192
206
|
if (
|
|
@@ -194,13 +208,16 @@ def _codex_weekly_periods(
|
|
|
194
208
|
and (start_at - ordered[-1][0]).total_seconds()
|
|
195
209
|
< _FIVE_HOUR_JITTER_FLOOR_SECONDS
|
|
196
210
|
):
|
|
197
|
-
first_start, latest_reset, existing_roots = ordered[-1]
|
|
211
|
+
first_start, latest_reset, existing_roots, existing_used = ordered[-1]
|
|
198
212
|
existing_roots.update(period_roots)
|
|
199
|
-
|
|
213
|
+
existing_used.extend(used_values)
|
|
214
|
+
ordered[-1] = (
|
|
215
|
+
first_start, max(latest_reset, resets_at), existing_roots, existing_used,
|
|
216
|
+
)
|
|
200
217
|
else:
|
|
201
|
-
ordered.append((start_at, resets_at, set(period_roots)))
|
|
218
|
+
ordered.append((start_at, resets_at, set(period_roots), list(used_values)))
|
|
202
219
|
periods: list[CodexWeeklyPeriod] = []
|
|
203
|
-
for index, (start_at, resets_at, period_roots) in enumerate(ordered):
|
|
220
|
+
for index, (start_at, resets_at, period_roots, used_values) in enumerate(ordered):
|
|
204
221
|
next_start = ordered[index + 1][0] if index + 1 < len(ordered) else None
|
|
205
222
|
end_at = min(resets_at, next_start) if next_start is not None else resets_at
|
|
206
223
|
if end_at <= start_at:
|
|
@@ -209,6 +226,7 @@ def _codex_weekly_periods(
|
|
|
209
226
|
start_at=start_at,
|
|
210
227
|
end_at=end_at,
|
|
211
228
|
source_root_keys=tuple(sorted(period_roots)),
|
|
229
|
+
used_percent=max(used_values) if used_values else None,
|
|
212
230
|
))
|
|
213
231
|
return tuple(periods)
|
|
214
232
|
|
|
@@ -245,6 +263,9 @@ class DashboardSourceSemantics:
|
|
|
245
263
|
week_start_idx: int
|
|
246
264
|
speed: str
|
|
247
265
|
codex_budget: Mapping[str, object] | None
|
|
266
|
+
codex_quota_actual_thresholds: tuple[int, ...]
|
|
267
|
+
codex_quota_projected_thresholds: tuple[int, ...]
|
|
268
|
+
cache_report_anomaly_threshold_pp: int
|
|
248
269
|
claude_identity: str
|
|
249
270
|
codex_identity: str
|
|
250
271
|
|
|
@@ -267,6 +288,17 @@ def resolve_dashboard_source_semantics(
|
|
|
267
288
|
week_start_idx = c.WEEKDAY_MAP[week_start_name]
|
|
268
289
|
speed = c._resolve_codex_speed("auto")
|
|
269
290
|
budget_config = c._get_budget_config(raw_config)
|
|
291
|
+
quota_alerts = c._get_quota_alerts_config(raw_config)
|
|
292
|
+
raw_cache_report = raw_config.get("cache_report")
|
|
293
|
+
raw_cache_threshold = (
|
|
294
|
+
raw_cache_report.get("anomaly_threshold_pp", 15)
|
|
295
|
+
if isinstance(raw_cache_report, Mapping) else 15
|
|
296
|
+
)
|
|
297
|
+
cache_threshold = (
|
|
298
|
+
int(raw_cache_threshold)
|
|
299
|
+
if isinstance(raw_cache_threshold, int) and not isinstance(raw_cache_threshold, bool)
|
|
300
|
+
and 1 <= raw_cache_threshold <= 100 else 15
|
|
301
|
+
)
|
|
270
302
|
raw_codex_budget = budget_config.get("codex")
|
|
271
303
|
codex_budget = (
|
|
272
304
|
MappingProxyType(dict(raw_codex_budget))
|
|
@@ -290,6 +322,8 @@ def resolve_dashboard_source_semantics(
|
|
|
290
322
|
}
|
|
291
323
|
codex_identity_payload = {
|
|
292
324
|
"codex_budget": dict(codex_budget) if codex_budget is not None else None,
|
|
325
|
+
"codex_quota_alerts": quota_alerts,
|
|
326
|
+
"cache_report_anomaly_threshold_pp": cache_threshold,
|
|
293
327
|
"display_tz_name": display_tz_name,
|
|
294
328
|
"speed": speed,
|
|
295
329
|
"week_start_name": week_start_name,
|
|
@@ -306,6 +340,9 @@ def resolve_dashboard_source_semantics(
|
|
|
306
340
|
week_start_idx=week_start_idx,
|
|
307
341
|
speed=speed,
|
|
308
342
|
codex_budget=codex_budget,
|
|
343
|
+
codex_quota_actual_thresholds=tuple(quota_alerts["actual_thresholds"]),
|
|
344
|
+
codex_quota_projected_thresholds=tuple(quota_alerts["projected_thresholds"]),
|
|
345
|
+
cache_report_anomaly_threshold_pp=cache_threshold,
|
|
309
346
|
claude_identity=claude_identity,
|
|
310
347
|
codex_identity=codex_identity,
|
|
311
348
|
)
|
|
@@ -324,6 +361,9 @@ class DashboardReadContext:
|
|
|
324
361
|
week_start_name: str = "monday"
|
|
325
362
|
speed: str = "standard"
|
|
326
363
|
codex_budget: Mapping[str, object] | None = None
|
|
364
|
+
codex_quota_actual_thresholds: tuple[int, ...] = ()
|
|
365
|
+
codex_quota_projected_thresholds: tuple[int, ...] = ()
|
|
366
|
+
cache_report_anomaly_threshold_pp: int = 15
|
|
327
367
|
|
|
328
368
|
def __post_init__(self) -> None:
|
|
329
369
|
for name in ("range_start", "now_utc"):
|
|
@@ -453,7 +493,7 @@ def _codex_budget_cost_events(
|
|
|
453
493
|
|
|
454
494
|
|
|
455
495
|
def _bucket_wire(bucket: Any) -> dict[str, object]:
|
|
456
|
-
|
|
496
|
+
result = {
|
|
457
497
|
"label": bucket.bucket,
|
|
458
498
|
"cost_usd": bucket.cost_usd,
|
|
459
499
|
"input_tokens": bucket.input_tokens,
|
|
@@ -464,6 +504,15 @@ def _bucket_wire(bucket: Any) -> dict[str, object]:
|
|
|
464
504
|
"models": tuple(bucket.models),
|
|
465
505
|
"model_breakdowns": tuple(dict(row) for row in bucket.model_breakdowns),
|
|
466
506
|
}
|
|
507
|
+
for name in ("period_start_at", "period_end_at"):
|
|
508
|
+
value = getattr(bucket, name, None)
|
|
509
|
+
if isinstance(value, dt.datetime):
|
|
510
|
+
result[name.replace("period_", "")] = value.astimezone(UTC).isoformat()
|
|
511
|
+
for name in ("used_pct", "dollar_per_pct"):
|
|
512
|
+
value = getattr(bucket, name, None)
|
|
513
|
+
if value is not None:
|
|
514
|
+
result[name] = value
|
|
515
|
+
return result
|
|
467
516
|
|
|
468
517
|
|
|
469
518
|
def _period_wire(view: Any) -> dict[str, object]:
|
|
@@ -475,6 +524,188 @@ def _period_wire(view: Any) -> dict[str, object]:
|
|
|
475
524
|
}
|
|
476
525
|
|
|
477
526
|
|
|
527
|
+
def _codex_cache_report_wire(
|
|
528
|
+
entries: Iterable[object],
|
|
529
|
+
*,
|
|
530
|
+
metadata: Mapping[tuple[str, str], Mapping[str, object]],
|
|
531
|
+
now_utc: dt.datetime,
|
|
532
|
+
display_tz_name: str | None,
|
|
533
|
+
speed: str,
|
|
534
|
+
anomaly_threshold_pp: int = 15,
|
|
535
|
+
window_days: int = 14,
|
|
536
|
+
) -> dict[str, object]:
|
|
537
|
+
"""Compute the canonical cache report from Codex's inclusive counters.
|
|
538
|
+
|
|
539
|
+
Codex input is cache-inclusive, so the shared cache-report kernel receives
|
|
540
|
+
uncached input plus cached input as two disjoint counters. OpenAI does not
|
|
541
|
+
charge a cache-write premium; the counterfactual is therefore the exact
|
|
542
|
+
uncached-vs-cached input price difference for each token-count event.
|
|
543
|
+
"""
|
|
544
|
+
c = sys.modules["cctally"]
|
|
545
|
+
crk = c._load_sibling("_lib_cache_report")
|
|
546
|
+
display_tz = ZoneInfo(display_tz_name) if display_tz_name else None
|
|
547
|
+
cutoff = now_utc - dt.timedelta(days=window_days)
|
|
548
|
+
|
|
549
|
+
def _tiered_cost(tokens: int, pricing: Mapping[str, object], base: str, above: str) -> float:
|
|
550
|
+
if tokens <= 0:
|
|
551
|
+
return 0.0
|
|
552
|
+
base_rate = float(pricing.get(base, 0.0) or 0.0)
|
|
553
|
+
above_rate = pricing.get(above)
|
|
554
|
+
threshold = int(c.CODEX_TIERED_THRESHOLD)
|
|
555
|
+
if tokens > threshold and above_rate is not None:
|
|
556
|
+
return threshold * base_rate + (tokens - threshold) * float(above_rate)
|
|
557
|
+
return tokens * base_rate
|
|
558
|
+
|
|
559
|
+
wrapped = []
|
|
560
|
+
for entry in entries:
|
|
561
|
+
timestamp = getattr(entry, "timestamp", None)
|
|
562
|
+
if not isinstance(timestamp, dt.datetime) or timestamp < cutoff:
|
|
563
|
+
continue
|
|
564
|
+
model = str(getattr(entry, "model", "") or "unknown")
|
|
565
|
+
input_tokens = int(getattr(entry, "input_tokens", 0))
|
|
566
|
+
cached_tokens = min(input_tokens, int(getattr(entry, "cached_input_tokens", 0)))
|
|
567
|
+
uncached_tokens = max(0, input_tokens - cached_tokens)
|
|
568
|
+
pricing, _is_fallback = c._resolve_codex_pricing(model)
|
|
569
|
+
pricing = pricing or {}
|
|
570
|
+
uncached_counterfactual = _tiered_cost(
|
|
571
|
+
cached_tokens, pricing,
|
|
572
|
+
"input_cost_per_token", "input_cost_per_token_above_272k_tokens",
|
|
573
|
+
)
|
|
574
|
+
cached_actual = _tiered_cost(
|
|
575
|
+
cached_tokens, pricing,
|
|
576
|
+
"cache_read_input_token_cost", "cache_read_input_token_cost_above_272k_tokens",
|
|
577
|
+
)
|
|
578
|
+
multiplier = c._codex_fast_multiplier(model) if speed == "fast" else 1.0
|
|
579
|
+
saved = max(0.0, uncached_counterfactual - cached_actual) * multiplier
|
|
580
|
+
identity = (
|
|
581
|
+
str(getattr(entry, "source_root_key", "") or ""),
|
|
582
|
+
str(getattr(entry, "source_path", "") or ""),
|
|
583
|
+
)
|
|
584
|
+
item_metadata = metadata.get(identity) or {}
|
|
585
|
+
project = (
|
|
586
|
+
str(getattr(entry, "project_label", "") or "").strip()
|
|
587
|
+
or str(item_metadata.get("project_label") or "").strip()
|
|
588
|
+
or "(unknown)"
|
|
589
|
+
)
|
|
590
|
+
wrapped.append(SimpleNamespace(
|
|
591
|
+
timestamp=timestamp,
|
|
592
|
+
model=model,
|
|
593
|
+
cost_usd=float(getattr(entry, "cost_usd", 0.0)),
|
|
594
|
+
project_path=project,
|
|
595
|
+
input_tokens=uncached_tokens,
|
|
596
|
+
output_tokens=int(getattr(entry, "output_tokens", 0)),
|
|
597
|
+
cache_creation_tokens=0,
|
|
598
|
+
cache_read_tokens=cached_tokens,
|
|
599
|
+
cache_saved_usd=saved,
|
|
600
|
+
cache_wasted_usd=0.0,
|
|
601
|
+
cache_net_usd=saved,
|
|
602
|
+
usage={
|
|
603
|
+
"input_tokens": uncached_tokens,
|
|
604
|
+
"output_tokens": int(getattr(entry, "output_tokens", 0)),
|
|
605
|
+
"cache_creation_input_tokens": 0,
|
|
606
|
+
"cache_read_input_tokens": cached_tokens,
|
|
607
|
+
},
|
|
608
|
+
))
|
|
609
|
+
|
|
610
|
+
today_iso = now_utc.astimezone(display_tz or UTC).strftime("%Y-%m-%d")
|
|
611
|
+
if not wrapped:
|
|
612
|
+
return {
|
|
613
|
+
"window_days": window_days,
|
|
614
|
+
"anomaly_threshold_pp": anomaly_threshold_pp,
|
|
615
|
+
"anomaly_window_days": window_days,
|
|
616
|
+
"today": {
|
|
617
|
+
"date": today_iso, "cache_hit_percent": 0.0,
|
|
618
|
+
"baseline_median_percent": None, "delta_pp": None,
|
|
619
|
+
"net_usd": 0.0, "saved_usd": 0.0, "wasted_usd": 0.0,
|
|
620
|
+
"anomaly_triggered": False, "anomaly_reasons": (),
|
|
621
|
+
"baseline_daily_row_count": 0,
|
|
622
|
+
},
|
|
623
|
+
"days": (), "by_project": (), "by_model": (),
|
|
624
|
+
"seven_day_net_usd": 0.0, "seven_day_anomaly_count": 0,
|
|
625
|
+
"fourteen_day_counterfactual_usd": 0.0,
|
|
626
|
+
"fourteen_day_efficiency_ratio": 0.0, "is_empty": True,
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
result = crk._build_cache_report(
|
|
630
|
+
wrapped,
|
|
631
|
+
now_utc=now_utc,
|
|
632
|
+
window_days=window_days,
|
|
633
|
+
anomaly_threshold_pp=anomaly_threshold_pp,
|
|
634
|
+
anomaly_window_days=window_days,
|
|
635
|
+
display_tz=display_tz,
|
|
636
|
+
pricing=c.CODEX_MODEL_PRICING,
|
|
637
|
+
cost_calculator=lambda _model, _usage, _mode, cost: float(cost or 0.0),
|
|
638
|
+
)
|
|
639
|
+
raw_rows = sorted(result.rows, key=lambda row: row.date or "", reverse=True)
|
|
640
|
+
days = tuple({
|
|
641
|
+
"date": row.date or "",
|
|
642
|
+
"cache_hit_percent": row.cache_hit_percent,
|
|
643
|
+
"input_tokens": row.input_tokens,
|
|
644
|
+
"output_tokens": row.output_tokens,
|
|
645
|
+
"cache_creation_tokens": row.cache_creation_tokens,
|
|
646
|
+
"cache_read_tokens": row.cache_read_tokens,
|
|
647
|
+
"saved_usd": row.saved_usd,
|
|
648
|
+
"wasted_usd": row.wasted_usd,
|
|
649
|
+
"net_usd": row.net_usd,
|
|
650
|
+
"anomaly_triggered": row.anomaly_triggered,
|
|
651
|
+
"anomaly_reasons": tuple(row.anomaly_reasons),
|
|
652
|
+
} for row in raw_rows[:window_days])
|
|
653
|
+
today_row = next((row for row in raw_rows if row.date == today_iso), None)
|
|
654
|
+
baseline_count = sum(1 for row in raw_rows if row.date != today_iso)
|
|
655
|
+
baseline = result.today_baseline_median
|
|
656
|
+
today_hit = today_row.cache_hit_percent if today_row else 0.0
|
|
657
|
+
kept_dates = {row["date"] for row in days}
|
|
658
|
+
kept_entries = [
|
|
659
|
+
entry for entry in wrapped
|
|
660
|
+
if entry.timestamp.astimezone(display_tz or UTC).strftime("%Y-%m-%d") in kept_dates
|
|
661
|
+
]
|
|
662
|
+
by_project = crk._aggregate_cache_breakdown(
|
|
663
|
+
kept_entries, key_fn=lambda entry: entry.project_path,
|
|
664
|
+
pricing=c.CODEX_MODEL_PRICING,
|
|
665
|
+
)
|
|
666
|
+
by_model = crk._aggregate_cache_breakdown(
|
|
667
|
+
kept_entries, key_fn=lambda entry: entry.model,
|
|
668
|
+
pricing=c.CODEX_MODEL_PRICING,
|
|
669
|
+
)
|
|
670
|
+
seven = days[:7]
|
|
671
|
+
saved_total = stable_sum(float(row["saved_usd"]) for row in days)
|
|
672
|
+
wasted_total = stable_sum(float(row["wasted_usd"]) for row in days)
|
|
673
|
+
efficiency_denom = saved_total + abs(wasted_total)
|
|
674
|
+
return {
|
|
675
|
+
"window_days": window_days,
|
|
676
|
+
"anomaly_threshold_pp": anomaly_threshold_pp,
|
|
677
|
+
"anomaly_window_days": window_days,
|
|
678
|
+
"today": {
|
|
679
|
+
"date": today_iso,
|
|
680
|
+
"cache_hit_percent": today_hit,
|
|
681
|
+
"baseline_median_percent": baseline,
|
|
682
|
+
"delta_pp": today_hit - baseline if baseline is not None else None,
|
|
683
|
+
"net_usd": today_row.net_usd if today_row else 0.0,
|
|
684
|
+
"saved_usd": today_row.saved_usd if today_row else 0.0,
|
|
685
|
+
"wasted_usd": today_row.wasted_usd if today_row else 0.0,
|
|
686
|
+
"anomaly_triggered": today_row.anomaly_triggered if today_row else False,
|
|
687
|
+
"anomaly_reasons": tuple(today_row.anomaly_reasons) if today_row else (),
|
|
688
|
+
"baseline_daily_row_count": baseline_count,
|
|
689
|
+
},
|
|
690
|
+
"days": days,
|
|
691
|
+
"by_project": tuple({
|
|
692
|
+
"key": row.key, "cache_hit_percent": row.cache_hit_percent,
|
|
693
|
+
"net_usd": row.net_usd,
|
|
694
|
+
} for row in by_project),
|
|
695
|
+
"by_model": tuple({
|
|
696
|
+
"key": row.key, "cache_hit_percent": row.cache_hit_percent,
|
|
697
|
+
"net_usd": row.net_usd,
|
|
698
|
+
} for row in by_model),
|
|
699
|
+
"seven_day_net_usd": stable_sum(float(row["net_usd"]) for row in seven),
|
|
700
|
+
"seven_day_anomaly_count": sum(bool(row["anomaly_triggered"]) for row in seven),
|
|
701
|
+
"fourteen_day_counterfactual_usd": saved_total,
|
|
702
|
+
"fourteen_day_efficiency_ratio": (
|
|
703
|
+
saved_total / efficiency_denom if efficiency_denom > 1e-9 else 0.0
|
|
704
|
+
),
|
|
705
|
+
"is_empty": False,
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
|
|
478
709
|
def _codex_conversation_metadata(
|
|
479
710
|
cache_conn: sqlite3.Connection,
|
|
480
711
|
) -> dict[tuple[str, str], dict[str, object]]:
|
|
@@ -656,6 +887,9 @@ def _session_wire(
|
|
|
656
887
|
"reasoning_output_tokens": row.reasoning_output_tokens,
|
|
657
888
|
"total_tokens": row.total_tokens,
|
|
658
889
|
"models": tuple(row.models),
|
|
890
|
+
"model_breakdowns": tuple(
|
|
891
|
+
dict(item) for item in getattr(row, "model_breakdowns", ())
|
|
892
|
+
),
|
|
659
893
|
})
|
|
660
894
|
return {
|
|
661
895
|
"rows": tuple(rows),
|
|
@@ -893,9 +1127,12 @@ def _configured_codex_budget_window(
|
|
|
893
1127
|
def _quota_read_model(
|
|
894
1128
|
context: DashboardReadContext,
|
|
895
1129
|
observations: Iterable[object],
|
|
1130
|
+
*,
|
|
1131
|
+
accounting_entries: Iterable[object] = (),
|
|
896
1132
|
) -> dict[str, object]:
|
|
897
1133
|
"""Use S2's pure history/block/forecast kernels over cache evidence."""
|
|
898
1134
|
quota_observations = tuple(observations)
|
|
1135
|
+
cost_entries = tuple(accounting_entries)
|
|
899
1136
|
histories = build_history(quota_observations)
|
|
900
1137
|
blocks = build_blocks(quota_observations)
|
|
901
1138
|
history_rows: list[dict[str, object]] = []
|
|
@@ -955,7 +1192,82 @@ def _quota_read_model(
|
|
|
955
1192
|
identity.window_minutes,
|
|
956
1193
|
block.resets_at.astimezone(UTC).isoformat(),
|
|
957
1194
|
)
|
|
1195
|
+
quota_key = dashboard_resource_key(
|
|
1196
|
+
"quota", "codex", identity.source_root_key,
|
|
1197
|
+
identity.logical_limit_key, identity.observed_slot,
|
|
1198
|
+
identity.window_minutes,
|
|
1199
|
+
)
|
|
1200
|
+
block_cost_entries = tuple(
|
|
1201
|
+
entry for entry in cost_entries
|
|
1202
|
+
if str(getattr(entry, "source_root_key", "")) == identity.source_root_key
|
|
1203
|
+
and block.nominal_start_at
|
|
1204
|
+
<= getattr(entry, "timestamp").astimezone(UTC)
|
|
1205
|
+
< block.resets_at
|
|
1206
|
+
)
|
|
1207
|
+
canonical_rows = ()
|
|
1208
|
+
if identity.window_minutes == 10_080 and block.resets_at > context.now_utc:
|
|
1209
|
+
try:
|
|
1210
|
+
canonical_rows = codex_quota_breakdown(
|
|
1211
|
+
identity,
|
|
1212
|
+
block.resets_at,
|
|
1213
|
+
speed=context.speed,
|
|
1214
|
+
cache_conn=context.cache_conn,
|
|
1215
|
+
stats_conn=context.stats_conn,
|
|
1216
|
+
)
|
|
1217
|
+
except sqlite3.Error:
|
|
1218
|
+
# Older or partially migrated stores retain the bounded
|
|
1219
|
+
# observation-derived fallback below. A coherent current
|
|
1220
|
+
# store always has the durable projection used by the CLI.
|
|
1221
|
+
canonical_rows = ()
|
|
1222
|
+
if canonical_rows:
|
|
1223
|
+
try:
|
|
1224
|
+
correlated_five_hour = tuple(
|
|
1225
|
+
observation
|
|
1226
|
+
for observation in load_codex_quota_observations(
|
|
1227
|
+
source_root_keys={identity.source_root_key},
|
|
1228
|
+
cache_conn=context.cache_conn,
|
|
1229
|
+
captured_at_or_after=block.nominal_start_at,
|
|
1230
|
+
)
|
|
1231
|
+
if observation.identity.window_minutes == 300
|
|
1232
|
+
and observation.identity.observed_slot == identity.observed_slot
|
|
1233
|
+
and observation.identity.limit_id == identity.limit_id
|
|
1234
|
+
)
|
|
1235
|
+
except sqlite3.Error:
|
|
1236
|
+
correlated_five_hour = ()
|
|
1237
|
+
|
|
1238
|
+
for row in canonical_rows:
|
|
1239
|
+
milestone_rows.append({
|
|
1240
|
+
"key": dashboard_resource_key(
|
|
1241
|
+
"quota_milestone", "codex", *block_parts,
|
|
1242
|
+
row.percent, row.captured_at.astimezone(UTC).isoformat(),
|
|
1243
|
+
),
|
|
1244
|
+
"source": "codex",
|
|
1245
|
+
"block_key": dashboard_resource_key("block", "codex", *block_parts),
|
|
1246
|
+
"quota_key": quota_key,
|
|
1247
|
+
"window_minutes": identity.window_minutes,
|
|
1248
|
+
"resets_at": block.resets_at.astimezone(UTC).isoformat(),
|
|
1249
|
+
"percent": row.percent,
|
|
1250
|
+
"captured_at": row.captured_at.astimezone(UTC).isoformat(),
|
|
1251
|
+
"cumulative_usd": row.cost_usd,
|
|
1252
|
+
"marginal_usd": row.marginal_cost_usd,
|
|
1253
|
+
"input_tokens": row.input_tokens,
|
|
1254
|
+
"cached_input_tokens": row.cached_input_tokens,
|
|
1255
|
+
"output_tokens": row.output_tokens,
|
|
1256
|
+
"reasoning_output_tokens": row.reasoning_output_tokens,
|
|
1257
|
+
"total_tokens": row.total_tokens,
|
|
1258
|
+
"five_hour_percent": codex_five_hour_percent_at_crossing(
|
|
1259
|
+
identity, row.captured_at, correlated_five_hour,
|
|
1260
|
+
),
|
|
1261
|
+
})
|
|
1262
|
+
continue
|
|
1263
|
+
|
|
1264
|
+
previous_cumulative = 0.0
|
|
958
1265
|
for milestone in percent_milestones(block):
|
|
1266
|
+
cumulative_usd = stable_sum(
|
|
1267
|
+
float(getattr(entry, "cost_usd", 0.0))
|
|
1268
|
+
for entry in block_cost_entries
|
|
1269
|
+
if getattr(entry, "timestamp").astimezone(UTC) <= milestone.captured_at
|
|
1270
|
+
)
|
|
959
1271
|
milestone_rows.append({
|
|
960
1272
|
"key": dashboard_resource_key(
|
|
961
1273
|
"quota_milestone", "codex", *block_parts,
|
|
@@ -963,9 +1275,15 @@ def _quota_read_model(
|
|
|
963
1275
|
),
|
|
964
1276
|
"source": "codex",
|
|
965
1277
|
"block_key": dashboard_resource_key("block", "codex", *block_parts),
|
|
1278
|
+
"quota_key": quota_key,
|
|
1279
|
+
"window_minutes": identity.window_minutes,
|
|
1280
|
+
"resets_at": block.resets_at.astimezone(UTC).isoformat(),
|
|
966
1281
|
"percent": milestone.percent,
|
|
967
1282
|
"captured_at": milestone.captured_at.astimezone(UTC).isoformat(),
|
|
1283
|
+
"cumulative_usd": cumulative_usd,
|
|
1284
|
+
"marginal_usd": max(0.0, cumulative_usd - previous_cumulative),
|
|
968
1285
|
})
|
|
1286
|
+
previous_cumulative = cumulative_usd
|
|
969
1287
|
latest_percent = max(
|
|
970
1288
|
(float(row["current_percent"]) for row in active_rows), default=None,
|
|
971
1289
|
)
|
|
@@ -1400,6 +1718,8 @@ def _partial_projects_wire(
|
|
|
1400
1718
|
"output_tokens": 0,
|
|
1401
1719
|
"reasoning_output_tokens": 0,
|
|
1402
1720
|
"total_tokens": 0,
|
|
1721
|
+
"models": {},
|
|
1722
|
+
"session_rows": {},
|
|
1403
1723
|
})
|
|
1404
1724
|
timestamp = getattr(entry, "timestamp")
|
|
1405
1725
|
group["first_seen"] = min(group["first_seen"], timestamp)
|
|
@@ -1410,6 +1730,27 @@ def _partial_projects_wire(
|
|
|
1410
1730
|
"reasoning_output_tokens", "total_tokens",
|
|
1411
1731
|
):
|
|
1412
1732
|
group[field] += getattr(entry, field)
|
|
1733
|
+
model = str(getattr(entry, "model", "") or "unknown")
|
|
1734
|
+
model_totals = group["models"].setdefault(model, {
|
|
1735
|
+
"model": model, "cost_usd": 0.0, "input_tokens": 0,
|
|
1736
|
+
"cached_input_tokens": 0, "output_tokens": 0,
|
|
1737
|
+
"reasoning_output_tokens": 0, "total_tokens": 0,
|
|
1738
|
+
})
|
|
1739
|
+
session_totals = group["session_rows"].setdefault(identity, {
|
|
1740
|
+
"label": str(row_metadata.get("title") or "Session"),
|
|
1741
|
+
"last_activity": timestamp.astimezone(UTC).isoformat(),
|
|
1742
|
+
"cost_usd": 0.0, "input_tokens": 0, "cached_input_tokens": 0,
|
|
1743
|
+
"output_tokens": 0, "reasoning_output_tokens": 0, "total_tokens": 0,
|
|
1744
|
+
})
|
|
1745
|
+
if timestamp.astimezone(UTC).isoformat() > session_totals["last_activity"]:
|
|
1746
|
+
session_totals["last_activity"] = timestamp.astimezone(UTC).isoformat()
|
|
1747
|
+
for field in (
|
|
1748
|
+
"cost_usd", "input_tokens", "cached_input_tokens", "output_tokens",
|
|
1749
|
+
"reasoning_output_tokens", "total_tokens",
|
|
1750
|
+
):
|
|
1751
|
+
value = getattr(entry, field)
|
|
1752
|
+
model_totals[field] += value
|
|
1753
|
+
session_totals[field] += value
|
|
1413
1754
|
|
|
1414
1755
|
rows = []
|
|
1415
1756
|
for (root_key, _project_key), group in groups.items():
|
|
@@ -1426,6 +1767,14 @@ def _partial_projects_wire(
|
|
|
1426
1767
|
"output_tokens": group["output_tokens"],
|
|
1427
1768
|
"reasoning_output_tokens": group["reasoning_output_tokens"],
|
|
1428
1769
|
"total_tokens": group["total_tokens"],
|
|
1770
|
+
"models": tuple(sorted(
|
|
1771
|
+
group["models"].values(),
|
|
1772
|
+
key=lambda item: (-float(item["cost_usd"]), str(item["model"])),
|
|
1773
|
+
)),
|
|
1774
|
+
"sessions": tuple(sorted(
|
|
1775
|
+
group["session_rows"].values(),
|
|
1776
|
+
key=lambda item: str(item["last_activity"]), reverse=True,
|
|
1777
|
+
)),
|
|
1429
1778
|
})
|
|
1430
1779
|
rows.sort(key=lambda row: (-float(row["cost_usd"]), str(row["label"]), str(row["key"])))
|
|
1431
1780
|
return {
|
|
@@ -1482,6 +1831,7 @@ def _build_codex_native_weekly_view(
|
|
|
1482
1831
|
bucket_by_entry: dict[int, str] = {}
|
|
1483
1832
|
display_tz = ZoneInfo(display_tz_name) if display_tz_name else None
|
|
1484
1833
|
labels: dict[str, str] = {}
|
|
1834
|
+
periods_by_bucket: dict[str, CodexWeeklyPeriod] = {}
|
|
1485
1835
|
for entry in entries:
|
|
1486
1836
|
timestamp = getattr(entry, "timestamp").astimezone(UTC)
|
|
1487
1837
|
root_key = str(getattr(entry, "source_root_key", "") or "")
|
|
@@ -1501,13 +1851,29 @@ def _build_codex_native_weekly_view(
|
|
|
1501
1851
|
if display_tz is not None else period.start_at.astimezone()
|
|
1502
1852
|
)
|
|
1503
1853
|
labels[bucket] = local_start.strftime("%m-%d %H:%M")
|
|
1854
|
+
periods_by_bucket[bucket] = period
|
|
1504
1855
|
|
|
1505
1856
|
rows = _aggregate_codex_buckets(
|
|
1506
1857
|
converted,
|
|
1507
1858
|
key_fn=lambda entry: bucket_by_entry[id(entry)],
|
|
1508
1859
|
speed=speed,
|
|
1509
1860
|
)
|
|
1510
|
-
display_rows = tuple(
|
|
1861
|
+
display_rows = tuple(
|
|
1862
|
+
replace(
|
|
1863
|
+
row,
|
|
1864
|
+
bucket=labels[row.bucket],
|
|
1865
|
+
period_start_at=periods_by_bucket[row.bucket].start_at,
|
|
1866
|
+
period_end_at=periods_by_bucket[row.bucket].end_at,
|
|
1867
|
+
used_pct=periods_by_bucket[row.bucket].used_percent,
|
|
1868
|
+
dollar_per_pct=(
|
|
1869
|
+
row.cost_usd / periods_by_bucket[row.bucket].used_percent
|
|
1870
|
+
if periods_by_bucket[row.bucket].used_percent is not None
|
|
1871
|
+
and periods_by_bucket[row.bucket].used_percent > 0
|
|
1872
|
+
else None
|
|
1873
|
+
),
|
|
1874
|
+
)
|
|
1875
|
+
for row in rows
|
|
1876
|
+
)
|
|
1511
1877
|
return CodexWeeklyView(
|
|
1512
1878
|
rows=display_rows,
|
|
1513
1879
|
total_cost_usd=stable_sum(row.cost_usd for row in display_rows),
|
|
@@ -1661,7 +2027,11 @@ def build_codex_source_state(
|
|
|
1661
2027
|
entries, now_utc=context.now_utc, tz_name=context.display_tz_name, speed=context.speed,
|
|
1662
2028
|
)
|
|
1663
2029
|
)
|
|
1664
|
-
quota = _quota_read_model(
|
|
2030
|
+
quota = _quota_read_model(
|
|
2031
|
+
context,
|
|
2032
|
+
quota_observations,
|
|
2033
|
+
accounting_entries=visible_accounting_entries,
|
|
2034
|
+
)
|
|
1665
2035
|
quota_blocks = _quota_wire(
|
|
1666
2036
|
context.stats_conn,
|
|
1667
2037
|
accounting_entries=visible_accounting_entries,
|
|
@@ -1677,6 +2047,14 @@ def build_codex_source_state(
|
|
|
1677
2047
|
context, budget_entries, cost_events=budget_cost_events,
|
|
1678
2048
|
)
|
|
1679
2049
|
conversation_metadata = _codex_conversation_metadata(context.cache_conn)
|
|
2050
|
+
cache_report = _codex_cache_report_wire(
|
|
2051
|
+
visible_accounting_entries,
|
|
2052
|
+
metadata=conversation_metadata,
|
|
2053
|
+
now_utc=context.now_utc,
|
|
2054
|
+
display_tz_name=context.display_tz_name,
|
|
2055
|
+
speed=context.speed,
|
|
2056
|
+
anomaly_threshold_pp=context.cache_report_anomaly_threshold_pp,
|
|
2057
|
+
)
|
|
1680
2058
|
projects = (
|
|
1681
2059
|
_partial_projects_wire(visible_accounting_entries, conversation_metadata)
|
|
1682
2060
|
if metadata_incomplete else _projects_wire(
|
|
@@ -1781,7 +2159,12 @@ def build_codex_source_state(
|
|
|
1781
2159
|
"projected": projected_budget_rows,
|
|
1782
2160
|
},
|
|
1783
2161
|
"projects": projects,
|
|
1784
|
-
"alerts": {
|
|
2162
|
+
"alerts": {
|
|
2163
|
+
"rows": alerts,
|
|
2164
|
+
"actual_thresholds": context.codex_quota_actual_thresholds,
|
|
2165
|
+
"projected_thresholds": context.codex_quota_projected_thresholds,
|
|
2166
|
+
},
|
|
2167
|
+
"cache_report": cache_report,
|
|
1785
2168
|
},
|
|
1786
2169
|
clock_data={"codex_budget_cost_events": budget_cost_events},
|
|
1787
2170
|
)
|