cctally 1.28.0 → 1.30.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.
- package/CHANGELOG.md +30 -0
- package/bin/_cctally_cache.py +147 -59
- package/bin/_cctally_core.py +22 -49
- package/bin/_cctally_dashboard.py +239 -152
- package/bin/_cctally_db.py +211 -31
- package/bin/_cctally_milestones.py +126 -166
- package/bin/_cctally_record.py +161 -192
- package/bin/_lib_alert_axes.py +7 -4
- package/bin/_lib_conversation.py +59 -8
- package/bin/_lib_conversation_query.py +306 -52
- package/bin/_lib_jsonl.py +69 -50
- package/bin/cctally +5 -5
- package/dashboard/static/assets/index-4OxMhN7N.js +53 -0
- package/dashboard/static/assets/index-DEDO-eqP.css +1 -0
- package/dashboard/static/assets/newsreader-latin-400-italic-CEihAR-f.woff2 +0 -0
- package/dashboard/static/assets/newsreader-latin-400-italic-CNZoH1hn.woff +0 -0
- package/dashboard/static/assets/newsreader-latin-400-normal-BFBkh4jY.woff2 +0 -0
- package/dashboard/static/assets/newsreader-latin-400-normal-gRTjlS2D.woff +0 -0
- package/dashboard/static/assets/newsreader-latin-500-normal-B66TYsaK.woff2 +0 -0
- package/dashboard/static/assets/newsreader-latin-500-normal-DFwuUcdu.woff +0 -0
- package/dashboard/static/assets/newsreader-latin-600-normal-30OJ_TG_.woff2 +0 -0
- package/dashboard/static/assets/newsreader-latin-600-normal-DUnT2r2g.woff +0 -0
- package/dashboard/static/assets/newsreader-latin-ext-400-italic-BMTE_bNQ.woff2 +0 -0
- package/dashboard/static/assets/newsreader-latin-ext-400-italic-qdgKLcPG.woff +0 -0
- package/dashboard/static/assets/newsreader-latin-ext-400-normal-DYA1XoQK.woff +0 -0
- package/dashboard/static/assets/newsreader-latin-ext-400-normal-svq1FPys.woff2 +0 -0
- package/dashboard/static/assets/newsreader-latin-ext-500-normal-BNHmvKvI.woff2 +0 -0
- package/dashboard/static/assets/newsreader-latin-ext-500-normal-CZruMFou.woff +0 -0
- package/dashboard/static/assets/newsreader-latin-ext-600-normal-BXv5iMHi.woff2 +0 -0
- package/dashboard/static/assets/newsreader-latin-ext-600-normal-BrbfzHZ5.woff +0 -0
- package/dashboard/static/assets/newsreader-vietnamese-400-italic-QbB8kb5s.woff +0 -0
- package/dashboard/static/assets/newsreader-vietnamese-400-italic-bZegYFuM.woff2 +0 -0
- package/dashboard/static/assets/newsreader-vietnamese-400-normal-BekUZro8.woff +0 -0
- package/dashboard/static/assets/newsreader-vietnamese-400-normal-DdKr49mV.woff2 +0 -0
- package/dashboard/static/assets/newsreader-vietnamese-500-normal-BEAbKU8A.woff +0 -0
- package/dashboard/static/assets/newsreader-vietnamese-500-normal-CL6a8tp2.woff2 +0 -0
- package/dashboard/static/assets/newsreader-vietnamese-600-normal-CVAR0otO.woff +0 -0
- package/dashboard/static/assets/newsreader-vietnamese-600-normal-CaH84vfx.woff2 +0 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +1 -1
- package/dashboard/static/assets/index-Bj5ckRUE.css +0 -1
- package/dashboard/static/assets/index-Dw4G5FD9.js +0 -18
|
@@ -331,54 +331,7 @@ def insert_percent_milestone(
|
|
|
331
331
|
def insert_budget_milestone(
|
|
332
332
|
conn: sqlite3.Connection,
|
|
333
333
|
*,
|
|
334
|
-
|
|
335
|
-
period: "str | None" = None,
|
|
336
|
-
threshold: int,
|
|
337
|
-
budget_usd: float,
|
|
338
|
-
spent_usd: float,
|
|
339
|
-
consumption_pct: float,
|
|
340
|
-
commit: bool = True,
|
|
341
|
-
) -> int:
|
|
342
|
-
"""INSERT OR IGNORE a budget threshold crossing. Returns ``cur.rowcount``
|
|
343
|
-
(1 = genuinely new crossing, 0 = INSERT OR IGNORE no-op on a pre-existing
|
|
344
|
-
``(week_start_at, period, threshold)`` row).
|
|
345
|
-
|
|
346
|
-
Mirrors :func:`insert_percent_milestone`'s rowcount contract so the
|
|
347
|
-
alert-fire predicate (`if inserted == 1`) is race-safe without a
|
|
348
|
-
follow-up SELECT. ``period`` (#137) is the configured period noun at
|
|
349
|
-
crossing ('calendar-week'|'calendar-month'|'subscription-week'); it
|
|
350
|
-
discriminates the UNIQUE key so calendar-week and calendar-month windows
|
|
351
|
-
that share a start instant don't collide. A NULL ``period`` is the pre-011
|
|
352
|
-
"unknown" sentinel (only seeded migration rows carry it). ``alerted_at`` is
|
|
353
|
-
left NULL — the caller stamps it in the SAME transaction BEFORE dispatching
|
|
354
|
-
(set-then-dispatch invariant, CLAUDE.md Alerts gotcha). ``commit=False``
|
|
355
|
-
lets the caller bundle the INSERT with the follow-up ``alerted_at`` UPDATE
|
|
356
|
-
in one transaction so a crash between them can't strand ``alerted_at`` NULL
|
|
357
|
-
forever.
|
|
358
|
-
"""
|
|
359
|
-
cur = conn.execute(
|
|
360
|
-
"INSERT OR IGNORE INTO budget_milestones "
|
|
361
|
-
"(week_start_at, period, threshold, budget_usd, spent_usd, "
|
|
362
|
-
" consumption_pct, crossed_at_utc) "
|
|
363
|
-
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
364
|
-
(
|
|
365
|
-
week_start_at,
|
|
366
|
-
period,
|
|
367
|
-
int(threshold),
|
|
368
|
-
float(budget_usd),
|
|
369
|
-
float(spent_usd),
|
|
370
|
-
float(consumption_pct),
|
|
371
|
-
now_utc_iso(),
|
|
372
|
-
),
|
|
373
|
-
)
|
|
374
|
-
if commit:
|
|
375
|
-
conn.commit()
|
|
376
|
-
return int(cur.rowcount)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
def insert_codex_budget_milestone(
|
|
380
|
-
conn: sqlite3.Connection,
|
|
381
|
-
*,
|
|
334
|
+
vendor: str,
|
|
382
335
|
period_start_at: str,
|
|
383
336
|
period: "str | None" = None,
|
|
384
337
|
threshold: int,
|
|
@@ -387,29 +340,34 @@ def insert_codex_budget_milestone(
|
|
|
387
340
|
consumption_pct: float,
|
|
388
341
|
commit: bool = True,
|
|
389
342
|
) -> int:
|
|
390
|
-
"""INSERT OR IGNORE a
|
|
391
|
-
``cur.rowcount`` (1 = genuinely new crossing, 0 =
|
|
392
|
-
pre-existing ``(period_start_at, period,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
``
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
343
|
+
"""INSERT OR IGNORE a budget threshold crossing into the unified vendor-tagged
|
|
344
|
+
table (#143). Returns ``cur.rowcount`` (1 = genuinely new crossing, 0 =
|
|
345
|
+
INSERT OR IGNORE no-op on a pre-existing ``(vendor, period_start_at, period,
|
|
346
|
+
threshold)`` row).
|
|
347
|
+
|
|
348
|
+
The merged ``budget_milestones`` table (migration 012) carries a ``vendor``
|
|
349
|
+
column (``'claude'``|``'codex'``) and the renamed ``period_start_at`` key
|
|
350
|
+
(the Claude subscription-week start OR the Codex calendar-period start —
|
|
351
|
+
Codex has no Anthropic subscription week, spec §6). Mirrors
|
|
352
|
+
:func:`insert_percent_milestone`'s rowcount contract so the alert-fire
|
|
353
|
+
predicate (`if inserted == 1`) is race-safe without a follow-up SELECT.
|
|
354
|
+
``period`` (#137) is the configured period noun at crossing
|
|
355
|
+
('calendar-week'|'calendar-month'|'subscription-week'); it discriminates the
|
|
356
|
+
UNIQUE key so calendar-week and calendar-month windows that share a start
|
|
357
|
+
instant don't collide. A NULL ``period`` is the pre-011 "unknown" sentinel
|
|
358
|
+
(only seeded migration rows carry it). ``alerted_at`` is left NULL — the
|
|
359
|
+
caller stamps it in the SAME transaction BEFORE dispatching (set-then-dispatch
|
|
360
|
+
invariant, CLAUDE.md Alerts gotcha). ``commit=False`` lets the caller bundle
|
|
361
|
+
the INSERT with the follow-up ``alerted_at`` UPDATE in one transaction so a
|
|
362
|
+
crash between them can't strand ``alerted_at`` NULL forever.
|
|
406
363
|
"""
|
|
407
364
|
cur = conn.execute(
|
|
408
|
-
"INSERT OR IGNORE INTO
|
|
409
|
-
"(period_start_at, period, threshold, budget_usd, spent_usd, "
|
|
365
|
+
"INSERT OR IGNORE INTO budget_milestones "
|
|
366
|
+
"(vendor, period_start_at, period, threshold, budget_usd, spent_usd, "
|
|
410
367
|
" consumption_pct, crossed_at_utc) "
|
|
411
|
-
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
368
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
412
369
|
(
|
|
370
|
+
str(vendor),
|
|
413
371
|
period_start_at,
|
|
414
372
|
period,
|
|
415
373
|
int(threshold),
|
|
@@ -555,50 +513,92 @@ def _resolve_claude_budget_window(conn, now_utc, *, period, config, tz):
|
|
|
555
513
|
§6). Subscription-week → the existing ``_resolve_current_budget_window``
|
|
556
514
|
(snapshot-anchored; may return ``None`` when no usage snapshot has landed
|
|
557
515
|
yet). Calendar period → the pure ``_resolve_calendar_window`` (derived purely
|
|
558
|
-
from ``now`` + the period; NEVER ``None``). The dedup key column
|
|
559
|
-
``
|
|
560
|
-
|
|
516
|
+
from ``now`` + the period; NEVER ``None``). The dedup key column is now
|
|
517
|
+
``period_start_at`` (#143) — it carries the resolved PERIOD-start instant
|
|
518
|
+
(subscription-week OR calendar period-start)."""
|
|
561
519
|
c = _cctally()
|
|
562
520
|
if period == "subscription-week":
|
|
563
521
|
return c._resolve_current_budget_window(conn, now_utc)
|
|
564
522
|
return c._resolve_calendar_window(period, now_utc, config, tz)
|
|
565
523
|
|
|
566
524
|
|
|
525
|
+
def _resolve_budget_window(conn, *, vendor, now_utc, period, config, tz):
|
|
526
|
+
"""Resolve the budget period-start instant for ``vendor`` (#143). CHEAP — does
|
|
527
|
+
NO cost SUM, preserving the pre-probe-before-spend hot path (spec §4.2): the
|
|
528
|
+
firing paths resolve this cheap window, pre-probe which thresholds are already
|
|
529
|
+
latched, and skip the cost SUM entirely when nothing is pending.
|
|
530
|
+
|
|
531
|
+
Dispatches to the per-vendor window primitive:
|
|
532
|
+
* claude → :func:`_resolve_claude_budget_window` (snapshot-anchored for
|
|
533
|
+
subscription-week → may be ``None`` pre-snapshot; calendar period → the
|
|
534
|
+
pure calendar window, never ``None``).
|
|
535
|
+
* codex → :func:`_resolve_codex_budget_period_window` (pure calendar window;
|
|
536
|
+
never ``None``).
|
|
537
|
+
|
|
538
|
+
Returns the period-start ``datetime`` or ``None`` (claude subscription-week
|
|
539
|
+
pre-snapshot)."""
|
|
540
|
+
if vendor == "claude":
|
|
541
|
+
window = _resolve_claude_budget_window(
|
|
542
|
+
conn, now_utc, period=period, config=config, tz=tz
|
|
543
|
+
)
|
|
544
|
+
else:
|
|
545
|
+
window = _resolve_codex_budget_period_window(period, now_utc, config, tz)
|
|
546
|
+
if window is None:
|
|
547
|
+
return None
|
|
548
|
+
start_at, _end_at = window
|
|
549
|
+
return start_at
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _budget_spend_for_vendor(conn, *, vendor, start_at, now_utc) -> float:
|
|
553
|
+
"""Spend over ``[start_at, now]`` for ``vendor`` (#143) — the COSTLY leg,
|
|
554
|
+
called only after the pre-probe finds pending thresholds (spec §4.2). claude
|
|
555
|
+
routes through the Claude cost SUM (``mode="auto"``); codex through the Codex
|
|
556
|
+
cost SUM."""
|
|
557
|
+
c = _cctally()
|
|
558
|
+
if vendor == "claude":
|
|
559
|
+
return c._sum_cost_for_range(start_at, now_utc, mode="auto")
|
|
560
|
+
return c._sum_codex_cost_for_range(start_at, now_utc)
|
|
561
|
+
|
|
562
|
+
|
|
567
563
|
def _reconcile_budget_milestones_on_set(
|
|
568
|
-
conn, *, target, thresholds, now_utc, period=
|
|
569
|
-
config=None, tz=None,
|
|
564
|
+
conn, *, vendor, target, thresholds, now_utc, period, config=None, tz=None,
|
|
570
565
|
):
|
|
571
|
-
"""Forward-only-from-set reconcile
|
|
572
|
-
threshold ALREADY crossed for the current
|
|
573
|
-
``alerted_at`` SET but WITHOUT dispatch — so
|
|
574
|
-
already at 95% does NOT instant-popup. Thresholds
|
|
575
|
-
row, so they fire later via
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
566
|
+
"""Forward-only-from-set reconcile for the budget axis (both vendors, #143):
|
|
567
|
+
on `budget set`, every threshold ALREADY crossed for the current
|
|
568
|
+
window/period is recorded with ``alerted_at`` SET but WITHOUT dispatch — so
|
|
569
|
+
setting a budget when you're already at 95% does NOT instant-popup. Thresholds
|
|
570
|
+
not yet crossed get NO row, so they fire later via the firing path
|
|
571
|
+
(:func:`maybe_record_budget_milestone` / :func:`maybe_record_codex_budget_milestone`).
|
|
572
|
+
|
|
573
|
+
A mid-window target change re-runs this; thresholds already alerted stay
|
|
574
|
+
deduped via UNIQUE(vendor, period_start_at, period, threshold) + the
|
|
575
|
+
``alerted_at IS NULL`` guard on the UPDATE (so an existing alerted row is never
|
|
576
|
+
re-stamped).
|
|
577
|
+
|
|
578
|
+
Cold path — no pre-probe; resolves the cheap window then computes spend right
|
|
579
|
+
after (spec §4.2 ordering). ``vendor`` selects the window + spend dispatcher;
|
|
580
|
+
claude subscription-week may resolve ``None`` pre-snapshot (early return).
|
|
581
|
+
Keeps its own stamp-no-dispatch tail (distinct from :func:`_budget_crossings`,
|
|
582
|
+
which dispatches) — that asymmetry is intrinsic, not duplication.
|
|
584
583
|
"""
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
conn, now_utc, period=period, config=config, tz=tz
|
|
584
|
+
start_at = _resolve_budget_window(
|
|
585
|
+
conn, vendor=vendor, now_utc=now_utc, period=period, config=config, tz=tz
|
|
588
586
|
)
|
|
589
|
-
if
|
|
587
|
+
if start_at is None:
|
|
590
588
|
return
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
#
|
|
589
|
+
period_key = start_at.isoformat(timespec="seconds")
|
|
590
|
+
spent = _budget_spend_for_vendor(
|
|
591
|
+
conn, vendor=vendor, start_at=start_at, now_utc=now_utc
|
|
592
|
+
)
|
|
593
|
+
# target > 0 guaranteed by the caller (validated weekly_usd / amount_usd);
|
|
594
|
+
# the else is belt-and-suspenders.
|
|
596
595
|
consumption_pct = (spent / target * 100.0) if target > 0 else 0.0
|
|
597
596
|
for t in sorted(thresholds):
|
|
598
597
|
if consumption_pct + 1e-9 >= t:
|
|
599
598
|
insert_budget_milestone(
|
|
600
599
|
conn,
|
|
601
|
-
|
|
600
|
+
vendor=vendor,
|
|
601
|
+
period_start_at=period_key,
|
|
602
602
|
period=period,
|
|
603
603
|
threshold=t,
|
|
604
604
|
budget_usd=target,
|
|
@@ -606,14 +606,14 @@ def _reconcile_budget_milestones_on_set(
|
|
|
606
606
|
consumption_pct=consumption_pct,
|
|
607
607
|
commit=False,
|
|
608
608
|
)
|
|
609
|
-
# alerted_at UPDATE keys on the CONCRETE period (not the
|
|
610
|
-
# only the row we just inserted
|
|
611
|
-
# pre-011 NULL-period sibling (#137).
|
|
609
|
+
# alerted_at UPDATE keys on the CONCRETE (vendor, period) (not the
|
|
610
|
+
# wildcard): only the row we just inserted is stamped, never a
|
|
611
|
+
# pre-011 NULL-period sibling (#137) or another vendor's row (#143).
|
|
612
612
|
conn.execute(
|
|
613
613
|
"UPDATE budget_milestones SET alerted_at = ? "
|
|
614
|
-
"WHERE
|
|
615
|
-
" AND alerted_at IS NULL",
|
|
616
|
-
(now_utc_iso(),
|
|
614
|
+
"WHERE vendor = ? AND period_start_at = ? AND period = ? "
|
|
615
|
+
" AND threshold = ? AND alerted_at IS NULL",
|
|
616
|
+
(now_utc_iso(), vendor, period_key, period, t),
|
|
617
617
|
)
|
|
618
618
|
conn.commit()
|
|
619
619
|
|
|
@@ -630,34 +630,36 @@ def _resolve_codex_budget_period_window(period, now_utc, config, tz):
|
|
|
630
630
|
return c._resolve_calendar_window(period, now_utc, config, tz)
|
|
631
631
|
|
|
632
632
|
|
|
633
|
-
def
|
|
634
|
-
conn, *, period_key, period=None, thresholds, target, spent, now_utc
|
|
633
|
+
def _budget_crossings(
|
|
634
|
+
conn, *, vendor, period_key, period=None, thresholds, target, spent, now_utc
|
|
635
635
|
):
|
|
636
|
-
"""Shared INSERT-and-arm core for the
|
|
637
|
-
STILL-pending threshold that's been crossed at ``spent``, ``INSERT OR
|
|
638
|
-
IGNORE`` a milestone (commit=False)
|
|
639
|
-
(rowcount==1) — stamp ``alerted_at`` in the
|
|
640
|
-
dispatch), returning the list of crossings the
|
|
636
|
+
"""Shared INSERT-and-arm core for the budget axis (both vendors, #143): for
|
|
637
|
+
every STILL-pending threshold that's been crossed at ``spent``, ``INSERT OR
|
|
638
|
+
IGNORE`` a milestone (commit=False) into the unified vendor-tagged table and —
|
|
639
|
+
on the genuine-new-crossing winner (rowcount==1) — stamp ``alerted_at`` in the
|
|
640
|
+
SAME transaction (set-then-dispatch), returning the list of crossings the
|
|
641
|
+
caller must dispatch.
|
|
641
642
|
|
|
642
643
|
Pure of config/window resolution: both firing sites (record-usage +
|
|
643
|
-
opportunistic ``cctally budget``)
|
|
644
|
-
``target`` / ``spent`` here, so
|
|
645
|
-
dispatch invariant live in ONE place
|
|
646
|
-
NOT commit — the caller owns the single durable commit that bundles every
|
|
644
|
+
opportunistic ``cctally budget``), for either vendor, feed the
|
|
645
|
+
already-resolved ``vendor`` / ``period_key`` / ``target`` / ``spent`` here, so
|
|
646
|
+
the crossing arithmetic + the set-then-dispatch invariant live in ONE place.
|
|
647
|
+
Does NOT commit — the caller owns the single durable commit that bundles every
|
|
647
648
|
INSERT with its ``alerted_at`` UPDATE. Applies the +1e-9 float-floor snap
|
|
648
649
|
(CLAUDE.md gotcha). Returns ``[(threshold, crossed_at, spent, target,
|
|
649
650
|
consumption_pct), ...]`` for the rowcount==1 winners only.
|
|
650
651
|
|
|
651
652
|
Forward-only / fire-once is enforced by INSERT OR IGNORE's rowcount on the
|
|
652
|
-
UNIQUE(period_start_at, period, threshold) key; a racing record-usage
|
|
653
|
+
UNIQUE(vendor, period_start_at, period, threshold) key; a racing record-usage
|
|
653
654
|
instance OR an already-recorded threshold gets rowcount==0 and is skipped
|
|
654
655
|
([Dedup mustn't gate side effects])."""
|
|
655
656
|
consumption_pct = (spent / target * 100.0) if target > 0 else 0.0
|
|
656
657
|
fired: "list" = []
|
|
657
658
|
for t in sorted(thresholds):
|
|
658
659
|
if consumption_pct + 1e-9 >= t:
|
|
659
|
-
inserted =
|
|
660
|
+
inserted = insert_budget_milestone(
|
|
660
661
|
conn,
|
|
662
|
+
vendor=vendor,
|
|
661
663
|
period_start_at=period_key,
|
|
662
664
|
period=period,
|
|
663
665
|
threshold=t,
|
|
@@ -668,63 +670,19 @@ def _codex_budget_crossings(
|
|
|
668
670
|
)
|
|
669
671
|
if inserted == 1:
|
|
670
672
|
crossed_at = now_utc_iso()
|
|
671
|
-
# alerted_at UPDATE keys on the CONCRETE period (#137
|
|
672
|
-
# row just inserted
|
|
673
|
-
# NULL-period sibling.
|
|
673
|
+
# alerted_at UPDATE keys on the CONCRETE (vendor, period) (#137 /
|
|
674
|
+
# #143): only the row just inserted is stamped, never a pre-011
|
|
675
|
+
# NULL-period sibling or another vendor's row.
|
|
674
676
|
conn.execute(
|
|
675
|
-
"UPDATE
|
|
676
|
-
"WHERE
|
|
677
|
-
" AND alerted_at IS NULL",
|
|
678
|
-
(crossed_at, period_key, period, t),
|
|
677
|
+
"UPDATE budget_milestones SET alerted_at = ? "
|
|
678
|
+
"WHERE vendor = ? AND period_start_at = ? AND period = ? "
|
|
679
|
+
" AND threshold = ? AND alerted_at IS NULL",
|
|
680
|
+
(crossed_at, vendor, period_key, period, t),
|
|
679
681
|
)
|
|
680
682
|
fired.append((t, crossed_at, spent, target, consumption_pct))
|
|
681
683
|
return fired
|
|
682
684
|
|
|
683
685
|
|
|
684
|
-
def _reconcile_codex_budget_milestones_on_set(
|
|
685
|
-
conn, *, target, thresholds, now_utc, period, config, tz
|
|
686
|
-
):
|
|
687
|
-
"""Forward-only-from-set reconcile for the Codex budget axis (spec §6),
|
|
688
|
-
mirroring :func:`_reconcile_budget_milestones_on_set` but keyed on the
|
|
689
|
-
resolved CALENDAR period window instead of the subscription week.
|
|
690
|
-
|
|
691
|
-
On a Codex `budget set` (or `config set budget.codex`), every threshold
|
|
692
|
-
ALREADY crossed for the current period is recorded with ``alerted_at`` SET
|
|
693
|
-
but WITHOUT dispatch — so setting a Codex budget mid-month while already over
|
|
694
|
-
does NOT instant-popup; a mid-period amount change never re-alerts an
|
|
695
|
-
already-fired threshold (deduped via UNIQUE(period_start_at, period,
|
|
696
|
-
threshold) + the ``alerted_at IS NULL`` UPDATE guard). Thresholds not yet
|
|
697
|
-
crossed get NO row, so they fire later via
|
|
698
|
-
:func:`maybe_record_codex_budget_milestone`."""
|
|
699
|
-
c = _cctally()
|
|
700
|
-
start_at, _end_at = _resolve_codex_budget_period_window(
|
|
701
|
-
period, now_utc, config, tz
|
|
702
|
-
)
|
|
703
|
-
period_key = start_at.isoformat(timespec="seconds")
|
|
704
|
-
spent = c._sum_codex_cost_for_range(start_at, now_utc)
|
|
705
|
-
consumption_pct = (spent / target * 100.0) if target > 0 else 0.0
|
|
706
|
-
for t in sorted(thresholds):
|
|
707
|
-
if consumption_pct + 1e-9 >= t:
|
|
708
|
-
insert_codex_budget_milestone(
|
|
709
|
-
conn,
|
|
710
|
-
period_start_at=period_key,
|
|
711
|
-
period=period,
|
|
712
|
-
threshold=t,
|
|
713
|
-
budget_usd=target,
|
|
714
|
-
spent_usd=spent,
|
|
715
|
-
consumption_pct=consumption_pct,
|
|
716
|
-
commit=False,
|
|
717
|
-
)
|
|
718
|
-
# alerted_at UPDATE keys on the CONCRETE period (#137).
|
|
719
|
-
conn.execute(
|
|
720
|
-
"UPDATE codex_budget_milestones SET alerted_at = ? "
|
|
721
|
-
"WHERE period_start_at = ? AND period = ? AND threshold = ? "
|
|
722
|
-
" AND alerted_at IS NULL",
|
|
723
|
-
(now_utc_iso(), period_key, period, t),
|
|
724
|
-
)
|
|
725
|
-
conn.commit()
|
|
726
|
-
|
|
727
|
-
|
|
728
686
|
def _reconcile_codex_budget_on_config_write(validated_budget):
|
|
729
687
|
"""Forward-only reconcile shared by the Codex-budget config write paths
|
|
730
688
|
(`budget set --vendor codex`, `config set budget.codex`). Gated +
|
|
@@ -745,8 +703,9 @@ def _reconcile_codex_budget_on_config_write(validated_budget):
|
|
|
745
703
|
tz = c.resolve_display_tz(argparse.Namespace(tz=None), config)
|
|
746
704
|
conn = open_db()
|
|
747
705
|
try:
|
|
748
|
-
|
|
706
|
+
_reconcile_budget_milestones_on_set(
|
|
749
707
|
conn,
|
|
708
|
+
vendor="codex",
|
|
750
709
|
target=codex["amount_usd"],
|
|
751
710
|
thresholds=thresholds,
|
|
752
711
|
now_utc=_command_as_of(),
|
|
@@ -779,6 +738,7 @@ def _reconcile_budget_on_config_write(validated_budget):
|
|
|
779
738
|
try:
|
|
780
739
|
_reconcile_budget_milestones_on_set(
|
|
781
740
|
conn,
|
|
741
|
+
vendor="claude",
|
|
782
742
|
target=validated_budget["weekly_usd"],
|
|
783
743
|
thresholds=thresholds,
|
|
784
744
|
now_utc=_command_as_of(),
|