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.
@@ -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
@@ -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
- "or equiv-$ budget (default: weekly).",
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) ----