cctally 1.65.0 → 1.66.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 +33 -0
- package/bin/_cctally_alerts.py +1 -1
- package/bin/_cctally_cache.py +203 -22
- package/bin/_cctally_cache_report.py +7 -5
- package/bin/_cctally_core.py +49 -14
- package/bin/_cctally_dashboard.py +481 -4891
- package/bin/_cctally_dashboard_cache_report.py +714 -0
- package/bin/_cctally_dashboard_conversation.py +771 -0
- package/bin/_cctally_dashboard_envelope.py +1520 -0
- package/bin/_cctally_dashboard_share.py +2012 -0
- package/bin/_cctally_db.py +127 -5
- package/bin/_cctally_doctor.py +104 -3
- package/bin/_cctally_five_hour.py +2 -1
- package/bin/_cctally_forecast.py +78 -135
- package/bin/_cctally_parser.py +919 -784
- package/bin/_cctally_percent_breakdown.py +1 -1
- package/bin/_cctally_pricing_check.py +39 -0
- package/bin/_cctally_project.py +8 -5
- package/bin/_cctally_record.py +279 -274
- package/bin/_cctally_reporting.py +1 -1
- package/bin/_cctally_sync_week.py +1 -1
- package/bin/_cctally_telemetry.py +3 -5
- package/bin/_cctally_tui.py +42 -18
- package/bin/_lib_alert_dispatch.py +1 -1
- package/bin/_lib_blocks.py +6 -1
- package/bin/_lib_credit.py +133 -0
- package/bin/_lib_doctor.py +150 -1
- package/bin/_lib_five_hour.py +34 -0
- package/bin/_lib_forecast.py +144 -0
- package/bin/_lib_json_envelope.py +38 -0
- package/bin/_lib_jsonl.py +115 -73
- package/bin/_lib_log.py +96 -0
- package/bin/_lib_pricing.py +47 -1
- package/bin/_lib_pricing_check.py +25 -3
- package/bin/_lib_record.py +179 -0
- package/bin/_lib_render.py +18 -10
- package/bin/_lib_snapshot_cache.py +61 -0
- package/bin/cctally +51 -7
- package/bin/cctally-dashboard +2 -2
- package/bin/cctally-statusline +1 -0
- package/bin/cctally-tui +2 -2
- package/package.json +10 -1
|
@@ -156,7 +156,7 @@ def cmd_percent_breakdown(args: argparse.Namespace) -> int:
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
if args.json:
|
|
159
|
-
print(json.dumps(output, indent=2))
|
|
159
|
+
print(json.dumps(c.stamp_schema_version(output), indent=2))
|
|
160
160
|
return 0
|
|
161
161
|
|
|
162
162
|
# Prefer the reset-adjusted ISO timestamps in the terminal header
|
|
@@ -283,6 +283,14 @@ def cmd_pricing_check(args: argparse.Namespace) -> int:
|
|
|
283
283
|
|
|
284
284
|
drift = {"value_drift": [], "missing_from_us": [], "ahead_of_litellm": []}
|
|
285
285
|
existence = {"status": "skipped", "unpriced_vendor_models": []}
|
|
286
|
+
# staleSuppressions is network-derived (needs the LiteLLM scoped table), so
|
|
287
|
+
# under --offline it is SKIPPED, not degraded — matching the offline branch,
|
|
288
|
+
# which leaves degraded_components empty (#279 S7 W7). expiredSuppressions is
|
|
289
|
+
# date-derived and runs OFFLINE too, off the _command_as_of() clock seam.
|
|
290
|
+
stale_suppressions: list = []
|
|
291
|
+
expired_suppressions = _lib_pricing_check.expired_allowlist_entries(
|
|
292
|
+
c.PRICING_DRIFT_ALLOWLIST, now_utc,
|
|
293
|
+
)
|
|
286
294
|
|
|
287
295
|
if not args.offline:
|
|
288
296
|
litellm, ok = _fetch_litellm_prices()
|
|
@@ -297,6 +305,10 @@ def cmd_pricing_check(args: argparse.Namespace) -> int:
|
|
|
297
305
|
"missing_from_us": list(res.missing_from_us),
|
|
298
306
|
"ahead_of_litellm": list(res.ahead_of_litellm),
|
|
299
307
|
}
|
|
308
|
+
stale_suppressions = _lib_pricing_check.stale_allowlist_entries(
|
|
309
|
+
c.PRICING_DRIFT_ALLOWLIST, c.CLAUDE_MODEL_PRICING,
|
|
310
|
+
c.CODEX_MODEL_PRICING, scoped,
|
|
311
|
+
)
|
|
300
312
|
else:
|
|
301
313
|
status = "degraded"
|
|
302
314
|
degraded.append("litellm")
|
|
@@ -307,11 +319,15 @@ def cmd_pricing_check(args: argparse.Namespace) -> int:
|
|
|
307
319
|
|
|
308
320
|
# Actionable = any finding on a leg that ran. `ahead_of_litellm` is
|
|
309
321
|
# NEVER actionable (invariant #2). A degraded leg contributes no finding.
|
|
322
|
+
# A stale OR expired suppression is actionable (remove the allowlist entry /
|
|
323
|
+
# re-sync the snapshot) — both wired in explicitly (#279 S7 W7).
|
|
310
324
|
actionable = (
|
|
311
325
|
bool(coverage)
|
|
312
326
|
or bool(drift["value_drift"])
|
|
313
327
|
or bool(drift["missing_from_us"])
|
|
314
328
|
or bool(existence["unpriced_vendor_models"])
|
|
329
|
+
or bool(stale_suppressions)
|
|
330
|
+
or bool(expired_suppressions)
|
|
315
331
|
)
|
|
316
332
|
|
|
317
333
|
payload = {
|
|
@@ -322,6 +338,12 @@ def cmd_pricing_check(args: argparse.Namespace) -> int:
|
|
|
322
338
|
"coverage": [dataclasses.asdict(g) for g in coverage],
|
|
323
339
|
"drift": drift,
|
|
324
340
|
"existence": existence,
|
|
341
|
+
# Additive keys on the existing schemaVersion 1 envelope (no bump — CLI
|
|
342
|
+
# contract additive rule). staleSuppressions: allowlist entries whose
|
|
343
|
+
# divergence no longer exists (network). expiredSuppressions: allowlist
|
|
344
|
+
# entries past their `expires` cutover (date).
|
|
345
|
+
"staleSuppressions": list(stale_suppressions),
|
|
346
|
+
"expiredSuppressions": list(expired_suppressions),
|
|
325
347
|
"litellmSource": c.LITELLM_PRICES_URL,
|
|
326
348
|
}
|
|
327
349
|
|
|
@@ -385,6 +407,23 @@ def _render_pricing_check_text(payload: dict, *, offline: bool, actionable: bool
|
|
|
385
407
|
out("\n Existence: all vendor models priced.\n")
|
|
386
408
|
elif ex["status"] == "degraded":
|
|
387
409
|
out("\n Existence: /v1/models unavailable (skipped).\n")
|
|
410
|
+
stale = payload.get("staleSuppressions") or []
|
|
411
|
+
if stale:
|
|
412
|
+
out(f"\n Stale suppressions ({len(stale)}) — the divergence no "
|
|
413
|
+
f"longer exists, remove the allowlist entry:\n")
|
|
414
|
+
for e in stale:
|
|
415
|
+
fld = f".{e['field']}" if e.get("field") else ""
|
|
416
|
+
out(f" • {e['model']}{fld}\n")
|
|
417
|
+
|
|
418
|
+
# Expired suppressions are date-derived, so they render in BOTH offline and
|
|
419
|
+
# online modes.
|
|
420
|
+
expired = payload.get("expiredSuppressions") or []
|
|
421
|
+
if expired:
|
|
422
|
+
out(f"\n Expired suppressions ({len(expired)}) — past their `expires` "
|
|
423
|
+
f"cutover, remove the allowlist entry / re-sync the snapshot:\n")
|
|
424
|
+
for e in expired:
|
|
425
|
+
fld = f".{e['field']}" if e.get("field") else ""
|
|
426
|
+
out(f" • {e['model']}{fld} (expired {e.get('expires')})\n")
|
|
388
427
|
|
|
389
428
|
# Single-sourced from cmd_pricing_check's exit-code predicate (don't
|
|
390
429
|
# recompute the four-clause boolean here — it would drift).
|
package/bin/_cctally_project.py
CHANGED
|
@@ -352,7 +352,7 @@ def _project_json_output(
|
|
|
352
352
|
"projects": projects_json,
|
|
353
353
|
"warnings": warnings,
|
|
354
354
|
}
|
|
355
|
-
return json.dumps(payload, indent=2)
|
|
355
|
+
return json.dumps(_cctally().stamp_schema_version(payload), indent=2)
|
|
356
356
|
|
|
357
357
|
|
|
358
358
|
def _project_sort_key(row: dict, sort_by: str, order: str):
|
|
@@ -390,10 +390,10 @@ def cmd_project(args: argparse.Namespace) -> int:
|
|
|
390
390
|
# Flag-combination validation (must run before any expensive work).
|
|
391
391
|
if args.weeks is not None and args.weeks < 1:
|
|
392
392
|
eprint("Error: --weeks must be >= 1")
|
|
393
|
-
return
|
|
393
|
+
return 2
|
|
394
394
|
if args.weeks is not None and (args.since or args.until):
|
|
395
395
|
eprint("Error: --weeks cannot be combined with --since/--until")
|
|
396
|
-
return
|
|
396
|
+
return 2
|
|
397
397
|
if args.since and args.until:
|
|
398
398
|
# Parse both as dates using the same multi-format helper shape used
|
|
399
399
|
# elsewhere in the codebase so YYYY-MM-DD and YYYYMMDD both compare
|
|
@@ -415,7 +415,7 @@ def cmd_project(args: argparse.Namespace) -> int:
|
|
|
415
415
|
# complaint triggered by garbage input).
|
|
416
416
|
if since_parsed is not None and until_parsed is not None and since_parsed > until_parsed:
|
|
417
417
|
eprint("Error: --since must be <= --until")
|
|
418
|
-
return
|
|
418
|
+
return 2
|
|
419
419
|
|
|
420
420
|
now = _command_as_of()
|
|
421
421
|
conn = open_db()
|
|
@@ -424,7 +424,10 @@ def cmd_project(args: argparse.Namespace) -> int:
|
|
|
424
424
|
if args.since or args.until:
|
|
425
425
|
parsed = c._parse_cli_date_range(args, now_utc=now)
|
|
426
426
|
if isinstance(parsed, int):
|
|
427
|
-
|
|
427
|
+
# Translate the ccusage-parity helper's exit 1 into project's own
|
|
428
|
+
# native usage code 2 — project is cctally-native, not a ccusage
|
|
429
|
+
# drop-in (docs/cli-contract.md; #279 S6 W2).
|
|
430
|
+
return 2
|
|
428
431
|
since_dt, until_dt = parsed
|
|
429
432
|
since_dt = since_dt.astimezone(dt.timezone.utc)
|
|
430
433
|
until_dt = until_dt.astimezone(dt.timezone.utc)
|