cctally 1.82.0 → 1.83.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 +70 -0
- package/README.md +52 -74
- package/bin/_cctally_alerts.py +8 -1
- package/bin/_cctally_cache.py +963 -149
- package/bin/_cctally_config.py +43 -4
- package/bin/_cctally_core.py +933 -759
- package/bin/_cctally_dashboard.py +157 -47
- package/bin/_cctally_dashboard_cache_report.py +13 -6
- package/bin/_cctally_dashboard_conversation.py +1 -0
- package/bin/_cctally_dashboard_envelope.py +186 -8
- package/bin/_cctally_dashboard_share.py +60 -20
- package/bin/_cctally_dashboard_sources.py +427 -128
- package/bin/_cctally_db.py +605 -128
- package/bin/_cctally_doctor.py +413 -28
- package/bin/_cctally_five_hour.py +12 -5
- package/bin/_cctally_journal.py +2050 -156
- package/bin/_cctally_journal_repair.py +519 -0
- package/bin/_cctally_milestone_history.py +142 -56
- package/bin/_cctally_milestones.py +179 -111
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_project.py +24 -18
- package/bin/_cctally_quota.py +139 -25
- package/bin/_cctally_record.py +279 -108
- package/bin/_cctally_rederive.py +1052 -0
- package/bin/_cctally_reporting.py +58 -53
- package/bin/_cctally_setup.py +1 -0
- package/bin/_cctally_source_analytics.py +4 -1
- package/bin/_cctally_statusline.py +11 -11
- package/bin/_cctally_store.py +1039 -31
- package/bin/_cctally_sync_week.py +17 -8
- package/bin/_cctally_tui.py +421 -54
- package/bin/_cctally_update.py +133 -8
- package/bin/_cctally_weekrefs.py +14 -0
- package/bin/_lib_aggregators.py +10 -6
- package/bin/_lib_cache_report.py +101 -9
- package/bin/_lib_codex_pools.py +82 -0
- package/bin/_lib_conversation_query.py +126 -33
- package/bin/_lib_dashboard_sources.py +126 -1
- package/bin/_lib_diff_kernel.py +28 -15
- package/bin/_lib_doctor.py +342 -4
- package/bin/_lib_journal.py +924 -2
- package/bin/_lib_jsonl.py +43 -14
- package/bin/_lib_pricing.py +140 -21
- package/bin/_lib_readme_refresh.py +401 -0
- package/bin/_lib_rederive.py +395 -0
- package/bin/_lib_share.py +58 -2
- package/bin/cctally +56 -8
- package/dashboard/static/assets/{index-DJP4gEB7.js → index-3bgCMVHb.js} +52 -52
- package/dashboard/static/assets/index-D27EIHEI.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +6 -1
- package/dashboard/static/assets/index-Dk1nplOz.css +0 -1
|
@@ -47,8 +47,9 @@ from zoneinfo import ZoneInfo
|
|
|
47
47
|
from _cctally_core import open_db, parse_iso_datetime
|
|
48
48
|
from _cctally_config import save_config, _load_config_unlocked
|
|
49
49
|
from _lib_fmt import stable_sum
|
|
50
|
-
from _lib_pricing import _calculate_entry_cost
|
|
50
|
+
from _lib_pricing import _calculate_entry_cost, claude_usage_dict
|
|
51
51
|
from _lib_five_hour import _canonical_5h_window_key
|
|
52
|
+
from _lib_dashboard_sources import source_domain_freshness
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
# Share-CLI helpers consumed by the dashboard's share-data builders.
|
|
@@ -395,12 +396,14 @@ def _share_top_projects_for_range(
|
|
|
395
396
|
# an empty top_projects.
|
|
396
397
|
return []
|
|
397
398
|
for entry in entries:
|
|
398
|
-
usage =
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
399
|
+
usage = claude_usage_dict( # #195 chokepoint
|
|
400
|
+
input_tokens=entry.input_tokens,
|
|
401
|
+
output_tokens=entry.output_tokens,
|
|
402
|
+
cache_creation_tokens=entry.cache_creation_tokens,
|
|
403
|
+
cache_read_tokens=entry.cache_read_tokens,
|
|
404
|
+
cache_1h_tokens=getattr(entry, "cache_1h_tokens", None),
|
|
405
|
+
speed=getattr(entry, "speed", None),
|
|
406
|
+
)
|
|
404
407
|
cost = _calculate_entry_cost(
|
|
405
408
|
entry.model, usage, mode="auto", cost_usd=entry.cost_usd,
|
|
406
409
|
)
|
|
@@ -434,12 +437,14 @@ def _share_all_projects_for_range(
|
|
|
434
437
|
except Exception:
|
|
435
438
|
return bucket
|
|
436
439
|
for entry in entries:
|
|
437
|
-
usage =
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
440
|
+
usage = claude_usage_dict( # #195 chokepoint
|
|
441
|
+
input_tokens=entry.input_tokens,
|
|
442
|
+
output_tokens=entry.output_tokens,
|
|
443
|
+
cache_creation_tokens=entry.cache_creation_tokens,
|
|
444
|
+
cache_read_tokens=entry.cache_read_tokens,
|
|
445
|
+
cache_1h_tokens=getattr(entry, "cache_1h_tokens", None),
|
|
446
|
+
speed=getattr(entry, "speed", None),
|
|
447
|
+
)
|
|
443
448
|
cost = _calculate_entry_cost(
|
|
444
449
|
entry.model, usage, mode="auto", cost_usd=entry.cost_usd,
|
|
445
450
|
)
|
|
@@ -477,12 +482,14 @@ def _share_per_day_per_project_for_range(
|
|
|
477
482
|
except Exception:
|
|
478
483
|
return out
|
|
479
484
|
for entry in entries:
|
|
480
|
-
usage =
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
485
|
+
usage = claude_usage_dict( # #195 chokepoint
|
|
486
|
+
input_tokens=entry.input_tokens,
|
|
487
|
+
output_tokens=entry.output_tokens,
|
|
488
|
+
cache_creation_tokens=entry.cache_creation_tokens,
|
|
489
|
+
cache_read_tokens=entry.cache_read_tokens,
|
|
490
|
+
cache_1h_tokens=getattr(entry, "cache_1h_tokens", None),
|
|
491
|
+
speed=getattr(entry, "speed", None),
|
|
492
|
+
)
|
|
486
493
|
cost = _calculate_entry_cost(
|
|
487
494
|
entry.model, usage, mode="auto", cost_usd=entry.cost_usd,
|
|
488
495
|
)
|
|
@@ -1504,7 +1511,16 @@ def _build_codex_source_share_snapshot(ls, *, state, panel: str,
|
|
|
1504
1511
|
continue
|
|
1505
1512
|
forecast = row.get("forecast") if isinstance(row.get("forecast"), Mapping) else {}
|
|
1506
1513
|
current = row.get("current_percent")
|
|
1507
|
-
|
|
1514
|
+
# #350 spec §3.6 — "Projections blank. Actuals stay." BOTH the build
|
|
1515
|
+
# and the idle clock deliberately preserve `projected_percent`
|
|
1516
|
+
# alongside a non-ok `status`, and this site formatted it with no
|
|
1517
|
+
# status check at all. That was masked while a stale Codex source
|
|
1518
|
+
# collapsed to `unavailable` here; §3.4 keeps the source coherent, so
|
|
1519
|
+
# a shared forecast would otherwise publish a stale projection.
|
|
1520
|
+
projected = (
|
|
1521
|
+
forecast.get("projected_percent")
|
|
1522
|
+
if forecast.get("status") == "ok" else None
|
|
1523
|
+
)
|
|
1508
1524
|
rows.append(ls.Row(cells={
|
|
1509
1525
|
"limit": ls.TextCell(str(row.get("label") or "Codex quota")),
|
|
1510
1526
|
"current": ls.TextCell("—" if current is None else f"{float(current):.1f}%"),
|
|
@@ -1636,6 +1652,20 @@ def _share_state_domain(state, panel: str):
|
|
|
1636
1652
|
return data.get(panel)
|
|
1637
1653
|
|
|
1638
1654
|
|
|
1655
|
+
def _share_apply_current_week_freshness(snapshot, state, panel: str):
|
|
1656
|
+
"""Qualify retained current-week actuals with provider-local evidence age."""
|
|
1657
|
+
if (
|
|
1658
|
+
panel != "current-week"
|
|
1659
|
+
or source_domain_freshness(state, "hero") != "stale"
|
|
1660
|
+
):
|
|
1661
|
+
return snapshot
|
|
1662
|
+
provider = "Claude" if state.source == "claude" else "Codex"
|
|
1663
|
+
note = (
|
|
1664
|
+
f"{provider} current-week spend is based on stale provider-cycle evidence."
|
|
1665
|
+
)
|
|
1666
|
+
return replace(snapshot, notes=tuple(snapshot.notes) + (note,))
|
|
1667
|
+
|
|
1668
|
+
|
|
1639
1669
|
def _share_digest_input(*, panel: str, template_id: str, source: str,
|
|
1640
1670
|
source_explicit: bool, states, snapshots,
|
|
1641
1671
|
panel_data, account: "str | None" = None):
|
|
@@ -1656,6 +1686,10 @@ def _share_digest_input(*, panel: str, template_id: str, source: str,
|
|
|
1656
1686
|
"source": state.source,
|
|
1657
1687
|
"data_version": state.data_version,
|
|
1658
1688
|
"availability": state.availability,
|
|
1689
|
+
**(
|
|
1690
|
+
{"hero_freshness": source_domain_freshness(state, "hero")}
|
|
1691
|
+
if panel == "current-week" else {}
|
|
1692
|
+
),
|
|
1659
1693
|
"period": {
|
|
1660
1694
|
"start": snapshot.period.start,
|
|
1661
1695
|
"end": snapshot.period.end,
|
|
@@ -1703,6 +1737,9 @@ def _share_build_source_snapshots(*, ls, template, template_id: str,
|
|
|
1703
1737
|
# does not carry the additive S4 source bundle.
|
|
1704
1738
|
if source_explicit or source == "all":
|
|
1705
1739
|
claude_state = _source_state_for_share(data_snap, "claude")
|
|
1740
|
+
claude_snapshot = _share_apply_current_week_freshness(
|
|
1741
|
+
claude_snapshot, claude_state, panel,
|
|
1742
|
+
)
|
|
1706
1743
|
|
|
1707
1744
|
codex_snapshot = None
|
|
1708
1745
|
codex_state = None
|
|
@@ -1717,6 +1754,9 @@ def _share_build_source_snapshots(*, ls, template, template_id: str,
|
|
|
1717
1754
|
template_id=template_id,
|
|
1718
1755
|
options=options,
|
|
1719
1756
|
)
|
|
1757
|
+
codex_snapshot = _share_apply_current_week_freshness(
|
|
1758
|
+
codex_snapshot, codex_state, panel,
|
|
1759
|
+
)
|
|
1720
1760
|
|
|
1721
1761
|
if source == "claude":
|
|
1722
1762
|
return (claude_snapshot,), (claude_state,), panel_data
|