cctally 1.7.2 → 1.7.3
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 +17 -0
- package/bin/_cctally_dashboard.py +64 -3
- package/bin/_cctally_db.py +182 -21
- package/bin/_cctally_record.py +457 -58
- package/bin/_cctally_tui.py +100 -1
- package/bin/_lib_render.py +20 -0
- package/bin/cctally +191 -6
- 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 +1 -1
- package/dashboard/static/assets/index-BgpoazlS.js +0 -18
- package/dashboard/static/assets/index-nJdUaGys.css +0 -1
package/bin/_cctally_tui.py
CHANGED
|
@@ -1030,6 +1030,56 @@ def _tui_build_percent_milestones(
|
|
|
1030
1030
|
return out
|
|
1031
1031
|
|
|
1032
1032
|
|
|
1033
|
+
def _tui_build_five_hour_milestones(
|
|
1034
|
+
conn: sqlite3.Connection,
|
|
1035
|
+
five_hour_window_key: int | None,
|
|
1036
|
+
) -> list[dict]:
|
|
1037
|
+
"""Return per-percent 5h-block milestones for the given window, in
|
|
1038
|
+
capture-time order. Spec §5.3 — drives the CurrentWeekModal's new
|
|
1039
|
+
5h-milestone timeline section.
|
|
1040
|
+
|
|
1041
|
+
Bucket B per §3.2: NO ``reset_event_id`` filter — both pre- and
|
|
1042
|
+
post-credit segments render in the merged chronological stream so
|
|
1043
|
+
the user sees the full history of the active block including
|
|
1044
|
+
repeated threshold values after an in-place credit. The React layer
|
|
1045
|
+
differentiates rows by ``reset_event_id`` for key uniqueness.
|
|
1046
|
+
|
|
1047
|
+
Returns [] when the current week has no API-anchored 5h block. The
|
|
1048
|
+
envelope-shaped dict mirrors the CLI ``five-hour-breakdown --json``
|
|
1049
|
+
milestone objects but with snake_case keys (envelope convention).
|
|
1050
|
+
"""
|
|
1051
|
+
if five_hour_window_key is None:
|
|
1052
|
+
return []
|
|
1053
|
+
rows = conn.execute(
|
|
1054
|
+
"""
|
|
1055
|
+
SELECT percent_threshold, captured_at_utc, block_cost_usd,
|
|
1056
|
+
marginal_cost_usd, seven_day_pct_at_crossing,
|
|
1057
|
+
reset_event_id
|
|
1058
|
+
FROM five_hour_milestones
|
|
1059
|
+
WHERE five_hour_window_key = ?
|
|
1060
|
+
ORDER BY captured_at_utc ASC, id ASC
|
|
1061
|
+
""",
|
|
1062
|
+
(int(five_hour_window_key),),
|
|
1063
|
+
).fetchall()
|
|
1064
|
+
out: list[dict] = []
|
|
1065
|
+
for r in rows:
|
|
1066
|
+
out.append({
|
|
1067
|
+
"percent_threshold": int(r["percent_threshold"]),
|
|
1068
|
+
"captured_at_utc": r["captured_at_utc"],
|
|
1069
|
+
"block_cost_usd": float(r["block_cost_usd"]),
|
|
1070
|
+
"marginal_cost_usd": (
|
|
1071
|
+
None if r["marginal_cost_usd"] is None
|
|
1072
|
+
else float(r["marginal_cost_usd"])
|
|
1073
|
+
),
|
|
1074
|
+
"seven_day_pct_at_crossing": (
|
|
1075
|
+
None if r["seven_day_pct_at_crossing"] is None
|
|
1076
|
+
else float(r["seven_day_pct_at_crossing"])
|
|
1077
|
+
),
|
|
1078
|
+
"reset_event_id": int(r["reset_event_id"] or 0),
|
|
1079
|
+
})
|
|
1080
|
+
return out
|
|
1081
|
+
|
|
1082
|
+
|
|
1033
1083
|
@dataclass
|
|
1034
1084
|
class DataSnapshot:
|
|
1035
1085
|
"""All data needed to render one TUI frame. Produced by sync thread,
|
|
@@ -1062,6 +1112,13 @@ class DataSnapshot:
|
|
|
1062
1112
|
# `current_week.five_hour_block` is precomputed via
|
|
1063
1113
|
# `_select_current_block_for_envelope`).
|
|
1064
1114
|
alerts: list[dict] = field(default_factory=list)
|
|
1115
|
+
# ---- 5h in-place credit (v1.7.x) ----
|
|
1116
|
+
# Already-envelope-shaped dicts for the CurrentWeekModal's new 5h
|
|
1117
|
+
# milestone timeline (spec §5.3, Codex r1 finding 3). Parallel to
|
|
1118
|
+
# ``percent_milestones`` (which carries the WEEKLY timeline). Loaded
|
|
1119
|
+
# at sync-thread time so ``snapshot_to_envelope`` stays a pure
|
|
1120
|
+
# renderer; empty list when no current 5h block is bound.
|
|
1121
|
+
five_hour_milestones: list[dict] = field(default_factory=list)
|
|
1065
1122
|
|
|
1066
1123
|
@classmethod
|
|
1067
1124
|
def synthesize_for_marketing(cls, *, as_of_iso: str) -> "DataSnapshot":
|
|
@@ -1888,6 +1945,19 @@ def _tui_build_snapshot(
|
|
|
1888
1945
|
alerts = _build_alerts_envelope_array(conn)
|
|
1889
1946
|
except Exception as exc:
|
|
1890
1947
|
errors.append(f"alerts: {exc}")
|
|
1948
|
+
# ---- 5h in-place credit (v1.7.x) ----
|
|
1949
|
+
# Load 5h milestones (pre + post credit) for the current
|
|
1950
|
+
# block's window so CurrentWeekModal can render a merged
|
|
1951
|
+
# chronological timeline alongside its weekly milestones.
|
|
1952
|
+
# Spec §5.3 (Codex r1 finding 3).
|
|
1953
|
+
fh_milestones: list[dict] = []
|
|
1954
|
+
try:
|
|
1955
|
+
win_key = None
|
|
1956
|
+
if cw is not None and isinstance(cw.five_hour_block, dict):
|
|
1957
|
+
win_key = cw.five_hour_block.get("five_hour_window_key")
|
|
1958
|
+
fh_milestones = _tui_build_five_hour_milestones(conn, win_key)
|
|
1959
|
+
except Exception as exc:
|
|
1960
|
+
errors.append(f"five-hour-milestones: {exc}")
|
|
1891
1961
|
return DataSnapshot(
|
|
1892
1962
|
current_week=cw,
|
|
1893
1963
|
forecast=fc,
|
|
@@ -1903,6 +1973,7 @@ def _tui_build_snapshot(
|
|
|
1903
1973
|
blocks_panel=blocks_panel,
|
|
1904
1974
|
daily_panel=daily_panel,
|
|
1905
1975
|
alerts=alerts,
|
|
1976
|
+
five_hour_milestones=fh_milestones,
|
|
1906
1977
|
)
|
|
1907
1978
|
finally:
|
|
1908
1979
|
conn.close()
|
|
@@ -2375,11 +2446,26 @@ def _tui_panel_current_week(
|
|
|
2375
2446
|
f"${cw.dollars_per_percent:.2f}"
|
|
2376
2447
|
if cw.dollars_per_percent is not None else "—"
|
|
2377
2448
|
)
|
|
2449
|
+
# Spec §5.4 — credit badge next to the 5h percent. Source: same
|
|
2450
|
+
# ``cw.five_hour_block.credits`` channel that drives the dashboard
|
|
2451
|
+
# chip; only show when at least one credit is present for the
|
|
2452
|
+
# current block. Format: ``⚡ -Xpp`` (single) / ``⚡ -Xpp, -Ypp``
|
|
2453
|
+
# (stacked across distinct 10-min slots).
|
|
2454
|
+
fh_credit_badge = ""
|
|
2455
|
+
fhb = getattr(cw, "five_hour_block", None)
|
|
2456
|
+
if isinstance(fhb, dict):
|
|
2457
|
+
fh_credits = fhb.get("credits") or []
|
|
2458
|
+
if fh_credits:
|
|
2459
|
+
deltas = ", ".join(
|
|
2460
|
+
f"{float(c.get('delta_pp', 0.0)):+.0f}pp"
|
|
2461
|
+
for c in fh_credits
|
|
2462
|
+
)
|
|
2463
|
+
fh_credit_badge = f" {{bright}}⚡ {deltas}{{/}}"
|
|
2378
2464
|
lines = [
|
|
2379
2465
|
"",
|
|
2380
2466
|
f" Used {{{used_cls}}}{bar_fill}{{/}} {{{used_cls}.b}}{cw.used_pct:>5.1f}%{{/}}",
|
|
2381
2467
|
"",
|
|
2382
|
-
f" 5-hour {{bar.accent}}{five_bar}{{/}} {{bright}}{int(five):>3d}%{{/}}",
|
|
2468
|
+
f" 5-hour {{bar.accent}}{five_bar}{{/}} {{bright}}{int(five):>3d}%{{/}}{fh_credit_badge}",
|
|
2383
2469
|
f" {{dim}}{fr_str}{{/}}" if fr_str else "",
|
|
2384
2470
|
"",
|
|
2385
2471
|
f" {{dim}}Spent{{/}} {{bright}}${cw.spent_usd:.2f}{{/}} "
|
|
@@ -2428,6 +2514,19 @@ def _tui_panel_current_week_hero(
|
|
|
2428
2514
|
else:
|
|
2429
2515
|
reset_suffix = ""
|
|
2430
2516
|
|
|
2517
|
+
# Spec §5.4 — credit badge in the hero variant. Same source as the
|
|
2518
|
+
# grid variant; append after the reset suffix so the badge follows
|
|
2519
|
+
# the "resets in" timer.
|
|
2520
|
+
fhb_hero = getattr(cw, "five_hour_block", None)
|
|
2521
|
+
if isinstance(fhb_hero, dict):
|
|
2522
|
+
fh_credits_hero = fhb_hero.get("credits") or []
|
|
2523
|
+
if fh_credits_hero:
|
|
2524
|
+
deltas_hero = ", ".join(
|
|
2525
|
+
f"{float(c.get('delta_pp', 0.0)):+.0f}pp"
|
|
2526
|
+
for c in fh_credits_hero
|
|
2527
|
+
)
|
|
2528
|
+
reset_suffix = f"{reset_suffix} {{bright}}⚡ {deltas_hero}{{/}}"
|
|
2529
|
+
|
|
2431
2530
|
if snap.last_sync_error:
|
|
2432
2531
|
health = "{warn}daemon error{/}"
|
|
2433
2532
|
elif snap.last_sync_at is None:
|
package/bin/_lib_render.py
CHANGED
|
@@ -2671,6 +2671,12 @@ def _five_hour_blocks_to_json(
|
|
|
2671
2671
|
"sevenDayPctAtBlockEnd": p_end,
|
|
2672
2672
|
"sevenDayPctDeltaPp": delta,
|
|
2673
2673
|
"crossedSevenDayReset": crossed,
|
|
2674
|
+
# Spec §5.1 — in-place credit events for this 5h block, in
|
|
2675
|
+
# ascending effective_reset_at order. Empty list when the
|
|
2676
|
+
# block carries no credit detections. Source: ``__credits``
|
|
2677
|
+
# side-channel attached by ``cmd_five_hour_blocks`` via
|
|
2678
|
+
# ``credits_by_window``.
|
|
2679
|
+
"credits": d.get("__credits") or [],
|
|
2674
2680
|
}
|
|
2675
2681
|
if breakdown_axis == "model":
|
|
2676
2682
|
out["modelBreakdowns"] = [
|
|
@@ -2752,6 +2758,20 @@ def _render_five_hour_blocks_table(
|
|
|
2752
2758
|
formatted_start = _format_block_start(d["block_start_at"], args._resolved_tz)
|
|
2753
2759
|
if crossed:
|
|
2754
2760
|
formatted_start = f"⚡ {formatted_start}"
|
|
2761
|
+
# Spec §5.1 — credit chip suffix. When the block carries one or
|
|
2762
|
+
# more in-place credit events (5h credit detection v1.7.x;
|
|
2763
|
+
# __credits side-channel set by ``cmd_five_hour_blocks``), append
|
|
2764
|
+
# a ⚡-prefixed chip listing the per-credit delta-pp. Multiple
|
|
2765
|
+
# credits in the same block concatenate (e.g.
|
|
2766
|
+
# `⚡ credited -20pp, -8pp`). The chip text sits AFTER the
|
|
2767
|
+
# block-start cell's existing crossed-reset glyph so the two
|
|
2768
|
+
# signals visually disambiguate by position (crossed-reset
|
|
2769
|
+
# prefix vs. credit suffix). Both use ⚡ — different semantics,
|
|
2770
|
+
# different positions.
|
|
2771
|
+
credits = d.get("__credits") or []
|
|
2772
|
+
if credits:
|
|
2773
|
+
deltas = ", ".join(f"{c['deltaPp']:+.0f}pp" for c in credits)
|
|
2774
|
+
formatted_start = f"{formatted_start} ⚡ credited {deltas}"
|
|
2755
2775
|
rows.append([
|
|
2756
2776
|
formatted_start,
|
|
2757
2777
|
"ACTIVE" if d["__is_active"] else "closed",
|
package/bin/cctally
CHANGED
|
@@ -3864,6 +3864,43 @@ def open_db() -> sqlite3.Connection:
|
|
|
3864
3864
|
)
|
|
3865
3865
|
_backfill_week_reset_events(conn)
|
|
3866
3866
|
|
|
3867
|
+
# ── five_hour_reset_events (Anthropic-issued in-place 5h credits) ──
|
|
3868
|
+
# Parallel concept to ``week_reset_events`` for the 5h dimension; lives
|
|
3869
|
+
# adjacent in ``_apply_schema`` because the two carry the same kind of
|
|
3870
|
+
# signal at different cadences. Diverges from weekly in that the payload
|
|
3871
|
+
# is the *percent values* (prior + post) rather than boundary keys,
|
|
3872
|
+
# because the 5h variant has a stable ``five_hour_window_key`` and only
|
|
3873
|
+
# the percent moves. See spec
|
|
3874
|
+
# docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md §3.1
|
|
3875
|
+
# for rationale.
|
|
3876
|
+
#
|
|
3877
|
+
# UNIQUE(five_hour_window_key, effective_reset_at_utc) — supports stacked
|
|
3878
|
+
# credits across DISTINCT 10-min slots inside one block (see spec §2.3
|
|
3879
|
+
# "Bounded stacked-credit resolution" for the cap statement: ~30 distinct
|
|
3880
|
+
# slots per 5h block when floor matches ``_canonical_5h_window_key``'s
|
|
3881
|
+
# 600-second floor; same-slot collisions silently absorbed by
|
|
3882
|
+
# INSERT OR IGNORE — an intentional cap, not a bug).
|
|
3883
|
+
#
|
|
3884
|
+
# No FK per CLAUDE.md gotcha: FKs in this codebase are documentation-only
|
|
3885
|
+
# (``PRAGMA foreign_keys`` not enabled). ``five_hour_window_key`` provides
|
|
3886
|
+
# the join key without a formal FK.
|
|
3887
|
+
#
|
|
3888
|
+
# No ``_backfill_five_hour_reset_events`` call follows (forward-only ship
|
|
3889
|
+
# per spec Q5; historical backfill deferred to a future issue).
|
|
3890
|
+
conn.execute(
|
|
3891
|
+
"""
|
|
3892
|
+
CREATE TABLE IF NOT EXISTS five_hour_reset_events (
|
|
3893
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3894
|
+
detected_at_utc TEXT NOT NULL,
|
|
3895
|
+
five_hour_window_key INTEGER NOT NULL,
|
|
3896
|
+
prior_percent REAL NOT NULL,
|
|
3897
|
+
post_percent REAL NOT NULL,
|
|
3898
|
+
effective_reset_at_utc TEXT NOT NULL,
|
|
3899
|
+
UNIQUE(five_hour_window_key, effective_reset_at_utc)
|
|
3900
|
+
)
|
|
3901
|
+
"""
|
|
3902
|
+
)
|
|
3903
|
+
|
|
3867
3904
|
# ── five_hour_blocks (rollup, one row per API-anchored 5h block) ──
|
|
3868
3905
|
conn.execute(
|
|
3869
3906
|
"""
|
|
@@ -3913,7 +3950,8 @@ def open_db() -> sqlite3.Connection:
|
|
|
3913
3950
|
block_cost_usd REAL NOT NULL DEFAULT 0,
|
|
3914
3951
|
marginal_cost_usd REAL,
|
|
3915
3952
|
seven_day_pct_at_crossing REAL,
|
|
3916
|
-
|
|
3953
|
+
reset_event_id INTEGER NOT NULL DEFAULT 0,
|
|
3954
|
+
UNIQUE(five_hour_window_key, percent_threshold, reset_event_id),
|
|
3917
3955
|
FOREIGN KEY (block_id) REFERENCES five_hour_blocks(id)
|
|
3918
3956
|
)
|
|
3919
3957
|
"""
|
|
@@ -3933,6 +3971,16 @@ def open_db() -> sqlite3.Connection:
|
|
|
3933
3971
|
# — never "delivery failed".
|
|
3934
3972
|
add_column_if_missing(conn, "five_hour_milestones", "alerted_at", "TEXT")
|
|
3935
3973
|
|
|
3974
|
+
# reset_event_id: segment column added by migration 006. Fresh-install
|
|
3975
|
+
# DBs get it via the live CREATE TABLE above + the dispatcher fast-stamps
|
|
3976
|
+
# the migration marker (the live DDL must carry the column AND the 3-col
|
|
3977
|
+
# UNIQUE for fast-stamp to be safe — see spec §3.2). Existing pre-006
|
|
3978
|
+
# DBs trip the migration's rename-recreate-copy idiom (handler in
|
|
3979
|
+
# bin/_cctally_db.py); the handler's fast-path probe stamps the marker
|
|
3980
|
+
# when the column is already present (covers the corner case where a
|
|
3981
|
+
# partially-upgraded DB has the column but not the new UNIQUE — re-run
|
|
3982
|
+
# is safe). Mirrors weekly migration 005 / `percent_milestones`.
|
|
3983
|
+
|
|
3936
3984
|
# ── five_hour_block_models (per-(block, model) rollup-child) ──
|
|
3937
3985
|
# MUST be created BEFORE the parent-backfill gate below, because
|
|
3938
3986
|
# _backfill_five_hour_blocks writes into this table on the fresh-install
|
|
@@ -7632,6 +7680,14 @@ def _backfill_week_reset_events(conn: sqlite3.Connection) -> None:
|
|
|
7632
7680
|
# 86→0 and 34→0 cases while filtering 35→33-style jitter.
|
|
7633
7681
|
_RESET_PCT_DROP_THRESHOLD = 25.0
|
|
7634
7682
|
|
|
7683
|
+
# In-place 5h-credit threshold. Mirrors `_RESET_PCT_DROP_THRESHOLD` but
|
|
7684
|
+
# scaled down for the 5h dimension: typical 5h usage stays under ~10pp in
|
|
7685
|
+
# a single block, so a 5pp drop sits well above natural variation while
|
|
7686
|
+
# proportionally being a larger signal than 25pp is on the weekly scale.
|
|
7687
|
+
# See spec docs/superpowers/specs/2026-05-16-5h-in-place-credit-detection.md
|
|
7688
|
+
# §2.1 (Q1) for rationale.
|
|
7689
|
+
_FIVE_HOUR_RESET_PCT_DROP_THRESHOLD = 5.0
|
|
7690
|
+
|
|
7635
7691
|
|
|
7636
7692
|
def _week_ref_has_reset_event(
|
|
7637
7693
|
conn: sqlite3.Connection, ref: WeekRef
|
|
@@ -9457,6 +9513,31 @@ def cmd_five_hour_blocks(args: argparse.Namespace) -> int:
|
|
|
9457
9513
|
# to fill seven_day_pct_at_block_end on the active row.
|
|
9458
9514
|
latest_7d, latest_window_key = _latest_seven_day_and_window(conn)
|
|
9459
9515
|
|
|
9516
|
+
# Pre-load credit events for every window_key the rows query
|
|
9517
|
+
# returned. Single index-scan over `five_hour_reset_events`;
|
|
9518
|
+
# build a window_key -> list[Credit] map keyed for in-process
|
|
9519
|
+
# JOIN against each block dict. Used by both the text/JSON
|
|
9520
|
+
# render path AND the share-output snapshot wiring (spec §5.1.1).
|
|
9521
|
+
# Loaded in a single pass — no per-block SELECT.
|
|
9522
|
+
credit_rows = conn.execute(
|
|
9523
|
+
"SELECT five_hour_window_key, prior_percent, post_percent, "
|
|
9524
|
+
" effective_reset_at_utc "
|
|
9525
|
+
" FROM five_hour_reset_events "
|
|
9526
|
+
" ORDER BY five_hour_window_key, effective_reset_at_utc"
|
|
9527
|
+
).fetchall()
|
|
9528
|
+
credits_by_window: dict[int, list[dict]] = {}
|
|
9529
|
+
for cr in credit_rows:
|
|
9530
|
+
credits_by_window.setdefault(
|
|
9531
|
+
int(cr["five_hour_window_key"]), []
|
|
9532
|
+
).append({
|
|
9533
|
+
"effectiveResetAtUtc": cr["effective_reset_at_utc"],
|
|
9534
|
+
"priorPercent": float(cr["prior_percent"]),
|
|
9535
|
+
"postPercent": float(cr["post_percent"]),
|
|
9536
|
+
"deltaPp": round(
|
|
9537
|
+
float(cr["post_percent"]) - float(cr["prior_percent"]), 1
|
|
9538
|
+
),
|
|
9539
|
+
})
|
|
9540
|
+
|
|
9460
9541
|
# Build per-block dicts with the active-flag side-channel.
|
|
9461
9542
|
block_dicts: list[dict] = []
|
|
9462
9543
|
for r in rows:
|
|
@@ -9465,6 +9546,11 @@ def cmd_five_hour_blocks(args: argparse.Namespace) -> int:
|
|
|
9465
9546
|
d["__is_active"] = is_active
|
|
9466
9547
|
if is_active and latest_7d is not None:
|
|
9467
9548
|
d["seven_day_pct_at_block_end"] = latest_7d
|
|
9549
|
+
# Side-channel (parallel to __is_active): list of credit
|
|
9550
|
+
# event dicts for this block's window. Empty list when none.
|
|
9551
|
+
d["__credits"] = credits_by_window.get(
|
|
9552
|
+
int(d["five_hour_window_key"]), []
|
|
9553
|
+
)
|
|
9468
9554
|
block_dicts.append(d)
|
|
9469
9555
|
|
|
9470
9556
|
# Shareable-reports gate: --format short-circuits the JSON / table
|
|
@@ -9578,18 +9664,50 @@ def cmd_five_hour_breakdown(args: argparse.Namespace) -> int:
|
|
|
9578
9664
|
)
|
|
9579
9665
|
return 2
|
|
9580
9666
|
|
|
9667
|
+
# Spec §5.2: ORDER BY captured_at_utc ASC (NOT percent_threshold)
|
|
9668
|
+
# so post-credit segments interleave with pre-credit ones in
|
|
9669
|
+
# time-order — same human threshold number can appear twice
|
|
9670
|
+
# (once per reset_event_id segment) and must render in the
|
|
9671
|
+
# order it crossed. Bucket B per §3.2: read ALL segments (no
|
|
9672
|
+
# ``reset_event_id`` filter).
|
|
9581
9673
|
milestones = conn.execute(
|
|
9582
9674
|
"""
|
|
9583
9675
|
SELECT percent_threshold, captured_at_utc,
|
|
9584
9676
|
block_cost_usd, marginal_cost_usd,
|
|
9585
|
-
seven_day_pct_at_crossing
|
|
9677
|
+
seven_day_pct_at_crossing, reset_event_id
|
|
9586
9678
|
FROM five_hour_milestones
|
|
9587
9679
|
WHERE block_id = ?
|
|
9588
|
-
ORDER BY
|
|
9680
|
+
ORDER BY captured_at_utc ASC, id ASC
|
|
9589
9681
|
""",
|
|
9590
9682
|
(block["id"],),
|
|
9591
9683
|
).fetchall()
|
|
9592
9684
|
|
|
9685
|
+
# Spec §5.2 — load in-place credit events for this block's
|
|
9686
|
+
# window, ascending by effective_reset_at_utc, so the text
|
|
9687
|
+
# renderer can interleave a ``⚡ CREDIT -Xpp @ HH:MM`` divider
|
|
9688
|
+
# row between pre- and post-credit milestone segments and JSON
|
|
9689
|
+
# consumers see the parallel ``credits[]`` array (Section 5.2).
|
|
9690
|
+
credit_rows = conn.execute(
|
|
9691
|
+
"""
|
|
9692
|
+
SELECT effective_reset_at_utc, prior_percent, post_percent
|
|
9693
|
+
FROM five_hour_reset_events
|
|
9694
|
+
WHERE five_hour_window_key = ?
|
|
9695
|
+
ORDER BY effective_reset_at_utc ASC
|
|
9696
|
+
""",
|
|
9697
|
+
(block["five_hour_window_key"],),
|
|
9698
|
+
).fetchall()
|
|
9699
|
+
credits_list: list[dict] = [
|
|
9700
|
+
{
|
|
9701
|
+
"effectiveResetAtUtc": c["effective_reset_at_utc"],
|
|
9702
|
+
"priorPercent": float(c["prior_percent"]),
|
|
9703
|
+
"postPercent": float(c["post_percent"]),
|
|
9704
|
+
"deltaPp": round(
|
|
9705
|
+
float(c["post_percent"]) - float(c["prior_percent"]), 1
|
|
9706
|
+
),
|
|
9707
|
+
}
|
|
9708
|
+
for c in credit_rows
|
|
9709
|
+
]
|
|
9710
|
+
|
|
9593
9711
|
crossed = bool(block.get("crossed_seven_day_reset"))
|
|
9594
9712
|
p_start = block.get("seven_day_pct_at_block_start")
|
|
9595
9713
|
p_end = block.get("seven_day_pct_at_block_end")
|
|
@@ -9626,6 +9744,10 @@ def cmd_five_hour_breakdown(args: argparse.Namespace) -> int:
|
|
|
9626
9744
|
"sevenDayPctDeltaPp": delta,
|
|
9627
9745
|
"crossedSevenDayReset": crossed,
|
|
9628
9746
|
}
|
|
9747
|
+
# Spec §5.2: expose ``resetEventId`` on each milestone so JSON
|
|
9748
|
+
# consumers can disambiguate post-credit threshold repeats from
|
|
9749
|
+
# pre-credit ones. ``0`` is the pre-credit/no-credit sentinel
|
|
9750
|
+
# (matches the schema default).
|
|
9629
9751
|
ms_out = [
|
|
9630
9752
|
{
|
|
9631
9753
|
"percentThreshold": m["percent_threshold"],
|
|
@@ -9636,13 +9758,23 @@ def cmd_five_hour_breakdown(args: argparse.Namespace) -> int:
|
|
|
9636
9758
|
else round(m["marginal_cost_usd"], 9)
|
|
9637
9759
|
),
|
|
9638
9760
|
"sevenDayPctAtCrossing": m["seven_day_pct_at_crossing"],
|
|
9761
|
+
"resetEventId": int(m["reset_event_id"] or 0),
|
|
9639
9762
|
}
|
|
9640
9763
|
for m in milestones
|
|
9641
9764
|
]
|
|
9642
9765
|
|
|
9643
9766
|
if args.json:
|
|
9767
|
+
# Spec §5.2: ``credits`` is the parallel array to
|
|
9768
|
+
# ``milestones`` — same shape as the ``credits`` field on
|
|
9769
|
+
# ``five-hour-blocks --json`` (§5.1). Stacked credits across
|
|
9770
|
+
# distinct 10-min slots produce multiple entries.
|
|
9644
9771
|
print(json.dumps(
|
|
9645
|
-
{
|
|
9772
|
+
{
|
|
9773
|
+
"schemaVersion": 1,
|
|
9774
|
+
"block": block_out,
|
|
9775
|
+
"milestones": ms_out,
|
|
9776
|
+
"credits": credits_list,
|
|
9777
|
+
},
|
|
9646
9778
|
indent=2,
|
|
9647
9779
|
))
|
|
9648
9780
|
return 0
|
|
@@ -9683,7 +9815,47 @@ def cmd_five_hour_breakdown(args: argparse.Namespace) -> int:
|
|
|
9683
9815
|
headers = ["#", "Threshold", "Cumulative Cost", "Marginal Cost",
|
|
9684
9816
|
"7d at crossing"]
|
|
9685
9817
|
rows = []
|
|
9686
|
-
|
|
9818
|
+
# Spec §5.2 — merged event stream. Interleave milestones and
|
|
9819
|
+
# credits in time-order (``capturedAt`` for milestones,
|
|
9820
|
+
# ``effectiveResetAtUtc`` for credits). Credits render as a
|
|
9821
|
+
# divider row with ``⚡ CREDIT`` in the Threshold cell and the
|
|
9822
|
+
# delta-pp + HH:MM in the rightmost cell; the milestone row
|
|
9823
|
+
# numbering counter (``#``) continues across the divider so the
|
|
9824
|
+
# ordinal still reflects "the Nth event in this block."
|
|
9825
|
+
merged_events: list[tuple[str, dict]] = []
|
|
9826
|
+
for m in ms_out:
|
|
9827
|
+
merged_events.append(("milestone", m))
|
|
9828
|
+
for c in credits_list:
|
|
9829
|
+
merged_events.append(("credit", c))
|
|
9830
|
+
merged_events.sort(key=lambda ev: (
|
|
9831
|
+
ev[1]["effectiveResetAtUtc"] if ev[0] == "credit"
|
|
9832
|
+
else ev[1]["capturedAt"]
|
|
9833
|
+
))
|
|
9834
|
+
idx = 0
|
|
9835
|
+
for kind, ev in merged_events:
|
|
9836
|
+
idx += 1
|
|
9837
|
+
if kind == "credit":
|
|
9838
|
+
# Spec §5.2: ⚡ CREDIT -Xpp @ HH:MM divider row.
|
|
9839
|
+
# HH:MM rendered in the display tz via format_display_dt.
|
|
9840
|
+
# ``format_display_dt`` is the documented chokepoint for
|
|
9841
|
+
# human-displayed datetimes (CLAUDE.md). The deltaPp
|
|
9842
|
+
# value is float; format as integer ppm (mirrors the
|
|
9843
|
+
# five-hour-blocks chip in §5.1).
|
|
9844
|
+
hhmm = format_display_dt(
|
|
9845
|
+
ev["effectiveResetAtUtc"],
|
|
9846
|
+
args._resolved_tz,
|
|
9847
|
+
fmt="%H:%M",
|
|
9848
|
+
suffix=False,
|
|
9849
|
+
)
|
|
9850
|
+
rows.append([
|
|
9851
|
+
str(idx),
|
|
9852
|
+
"⚡ CREDIT",
|
|
9853
|
+
f"{ev['deltaPp']:+.0f}pp",
|
|
9854
|
+
"",
|
|
9855
|
+
f"@ {hhmm}",
|
|
9856
|
+
])
|
|
9857
|
+
continue
|
|
9858
|
+
m = ev
|
|
9687
9859
|
cum = f"${m['blockCostUSD']:.6f}"
|
|
9688
9860
|
marg = (
|
|
9689
9861
|
"n/a" if m["marginalCostUSD"] is None
|
|
@@ -13832,8 +14004,21 @@ def _build_five_hour_blocks_snapshot(
|
|
|
13832
14004
|
used_pct = float(r.get("final_five_hour_percent") or 0.0)
|
|
13833
14005
|
crossed = bool(r.get("crossed_seven_day_reset"))
|
|
13834
14006
|
cell_text = "⚡" if crossed else "—"
|
|
14007
|
+
# Spec §5.1.1 (Codex r2 finding 3): consume the ``__credits``
|
|
14008
|
+
# side-channel set by ``cmd_five_hour_blocks`` and append a
|
|
14009
|
+
# ``⚡ -Xpp, -Ypp`` chip to the block_start cell. Pure-string
|
|
14010
|
+
# cell content flows uniformly through markdown / HTML table /
|
|
14011
|
+
# SVG text renderers without per-format additions. Symmetric to
|
|
14012
|
+
# the existing ⚡ glyph in the cross_reset cell — by position
|
|
14013
|
+
# (block_start suffix vs. dedicated column) the two annotations
|
|
14014
|
+
# remain visually distinguishable.
|
|
14015
|
+
credits = r.get("__credits") or []
|
|
14016
|
+
block_cell = block_lbl
|
|
14017
|
+
if credits:
|
|
14018
|
+
deltas = ", ".join(f"{c['deltaPp']:+.0f}pp" for c in credits)
|
|
14019
|
+
block_cell = f"{block_lbl} ⚡ {deltas}"
|
|
13835
14020
|
snap_rows.append(_lib_share.Row(cells={
|
|
13836
|
-
"block_start": _lib_share.TextCell(
|
|
14021
|
+
"block_start": _lib_share.TextCell(block_cell),
|
|
13837
14022
|
"cost": _lib_share.MoneyCell(cost_usd),
|
|
13838
14023
|
"used_pct": _lib_share.PercentCell(used_pct),
|
|
13839
14024
|
"cross_reset": _lib_share.TextCell(cell_text),
|