cctally 1.7.2 → 1.7.4
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 +27 -0
- package/bin/_cctally_alerts.py +12 -5
- package/bin/_cctally_cache.py +12 -11
- package/bin/_cctally_config.py +34 -19
- package/bin/_cctally_core.py +890 -0
- package/bin/_cctally_dashboard.py +104 -113
- package/bin/_cctally_db.py +271 -41
- package/bin/_cctally_record.py +516 -116
- package/bin/_cctally_refresh.py +35 -20
- package/bin/_cctally_setup.py +26 -16
- package/bin/_cctally_sync_week.py +21 -6
- package/bin/_cctally_tui.py +128 -52
- package/bin/_cctally_update.py +11 -16
- package/bin/_lib_aggregators.py +7 -1
- package/bin/_lib_diff_kernel.py +19 -21
- package/bin/_lib_render.py +20 -0
- package/bin/_lib_subscription_weeks.py +17 -9
- package/bin/cctally +191 -779
- package/dashboard/static/assets/index-DhCnIFq9.js +18 -0
- package/dashboard/static/assets/index-Dv5Dzag5.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +2 -1
- package/dashboard/static/assets/index-BgpoazlS.js +0 -18
- package/dashboard/static/assets/index-nJdUaGys.css +0 -1
package/bin/_cctally_record.py
CHANGED
|
@@ -156,25 +156,40 @@ def _cctally():
|
|
|
156
156
|
return sys.modules["cctally"]
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
# === Honest imports from extracted homes ===================================
|
|
160
|
+
# Spec 2026-05-17-cctally-core-kernel-extraction.md §3.3.
|
|
161
|
+
from _cctally_core import (
|
|
162
|
+
eprint,
|
|
163
|
+
now_utc_iso,
|
|
164
|
+
parse_iso_datetime,
|
|
165
|
+
open_db,
|
|
166
|
+
get_week_start_name,
|
|
167
|
+
compute_week_bounds,
|
|
168
|
+
parse_date_str,
|
|
169
|
+
_canonicalize_optional_iso,
|
|
170
|
+
make_week_ref,
|
|
171
|
+
_get_alerts_config,
|
|
172
|
+
_AlertsConfigError,
|
|
173
|
+
)
|
|
174
|
+
from _lib_five_hour import _canonical_5h_window_key
|
|
175
|
+
from _lib_pricing import _calculate_entry_cost
|
|
176
|
+
|
|
177
|
+
|
|
159
178
|
# Module-level back-ref shims. Each shim resolves
|
|
160
179
|
# ``sys.modules['cctally'].X`` at CALL TIME (not bind time), so
|
|
161
180
|
# monkeypatches on cctally's namespace propagate into the moved code
|
|
162
|
-
# unchanged.
|
|
163
|
-
#
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def
|
|
169
|
-
return sys.modules["cctally"].
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def parse_iso_datetime(*args, **kwargs):
|
|
173
|
-
return sys.modules["cctally"].parse_iso_datetime(*args, **kwargs)
|
|
181
|
+
# unchanged. `load_config` and `get_claude_session_entries` STAY as
|
|
182
|
+
# shims even though their natural homes are decentralized
|
|
183
|
+
# (_cctally_config / _cctally_cache) — tests monkeypatch them via
|
|
184
|
+
# `ns["X"]` (21 sites total, audited 2026-05-17); direct imports would
|
|
185
|
+
# silently bypass the patches.
|
|
186
|
+
# See spec §3.5 (carve-out) and §3.7 (stays-on-shim allowlist).
|
|
187
|
+
def load_config(*args, **kwargs):
|
|
188
|
+
return sys.modules["cctally"].load_config(*args, **kwargs)
|
|
174
189
|
|
|
175
190
|
|
|
176
|
-
def
|
|
177
|
-
return sys.modules["cctally"].
|
|
191
|
+
def get_claude_session_entries(*args, **kwargs):
|
|
192
|
+
return sys.modules["cctally"].get_claude_session_entries(*args, **kwargs)
|
|
178
193
|
|
|
179
194
|
|
|
180
195
|
def open_cache_db(*args, **kwargs):
|
|
@@ -185,30 +200,6 @@ def sync_cache(*args, **kwargs):
|
|
|
185
200
|
return sys.modules["cctally"].sync_cache(*args, **kwargs)
|
|
186
201
|
|
|
187
202
|
|
|
188
|
-
def load_config(*args, **kwargs):
|
|
189
|
-
return sys.modules["cctally"].load_config(*args, **kwargs)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def get_week_start_name(*args, **kwargs):
|
|
193
|
-
return sys.modules["cctally"].get_week_start_name(*args, **kwargs)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def compute_week_bounds(*args, **kwargs):
|
|
197
|
-
return sys.modules["cctally"].compute_week_bounds(*args, **kwargs)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
def parse_date_str(*args, **kwargs):
|
|
201
|
-
return sys.modules["cctally"].parse_date_str(*args, **kwargs)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def _canonicalize_optional_iso(*args, **kwargs):
|
|
205
|
-
return sys.modules["cctally"]._canonicalize_optional_iso(*args, **kwargs)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def _canonical_5h_window_key(*args, **kwargs):
|
|
209
|
-
return sys.modules["cctally"]._canonical_5h_window_key(*args, **kwargs)
|
|
210
|
-
|
|
211
|
-
|
|
212
203
|
def _floor_to_hour(*args, **kwargs):
|
|
213
204
|
return sys.modules["cctally"]._floor_to_hour(*args, **kwargs)
|
|
214
205
|
|
|
@@ -245,22 +236,10 @@ def insert_percent_milestone(*args, **kwargs):
|
|
|
245
236
|
return sys.modules["cctally"].insert_percent_milestone(*args, **kwargs)
|
|
246
237
|
|
|
247
238
|
|
|
248
|
-
def make_week_ref(*args, **kwargs):
|
|
249
|
-
return sys.modules["cctally"].make_week_ref(*args, **kwargs)
|
|
250
|
-
|
|
251
|
-
|
|
252
239
|
def cmd_sync_week(*args, **kwargs):
|
|
253
240
|
return sys.modules["cctally"].cmd_sync_week(*args, **kwargs)
|
|
254
241
|
|
|
255
242
|
|
|
256
|
-
def _calculate_entry_cost(*args, **kwargs):
|
|
257
|
-
return sys.modules["cctally"]._calculate_entry_cost(*args, **kwargs)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def get_claude_session_entries(*args, **kwargs):
|
|
261
|
-
return sys.modules["cctally"].get_claude_session_entries(*args, **kwargs)
|
|
262
|
-
|
|
263
|
-
|
|
264
243
|
def _resolve_primary_model_for_block(*args, **kwargs):
|
|
265
244
|
return sys.modules["cctally"]._resolve_primary_model_for_block(*args, **kwargs)
|
|
266
245
|
|
|
@@ -281,10 +260,6 @@ def _dispatch_alert_notification(*args, **kwargs):
|
|
|
281
260
|
return sys.modules["cctally"]._dispatch_alert_notification(*args, **kwargs)
|
|
282
261
|
|
|
283
262
|
|
|
284
|
-
def _get_alerts_config(*args, **kwargs):
|
|
285
|
-
return sys.modules["cctally"]._get_alerts_config(*args, **kwargs)
|
|
286
|
-
|
|
287
|
-
|
|
288
263
|
def _warn_alerts_bad_config_once(*args, **kwargs):
|
|
289
264
|
return sys.modules["cctally"]._warn_alerts_bad_config_once(*args, **kwargs)
|
|
290
265
|
|
|
@@ -372,6 +347,47 @@ def _normalize_percent(value: "float | int | None") -> "float | None":
|
|
|
372
347
|
return round(float(value), _PERCENT_NORMALIZE_DECIMALS)
|
|
373
348
|
|
|
374
349
|
|
|
350
|
+
def _resolve_active_five_hour_reset_event_id(
|
|
351
|
+
conn: "sqlite3.Connection",
|
|
352
|
+
five_hour_window_key: int,
|
|
353
|
+
) -> int:
|
|
354
|
+
"""Return ``id`` of the most-recent ``five_hour_reset_events`` row for
|
|
355
|
+
``five_hour_window_key``, else 0 (pre-credit / no-event sentinel).
|
|
356
|
+
|
|
357
|
+
Mirrors the weekly active-segment resolution pattern used by
|
|
358
|
+
``maybe_record_milestone`` for ``percent_milestones.reset_event_id``.
|
|
359
|
+
Called once per ``maybe_update_five_hour_block`` invocation and the
|
|
360
|
+
return value is threaded through every read/write site that keys on
|
|
361
|
+
``(five_hour_window_key, percent_threshold)`` so post-credit threshold
|
|
362
|
+
crossings land as a distinct row from any pre-credit one at the same
|
|
363
|
+
threshold. See spec
|
|
364
|
+
docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md §3.3.
|
|
365
|
+
|
|
366
|
+
Returns ``0`` when:
|
|
367
|
+
- The window has no ``five_hour_reset_events`` row (most blocks).
|
|
368
|
+
- The table doesn't exist yet (DB predates this feature).
|
|
369
|
+
|
|
370
|
+
Returns the largest ``id`` matching the window otherwise; the
|
|
371
|
+
``ORDER BY id DESC LIMIT 1`` clause is what *defines* "active" in
|
|
372
|
+
the stacked-credit case (spec §2.3 — multiple events across distinct
|
|
373
|
+
10-min slots): pre-credit milestones key on ``seg=0``, milestones
|
|
374
|
+
between credit 1 and credit 2 key on event-1's id, and milestones
|
|
375
|
+
after credit 2 key on event-2's id.
|
|
376
|
+
"""
|
|
377
|
+
try:
|
|
378
|
+
row = conn.execute(
|
|
379
|
+
"SELECT id FROM five_hour_reset_events "
|
|
380
|
+
"WHERE five_hour_window_key = ? "
|
|
381
|
+
"ORDER BY id DESC LIMIT 1",
|
|
382
|
+
(int(five_hour_window_key),),
|
|
383
|
+
).fetchone()
|
|
384
|
+
except sqlite3.DatabaseError:
|
|
385
|
+
return 0
|
|
386
|
+
if row is None:
|
|
387
|
+
return 0
|
|
388
|
+
return int(row["id"])
|
|
389
|
+
|
|
390
|
+
|
|
375
391
|
def maybe_record_milestone(
|
|
376
392
|
saved: dict[str, Any],
|
|
377
393
|
) -> None:
|
|
@@ -491,7 +507,7 @@ def maybe_record_milestone(
|
|
|
491
507
|
# the underlying problem is config-wide, not axis-specific.
|
|
492
508
|
try:
|
|
493
509
|
alerts_cfg: "dict | None" = _get_alerts_config(load_config())
|
|
494
|
-
except
|
|
510
|
+
except _AlertsConfigError as exc:
|
|
495
511
|
_warn_alerts_bad_config_once(exc)
|
|
496
512
|
alerts_cfg = None
|
|
497
513
|
|
|
@@ -762,7 +778,7 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
762
778
|
cfg_for_alerts = load_config()
|
|
763
779
|
try:
|
|
764
780
|
alerts_cfg: "dict | None" = _get_alerts_config(cfg_for_alerts)
|
|
765
|
-
except
|
|
781
|
+
except _AlertsConfigError as exc:
|
|
766
782
|
_warn_alerts_bad_config_once(exc)
|
|
767
783
|
alerts_cfg = None
|
|
768
784
|
# Resolve display.tz once (shares the cfg load above). Threaded
|
|
@@ -963,7 +979,23 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
963
979
|
# Snap-up-by-1e-9 per the gotcha: 0.50 * 100 == 49.99...9 in
|
|
964
980
|
# IEEE-754, so bare math.floor would miss the 50 threshold.
|
|
965
981
|
current_floor = math.floor(float(five_hour_percent) + 1e-9)
|
|
982
|
+
|
|
983
|
+
# Resolve active segment ONCE so every per-site read + write
|
|
984
|
+
# below sees the same value within this transaction. Spec
|
|
985
|
+
# §3.3 & §3.4 of
|
|
986
|
+
# docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md:
|
|
987
|
+
# the active segment is the latest five_hour_reset_events row
|
|
988
|
+
# for this window_key, else sentinel 0 (pre-credit).
|
|
989
|
+
active_reset_event_id = _resolve_active_five_hour_reset_event_id(
|
|
990
|
+
conn, int(five_hour_window_key)
|
|
991
|
+
)
|
|
992
|
+
|
|
966
993
|
if current_floor >= 1:
|
|
994
|
+
# Site A — MAX(percent_threshold) scoped to active segment.
|
|
995
|
+
# Without the reset_event_id filter, MAX returns the
|
|
996
|
+
# pre-credit max and post-credit milestones from 1..max
|
|
997
|
+
# are silently never emitted.
|
|
998
|
+
#
|
|
967
999
|
# Use max(percent_threshold) directly (not prior block's
|
|
968
1000
|
# final_pct) so first-observation already-mid-stream doesn't
|
|
969
1001
|
# synthesize crossings 1..(current_floor - 1) we never had
|
|
@@ -971,8 +1003,8 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
971
1003
|
# maybe_record_milestone's max_existing path.
|
|
972
1004
|
row = conn.execute(
|
|
973
1005
|
"SELECT MAX(percent_threshold) AS m FROM five_hour_milestones "
|
|
974
|
-
"WHERE five_hour_window_key = ?",
|
|
975
|
-
(int(five_hour_window_key),),
|
|
1006
|
+
"WHERE five_hour_window_key = ? AND reset_event_id = ?",
|
|
1007
|
+
(int(five_hour_window_key), active_reset_event_id),
|
|
976
1008
|
).fetchone()
|
|
977
1009
|
max_existing = row["m"] if row and row["m"] is not None else None
|
|
978
1010
|
|
|
@@ -985,14 +1017,21 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
985
1017
|
# block_id was resolved above (before the children writes) and
|
|
986
1018
|
# is still in scope here.
|
|
987
1019
|
|
|
1020
|
+
# Site B — prior-cost lookup scoped to active segment.
|
|
988
1021
|
# Marginal-cost lookup for the start_threshold milestone
|
|
989
1022
|
# (only when there's a prior milestone in this block).
|
|
1023
|
+
# Without the reset_event_id filter, marginal could be
|
|
1024
|
+
# computed against a pre-credit row whose block_cost is
|
|
1025
|
+
# unrelated to the post-credit segment's totals.
|
|
990
1026
|
prior_cost: float | None = None
|
|
991
1027
|
if max_existing is not None:
|
|
992
1028
|
prev_row = conn.execute(
|
|
993
1029
|
"SELECT block_cost_usd FROM five_hour_milestones "
|
|
994
|
-
"WHERE five_hour_window_key = ?
|
|
995
|
-
|
|
1030
|
+
"WHERE five_hour_window_key = ? "
|
|
1031
|
+
" AND percent_threshold = ? "
|
|
1032
|
+
" AND reset_event_id = ?",
|
|
1033
|
+
(int(five_hour_window_key), int(max_existing),
|
|
1034
|
+
active_reset_event_id),
|
|
996
1035
|
).fetchone()
|
|
997
1036
|
if prev_row is not None:
|
|
998
1037
|
prior_cost = float(prev_row["block_cost_usd"])
|
|
@@ -1002,6 +1041,12 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
1002
1041
|
marginal: float | None = totals["cost_usd"] - prior_cost
|
|
1003
1042
|
else:
|
|
1004
1043
|
marginal = None
|
|
1044
|
+
# Site C — INSERT stamps the resolved
|
|
1045
|
+
# ``active_reset_event_id`` (0 = pre-credit, else
|
|
1046
|
+
# the latest five_hour_reset_events.id). UNIQUE
|
|
1047
|
+
# is now (window_key, threshold, reset_event_id)
|
|
1048
|
+
# so post-credit threshold crossings re-fire
|
|
1049
|
+
# fresh — not absorbed into the pre-credit row.
|
|
1005
1050
|
cur = conn.execute(
|
|
1006
1051
|
"""
|
|
1007
1052
|
INSERT OR IGNORE INTO five_hour_milestones (
|
|
@@ -1016,9 +1061,10 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
1016
1061
|
block_cache_read_tokens,
|
|
1017
1062
|
block_cost_usd,
|
|
1018
1063
|
marginal_cost_usd,
|
|
1019
|
-
seven_day_pct_at_crossing
|
|
1064
|
+
seven_day_pct_at_crossing,
|
|
1065
|
+
reset_event_id
|
|
1020
1066
|
)
|
|
1021
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1067
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1022
1068
|
""",
|
|
1023
1069
|
(
|
|
1024
1070
|
block_id,
|
|
@@ -1033,6 +1079,7 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
1033
1079
|
totals["cost_usd"],
|
|
1034
1080
|
marginal,
|
|
1035
1081
|
weekly_percent,
|
|
1082
|
+
active_reset_event_id,
|
|
1036
1083
|
),
|
|
1037
1084
|
)
|
|
1038
1085
|
# ── Threshold-actions dispatch (set-then-dispatch, spec §3.2) ──
|
|
@@ -1061,11 +1108,19 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
1061
1108
|
and pct in alerts_cfg["five_hour_thresholds"]
|
|
1062
1109
|
):
|
|
1063
1110
|
crossed_at = now_utc_iso()
|
|
1111
|
+
# Site D — alerted_at UPDATE scoped to the
|
|
1112
|
+
# active segment, so the post-credit row
|
|
1113
|
+
# gets stamped without overwriting an
|
|
1114
|
+
# already-alerted pre-credit row at the
|
|
1115
|
+
# same threshold.
|
|
1064
1116
|
conn.execute(
|
|
1065
1117
|
"UPDATE five_hour_milestones SET alerted_at = ? "
|
|
1066
|
-
"WHERE five_hour_window_key = ?
|
|
1067
|
-
"AND
|
|
1068
|
-
|
|
1118
|
+
"WHERE five_hour_window_key = ? "
|
|
1119
|
+
" AND percent_threshold = ? "
|
|
1120
|
+
" AND reset_event_id = ? "
|
|
1121
|
+
" AND alerted_at IS NULL",
|
|
1122
|
+
(crossed_at, int(five_hour_window_key),
|
|
1123
|
+
int(pct), active_reset_event_id),
|
|
1069
1124
|
)
|
|
1070
1125
|
# Cheap re-reads inside BEGIN are SELECT-only and
|
|
1071
1126
|
# safe; values reflect post-INSERT state. We
|
|
@@ -1073,10 +1128,17 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
1073
1128
|
# are in scope) and defer ONLY the Popen-side
|
|
1074
1129
|
# _dispatch_alert_notification to after the outer
|
|
1075
1130
|
# commit.
|
|
1131
|
+
# Site E — alert-payload reread scoped to
|
|
1132
|
+
# the active segment so the dispatch shows
|
|
1133
|
+
# post-credit cost, not the pre-credit
|
|
1134
|
+
# row's stale value at the same threshold.
|
|
1076
1135
|
cost_row = conn.execute(
|
|
1077
1136
|
"SELECT block_cost_usd FROM five_hour_milestones "
|
|
1078
|
-
"WHERE five_hour_window_key = ?
|
|
1079
|
-
|
|
1137
|
+
"WHERE five_hour_window_key = ? "
|
|
1138
|
+
" AND percent_threshold = ? "
|
|
1139
|
+
" AND reset_event_id = ?",
|
|
1140
|
+
(int(five_hour_window_key), int(pct),
|
|
1141
|
+
active_reset_event_id),
|
|
1080
1142
|
).fetchone()
|
|
1081
1143
|
block_row = conn.execute(
|
|
1082
1144
|
"SELECT block_start_at FROM five_hour_blocks "
|
|
@@ -1393,9 +1455,9 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1393
1455
|
"WHERE new_week_end_at = ? LIMIT 1",
|
|
1394
1456
|
(cur_end_canon,),
|
|
1395
1457
|
).fetchone()
|
|
1458
|
+
effective_dt = _floor_to_hour(now_utc)
|
|
1459
|
+
effective_iso = effective_dt.isoformat(timespec="seconds")
|
|
1396
1460
|
if already is None:
|
|
1397
|
-
effective_dt = _floor_to_hour(now_utc)
|
|
1398
|
-
effective_iso = effective_dt.isoformat(timespec="seconds")
|
|
1399
1461
|
# Row shape: old=effective_iso, new=cur_end_canon
|
|
1400
1462
|
# (distinct values). The previous shape stored
|
|
1401
1463
|
# old==new==cur_end_canon, which let BOTH
|
|
@@ -1413,64 +1475,318 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1413
1475
|
# UNIQUE(old, new) constraint permits this
|
|
1414
1476
|
# row, and the pre-check above keys on
|
|
1415
1477
|
# new_week_end_at so dedup still works.
|
|
1478
|
+
# Stamp ``observed_pre_credit_pct = prior_pct``
|
|
1479
|
+
# (issue #45): durable record of the pre-credit
|
|
1480
|
+
# baseline we observed at write time. Decouples
|
|
1481
|
+
# any future cleanup tooling from re-deriving
|
|
1482
|
+
# prior_pct via SELECT. Existing rows from
|
|
1483
|
+
# migration 007 carry NULL.
|
|
1416
1484
|
conn.execute(
|
|
1417
1485
|
"INSERT OR IGNORE INTO week_reset_events "
|
|
1418
1486
|
"(detected_at_utc, old_week_end_at, new_week_end_at, "
|
|
1419
|
-
" effective_reset_at_utc)
|
|
1487
|
+
" effective_reset_at_utc, observed_pre_credit_pct) "
|
|
1488
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
1420
1489
|
(now_utc_iso(), effective_iso, cur_end_canon,
|
|
1421
|
-
effective_iso),
|
|
1490
|
+
effective_iso, float(prior_pct)),
|
|
1422
1491
|
)
|
|
1423
1492
|
conn.commit()
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1493
|
+
# Pivots fire UNCONDITIONALLY whenever a credit
|
|
1494
|
+
# is detected — they're NOT gated on
|
|
1495
|
+
# ``already is None``. Memory
|
|
1496
|
+
# ``project_dedup_must_not_gate_side_effects.md``:
|
|
1497
|
+
# "Skipping a no-op INSERT must NOT skip
|
|
1498
|
+
# milestones/rollups/alerts; prior run may have
|
|
1499
|
+
# died mid-flight." Crash scenario: tick N
|
|
1500
|
+
# committed the event row, then died before
|
|
1501
|
+
# HWM + DELETE. Tick N+1's pre-check sees
|
|
1502
|
+
# ``already`` non-None (the row IS in the
|
|
1503
|
+
# table) and would skip the pivots, leaving
|
|
1504
|
+
# the system wedged on pre-credit HWM + stale-
|
|
1505
|
+
# replica rows. Pivots are individually
|
|
1506
|
+
# idempotent (file overwrite + DELETE on stable
|
|
1507
|
+
# predicate), so re-running them is safe.
|
|
1508
|
+
# ``effective_iso`` is resolved above; on a
|
|
1509
|
+
# recovery tick it lands on the SAME 10-min
|
|
1510
|
+
# slot as the original (now_utc has drifted
|
|
1511
|
+
# only seconds), so the DELETE predicate's
|
|
1512
|
+
# ``unixepoch(captured_at_utc) >= unixepoch(?)``
|
|
1513
|
+
# still matches every stale-replica row.
|
|
1514
|
+
#
|
|
1515
|
+
# Force-write hwm-7d so the next status-line
|
|
1516
|
+
# render reflects the post-credit value. The
|
|
1517
|
+
# monotonic guard at the normal write site
|
|
1518
|
+
# (below) would refuse to decrease the file;
|
|
1519
|
+
# this write is the credit-only escape hatch.
|
|
1520
|
+
# Lands AFTER the conn.commit() so a concurrent
|
|
1521
|
+
# record-usage reader doesn't see the new HWM
|
|
1522
|
+
# before the event row is durable.
|
|
1523
|
+
try:
|
|
1524
|
+
(c.APP_DIR / "hwm-7d").write_text(
|
|
1525
|
+
f"{week_start_date} {weekly_percent}\n"
|
|
1526
|
+
)
|
|
1527
|
+
except OSError:
|
|
1528
|
+
pass
|
|
1529
|
+
|
|
1530
|
+
# Race-defensive cleanup. Between the moment
|
|
1531
|
+
# Anthropic credited the user (effective_iso)
|
|
1532
|
+
# and this code firing, the EXTERNAL
|
|
1533
|
+
# claude-statusline tool can replay stale
|
|
1534
|
+
# pre-credit `--percent` values (it has its
|
|
1535
|
+
# own in-memory HWM cache and re-runs us once
|
|
1536
|
+
# per status-line tick). Those replays land
|
|
1537
|
+
# captured_at_utc >= effective_iso with
|
|
1538
|
+
# weekly_percent near prior_pct (the pre-credit
|
|
1539
|
+
# value), and they dominate the reset-aware
|
|
1540
|
+
# clamp's MAX over the post-credit segment so
|
|
1541
|
+
# legitimate fresh OAuth values are rejected.
|
|
1542
|
+
# 1.0pp tolerance band (issue #45) around the
|
|
1543
|
+
# observed pre-credit baseline absorbs any
|
|
1544
|
+
# rounding drift between cctally's OAuth read
|
|
1545
|
+
# and statusline's --percent payload (today
|
|
1546
|
+
# they match byte-identically, but the band
|
|
1547
|
+
# future-proofs against Anthropic or statusline
|
|
1548
|
+
# changing rounding). The band stays well below
|
|
1549
|
+
# the 25pp in-place credit detection threshold,
|
|
1550
|
+
# so legitimate post-credit values are never
|
|
1551
|
+
# caught. Bind is the in-scope ``prior_pct``,
|
|
1552
|
+
# which equals the just-stamped
|
|
1553
|
+
# ``observed_pre_credit_pct`` on the event row.
|
|
1554
|
+
try:
|
|
1555
|
+
conn.execute(
|
|
1556
|
+
"DELETE FROM weekly_usage_snapshots "
|
|
1557
|
+
"WHERE week_start_date = ? "
|
|
1558
|
+
" AND unixepoch(captured_at_utc) >= "
|
|
1559
|
+
" unixepoch(?) "
|
|
1560
|
+
" AND ABS(weekly_percent - ?) < 1.0",
|
|
1561
|
+
(week_start_date, effective_iso,
|
|
1562
|
+
float(prior_pct)),
|
|
1563
|
+
)
|
|
1564
|
+
conn.commit()
|
|
1565
|
+
except sqlite3.DatabaseError as exc:
|
|
1566
|
+
eprint(
|
|
1567
|
+
"[record-usage] post-credit cleanup "
|
|
1568
|
+
f"failed: {exc}"
|
|
1569
|
+
)
|
|
1570
|
+
|
|
1571
|
+
# ── 5h in-place credit detection (parallel to weekly above) ──
|
|
1572
|
+
# Spec §2.2 of
|
|
1573
|
+
# docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md.
|
|
1574
|
+
# Slot SECOND so the weekly branch retains control-flow
|
|
1575
|
+
# priority — both branches are independent (they touch
|
|
1576
|
+
# different tables) and the order has no behavioral
|
|
1577
|
+
# interaction. Same outer try/except wraps both so a
|
|
1578
|
+
# 5h-detection failure logs but does not regress the rest
|
|
1579
|
+
# of cmd_record_usage.
|
|
1580
|
+
#
|
|
1581
|
+
# Diverges from weekly in three places:
|
|
1582
|
+
# - Threshold: 5.0pp (constant on cctally module), not 25.0pp.
|
|
1583
|
+
# The 5h envelope is smaller so a 5pp move is
|
|
1584
|
+
# proportionally larger.
|
|
1585
|
+
# - Effective-iso floor: 10-min (matches
|
|
1586
|
+
# ``_canonical_5h_window_key``'s 600s floor), not hour.
|
|
1587
|
+
# Up to ~30 distinct slots per 5h block; same-slot
|
|
1588
|
+
# collisions absorbed by UNIQUE per spec §2.3.
|
|
1589
|
+
# - Pre-check: pair-checks the latest event's
|
|
1590
|
+
# ``(prior_percent, post_percent)`` against this tick's
|
|
1591
|
+
# ``(prior_5h_pct, five_hour_percent)``, not
|
|
1592
|
+
# ``new_week_end_at`` equality. A genuine replay matches
|
|
1593
|
+
# BOTH fields; a NEW credit-with-idle (prior_pct equals
|
|
1594
|
+
# the prior credit's post_pct because the user didn't
|
|
1595
|
+
# move between credits) matches only one field and
|
|
1596
|
+
# correctly proceeds to write a second event row.
|
|
1597
|
+
try:
|
|
1598
|
+
if (
|
|
1599
|
+
five_hour_window_key is not None
|
|
1600
|
+
and five_hour_percent is not None
|
|
1601
|
+
):
|
|
1602
|
+
prior_5h_row = conn.execute(
|
|
1603
|
+
"SELECT five_hour_window_key, five_hour_percent, "
|
|
1604
|
+
" five_hour_resets_at "
|
|
1605
|
+
" FROM weekly_usage_snapshots "
|
|
1606
|
+
" WHERE five_hour_window_key IS NOT NULL "
|
|
1607
|
+
" AND five_hour_percent IS NOT NULL "
|
|
1608
|
+
" ORDER BY captured_at_utc DESC, id DESC LIMIT 1"
|
|
1609
|
+
).fetchone()
|
|
1610
|
+
if (
|
|
1611
|
+
prior_5h_row is not None
|
|
1612
|
+
and int(prior_5h_row["five_hour_window_key"])
|
|
1613
|
+
== int(five_hour_window_key)
|
|
1614
|
+
and prior_5h_row["five_hour_resets_at"] is not None
|
|
1615
|
+
):
|
|
1616
|
+
prior_5h_pct = float(prior_5h_row["five_hour_percent"])
|
|
1617
|
+
prior_5h_resets_dt = parse_iso_datetime(
|
|
1618
|
+
prior_5h_row["five_hour_resets_at"],
|
|
1619
|
+
"prior.five_hour_resets_at",
|
|
1620
|
+
)
|
|
1621
|
+
# ``now_utc`` was bound earlier in this same
|
|
1622
|
+
# outer try block from
|
|
1623
|
+
# ``dt.datetime.now(dt.timezone.utc)``; reuse it
|
|
1624
|
+
# so both branches see the same instant.
|
|
1625
|
+
if (
|
|
1626
|
+
prior_5h_resets_dt > now_utc
|
|
1627
|
+
and (prior_5h_pct - float(five_hour_percent))
|
|
1628
|
+
>= c._FIVE_HOUR_RESET_PCT_DROP_THRESHOLD
|
|
1629
|
+
):
|
|
1630
|
+
# Pair-check dedup pre-check (spec §2.2;
|
|
1631
|
+
# refined by Codex r4 P1 finding). The
|
|
1632
|
+
# round-1 predicate compared only the
|
|
1633
|
+
# latest event's ``post_percent`` against
|
|
1634
|
+
# this tick's ``prior_5h_pct``; that
|
|
1635
|
+
# false-positived on a legitimate 2nd
|
|
1636
|
+
# credit where the user was idle between
|
|
1637
|
+
# credits (Credit 1 lands prior=20/post=5;
|
|
1638
|
+
# user does nothing; Credit 2 arrives with
|
|
1639
|
+
# CLI percent=0 so prior_5h_pct=5 reads
|
|
1640
|
+
# equal to stored post_percent=5 →
|
|
1641
|
+
# silently swallowed). Pair-checking
|
|
1642
|
+
# against BOTH fields disambiguates: a
|
|
1643
|
+
# genuine replay matches BOTH; a new
|
|
1644
|
+
# credit-with-idle matches at most ONE
|
|
1645
|
+
# (the prior side coincides but
|
|
1646
|
+
# post_percent differs).
|
|
1647
|
+
most_recent = conn.execute(
|
|
1648
|
+
"SELECT prior_percent, post_percent "
|
|
1649
|
+
" FROM five_hour_reset_events "
|
|
1650
|
+
" WHERE five_hour_window_key = ? "
|
|
1651
|
+
" ORDER BY id DESC LIMIT 1",
|
|
1652
|
+
(int(five_hour_window_key),),
|
|
1653
|
+
).fetchone()
|
|
1654
|
+
is_dup = (
|
|
1655
|
+
most_recent is not None
|
|
1656
|
+
and round(prior_5h_pct, 1)
|
|
1657
|
+
== round(float(most_recent["prior_percent"]), 1)
|
|
1658
|
+
and round(float(five_hour_percent), 1)
|
|
1659
|
+
== round(float(most_recent["post_percent"]), 1)
|
|
1660
|
+
)
|
|
1661
|
+
# 10-min floor (spec §2.3 — bounded
|
|
1662
|
+
# stacked-credit resolution; one event per
|
|
1663
|
+
# 10-min slot per block). Resolved BEFORE
|
|
1664
|
+
# the ``if not is_dup`` branch so it's in
|
|
1665
|
+
# scope for the pivots below (per memory
|
|
1666
|
+
# ``project_dedup_must_not_gate_side_effects.md``:
|
|
1667
|
+
# the recovery-tick path must still force
|
|
1668
|
+
# HWM + DELETE even when the INSERT is
|
|
1669
|
+
# absorbed by the pre-check or by
|
|
1670
|
+
# UNIQUE — see comment below for the
|
|
1671
|
+
# crash scenario). ``_floor_to_ten_minutes``
|
|
1672
|
+
# is a cctally module attribute; the
|
|
1673
|
+
# ``c.X`` accessor resolves at call time
|
|
1674
|
+
# so test ``monkeypatch.setitem(ns,
|
|
1675
|
+
# "_floor_to_ten_minutes", …)``
|
|
1676
|
+
# propagates.
|
|
1677
|
+
effective_dt = c._floor_to_ten_minutes(now_utc)
|
|
1678
|
+
effective_iso = effective_dt.isoformat(
|
|
1679
|
+
timespec="seconds"
|
|
1680
|
+
)
|
|
1681
|
+
if not is_dup:
|
|
1682
|
+
conn.execute(
|
|
1683
|
+
"INSERT OR IGNORE INTO five_hour_reset_events "
|
|
1684
|
+
"(detected_at_utc, five_hour_window_key, "
|
|
1685
|
+
" prior_percent, post_percent, "
|
|
1686
|
+
" effective_reset_at_utc) "
|
|
1687
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
1688
|
+
(
|
|
1689
|
+
now_utc_iso(),
|
|
1690
|
+
int(five_hour_window_key),
|
|
1691
|
+
prior_5h_pct,
|
|
1692
|
+
float(five_hour_percent),
|
|
1693
|
+
effective_iso,
|
|
1694
|
+
),
|
|
1695
|
+
)
|
|
1696
|
+
conn.commit()
|
|
1697
|
+
# Pivots fire UNCONDITIONALLY whenever a
|
|
1698
|
+
# credit is detected — NOT gated on
|
|
1699
|
+
# ``not is_dup`` and NOT on
|
|
1700
|
+
# ``rowcount == 1``. Memory
|
|
1701
|
+
# ``project_dedup_must_not_gate_side_effects.md``:
|
|
1702
|
+
# "Skipping a no-op INSERT must NOT skip
|
|
1703
|
+
# milestones/rollups/alerts; prior run may
|
|
1704
|
+
# have died mid-flight." Crash scenario A:
|
|
1705
|
+
# tick N committed the event row, then died
|
|
1706
|
+
# before HWM + DELETE. Tick N+1's
|
|
1707
|
+
# INSERT OR IGNORE returns rowcount == 0
|
|
1708
|
+
# (UNIQUE absorbs) but the system is still
|
|
1709
|
+
# wedged on the pre-credit HWM + stale-
|
|
1710
|
+
# replica rows. Crash scenario B (the
|
|
1711
|
+
# Codex r4 finding): a recovery tick where
|
|
1712
|
+
# ``(prior, post)`` pair-matches the
|
|
1713
|
+
# already-stored event row also takes the
|
|
1714
|
+
# ``is_dup`` branch; without the hoist the
|
|
1715
|
+
# pivots would be skipped and the system
|
|
1716
|
+
# would stay wedged. The pivots are
|
|
1717
|
+
# individually idempotent (file overwrite
|
|
1718
|
+
# + DELETE on a stable predicate), so
|
|
1719
|
+
# re-running them on the recovery tick is
|
|
1720
|
+
# always safe. Mirrors the weekly hoist at
|
|
1721
|
+
# ``_cctally_record.py`` after the
|
|
1722
|
+
# ``if already is None`` block (grep
|
|
1723
|
+
# ``Force-write hwm-7d``).
|
|
1724
|
+
#
|
|
1725
|
+
# Force-write hwm-5h: bypasses the
|
|
1726
|
+
# monotonic guard at the normal hwm-5h
|
|
1727
|
+
# writer below. Lands AFTER
|
|
1728
|
+
# ``conn.commit()`` so a concurrent reader
|
|
1729
|
+
# doesn't see the new HWM before the
|
|
1730
|
+
# event row is durable. File format
|
|
1731
|
+
# matches the canonical writer:
|
|
1732
|
+
# ``<key> <percent>\n``.
|
|
1432
1733
|
try:
|
|
1433
|
-
(c.APP_DIR / "hwm-
|
|
1434
|
-
f"{
|
|
1734
|
+
(c.APP_DIR / "hwm-5h").write_text(
|
|
1735
|
+
f"{int(five_hour_window_key)} "
|
|
1736
|
+
f"{float(five_hour_percent)}\n"
|
|
1435
1737
|
)
|
|
1436
1738
|
except OSError:
|
|
1437
1739
|
pass
|
|
1438
|
-
|
|
1439
|
-
#
|
|
1440
|
-
#
|
|
1441
|
-
#
|
|
1442
|
-
#
|
|
1443
|
-
#
|
|
1444
|
-
#
|
|
1445
|
-
#
|
|
1446
|
-
#
|
|
1447
|
-
#
|
|
1448
|
-
#
|
|
1449
|
-
#
|
|
1450
|
-
#
|
|
1451
|
-
#
|
|
1452
|
-
#
|
|
1453
|
-
#
|
|
1454
|
-
#
|
|
1455
|
-
#
|
|
1456
|
-
# credit
|
|
1740
|
+
# Stale-replica DELETE (spec §4.3).
|
|
1741
|
+
# Defends against claude-statusline
|
|
1742
|
+
# replaying the pre-credit
|
|
1743
|
+
# ``--five-hour-percent`` value past the
|
|
1744
|
+
# credit moment from its own in-memory
|
|
1745
|
+
# HWM cache. 1.0pp tolerance band (issue
|
|
1746
|
+
# #48 — symmetric follow-up to weekly #45)
|
|
1747
|
+
# around the observed pre-credit baseline
|
|
1748
|
+
# absorbs any rounding drift between
|
|
1749
|
+
# cctally's OAuth read and statusline's
|
|
1750
|
+
# ``--five-hour-percent`` payload (today
|
|
1751
|
+
# they match byte-identically, but the
|
|
1752
|
+
# band future-proofs against Anthropic or
|
|
1753
|
+
# statusline changing 5h rounding). The
|
|
1754
|
+
# band stays well below the 5.0pp 5h
|
|
1755
|
+
# in-place credit detection threshold
|
|
1756
|
+
# (``_FIVE_HOUR_RESET_PCT_DROP_THRESHOLD``)
|
|
1757
|
+
# — 4pp safety margin — so legitimate
|
|
1758
|
+
# post-credit values are never caught.
|
|
1759
|
+
# ``unixepoch()`` on both sides for offset
|
|
1760
|
+
# robustness (Z vs +00:00). Bind is the
|
|
1761
|
+
# in-scope ``prior_5h_pct``, which equals
|
|
1762
|
+
# the just-stamped
|
|
1763
|
+
# ``five_hour_reset_events.prior_percent``
|
|
1764
|
+
# on the event row.
|
|
1457
1765
|
try:
|
|
1458
1766
|
conn.execute(
|
|
1459
1767
|
"DELETE FROM weekly_usage_snapshots "
|
|
1460
|
-
"WHERE
|
|
1461
|
-
"
|
|
1462
|
-
"
|
|
1463
|
-
"
|
|
1464
|
-
"
|
|
1465
|
-
(
|
|
1466
|
-
|
|
1768
|
+
" WHERE five_hour_window_key = ? "
|
|
1769
|
+
" AND unixepoch(captured_at_utc) "
|
|
1770
|
+
" >= unixepoch(?) "
|
|
1771
|
+
" AND ABS(five_hour_percent - ?) "
|
|
1772
|
+
" < 1.0",
|
|
1773
|
+
(
|
|
1774
|
+
int(five_hour_window_key),
|
|
1775
|
+
effective_iso,
|
|
1776
|
+
prior_5h_pct,
|
|
1777
|
+
),
|
|
1467
1778
|
)
|
|
1468
1779
|
conn.commit()
|
|
1469
1780
|
except sqlite3.DatabaseError as exc:
|
|
1470
1781
|
eprint(
|
|
1471
|
-
"[record-usage] post-credit
|
|
1472
|
-
f"failed: {exc}"
|
|
1782
|
+
"[record-usage] 5h post-credit "
|
|
1783
|
+
f"cleanup failed: {exc}"
|
|
1473
1784
|
)
|
|
1785
|
+
except (sqlite3.DatabaseError, ValueError, TypeError) as exc:
|
|
1786
|
+
eprint(
|
|
1787
|
+
f"[record-usage] 5h in-place-credit detection "
|
|
1788
|
+
f"failed: {exc}"
|
|
1789
|
+
)
|
|
1474
1790
|
except (sqlite3.DatabaseError, ValueError) as exc:
|
|
1475
1791
|
eprint(f"[record-usage] reset-event detection failed: {exc}")
|
|
1476
1792
|
|
|
@@ -1511,19 +1827,54 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1511
1827
|
if max_row and max_row["v"] is not None and round(weekly_percent, 1) < round(float(max_row["v"]), 1):
|
|
1512
1828
|
should_insert = False
|
|
1513
1829
|
else:
|
|
1514
|
-
# 5-hour usage is monotonically non-decreasing within a window
|
|
1515
|
-
#
|
|
1516
|
-
#
|
|
1830
|
+
# 5-hour usage is monotonically non-decreasing within a window
|
|
1831
|
+
# — UNTIL an in-place 5h credit fires. When a
|
|
1832
|
+
# ``five_hour_reset_events`` row exists for THIS
|
|
1833
|
+
# ``five_hour_window_key``, the MAX query filters to samples
|
|
1834
|
+
# captured at-or-after the event's ``effective_reset_at_utc``
|
|
1835
|
+
# so a fresh post-credit OAuth value (e.g. 4%) lands instead
|
|
1836
|
+
# of being re-clamped to the pre-credit max (e.g. 28%). When
|
|
1837
|
+
# no event row exists, ``COALESCE`` defaults to epoch-zero so
|
|
1838
|
+
# the filter is a no-op and legacy clamp behavior is preserved
|
|
1839
|
+
# byte-identically.
|
|
1840
|
+
#
|
|
1841
|
+
# ``unixepoch()`` on BOTH sides for offset robustness — stored
|
|
1842
|
+
# ``captured_at_utc`` carries ``Z`` while
|
|
1843
|
+
# ``effective_reset_at_utc`` carries ``+00:00``. Lex compare
|
|
1844
|
+
# would silently mis-order moments for non-UTC hosts (same
|
|
1845
|
+
# gotcha as the weekly clamp / 5h-block cross-reset flag).
|
|
1846
|
+
#
|
|
1847
|
+
# Joining on ``five_hour_window_key`` (canonical 10-min-floored
|
|
1517
1848
|
# epoch) absorbs Anthropic's seconds-level jitter on
|
|
1518
|
-
# resets_at
|
|
1849
|
+
# ``resets_at``; an ISO-string equality at this site silently
|
|
1519
1850
|
# skipped the clamp every time a jittered fetch landed in
|
|
1520
1851
|
# the same physical 5h window (spec Bug B).
|
|
1852
|
+
#
|
|
1853
|
+
# Spec §4.1 of
|
|
1854
|
+
# docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md.
|
|
1521
1855
|
if five_hour_percent is not None and five_hour_window_key is not None:
|
|
1522
1856
|
max_5h_row = conn.execute(
|
|
1523
|
-
"
|
|
1524
|
-
(
|
|
1857
|
+
"""
|
|
1858
|
+
SELECT MAX(five_hour_percent) AS v
|
|
1859
|
+
FROM weekly_usage_snapshots
|
|
1860
|
+
WHERE five_hour_window_key = ?
|
|
1861
|
+
AND unixepoch(captured_at_utc) >= unixepoch(COALESCE(
|
|
1862
|
+
(SELECT effective_reset_at_utc
|
|
1863
|
+
FROM five_hour_reset_events
|
|
1864
|
+
WHERE five_hour_window_key = ?
|
|
1865
|
+
ORDER BY id DESC
|
|
1866
|
+
LIMIT 1),
|
|
1867
|
+
'1970-01-01T00:00:00Z'
|
|
1868
|
+
))
|
|
1869
|
+
""",
|
|
1870
|
+
(int(five_hour_window_key), int(five_hour_window_key)),
|
|
1525
1871
|
).fetchone()
|
|
1526
|
-
if
|
|
1872
|
+
if (
|
|
1873
|
+
max_5h_row
|
|
1874
|
+
and max_5h_row["v"] is not None
|
|
1875
|
+
and round(five_hour_percent, 1)
|
|
1876
|
+
< round(float(max_5h_row["v"]), 1)
|
|
1877
|
+
):
|
|
1527
1878
|
five_hour_percent = float(max_5h_row["v"])
|
|
1528
1879
|
|
|
1529
1880
|
# Dedup vs last snapshot: if BOTH weekly_percent and
|
|
@@ -1627,6 +1978,16 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1627
1978
|
# last_observed_at_utc is stale relative to the latest
|
|
1628
1979
|
# snapshot's captured_at_utc (the kill landed between
|
|
1629
1980
|
# insert_usage_snapshot and maybe_update_five_hour_block).
|
|
1981
|
+
#
|
|
1982
|
+
# Round-3: ALSO scope the milestone-coverage half of
|
|
1983
|
+
# this probe by ACTIVE 5h SEGMENT. Without this, a
|
|
1984
|
+
# credited block's MAX over the whole milestone ledger
|
|
1985
|
+
# would still read the pre-credit ceiling (e.g. 28%) and
|
|
1986
|
+
# silently suppress the post-credit ledger's heal even
|
|
1987
|
+
# though it has zero rows. Mirrors weekly Probe 1's
|
|
1988
|
+
# segment-aware fix above. Uses
|
|
1989
|
+
# ``_resolve_active_five_hour_reset_event_id`` to find
|
|
1990
|
+
# the active segment for the latest snapshot's window.
|
|
1630
1991
|
need_5h_heal = False
|
|
1631
1992
|
window_key = latest_row["five_hour_window_key"]
|
|
1632
1993
|
if window_key is not None:
|
|
@@ -1643,6 +2004,45 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1643
2004
|
< latest_row["captured_at_utc"]
|
|
1644
2005
|
):
|
|
1645
2006
|
need_5h_heal = True
|
|
2007
|
+
else:
|
|
2008
|
+
# Block row exists AND last_observed is fresh
|
|
2009
|
+
# — but the post-credit milestone segment may
|
|
2010
|
+
# still owe rows. Scope MAX(percent_threshold)
|
|
2011
|
+
# by the active reset_event_id segment so
|
|
2012
|
+
# post-credit climbs from threshold 1 trigger
|
|
2013
|
+
# heal even when the pre-credit segment already
|
|
2014
|
+
# crossed higher thresholds. Probe shape mirrors
|
|
2015
|
+
# weekly Probe 1 (lines 1922-1956).
|
|
2016
|
+
five_hour_percent_for_probe = latest_row[
|
|
2017
|
+
"five_hour_percent"
|
|
2018
|
+
]
|
|
2019
|
+
if five_hour_percent_for_probe is not None:
|
|
2020
|
+
latest_5h_floor = math.floor(
|
|
2021
|
+
float(five_hour_percent_for_probe) + 1e-9
|
|
2022
|
+
)
|
|
2023
|
+
if latest_5h_floor >= 1:
|
|
2024
|
+
heal_5h_segment = (
|
|
2025
|
+
_resolve_active_five_hour_reset_event_id(
|
|
2026
|
+
heal_conn, int(window_key)
|
|
2027
|
+
)
|
|
2028
|
+
)
|
|
2029
|
+
max_5h_existing = heal_conn.execute(
|
|
2030
|
+
"SELECT MAX(percent_threshold) AS m "
|
|
2031
|
+
"FROM five_hour_milestones "
|
|
2032
|
+
"WHERE five_hour_window_key = ? "
|
|
2033
|
+
" AND reset_event_id = ?",
|
|
2034
|
+
(int(window_key), heal_5h_segment),
|
|
2035
|
+
).fetchone()
|
|
2036
|
+
if (
|
|
2037
|
+
max_5h_existing is None
|
|
2038
|
+
or max_5h_existing["m"] is None
|
|
2039
|
+
):
|
|
2040
|
+
need_5h_heal = True
|
|
2041
|
+
elif (
|
|
2042
|
+
int(max_5h_existing["m"])
|
|
2043
|
+
< latest_5h_floor
|
|
2044
|
+
):
|
|
2045
|
+
need_5h_heal = True
|
|
1646
2046
|
finally:
|
|
1647
2047
|
heal_conn.close()
|
|
1648
2048
|
|