cctally 1.22.4 → 1.24.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 +20 -0
- package/bin/_cctally_alerts.py +133 -24
- package/bin/_cctally_config.py +195 -14
- package/bin/_cctally_core.py +102 -2
- package/bin/_cctally_dashboard.py +277 -62
- package/bin/_cctally_forecast.py +25 -3
- package/bin/_cctally_milestones.py +68 -0
- package/bin/_cctally_parser.py +10 -2
- package/bin/_cctally_record.py +470 -137
- package/bin/_cctally_tui.py +1 -0
- package/bin/_lib_alert_axes.py +53 -0
- package/bin/_lib_alert_dispatch.py +141 -0
- package/bin/_lib_alerts_payload.py +67 -0
- package/bin/_lib_budget.py +8 -0
- package/bin/cctally +17 -0
- package/dashboard/static/assets/{index-BxmaYT1y.css → index-CsqqtRBB.css} +1 -1
- package/dashboard/static/assets/index-DwuW39Tv.js +18 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +3 -1
- package/dashboard/static/assets/index-CLcd-Tnm.js +0 -18
|
@@ -369,6 +369,74 @@ def insert_budget_milestone(
|
|
|
369
369
|
return int(cur.rowcount)
|
|
370
370
|
|
|
371
371
|
|
|
372
|
+
def insert_projected_milestone(
|
|
373
|
+
conn: sqlite3.Connection,
|
|
374
|
+
*,
|
|
375
|
+
week_start_at: str,
|
|
376
|
+
metric: str,
|
|
377
|
+
threshold: int,
|
|
378
|
+
projected_value: float,
|
|
379
|
+
denominator: float,
|
|
380
|
+
commit: bool = True,
|
|
381
|
+
) -> int:
|
|
382
|
+
"""INSERT OR IGNORE a projected-pace crossing. Returns ``cur.rowcount``
|
|
383
|
+
(1 = genuinely new crossing, 0 = INSERT OR IGNORE no-op on a pre-existing
|
|
384
|
+
``(week_start_at, metric, threshold)`` row).
|
|
385
|
+
|
|
386
|
+
Mirrors :func:`insert_budget_milestone`'s rowcount contract so the
|
|
387
|
+
alert-fire predicate (`if inserted == 1`) is race-safe without a follow-up
|
|
388
|
+
SELECT. ``alerted_at`` is left NULL — the caller stamps it in the SAME
|
|
389
|
+
transaction BEFORE dispatching (set-then-dispatch invariant, CLAUDE.md
|
|
390
|
+
Alerts gotcha). ``commit=False`` lets the caller bundle the INSERT with the
|
|
391
|
+
follow-up ``alerted_at`` UPDATE in one transaction so a crash between them
|
|
392
|
+
can't strand ``alerted_at`` NULL forever.
|
|
393
|
+
"""
|
|
394
|
+
cur = conn.execute(
|
|
395
|
+
"INSERT OR IGNORE INTO projected_milestones "
|
|
396
|
+
"(week_start_at, metric, threshold, projected_value, denominator, "
|
|
397
|
+
" crossed_at_utc) "
|
|
398
|
+
"VALUES (?, ?, ?, ?, ?, ?)",
|
|
399
|
+
(
|
|
400
|
+
week_start_at,
|
|
401
|
+
str(metric),
|
|
402
|
+
int(threshold),
|
|
403
|
+
float(projected_value),
|
|
404
|
+
float(denominator),
|
|
405
|
+
now_utc_iso(),
|
|
406
|
+
),
|
|
407
|
+
)
|
|
408
|
+
if commit:
|
|
409
|
+
conn.commit()
|
|
410
|
+
return int(cur.rowcount)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _projected_levels_already_latched(
|
|
414
|
+
conn: sqlite3.Connection,
|
|
415
|
+
*,
|
|
416
|
+
week_start_at: str,
|
|
417
|
+
metric: str,
|
|
418
|
+
levels: "tuple[int, ...]",
|
|
419
|
+
) -> bool:
|
|
420
|
+
"""True iff EVERY level in ``levels`` already has a row for
|
|
421
|
+
``(week_start_at, metric)``.
|
|
422
|
+
|
|
423
|
+
Cheap indexed SELECT used as the pre-probe gate BEFORE any projection math
|
|
424
|
+
/ cost work ([Pre-probe before sync_cache]). Empty ``levels`` → True
|
|
425
|
+
(nothing owed). When False, at least one level is still un-recorded and the
|
|
426
|
+
caller must do the projection. Mirrors the per-week pre-probe SELECT in
|
|
427
|
+
:func:`maybe_record_budget_milestone`.
|
|
428
|
+
"""
|
|
429
|
+
if not levels:
|
|
430
|
+
return True
|
|
431
|
+
rows = conn.execute(
|
|
432
|
+
"SELECT threshold FROM projected_milestones "
|
|
433
|
+
"WHERE week_start_at = ? AND metric = ?",
|
|
434
|
+
(week_start_at, str(metric)),
|
|
435
|
+
).fetchall()
|
|
436
|
+
have = {int(r[0]) for r in rows}
|
|
437
|
+
return all(int(level) in have for level in levels)
|
|
438
|
+
|
|
439
|
+
|
|
372
440
|
def _reconcile_budget_milestones_on_set(conn, *, target, thresholds, now_utc):
|
|
373
441
|
"""Forward-only-from-set reconcile (spec §5): on `budget set`, every
|
|
374
442
|
threshold ALREADY crossed for the current week is recorded with
|
package/bin/_cctally_parser.py
CHANGED
|
@@ -2186,14 +2186,15 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2186
2186
|
cctally alerts test
|
|
2187
2187
|
cctally alerts test --axis five-hour --threshold 95
|
|
2188
2188
|
cctally alerts test --axis budget --threshold 100
|
|
2189
|
+
cctally alerts test --axis projected --metric budget_usd
|
|
2189
2190
|
"""),
|
|
2190
2191
|
)
|
|
2191
2192
|
p_alerts_test.add_argument(
|
|
2192
2193
|
"--axis",
|
|
2193
|
-
choices=["weekly", "five-hour", "budget"],
|
|
2194
|
+
choices=["weekly", "five-hour", "budget", "projected"],
|
|
2194
2195
|
default="weekly",
|
|
2195
2196
|
help="Alert axis to simulate: weekly subscription window, 5h block, "
|
|
2196
|
-
"
|
|
2197
|
+
"equiv-$ budget, or projected-pace (default: weekly).",
|
|
2197
2198
|
)
|
|
2198
2199
|
p_alerts_test.add_argument(
|
|
2199
2200
|
"--threshold",
|
|
@@ -2201,6 +2202,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2201
2202
|
default=90,
|
|
2202
2203
|
help="Threshold percent (1-100, default: 90).",
|
|
2203
2204
|
)
|
|
2205
|
+
p_alerts_test.add_argument(
|
|
2206
|
+
"--metric",
|
|
2207
|
+
choices=["weekly_pct", "budget_usd"],
|
|
2208
|
+
default="weekly_pct",
|
|
2209
|
+
help="For --axis projected: which projected metric to preview "
|
|
2210
|
+
"(default: weekly_pct).",
|
|
2211
|
+
)
|
|
2204
2212
|
p_alerts_test.set_defaults(func=c.cmd_alerts_test)
|
|
2205
2213
|
|
|
2206
2214
|
# ---- setup (onboarding spec §2) ----
|