cctally 1.79.0 → 1.79.2
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.py +5 -3
- package/bin/_cctally_dashboard_sources.py +24 -10
- package/bin/_cctally_milestone_history.py +275 -154
- package/bin/_lib_milestone_history.py +1 -78
- package/dashboard/static/assets/index-BDkr0bdD.js +92 -0
- package/dashboard/static/dashboard.html +1 -1
- package/package.json +1 -1
- package/dashboard/static/assets/index-BwvAcS_k.js +0 -92
|
@@ -11,8 +11,8 @@ This module is the IMPURE counterpart to the pure kernel
|
|
|
11
11
|
``_lib_milestone_history.py``. Honest imports are restricted to pure
|
|
12
12
|
siblings (``_cctally_core`` kernel + ``_lib_*`` pure kernels, none of which
|
|
13
13
|
back-import ``cctally``); every impure cctally-namespace symbol
|
|
14
|
-
(``get_recent_weeks`` / ``
|
|
15
|
-
|
|
14
|
+
(``get_recent_weeks`` / ``_tui_build_five_hour_milestones`` / ``load_config``)
|
|
15
|
+
is reached via the
|
|
16
16
|
call-time ``_cctally()`` accessor so test monkeypatches through the
|
|
17
17
|
``cctally`` namespace are preserved.
|
|
18
18
|
|
|
@@ -24,8 +24,11 @@ host-local offset while boundaries are canonical UTC).
|
|
|
24
24
|
"""
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
+
import datetime as dt
|
|
28
|
+
import json
|
|
27
29
|
import sqlite3
|
|
28
30
|
import sys
|
|
31
|
+
from dataclasses import replace
|
|
29
32
|
|
|
30
33
|
from _cctally_core import make_week_ref, parse_iso_datetime
|
|
31
34
|
from _cctally_quota import codex_quota_breakdown
|
|
@@ -34,9 +37,6 @@ from _lib_display_tz import _resolve_display_tz_obj, format_display_dt
|
|
|
34
37
|
from _lib_json_envelope import _iso_z
|
|
35
38
|
from _lib_quota import QuotaWindowIdentity
|
|
36
39
|
|
|
37
|
-
import datetime as dt
|
|
38
|
-
import json
|
|
39
|
-
|
|
40
40
|
import _lib_milestone_history as _mh
|
|
41
41
|
|
|
42
42
|
UTC = dt.timezone.utc
|
|
@@ -115,7 +115,7 @@ def _navigable_claude_refs(conn: sqlite3.Connection) -> list:
|
|
|
115
115
|
|
|
116
116
|
Navigable set = weeks with ≥1 ``weekly_usage_snapshots`` row ∪ weeks with
|
|
117
117
|
≥1 ``percent_milestones`` row (cost-only weeks excluded). Same-key credit
|
|
118
|
-
|
|
118
|
+
reset-defined segment remains independently navigable; a defensive
|
|
119
119
|
milestone-only week absent from ``get_recent_weeks`` is synthesized from
|
|
120
120
|
its milestone rows' stored boundaries.
|
|
121
121
|
"""
|
|
@@ -137,14 +137,104 @@ def _navigable_claude_refs(conn: sqlite3.Connection) -> list:
|
|
|
137
137
|
navigable = usage_keys | milestone_keys
|
|
138
138
|
|
|
139
139
|
refs = [r for r in refs if r.key in navigable]
|
|
140
|
-
refs = _mh.coalesce_week_refs(refs)
|
|
141
|
-
|
|
142
140
|
present = {r.key for r in refs}
|
|
143
141
|
for k in sorted(milestone_keys - present, reverse=True):
|
|
144
142
|
ref = _synthesize_milestone_only_ref(conn, k)
|
|
145
143
|
if ref is not None:
|
|
146
144
|
refs.append(ref)
|
|
147
|
-
return refs
|
|
145
|
+
return _derive_claude_reset_cycles(conn, refs)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _parse_optional_iso(value):
|
|
149
|
+
if not value:
|
|
150
|
+
return None
|
|
151
|
+
try:
|
|
152
|
+
return parse_iso_datetime(value, "milestone_cycle.boundary")
|
|
153
|
+
except (TypeError, ValueError):
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _claude_storage_boundaries(conn: sqlite3.Connection, key: str):
|
|
158
|
+
"""Return the outer retained boundary for one storage date bucket."""
|
|
159
|
+
rows = conn.execute(
|
|
160
|
+
"SELECT week_start_at, week_end_at FROM weekly_usage_snapshots "
|
|
161
|
+
"WHERE week_start_date=? AND week_start_at IS NOT NULL "
|
|
162
|
+
"AND week_end_at IS NOT NULL "
|
|
163
|
+
"UNION ALL "
|
|
164
|
+
"SELECT week_start_at, week_end_at FROM percent_milestones "
|
|
165
|
+
"WHERE week_start_date=? AND week_start_at IS NOT NULL "
|
|
166
|
+
"AND week_end_at IS NOT NULL",
|
|
167
|
+
(key, key),
|
|
168
|
+
).fetchall()
|
|
169
|
+
pairs = [
|
|
170
|
+
(start, end)
|
|
171
|
+
for row in rows
|
|
172
|
+
if (start := _parse_optional_iso(row[0])) is not None
|
|
173
|
+
and (end := _parse_optional_iso(row[1])) is not None
|
|
174
|
+
and end > start
|
|
175
|
+
]
|
|
176
|
+
if not pairs:
|
|
177
|
+
return None
|
|
178
|
+
return min(start for start, _end in pairs), max(end for _start, end in pairs)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _linked_reset_events(conn: sqlite3.Connection, key: str) -> list:
|
|
182
|
+
"""Reset rows linked to any retained boundary in a storage bucket."""
|
|
183
|
+
return conn.execute(
|
|
184
|
+
"SELECT DISTINCT e.id, e.effective_reset_at_utc "
|
|
185
|
+
"FROM week_reset_events e "
|
|
186
|
+
"WHERE EXISTS ("
|
|
187
|
+
" SELECT 1 FROM weekly_usage_snapshots s "
|
|
188
|
+
" WHERE s.week_start_date=? AND ("
|
|
189
|
+
" unixepoch(s.week_end_at)=unixepoch(e.old_week_end_at) OR "
|
|
190
|
+
" unixepoch(s.week_end_at)=unixepoch(e.new_week_end_at)"
|
|
191
|
+
" )"
|
|
192
|
+
") OR EXISTS ("
|
|
193
|
+
" SELECT 1 FROM percent_milestones m "
|
|
194
|
+
" WHERE m.week_start_date=? AND ("
|
|
195
|
+
" unixepoch(m.week_end_at)=unixepoch(e.old_week_end_at) OR "
|
|
196
|
+
" unixepoch(m.week_end_at)=unixepoch(e.new_week_end_at)"
|
|
197
|
+
" )"
|
|
198
|
+
") "
|
|
199
|
+
"ORDER BY unixepoch(e.effective_reset_at_utc), e.id",
|
|
200
|
+
(key, key),
|
|
201
|
+
).fetchall()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _derive_claude_reset_cycles(conn: sqlite3.Connection, refs: list) -> list:
|
|
205
|
+
"""Expand storage buckets into provider reset-defined cycles.
|
|
206
|
+
|
|
207
|
+
``get_recent_weeks`` can split an in-place credit, but an early re-anchor
|
|
208
|
+
whose old and new boundaries both remain under one ``week_start_date``
|
|
209
|
+
only exposes the pre-reset half. The retained snapshot boundary set plus
|
|
210
|
+
``week_reset_events`` is the authoritative reset ledger for both shapes.
|
|
211
|
+
"""
|
|
212
|
+
out: list = []
|
|
213
|
+
by_key: dict = {}
|
|
214
|
+
for ref in refs:
|
|
215
|
+
by_key.setdefault(ref.key, ref)
|
|
216
|
+
for key, template in by_key.items():
|
|
217
|
+
outer = _claude_storage_boundaries(conn, key)
|
|
218
|
+
if outer is None:
|
|
219
|
+
out.append(template)
|
|
220
|
+
continue
|
|
221
|
+
start, end = outer
|
|
222
|
+
cuts = []
|
|
223
|
+
for event in _linked_reset_events(conn, key):
|
|
224
|
+
effective = _parse_optional_iso(event["effective_reset_at_utc"])
|
|
225
|
+
if effective is not None and start < effective < end:
|
|
226
|
+
cuts.append(effective)
|
|
227
|
+
boundaries = [start, *sorted(set(cuts)), end]
|
|
228
|
+
cycles = []
|
|
229
|
+
for cycle_start, cycle_end in zip(boundaries, boundaries[1:]):
|
|
230
|
+
cycles.append(replace(
|
|
231
|
+
template,
|
|
232
|
+
week_start_at=cycle_start.isoformat(timespec="seconds"),
|
|
233
|
+
week_end_at=cycle_end.isoformat(timespec="seconds"),
|
|
234
|
+
week_end=(cycle_end - dt.timedelta(seconds=1)).date(),
|
|
235
|
+
))
|
|
236
|
+
out.extend(reversed(cycles))
|
|
237
|
+
return _cctally()._apply_overlap_clamp_to_weekrefs(out)
|
|
148
238
|
|
|
149
239
|
|
|
150
240
|
def _synthesize_milestone_only_ref(conn: sqlite3.Connection, key: str):
|
|
@@ -181,31 +271,65 @@ def _current_claude_week_key(conn: sqlite3.Connection) -> "str | None":
|
|
|
181
271
|
return row[0] if row is not None else None
|
|
182
272
|
|
|
183
273
|
|
|
184
|
-
def
|
|
185
|
-
|
|
186
|
-
"
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
274
|
+
def _claude_cycle_key(ref) -> str:
|
|
275
|
+
return dashboard_resource_key(
|
|
276
|
+
"milestone_cycle", "claude", ref.key,
|
|
277
|
+
ref.week_start_at or "", ref.week_end_at or "",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _claude_cycle_rows(conn: sqlite3.Connection, ref) -> list:
|
|
282
|
+
cohort_id = 0
|
|
283
|
+
if ref.week_start_at:
|
|
284
|
+
row = conn.execute(
|
|
285
|
+
"SELECT e.id FROM week_reset_events e "
|
|
286
|
+
"WHERE unixepoch(e.effective_reset_at_utc)=unixepoch(?) "
|
|
287
|
+
"AND (EXISTS ("
|
|
288
|
+
" SELECT 1 FROM weekly_usage_snapshots s "
|
|
289
|
+
" WHERE s.week_start_date=? AND ("
|
|
290
|
+
" unixepoch(s.week_end_at)=unixepoch(e.old_week_end_at) OR "
|
|
291
|
+
" unixepoch(s.week_end_at)=unixepoch(e.new_week_end_at)"
|
|
292
|
+
" )"
|
|
293
|
+
") OR EXISTS ("
|
|
294
|
+
" SELECT 1 FROM percent_milestones m "
|
|
295
|
+
" WHERE m.week_start_date=? AND ("
|
|
296
|
+
" unixepoch(m.week_end_at)=unixepoch(e.old_week_end_at) OR "
|
|
297
|
+
" unixepoch(m.week_end_at)=unixepoch(e.new_week_end_at)"
|
|
298
|
+
" )"
|
|
299
|
+
")) ORDER BY e.id DESC LIMIT 1",
|
|
300
|
+
(ref.week_start_at, ref.key, ref.key),
|
|
301
|
+
).fetchone()
|
|
302
|
+
if row is not None:
|
|
303
|
+
cohort_id = int(row[0])
|
|
304
|
+
return conn.execute(
|
|
305
|
+
"SELECT * FROM percent_milestones WHERE week_start_date=? "
|
|
306
|
+
"AND reset_event_id=? "
|
|
307
|
+
"ORDER BY unixepoch(captured_at_utc) ASC, percent_threshold ASC",
|
|
308
|
+
(ref.key, cohort_id),
|
|
309
|
+
).fetchall()
|
|
310
|
+
|
|
193
311
|
|
|
312
|
+
def _index_entry(conn, ref, current_cycle_key, tz) -> dict:
|
|
313
|
+
rows = _claude_cycle_rows(conn, ref)
|
|
314
|
+
milestone_count = len(rows)
|
|
315
|
+
segment_count = 1 if rows else 0
|
|
316
|
+
max_captured = max((r["captured_at_utc"] for r in rows), default=None)
|
|
194
317
|
start_z = _to_iso_z(ref.week_start_at)
|
|
195
318
|
end_z = _to_iso_z(ref.week_end_at)
|
|
196
319
|
block_count = _count_blocks(conn, start_z, end_z)
|
|
320
|
+
key = _claude_cycle_key(ref)
|
|
197
321
|
|
|
198
322
|
return {
|
|
199
|
-
"key":
|
|
323
|
+
"key": key,
|
|
200
324
|
"start_at_utc": start_z,
|
|
201
325
|
"end_at_utc": end_z,
|
|
202
326
|
"label": _week_label(ref, tz),
|
|
203
|
-
"is_current":
|
|
327
|
+
"is_current": key == current_cycle_key,
|
|
204
328
|
"milestone_count": milestone_count,
|
|
205
329
|
"block_count": block_count,
|
|
206
330
|
"segment_count": segment_count,
|
|
207
331
|
"detail_stamp": _mh.compute_detail_stamp(
|
|
208
|
-
|
|
332
|
+
key, milestone_count, block_count, segment_count, max_captured
|
|
209
333
|
),
|
|
210
334
|
}
|
|
211
335
|
|
|
@@ -213,16 +337,21 @@ def _index_entry(conn, ref, current_key, tz) -> dict:
|
|
|
213
337
|
def build_claude_week_index(conn: sqlite3.Connection) -> list:
|
|
214
338
|
"""Newest-first navigable Claude week index (spec §1a, §3).
|
|
215
339
|
|
|
216
|
-
One entry per
|
|
217
|
-
|
|
218
|
-
is a content digest that moves when the week's underlying rows change so
|
|
219
|
-
the client cache revalidates.
|
|
340
|
+
One entry per effective reset-defined cycle. Multiple cycles may share one
|
|
341
|
+
storage ``week_start_date`` but always have distinct opaque keys.
|
|
220
342
|
"""
|
|
221
343
|
refs = _navigable_claude_refs(conn)
|
|
222
344
|
current_key = _current_claude_week_key(conn)
|
|
345
|
+
current_refs = [r for r in refs if r.key == current_key]
|
|
346
|
+
current_ref = max(
|
|
347
|
+
current_refs,
|
|
348
|
+
key=lambda r: r.week_start_at or "",
|
|
349
|
+
default=None,
|
|
350
|
+
)
|
|
351
|
+
current_cycle_key = _claude_cycle_key(current_ref) if current_ref else None
|
|
223
352
|
tz = _resolve_display_tz()
|
|
224
|
-
entries = [_index_entry(conn, ref,
|
|
225
|
-
entries.sort(key=lambda e: e["
|
|
353
|
+
entries = [_index_entry(conn, ref, current_cycle_key, tz) for ref in refs]
|
|
354
|
+
entries.sort(key=lambda e: e["start_at_utc"] or "", reverse=True)
|
|
226
355
|
return entries
|
|
227
356
|
|
|
228
357
|
|
|
@@ -244,58 +373,6 @@ def _shape_weekly_milestone(row) -> dict:
|
|
|
244
373
|
}
|
|
245
374
|
|
|
246
375
|
|
|
247
|
-
def _build_segments(rows) -> list:
|
|
248
|
-
"""Group milestone rows into segments (by ``reset_event_id``), ordered
|
|
249
|
-
chronologically by each segment's first ``captured_at_utc``; rows within a
|
|
250
|
-
segment ascend by capture time then threshold (spec §1b)."""
|
|
251
|
-
groups: dict = {}
|
|
252
|
-
for r in rows:
|
|
253
|
-
seg = int(r["reset_event_id"] or 0)
|
|
254
|
-
groups.setdefault(seg, []).append(r)
|
|
255
|
-
ordered: list = []
|
|
256
|
-
for seg, seg_rows in groups.items():
|
|
257
|
-
seg_rows.sort(key=lambda r: (r["captured_at_utc"], int(r["percent_threshold"])))
|
|
258
|
-
first_cap = seg_rows[0]["captured_at_utc"]
|
|
259
|
-
ordered.append(
|
|
260
|
-
(first_cap, seg, [_shape_weekly_milestone(r) for r in seg_rows])
|
|
261
|
-
)
|
|
262
|
-
ordered.sort(key=lambda t: (t[0], t[1]))
|
|
263
|
-
return [{"reset_event_id": seg, "milestones": ms} for (_c, seg, ms) in ordered]
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def _build_dividers(conn: sqlite3.Connection, segments: list) -> list:
|
|
267
|
-
"""Credit dividers between consecutive segments (spec §1b, Q4).
|
|
268
|
-
|
|
269
|
-
A divider is emitted for each later segment whose ``reset_event_id``
|
|
270
|
-
resolves to an in-place-credit ``week_reset_events`` row
|
|
271
|
-
(``old_week_end_at == effective_reset_at_utc``). It carries the effective
|
|
272
|
-
time + prior percent (``observed_pre_credit_pct``); no post/delta value is
|
|
273
|
-
recorded for weekly credits.
|
|
274
|
-
"""
|
|
275
|
-
dividers: list = []
|
|
276
|
-
for later in segments[1:]:
|
|
277
|
-
seg_id = later["reset_event_id"]
|
|
278
|
-
if not seg_id:
|
|
279
|
-
continue
|
|
280
|
-
ev = conn.execute(
|
|
281
|
-
"SELECT effective_reset_at_utc, observed_pre_credit_pct, old_week_end_at "
|
|
282
|
-
"FROM week_reset_events WHERE id = ?",
|
|
283
|
-
(seg_id,),
|
|
284
|
-
).fetchone()
|
|
285
|
-
if ev is None:
|
|
286
|
-
continue
|
|
287
|
-
if ev["old_week_end_at"] != ev["effective_reset_at_utc"]:
|
|
288
|
-
continue # not an in-place credit → no divider
|
|
289
|
-
prior = ev["observed_pre_credit_pct"]
|
|
290
|
-
dividers.append(
|
|
291
|
-
{
|
|
292
|
-
"effective_at_utc": _to_iso_z(ev["effective_reset_at_utc"]),
|
|
293
|
-
"prior_percent": None if prior is None else float(prior),
|
|
294
|
-
}
|
|
295
|
-
)
|
|
296
|
-
return dividers
|
|
297
|
-
|
|
298
|
-
|
|
299
376
|
def _load_block_credits(conn: sqlite3.Connection, window_key: int) -> list:
|
|
300
377
|
"""5h in-place credit rows for a block, ascending by effective time —
|
|
301
378
|
same wire shape as the envelope's ``five_hour_block.credits``."""
|
|
@@ -354,30 +431,21 @@ def _build_blocks(conn: sqlite3.Connection, start_iso, end_iso) -> list:
|
|
|
354
431
|
return out
|
|
355
432
|
|
|
356
433
|
|
|
357
|
-
def build_claude_week_detail(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
Segments carry ALL ``reset_event_id`` cohorts (both pre- and post-credit)
|
|
364
|
-
ordered chronologically; ``dividers`` sit between consecutive segments;
|
|
365
|
-
``blocks`` are the intersecting 5h blocks (dual-membership straddlers
|
|
366
|
-
included). Boundary/label/``is_current``/``detail_stamp`` are taken from
|
|
367
|
-
the index entry so the detail and index agree exactly.
|
|
368
|
-
"""
|
|
369
|
-
c = _cctally()
|
|
370
|
-
entry = None
|
|
371
|
-
for e in build_claude_week_index(conn):
|
|
372
|
-
if e["key"] == week_start_date:
|
|
373
|
-
entry = e
|
|
374
|
-
break
|
|
375
|
-
if entry is None:
|
|
434
|
+
def build_claude_week_detail(conn: sqlite3.Connection, key: str) -> "dict | None":
|
|
435
|
+
"""Complete payload for one reset-defined Claude cycle."""
|
|
436
|
+
refs = _navigable_claude_refs(conn)
|
|
437
|
+
ref = next((candidate for candidate in refs if _claude_cycle_key(candidate) == key), None)
|
|
438
|
+
if ref is None:
|
|
376
439
|
return None
|
|
377
|
-
|
|
378
|
-
rows =
|
|
379
|
-
|
|
380
|
-
|
|
440
|
+
entry = next(e for e in build_claude_week_index(conn) if e["key"] == key)
|
|
441
|
+
rows = _claude_cycle_rows(conn, ref)
|
|
442
|
+
segment_key = dashboard_resource_key(
|
|
443
|
+
"milestone_segment", "claude", ref.key,
|
|
444
|
+
ref.week_start_at or "", ref.week_end_at or "",
|
|
445
|
+
)
|
|
446
|
+
segments = ([{"key": segment_key,
|
|
447
|
+
"milestones": [_shape_weekly_milestone(r) for r in rows]}]
|
|
448
|
+
if rows else [])
|
|
381
449
|
blocks = _build_blocks(conn, entry["start_at_utc"], entry["end_at_utc"])
|
|
382
450
|
|
|
383
451
|
return {
|
|
@@ -389,7 +457,7 @@ def build_claude_week_detail(
|
|
|
389
457
|
"is_current": entry["is_current"],
|
|
390
458
|
"detail_stamp": entry["detail_stamp"],
|
|
391
459
|
"segments": segments,
|
|
392
|
-
"dividers":
|
|
460
|
+
"dividers": [],
|
|
393
461
|
"blocks": blocks,
|
|
394
462
|
}
|
|
395
463
|
|
|
@@ -400,9 +468,9 @@ def build_claude_week_detail(
|
|
|
400
468
|
# (quota_window_blocks, stats.db) — explicitly NOT the bounded public quota
|
|
401
469
|
# read model (35-day / 1000-observation / 250-row caps). Boundaries are the
|
|
402
470
|
# effective clipped, non-overlapping periods: a provider early re-anchor
|
|
403
|
-
# ends the prior cycle at min(raw reset, next nominal start).
|
|
404
|
-
#
|
|
405
|
-
#
|
|
471
|
+
# ends the prior cycle at min(raw reset, next nominal start). One full native
|
|
472
|
+
# identity is selected per physical reset; the opaque cycle key retains that
|
|
473
|
+
# identity so detail resolves the exact selected ledger.
|
|
406
474
|
#
|
|
407
475
|
# `_codex_weekly_periods` (bin/_cctally_dashboard_sources.py) computes the
|
|
408
476
|
# same clip formula but collapses across roots (losing the per-identity key)
|
|
@@ -490,6 +558,10 @@ class _CodexCycle:
|
|
|
490
558
|
def key_parts(self) -> tuple:
|
|
491
559
|
return (self.root, self.limit, self.slot, self.window, self.reset_iso)
|
|
492
560
|
|
|
561
|
+
@property
|
|
562
|
+
def identity_parts(self) -> tuple:
|
|
563
|
+
return (self.root, self.limit, self.slot, self.window)
|
|
564
|
+
|
|
493
565
|
@property
|
|
494
566
|
def key(self) -> str:
|
|
495
567
|
return dashboard_resource_key("milestone_cycle", "codex", *self.key_parts)
|
|
@@ -499,9 +571,62 @@ class _CodexCycle:
|
|
|
499
571
|
return dashboard_resource_key("block", "codex", *self.key_parts)
|
|
500
572
|
|
|
501
573
|
|
|
502
|
-
def
|
|
503
|
-
|
|
504
|
-
|
|
574
|
+
def _identity_matches_cycle(identity, cyc) -> bool:
|
|
575
|
+
return (
|
|
576
|
+
identity is not None
|
|
577
|
+
and getattr(identity, "source_root_key", None) == cyc.root
|
|
578
|
+
and getattr(identity, "logical_limit_key", None) == cyc.limit
|
|
579
|
+
and getattr(identity, "observed_slot", None) == cyc.slot
|
|
580
|
+
and getattr(identity, "window_minutes", None) == cyc.window
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _choose_physical_cycle(candidates: list, *, preferred_identity=None) -> _CodexCycle:
|
|
585
|
+
preferred = [c for c in candidates if _identity_matches_cycle(preferred_identity, c)]
|
|
586
|
+
pool = preferred or candidates
|
|
587
|
+
return max(
|
|
588
|
+
pool,
|
|
589
|
+
key=lambda c: (
|
|
590
|
+
-1.0 if c.current_percent is None else c.current_percent,
|
|
591
|
+
c.reset,
|
|
592
|
+
c.identity_parts,
|
|
593
|
+
),
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def _canonical_identity_cycles(cycles: list) -> list:
|
|
598
|
+
"""Jitter-collapse rows within each full native identity."""
|
|
599
|
+
groups: dict = {}
|
|
600
|
+
for cyc in cycles:
|
|
601
|
+
groups.setdefault(cyc.identity_parts, []).append(cyc)
|
|
602
|
+
return [
|
|
603
|
+
_canonicalize_codex_cluster(cluster)
|
|
604
|
+
for members in groups.values()
|
|
605
|
+
for cluster in _mh.cluster_by_reset_jitter(
|
|
606
|
+
members, reset_key=lambda c: c.reset.timestamp()
|
|
607
|
+
)
|
|
608
|
+
]
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _select_physical_cycles(cycles: list, *, preferred_identity=None) -> list:
|
|
612
|
+
"""Select one full identity per physical reset boundary, then clip globally."""
|
|
613
|
+
identity_cycles = _canonical_identity_cycles(cycles)
|
|
614
|
+
selected = [
|
|
615
|
+
_choose_physical_cycle(cluster, preferred_identity=preferred_identity)
|
|
616
|
+
for cluster in _mh.cluster_by_reset_jitter(
|
|
617
|
+
identity_cycles, reset_key=lambda c: c.reset.timestamp()
|
|
618
|
+
)
|
|
619
|
+
]
|
|
620
|
+
selected.sort(key=lambda c: (c.start, c.reset, c.identity_parts))
|
|
621
|
+
for index, cyc in enumerate(selected):
|
|
622
|
+
next_start = selected[index + 1].start if index + 1 < len(selected) else None
|
|
623
|
+
cyc.end = min(cyc.reset, next_start) if next_start is not None else cyc.reset
|
|
624
|
+
return [c for c in selected if c.end > c.start]
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _load_codex_cycles(stats_conn, root_keys, *, include_orphaned=False,
|
|
628
|
+
preferred_identity=None) -> list:
|
|
629
|
+
"""Unbounded reset-defined 7-day cycles, one selected identity per boundary."""
|
|
505
630
|
roots = tuple(sorted({r for r in root_keys if isinstance(r, str) and r}))
|
|
506
631
|
if not roots:
|
|
507
632
|
return []
|
|
@@ -528,34 +653,9 @@ def _load_codex_cycles(stats_conn, root_keys, *, include_orphaned=False) -> list
|
|
|
528
653
|
continue
|
|
529
654
|
cycles.append(cyc)
|
|
530
655
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
# fan of degenerate 1-second spans (ui-qa P2). Genuine early re-anchors
|
|
535
|
-
# (hours apart) stay distinct (see ``cluster_by_reset_jitter``).
|
|
536
|
-
groups: dict = {}
|
|
537
|
-
for cyc in cycles:
|
|
538
|
-
groups.setdefault((cyc.root, cyc.limit, cyc.slot), []).append(cyc)
|
|
539
|
-
clustered: list = []
|
|
540
|
-
for members in groups.values():
|
|
541
|
-
for cluster in _mh.cluster_by_reset_jitter(
|
|
542
|
-
members, reset_key=lambda c: c.reset.timestamp()
|
|
543
|
-
):
|
|
544
|
-
clustered.append(_canonicalize_codex_cluster(cluster))
|
|
545
|
-
|
|
546
|
-
# Effective clip per identity: end each canonical cycle at the NEXT
|
|
547
|
-
# canonical cycle's start (min with its own reset), non-overlapping.
|
|
548
|
-
cgroups: dict = {}
|
|
549
|
-
for cyc in clustered:
|
|
550
|
-
cgroups.setdefault((cyc.root, cyc.limit, cyc.slot), []).append(cyc)
|
|
551
|
-
for members in cgroups.values():
|
|
552
|
-
members.sort(key=lambda c: c.start)
|
|
553
|
-
for i, cyc in enumerate(members):
|
|
554
|
-
nxt = members[i + 1].start if i + 1 < len(members) else None
|
|
555
|
-
cyc.end = min(cyc.reset, nxt) if nxt is not None else cyc.reset
|
|
556
|
-
clustered = [c for c in clustered if c.end > c.start]
|
|
557
|
-
clustered.sort(key=lambda c: c.reset, reverse=True)
|
|
558
|
-
return clustered
|
|
656
|
+
selected = _select_physical_cycles(cycles, preferred_identity=preferred_identity)
|
|
657
|
+
selected.sort(key=lambda c: c.start, reverse=True)
|
|
658
|
+
return selected
|
|
559
659
|
|
|
560
660
|
|
|
561
661
|
def _canonicalize_codex_cluster(members: list) -> "_CodexCycle":
|
|
@@ -596,12 +696,12 @@ def _codex_is_current(cyc, identity, now_utc) -> bool:
|
|
|
596
696
|
|
|
597
697
|
|
|
598
698
|
def _codex_milestone_count(stats_conn, cyc) -> tuple[int, str | None]:
|
|
599
|
-
"""
|
|
600
|
-
jittered member reset contributes (rows split across sibling resets)."""
|
|
699
|
+
"""Unique threshold count + max captured-at for one selected identity."""
|
|
601
700
|
resets = cyc.member_reset_isos
|
|
602
701
|
placeholders = ",".join("unixepoch(?)" for _ in resets)
|
|
603
702
|
row = stats_conn.execute(
|
|
604
|
-
"SELECT COUNT(
|
|
703
|
+
"SELECT COUNT(DISTINCT percent_threshold), MAX(captured_at_utc) "
|
|
704
|
+
"FROM quota_percent_milestones "
|
|
605
705
|
"WHERE source='codex' AND source_root_key=? AND logical_limit_key=? "
|
|
606
706
|
" AND observed_slot=? AND window_minutes=? "
|
|
607
707
|
f" AND unixepoch(resets_at_utc) IN ({placeholders}) AND orphaned_at IS NULL",
|
|
@@ -611,8 +711,7 @@ def _codex_milestone_count(stats_conn, cyc) -> tuple[int, str | None]:
|
|
|
611
711
|
|
|
612
712
|
|
|
613
713
|
def _codex_five_hour_rows(stats_conn, cyc, *, include_orphaned=False) -> list:
|
|
614
|
-
"""
|
|
615
|
-
(root + observed_slot + limit_id) intersecting [start, end)."""
|
|
714
|
+
"""Every retained 5h block on the selected root intersecting [start, end)."""
|
|
616
715
|
orphan_clause = "" if include_orphaned else "AND orphaned_at IS NULL "
|
|
617
716
|
return stats_conn.execute(
|
|
618
717
|
"SELECT source_root_key, logical_limit_key, observed_slot, "
|
|
@@ -621,12 +720,12 @@ def _codex_five_hour_rows(stats_conn, cyc, *, include_orphaned=False) -> list:
|
|
|
621
720
|
"FROM quota_window_blocks "
|
|
622
721
|
"WHERE source='codex' AND window_minutes=300 "
|
|
623
722
|
f"{orphan_clause}"
|
|
624
|
-
"AND source_root_key=?
|
|
723
|
+
"AND source_root_key=? "
|
|
625
724
|
"AND unixepoch(nominal_start_at_utc) < unixepoch(?) "
|
|
626
725
|
"AND unixepoch(resets_at_utc) > unixepoch(?) "
|
|
627
726
|
"ORDER BY unixepoch(nominal_start_at_utc) ASC",
|
|
628
|
-
(cyc.root, cyc.
|
|
629
|
-
cyc.
|
|
727
|
+
(cyc.root, cyc.end.astimezone(UTC).isoformat(),
|
|
728
|
+
cyc.start.astimezone(UTC).isoformat()),
|
|
630
729
|
).fetchall()
|
|
631
730
|
|
|
632
731
|
|
|
@@ -645,10 +744,11 @@ def _codex_five_hour_clusters(stats_conn, cyc, *, include_orphaned=False) -> lis
|
|
|
645
744
|
if block.start is None or block.reset is None:
|
|
646
745
|
continue
|
|
647
746
|
blocks.append(block)
|
|
747
|
+
identity_clusters = _canonical_identity_cycles(blocks)
|
|
648
748
|
clusters = [
|
|
649
|
-
|
|
749
|
+
_choose_physical_cycle(members)
|
|
650
750
|
for members in _mh.cluster_by_reset_jitter(
|
|
651
|
-
|
|
751
|
+
identity_clusters, reset_key=lambda b: b.reset.timestamp()
|
|
652
752
|
)
|
|
653
753
|
]
|
|
654
754
|
clusters.sort(key=lambda b: b.start)
|
|
@@ -686,11 +786,14 @@ def _codex_cycle_label(cyc, tz) -> str:
|
|
|
686
786
|
|
|
687
787
|
def build_codex_cycle_index(stats_conn, *, identity, now_utc) -> list:
|
|
688
788
|
"""Newest-first Codex cycle index over the durable projection (spec §1c,
|
|
689
|
-
§3). Enumerates
|
|
690
|
-
|
|
789
|
+
§3). Enumerates the hero root's reset-defined ledger with no depth cap;
|
|
790
|
+
one selected full native identity per physical reset."""
|
|
691
791
|
now = now_utc.astimezone(UTC) if now_utc.tzinfo else now_utc.replace(tzinfo=UTC)
|
|
692
792
|
tz = _resolve_display_tz()
|
|
693
|
-
cycles = _load_codex_cycles(
|
|
793
|
+
cycles = _load_codex_cycles(
|
|
794
|
+
stats_conn, getattr(identity, "source_root_keys", ()),
|
|
795
|
+
preferred_identity=getattr(identity, "quota_identity", None),
|
|
796
|
+
)
|
|
694
797
|
return [_codex_cycle_entry(stats_conn, c, identity, now, tz) for c in cycles]
|
|
695
798
|
|
|
696
799
|
|
|
@@ -753,8 +856,19 @@ def _union_cluster_milestones(cluster, block_key, speed, cache_conn, stats_conn)
|
|
|
753
856
|
_shape_codex_milestone(r, key_parts=member.key_parts, block_key=block_key)
|
|
754
857
|
for r in breakdown
|
|
755
858
|
)
|
|
756
|
-
|
|
757
|
-
|
|
859
|
+
# One physical quota ledger has one crossing per integer threshold. Jitter
|
|
860
|
+
# siblings can contribute missing evidence, but never duplicate a row.
|
|
861
|
+
by_percent: dict[int, dict] = {}
|
|
862
|
+
for row in shaped:
|
|
863
|
+
percent = int(row["percent"])
|
|
864
|
+
prior = by_percent.get(percent)
|
|
865
|
+
if prior is None or (row["captured_at"] or "", row["key"]) < (
|
|
866
|
+
prior["captured_at"] or "", prior["key"]
|
|
867
|
+
):
|
|
868
|
+
by_percent[percent] = row
|
|
869
|
+
return sorted(
|
|
870
|
+
by_percent.values(), key=lambda m: (m["captured_at"] or "", m["percent"])
|
|
871
|
+
)
|
|
758
872
|
|
|
759
873
|
|
|
760
874
|
def _codex_cycle_blocks(stats_conn, cache_conn, cyc, speed) -> "list | None":
|
|
@@ -801,7 +915,8 @@ def build_codex_cycle_detail(
|
|
|
801
915
|
tz = _resolve_display_tz()
|
|
802
916
|
try:
|
|
803
917
|
cycles = _load_codex_cycles(
|
|
804
|
-
stats_conn, getattr(identity, "source_root_keys", ())
|
|
918
|
+
stats_conn, getattr(identity, "source_root_keys", ()),
|
|
919
|
+
preferred_identity=getattr(identity, "quota_identity", None),
|
|
805
920
|
)
|
|
806
921
|
except sqlite3.Error:
|
|
807
922
|
return (None, "projection_incoherent")
|
|
@@ -813,6 +928,7 @@ def build_codex_cycle_detail(
|
|
|
813
928
|
orphaned = _load_codex_cycles(
|
|
814
929
|
stats_conn, getattr(identity, "source_root_keys", ()),
|
|
815
930
|
include_orphaned=True,
|
|
931
|
+
preferred_identity=getattr(identity, "quota_identity", None),
|
|
816
932
|
)
|
|
817
933
|
except sqlite3.Error:
|
|
818
934
|
return (None, "projection_incoherent")
|
|
@@ -839,7 +955,12 @@ def build_codex_cycle_detail(
|
|
|
839
955
|
"label": entry["label"],
|
|
840
956
|
"is_current": entry["is_current"],
|
|
841
957
|
"detail_stamp": entry["detail_stamp"],
|
|
842
|
-
"segments": [{
|
|
958
|
+
"segments": [{
|
|
959
|
+
"key": dashboard_resource_key(
|
|
960
|
+
"milestone_segment", "codex", *match.key_parts
|
|
961
|
+
),
|
|
962
|
+
"milestones": milestones,
|
|
963
|
+
}],
|
|
843
964
|
"dividers": [],
|
|
844
965
|
"blocks": blocks,
|
|
845
966
|
}
|