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.
@@ -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. Mirrors the precedent established in
258
- # ``bin/_cctally_record.py`` (34 shims), ``bin/_cctally_cache.py``
259
- # (4 shims), ``bin/_cctally_db.py`` (4 shims), and
260
- # ``bin/_cctally_update.py`` (8 shims).
261
- def eprint(*args, **kwargs):
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 sys.modules["cctally"]._AlertsConfigError as exc:
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 sys.modules["cctally"]._AlertsConfigError as exc:
3715
+ except _AlertsConfigError as exc:
3786
3716
  self._respond_json(400, {"error": str(exc)})
3787
3717
  return
3788
3718
 
@@ -71,35 +71,32 @@ def _cctally():
71
71
  return sys.modules["cctally"]
72
72
 
73
73
 
74
- # Module-level back-ref shims for the four callables most heavily used
75
- # across migration handlers + cmd_db_* renderers. Each shim resolves
76
- # `sys.modules['cctally'].X` at CALL TIME (not bind time), so
77
- # monkeypatches on cctally's namespace propagate into the moved code
78
- # unchanged. This lets the moved function bodies stay byte-identical
79
- # at every bare-name call site (`now_utc_iso(...)`,
80
- # `parse_iso_datetime(...)`, etc.) without requiring per-function
81
- # `c = _cctally()` boilerplate or `c.X` rewrites at every call site.
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
  # ──────────────────────────────────────────────────────────────────────
@@ -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. Mirrors the precedent established in
163
- # ``bin/_cctally_cache.py`` and ``bin/_cctally_db.py``.
164
- def eprint(*args, **kwargs):
165
- return sys.modules["cctally"].eprint(*args, **kwargs)
166
-
167
-
168
- def now_utc_iso(*args, **kwargs):
169
- return sys.modules["cctally"].now_utc_iso(*args, **kwargs)
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 open_db(*args, **kwargs):
177
- return sys.modules["cctally"].open_db(*args, **kwargs)
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 sys.modules["cctally"]._AlertsConfigError as exc:
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 sys.modules["cctally"]._AlertsConfigError as exc:
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) VALUES (?, ?, ?, ?)",
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 == prior_pct (the pre-credit
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
- # Strict equality (round(.,1)) keeps this
1561
- # narrow: we only delete rows whose percent
1562
- # exactly matches the pre-credit value we just
1563
- # observed legitimate post-credit climbs
1564
- # past `prior_pct` (rare, but possible if the
1565
- # credit is small + activity is heavy) stay.
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 round(weekly_percent, 1) = "
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. Strict round-1 equality
1759
- # keeps the scope narrow only rows
1760
- # whose five_hour_percent exactly matches
1761
- # the just-observed pre-credit value are
1762
- # removed. ``unixepoch()`` on both sides
1763
- # for offset robustness (Z vs +00:00).
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 round(five_hour_percent, 1) "
1771
- " = round(?, 1)",
1771
+ " AND ABS(five_hour_percent - ?) "
1772
+ " < 1.0",
1772
1773
  (
1773
1774
  int(five_hour_window_key),
1774
1775
  effective_iso,