cctally 1.60.0 → 1.62.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 +24 -0
- package/bin/_cctally_cache.py +479 -54
- package/bin/_cctally_core.py +8 -0
- package/bin/_cctally_dashboard.py +1467 -234
- package/bin/_cctally_db.py +27 -1
- package/bin/_cctally_doctor.py +2 -0
- package/bin/_cctally_forecast.py +24 -2
- package/bin/_cctally_parser.py +28 -2
- package/bin/_cctally_tui.py +708 -16
- package/bin/_cctally_update.py +49 -9
- package/bin/_cctally_weekrefs.py +101 -2
- package/bin/_lib_aggregators.py +98 -71
- package/bin/_lib_cache_report.py +455 -67
- package/bin/_lib_doctor.py +10 -0
- package/bin/_lib_pricing.py +31 -4
- package/bin/_lib_snapshot_cache.py +1797 -0
- package/bin/_lib_view_models.py +107 -12
- package/bin/cctally +57 -0
- package/dashboard/static/assets/index-0jzYm75p.css +1 -0
- package/dashboard/static/assets/{index-DQH6UPc_.js → index-D_Ylyqsf.js} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +2 -1
- package/dashboard/static/assets/index-uGwZSssU.css +0 -1
package/bin/_cctally_db.py
CHANGED
|
@@ -2310,7 +2310,9 @@ def _apply_cache_schema(conn: sqlite3.Connection) -> None:
|
|
|
2310
2310
|
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
|
2311
2311
|
usage_extra_json TEXT,
|
|
2312
2312
|
cost_usd_raw REAL,
|
|
2313
|
-
speed TEXT
|
|
2313
|
+
speed TEXT,
|
|
2314
|
+
mutation_seq INTEGER NOT NULL DEFAULT 0,
|
|
2315
|
+
mutation_min_ts TEXT
|
|
2314
2316
|
);
|
|
2315
2317
|
CREATE INDEX IF NOT EXISTS idx_entries_timestamp
|
|
2316
2318
|
ON session_entries(timestamp_utc);
|
|
@@ -2474,6 +2476,30 @@ def _apply_cache_schema(conn: sqlite3.Connection) -> None:
|
|
|
2474
2476
|
# after cost_usd_raw to match the CREATE TABLE order; cache migration 008
|
|
2475
2477
|
# then backfills it from the legacy blob on existing rows.
|
|
2476
2478
|
add_column_if_missing(conn, "session_entries", "speed", "TEXT")
|
|
2479
|
+
# #270: the durable per-row mutation signal. ``mutation_seq`` is a change
|
|
2480
|
+
# stamp bumped on every insert + every WHERE-passing in-place UPSERT (from a
|
|
2481
|
+
# cache_meta counter, ingest side); ``mutation_min_ts`` records the EARLIEST
|
|
2482
|
+
# event time the row has ever held, so a finalization that overwrites
|
|
2483
|
+
# ``timestamp_utc`` and moves the row across a bucket boundary still lets the
|
|
2484
|
+
# closed-bucket watermark reach the OLD bucket. Idempotent column-adds (no
|
|
2485
|
+
# marker, no version — a pure column+index add, not a framework migration);
|
|
2486
|
+
# ``INTEGER NOT NULL DEFAULT 0`` is a metadata-only add in SQLite (no table
|
|
2487
|
+
# rewrite on a large session_entries). The covering index
|
|
2488
|
+
# ``idx_entries_mutation_seq`` backs the one new query shape
|
|
2489
|
+
# ``MIN(mutation_min_ts) WHERE mutation_seq > ?`` (index-only range-min over
|
|
2490
|
+
# the delta) plus the ``WHERE mutation_seq > ?`` filters. Both the column
|
|
2491
|
+
# adds AND the index MUST run BEFORE the legacy FTS early-return below (the
|
|
2492
|
+
# ``_apply_cache_schema_legacy_early_return_before_new_table`` gotcha), so an
|
|
2493
|
+
# old cache.db that early-returns still receives them. The index create is
|
|
2494
|
+
# kept OUT of the top executescript on purpose: on an existing DB the CREATE
|
|
2495
|
+
# TABLE there is a no-op, so the columns do not exist yet and an index over
|
|
2496
|
+
# them would raise — it must follow the add_column_if_missing calls.
|
|
2497
|
+
add_column_if_missing(
|
|
2498
|
+
conn, "session_entries", "mutation_seq", "INTEGER NOT NULL DEFAULT 0")
|
|
2499
|
+
add_column_if_missing(conn, "session_entries", "mutation_min_ts", "TEXT")
|
|
2500
|
+
conn.execute(
|
|
2501
|
+
"CREATE INDEX IF NOT EXISTS idx_entries_mutation_seq "
|
|
2502
|
+
"ON session_entries(mutation_seq, mutation_min_ts)")
|
|
2477
2503
|
# Existing-DB guard for the skill-content fold link (cctally-dev
|
|
2478
2504
|
# skill-content-nesting): the message-level sourceToolUseID. Idempotent
|
|
2479
2505
|
# column-add (no marker, no version); cache migration 006 then re-ingests
|
package/bin/_cctally_doctor.py
CHANGED
|
@@ -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).
|
package/bin/_cctally_forecast.py
CHANGED
|
@@ -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
|
-
|
|
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)
|
package/bin/_cctally_parser.py
CHANGED
|
@@ -1523,8 +1523,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1523
1523
|
dp.add_argument(
|
|
1524
1524
|
"--port",
|
|
1525
1525
|
type=int,
|
|
1526
|
-
default=
|
|
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",
|