cctally 1.117.0 → 1.118.0

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +7 -3
  3. package/bin/_cctally_cache.py +2783 -175
  4. package/bin/_cctally_completion.py +106 -33
  5. package/bin/_cctally_core.py +69 -4
  6. package/bin/_cctally_dashboard.py +227 -23
  7. package/bin/_cctally_dashboard_conversation.py +5 -1
  8. package/bin/_cctally_dashboard_envelope.py +60 -19
  9. package/bin/_cctally_dashboard_sources.py +23 -1
  10. package/bin/_cctally_db.py +193 -153
  11. package/bin/_cctally_diagnosis_sources.py +63 -7
  12. package/bin/_cctally_doctor.py +865 -93
  13. package/bin/_cctally_five_hour.py +124 -54
  14. package/bin/_cctally_forecast.py +301 -7
  15. package/bin/_cctally_journal.py +186 -18
  16. package/bin/_cctally_milestone_history.py +1193 -108
  17. package/bin/_cctally_oauth_samples.py +286 -15
  18. package/bin/_cctally_parser.py +2 -2
  19. package/bin/_cctally_quota.py +104 -12
  20. package/bin/_cctally_record.py +153 -29
  21. package/bin/_cctally_rederive.py +608 -68
  22. package/bin/_cctally_statusline.py +108 -81
  23. package/bin/_cctally_store.py +179 -38
  24. package/bin/_cctally_tui.py +1210 -170
  25. package/bin/_cctally_update.py +32 -13
  26. package/bin/_lib_codex_conversation_query.py +52 -14
  27. package/bin/_lib_codex_drain_admission.py +224 -6
  28. package/bin/_lib_codex_metadata.py +39 -0
  29. package/bin/_lib_conversation_anon.py +15 -8
  30. package/bin/_lib_conversation_dispatch.py +7 -0
  31. package/bin/_lib_conversation_query.py +289 -70
  32. package/bin/_lib_dashboard_sources.py +131 -9
  33. package/bin/_lib_detector_access_registry.py +497 -26
  34. package/bin/_lib_doctor.py +85 -20
  35. package/bin/_lib_doctor_scan_index.py +321 -0
  36. package/bin/_lib_ingest_frontier.py +20 -1
  37. package/bin/_lib_oauth_samples.py +23 -7
  38. package/bin/_lib_pricing.py +146 -16
  39. package/bin/_lib_quota.py +19 -0
  40. package/bin/_lib_quota_model.py +29 -11
  41. package/bin/_lib_rederive.py +11 -6
  42. package/bin/_lib_retained_records.py +516 -0
  43. package/bin/_lib_snapshot_cache.py +18 -0
  44. package/bin/_lib_stale_proof.py +272 -43
  45. package/bin/_lib_stats_content_revision.py +831 -0
  46. package/bin/_lib_tick_stats.py +10 -5
  47. package/bin/_lib_write_io.py +12 -5
  48. package/bin/cctally +8 -1
  49. package/dashboard/static/assets/ConversationsView-D4Iv4ku0.js +72 -0
  50. package/dashboard/static/assets/{DoctorModal-BqmXbnhn.js → DoctorModal-D4QSq6uz.js} +1 -1
  51. package/dashboard/static/assets/ModalRoot-C9F5dhZi.js +1 -0
  52. package/dashboard/static/assets/{ProjectsDrillPanel-B-HZkDnT.js → ProjectsDrillPanel-WxAdRDMt.js} +1 -1
  53. package/dashboard/static/assets/{SourceDetailModal-QhJkHB7i.js → SourceDetailModal-NyyEfXmc.js} +1 -1
  54. package/dashboard/static/assets/{UpdateModal-CkXEIQcK.js → UpdateModal-CFLq9pT1.js} +1 -1
  55. package/dashboard/static/assets/{index-HKS1s56o.js → index-BKTWH9JE.js} +2 -2
  56. package/dashboard/static/assets/{outlineNavigation-BNGaNPCp.js → outlineNavigation-BnGUwCS3.js} +3 -3
  57. package/dashboard/static/dashboard.html +1 -1
  58. package/package.json +4 -1
  59. package/dashboard/static/assets/ConversationsView-Dcfn8T6f.js +0 -72
  60. package/dashboard/static/assets/ModalRoot-Ck3WJK2V.js +0 -1
@@ -44,8 +44,10 @@ import sqlite3
44
44
  import sys
45
45
  from dataclasses import dataclass
46
46
 
47
+ import _cctally_core # `weekly_held_exclusion`: the held-column guard (#836)
47
48
  import _lib_accounts # pure stdlib kernel; UNATTRIBUTED sentinel (#341)
48
49
  import _lib_rebuild_progress # #997 D9 progress ticks (stdlib only)
50
+ import _lib_stats_content_revision # #1050 identical child-set check
49
51
  from _lib_five_hour import latest_five_hour_reading
50
52
  from _cctally_core import _command_as_of, eprint, now_utc_iso, open_db, parse_iso_datetime
51
53
 
@@ -1086,13 +1088,57 @@ def _credit_aware_block_weekly_axes(conn, block, *, now_utc, cache=None):
1086
1088
  block.get("seven_day_pct_at_block_end"))
1087
1089
 
1088
1090
 
1091
+ def _weekly_retired_sql(conn, alias: str) -> "str | None":
1092
+ """The suppression-retired predicate over ``alias``, or None on a store
1093
+ without the held column (#836, spec 5.4).
1094
+
1095
+ A held WRITE stores ``"weeklyObservationHeld": true`` in ``payload_json``
1096
+ beside its effective basis value (`_cctally_record`, the held branch of
1097
+ the snapshot write). A weekly credit's cleanup instead turns an UNHELD row,
1098
+ whose payload never carried that marker, into a held one and leaves its
1099
+ stale `weekly_percent` in place (`_delete_doomed_snapshot_rows`, replay
1100
+ `_cctally_journal._retire_weekly_effects`). The marker therefore separates
1101
+ the two exactly. A store without the column cannot hold a held row, the
1102
+ reason `_cctally_core.weekly_held_exclusion` omits its own predicate there.
1103
+ """
1104
+ if not _cctally_core.weekly_held_exclusion(conn):
1105
+ return None
1106
+ return (f"({alias}.weekly_observation_held = 1 AND "
1107
+ f"json_extract({alias}.payload_json, '$.weeklyObservationHeld') "
1108
+ f"IS NOT 1)")
1109
+
1110
+
1111
+ def effective_weekly_value_sql(conn, alias: str = "s") -> str:
1112
+ """The joined snapshot's EFFECTIVE weekly value (#836, spec 5.4): NULL for
1113
+ a suppression-retired row, its stored `weekly_percent` otherwise. A
1114
+ clamped held write and a genuine climb-back keep their values; no
1115
+ percentage band is involved."""
1116
+ retired = _weekly_retired_sql(conn, alias)
1117
+ if retired is None:
1118
+ return f"{alias}.weekly_percent"
1119
+ return f"CASE WHEN {retired} THEN NULL ELSE {alias}.weekly_percent END"
1120
+
1121
+
1122
+ def weekly_join_unavailable_sql(conn, alias: str = "s") -> str:
1123
+ """A five-hour milestone's effective weekly join is UNAVAILABLE (#836,
1124
+ spec 5.4): the joined snapshot is absent — deleted, or the ``0`` sentinel
1125
+ `trg_usage_snapshot_suppression` writes — or suppression-retired. The
1126
+ shared predicate of the detail stamp and the dashboard's retired-join
1127
+ signature leg, over a ``LEFT JOIN`` aliased ``alias``."""
1128
+ retired = _weekly_retired_sql(conn, alias)
1129
+ if retired is None:
1130
+ return f"({alias}.id IS NULL)"
1131
+ return f"({alias}.id IS NULL OR {retired})"
1132
+
1133
+
1089
1134
  #: The six `five_hour_milestones` columns every surface already published, plus
1090
- #: the joined EFFECTIVE weekly value. Ordering is `captured_at_utc, id` — spec
1091
- #: §5.2's merged-stream order, which post-credit threshold repeats depend on.
1135
+ #: the joined EFFECTIVE weekly value (`effective_weekly_value_sql`, filled in
1136
+ #: as ``{effective}``). Ordering is `captured_at_utc, id` — spec §5.2's
1137
+ #: merged-stream order, which post-credit threshold repeats depend on.
1092
1138
  _FIVE_HOUR_MILESTONE_SQL = """
1093
1139
  SELECT m.percent_threshold, m.captured_at_utc, m.block_cost_usd,
1094
1140
  m.marginal_cost_usd, m.seven_day_pct_at_crossing, m.reset_event_id,
1095
- s.weekly_percent AS effective_seven_day_pct_at_crossing
1141
+ {effective} AS effective_seven_day_pct_at_crossing
1096
1142
  FROM five_hour_milestones m
1097
1143
  LEFT JOIN weekly_usage_snapshots s ON s.id = m.usage_snapshot_id
1098
1144
  WHERE {selector}
@@ -1139,6 +1185,12 @@ def _load_five_hour_milestones(
1139
1185
  store is corrected by `db rederive`'s reviewed `retract`. A missing joined
1140
1186
  snapshot still publishes None and never falls back to the raw value.
1141
1187
 
1188
+ **A suppression-retired snapshot publishes None** (#836, spec 5.4). The
1189
+ fold rejected its weekly value, and the cleanup kept the row only for its
1190
+ five-hour evidence; its provenance is exact (no held-write marker on a
1191
+ held row), so this is not the withdrawn band. See
1192
+ `effective_weekly_value_sql`.
1193
+
1142
1194
  SELECTOR. Exactly one of `block_id` or `five_hour_window_key` must be given.
1143
1195
  `block_id` is the precise form — `five_hour_milestones.block_id` names one
1144
1196
  block outright. The pair form filters on the window key AND the account,
@@ -1166,7 +1218,9 @@ def _load_five_hour_milestones(
1166
1218
  selector += " AND m.account_key = ?"
1167
1219
  params += (account_key,)
1168
1220
  rows = conn.execute(
1169
- _FIVE_HOUR_MILESTONE_SQL.format(selector=selector), params,
1221
+ _FIVE_HOUR_MILESTONE_SQL.format(
1222
+ selector=selector,
1223
+ effective=effective_weekly_value_sql(conn, "s")), params,
1170
1224
  ).fetchall()
1171
1225
  out: list[dict] = []
1172
1226
  for r in rows:
@@ -1981,60 +2035,76 @@ def project_open_block_end(
1981
2035
  ),
1982
2036
  )
1983
2037
  # Replace-all children, keyed on the window (not block_id) so an orphan
1984
- # child of an earlier parent rebuild is cleaned up as well.
2038
+ # child of an earlier parent rebuild is cleaned up as well. An identical
2039
+ # stored set is left in place (#1050): a DELETE and re-INSERT of the same
2040
+ # rows would advance the content revision with nothing changed.
1985
2041
  block_id = int(current["id"])
1986
- conn.execute(
1987
- "DELETE FROM five_hour_block_models "
1988
- "WHERE five_hour_window_key = ? AND account_key = ?",
1989
- (window_key, account_key),
1990
- )
1991
- if totals.get("by_model"):
1992
- conn.executemany(
1993
- """
1994
- INSERT INTO five_hour_block_models (
1995
- block_id, five_hour_window_key, model,
1996
- input_tokens, output_tokens,
1997
- cache_create_tokens, cache_read_tokens,
1998
- cost_usd, entry_count, account_key
1999
- )
2000
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2001
- """,
2002
- [
2003
- (
2004
- block_id, window_key, model,
2005
- b["input_tokens"], b["output_tokens"],
2006
- b["cache_create_tokens"], b["cache_read_tokens"],
2007
- b["cost_usd"], b["entry_count"], account_key,
2008
- )
2009
- for model, b in totals["by_model"].items()
2010
- ],
2042
+ model_rows = _open_block_child_rows(
2043
+ block_id, window_key, account_key, "model", totals.get("by_model"))
2044
+ if not _lib_stats_content_revision.child_set_unchanged(
2045
+ conn, "five_hour_block_models", five_hour_window_key=window_key,
2046
+ account_key=account_key, replacement=model_rows):
2047
+ conn.execute(
2048
+ "DELETE FROM five_hour_block_models "
2049
+ "WHERE five_hour_window_key = ? AND account_key = ?",
2050
+ (window_key, account_key),
2011
2051
  )
2012
- conn.execute(
2013
- "DELETE FROM five_hour_block_projects "
2014
- "WHERE five_hour_window_key = ? AND account_key = ?",
2015
- (window_key, account_key),
2016
- )
2017
- if totals.get("by_project"):
2018
- conn.executemany(
2019
- """
2020
- INSERT INTO five_hour_block_projects (
2021
- block_id, five_hour_window_key, project_path,
2022
- input_tokens, output_tokens,
2023
- cache_create_tokens, cache_read_tokens,
2024
- cost_usd, entry_count, account_key
2025
- )
2026
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2027
- """,
2028
- [
2029
- (
2030
- block_id, window_key, project_path,
2031
- b["input_tokens"], b["output_tokens"],
2032
- b["cache_create_tokens"], b["cache_read_tokens"],
2033
- b["cost_usd"], b["entry_count"], account_key,
2052
+ if model_rows:
2053
+ conn.executemany(
2054
+ """
2055
+ INSERT INTO five_hour_block_models (
2056
+ block_id, five_hour_window_key, model,
2057
+ input_tokens, output_tokens,
2058
+ cache_create_tokens, cache_read_tokens,
2059
+ cost_usd, entry_count, account_key
2034
2060
  )
2035
- for project_path, b in totals["by_project"].items()
2036
- ],
2061
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2062
+ """,
2063
+ [tuple(row.values()) for row in model_rows],
2064
+ )
2065
+ project_rows = _open_block_child_rows(
2066
+ block_id, window_key, account_key, "project_path",
2067
+ totals.get("by_project"))
2068
+ if not _lib_stats_content_revision.child_set_unchanged(
2069
+ conn, "five_hour_block_projects", five_hour_window_key=window_key,
2070
+ account_key=account_key, replacement=project_rows):
2071
+ conn.execute(
2072
+ "DELETE FROM five_hour_block_projects "
2073
+ "WHERE five_hour_window_key = ? AND account_key = ?",
2074
+ (window_key, account_key),
2037
2075
  )
2076
+ if project_rows:
2077
+ conn.executemany(
2078
+ """
2079
+ INSERT INTO five_hour_block_projects (
2080
+ block_id, five_hour_window_key, project_path,
2081
+ input_tokens, output_tokens,
2082
+ cache_create_tokens, cache_read_tokens,
2083
+ cost_usd, entry_count, account_key
2084
+ )
2085
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2086
+ """,
2087
+ [tuple(row.values()) for row in project_rows],
2088
+ )
2089
+
2090
+
2091
+ def _open_block_child_rows(block_id, window_key, account_key, label,
2092
+ breakdown) -> "list[dict]":
2093
+ """One open block's child rows from a `_compute_block_totals` breakdown,
2094
+ as column → value mappings in the INSERT's column order."""
2095
+ return [
2096
+ {
2097
+ "block_id": block_id, "five_hour_window_key": window_key,
2098
+ label: name,
2099
+ "input_tokens": b["input_tokens"],
2100
+ "output_tokens": b["output_tokens"],
2101
+ "cache_create_tokens": b["cache_create_tokens"],
2102
+ "cache_read_tokens": b["cache_read_tokens"],
2103
+ "cost_usd": b["cost_usd"], "entry_count": b["entry_count"],
2104
+ "account_key": account_key,
2105
+ }
2106
+ for name, b in (breakdown or {}).items()
2107
+ ]
2038
2108
 
2039
2109
 
2040
2110
  def _materialize_open_candidate(conn, key, acct, start_row) -> int:
@@ -52,6 +52,12 @@ from _cctally_core import (
52
52
  _BudgetConfigError, _get_budget_config,
53
53
  BUDGET_PERIODS as _CCTALLY_BUDGET_PERIODS,
54
54
  )
55
+ # #836: the decorated Current Week's owner and cycle identity read the reset
56
+ # chokepoint and the held-column guard directly; neither is ns-patched.
57
+ from _cctally_core import (
58
+ _canonicalize_optional_iso, _latest_reset_event_for_end,
59
+ weekly_held_exclusion,
60
+ )
55
61
 
56
62
  import importlib.util as _ilu
57
63
 
@@ -129,15 +135,19 @@ def _resolve_forecast_now(as_of: str | None) -> dt.datetime:
129
135
 
130
136
 
131
137
  def _shape_week_samples(rows, *, include_held: bool,
132
- include_account: bool = False) -> list:
138
+ include_account: bool = False,
139
+ include_journal_id: bool = False) -> list:
133
140
  """Turn snapshot rows into the sample tuples `_fetch_current_week_snapshots`
134
141
  returns. Both of that function's row-fetch legs go through here so the two
135
142
  cannot drift on the tuple width.
136
143
 
137
144
  `rows` are `(captured_at_utc, weekly_percent, five_hour_percent,
138
- weekly_observation_held[, account_key])`. The held flag is appended only
139
- for a held-inclusive read, and the account only for an explicit identity
140
- read. Existing consumers keep the historical three- or four-field shape.
145
+ weekly_observation_held[, account_key][, journal_id])`. The held flag is
146
+ appended only for a held-inclusive read, the account only for an explicit
147
+ identity read, and the row's `journal_id` (#1018 twin spec 5.3: the
148
+ displayed row's originating observation) only when asked for, after
149
+ every other field. Existing consumers keep the historical three- or
150
+ four-field shape.
141
151
  """
142
152
  out = []
143
153
  for row in rows:
@@ -150,6 +160,8 @@ def _shape_week_samples(rows, *, include_held: bool,
150
160
  sample = sample + (int(row[3] or 0),)
151
161
  if include_account:
152
162
  sample = sample + (row[4],)
163
+ if include_journal_id:
164
+ sample = sample + (row[5 if include_account else 4],)
153
165
  out.append(sample)
154
166
  return out
155
167
 
@@ -177,7 +189,8 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
177
189
  *, account_key: "str | None" = None,
178
190
  include_held: bool = False,
179
191
  include_account: bool = False,
180
- include_scope: bool = False):
192
+ include_scope: bool = False,
193
+ include_journal_id: bool = False):
181
194
  """Return (week_start_at, week_end_at, list[(captured_at, percent, five_hr)])
182
195
  for the subscription week containing `now_utc`, or None if no snapshot
183
196
  exists for the current week.
@@ -235,6 +248,12 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
235
248
  reading the five-hour axis selects its reading under exactly this scope
236
249
  rather than from the newest sample, which a retired replica or a
237
250
  weekly-only reading leaves without a five-hour value.
251
+
252
+ ``include_journal_id`` (#1018 twin spec 5.3) appends each row's
253
+ `journal_id` as the LAST sample field, after the held flag and the
254
+ account, so the Current Week card can resolve its displayed row's
255
+ originating observation. Every other caller keeps its tuple width, and
256
+ the trailing SQL row id ordering is unchanged.
238
257
  """
239
258
  # #341: optional account scoping — same predicate on every leg so the whole
240
259
  # window resolution stays consistent (a real key or the `unattributed`
@@ -248,6 +267,8 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
248
267
  # The TUI's multi-account card needs the physical owner of the sample it
249
268
  # actually displays. Keep the historical sample shape for every other read.
250
269
  _owner_column = ", account_key" if include_account else ""
270
+ if include_journal_id:
271
+ _owner_column += ", journal_id"
251
272
  _sample_order = " ASC, id ASC" if include_account else " ASC"
252
273
  candidates = conn.execute(
253
274
  "SELECT week_start_at, week_end_at, week_start_date, MAX(captured_at_utc) AS latest_cap "
@@ -304,7 +325,8 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
304
325
  (drow[0],) + _acct_p,
305
326
  ).fetchall()
306
327
  samples = _shape_week_samples(rows, include_held=include_held,
307
- include_account=include_account)
328
+ include_account=include_account,
329
+ include_journal_id=include_journal_id)
308
330
  samples = [s for s in samples if s[0] <= now_utc]
309
331
  if include_scope:
310
332
  return week_start_at, week_end_at, samples, (drow[0], None)
@@ -337,7 +359,8 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
337
359
  tuple(matching_texts) + (chosen_date,) + _acct_p,
338
360
  ), by_id=include_account)
339
361
  samples = _shape_week_samples(rows, include_held=include_held,
340
- include_account=include_account)
362
+ include_account=include_account,
363
+ include_journal_id=include_journal_id)
341
364
  samples = [s for s in samples if s[0] <= now_utc]
342
365
  if include_scope:
343
366
  return (week_start_at, week_end_at, samples,
@@ -389,6 +412,277 @@ def _apply_midweek_reset_override(
389
412
  return week_start_at, samples
390
413
 
391
414
 
415
+ # === #836: the decorated Current Week — one owner, one cycle identity =======
416
+ #
417
+ # On a store with more than one REAL Claude account (`provider_is_decorated`)
418
+ # the Current Week card, its weekly milestones and the history index's current
419
+ # entry describe ONE account: the owner of the newest non-held weekly sample at
420
+ # the build's as-of instant (#839's contract, spec 5.2). The merged reads above
421
+ # stay unchanged for every undecorated store and every other caller.
422
+
423
+ #: Every subscription window that can contain an instant starts no more than
424
+ #: seven days (plus host-offset slack on `week_start_date`) before it, so this
425
+ #: leading `week_start_date` bound keeps every candidate read an
426
+ #: `idx_usage_week_time` range rather than a scan of the whole history.
427
+ _CLAUDE_CYCLE_CANDIDATE_DAYS = 8
428
+
429
+
430
+ @dataclass(frozen=True)
431
+ class ClaudeCycleScope:
432
+ """The decorated Current Week's cycle identity (#836, spec 5.2 step 2).
433
+
434
+ Resolved once per build by `_claude_current_cycle_scope` and shared by the
435
+ card, the weekly milestones and the scoped history index, so the three
436
+ readers of one cycle cannot choose it differently.
437
+
438
+ ``account_key``: the owner. ``as_of_utc``: the build instant (ISO-8601
439
+ ``Z``); reset events are admitted only at or before it.
440
+ ``week_start_date``: the storage key milestones are recorded under.
441
+ ``week_start_at``: the owner's stored boundary text (``None`` on the
442
+ date-only leg). ``week_end_at``: the canonical end
443
+ (`_canonicalize_optional_iso`, the `week_reset_events.new_week_end_at`
444
+ form; ``None`` on the date-only leg). ``effective_start_at``: the week
445
+ start, or the active reset's effective instant when later (ISO-8601 ``Z``;
446
+ ``None`` on the date-only leg). ``reset_event_id``: the active reset's id
447
+ (the milestone segment), ``0`` when none.
448
+ """
449
+ account_key: str
450
+ as_of_utc: str
451
+ week_start_date: str
452
+ week_start_at: "str | None"
453
+ week_end_at: "str | None"
454
+ effective_start_at: "str | None"
455
+ reset_event_id: int
456
+
457
+
458
+ def _claude_newest_weekly_sample(conn, now_utc: dt.datetime, *,
459
+ account_key: "str | None" = None):
460
+ """The newest non-held weekly sample captured at or before ``now_utc``
461
+ among windows containing it, or ``None`` (#836, spec 5.2 step 1).
462
+
463
+ Only non-held rows take part, so a newer held row of another account (a
464
+ copy of an older weekly value under a newer capture) never chooses the
465
+ window — the defect of the held-inclusive ``MAX(captured_at_utc)`` window
466
+ choice in `_fetch_current_week_snapshots` (spec D-C). Captures after
467
+ ``now_utc`` never participate. Boundaryful windows
468
+ (``[week_start_at, week_end_at)`` containing ``now_utc``) are preferred;
469
+ when none qualifies, the date-only leg (``week_start_date`` ..
470
+ ``week_end_date`` containing today's host-local date) applies, as in
471
+ `_fetch_current_week_snapshots`. Ties break by ``(captured_at_utc, id)``.
472
+
473
+ Returns a dict of the row's identity and boundary columns plus
474
+ ``boundaryful``: whether the CHOSEN row carries both parseable bounds,
475
+ never which pool it came from. On the window's end date, after its reset
476
+ instant and before any next-window sample, a bounded row reaches the
477
+ date-only pool; its scope is still built from its own bounds, so the
478
+ account's own reset stays in effect (#836 review F1).
479
+ """
480
+ floor_date = (now_utc.astimezone(dt.timezone.utc).date()
481
+ - dt.timedelta(days=_CLAUDE_CYCLE_CANDIDATE_DAYS)).isoformat()
482
+ acct_pred = "" if account_key is None else " AND account_key = ?"
483
+ acct_p: tuple = () if account_key is None else (account_key,)
484
+ rows = conn.execute(
485
+ "SELECT id, account_key, week_start_date, week_end_date, "
486
+ " week_start_at, week_end_at, captured_at_utc "
487
+ "FROM weekly_usage_snapshots "
488
+ "WHERE week_start_date >= ?" + acct_pred
489
+ + weekly_held_exclusion(conn),
490
+ (floor_date,) + acct_p,
491
+ ).fetchall()
492
+ # internal fallback: host-local intentional (the date-only leg's day,
493
+ # matching `_fetch_current_week_snapshots`)
494
+ today_local = now_utc.astimezone().date().isoformat()
495
+ boundaryful = []
496
+ date_only = []
497
+ for r in rows:
498
+ try:
499
+ cap = parse_iso_datetime(r[6], "captured_at_utc")
500
+ except (TypeError, ValueError):
501
+ continue
502
+ if cap > now_utc:
503
+ continue
504
+ rank = (cap, int(r[0]))
505
+ try:
506
+ ws = parse_iso_datetime(r[4], "week_start_at") if r[4] else None
507
+ we = parse_iso_datetime(r[5], "week_end_at") if r[5] else None
508
+ except ValueError:
509
+ ws = we = None
510
+ bounded = ws is not None and we is not None
511
+ if bounded and ws <= now_utc < we:
512
+ boundaryful.append((rank, r, True))
513
+ elif (r[2] is not None and r[3] is not None
514
+ and r[2] <= today_local <= r[3]):
515
+ date_only.append((rank, r, bounded))
516
+ pool = boundaryful or date_only
517
+ if not pool:
518
+ return None
519
+ _rank, r, bounded = max(pool, key=lambda item: item[0])
520
+ return {
521
+ "id": int(r[0]),
522
+ "account_key": r[1],
523
+ "week_start_date": r[2],
524
+ "week_end_date": r[3],
525
+ "week_start_at": r[4],
526
+ "week_end_at": r[5],
527
+ "captured_at_utc": r[6],
528
+ "boundaryful": bounded,
529
+ }
530
+
531
+
532
+ def _claude_select_current_owner(conn, now_utc: dt.datetime) -> "str | None":
533
+ """The displayed account: the owner of the newest non-held weekly sample
534
+ at ``now_utc`` (#836, spec 5.2 step 1), or ``None`` when there is none."""
535
+ sample = _claude_newest_weekly_sample(conn, now_utc)
536
+ return None if sample is None else sample["account_key"]
537
+
538
+
539
+ def _claude_current_cycle_scope(conn, now_utc: dt.datetime, *,
540
+ account_key: str) -> "ClaudeCycleScope | None":
541
+ """``account_key``'s current cycle identity at ``now_utc`` (#836, spec 5.2
542
+ step 2), or ``None`` when the account has no eligible non-held sample.
543
+
544
+ The window is the one holding the account's newest eligible non-held
545
+ sample. The active reset is `_latest_reset_event_for_end` for the
546
+ canonical end, scoped to the account and admitted only at or before
547
+ ``now_utc`` — the #750 S3 chokepoint's effective-instant order — so another
548
+ account's credit never re-anchors this cycle (spec D-A) and a reset that
549
+ takes effect later does not cut it yet. The effective start is that
550
+ reset's instant exactly when the history index cuts the account's storage
551
+ bucket there (`_claude_reset_cuts_cycle`, the one rule both use) — which
552
+ can be EARLIER than the nominal week start after a same-date early
553
+ re-anchor (#836 review F7). The date-only leg has no end text, so it
554
+ carries no reset.
555
+ """
556
+ sample = _claude_newest_weekly_sample(conn, now_utc, account_key=account_key)
557
+ if sample is None:
558
+ return None
559
+ as_of = _iso_z(now_utc)
560
+ if not sample["boundaryful"]:
561
+ return ClaudeCycleScope(
562
+ account_key=account_key, as_of_utc=as_of,
563
+ week_start_date=sample["week_start_date"],
564
+ week_start_at=None, week_end_at=None, effective_start_at=None,
565
+ reset_event_id=0)
566
+ week_start = parse_iso_datetime(sample["week_start_at"], "week_start_at")
567
+ canon_end = _canonicalize_optional_iso(sample["week_end_at"],
568
+ "claude.cycle.end")
569
+ effective_start = week_start
570
+ reset_event_id = 0
571
+ if canon_end:
572
+ event = _latest_reset_event_for_end(
573
+ conn, canon_end, account_key=account_key, as_of_utc=as_of)
574
+ if event is not None:
575
+ reset_event_id = int(event["id"])
576
+ if event["effective_reset_at_utc"]:
577
+ try:
578
+ reset_dt = parse_iso_datetime(
579
+ event["effective_reset_at_utc"], "reset_event.effective")
580
+ except ValueError:
581
+ reset_dt = None
582
+ if reset_dt is not None:
583
+ import _cctally_milestone_history as _mhist
584
+ outer = _mhist._claude_storage_boundaries(
585
+ conn, sample["week_start_date"],
586
+ account_key=account_key)
587
+ if _mhist._claude_reset_cuts_cycle(outer, reset_dt):
588
+ effective_start = reset_dt
589
+ return ClaudeCycleScope(
590
+ account_key=account_key, as_of_utc=as_of,
591
+ week_start_date=sample["week_start_date"],
592
+ week_start_at=sample["week_start_at"],
593
+ week_end_at=canon_end,
594
+ effective_start_at=_iso_z(effective_start),
595
+ reset_event_id=reset_event_id)
596
+
597
+
598
+ def _claude_cycle_scope_samples(conn, scope: ClaudeCycleScope,
599
+ now_utc: dt.datetime, *,
600
+ include_journal_id: bool = False):
601
+ """The decorated card's held-inclusive samples for ``scope`` (#836).
602
+
603
+ The shape of ``_fetch_current_week_snapshots(include_held=True,
604
+ include_account=True, include_scope=True)`` — ``(week_start_at,
605
+ week_end_at, samples, week_scope)`` — restricted to the scope's account and
606
+ window, so the card reads the window its owner was chosen in rather than
607
+ one `_fetch_current_week_snapshots` would choose by its held-inclusive
608
+ ``MAX(captured_at_utc)``. ``week_start_at`` is the NOMINAL start; the
609
+ caller filters from ``scope.effective_start_at``. Samples are filtered to
610
+ ``captured_at <= now_utc``. Returns ``None`` when the window cannot be
611
+ resolved. ``include_journal_id`` appends each row's `journal_id` as the
612
+ last sample field (#1018 twin spec 5.3).
613
+ """
614
+ _jid = ", journal_id" if include_journal_id else ""
615
+ floor_date = (now_utc.astimezone(dt.timezone.utc).date()
616
+ - dt.timedelta(days=_CLAUDE_CYCLE_CANDIDATE_DAYS)).isoformat()
617
+ if scope.week_start_at is None:
618
+ drow = conn.execute(
619
+ "SELECT week_end_date FROM weekly_usage_snapshots "
620
+ "WHERE week_start_date = ? AND account_key = ? "
621
+ "AND week_end_date IS NOT NULL "
622
+ "ORDER BY captured_at_utc DESC, id DESC LIMIT 1",
623
+ (scope.week_start_date, scope.account_key),
624
+ ).fetchone()
625
+ if drow is None:
626
+ return None
627
+ # internal fallback: host-local intentional (the date-only leg)
628
+ local_tz = dt.datetime.now().astimezone().tzinfo
629
+ ws_date = dt.date.fromisoformat(scope.week_start_date)
630
+ we_date = dt.date.fromisoformat(drow[0])
631
+ week_start_at = dt.datetime.combine(
632
+ ws_date, dt.time(0, 0), local_tz).astimezone(dt.timezone.utc)
633
+ week_end_at = dt.datetime.combine(
634
+ we_date + dt.timedelta(days=1), dt.time(0, 0),
635
+ local_tz).astimezone(dt.timezone.utc)
636
+ rows = conn.execute(
637
+ "SELECT captured_at_utc, weekly_percent, five_hour_percent, "
638
+ " weekly_observation_held, account_key" + _jid + " "
639
+ "FROM weekly_usage_snapshots "
640
+ "WHERE week_start_date = ? AND account_key = ? "
641
+ "ORDER BY captured_at_utc ASC, id ASC",
642
+ (scope.week_start_date, scope.account_key),
643
+ ).fetchall()
644
+ samples = _shape_week_samples(rows, include_held=True,
645
+ include_account=True,
646
+ include_journal_id=include_journal_id)
647
+ samples = [s for s in samples if s[0] <= now_utc]
648
+ return (week_start_at, week_end_at, samples,
649
+ (scope.week_start_date, None))
650
+ week_start_at = parse_iso_datetime(scope.week_start_at, "week_start_at")
651
+ week_end_at = parse_iso_datetime(scope.week_end_at, "week_end_at")
652
+ # Every textual spelling of the same start instant, as the merged read
653
+ # gathers them for an upgrade-in-progress window.
654
+ matching_texts: list[str] = []
655
+ for (text,) in conn.execute(
656
+ "SELECT DISTINCT week_start_at FROM weekly_usage_snapshots "
657
+ "WHERE week_start_date >= ? AND account_key = ? "
658
+ "AND week_start_at IS NOT NULL AND week_end_at IS NOT NULL",
659
+ (floor_date, scope.account_key)).fetchall():
660
+ try:
661
+ if parse_iso_datetime(text, "week_start_at") == week_start_at:
662
+ matching_texts.append(text)
663
+ except ValueError:
664
+ continue
665
+ if scope.week_start_at not in matching_texts:
666
+ matching_texts.append(scope.week_start_at)
667
+ matching_texts.sort()
668
+ placeholders = ",".join("?" * len(matching_texts))
669
+ rows = _week_rows_by_capture(conn.execute(
670
+ f"SELECT captured_at_utc, weekly_percent, five_hour_percent, "
671
+ f" weekly_observation_held, account_key{_jid}, id "
672
+ f"FROM weekly_usage_snapshots "
673
+ f"WHERE (week_start_at IN ({placeholders}) "
674
+ f" OR (week_start_at IS NULL AND week_start_date = ?)) "
675
+ f" AND account_key = ?",
676
+ tuple(matching_texts) + (scope.week_start_date, scope.account_key),
677
+ ), by_id=True)
678
+ samples = _shape_week_samples(rows, include_held=True,
679
+ include_account=True,
680
+ include_journal_id=include_journal_id)
681
+ samples = [s for s in samples if s[0] <= now_utc]
682
+ return (week_start_at, week_end_at, samples,
683
+ (scope.week_start_date, tuple(matching_texts)))
684
+
685
+
392
686
  def _current_week_five_hour_percent(conn, now_utc, week_scope, *,
393
687
  account_key, week_start_at,
394
688
  week_start_before_override):