cctally 1.69.1 → 1.69.3
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 +10 -0
- package/bin/_cctally_dashboard_sources.py +1049 -0
- package/bin/_cctally_source_analytics.py +2050 -0
- package/bin/_lib_dashboard_sources.py +511 -0
- package/bin/_lib_source_analytics.py +1375 -0
- package/package.json +5 -1
|
@@ -0,0 +1,1049 @@
|
|
|
1
|
+
"""Dashboard-only, cache-backed provider read models for #294 S4."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import datetime as dt
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import sqlite3
|
|
8
|
+
import sys
|
|
9
|
+
from collections.abc import Mapping
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from types import MappingProxyType
|
|
12
|
+
from typing import Any, Iterable
|
|
13
|
+
from zoneinfo import ZoneInfo
|
|
14
|
+
|
|
15
|
+
from _cctally_core import get_week_start_name
|
|
16
|
+
from _cctally_quota import (
|
|
17
|
+
codex_physical_mutation_seq,
|
|
18
|
+
load_codex_quota_observations,
|
|
19
|
+
load_codex_quota_projection_certificate,
|
|
20
|
+
)
|
|
21
|
+
from _cctally_source_analytics import load_qualified_codex_entries
|
|
22
|
+
from _lib_dashboard_sources import (
|
|
23
|
+
CapabilityRecord,
|
|
24
|
+
ProjectionCoherence,
|
|
25
|
+
SourceDashboardState,
|
|
26
|
+
assess_codex_projection_coherence,
|
|
27
|
+
dashboard_resource_key,
|
|
28
|
+
)
|
|
29
|
+
from _lib_quota import (
|
|
30
|
+
build_blocks,
|
|
31
|
+
build_history,
|
|
32
|
+
forecast_quota,
|
|
33
|
+
percent_milestones,
|
|
34
|
+
quota_freshness,
|
|
35
|
+
select_baseline,
|
|
36
|
+
)
|
|
37
|
+
from _lib_jsonl import CodexEntry
|
|
38
|
+
from _lib_source_analytics import build_codex_project_result
|
|
39
|
+
from _lib_view_models import (
|
|
40
|
+
build_codex_daily_view,
|
|
41
|
+
build_codex_monthly_view,
|
|
42
|
+
build_codex_session_view,
|
|
43
|
+
build_codex_weekly_view,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
UTC = dt.timezone.utc
|
|
48
|
+
SOURCE_HISTORY_LIMIT = 250
|
|
49
|
+
DASHBOARD_QUOTA_OBSERVATION_LIMIT = 1000
|
|
50
|
+
DASHBOARD_QUOTA_RECENT_DAYS = 35
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CodexProjectionIncoherent(RuntimeError):
|
|
54
|
+
"""Physical cache and S2 interpreted quota state cannot be mixed."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, reason: str | None) -> None:
|
|
57
|
+
self.reason = reason
|
|
58
|
+
super().__init__("Codex quota projection is incoherent")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SourceCapabilityUnavailable(ValueError):
|
|
62
|
+
"""A source is not a physical owner or cannot serve a resource domain."""
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class SourceResourceNotFound(LookupError):
|
|
66
|
+
"""A valid opaque resource key has no row in its provider state."""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class DashboardSourceSemantics:
|
|
71
|
+
"""One canonical CLI configuration resolution for a dashboard read.
|
|
72
|
+
|
|
73
|
+
The source bundle must use the same effective Codex tier and calendar-week
|
|
74
|
+
anchor as the CLI. Keeping that resolution in one small immutable object
|
|
75
|
+
also makes every render-affecting configuration input explicit in the
|
|
76
|
+
provider identity rather than accidentally treating it as an idle tick.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
display_tz_name: str | None
|
|
80
|
+
week_start_name: str
|
|
81
|
+
week_start_idx: int
|
|
82
|
+
speed: str
|
|
83
|
+
codex_budget: Mapping[str, object] | None
|
|
84
|
+
claude_identity: str
|
|
85
|
+
codex_identity: str
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def resolve_dashboard_source_semantics(
|
|
89
|
+
config: Mapping[str, object] | None,
|
|
90
|
+
*,
|
|
91
|
+
display_tz_name: str | None,
|
|
92
|
+
) -> DashboardSourceSemantics:
|
|
93
|
+
"""Resolve dashboard semantics through the shipped CLI kernels.
|
|
94
|
+
|
|
95
|
+
``_resolve_codex_speed('auto')`` is intentionally the only tier resolver:
|
|
96
|
+
it preserves the CLI's all-$CODEX_HOME fast-service-tier behavior. The
|
|
97
|
+
weekly index comes from the same ``get_week_start_name``/``WEEKDAY_MAP``
|
|
98
|
+
pair used by the report and budget command surfaces.
|
|
99
|
+
"""
|
|
100
|
+
c = sys.modules["cctally"]
|
|
101
|
+
raw_config = dict(config or {})
|
|
102
|
+
week_start_name = get_week_start_name(raw_config)
|
|
103
|
+
week_start_idx = c.WEEKDAY_MAP[week_start_name]
|
|
104
|
+
speed = c._resolve_codex_speed("auto")
|
|
105
|
+
budget_config = c._get_budget_config(raw_config)
|
|
106
|
+
raw_codex_budget = budget_config.get("codex")
|
|
107
|
+
codex_budget = (
|
|
108
|
+
MappingProxyType(dict(raw_codex_budget))
|
|
109
|
+
if isinstance(raw_codex_budget, Mapping) else None
|
|
110
|
+
)
|
|
111
|
+
# The legacy Claude projection owns the non-Codex config surface. Codex
|
|
112
|
+
# budget semantics are explicitly excluded so changing them cannot evict a
|
|
113
|
+
# byte-identical Claude source object.
|
|
114
|
+
claude_config = dict(raw_config)
|
|
115
|
+
raw_budget = claude_config.get("budget")
|
|
116
|
+
if isinstance(raw_budget, Mapping):
|
|
117
|
+
claude_budget = dict(raw_budget)
|
|
118
|
+
claude_budget.pop("codex", None)
|
|
119
|
+
if claude_budget:
|
|
120
|
+
claude_config["budget"] = claude_budget
|
|
121
|
+
else:
|
|
122
|
+
claude_config.pop("budget", None)
|
|
123
|
+
claude_identity_payload = {
|
|
124
|
+
"display_tz_name": display_tz_name,
|
|
125
|
+
"render_config": claude_config,
|
|
126
|
+
}
|
|
127
|
+
codex_identity_payload = {
|
|
128
|
+
"codex_budget": dict(codex_budget) if codex_budget is not None else None,
|
|
129
|
+
"display_tz_name": display_tz_name,
|
|
130
|
+
"speed": speed,
|
|
131
|
+
"week_start_name": week_start_name,
|
|
132
|
+
}
|
|
133
|
+
claude_identity = hashlib.sha256(json.dumps(
|
|
134
|
+
claude_identity_payload, sort_keys=True, separators=(",", ":"), allow_nan=False,
|
|
135
|
+
).encode("utf-8")).hexdigest()[:24]
|
|
136
|
+
codex_identity = hashlib.sha256(json.dumps(
|
|
137
|
+
codex_identity_payload, sort_keys=True, separators=(",", ":"), allow_nan=False,
|
|
138
|
+
).encode("utf-8")).hexdigest()[:24]
|
|
139
|
+
return DashboardSourceSemantics(
|
|
140
|
+
display_tz_name=display_tz_name,
|
|
141
|
+
week_start_name=week_start_name,
|
|
142
|
+
week_start_idx=week_start_idx,
|
|
143
|
+
speed=speed,
|
|
144
|
+
codex_budget=codex_budget,
|
|
145
|
+
claude_identity=claude_identity,
|
|
146
|
+
codex_identity=codex_identity,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class DashboardReadContext:
|
|
152
|
+
"""Already-open, coordinated-ingest database inputs for one provider read."""
|
|
153
|
+
|
|
154
|
+
cache_conn: sqlite3.Connection
|
|
155
|
+
stats_conn: sqlite3.Connection
|
|
156
|
+
range_start: dt.datetime
|
|
157
|
+
now_utc: dt.datetime
|
|
158
|
+
display_tz_name: str | None
|
|
159
|
+
week_start_idx: int = 0
|
|
160
|
+
week_start_name: str = "monday"
|
|
161
|
+
speed: str = "standard"
|
|
162
|
+
codex_budget: Mapping[str, object] | None = None
|
|
163
|
+
|
|
164
|
+
def __post_init__(self) -> None:
|
|
165
|
+
for name in ("range_start", "now_utc"):
|
|
166
|
+
value = getattr(self, name)
|
|
167
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
168
|
+
raise ValueError(f"{name} must be timezone-aware")
|
|
169
|
+
object.__setattr__(self, name, value.astimezone(UTC))
|
|
170
|
+
if self.now_utc < self.range_start:
|
|
171
|
+
raise ValueError("now_utc must not precede range_start")
|
|
172
|
+
if not isinstance(self.week_start_name, str) or not self.week_start_name:
|
|
173
|
+
raise ValueError("week_start_name must be a non-empty string")
|
|
174
|
+
if self.codex_budget is not None and not isinstance(self.codex_budget, Mapping):
|
|
175
|
+
raise ValueError("codex_budget must be a mapping or None")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
_RESOURCE_ROWS = {
|
|
179
|
+
"session": ("sessions", "rows"),
|
|
180
|
+
"project": ("projects", "rows"),
|
|
181
|
+
"block": ("quota", "blocks"),
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _public_copy(value: object) -> object:
|
|
186
|
+
"""Detach a bounded source row from its immutable published state."""
|
|
187
|
+
if isinstance(value, Mapping):
|
|
188
|
+
return {str(key): _public_copy(item) for key, item in value.items()}
|
|
189
|
+
if isinstance(value, tuple):
|
|
190
|
+
return [_public_copy(item) for item in value]
|
|
191
|
+
return value
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def source_detail_lookup(bundle: object, source: str, resource: str, key: str) -> dict[str, object]:
|
|
195
|
+
"""Find one provider-owned opaque row without I/O, ingest, or fallback.
|
|
196
|
+
|
|
197
|
+
The handler has already parsed the fixed route grammar. This adapter only
|
|
198
|
+
reads the frozen bundle published by the dashboard owner thread, so a
|
|
199
|
+
request cannot accidentally trigger cache sync, rollout parsing, or a
|
|
200
|
+
Claude fallback for a Codex key.
|
|
201
|
+
"""
|
|
202
|
+
if source not in ("claude", "codex") or resource not in _RESOURCE_ROWS:
|
|
203
|
+
raise SourceCapabilityUnavailable()
|
|
204
|
+
try:
|
|
205
|
+
state = bundle.sources[source]
|
|
206
|
+
data = state.data
|
|
207
|
+
except (AttributeError, KeyError, TypeError) as exc:
|
|
208
|
+
raise SourceCapabilityUnavailable() from exc
|
|
209
|
+
if state.availability == "unavailable" or not isinstance(data, Mapping):
|
|
210
|
+
raise SourceCapabilityUnavailable()
|
|
211
|
+
domain, rows_key = _RESOURCE_ROWS[resource]
|
|
212
|
+
try:
|
|
213
|
+
rows = data[domain][rows_key]
|
|
214
|
+
except (KeyError, TypeError) as exc:
|
|
215
|
+
raise SourceCapabilityUnavailable() from exc
|
|
216
|
+
for row in rows:
|
|
217
|
+
if isinstance(row, Mapping) and row.get("key") == key:
|
|
218
|
+
return _public_copy(row) # type: ignore[return-value]
|
|
219
|
+
raise SourceResourceNotFound()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def codex_projection_coherence(
|
|
223
|
+
context: DashboardReadContext,
|
|
224
|
+
) -> ProjectionCoherence:
|
|
225
|
+
"""Check every active root against the post-reconciliation certificate.
|
|
226
|
+
|
|
227
|
+
The source adapter is intentionally a reader: it never reconciles or
|
|
228
|
+
mutates either database. The certificate is stamped from S2's exact full
|
|
229
|
+
physical signature only after its stats transaction commits, and its cache
|
|
230
|
+
sequence must still match before presentation can use it.
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
active_roots = tuple(sorted(
|
|
234
|
+
str(row[0]) for row in context.cache_conn.execute(
|
|
235
|
+
"SELECT source_root_key FROM codex_source_roots"
|
|
236
|
+
)
|
|
237
|
+
))
|
|
238
|
+
if not active_roots:
|
|
239
|
+
return ProjectionCoherence(True)
|
|
240
|
+
certificate = load_codex_quota_projection_certificate(context.cache_conn)
|
|
241
|
+
if certificate is None or certificate[0] != codex_physical_mutation_seq(context.cache_conn):
|
|
242
|
+
return ProjectionCoherence(False, "projection_certificate_stale")
|
|
243
|
+
resolved_physical_signatures = dict(certificate[1])
|
|
244
|
+
projection_signatures = {
|
|
245
|
+
str(root_key): str(signature)
|
|
246
|
+
for root_key, signature in context.stats_conn.execute(
|
|
247
|
+
"SELECT source_root_key, physical_signature "
|
|
248
|
+
"FROM quota_projection_state"
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
except (sqlite3.Error, OSError, ValueError, TypeError):
|
|
252
|
+
return ProjectionCoherence(False, "projection_read_failed")
|
|
253
|
+
return assess_codex_projection_coherence(
|
|
254
|
+
active_root_keys=active_roots,
|
|
255
|
+
physical_signatures=resolved_physical_signatures,
|
|
256
|
+
projection_signatures=projection_signatures,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _codex_budget_cost_events(
|
|
261
|
+
context: DashboardReadContext,
|
|
262
|
+
entries: Iterable[object],
|
|
263
|
+
) -> tuple[tuple[dt.datetime, float], ...]:
|
|
264
|
+
"""Freeze every configured-window cost event for exact idle pace updates."""
|
|
265
|
+
if context.codex_budget is None:
|
|
266
|
+
return ()
|
|
267
|
+
_period, start_at, end_at = _configured_codex_budget_window(context)
|
|
268
|
+
c = sys.modules["cctally"]
|
|
269
|
+
events: list[tuple[dt.datetime, float]] = []
|
|
270
|
+
for entry in entries:
|
|
271
|
+
timestamp = getattr(entry, "timestamp", None)
|
|
272
|
+
if not isinstance(timestamp, dt.datetime):
|
|
273
|
+
continue
|
|
274
|
+
timestamp = timestamp.astimezone(UTC)
|
|
275
|
+
if not start_at <= timestamp < end_at:
|
|
276
|
+
continue
|
|
277
|
+
events.append((
|
|
278
|
+
timestamp,
|
|
279
|
+
c._calculate_codex_entry_cost(
|
|
280
|
+
str(getattr(entry, "model")),
|
|
281
|
+
int(getattr(entry, "input_tokens")),
|
|
282
|
+
int(getattr(entry, "cached_input_tokens")),
|
|
283
|
+
int(getattr(entry, "output_tokens")),
|
|
284
|
+
int(getattr(entry, "reasoning_output_tokens")),
|
|
285
|
+
speed=context.speed,
|
|
286
|
+
),
|
|
287
|
+
))
|
|
288
|
+
return tuple(events)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _bucket_wire(bucket: Any) -> dict[str, object]:
|
|
292
|
+
return {
|
|
293
|
+
"label": bucket.bucket,
|
|
294
|
+
"cost_usd": bucket.cost_usd,
|
|
295
|
+
"input_tokens": bucket.input_tokens,
|
|
296
|
+
"cached_input_tokens": bucket.cached_input_tokens,
|
|
297
|
+
"output_tokens": bucket.output_tokens,
|
|
298
|
+
"reasoning_output_tokens": bucket.reasoning_output_tokens,
|
|
299
|
+
"total_tokens": bucket.total_tokens,
|
|
300
|
+
"models": tuple(bucket.models),
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _period_wire(view: Any) -> dict[str, object]:
|
|
305
|
+
return {
|
|
306
|
+
"rows": tuple(_bucket_wire(row) for row in view.rows),
|
|
307
|
+
"total_cost_usd": view.total_cost_usd,
|
|
308
|
+
"total_tokens": view.total_tokens,
|
|
309
|
+
"display_tz": view.display_tz_label,
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _session_wire(view: Any) -> dict[str, object]:
|
|
314
|
+
rows = []
|
|
315
|
+
for ordinal, row in enumerate(view.rows, start=1):
|
|
316
|
+
# The Codex session aggregator intentionally splits equal relative
|
|
317
|
+
# session paths from distinct $CODEX_HOME roots. The opaque detail
|
|
318
|
+
# key must use that same grouping identity or two visible rows route
|
|
319
|
+
# to one another's detail payload.
|
|
320
|
+
root_identity = row.codex_root or "single-root"
|
|
321
|
+
rows.append({
|
|
322
|
+
"key": dashboard_resource_key(
|
|
323
|
+
"session", "codex", root_identity, row.session_id_path,
|
|
324
|
+
),
|
|
325
|
+
"source": "codex",
|
|
326
|
+
"label": f"Session {ordinal}",
|
|
327
|
+
"last_activity": row.last_activity.astimezone(UTC).isoformat(),
|
|
328
|
+
"cost_usd": row.cost_usd,
|
|
329
|
+
"input_tokens": row.input_tokens,
|
|
330
|
+
"cached_input_tokens": row.cached_input_tokens,
|
|
331
|
+
"output_tokens": row.output_tokens,
|
|
332
|
+
"reasoning_output_tokens": row.reasoning_output_tokens,
|
|
333
|
+
"total_tokens": row.total_tokens,
|
|
334
|
+
"models": tuple(row.models),
|
|
335
|
+
})
|
|
336
|
+
return {
|
|
337
|
+
"rows": tuple(rows),
|
|
338
|
+
"total_sessions": view.total_sessions,
|
|
339
|
+
"total_cost_usd": view.total_cost_usd,
|
|
340
|
+
"total_tokens": view.total_tokens,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _quota_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, object], ...]:
|
|
345
|
+
try:
|
|
346
|
+
rows = stats_conn.execute(
|
|
347
|
+
"SELECT source_root_key, logical_limit_key, observed_slot, window_minutes, "
|
|
348
|
+
"limit_name, resets_at_utc, current_percent, orphaned_at "
|
|
349
|
+
"FROM quota_window_blocks WHERE source='codex' "
|
|
350
|
+
"ORDER BY resets_at_utc DESC, source_root_key, logical_limit_key, observed_slot "
|
|
351
|
+
"LIMIT ?",
|
|
352
|
+
(SOURCE_HISTORY_LIMIT,),
|
|
353
|
+
).fetchall()
|
|
354
|
+
except sqlite3.Error:
|
|
355
|
+
return ()
|
|
356
|
+
return tuple({
|
|
357
|
+
"key": dashboard_resource_key(
|
|
358
|
+
"block", "codex", root_key, logical_limit_key, observed_slot, window_minutes, resets_at,
|
|
359
|
+
),
|
|
360
|
+
"source": "codex",
|
|
361
|
+
"label": limit_name or "Codex quota",
|
|
362
|
+
"resets_at": resets_at,
|
|
363
|
+
"current_percent": current_percent,
|
|
364
|
+
"orphaned": orphaned_at is not None,
|
|
365
|
+
} for (
|
|
366
|
+
root_key, logical_limit_key, observed_slot, window_minutes,
|
|
367
|
+
limit_name, resets_at, current_percent, orphaned_at,
|
|
368
|
+
) in rows)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _budget_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, object], ...]:
|
|
372
|
+
try:
|
|
373
|
+
rows = stats_conn.execute(
|
|
374
|
+
"SELECT period_start_at, period, threshold, budget_usd, spent_usd, "
|
|
375
|
+
"consumption_pct FROM budget_milestones WHERE vendor='codex' "
|
|
376
|
+
"ORDER BY period_start_at DESC, threshold DESC LIMIT ?",
|
|
377
|
+
(SOURCE_HISTORY_LIMIT,),
|
|
378
|
+
).fetchall()
|
|
379
|
+
except sqlite3.Error:
|
|
380
|
+
return ()
|
|
381
|
+
return tuple({
|
|
382
|
+
"period_start_at": period_start_at,
|
|
383
|
+
"period": period,
|
|
384
|
+
"threshold": threshold,
|
|
385
|
+
"budget_usd": budget_usd,
|
|
386
|
+
"spent_usd": spent_usd,
|
|
387
|
+
"consumption_pct": consumption_pct,
|
|
388
|
+
} for period_start_at, period, threshold, budget_usd, spent_usd, consumption_pct in rows)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def _projected_budget_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, object], ...]:
|
|
392
|
+
try:
|
|
393
|
+
rows = stats_conn.execute(
|
|
394
|
+
"SELECT period, threshold, projected_value, denominator, crossed_at_utc, alerted_at "
|
|
395
|
+
"FROM projected_milestones WHERE metric='codex_budget_usd' "
|
|
396
|
+
"ORDER BY crossed_at_utc DESC, threshold DESC LIMIT ?",
|
|
397
|
+
(SOURCE_HISTORY_LIMIT,),
|
|
398
|
+
).fetchall()
|
|
399
|
+
except sqlite3.Error:
|
|
400
|
+
return ()
|
|
401
|
+
return tuple({
|
|
402
|
+
"period": period,
|
|
403
|
+
"threshold": threshold,
|
|
404
|
+
"projected_value": projected_value,
|
|
405
|
+
"denominator": denominator,
|
|
406
|
+
"crossed_at": crossed_at,
|
|
407
|
+
"alerted_at": alerted_at,
|
|
408
|
+
} for period, threshold, projected_value, denominator, crossed_at, alerted_at in rows)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _configured_codex_budget_status(
|
|
412
|
+
context: DashboardReadContext,
|
|
413
|
+
entries: Iterable[object],
|
|
414
|
+
*,
|
|
415
|
+
cost_events: tuple[tuple[dt.datetime, float], ...] | None = None,
|
|
416
|
+
) -> dict[str, object] | None:
|
|
417
|
+
"""Compute the live configured Codex budget from the coordinated entries.
|
|
418
|
+
|
|
419
|
+
Durable milestone rows are alert history, not the current budget status.
|
|
420
|
+
This reuses the CLI's calendar-window and ``BudgetInputs``/status kernels
|
|
421
|
+
while deliberately keeping the accounting read on the caller-owned cache
|
|
422
|
+
snapshot.
|
|
423
|
+
"""
|
|
424
|
+
config = context.codex_budget
|
|
425
|
+
if config is None:
|
|
426
|
+
return None
|
|
427
|
+
c = sys.modules["cctally"]
|
|
428
|
+
period, start_at, end_at = _configured_codex_budget_window(context)
|
|
429
|
+
|
|
430
|
+
resolved_events = cost_events if cost_events is not None else _codex_budget_cost_events(
|
|
431
|
+
context, entries,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def _sum_cost(start: dt.datetime, end: dt.datetime) -> float:
|
|
435
|
+
return sum(cost for timestamp, cost in resolved_events if start <= timestamp < end)
|
|
436
|
+
|
|
437
|
+
recent_start = max(start_at, context.now_utc - dt.timedelta(hours=24))
|
|
438
|
+
inputs = c.BudgetInputs(
|
|
439
|
+
target_usd=float(config["amount_usd"]),
|
|
440
|
+
spent_usd=_sum_cost(start_at, context.now_utc),
|
|
441
|
+
recent_24h_usd=_sum_cost(recent_start, context.now_utc),
|
|
442
|
+
week_start_at=start_at,
|
|
443
|
+
week_end_at=end_at,
|
|
444
|
+
now=context.now_utc,
|
|
445
|
+
alert_thresholds=tuple(config["alert_thresholds"]),
|
|
446
|
+
)
|
|
447
|
+
status = c.compute_budget_status(inputs)
|
|
448
|
+
return {
|
|
449
|
+
"period": period,
|
|
450
|
+
"budget_usd": inputs.target_usd,
|
|
451
|
+
"spent_usd": status.spent_usd,
|
|
452
|
+
"remaining_usd": status.remaining_usd,
|
|
453
|
+
"consumption_pct": status.consumption_pct,
|
|
454
|
+
"verdict": status.verdict,
|
|
455
|
+
"low_confidence": status.low_confidence,
|
|
456
|
+
"window_start_at": start_at.astimezone(UTC).isoformat(),
|
|
457
|
+
"window_end_at": end_at.astimezone(UTC).isoformat(),
|
|
458
|
+
"recent_24h_usd": inputs.recent_24h_usd,
|
|
459
|
+
"alert_thresholds": inputs.alert_thresholds,
|
|
460
|
+
"pace": {
|
|
461
|
+
"daily_usd": status.daily_pace_usd,
|
|
462
|
+
"projected_low_usd": status.projected_eow_low_usd,
|
|
463
|
+
"projected_high_usd": status.projected_eow_high_usd,
|
|
464
|
+
"week_avg_projection_usd": status.week_avg_projection_usd,
|
|
465
|
+
},
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _configured_codex_budget_window(
|
|
470
|
+
context: DashboardReadContext,
|
|
471
|
+
) -> tuple[str, dt.datetime, dt.datetime]:
|
|
472
|
+
"""Resolve the configured Codex accounting window through the CLI kernel."""
|
|
473
|
+
config = context.codex_budget
|
|
474
|
+
if config is None:
|
|
475
|
+
raise ValueError("Codex budget is not configured")
|
|
476
|
+
c = sys.modules["cctally"]
|
|
477
|
+
period = str(config["period"])
|
|
478
|
+
tz = ZoneInfo(context.display_tz_name) if context.display_tz_name else None
|
|
479
|
+
forecast = c._load_sibling("_cctally_forecast")
|
|
480
|
+
start_at, end_at = forecast._resolve_calendar_window(
|
|
481
|
+
period,
|
|
482
|
+
context.now_utc,
|
|
483
|
+
{"collector": {"week_start": context.week_start_name}},
|
|
484
|
+
tz,
|
|
485
|
+
)
|
|
486
|
+
return period, start_at.astimezone(UTC), end_at.astimezone(UTC)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _quota_read_model(
|
|
490
|
+
context: DashboardReadContext,
|
|
491
|
+
observations: Iterable[object],
|
|
492
|
+
) -> dict[str, object]:
|
|
493
|
+
"""Use S2's pure history/block/forecast kernels over cache evidence."""
|
|
494
|
+
quota_observations = tuple(observations)
|
|
495
|
+
histories = build_history(quota_observations)
|
|
496
|
+
blocks = build_blocks(quota_observations)
|
|
497
|
+
history_rows: list[dict[str, object]] = []
|
|
498
|
+
milestone_rows: list[dict[str, object]] = []
|
|
499
|
+
active_rows: list[dict[str, object]] = []
|
|
500
|
+
for history in histories:
|
|
501
|
+
identity = history.identity
|
|
502
|
+
key_parts = (
|
|
503
|
+
identity.source_root_key,
|
|
504
|
+
identity.logical_limit_key,
|
|
505
|
+
identity.observed_slot,
|
|
506
|
+
identity.window_minutes,
|
|
507
|
+
)
|
|
508
|
+
baseline = select_baseline(history.observations, context.now_utc)
|
|
509
|
+
freshness = quota_freshness(history.physical_observations, context.now_utc)
|
|
510
|
+
forecast = forecast_quota(history.physical_observations, context.now_utc)
|
|
511
|
+
history_rows.append({
|
|
512
|
+
"key": dashboard_resource_key("quota", "codex", *key_parts),
|
|
513
|
+
"source": "codex",
|
|
514
|
+
"label": identity.limit_name or "Codex quota",
|
|
515
|
+
"observed_slot": identity.observed_slot,
|
|
516
|
+
"window_minutes": identity.window_minutes,
|
|
517
|
+
"current_percent": baseline.used_percent if baseline is not None else None,
|
|
518
|
+
"captured_at": (
|
|
519
|
+
freshness.captured_at.astimezone(UTC).isoformat()
|
|
520
|
+
if freshness.captured_at is not None else None
|
|
521
|
+
),
|
|
522
|
+
"freshness": freshness.state,
|
|
523
|
+
"stale_after_seconds": freshness.stale_after_seconds,
|
|
524
|
+
"forecast": {
|
|
525
|
+
"status": forecast.status,
|
|
526
|
+
"current_percent": forecast.current_percent,
|
|
527
|
+
"rate_percent_per_hour": forecast.rate_percent_per_hour,
|
|
528
|
+
"projected_percent": forecast.projected_percent,
|
|
529
|
+
"resets_at": forecast.resets_at.astimezone(UTC).isoformat() if forecast.resets_at else None,
|
|
530
|
+
"remaining_seconds": forecast.remaining_seconds,
|
|
531
|
+
"sample_count": forecast.sample_count,
|
|
532
|
+
"sample_span_seconds": forecast.sample_span_seconds,
|
|
533
|
+
"confidence": forecast.confidence,
|
|
534
|
+
},
|
|
535
|
+
})
|
|
536
|
+
if baseline is not None and baseline.resets_at > context.now_utc:
|
|
537
|
+
active_rows.append({
|
|
538
|
+
"key": dashboard_resource_key("quota", "codex", *key_parts),
|
|
539
|
+
"current_percent": baseline.used_percent,
|
|
540
|
+
"captured_at": baseline.captured_at.astimezone(UTC).isoformat(),
|
|
541
|
+
"resets_at": baseline.resets_at.astimezone(UTC).isoformat(),
|
|
542
|
+
"freshness": freshness.state,
|
|
543
|
+
"stale_after_seconds": freshness.stale_after_seconds,
|
|
544
|
+
})
|
|
545
|
+
for block in blocks:
|
|
546
|
+
identity = block.identity
|
|
547
|
+
block_parts = (
|
|
548
|
+
identity.source_root_key,
|
|
549
|
+
identity.logical_limit_key,
|
|
550
|
+
identity.observed_slot,
|
|
551
|
+
identity.window_minutes,
|
|
552
|
+
block.resets_at.astimezone(UTC).isoformat(),
|
|
553
|
+
)
|
|
554
|
+
for milestone in percent_milestones(block):
|
|
555
|
+
milestone_rows.append({
|
|
556
|
+
"key": dashboard_resource_key(
|
|
557
|
+
"quota_milestone", "codex", *block_parts,
|
|
558
|
+
milestone.percent, milestone.captured_at.astimezone(UTC).isoformat(),
|
|
559
|
+
),
|
|
560
|
+
"source": "codex",
|
|
561
|
+
"block_key": dashboard_resource_key("block", "codex", *block_parts),
|
|
562
|
+
"percent": milestone.percent,
|
|
563
|
+
"captured_at": milestone.captured_at.astimezone(UTC).isoformat(),
|
|
564
|
+
})
|
|
565
|
+
latest_percent = max(
|
|
566
|
+
(float(row["current_percent"]) for row in active_rows), default=None,
|
|
567
|
+
)
|
|
568
|
+
active_freshness = (
|
|
569
|
+
"fresh" if active_rows and all(row["freshness"] == "fresh" for row in active_rows)
|
|
570
|
+
else ("unavailable" if not active_rows else "stale")
|
|
571
|
+
)
|
|
572
|
+
# Active identities are presentation-critical. Keep them ahead of
|
|
573
|
+
# inactive retained history before enforcing the public cardinality cap.
|
|
574
|
+
active_keys = {str(row["key"]) for row in active_rows}
|
|
575
|
+
history_rows.sort(key=lambda row: (str(row["key"]) not in active_keys, str(row["key"])))
|
|
576
|
+
history_rows = history_rows[:SOURCE_HISTORY_LIMIT]
|
|
577
|
+
milestone_rows.sort(key=lambda row: str(row["captured_at"]), reverse=True)
|
|
578
|
+
milestone_rows = milestone_rows[:SOURCE_HISTORY_LIMIT]
|
|
579
|
+
active_rows = active_rows[:SOURCE_HISTORY_LIMIT]
|
|
580
|
+
return {
|
|
581
|
+
"summary": {
|
|
582
|
+
"window_count": len(blocks),
|
|
583
|
+
"active_window_count": len(active_rows),
|
|
584
|
+
"latest_percent": latest_percent,
|
|
585
|
+
"freshness": active_freshness,
|
|
586
|
+
"active": tuple(active_rows),
|
|
587
|
+
},
|
|
588
|
+
"histories": tuple(history_rows),
|
|
589
|
+
"milestones": tuple(milestone_rows),
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _clock_freshness(
|
|
594
|
+
captured_at: object,
|
|
595
|
+
stale_after: object,
|
|
596
|
+
now_utc: dt.datetime,
|
|
597
|
+
) -> str:
|
|
598
|
+
if not isinstance(captured_at, str) or not isinstance(stale_after, int):
|
|
599
|
+
return "unavailable"
|
|
600
|
+
try:
|
|
601
|
+
captured = dt.datetime.fromisoformat(captured_at.replace("Z", "+00:00")).astimezone(UTC)
|
|
602
|
+
except (TypeError, ValueError):
|
|
603
|
+
return "unavailable"
|
|
604
|
+
age_seconds = int((now_utc - captured).total_seconds())
|
|
605
|
+
if age_seconds < -300:
|
|
606
|
+
return "future"
|
|
607
|
+
return "stale" if age_seconds > stale_after else "fresh"
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def _refresh_budget_status_clock(
|
|
611
|
+
status: Mapping[str, object] | None,
|
|
612
|
+
now_utc: dt.datetime,
|
|
613
|
+
*,
|
|
614
|
+
cost_events: object = (),
|
|
615
|
+
) -> dict[str, object] | None:
|
|
616
|
+
"""Re-run only the pure pace kernel from already-published budget facts."""
|
|
617
|
+
if status is None:
|
|
618
|
+
return None
|
|
619
|
+
try:
|
|
620
|
+
c = sys.modules["cctally"]
|
|
621
|
+
start_at = dt.datetime.fromisoformat(
|
|
622
|
+
str(status["window_start_at"]).replace("Z", "+00:00")
|
|
623
|
+
).astimezone(UTC)
|
|
624
|
+
end_at = dt.datetime.fromisoformat(
|
|
625
|
+
str(status["window_end_at"]).replace("Z", "+00:00")
|
|
626
|
+
).astimezone(UTC)
|
|
627
|
+
recent_start = max(start_at, now_utc - dt.timedelta(hours=24))
|
|
628
|
+
recent_24h_usd = sum(
|
|
629
|
+
float(cost) for timestamp, cost in cost_events
|
|
630
|
+
if isinstance(timestamp, dt.datetime)
|
|
631
|
+
and start_at <= timestamp.astimezone(UTC) < now_utc
|
|
632
|
+
and timestamp.astimezone(UTC) >= recent_start
|
|
633
|
+
)
|
|
634
|
+
inputs = c.BudgetInputs(
|
|
635
|
+
target_usd=float(status["budget_usd"]),
|
|
636
|
+
spent_usd=float(status["spent_usd"]),
|
|
637
|
+
recent_24h_usd=recent_24h_usd,
|
|
638
|
+
week_start_at=start_at,
|
|
639
|
+
week_end_at=end_at,
|
|
640
|
+
now=now_utc,
|
|
641
|
+
alert_thresholds=tuple(int(value) for value in status["alert_thresholds"]),
|
|
642
|
+
)
|
|
643
|
+
refreshed = c.compute_budget_status(inputs)
|
|
644
|
+
except (KeyError, TypeError, ValueError, OverflowError):
|
|
645
|
+
return dict(status)
|
|
646
|
+
return {
|
|
647
|
+
**dict(status),
|
|
648
|
+
"recent_24h_usd": inputs.recent_24h_usd,
|
|
649
|
+
"remaining_usd": refreshed.remaining_usd,
|
|
650
|
+
"consumption_pct": refreshed.consumption_pct,
|
|
651
|
+
"verdict": refreshed.verdict,
|
|
652
|
+
"low_confidence": refreshed.low_confidence,
|
|
653
|
+
"pace": {
|
|
654
|
+
"daily_usd": refreshed.daily_pace_usd,
|
|
655
|
+
"projected_low_usd": refreshed.projected_eow_low_usd,
|
|
656
|
+
"projected_high_usd": refreshed.projected_eow_high_usd,
|
|
657
|
+
"week_avg_projection_usd": refreshed.week_avg_projection_usd,
|
|
658
|
+
},
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def refresh_codex_source_clock(
|
|
663
|
+
state: SourceDashboardState,
|
|
664
|
+
*,
|
|
665
|
+
now_utc: dt.datetime,
|
|
666
|
+
) -> SourceDashboardState:
|
|
667
|
+
"""Refresh idle-only freshness/pace from frozen facts without provider I/O."""
|
|
668
|
+
if state.source != "codex" or not isinstance(state.data, Mapping):
|
|
669
|
+
return state
|
|
670
|
+
if now_utc.tzinfo is None or now_utc.utcoffset() is None:
|
|
671
|
+
raise ValueError("now_utc must be timezone-aware")
|
|
672
|
+
now_utc = now_utc.astimezone(UTC)
|
|
673
|
+
# Structural copy only: untouched period/session/project branches remain
|
|
674
|
+
# the exact frozen objects from the prior publication. Idle refresh must
|
|
675
|
+
# not walk or re-freeze the provider's heavy read model.
|
|
676
|
+
data = dict(state.data)
|
|
677
|
+
quota = data.get("quota")
|
|
678
|
+
quota_changed = False
|
|
679
|
+
if isinstance(quota, Mapping):
|
|
680
|
+
quota = dict(quota)
|
|
681
|
+
refreshed_histories: list[dict[str, object]] = []
|
|
682
|
+
active_rows: list[dict[str, object]] = []
|
|
683
|
+
for raw_history in quota.get("histories", ()):
|
|
684
|
+
if not isinstance(raw_history, Mapping):
|
|
685
|
+
continue
|
|
686
|
+
history = dict(raw_history)
|
|
687
|
+
freshness = _clock_freshness(
|
|
688
|
+
history.get("captured_at"), history.get("stale_after_seconds"), now_utc,
|
|
689
|
+
)
|
|
690
|
+
history["freshness"] = freshness
|
|
691
|
+
forecast = history.get("forecast")
|
|
692
|
+
if isinstance(forecast, Mapping):
|
|
693
|
+
forecast = dict(forecast)
|
|
694
|
+
resets_at = forecast.get("resets_at")
|
|
695
|
+
try:
|
|
696
|
+
reset = dt.datetime.fromisoformat(
|
|
697
|
+
str(resets_at).replace("Z", "+00:00")
|
|
698
|
+
).astimezone(UTC)
|
|
699
|
+
except (TypeError, ValueError):
|
|
700
|
+
reset = None
|
|
701
|
+
remaining = max(0, int((reset - now_utc).total_seconds())) if reset else None
|
|
702
|
+
forecast["remaining_seconds"] = remaining
|
|
703
|
+
sample_count = int(forecast.get("sample_count") or 0)
|
|
704
|
+
if freshness == "future":
|
|
705
|
+
forecast["status"] = "future"
|
|
706
|
+
elif freshness == "stale":
|
|
707
|
+
forecast["status"] = "stale"
|
|
708
|
+
elif sample_count == 0:
|
|
709
|
+
forecast["status"] = "insufficient-history"
|
|
710
|
+
else:
|
|
711
|
+
forecast["status"] = "ok"
|
|
712
|
+
rate = forecast.get("rate_percent_per_hour")
|
|
713
|
+
current = forecast.get("current_percent")
|
|
714
|
+
if (
|
|
715
|
+
isinstance(rate, (int, float)) and not isinstance(rate, bool)
|
|
716
|
+
and isinstance(current, (int, float)) and not isinstance(current, bool)
|
|
717
|
+
and remaining is not None
|
|
718
|
+
):
|
|
719
|
+
forecast["projected_percent"] = min(
|
|
720
|
+
100.0, max(float(current), float(current) + float(rate) * remaining / 3600),
|
|
721
|
+
)
|
|
722
|
+
history["forecast"] = forecast
|
|
723
|
+
if reset is not None and reset > now_utc and current is not None:
|
|
724
|
+
active_rows.append({
|
|
725
|
+
"key": history.get("key"),
|
|
726
|
+
"current_percent": current,
|
|
727
|
+
"captured_at": history.get("captured_at"),
|
|
728
|
+
"resets_at": resets_at,
|
|
729
|
+
"freshness": freshness,
|
|
730
|
+
"stale_after_seconds": history.get("stale_after_seconds"),
|
|
731
|
+
})
|
|
732
|
+
refreshed_histories.append(history)
|
|
733
|
+
quota["histories"] = refreshed_histories
|
|
734
|
+
latest_percent = max(
|
|
735
|
+
(float(row["current_percent"]) for row in active_rows), default=None,
|
|
736
|
+
)
|
|
737
|
+
summary = dict(quota.get("summary") or {})
|
|
738
|
+
prior_active = summary.get("active")
|
|
739
|
+
if isinstance(prior_active, (tuple, list)):
|
|
740
|
+
active_order = {
|
|
741
|
+
str(row.get("key")): index
|
|
742
|
+
for index, row in enumerate(prior_active)
|
|
743
|
+
if isinstance(row, Mapping)
|
|
744
|
+
}
|
|
745
|
+
active_rows.sort(
|
|
746
|
+
key=lambda row: active_order.get(str(row.get("key")), len(active_order)),
|
|
747
|
+
)
|
|
748
|
+
summary.update({
|
|
749
|
+
"active_window_count": len(active_rows),
|
|
750
|
+
"latest_percent": latest_percent,
|
|
751
|
+
"freshness": (
|
|
752
|
+
"fresh" if active_rows and all(row["freshness"] == "fresh" for row in active_rows)
|
|
753
|
+
else ("unavailable" if not active_rows else "stale")
|
|
754
|
+
),
|
|
755
|
+
"active": active_rows,
|
|
756
|
+
})
|
|
757
|
+
quota["summary"] = summary
|
|
758
|
+
data["quota"] = quota
|
|
759
|
+
quota_changed = bool(refreshed_histories)
|
|
760
|
+
budget_domain = data.get("budget")
|
|
761
|
+
budget_changed = False
|
|
762
|
+
if isinstance(budget_domain, Mapping):
|
|
763
|
+
budget_domain = dict(budget_domain)
|
|
764
|
+
refreshed_budget = _refresh_budget_status_clock(
|
|
765
|
+
budget_domain.get("status") if isinstance(budget_domain.get("status"), Mapping) else None,
|
|
766
|
+
now_utc,
|
|
767
|
+
cost_events=(
|
|
768
|
+
state.clock_data.get("codex_budget_cost_events", ())
|
|
769
|
+
if isinstance(state.clock_data, Mapping) else ()
|
|
770
|
+
),
|
|
771
|
+
)
|
|
772
|
+
if refreshed_budget is not None:
|
|
773
|
+
budget_domain["status"] = refreshed_budget
|
|
774
|
+
data["budget"] = budget_domain
|
|
775
|
+
hero = data.get("hero")
|
|
776
|
+
if isinstance(hero, Mapping):
|
|
777
|
+
hero = dict(hero)
|
|
778
|
+
hero["budget"] = refreshed_budget
|
|
779
|
+
data["hero"] = hero
|
|
780
|
+
budget_changed = True
|
|
781
|
+
if not (quota_changed or budget_changed):
|
|
782
|
+
return state
|
|
783
|
+
refreshed_state = SourceDashboardState(
|
|
784
|
+
source=state.source,
|
|
785
|
+
availability=state.availability,
|
|
786
|
+
freshness=state.freshness,
|
|
787
|
+
warnings=state.warnings,
|
|
788
|
+
data_version=state.data_version,
|
|
789
|
+
last_success_at=state.last_success_at,
|
|
790
|
+
capabilities=state.capabilities,
|
|
791
|
+
data=data,
|
|
792
|
+
clock_data=state.clock_data,
|
|
793
|
+
)
|
|
794
|
+
return state if refreshed_state.data == state.data else refreshed_state
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def _alerts_wire(stats_conn: sqlite3.Connection) -> tuple[dict[str, object], ...]:
|
|
798
|
+
"""Return only safe, source-owned Codex alert context in newest-first order."""
|
|
799
|
+
rows: list[dict[str, object]] = []
|
|
800
|
+
try:
|
|
801
|
+
for period, threshold, consumption_pct, crossed_at in stats_conn.execute(
|
|
802
|
+
"SELECT period, threshold, consumption_pct, crossed_at_utc "
|
|
803
|
+
"FROM budget_milestones WHERE vendor='codex' AND alerted_at IS NOT NULL "
|
|
804
|
+
"ORDER BY crossed_at_utc DESC, threshold DESC LIMIT ?",
|
|
805
|
+
(SOURCE_HISTORY_LIMIT,),
|
|
806
|
+
):
|
|
807
|
+
rows.append({
|
|
808
|
+
"key": dashboard_resource_key("alert", "codex", "codex_budget", period, threshold, crossed_at),
|
|
809
|
+
"source": "codex",
|
|
810
|
+
"axis": "codex_budget", "period": period, "threshold": threshold,
|
|
811
|
+
"value": consumption_pct, "created_at": crossed_at,
|
|
812
|
+
})
|
|
813
|
+
for period, threshold, projected_value, crossed_at in stats_conn.execute(
|
|
814
|
+
"SELECT period, threshold, projected_value, crossed_at_utc "
|
|
815
|
+
"FROM projected_milestones WHERE metric='codex_budget_usd' AND alerted_at IS NOT NULL "
|
|
816
|
+
"ORDER BY crossed_at_utc DESC, threshold DESC LIMIT ?",
|
|
817
|
+
(SOURCE_HISTORY_LIMIT,),
|
|
818
|
+
):
|
|
819
|
+
rows.append({
|
|
820
|
+
"key": dashboard_resource_key("alert", "codex", "projected", period, threshold, crossed_at),
|
|
821
|
+
"source": "codex",
|
|
822
|
+
"axis": "projected", "period": period, "threshold": threshold,
|
|
823
|
+
"value": projected_value, "created_at": crossed_at,
|
|
824
|
+
})
|
|
825
|
+
for root_key, logical_key, observed_slot, window_minutes, resets_at, threshold, severity, created_at in stats_conn.execute(
|
|
826
|
+
"SELECT source_root_key, logical_limit_key, observed_slot, window_minutes, resets_at_utc, "
|
|
827
|
+
"threshold, severity, created_at_utc FROM quota_threshold_events "
|
|
828
|
+
"WHERE source='codex' AND disposition='alerted' AND orphaned_at IS NULL "
|
|
829
|
+
"ORDER BY created_at_utc DESC, source_root_key, logical_limit_key, observed_slot, threshold "
|
|
830
|
+
"LIMIT ?",
|
|
831
|
+
(SOURCE_HISTORY_LIMIT,),
|
|
832
|
+
):
|
|
833
|
+
rows.append({
|
|
834
|
+
"key": dashboard_resource_key(
|
|
835
|
+
"alert", "codex", "quota", root_key, logical_key, observed_slot,
|
|
836
|
+
window_minutes, resets_at, threshold, created_at,
|
|
837
|
+
),
|
|
838
|
+
"source": "codex",
|
|
839
|
+
"axis": "quota", "threshold": threshold, "severity": severity,
|
|
840
|
+
"created_at": created_at,
|
|
841
|
+
})
|
|
842
|
+
except sqlite3.Error:
|
|
843
|
+
return ()
|
|
844
|
+
return tuple(sorted(
|
|
845
|
+
rows,
|
|
846
|
+
key=lambda item: str(item.get("created_at") or ""),
|
|
847
|
+
reverse=True,
|
|
848
|
+
)[:SOURCE_HISTORY_LIMIT])
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def _projects_wire(
|
|
852
|
+
context: DashboardReadContext,
|
|
853
|
+
quota_observations: Iterable[object],
|
|
854
|
+
entries: Iterable[object],
|
|
855
|
+
*,
|
|
856
|
+
accounting_end: dt.datetime,
|
|
857
|
+
) -> dict[str, object]:
|
|
858
|
+
"""Adapt S3's already-qualified attribution result without re-formulas."""
|
|
859
|
+
qualified_entries = tuple(entries)
|
|
860
|
+
result = build_codex_project_result(
|
|
861
|
+
qualified_entries,
|
|
862
|
+
range_start=context.range_start,
|
|
863
|
+
range_end=accounting_end,
|
|
864
|
+
blocks=build_blocks(quota_observations),
|
|
865
|
+
as_of=context.now_utc,
|
|
866
|
+
allocation_entries=qualified_entries,
|
|
867
|
+
)
|
|
868
|
+
data = result.data
|
|
869
|
+
if data is None:
|
|
870
|
+
return {"rows": (), "total_cost_usd": 0.0, "total_tokens": 0}
|
|
871
|
+
return {
|
|
872
|
+
"rows": tuple({
|
|
873
|
+
"key": dashboard_resource_key("project", "codex", row.project_key),
|
|
874
|
+
"source": "codex",
|
|
875
|
+
"label": row.display_label,
|
|
876
|
+
"session_count": row.session_count,
|
|
877
|
+
"first_seen": row.first_seen.astimezone(UTC).isoformat(),
|
|
878
|
+
"last_seen": row.last_seen.astimezone(UTC).isoformat(),
|
|
879
|
+
"cost_usd": row.totals.cost_usd,
|
|
880
|
+
"input_tokens": row.totals.input_tokens,
|
|
881
|
+
"cached_input_tokens": row.totals.cached_input_tokens,
|
|
882
|
+
"output_tokens": row.totals.output_tokens,
|
|
883
|
+
"reasoning_output_tokens": row.totals.reasoning_output_tokens,
|
|
884
|
+
"total_tokens": row.totals.total_tokens,
|
|
885
|
+
} for row in data.projects),
|
|
886
|
+
"total_cost_usd": data.totals.cost_usd,
|
|
887
|
+
"total_tokens": data.totals.total_tokens,
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _codex_entries_from_qualified(entries: Iterable[object]) -> list[CodexEntry]:
|
|
892
|
+
"""Adapt the one root-qualified accounting read for shipped view kernels."""
|
|
893
|
+
converted: list[CodexEntry] = []
|
|
894
|
+
for entry in entries:
|
|
895
|
+
source_path = str(getattr(entry, "source_path", "") or "")
|
|
896
|
+
session_id = str(getattr(entry, "session_id", "") or "")
|
|
897
|
+
if not source_path or not session_id:
|
|
898
|
+
raise SourceCapabilityUnavailable("qualified accounting lacks session identity")
|
|
899
|
+
converted.append(CodexEntry(
|
|
900
|
+
timestamp=getattr(entry, "timestamp"),
|
|
901
|
+
session_id=session_id,
|
|
902
|
+
model=str(getattr(entry, "model")),
|
|
903
|
+
input_tokens=int(getattr(entry, "input_tokens")),
|
|
904
|
+
cached_input_tokens=int(getattr(entry, "cached_input_tokens")),
|
|
905
|
+
output_tokens=int(getattr(entry, "output_tokens")),
|
|
906
|
+
reasoning_output_tokens=int(getattr(entry, "reasoning_output_tokens")),
|
|
907
|
+
total_tokens=int(getattr(entry, "total_tokens")),
|
|
908
|
+
source_path=source_path,
|
|
909
|
+
))
|
|
910
|
+
return converted
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
def build_codex_source_state(
|
|
914
|
+
context: DashboardReadContext,
|
|
915
|
+
*,
|
|
916
|
+
data_version: str,
|
|
917
|
+
) -> SourceDashboardState:
|
|
918
|
+
"""Build Codex data strictly from the coordinated cache/stats reads.
|
|
919
|
+
|
|
920
|
+
No sync, rollout scan, CLI parser, or fallback is reachable from this
|
|
921
|
+
adapter. Period and session arithmetic remains delegated to the shipped
|
|
922
|
+
S3 view kernels, preserving the CLI's inclusive-token vocabulary.
|
|
923
|
+
"""
|
|
924
|
+
active_roots = tuple(sorted(
|
|
925
|
+
str(row[0]) for row in context.cache_conn.execute(
|
|
926
|
+
"SELECT source_root_key FROM codex_source_roots"
|
|
927
|
+
)
|
|
928
|
+
))
|
|
929
|
+
quota_observations = load_codex_quota_observations(
|
|
930
|
+
source_root_keys=active_roots,
|
|
931
|
+
cache_conn=context.cache_conn,
|
|
932
|
+
captured_at_or_after=(
|
|
933
|
+
context.now_utc - dt.timedelta(days=DASHBOARD_QUOTA_RECENT_DAYS)
|
|
934
|
+
),
|
|
935
|
+
active_at=context.now_utc,
|
|
936
|
+
max_rows=DASHBOARD_QUOTA_OBSERVATION_LIMIT,
|
|
937
|
+
)
|
|
938
|
+
coherence = codex_projection_coherence(
|
|
939
|
+
context,
|
|
940
|
+
)
|
|
941
|
+
if not coherence.coherent:
|
|
942
|
+
raise CodexProjectionIncoherent(coherence.reason)
|
|
943
|
+
# The cache reader's established report surface treats the ``now`` instant
|
|
944
|
+
# as inclusive. The qualified adapter is half-open, so extend only its
|
|
945
|
+
# query/result boundary by one microsecond and keep all live budget sums
|
|
946
|
+
# explicitly half-open at ``now`` below.
|
|
947
|
+
accounting_end = context.now_utc + dt.timedelta(microseconds=1)
|
|
948
|
+
accounting_start = context.range_start
|
|
949
|
+
if context.codex_budget is not None:
|
|
950
|
+
_period, budget_start, _budget_end = _configured_codex_budget_window(context)
|
|
951
|
+
accounting_start = min(accounting_start, budget_start)
|
|
952
|
+
qualified_entries = load_qualified_codex_entries(
|
|
953
|
+
accounting_start,
|
|
954
|
+
accounting_end,
|
|
955
|
+
speed=context.speed,
|
|
956
|
+
sync=False,
|
|
957
|
+
cache_conn=context.cache_conn,
|
|
958
|
+
)
|
|
959
|
+
budget_entries = _codex_entries_from_qualified(qualified_entries)
|
|
960
|
+
visible_qualified_entries = tuple(
|
|
961
|
+
entry for entry in qualified_entries
|
|
962
|
+
if context.range_start <= getattr(entry, "timestamp").astimezone(UTC) < accounting_end
|
|
963
|
+
)
|
|
964
|
+
entries = _codex_entries_from_qualified(visible_qualified_entries)
|
|
965
|
+
daily = build_codex_daily_view(
|
|
966
|
+
entries, now_utc=context.now_utc, tz_name=context.display_tz_name, speed=context.speed,
|
|
967
|
+
)
|
|
968
|
+
monthly = build_codex_monthly_view(
|
|
969
|
+
entries, now_utc=context.now_utc, tz_name=context.display_tz_name, speed=context.speed,
|
|
970
|
+
)
|
|
971
|
+
weekly = build_codex_weekly_view(
|
|
972
|
+
entries,
|
|
973
|
+
now_utc=context.now_utc,
|
|
974
|
+
tz_name=context.display_tz_name,
|
|
975
|
+
week_start_idx=context.week_start_idx,
|
|
976
|
+
speed=context.speed,
|
|
977
|
+
)
|
|
978
|
+
sessions = build_codex_session_view(
|
|
979
|
+
entries, now_utc=context.now_utc, tz_name=context.display_tz_name, speed=context.speed,
|
|
980
|
+
)
|
|
981
|
+
quota = _quota_read_model(context, quota_observations)
|
|
982
|
+
quota_blocks = _quota_wire(context.stats_conn)
|
|
983
|
+
quota = {**quota, "blocks": quota_blocks}
|
|
984
|
+
budget_rows = _budget_wire(context.stats_conn)
|
|
985
|
+
projected_budget_rows = _projected_budget_wire(context.stats_conn)
|
|
986
|
+
budget_cost_events = _codex_budget_cost_events(context, budget_entries)
|
|
987
|
+
configured_budget = _configured_codex_budget_status(
|
|
988
|
+
context, budget_entries, cost_events=budget_cost_events,
|
|
989
|
+
)
|
|
990
|
+
projects = _projects_wire(
|
|
991
|
+
context,
|
|
992
|
+
quota_observations,
|
|
993
|
+
visible_qualified_entries,
|
|
994
|
+
accounting_end=accounting_end,
|
|
995
|
+
)
|
|
996
|
+
alerts = _alerts_wire(context.stats_conn)
|
|
997
|
+
availability = "ok" if (entries or quota_blocks or budget_rows) else "empty"
|
|
998
|
+
total_input = sum(entry.input_tokens for entry in entries)
|
|
999
|
+
total_cached = sum(entry.cached_input_tokens for entry in entries)
|
|
1000
|
+
total_output = sum(entry.output_tokens for entry in entries)
|
|
1001
|
+
total_reasoning = sum(entry.reasoning_output_tokens for entry in entries)
|
|
1002
|
+
return SourceDashboardState(
|
|
1003
|
+
source="codex",
|
|
1004
|
+
availability=availability,
|
|
1005
|
+
freshness="fresh",
|
|
1006
|
+
warnings=(),
|
|
1007
|
+
data_version=data_version,
|
|
1008
|
+
last_success_at=context.now_utc,
|
|
1009
|
+
capabilities={
|
|
1010
|
+
"hero": CapabilityRecord("supported", "calendar-accounting"),
|
|
1011
|
+
"daily": CapabilityRecord("supported", "calendar-day"),
|
|
1012
|
+
"monthly": CapabilityRecord("supported", "calendar-month"),
|
|
1013
|
+
"weekly": CapabilityRecord("supported", "calendar-week"),
|
|
1014
|
+
"sessions": CapabilityRecord("supported", "inclusive-input-tokens"),
|
|
1015
|
+
"forensics": CapabilityRecord("supported", "inclusive-input-token-reuse"),
|
|
1016
|
+
"quota": CapabilityRecord("derived", "native-windows"),
|
|
1017
|
+
"budget": CapabilityRecord("supported", "calendar-period"),
|
|
1018
|
+
"projects": CapabilityRecord("supported", "qualified-attribution"),
|
|
1019
|
+
"alerts": CapabilityRecord("supported", "provider-native"),
|
|
1020
|
+
},
|
|
1021
|
+
data={
|
|
1022
|
+
"hero": {
|
|
1023
|
+
"cost_usd": daily.total_cost_usd,
|
|
1024
|
+
"input_tokens": total_input,
|
|
1025
|
+
"cached_input_tokens": total_cached,
|
|
1026
|
+
"output_tokens": total_output,
|
|
1027
|
+
"reasoning_output_tokens": total_reasoning,
|
|
1028
|
+
"total_tokens": daily.total_tokens,
|
|
1029
|
+
"quota": quota["summary"],
|
|
1030
|
+
"budget": configured_budget,
|
|
1031
|
+
"alerts": {"count": len(alerts)},
|
|
1032
|
+
},
|
|
1033
|
+
"periods": {
|
|
1034
|
+
"daily": _period_wire(daily),
|
|
1035
|
+
"monthly": _period_wire(monthly),
|
|
1036
|
+
"weekly": _period_wire(weekly),
|
|
1037
|
+
},
|
|
1038
|
+
"sessions": _session_wire(sessions),
|
|
1039
|
+
"quota": quota,
|
|
1040
|
+
"budget": {
|
|
1041
|
+
"status": configured_budget,
|
|
1042
|
+
"milestones": budget_rows,
|
|
1043
|
+
"projected": projected_budget_rows,
|
|
1044
|
+
},
|
|
1045
|
+
"projects": projects,
|
|
1046
|
+
"alerts": {"rows": alerts},
|
|
1047
|
+
},
|
|
1048
|
+
clock_data={"codex_budget_cost_events": budget_cost_events},
|
|
1049
|
+
)
|