Skip to content

Commit 685a4e0

Browse files
postadelmagaclaude
andcommitted
fix(claude): detect rolling 5h session window from actual usage
The fixed 03:00 UTC grid (5 slots of 5/5/5/5/4h) did not match Claude's real rate-limit behavior: each 5h window starts at the first message after a ≥5h gap, not at a clock boundary. Detect the window start dynamically by walking buffered usage events and fall back to the fixed grid only when there is no activity in the last 5h. Payload now exposes start_ts and source ("rolling" vs "fixed") alongside the existing end_ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 91851be commit 685a4e0

1 file changed

Lines changed: 45 additions & 3 deletions

File tree

ohmytoken/contents/code/local_stats.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
month_ts = (local_midnight - timedelta(days=30)).timestamp() * 1000
5353

5454
WINDOW_HOURS = [5, 5, 5, 5, 4]
55+
WINDOW_DURATION_MS = 5 * 3600 * 1000
5556
window_boundaries = []
5657
cum_h = 0
5758
for wh in WINDOW_HOURS:
@@ -66,9 +67,17 @@
6667
current_window_idx = i
6768
break
6869

70+
# Fallback (fixed grid) — overridden below once we've seen real activity.
6971
cur_win_start = window_boundaries[current_window_idx][0]
7072
cur_win_end = window_boundaries[current_window_idx][1]
7173

74+
# Buffer for rolling-window detection: Claude's real 5h rate-limit window
75+
# starts from the first message after a ≥5h gap, not from a fixed clock grid.
76+
# We keep ~6h of events so we can reconstruct the current window start from data.
77+
_WINDOW_BUFFER_MS = WINDOW_DURATION_MS + 3600 * 1000
78+
window_event_cutoff_ts = now_ms - _WINDOW_BUFFER_MS
79+
window_events = [] # [(ts_ms, inp, out, cr, cc)]
80+
7281
# --- Credentials & tier ---
7382
sub_type = "unknown"
7483
tier = "unknown"
@@ -174,9 +183,10 @@ def add_tokens_by_ts(ts_ms, inp, out, cr, cc):
174183
tok_today["cache_read"] += cr; tok_today["cache_create"] += cc
175184
tok_total["input"] += inp; tok_total["output"] += out
176185
tok_total["cache_read"] += cr; tok_total["cache_create"] += cc
177-
# Session window
178-
if cur_win_start <= ts_ms < cur_win_end:
179-
win_input += total_in; win_output += out
186+
# Session window: buffer events; computed after full scan so we can align
187+
# to the real rolling 5h window (first-message-after-gap), not a fixed grid.
188+
if ts_ms >= window_event_cutoff_ts:
189+
window_events.append((ts_ms, inp, out, cr, cc))
180190
# Rate tracking — keep components separate for per-type rates
181191
if ts_ms >= rate_cutoff_ts:
182192
recent_events.append((ts_ms, inp, out, cr, cc))
@@ -561,6 +571,36 @@ def calc_rate(window_ms, extract_fn):
561571
rate_output_5m = rate_output_30m = 0.0
562572
rate_all_5m = rate_all_30m = 0.0
563573

574+
# --- Rolling session window detection ---
575+
# Walk events ascending; a new window starts whenever an event arrives ≥5h
576+
# after the previous window start. The last computed start is the current one
577+
# if it hasn't expired yet; otherwise fall back to the fixed clock grid.
578+
window_source = "fixed"
579+
if window_events:
580+
window_events.sort(key=lambda e: e[0])
581+
ws_roll = window_events[0][0]
582+
for ev in window_events:
583+
if ev[0] >= ws_roll + WINDOW_DURATION_MS:
584+
ws_roll = ev[0]
585+
we_roll = ws_roll + WINDOW_DURATION_MS
586+
if we_roll > now_ms:
587+
cur_win_start = ws_roll
588+
cur_win_end = we_roll
589+
window_source = "rolling"
590+
# Realign window slot index to whichever fixed slot contains the
591+
# detected start (keeps the "N/5" indicator meaningful).
592+
for i, (ws_fb, we_fb) in enumerate(window_boundaries):
593+
if ws_fb <= ws_roll < we_fb:
594+
current_window_idx = i
595+
break
596+
597+
win_input = 0
598+
win_output = 0
599+
for ts, inp, out, cr, cc in window_events:
600+
if cur_win_start <= ts < cur_win_end:
601+
win_input += inp + cr + cc
602+
win_output += out
603+
564604
print(json.dumps({
565605
"subscription": {"type": sub_type, "tier": tier},
566606
"limits": limits,
@@ -570,7 +610,9 @@ def calc_rate(window_ms, extract_fn):
570610
"est_cost": {"today": round(cost_today, 4), "week": round(cost_week, 4), "total": round(cost_total, 4)},
571611
"session_window": {
572612
"number": current_window_idx + 1, "total": len(WINDOW_HOURS),
613+
"start_ts": int(cur_win_start),
573614
"end_ts": int(cur_win_end),
615+
"source": window_source,
574616
"input_limit": daily_in // 5, "output_limit": daily_out // 5,
575617
"input_used": win_input, "output_used": win_output,
576618
},

0 commit comments

Comments
 (0)