cctally 1.59.0 → 1.61.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.
@@ -516,6 +516,8 @@ def doctor_gather_state(
516
516
  dev_mode=_cctally_core.DEV_MODE,
517
517
  app_dir=str(_cctally_core.APP_DIR),
518
518
  is_dev_checkout=_cctally_core._is_dev_checkout(),
519
+ # Preview channel (CCTALLY_CHANNEL=preview): surfaced in install.mode.
520
+ channel=("preview" if _cctally_core.is_preview_channel() else "prod"),
519
521
  # Pricing-freshness check (spec §5.1): trailing-30d coverage gaps.
520
522
  pricing_coverage=pricing_coverage,
521
523
  # Conversation-sessions rollup consistency (#217 S1 / U9).
@@ -327,6 +327,7 @@ def _select_dollars_per_percent(
327
327
  spent_usd: float,
328
328
  *,
329
329
  skip_sync: bool = False,
330
+ use_weekref_cost_cache: bool = False,
330
331
  ) -> tuple[float, str]:
331
332
  """Return (dollars_per_percent, source_label). See spec §1 selection rule.
332
333
 
@@ -375,7 +376,26 @@ def _select_dollars_per_percent(
375
376
  import statistics
376
377
  values: list[float] = []
377
378
  for ws, we, final_pct in prior:
378
- week_cost = c._sum_cost_for_range(ws, we, mode="auto", skip_sync=skip_sync)
379
+ if use_weekref_cost_cache:
380
+ # #269 §4: every `prior` week satisfies `we < now_utc` (an
381
+ # eligibility filter above), so all four are CLOSED and
382
+ # cacheable — this is the SAME immutable per-weekref cost the
383
+ # trend builder caches (B1↔B3 shared key). The `ws=ws, we=we`
384
+ # default-arg capture pins the loop variables (the closure is
385
+ # invoked lazily inside cached_weekref_cost). `skip_sync=True`
386
+ # unconditionally (the dashboard synced once at the top of the
387
+ # rebuild; the flag is only True there).
388
+ _sc = c._load_sibling("_lib_snapshot_cache")
389
+ week_cost = _sc.cached_weekref_cost(
390
+ week_start_at=ws, week_end_at=we, now_utc=now_utc,
391
+ compute=lambda ws=ws, we=we: c._sum_cost_for_range(
392
+ ws, we, mode="auto", skip_sync=True
393
+ ),
394
+ )
395
+ else:
396
+ week_cost = c._sum_cost_for_range(
397
+ ws, we, mode="auto", skip_sync=skip_sync
398
+ )
379
399
  values.append(week_cost / final_pct)
380
400
  return statistics.median(values), "trailing_4wk_median"
381
401
 
@@ -422,6 +442,7 @@ def _load_forecast_inputs(
422
442
  now_utc: dt.datetime,
423
443
  *,
424
444
  skip_sync: bool = False,
445
+ use_weekref_cost_cache: bool = False,
425
446
  ) -> ForecastInputs | None:
426
447
  """Gather everything from the DB. Returns None if no current-week snapshot.
427
448
 
@@ -466,7 +487,8 @@ def _load_forecast_inputs(
466
487
  # re-syncs in downstream cost lookups (trailing-4wk-median loop hits
467
488
  # _sum_cost_for_range once per historical week).
468
489
  dpp, dpp_source = _select_dollars_per_percent(
469
- conn, now_utc, week_start_at, p_now, spent_usd, skip_sync=True
490
+ conn, now_utc, week_start_at, p_now, spent_usd, skip_sync=True,
491
+ use_weekref_cost_cache=use_weekref_cost_cache,
470
492
  )
471
493
  confidence, reasons = _assess_forecast_confidence(elapsed_hours, p_now, len(samples))
472
494
  target_24h = now_utc - dt.timedelta(hours=24)
@@ -1523,8 +1523,8 @@ def build_parser() -> argparse.ArgumentParser:
1523
1523
  dp.add_argument(
1524
1524
  "--port",
1525
1525
  type=int,
1526
- default=8789,
1527
- help="TCP port to bind (default: 8789).",
1526
+ default=None,
1527
+ help="TCP port to bind (default: 8789; 8790 under the preview channel).",
1528
1528
  )
1529
1529
  dp.add_argument(
1530
1530
  "--host",
@@ -1960,6 +1960,12 @@ def build_parser() -> argparse.ArgumentParser:
1960
1960
  default="all",
1961
1961
  help="Which ingest half to sync/rebuild (default: all).",
1962
1962
  )
1963
+ p_cache_sync.add_argument(
1964
+ "--prune-orphans",
1965
+ action="store_true",
1966
+ help="Prune cache rows for source files removed from disk "
1967
+ "(e.g. a deleted git worktree) without a full rebuild.",
1968
+ )
1963
1969
  p_cache_sync.set_defaults(func=c.cmd_cache_sync)
1964
1970
 
1965
1971
  # -- daily --
@@ -2533,6 +2539,26 @@ def build_parser() -> argparse.ArgumentParser:
2533
2539
  help=argparse.SUPPRESS) # JSON string fed to mock fetch (tests only)
2534
2540
  ht.set_defaults(func=c.cmd_hook_tick)
2535
2541
 
2542
+ # ---- __preview (internal — maintainer-local preview channel) ----
2543
+ # The preview modules are maintainer-only (excluded from the public
2544
+ # mirror). On a public build `c.cmd_preview` is None — its load in
2545
+ # bin/cctally is wrapped in a tolerant try/except — so skip registering
2546
+ # the hidden subcommand entirely rather than wiring a None handler.
2547
+ if getattr(c, "cmd_preview", None) is not None:
2548
+ pv = sub.add_parser("__preview", help=argparse.SUPPRESS,
2549
+ formatter_class=CLIHelpFormatter,
2550
+ description="Internal: provision/manage the preview-channel data dir.")
2551
+ pv_sub = pv.add_subparsers(dest="action", required=True)
2552
+ pv_ensure = pv_sub.add_parser("ensure", help=argparse.SUPPRESS)
2553
+ pv_ensure.add_argument("--no-refresh", action="store_true")
2554
+ pv_ensure.add_argument("--reseed", action="store_true")
2555
+ pv_ensure.set_defaults(func=c.cmd_preview)
2556
+ pv_clean = pv_sub.add_parser("clean", help=argparse.SUPPRESS)
2557
+ pv_clean.add_argument("--dry-run", action="store_true")
2558
+ pv_clean.set_defaults(func=c.cmd_preview)
2559
+ pv_status = pv_sub.add_parser("status", help=argparse.SUPPRESS)
2560
+ pv_status.set_defaults(func=c.cmd_preview)
2561
+
2536
2562
  # ---- update (user-facing self-update subcommand; spec §4) ----
2537
2563
  sub_update = sub.add_parser(
2538
2564
  "update",