cctally 1.22.2 → 1.22.4
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 +21 -0
- package/bin/_cctally_cache_report.py +1133 -880
- package/bin/_cctally_codex.py +518 -0
- package/bin/_cctally_dashboard.py +3 -3
- package/bin/_cctally_diff.py +240 -0
- package/bin/_cctally_doctor.py +479 -0
- package/bin/_cctally_five_hour.py +1688 -0
- package/bin/_cctally_forecast.py +1979 -0
- package/bin/_cctally_milestones.py +433 -0
- package/bin/_cctally_percent_breakdown.py +199 -0
- package/bin/_cctally_pricing_check.py +393 -0
- package/bin/_cctally_record.py +8 -5
- package/bin/_cctally_reporting.py +749 -0
- package/bin/_cctally_setup.py +172 -13
- package/bin/_cctally_statusline.py +630 -0
- package/bin/_cctally_sync_week.py +5 -4
- package/bin/_cctally_weekrefs.py +484 -0
- package/bin/_lib_cache_report.py +938 -0
- package/bin/_lib_diff_kernel.py +5 -8
- package/bin/_lib_fmt.py +325 -0
- package/bin/_lib_pricing_debug.py +182 -0
- package/bin/_lib_render.py +9 -24
- package/bin/_lib_subscription_weeks.py +2 -2
- package/bin/cctally +466 -9190
- package/package.json +15 -1
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""`cctally pricing-check` subcommand entry point.
|
|
2
|
+
|
|
3
|
+
I/O sibling: holds the network/existence fetchers + `cmd_pricing_check`
|
|
4
|
+
+ the text renderer + `_pricing_observed_models` (the offline cache-scan
|
|
5
|
+
coverage leg, #125 Batch E C7), plus the two `_ENV_PRICING_*`
|
|
6
|
+
test-injection env-var name constants (module-private here). The pure
|
|
7
|
+
decision kernel lives in `_lib_pricing_check` (imported qualified,
|
|
8
|
+
module-top).
|
|
9
|
+
|
|
10
|
+
Honest *name* imports are KERNEL-ONLY (`_cctally_core`). The qualified
|
|
11
|
+
`_lib_pricing_check` import is the eagerly-preloaded library kernel
|
|
12
|
+
(bin/cctally:287). Every other sibling-homed symbol the command calls is
|
|
13
|
+
reached via the call-time `_cctally()` accessor so test monkeypatches
|
|
14
|
+
through `cctally`'s namespace are preserved — see spec §3.2.
|
|
15
|
+
|
|
16
|
+
bin/cctally re-exports `cmd_pricing_check` (eager) so the parser's
|
|
17
|
+
`set_defaults(func=c.cmd_pricing_check)` resolves unchanged.
|
|
18
|
+
|
|
19
|
+
Spec: docs/superpowers/specs/2026-05-30-extract-diagnostics-cmd-design.md
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import dataclasses
|
|
25
|
+
import datetime as dt
|
|
26
|
+
import json
|
|
27
|
+
import os
|
|
28
|
+
import pathlib
|
|
29
|
+
import sqlite3
|
|
30
|
+
import sys
|
|
31
|
+
import urllib.request
|
|
32
|
+
|
|
33
|
+
import _cctally_core
|
|
34
|
+
import _lib_pricing_check
|
|
35
|
+
from _cctally_core import _command_as_of, eprint
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _cctally():
|
|
39
|
+
"""Resolve the current `cctally` module at call-time (spec §3.2)."""
|
|
40
|
+
return sys.modules["cctally"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ── pricing-check network legs (spec §5.2) ──────────────────────────────
|
|
44
|
+
#
|
|
45
|
+
# Hidden dev hooks (like `project`'s CCTALLY_AS_OF): read at call time, NOT
|
|
46
|
+
# import time, so a test/harness can set them in the child process env. They
|
|
47
|
+
# are deliberately absent from `--help` — they only exist to make the
|
|
48
|
+
# network legs deterministic in tests (invariant #4: no test hits the
|
|
49
|
+
# network). When set to a path, the corresponding fetcher reads that local
|
|
50
|
+
# JSON instead of issuing an HTTP request.
|
|
51
|
+
_ENV_PRICING_LITELLM_FILE = "CCTALLY_PRICING_LITELLM_FILE"
|
|
52
|
+
_ENV_PRICING_MODELS_FILE = "CCTALLY_PRICING_MODELS_FILE"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _fetch_litellm_prices() -> "tuple[dict, bool]":
|
|
56
|
+
"""Fetch the LiteLLM model_prices map. Returns ``(data, ok)``.
|
|
57
|
+
|
|
58
|
+
NEVER raises to the caller: on any failure (bad inject file, network
|
|
59
|
+
error, non-JSON, non-dict body) returns ``({}, False)`` so the drift
|
|
60
|
+
leg degrades gracefully (spec invariant #1). Honors the hidden
|
|
61
|
+
``CCTALLY_PRICING_LITELLM_FILE`` env hook for deterministic tests.
|
|
62
|
+
"""
|
|
63
|
+
c = _cctally()
|
|
64
|
+
inject = os.environ.get(_ENV_PRICING_LITELLM_FILE, "").strip()
|
|
65
|
+
if inject:
|
|
66
|
+
try:
|
|
67
|
+
data = json.loads(pathlib.Path(inject).read_text())
|
|
68
|
+
return (data, True) if isinstance(data, dict) else ({}, False)
|
|
69
|
+
except Exception:
|
|
70
|
+
return {}, False
|
|
71
|
+
try:
|
|
72
|
+
# UA can be plain here — LiteLLM is a raw GitHub JSON blob, not the
|
|
73
|
+
# Anthropic rate-limited surface that requires `claude-code/*`.
|
|
74
|
+
req = urllib.request.Request(
|
|
75
|
+
c.LITELLM_PRICES_URL, headers={"User-Agent": "cctally"},
|
|
76
|
+
)
|
|
77
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
78
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
79
|
+
return (data, True) if isinstance(data, dict) else ({}, False)
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
eprint(f"[pricing-check] LiteLLM fetch failed: {exc}")
|
|
82
|
+
return {}, False
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _fetch_anthropic_models_or_none() -> "dict | None":
|
|
86
|
+
"""GET https://api.anthropic.com/v1/models with the Claude OAuth bearer.
|
|
87
|
+
|
|
88
|
+
Returns the parsed JSON object on success, or ``None`` on ANY failure
|
|
89
|
+
(no token, 401/403, network error, non-JSON, non-dict body) so the
|
|
90
|
+
existence leg degrades to ``status: degraded``. NEVER raises to the
|
|
91
|
+
caller — wrapped in a broad try/except.
|
|
92
|
+
|
|
93
|
+
C0 DE-RISK SPIKE STATUS — UNVERIFIED LIVE REACHABILITY: this code path
|
|
94
|
+
was authored WITHOUT a live `/v1/models` call (the sandbox has no real
|
|
95
|
+
OAuth token). It is UNKNOWN whether the Claude Code OAuth bearer
|
|
96
|
+
authorizes `GET /v1/models`; if the endpoint 401/403s, this function
|
|
97
|
+
returns None and the existence leg reports `status: degraded` (the
|
|
98
|
+
feature still stands on LiteLLM drift + the local coverage guard). The
|
|
99
|
+
maintainer must run `cctally pricing-check` on a machine with a real
|
|
100
|
+
OAuth token to confirm the leg actually reaches `status: ok`. The whole
|
|
101
|
+
leg is fully exercisable offline via `CCTALLY_PRICING_MODELS_FILE`.
|
|
102
|
+
"""
|
|
103
|
+
c = _cctally()
|
|
104
|
+
try:
|
|
105
|
+
token = c._resolve_oauth_token()
|
|
106
|
+
if not token:
|
|
107
|
+
return None
|
|
108
|
+
# Mirror the OAuth-usage UA discipline: Anthropic rate-limits
|
|
109
|
+
# per-UA, so use `claude-code/<version>` (NOT Python-urllib).
|
|
110
|
+
# RAW config read (NOT load_config / _load_config_unlocked — both
|
|
111
|
+
# call ensure_dirs() and would mutate a fresh HOME, violating the
|
|
112
|
+
# read-only contract). Honors an `oauth_usage.user_agent` override
|
|
113
|
+
# when config.json exists; otherwise the default `claude-code/<v>`.
|
|
114
|
+
raw_cfg = {}
|
|
115
|
+
try:
|
|
116
|
+
if _cctally_core.CONFIG_PATH.exists():
|
|
117
|
+
parsed = json.loads(
|
|
118
|
+
_cctally_core.CONFIG_PATH.read_text(encoding="utf-8"))
|
|
119
|
+
if isinstance(parsed, dict):
|
|
120
|
+
raw_cfg = parsed
|
|
121
|
+
except (json.JSONDecodeError, OSError):
|
|
122
|
+
raw_cfg = {}
|
|
123
|
+
cfg = c._get_oauth_usage_config(raw_cfg)
|
|
124
|
+
user_agent = c._resolve_oauth_usage_user_agent(cfg)
|
|
125
|
+
req = urllib.request.Request(
|
|
126
|
+
"https://api.anthropic.com/v1/models",
|
|
127
|
+
headers={
|
|
128
|
+
"Authorization": f"Bearer {token}",
|
|
129
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
130
|
+
"anthropic-version": "2023-06-01",
|
|
131
|
+
"User-Agent": user_agent,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
135
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
136
|
+
return data if isinstance(data, dict) else None
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
eprint(f"[pricing-check] /v1/models fetch failed: {exc}")
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _pricing_existence_check() -> dict:
|
|
143
|
+
"""Anthropic-only vendor `/v1/models` coverage gap.
|
|
144
|
+
|
|
145
|
+
Returns ``{"status": "ok"|"degraded"|"skipped", "unpriced_vendor_models":
|
|
146
|
+
[...]}``. ``ok`` = the vendor list was obtained; the gap is the IDs the
|
|
147
|
+
vendor offers that ``_resolve_model_pricing`` cannot price. ``degraded``
|
|
148
|
+
= the fetch failed (no token / 401 / network / non-JSON). Honors the
|
|
149
|
+
hidden ``CCTALLY_PRICING_MODELS_FILE`` env hook.
|
|
150
|
+
|
|
151
|
+
Codex existence is intentionally out of scope (no OpenAI credentials —
|
|
152
|
+
spec §4 non-goals); the payload's existence block is Anthropic-only.
|
|
153
|
+
"""
|
|
154
|
+
c = _cctally()
|
|
155
|
+
inject = os.environ.get(_ENV_PRICING_MODELS_FILE, "").strip()
|
|
156
|
+
if inject:
|
|
157
|
+
try:
|
|
158
|
+
raw = json.loads(pathlib.Path(inject).read_text())
|
|
159
|
+
except Exception:
|
|
160
|
+
return {"status": "degraded", "unpriced_vendor_models": []}
|
|
161
|
+
else:
|
|
162
|
+
raw = _fetch_anthropic_models_or_none()
|
|
163
|
+
if raw is None:
|
|
164
|
+
return {"status": "degraded", "unpriced_vendor_models": []}
|
|
165
|
+
if not isinstance(raw, dict):
|
|
166
|
+
return {"status": "degraded", "unpriced_vendor_models": []}
|
|
167
|
+
ids = [m.get("id") for m in raw.get("data", []) if isinstance(m, dict) and m.get("id")]
|
|
168
|
+
# Detection-only: warn=False so a vendor model we don't price doesn't
|
|
169
|
+
# fire the cost-engine's one-shot stderr warning.
|
|
170
|
+
gap = sorted(i for i in ids if c._resolve_model_pricing(i, warn=False) is None)
|
|
171
|
+
return {"status": "ok", "unpriced_vendor_models": gap}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Private sentinel so `_pricing_observed_models` can tell "default 30-day
|
|
175
|
+
# window" apart from an explicit `since=None` all-history scan.
|
|
176
|
+
_PRICING_SCAN_DEFAULT_WINDOW = object()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _pricing_observed_models(now_utc, *, since=_PRICING_SCAN_DEFAULT_WINDOW):
|
|
180
|
+
"""Read-only scan of the session-entry cache for observed models.
|
|
181
|
+
|
|
182
|
+
Returns a list of ``(provider, model, entry_count, token_total)`` tuples,
|
|
183
|
+
one per DISTINCT model seen in ``cache.db`` (Claude ``session_entries`` +
|
|
184
|
+
Codex ``codex_session_entries``). By default it scans the trailing 30-day
|
|
185
|
+
window relative to ``now_utc`` (the `doctor` coverage signal — recent =
|
|
186
|
+
actionable). Pass ``since=<datetime>`` to widen/narrow the window, or
|
|
187
|
+
``since=None`` explicitly for an all-history scan (used by `pricing-check`).
|
|
188
|
+
|
|
189
|
+
Read-only / no-mutation contract (spec §5.1): mirrors the freshness read
|
|
190
|
+
in this same function — guard on ``CACHE_DB_PATH.exists()``, raw
|
|
191
|
+
``sqlite3.connect`` (NEVER ``open_cache_db()`` / ``sync_cache()`` /
|
|
192
|
+
``load_config()`` / ``ensure_dirs()``), and treat a missing table/column as
|
|
193
|
+
"no observed models" rather than crashing. ``doctor --json`` on a virgin
|
|
194
|
+
HOME must not create ``APP_DIR`` — regression
|
|
195
|
+
``test_pricing_observed_models_no_mutation_on_fresh_home``.
|
|
196
|
+
"""
|
|
197
|
+
out: list = []
|
|
198
|
+
if not _cctally_core.CACHE_DB_PATH.exists():
|
|
199
|
+
return out
|
|
200
|
+
# Sentinel: the 30-day window is the default; `since=False` is not a
|
|
201
|
+
# supported value, so distinguish "caller wants all-history" (None) from
|
|
202
|
+
# "caller did not pass since" via a private marker.
|
|
203
|
+
if since is _PRICING_SCAN_DEFAULT_WINDOW:
|
|
204
|
+
cutoff_iso = (now_utc - dt.timedelta(days=30)).isoformat()
|
|
205
|
+
elif since is None:
|
|
206
|
+
cutoff_iso = None # all-history
|
|
207
|
+
else:
|
|
208
|
+
cutoff_iso = since.isoformat()
|
|
209
|
+
try:
|
|
210
|
+
conn = sqlite3.connect(str(_cctally_core.CACHE_DB_PATH))
|
|
211
|
+
except sqlite3.Error:
|
|
212
|
+
return out
|
|
213
|
+
try:
|
|
214
|
+
# Token-sum expressions use the ACTUAL cache column names from
|
|
215
|
+
# bin/_cctally_db.py::_apply_cache_schema (verified — Claude uses
|
|
216
|
+
# cache_create_tokens, NOT cache_creation_tokens; Codex carries a
|
|
217
|
+
# materialized total_tokens covering input/cache/output/reasoning).
|
|
218
|
+
for provider, table, tok_expr in (
|
|
219
|
+
("claude", "session_entries",
|
|
220
|
+
"COALESCE(input_tokens,0)+COALESCE(output_tokens,0)+"
|
|
221
|
+
"COALESCE(cache_create_tokens,0)+COALESCE(cache_read_tokens,0)"),
|
|
222
|
+
("codex", "codex_session_entries",
|
|
223
|
+
"COALESCE(total_tokens,0)"),
|
|
224
|
+
):
|
|
225
|
+
where = "model IS NOT NULL"
|
|
226
|
+
params: tuple = ()
|
|
227
|
+
if cutoff_iso is not None:
|
|
228
|
+
where = "timestamp_utc >= ? AND " + where
|
|
229
|
+
params = (cutoff_iso,)
|
|
230
|
+
try:
|
|
231
|
+
rows = conn.execute(
|
|
232
|
+
f"SELECT model, COUNT(*), SUM({tok_expr}) FROM {table} "
|
|
233
|
+
f"WHERE {where} GROUP BY model",
|
|
234
|
+
params,
|
|
235
|
+
).fetchall()
|
|
236
|
+
except sqlite3.OperationalError:
|
|
237
|
+
rows = [] # table/column missing — treat as none
|
|
238
|
+
for model, cnt, toks in rows:
|
|
239
|
+
out.append((provider, model, int(cnt or 0), int(toks or 0)))
|
|
240
|
+
finally:
|
|
241
|
+
conn.close()
|
|
242
|
+
return out
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def cmd_pricing_check(args: argparse.Namespace) -> int:
|
|
246
|
+
"""`cctally pricing-check` — detect stale/missing embedded pricing.
|
|
247
|
+
|
|
248
|
+
Three independently-degrading legs (spec §5.2):
|
|
249
|
+
1. coverage (offline, ALL-HISTORY) — models in cache.db we can't price.
|
|
250
|
+
2. drift (network, LiteLLM) — embedded value vs LiteLLM (direction-aware
|
|
251
|
+
+ allowlist-suppressed).
|
|
252
|
+
3. existence (network, Anthropic `/v1/models`) — vendor models absent
|
|
253
|
+
from our table.
|
|
254
|
+
|
|
255
|
+
Exit-code precedence (spec invariant #1, §5.2):
|
|
256
|
+
1 — ANY actionable finding (coverage gap OR value_drift OR
|
|
257
|
+
missing_from_us OR an existence gap), EVEN IF a network leg
|
|
258
|
+
degraded. Findings always win over degradation.
|
|
259
|
+
0 — NO actionable findings (fully clean OR partially/fully degraded
|
|
260
|
+
but nothing actionable). JSON still carries status=degraded.
|
|
261
|
+
2 — argument/usage error (argparse handles before we run).
|
|
262
|
+
|
|
263
|
+
``status`` (ok|degraded) reports check COMPLETENESS; the exit code
|
|
264
|
+
reports whether the operator must ACT. They are orthogonal: a degraded
|
|
265
|
+
leg never masks a finding and never fabricates one.
|
|
266
|
+
"""
|
|
267
|
+
c = _cctally()
|
|
268
|
+
now_utc = _command_as_of()
|
|
269
|
+
status = "ok"
|
|
270
|
+
degraded: list[str] = []
|
|
271
|
+
|
|
272
|
+
# 1. Coverage — offline, all-history (since=None). Read-only scan; any
|
|
273
|
+
# failure degrades to [] (the scan itself swallows DB errors).
|
|
274
|
+
try:
|
|
275
|
+
observed = _pricing_observed_models(now_utc, since=None)
|
|
276
|
+
coverage = _lib_pricing_check.classify_coverage(
|
|
277
|
+
observed,
|
|
278
|
+
lambda m: c._resolve_model_pricing(m, warn=False),
|
|
279
|
+
c._is_codex_fallback,
|
|
280
|
+
)
|
|
281
|
+
except Exception:
|
|
282
|
+
coverage = []
|
|
283
|
+
|
|
284
|
+
drift = {"value_drift": [], "missing_from_us": [], "ahead_of_litellm": []}
|
|
285
|
+
existence = {"status": "skipped", "unpriced_vendor_models": []}
|
|
286
|
+
|
|
287
|
+
if not args.offline:
|
|
288
|
+
litellm, ok = _fetch_litellm_prices()
|
|
289
|
+
if ok:
|
|
290
|
+
scoped = _lib_pricing_check.scope_litellm(litellm)
|
|
291
|
+
res = _lib_pricing_check.diff_pricing(
|
|
292
|
+
c.CLAUDE_MODEL_PRICING, c.CODEX_MODEL_PRICING,
|
|
293
|
+
scoped, c.PRICING_DRIFT_ALLOWLIST,
|
|
294
|
+
)
|
|
295
|
+
drift = {
|
|
296
|
+
"value_drift": [dataclasses.asdict(r) for r in res.value_drift],
|
|
297
|
+
"missing_from_us": list(res.missing_from_us),
|
|
298
|
+
"ahead_of_litellm": list(res.ahead_of_litellm),
|
|
299
|
+
}
|
|
300
|
+
else:
|
|
301
|
+
status = "degraded"
|
|
302
|
+
degraded.append("litellm")
|
|
303
|
+
existence = _pricing_existence_check()
|
|
304
|
+
if existence["status"] == "degraded":
|
|
305
|
+
status = "degraded"
|
|
306
|
+
degraded.append("models_api")
|
|
307
|
+
|
|
308
|
+
# Actionable = any finding on a leg that ran. `ahead_of_litellm` is
|
|
309
|
+
# NEVER actionable (invariant #2). A degraded leg contributes no finding.
|
|
310
|
+
actionable = (
|
|
311
|
+
bool(coverage)
|
|
312
|
+
or bool(drift["value_drift"])
|
|
313
|
+
or bool(drift["missing_from_us"])
|
|
314
|
+
or bool(existence["unpriced_vendor_models"])
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
payload = {
|
|
318
|
+
"schemaVersion": 1,
|
|
319
|
+
"status": status,
|
|
320
|
+
"degraded_components": degraded,
|
|
321
|
+
"snapshotDate": c.PRICING_SNAPSHOT_DATE,
|
|
322
|
+
"coverage": [dataclasses.asdict(g) for g in coverage],
|
|
323
|
+
"drift": drift,
|
|
324
|
+
"existence": existence,
|
|
325
|
+
"litellmSource": c.LITELLM_PRICES_URL,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if getattr(args, "json", False):
|
|
329
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
330
|
+
else:
|
|
331
|
+
_render_pricing_check_text(payload, offline=args.offline, actionable=actionable)
|
|
332
|
+
|
|
333
|
+
return 1 if actionable else 0
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _render_pricing_check_text(payload: dict, *, offline: bool, actionable: bool) -> None:
|
|
337
|
+
"""Human-readable render of the pricing-check payload. JSON is the
|
|
338
|
+
machine contract; this is a readable summary for interactive use."""
|
|
339
|
+
out = sys.stdout.write
|
|
340
|
+
status = payload["status"]
|
|
341
|
+
out(f"pricing-check (snapshot {payload['snapshotDate']})\n")
|
|
342
|
+
if status == "degraded":
|
|
343
|
+
out(f" status: degraded — incomplete check "
|
|
344
|
+
f"({', '.join(payload['degraded_components'])} unavailable)\n")
|
|
345
|
+
else:
|
|
346
|
+
out(" status: ok\n")
|
|
347
|
+
|
|
348
|
+
cov = payload["coverage"]
|
|
349
|
+
if cov:
|
|
350
|
+
out(f"\n Coverage gaps ({len(cov)} model(s) we cannot price exactly):\n")
|
|
351
|
+
for g in cov:
|
|
352
|
+
kind = ("unpriced ($0)" if g["kind"] == "unpriced"
|
|
353
|
+
else "approximated via gpt-5")
|
|
354
|
+
entries = g["entry_count"]
|
|
355
|
+
noun = "entry" if entries == 1 else "entries"
|
|
356
|
+
out(f" • {g['model']} ({g['provider']}): {entries} "
|
|
357
|
+
f"{noun} / {g['token_total']} tokens — {kind}\n")
|
|
358
|
+
else:
|
|
359
|
+
out("\n Coverage: all observed models priced.\n")
|
|
360
|
+
|
|
361
|
+
if offline:
|
|
362
|
+
out("\n (offline — network drift + existence legs skipped)\n")
|
|
363
|
+
else:
|
|
364
|
+
vd = payload["drift"]["value_drift"]
|
|
365
|
+
mu = payload["drift"]["missing_from_us"]
|
|
366
|
+
if vd:
|
|
367
|
+
out(f"\n Value drift vs LiteLLM ({len(vd)} field(s)):\n")
|
|
368
|
+
for d in vd:
|
|
369
|
+
out(f" • {d['model']}.{d['field']}: ours={d['ours']} "
|
|
370
|
+
f"litellm={d['theirs']}\n")
|
|
371
|
+
if mu:
|
|
372
|
+
out(f"\n Models LiteLLM prices but we don't ({len(mu)}):\n")
|
|
373
|
+
for m in mu:
|
|
374
|
+
out(f" • {m}\n")
|
|
375
|
+
if not vd and not mu and "litellm" not in payload["degraded_components"]:
|
|
376
|
+
out("\n Drift: embedded pricing matches LiteLLM.\n")
|
|
377
|
+
ex = payload["existence"]
|
|
378
|
+
if ex["status"] == "ok":
|
|
379
|
+
gap = ex["unpriced_vendor_models"]
|
|
380
|
+
if gap:
|
|
381
|
+
out(f"\n Vendor models not in our table ({len(gap)}):\n")
|
|
382
|
+
for m in gap:
|
|
383
|
+
out(f" • {m}\n")
|
|
384
|
+
else:
|
|
385
|
+
out("\n Existence: all vendor models priced.\n")
|
|
386
|
+
elif ex["status"] == "degraded":
|
|
387
|
+
out("\n Existence: /v1/models unavailable (skipped).\n")
|
|
388
|
+
|
|
389
|
+
# Single-sourced from cmd_pricing_check's exit-code predicate (don't
|
|
390
|
+
# recompute the four-clause boolean here — it would drift).
|
|
391
|
+
if actionable:
|
|
392
|
+
out("\n Action: review CLAUDE_MODEL_PRICING / CODEX_MODEL_PRICING; "
|
|
393
|
+
"bump PRICING_SNAPSHOT_DATE on sync.\n")
|
package/bin/_cctally_record.py
CHANGED
|
@@ -109,8 +109,10 @@ What stays in bin/cctally:
|
|
|
109
109
|
``_build_alert_payload_five_hour``, ``eprint``,
|
|
110
110
|
``get_claude_session_entries``, ``_FIVE_HOUR_JITTER_FLOOR_SECONDS``,
|
|
111
111
|
``_RESET_PCT_DROP_THRESHOLD`` — boundary helpers, already-extracted
|
|
112
|
-
subsystems, or constants
|
|
113
|
-
|
|
112
|
+
subsystems, or constants reached through the cctally namespace
|
|
113
|
+
(``_RESET_PCT_DROP_THRESHOLD`` now lives in ``bin/_cctally_weekrefs.py``,
|
|
114
|
+
re-exported on the cctally ns). All accessed via the same shim/``c.X``
|
|
115
|
+
pattern.
|
|
114
116
|
|
|
115
117
|
§5.6 audit on this extraction's monkeypatch surface:
|
|
116
118
|
- ``cmd_record_usage`` — patched via ``monkeypatch.setitem(ns, …)``
|
|
@@ -381,7 +383,8 @@ _logged_window_key_coerce_failure = False
|
|
|
381
383
|
# _cctally_core.HOOK_TICK_LOG_DIR / _PATH / _ROTATED_PATH / _ROTATE_BYTES
|
|
382
384
|
# _cctally_core.HOOK_TICK_THROTTLE_PATH / _LOCK_PATH
|
|
383
385
|
# c._FIVE_HOUR_JITTER_FLOOR_SECONDS — _lib_five_hour.* re-export
|
|
384
|
-
# c._RESET_PCT_DROP_THRESHOLD — bin/
|
|
386
|
+
# c._RESET_PCT_DROP_THRESHOLD — bin/_cctally_weekrefs.py constant (re-exported on cctally ns)
|
|
387
|
+
# c._is_reset_drop — bin/_cctally_weekrefs.py helper (re-exported on cctally ns)
|
|
385
388
|
# c.HOOK_TICK_DEFAULT_THROTTLE_SECONDS
|
|
386
389
|
|
|
387
390
|
|
|
@@ -1637,7 +1640,7 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1637
1640
|
if (
|
|
1638
1641
|
prior_end_dt > now_utc
|
|
1639
1642
|
and prior_pct is not None
|
|
1640
|
-
and (
|
|
1643
|
+
and c._is_reset_drop(prior_pct, weekly_percent)
|
|
1641
1644
|
):
|
|
1642
1645
|
# See _backfill_week_reset_events for why we floor
|
|
1643
1646
|
# the reset moment to the hour (natural display
|
|
@@ -1665,7 +1668,7 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
|
|
|
1665
1668
|
if (
|
|
1666
1669
|
prior_end_dt > now_utc
|
|
1667
1670
|
and prior_pct is not None
|
|
1668
|
-
and (
|
|
1671
|
+
and c._is_reset_drop(prior_pct, weekly_percent)
|
|
1669
1672
|
):
|
|
1670
1673
|
# Pre-check (Q5 belt-and-suspenders): suppress duplicate
|
|
1671
1674
|
# event rows for the same new_week_end_at across
|