cctally 1.7.3 → 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 +10 -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 +40 -110
- package/bin/_cctally_db.py +89 -20
- package/bin/_cctally_record.py +76 -75
- 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 +28 -51
- 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_subscription_weeks.py +17 -9
- package/bin/cctally +49 -822
- package/package.json +2 -1
|
@@ -251,121 +251,55 @@ def _cctally():
|
|
|
251
251
|
return sys.modules["cctally"]
|
|
252
252
|
|
|
253
253
|
|
|
254
|
+
# === Honest imports from extracted homes ===================================
|
|
255
|
+
# Spec 2026-05-17-cctally-core-kernel-extraction.md §3.3: kernel symbols
|
|
256
|
+
# import from _cctally_core; already-decentralized buckets (X = _lib_*,
|
|
257
|
+
# Y = _cctally_*) import from their natural home. These bypass the
|
|
258
|
+
# legacy shim pattern entirely.
|
|
259
|
+
from _cctally_core import (
|
|
260
|
+
eprint,
|
|
261
|
+
now_utc_iso,
|
|
262
|
+
parse_iso_datetime,
|
|
263
|
+
_now_utc,
|
|
264
|
+
_command_as_of,
|
|
265
|
+
open_db,
|
|
266
|
+
get_latest_usage_for_week,
|
|
267
|
+
make_week_ref,
|
|
268
|
+
_get_alerts_config,
|
|
269
|
+
_AlertsConfigError,
|
|
270
|
+
)
|
|
271
|
+
from _lib_display_tz import (
|
|
272
|
+
format_display_dt,
|
|
273
|
+
resolve_display_tz,
|
|
274
|
+
normalize_display_tz_value,
|
|
275
|
+
_compute_display_block,
|
|
276
|
+
)
|
|
277
|
+
from _lib_aggregators import _aggregate_daily, _aggregate_monthly, _aggregate_weekly
|
|
278
|
+
from _lib_pricing import _calculate_entry_cost, _chip_for_model, _short_model_name
|
|
279
|
+
from _lib_five_hour import _canonical_5h_window_key
|
|
280
|
+
from _lib_subscription_weeks import _compute_subscription_weeks
|
|
281
|
+
from _lib_blocks import _group_entries_into_blocks
|
|
282
|
+
from _cctally_config import save_config, _load_config_unlocked
|
|
283
|
+
from _cctally_db import _render_migration_error_banner
|
|
284
|
+
from _cctally_cache import get_entries
|
|
285
|
+
|
|
286
|
+
|
|
254
287
|
# === Module-level back-ref shims for helpers that STAY in bin/cctally ======
|
|
255
288
|
# Each shim resolves ``sys.modules['cctally'].X`` at CALL TIME (not bind
|
|
256
289
|
# time), so monkeypatches on cctally's namespace propagate into the moved
|
|
257
|
-
# code unchanged.
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
|
|
262
|
-
return sys.modules["cctally"].eprint(*args, **kwargs)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
def now_utc_iso(*args, **kwargs):
|
|
266
|
-
return sys.modules["cctally"].now_utc_iso(*args, **kwargs)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
def parse_iso_datetime(*args, **kwargs):
|
|
270
|
-
return sys.modules["cctally"].parse_iso_datetime(*args, **kwargs)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
def _now_utc(*args, **kwargs):
|
|
274
|
-
return sys.modules["cctally"]._now_utc(*args, **kwargs)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def _command_as_of(*args, **kwargs):
|
|
278
|
-
return sys.modules["cctally"]._command_as_of(*args, **kwargs)
|
|
279
|
-
|
|
280
|
-
|
|
290
|
+
# code unchanged. `load_config` and `get_claude_session_entries` STAY as
|
|
291
|
+
# shims even though their natural homes are decentralized (_cctally_config
|
|
292
|
+
# / _cctally_cache) — tests monkeypatch them via `ns["X"]` (21 sites total,
|
|
293
|
+
# audited 2026-05-17); direct imports would silently bypass the patches.
|
|
294
|
+
# See spec §3.5 (carve-out) and §3.7 (stays-on-shim allowlist).
|
|
281
295
|
def load_config(*args, **kwargs):
|
|
282
296
|
return sys.modules["cctally"].load_config(*args, **kwargs)
|
|
283
297
|
|
|
284
298
|
|
|
285
|
-
def save_config(*args, **kwargs):
|
|
286
|
-
return sys.modules["cctally"].save_config(*args, **kwargs)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def open_db(*args, **kwargs):
|
|
290
|
-
return sys.modules["cctally"].open_db(*args, **kwargs)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def get_entries(*args, **kwargs):
|
|
294
|
-
return sys.modules["cctally"].get_entries(*args, **kwargs)
|
|
295
|
-
|
|
296
|
-
|
|
297
299
|
def get_claude_session_entries(*args, **kwargs):
|
|
298
300
|
return sys.modules["cctally"].get_claude_session_entries(*args, **kwargs)
|
|
299
301
|
|
|
300
302
|
|
|
301
|
-
def get_latest_usage_for_week(*args, **kwargs):
|
|
302
|
-
return sys.modules["cctally"].get_latest_usage_for_week(*args, **kwargs)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
def make_week_ref(*args, **kwargs):
|
|
306
|
-
return sys.modules["cctally"].make_week_ref(*args, **kwargs)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def format_display_dt(*args, **kwargs):
|
|
310
|
-
return sys.modules["cctally"].format_display_dt(*args, **kwargs)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
def resolve_display_tz(*args, **kwargs):
|
|
314
|
-
return sys.modules["cctally"].resolve_display_tz(*args, **kwargs)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def normalize_display_tz_value(*args, **kwargs):
|
|
318
|
-
return sys.modules["cctally"].normalize_display_tz_value(*args, **kwargs)
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
def _compute_display_block(*args, **kwargs):
|
|
322
|
-
return sys.modules["cctally"]._compute_display_block(*args, **kwargs)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def _render_migration_error_banner(*args, **kwargs):
|
|
326
|
-
return sys.modules["cctally"]._render_migration_error_banner(*args, **kwargs)
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def _aggregate_daily(*args, **kwargs):
|
|
330
|
-
return sys.modules["cctally"]._aggregate_daily(*args, **kwargs)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
def _aggregate_monthly(*args, **kwargs):
|
|
334
|
-
return sys.modules["cctally"]._aggregate_monthly(*args, **kwargs)
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def _aggregate_weekly(*args, **kwargs):
|
|
338
|
-
return sys.modules["cctally"]._aggregate_weekly(*args, **kwargs)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
def _calculate_entry_cost(*args, **kwargs):
|
|
342
|
-
return sys.modules["cctally"]._calculate_entry_cost(*args, **kwargs)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
def _canonical_5h_window_key(*args, **kwargs):
|
|
346
|
-
return sys.modules["cctally"]._canonical_5h_window_key(*args, **kwargs)
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def _chip_for_model(*args, **kwargs):
|
|
350
|
-
return sys.modules["cctally"]._chip_for_model(*args, **kwargs)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
def _short_model_name(*args, **kwargs):
|
|
354
|
-
return sys.modules["cctally"]._short_model_name(*args, **kwargs)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
def _compute_subscription_weeks(*args, **kwargs):
|
|
358
|
-
return sys.modules["cctally"]._compute_subscription_weeks(*args, **kwargs)
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
def _group_entries_into_blocks(*args, **kwargs):
|
|
362
|
-
return sys.modules["cctally"]._group_entries_into_blocks(*args, **kwargs)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
def _get_alerts_config(*args, **kwargs):
|
|
366
|
-
return sys.modules["cctally"]._get_alerts_config(*args, **kwargs)
|
|
367
|
-
|
|
368
|
-
|
|
369
303
|
def _warn_alerts_bad_config_once(*args, **kwargs):
|
|
370
304
|
return sys.modules["cctally"]._warn_alerts_bad_config_once(*args, **kwargs)
|
|
371
305
|
|
|
@@ -402,10 +336,6 @@ def doctor_gather_state(*args, **kwargs):
|
|
|
402
336
|
return sys.modules["cctally"].doctor_gather_state(*args, **kwargs)
|
|
403
337
|
|
|
404
338
|
|
|
405
|
-
def _load_config_unlocked(*args, **kwargs):
|
|
406
|
-
return sys.modules["cctally"]._load_config_unlocked(*args, **kwargs)
|
|
407
|
-
|
|
408
|
-
|
|
409
339
|
def _apply_display_tz_override(*args, **kwargs):
|
|
410
340
|
return sys.modules["cctally"]._apply_display_tz_override(*args, **kwargs)
|
|
411
341
|
|
|
@@ -2987,7 +2917,7 @@ def snapshot_to_envelope(snap: "DataSnapshot", *,
|
|
|
2987
2917
|
alerts_array = list(getattr(snap, "alerts", []) or [])
|
|
2988
2918
|
try:
|
|
2989
2919
|
_alerts_cfg = _get_alerts_config(load_config())
|
|
2990
|
-
except
|
|
2920
|
+
except _AlertsConfigError as exc:
|
|
2991
2921
|
_warn_alerts_bad_config_once(exc)
|
|
2992
2922
|
_alerts_cfg = {
|
|
2993
2923
|
"enabled": False,
|
|
@@ -3782,7 +3712,7 @@ class DashboardHTTPHandler(BaseHTTPRequestHandler):
|
|
|
3782
3712
|
# save_config has not yet been called).
|
|
3783
3713
|
try:
|
|
3784
3714
|
_get_alerts_config(merged)
|
|
3785
|
-
except
|
|
3715
|
+
except _AlertsConfigError as exc:
|
|
3786
3716
|
self._respond_json(400, {"error": str(exc)})
|
|
3787
3717
|
return
|
|
3788
3718
|
|
package/bin/_cctally_db.py
CHANGED
|
@@ -71,35 +71,32 @@ def _cctally():
|
|
|
71
71
|
return sys.modules["cctally"]
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
# === Honest imports from extracted homes ===================================
|
|
75
|
+
# Spec 2026-05-17-cctally-core-kernel-extraction.md §3.3: kernel symbols
|
|
76
|
+
# import from _cctally_core. The legacy shim functions for these names
|
|
77
|
+
# are deleted.
|
|
78
|
+
from _cctally_core import (
|
|
79
|
+
eprint,
|
|
80
|
+
now_utc_iso,
|
|
81
|
+
parse_iso_datetime,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Module-level back-ref shim for the one Z-high callable that STAYS in
|
|
86
|
+
# bin/cctally. Resolves `sys.modules['cctally'].X` at CALL TIME (not
|
|
87
|
+
# bind time), so monkeypatches on cctally's namespace propagate into the
|
|
88
|
+
# moved code unchanged. `_compute_block_totals` is Z-high (reaches into
|
|
89
|
+
# _cctally_cache via get_claude_session_entries) and is explicitly listed
|
|
90
|
+
# in spec §3.7's stays-on-shim allowlist.
|
|
82
91
|
#
|
|
83
92
|
# Path constants and rarer helpers (`MIGRATION_ERROR_LOG_PATH`,
|
|
84
93
|
# `LOG_DIR`, `DB_PATH`, `CACHE_DB_PATH`, `format_local_iso`) are
|
|
85
94
|
# accessed via the standard `c = _cctally()` + `c.X` pattern instead
|
|
86
95
|
# (call-time lookup so fixture-HOME redirects propagate).
|
|
87
|
-
def now_utc_iso(*args, **kwargs):
|
|
88
|
-
return sys.modules["cctally"].now_utc_iso(*args, **kwargs)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def parse_iso_datetime(*args, **kwargs):
|
|
92
|
-
return sys.modules["cctally"].parse_iso_datetime(*args, **kwargs)
|
|
93
|
-
|
|
94
|
-
|
|
95
96
|
def _compute_block_totals(*args, **kwargs):
|
|
96
97
|
return sys.modules["cctally"]._compute_block_totals(*args, **kwargs)
|
|
97
98
|
|
|
98
99
|
|
|
99
|
-
def eprint(*args, **kwargs):
|
|
100
|
-
return sys.modules["cctally"].eprint(*args, **kwargs)
|
|
101
|
-
|
|
102
|
-
|
|
103
100
|
# === BEGIN MOVED REGIONS ===
|
|
104
101
|
# Regions below are inserted verbatim from bin/cctally. Bare-name
|
|
105
102
|
# references to `now_utc_iso(...)`, `parse_iso_datetime(...)`,
|
|
@@ -1598,6 +1595,78 @@ def _migration_five_hour_milestones_reset_event_id(conn: sqlite3.Connection) ->
|
|
|
1598
1595
|
raise
|
|
1599
1596
|
|
|
1600
1597
|
|
|
1598
|
+
@stats_migration("007_observed_pre_credit_pct")
|
|
1599
|
+
def _migration_observed_pre_credit_pct(conn: sqlite3.Connection) -> None:
|
|
1600
|
+
"""Add ``observed_pre_credit_pct`` to ``week_reset_events`` so the
|
|
1601
|
+
race-defensive cleanup DELETE in the in-place weekly credit branch
|
|
1602
|
+
has a durable record of the pre-credit baseline we observed at
|
|
1603
|
+
write time — independent of how the upstream claude-statusline
|
|
1604
|
+
tool rounds replays.
|
|
1605
|
+
|
|
1606
|
+
Today statusline replays cctally's ``hwm-7d`` value byte-identically,
|
|
1607
|
+
so the existing strict ``round(.,1)`` equality predicate is sound.
|
|
1608
|
+
Future-proofs against rounding drift: if Anthropic ever rounds the
|
|
1609
|
+
``--percent`` payload differently from the OAuth API used by
|
|
1610
|
+
record-usage, or if statusline grows its own coarser rounding, a
|
|
1611
|
+
replay at e.g. 67.5 against a stored prior_pct = 67.4 would slip
|
|
1612
|
+
past strict equality and then dominate the reset-aware clamp's
|
|
1613
|
+
MAX over the post-credit segment. With the value stamped on the
|
|
1614
|
+
event row, the cleanup predicate widens to a 1.0pp tolerance band
|
|
1615
|
+
(issue #45) — wide enough to absorb single-digit drift, narrow
|
|
1616
|
+
enough that legitimate post-credit observations (≥25pp away by
|
|
1617
|
+
the in-place credit detection threshold's hypothesis) stay.
|
|
1618
|
+
|
|
1619
|
+
Backfill: NULL on existing rows. NULL is legacy / never-stamped;
|
|
1620
|
+
the live cleanup's bind still uses the current tick's in-scope
|
|
1621
|
+
``prior_pct`` (the value we just observed and would have stamped),
|
|
1622
|
+
so the cleanup remains correct on the very tick that writes the
|
|
1623
|
+
row. The stored value matters for future tooling that may re-run
|
|
1624
|
+
cleanup against an already-written event row.
|
|
1625
|
+
|
|
1626
|
+
Companion live-path edits land in:
|
|
1627
|
+
- bin/cctally — CREATE TABLE adds the column for fresh installs.
|
|
1628
|
+
- bin/_cctally_record.py — in-place credit INSERT stamps
|
|
1629
|
+
``observed_pre_credit_pct = prior_pct``; race-defensive DELETE
|
|
1630
|
+
switches from ``round(weekly_percent,1) = round(?,1)`` to
|
|
1631
|
+
``ABS(weekly_percent - ?) < 1.0``.
|
|
1632
|
+
|
|
1633
|
+
Idempotent: a second invocation finds the column already present
|
|
1634
|
+
and returns. Empty-column fast path: when the live CREATE TABLE
|
|
1635
|
+
already carries the column (fresh install), stamp the marker and
|
|
1636
|
+
return without an ALTER. Simple ADD COLUMN — no UNIQUE constraint
|
|
1637
|
+
change, so no rename-recreate-copy needed (contrast migrations
|
|
1638
|
+
005 / 006).
|
|
1639
|
+
"""
|
|
1640
|
+
cols = {
|
|
1641
|
+
str(r[1])
|
|
1642
|
+
for r in conn.execute("PRAGMA table_info(week_reset_events)").fetchall()
|
|
1643
|
+
}
|
|
1644
|
+
if "observed_pre_credit_pct" in cols:
|
|
1645
|
+
conn.execute(
|
|
1646
|
+
"INSERT OR IGNORE INTO schema_migrations (name, applied_at_utc) "
|
|
1647
|
+
"VALUES (?, ?)",
|
|
1648
|
+
("007_observed_pre_credit_pct", now_utc_iso()),
|
|
1649
|
+
)
|
|
1650
|
+
conn.commit()
|
|
1651
|
+
return
|
|
1652
|
+
|
|
1653
|
+
conn.execute("BEGIN")
|
|
1654
|
+
try:
|
|
1655
|
+
conn.execute(
|
|
1656
|
+
"ALTER TABLE week_reset_events "
|
|
1657
|
+
"ADD COLUMN observed_pre_credit_pct REAL"
|
|
1658
|
+
)
|
|
1659
|
+
conn.execute(
|
|
1660
|
+
"INSERT OR IGNORE INTO schema_migrations (name, applied_at_utc) "
|
|
1661
|
+
"VALUES (?, ?)",
|
|
1662
|
+
("007_observed_pre_credit_pct", now_utc_iso()),
|
|
1663
|
+
)
|
|
1664
|
+
conn.commit()
|
|
1665
|
+
except Exception:
|
|
1666
|
+
conn.rollback()
|
|
1667
|
+
raise
|
|
1668
|
+
|
|
1669
|
+
|
|
1601
1670
|
# === Region 8: Test-only migration registration (was bin/cctally:12086-12140) ===
|
|
1602
1671
|
|
|
1603
1672
|
# ──────────────────────────────────────────────────────────────────────
|
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
|
|
|
@@ -532,7 +507,7 @@ def maybe_record_milestone(
|
|
|
532
507
|
# the underlying problem is config-wide, not axis-specific.
|
|
533
508
|
try:
|
|
534
509
|
alerts_cfg: "dict | None" = _get_alerts_config(load_config())
|
|
535
|
-
except
|
|
510
|
+
except _AlertsConfigError as exc:
|
|
536
511
|
_warn_alerts_bad_config_once(exc)
|
|
537
512
|
alerts_cfg = None
|
|
538
513
|
|
|
@@ -803,7 +778,7 @@ def maybe_update_five_hour_block(saved: dict[str, Any]) -> None:
|
|
|
803
778
|
cfg_for_alerts = load_config()
|
|
804
779
|
try:
|
|
805
780
|
alerts_cfg: "dict | None" = _get_alerts_config(cfg_for_alerts)
|
|
806
|
-
except
|
|
781
|
+
except _AlertsConfigError as exc:
|
|
807
782
|
_warn_alerts_bad_config_once(exc)
|
|
808
783
|
alerts_cfg = None
|
|
809
784
|
# Resolve display.tz once (shares the cfg load above). Threaded
|
|
@@ -1500,12 +1475,19 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1500
1475
|
# UNIQUE(old, new) constraint permits this
|
|
1501
1476
|
# row, and the pre-check above keys on
|
|
1502
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.
|
|
1503
1484
|
conn.execute(
|
|
1504
1485
|
"INSERT OR IGNORE INTO week_reset_events "
|
|
1505
1486
|
"(detected_at_utc, old_week_end_at, new_week_end_at, "
|
|
1506
|
-
" effective_reset_at_utc)
|
|
1487
|
+
" effective_reset_at_utc, observed_pre_credit_pct) "
|
|
1488
|
+
"VALUES (?, ?, ?, ?, ?)",
|
|
1507
1489
|
(now_utc_iso(), effective_iso, cur_end_canon,
|
|
1508
|
-
effective_iso),
|
|
1490
|
+
effective_iso, float(prior_pct)),
|
|
1509
1491
|
)
|
|
1510
1492
|
conn.commit()
|
|
1511
1493
|
# Pivots fire UNCONDITIONALLY whenever a credit
|
|
@@ -1553,24 +1535,29 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1553
1535
|
# own in-memory HWM cache and re-runs us once
|
|
1554
1536
|
# per status-line tick). Those replays land
|
|
1555
1537
|
# captured_at_utc >= effective_iso with
|
|
1556
|
-
# weekly_percent
|
|
1538
|
+
# weekly_percent near prior_pct (the pre-credit
|
|
1557
1539
|
# value), and they dominate the reset-aware
|
|
1558
1540
|
# clamp's MAX over the post-credit segment so
|
|
1559
1541
|
# legitimate fresh OAuth values are rejected.
|
|
1560
|
-
#
|
|
1561
|
-
#
|
|
1562
|
-
#
|
|
1563
|
-
#
|
|
1564
|
-
#
|
|
1565
|
-
#
|
|
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.
|
|
1566
1554
|
try:
|
|
1567
1555
|
conn.execute(
|
|
1568
1556
|
"DELETE FROM weekly_usage_snapshots "
|
|
1569
1557
|
"WHERE week_start_date = ? "
|
|
1570
1558
|
" AND unixepoch(captured_at_utc) >= "
|
|
1571
1559
|
" unixepoch(?) "
|
|
1572
|
-
" AND
|
|
1573
|
-
" round(?, 1)",
|
|
1560
|
+
" AND ABS(weekly_percent - ?) < 1.0",
|
|
1574
1561
|
(week_start_date, effective_iso,
|
|
1575
1562
|
float(prior_pct)),
|
|
1576
1563
|
)
|
|
@@ -1755,20 +1742,34 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1755
1742
|
# replaying the pre-credit
|
|
1756
1743
|
# ``--five-hour-percent`` value past the
|
|
1757
1744
|
# credit moment from its own in-memory
|
|
1758
|
-
# HWM cache.
|
|
1759
|
-
#
|
|
1760
|
-
#
|
|
1761
|
-
#
|
|
1762
|
-
#
|
|
1763
|
-
#
|
|
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.
|
|
1764
1765
|
try:
|
|
1765
1766
|
conn.execute(
|
|
1766
1767
|
"DELETE FROM weekly_usage_snapshots "
|
|
1767
1768
|
" WHERE five_hour_window_key = ? "
|
|
1768
1769
|
" AND unixepoch(captured_at_utc) "
|
|
1769
1770
|
" >= unixepoch(?) "
|
|
1770
|
-
" AND
|
|
1771
|
-
"
|
|
1771
|
+
" AND ABS(five_hour_percent - ?) "
|
|
1772
|
+
" < 1.0",
|
|
1772
1773
|
(
|
|
1773
1774
|
int(five_hour_window_key),
|
|
1774
1775
|
effective_iso,
|