cctally 1.111.0 → 1.112.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 +17 -0
- package/README.md +5 -6
- package/bin/_cctally_cache.py +14 -7
- package/bin/_cctally_core.py +9 -1
- package/bin/_cctally_dashboard_envelope.py +7 -6
- package/bin/_cctally_dashboard_sources.py +90 -55
- package/bin/_cctally_doctor.py +73 -27
- package/bin/_cctally_forecast.py +2 -1
- package/bin/_cctally_journal.py +147 -6
- package/bin/_cctally_milestones.py +29 -3
- package/bin/_cctally_quota.py +10 -1
- package/bin/_cctally_record.py +136 -4
- package/bin/_cctally_rederive.py +311 -8
- package/bin/_cctally_source_analytics.py +20 -8
- package/bin/_cctally_tui.py +546 -45
- package/bin/_lib_codex_conversation_query.py +40 -4
- package/bin/_lib_codex_metadata.py +57 -11
- package/bin/_lib_cost_provenance.py +303 -0
- package/bin/_lib_dashboard_sources.py +263 -28
- package/bin/_lib_doctor.py +2 -2
- package/bin/_lib_ingest_frontier.py +137 -38
- package/bin/_lib_pricing.py +13 -1
- package/bin/_lib_rederive.py +704 -36
- package/bin/_lib_snapshot_cache.py +332 -9
- package/dashboard/static/assets/{ConversationsView-BB9vUiL6.js → ConversationsView-CEZ-BUyi.js} +1 -1
- package/dashboard/static/assets/{DoctorModal-DFY2iPDf.js → DoctorModal-CE1oH_Zy.js} +1 -1
- package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +1 -0
- package/dashboard/static/assets/{ProjectsDrillPanel-DAv39uSo.js → ProjectsDrillPanel-B3DZoCeD.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-DSAWo2Fp.js → SourceDetailModal-CHp9EgLP.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-R_9dOVnh.js → UpdateModal-5iKST6jo.js} +1 -1
- package/dashboard/static/assets/{index-DdU62sov.js → index-Cm0VTxJC.js} +2 -2
- package/dashboard/static/assets/index-Dt4tqf43.css +1 -0
- package/dashboard/static/assets/{outlineNavigation-BOCLcRa6.js → outlineNavigation-DkXGL8J6.js} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +2 -1
- package/dashboard/static/assets/ModalRoot-DvRKHbQE.js +0 -1
- package/dashboard/static/assets/index-COhxe3vU.css +0 -1
|
@@ -3314,11 +3314,8 @@ def _stored_filter_sql(
|
|
|
3314
3314
|
clauses.append(f"{alias}.project_key = ?")
|
|
3315
3315
|
params.append(project_key)
|
|
3316
3316
|
if undecodable_keys:
|
|
3317
|
-
ordered = sorted(undecodable_keys)
|
|
3318
|
-
placeholders = ",".join("?" for _ in ordered)
|
|
3319
3317
|
clauses.append(
|
|
3320
|
-
f"{alias}.conversation_key
|
|
3321
|
-
params.extend(ordered)
|
|
3318
|
+
f"cctally_codex_undecodable({alias}.conversation_key) = 0")
|
|
3322
3319
|
if model is not None:
|
|
3323
3320
|
# models_json is the writer's canonical JSON array. Searching for the
|
|
3324
3321
|
# complete JSON string literal is exact and does not require JSON1.
|
|
@@ -3347,10 +3344,49 @@ def _page_costs(
|
|
|
3347
3344
|
return totals
|
|
3348
3345
|
|
|
3349
3346
|
|
|
3347
|
+
@contextlib.contextmanager
|
|
3348
|
+
def _stored_undecodable_membership(
|
|
3349
|
+
conn: sqlite3.Connection, undecodable_keys: "frozenset[str]",
|
|
3350
|
+
):
|
|
3351
|
+
"""Install the stored page's request-bounded membership predicate.
|
|
3352
|
+
|
|
3353
|
+
Only one membership bit crosses the SQL boundary; the raw unscoped read's
|
|
3354
|
+
attribution values never do. Key cardinality consumes no host parameters,
|
|
3355
|
+
registration executes no SQL, and cleanup prevents a later request from
|
|
3356
|
+
seeing this request's frozen set.
|
|
3357
|
+
"""
|
|
3358
|
+
if not undecodable_keys:
|
|
3359
|
+
yield
|
|
3360
|
+
return
|
|
3361
|
+
frozen = frozenset(undecodable_keys)
|
|
3362
|
+
conn.create_function(
|
|
3363
|
+
"cctally_codex_undecodable", 1,
|
|
3364
|
+
lambda conversation_key: int(conversation_key in frozen),
|
|
3365
|
+
deterministic=True,
|
|
3366
|
+
)
|
|
3367
|
+
try:
|
|
3368
|
+
yield
|
|
3369
|
+
finally:
|
|
3370
|
+
conn.create_function("cctally_codex_undecodable", 1, None)
|
|
3371
|
+
|
|
3372
|
+
|
|
3350
3373
|
def _stored_browse_page(
|
|
3351
3374
|
conn: sqlite3.Connection, *, effective_speed: str,
|
|
3352
3375
|
project_key: str | None, model: str | None, limit: int,
|
|
3353
3376
|
cursor: str | None, undecodable_keys: "frozenset[str]" = frozenset(),
|
|
3377
|
+
):
|
|
3378
|
+
membership = undecodable_keys if project_key is not None else frozenset()
|
|
3379
|
+
with _stored_undecodable_membership(conn, membership):
|
|
3380
|
+
return _stored_browse_page_queries(
|
|
3381
|
+
conn, effective_speed=effective_speed, project_key=project_key,
|
|
3382
|
+
model=model, limit=limit, cursor=cursor,
|
|
3383
|
+
undecodable_keys=undecodable_keys)
|
|
3384
|
+
|
|
3385
|
+
|
|
3386
|
+
def _stored_browse_page_queries(
|
|
3387
|
+
conn: sqlite3.Connection, *, effective_speed: str,
|
|
3388
|
+
project_key: str | None, model: str | None, limit: int,
|
|
3389
|
+
cursor: str | None, undecodable_keys: "frozenset[str]" = frozenset(),
|
|
3354
3390
|
):
|
|
3355
3391
|
where_sql, filter_params = _stored_filter_sql(
|
|
3356
3392
|
"r", project_key, model, undecodable_keys)
|
|
@@ -23,6 +23,8 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
import dataclasses
|
|
25
25
|
import sqlite3
|
|
26
|
+
import threading
|
|
27
|
+
from collections import OrderedDict
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
#: The physical name of the raw threads table. ``resolve_codex_threads_table``
|
|
@@ -30,6 +32,16 @@ import sqlite3
|
|
|
30
32
|
CODEX_THREADS_TABLE = "codex_conversation_threads"
|
|
31
33
|
|
|
32
34
|
|
|
35
|
+
# ``sqlite3.Connection`` has no writable attribute dictionary and cannot be
|
|
36
|
+
# weak-referenced. A small strong-key LRU carries each positive resolution on
|
|
37
|
+
# the connection object itself. Holding the object prevents an ``id`` reuse
|
|
38
|
+
# from inheriting another connection's answer; the bound prevents closed
|
|
39
|
+
# connection objects from accumulating without limit.
|
|
40
|
+
_THREADS_TABLE_RESOLUTION_CACHE_MAX = 64
|
|
41
|
+
_threads_table_resolution_cache: "OrderedDict[object, str]" = OrderedDict()
|
|
42
|
+
_threads_table_resolution_lock = threading.Lock()
|
|
43
|
+
|
|
44
|
+
|
|
33
45
|
@dataclasses.dataclass(frozen=True)
|
|
34
46
|
class DecodedCodexProjectMetadata:
|
|
35
47
|
"""One thread row's two project-metadata fields, decoded field by field.
|
|
@@ -171,7 +183,7 @@ def _schema_holds_threads_table(conn: sqlite3.Connection, schema: str) -> bool:
|
|
|
171
183
|
|
|
172
184
|
|
|
173
185
|
def resolve_codex_threads_table(conn: sqlite3.Connection) -> str:
|
|
174
|
-
"""The RAW threads table reference for this connection
|
|
186
|
+
"""The RAW threads table reference for this connection.
|
|
175
187
|
|
|
176
188
|
A bare cache connection (``open_cache_db()``) holds the table in its own
|
|
177
189
|
``main`` schema; a conversations connection
|
|
@@ -187,6 +199,16 @@ def resolve_codex_threads_table(conn: sqlite3.Connection) -> str:
|
|
|
187
199
|
with ``no such table`` on the bare cache connection, which is why the
|
|
188
200
|
reference is resolved rather than written out.
|
|
189
201
|
|
|
202
|
+
A positive resolution is retained for this connection object. Production
|
|
203
|
+
connections establish their fixed ``main``/``cache_db`` topology before
|
|
204
|
+
their first read, so steady-state requests perform no repeated
|
|
205
|
+
``sqlite_master`` or ``database_list`` probes. A caller that changes that
|
|
206
|
+
topology with ATTACH, DETACH, or table DDL must call
|
|
207
|
+
:func:`invalidate_codex_threads_table_resolution` before the next read.
|
|
208
|
+
Closing the connection ends the useful lifetime of its entry, and the
|
|
209
|
+
bounded LRU eventually releases the closed object. Absent-table results
|
|
210
|
+
are never cached.
|
|
211
|
+
|
|
190
212
|
``RuntimeError`` means exactly one thing: the table is absent from every
|
|
191
213
|
schema this connection can see. A ``sqlite3.Error`` raised while
|
|
192
214
|
resolving is a read the store could not perform and propagates unchanged,
|
|
@@ -194,14 +216,38 @@ def resolve_codex_threads_table(conn: sqlite3.Connection) -> str:
|
|
|
194
216
|
(zero undecodable rows, the empty key set) never gives that answer for a
|
|
195
217
|
locked or malformed store.
|
|
196
218
|
"""
|
|
219
|
+
with _threads_table_resolution_lock:
|
|
220
|
+
cached = _threads_table_resolution_cache.get(conn)
|
|
221
|
+
if cached is not None:
|
|
222
|
+
_threads_table_resolution_cache.move_to_end(conn)
|
|
223
|
+
return cached
|
|
224
|
+
|
|
225
|
+
resolved = None
|
|
197
226
|
if _schema_holds_threads_table(conn, "main"):
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
227
|
+
resolved = f"main.{CODEX_THREADS_TABLE}"
|
|
228
|
+
else:
|
|
229
|
+
attached = tuple(conn.execute("PRAGMA database_list"))
|
|
230
|
+
for row in attached:
|
|
231
|
+
name = str(row[1]) if len(row) > 1 else ""
|
|
232
|
+
if name == "cache_db" and _schema_holds_threads_table(conn, name):
|
|
233
|
+
resolved = f"cache_db.{CODEX_THREADS_TABLE}"
|
|
234
|
+
break
|
|
235
|
+
if resolved is None:
|
|
236
|
+
raise RuntimeError(
|
|
237
|
+
"the Codex threads table is not present in this connection's main "
|
|
238
|
+
"schema and no cache database is attached as cache_db"
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
with _threads_table_resolution_lock:
|
|
242
|
+
_threads_table_resolution_cache[conn] = resolved
|
|
243
|
+
_threads_table_resolution_cache.move_to_end(conn)
|
|
244
|
+
while len(_threads_table_resolution_cache) > (
|
|
245
|
+
_THREADS_TABLE_RESOLUTION_CACHE_MAX):
|
|
246
|
+
_threads_table_resolution_cache.popitem(last=False)
|
|
247
|
+
return resolved
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def invalidate_codex_threads_table_resolution(conn: sqlite3.Connection) -> None:
|
|
251
|
+
"""Forget a positive raw-table answer after connection schema changes."""
|
|
252
|
+
with _threads_table_resolution_lock:
|
|
253
|
+
_threads_table_resolution_cache.pop(conn, None)
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""Durable evidence for cost facts whose model lacked a direct rate card."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import datetime as dt
|
|
6
|
+
import hashlib
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _pricing():
|
|
12
|
+
"""The pricing kernel the cost engine prices with, resolved per call.
|
|
13
|
+
|
|
14
|
+
A module-top binding pins whichever `_lib_pricing` object existed when this
|
|
15
|
+
module first loaded. A test loader that replaces that entry would then
|
|
16
|
+
leave this evidence describing a different rate table from the one the
|
|
17
|
+
cost it annotates was computed with.
|
|
18
|
+
"""
|
|
19
|
+
import _lib_pricing
|
|
20
|
+
return _lib_pricing
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def codex_cost_with_provenance(entries, *, end, speed):
|
|
24
|
+
"""Price one retained Codex population and fingerprint its exact inputs."""
|
|
25
|
+
_lib_pricing = _pricing()
|
|
26
|
+
total = 0.0
|
|
27
|
+
fallback_cost = 0.0
|
|
28
|
+
model_costs = {}
|
|
29
|
+
source = []
|
|
30
|
+
fallback = set()
|
|
31
|
+
for entry in entries:
|
|
32
|
+
if entry.timestamp >= end: # budgets use a half-open period
|
|
33
|
+
continue
|
|
34
|
+
item = [
|
|
35
|
+
entry.timestamp.isoformat(), entry.source_path, entry.session_id,
|
|
36
|
+
entry.model, entry.input_tokens, entry.cached_input_tokens,
|
|
37
|
+
entry.output_tokens, entry.reasoning_output_tokens,
|
|
38
|
+
]
|
|
39
|
+
source.append(item)
|
|
40
|
+
used_fallback = (
|
|
41
|
+
entry.input_tokens or entry.output_tokens
|
|
42
|
+
) and _lib_pricing._is_codex_fallback(entry.model)
|
|
43
|
+
if used_fallback:
|
|
44
|
+
fallback.add(entry.model)
|
|
45
|
+
cost = _lib_pricing._calculate_codex_entry_cost(
|
|
46
|
+
entry.model, entry.input_tokens, entry.cached_input_tokens,
|
|
47
|
+
entry.output_tokens, entry.reasoning_output_tokens, speed=speed,
|
|
48
|
+
)
|
|
49
|
+
total += cost
|
|
50
|
+
model_costs[entry.model] = model_costs.get(entry.model, 0.0) + cost
|
|
51
|
+
if used_fallback:
|
|
52
|
+
fallback_cost += cost
|
|
53
|
+
encoded = json.dumps(
|
|
54
|
+
sorted(source), sort_keys=True, separators=(",", ":"),
|
|
55
|
+
ensure_ascii=False,
|
|
56
|
+
).encode("utf-8")
|
|
57
|
+
evidence = {
|
|
58
|
+
"version": 1,
|
|
59
|
+
"pricingDate": _lib_pricing.current_pricing_snapshot().snapshot_date,
|
|
60
|
+
"fallbackModels": sorted(fallback),
|
|
61
|
+
"sourceHash": "sha256:" + hashlib.sha256(encoded).hexdigest(),
|
|
62
|
+
"entryCount": len(source),
|
|
63
|
+
"speed": speed,
|
|
64
|
+
"directCostUsd": total - fallback_cost,
|
|
65
|
+
"modelCostsUsd": model_costs,
|
|
66
|
+
}
|
|
67
|
+
return total, evidence
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
# Historical Claude five-hour closes (#869)
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
#: The ``basis`` of a close whose missing card was proven AFTER it closed, from
|
|
75
|
+
#: its retained entries, rather than recorded while its cost was computed. The
|
|
76
|
+
#: marker freezes the corrected close against every later card revision.
|
|
77
|
+
HISTORICAL_CLOSE_BASIS = "historical-zero-cost-inference"
|
|
78
|
+
|
|
79
|
+
#: The ``basis`` of a close corrected from the missing-card state its own cost
|
|
80
|
+
#: computation recorded. Without it the corrected marker is indistinguishable
|
|
81
|
+
#: from a close that was priced all along, and the plan could not freeze the
|
|
82
|
+
#: corrected close's five-hour milestones with it.
|
|
83
|
+
RECORDED_CLOSE_CORRECTION_BASIS = "computation-time-missing-card"
|
|
84
|
+
|
|
85
|
+
_BLOCK_TOKEN_KEYS = (
|
|
86
|
+
"input_tokens", "output_tokens", "cache_create_tokens", "cache_read_tokens",
|
|
87
|
+
)
|
|
88
|
+
_CLAUDE_BASE_RATES = (
|
|
89
|
+
"input_cost_per_token",
|
|
90
|
+
"output_cost_per_token",
|
|
91
|
+
"cache_creation_input_token_cost",
|
|
92
|
+
"cache_read_input_token_cost",
|
|
93
|
+
)
|
|
94
|
+
_CLAUDE_TIER_RATES = (
|
|
95
|
+
"input_cost_per_token_above_200k_tokens",
|
|
96
|
+
"output_cost_per_token_above_200k_tokens",
|
|
97
|
+
"cache_creation_input_token_cost_above_200k_tokens",
|
|
98
|
+
"cache_read_input_token_cost_above_200k_tokens",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _positive_rate(value) -> bool:
|
|
103
|
+
return (
|
|
104
|
+
isinstance(value, (int, float))
|
|
105
|
+
and not isinstance(value, bool)
|
|
106
|
+
and math.isfinite(value)
|
|
107
|
+
and value > 0
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _zero_amount(value) -> bool:
|
|
112
|
+
return (
|
|
113
|
+
isinstance(value, (int, float))
|
|
114
|
+
and not isinstance(value, bool)
|
|
115
|
+
and value == 0
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def claude_card_rate_violations(snapshot) -> list:
|
|
120
|
+
"""Every ``(model, rate)`` a zero-cost inference would silently rely on.
|
|
121
|
+
|
|
122
|
+
A closed amount of exactly $0 proves a missing card only while no Claude
|
|
123
|
+
card prices a used token class at zero. Base rates, any present long-
|
|
124
|
+
context tier, the derived 1-hour cache-write rate and any Fast multiplier
|
|
125
|
+
must all be positive; an empty list is that invariant.
|
|
126
|
+
"""
|
|
127
|
+
_lib_pricing = _pricing()
|
|
128
|
+
out = []
|
|
129
|
+
multiplier = snapshot.cache_write_1h_multiplier
|
|
130
|
+
fast = snapshot.fast_multipliers.get("claude") or {}
|
|
131
|
+
for model, card in sorted(snapshot.claude_pricing.items()):
|
|
132
|
+
for field in _CLAUDE_BASE_RATES:
|
|
133
|
+
if not _positive_rate(card.get(field)):
|
|
134
|
+
out.append((model, field))
|
|
135
|
+
for field in _CLAUDE_TIER_RATES:
|
|
136
|
+
if field in card and not _positive_rate(card[field]):
|
|
137
|
+
out.append((model, field))
|
|
138
|
+
if not (_positive_rate(multiplier)
|
|
139
|
+
and _positive_rate(card.get("input_cost_per_token"))):
|
|
140
|
+
out.append((model, "cache_write_1h"))
|
|
141
|
+
tier_input = card.get("input_cost_per_token_above_200k_tokens")
|
|
142
|
+
if tier_input is not None and not (
|
|
143
|
+
_positive_rate(multiplier) and _positive_rate(tier_input)
|
|
144
|
+
):
|
|
145
|
+
out.append((model, "cache_write_1h_above_200k_tokens"))
|
|
146
|
+
stripped = _lib_pricing._strip_anthropic_model_prefix(model)
|
|
147
|
+
if stripped in fast and not _positive_rate(fast[stripped]):
|
|
148
|
+
out.append((model, "fast_multiplier"))
|
|
149
|
+
return out
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def claude_entry_rates_positive(card, entry, snapshot) -> bool:
|
|
153
|
+
"""True iff every rate that prices one of ``entry``'s tokens is positive.
|
|
154
|
+
|
|
155
|
+
Mirrors `_lib_pricing._calculate_entry_cost` term by term: each token class
|
|
156
|
+
is priced below the long-context threshold at its base rate and above it at
|
|
157
|
+
a present tier rate, and a known 1-hour cache-write split prices its share
|
|
158
|
+
at the derived input-times-multiplier rate.
|
|
159
|
+
"""
|
|
160
|
+
if card is None:
|
|
161
|
+
return False
|
|
162
|
+
_lib_pricing = _pricing()
|
|
163
|
+
threshold = snapshot.tier_thresholds["claude"]
|
|
164
|
+
|
|
165
|
+
def tiered(tokens, base_key, tier_key):
|
|
166
|
+
if tokens <= 0:
|
|
167
|
+
return True
|
|
168
|
+
if not _positive_rate(card.get(base_key)):
|
|
169
|
+
return False
|
|
170
|
+
tier = card.get(tier_key)
|
|
171
|
+
if tokens > threshold and tier is not None:
|
|
172
|
+
return _positive_rate(tier)
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
if not (
|
|
176
|
+
tiered(int(entry.input_tokens or 0), "input_cost_per_token",
|
|
177
|
+
"input_cost_per_token_above_200k_tokens")
|
|
178
|
+
and tiered(int(entry.output_tokens or 0), "output_cost_per_token",
|
|
179
|
+
"output_cost_per_token_above_200k_tokens")
|
|
180
|
+
and tiered(int(entry.cache_read_tokens or 0),
|
|
181
|
+
"cache_read_input_token_cost",
|
|
182
|
+
"cache_read_input_token_cost_above_200k_tokens")
|
|
183
|
+
):
|
|
184
|
+
return False
|
|
185
|
+
flat = int(entry.cache_creation_tokens or 0)
|
|
186
|
+
if flat > 0:
|
|
187
|
+
raw_1h = getattr(entry, "cache_1h_tokens", None)
|
|
188
|
+
hour = 0 if raw_1h is None else max(0, min(int(raw_1h), flat))
|
|
189
|
+
if hour == 0:
|
|
190
|
+
if not tiered(flat, "cache_creation_input_token_cost",
|
|
191
|
+
"cache_creation_input_token_cost_above_200k_tokens"):
|
|
192
|
+
return False
|
|
193
|
+
else:
|
|
194
|
+
multiplier = snapshot.cache_write_1h_multiplier
|
|
195
|
+
base = card.get("input_cost_per_token", 0.0)
|
|
196
|
+
rate_5m = card.get("cache_creation_input_token_cost", 0.0)
|
|
197
|
+
below = min(flat, threshold)
|
|
198
|
+
above = max(0, flat - threshold)
|
|
199
|
+
terms = (
|
|
200
|
+
(below and hour, _positive_rate(multiplier)
|
|
201
|
+
and _positive_rate(base)),
|
|
202
|
+
(above and hour, _positive_rate(multiplier) and _positive_rate(
|
|
203
|
+
card.get("input_cost_per_token_above_200k_tokens", base))),
|
|
204
|
+
(below and flat - hour, _positive_rate(rate_5m)),
|
|
205
|
+
(above and flat - hour, _positive_rate(card.get(
|
|
206
|
+
"cache_creation_input_token_cost_above_200k_tokens",
|
|
207
|
+
rate_5m))),
|
|
208
|
+
)
|
|
209
|
+
if any(quantity and not positive for quantity, positive in terms):
|
|
210
|
+
return False
|
|
211
|
+
if getattr(entry, "speed", None) == "fast":
|
|
212
|
+
fast = (snapshot.fast_multipliers.get("claude") or {}).get(
|
|
213
|
+
_lib_pricing._strip_anthropic_model_prefix(entry.model), 1.0)
|
|
214
|
+
if not _positive_rate(fast):
|
|
215
|
+
return False
|
|
216
|
+
return True
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def historical_close_marker(close_payload, owned_entries, *, unattributed):
|
|
220
|
+
"""The correction marker for an unmarked zero-cost close, or None.
|
|
221
|
+
|
|
222
|
+
The close must carry no computation-time marker, total exactly $0, and have
|
|
223
|
+
every positive-token model child at exactly $0; one priced model makes the
|
|
224
|
+
whole close unprovable, because its project children have no model axis.
|
|
225
|
+
``owned_entries`` are the retained entries the current ownership rule
|
|
226
|
+
assigns to this close. Each one must belong to the close's account, a
|
|
227
|
+
positive-token model's entries must carry no provider cost, and today's
|
|
228
|
+
card for that model must price every token class the entry used at a
|
|
229
|
+
positive rate. The caller still compares the replayed child populations.
|
|
230
|
+
"""
|
|
231
|
+
if (
|
|
232
|
+
close_payload.get("_pricing")
|
|
233
|
+
or close_payload.get("pricing_provenance_json") is not None
|
|
234
|
+
or close_payload.get("is_closed") != 1
|
|
235
|
+
or not _zero_amount(close_payload.get("total_cost_usd"))
|
|
236
|
+
or owned_entries is None
|
|
237
|
+
):
|
|
238
|
+
return None
|
|
239
|
+
inferred = set()
|
|
240
|
+
for child in close_payload.get("_models") or []:
|
|
241
|
+
if any(int(child.get(key) or 0) > 0 for key in _BLOCK_TOKEN_KEYS):
|
|
242
|
+
model = child.get("model")
|
|
243
|
+
if not isinstance(model, str) or not model:
|
|
244
|
+
return None
|
|
245
|
+
if not _zero_amount(child.get("cost_usd")):
|
|
246
|
+
return None
|
|
247
|
+
inferred.add(model)
|
|
248
|
+
if not inferred or any(
|
|
249
|
+
not _zero_amount(child.get("cost_usd"))
|
|
250
|
+
for child in close_payload.get("_projects") or []
|
|
251
|
+
):
|
|
252
|
+
return None
|
|
253
|
+
account = close_payload.get("account_key") or unattributed
|
|
254
|
+
_lib_pricing = _pricing()
|
|
255
|
+
snapshot = _lib_pricing.current_pricing_snapshot()
|
|
256
|
+
source = []
|
|
257
|
+
priced_models = set()
|
|
258
|
+
for entry in owned_entries:
|
|
259
|
+
offset = getattr(entry, "source_line_offset", None)
|
|
260
|
+
if (getattr(entry, "source_account_key", None) or unattributed) != account:
|
|
261
|
+
return None
|
|
262
|
+
if offset is None:
|
|
263
|
+
return None
|
|
264
|
+
positive = any((
|
|
265
|
+
entry.input_tokens, entry.output_tokens,
|
|
266
|
+
entry.cache_creation_tokens, entry.cache_read_tokens,
|
|
267
|
+
))
|
|
268
|
+
if entry.model in inferred:
|
|
269
|
+
if entry.cost_usd is not None:
|
|
270
|
+
return None
|
|
271
|
+
if positive and not claude_entry_rates_positive(
|
|
272
|
+
_lib_pricing._resolve_model_pricing(entry.model, warn=False),
|
|
273
|
+
entry, snapshot,
|
|
274
|
+
):
|
|
275
|
+
return None
|
|
276
|
+
if positive:
|
|
277
|
+
priced_models.add(entry.model)
|
|
278
|
+
elif positive:
|
|
279
|
+
return None
|
|
280
|
+
source.append([
|
|
281
|
+
entry.timestamp.astimezone(dt.timezone.utc).isoformat(),
|
|
282
|
+
entry.source_path, int(offset), entry.model,
|
|
283
|
+
int(entry.input_tokens or 0), int(entry.output_tokens or 0),
|
|
284
|
+
int(entry.cache_creation_tokens or 0),
|
|
285
|
+
getattr(entry, "cache_1h_tokens", None),
|
|
286
|
+
int(entry.cache_read_tokens or 0),
|
|
287
|
+
getattr(entry, "speed", None),
|
|
288
|
+
])
|
|
289
|
+
if priced_models != inferred:
|
|
290
|
+
return None
|
|
291
|
+
encoded = json.dumps(
|
|
292
|
+
sorted(source, key=lambda item: json.dumps(item)),
|
|
293
|
+
sort_keys=True, separators=(",", ":"), ensure_ascii=False,
|
|
294
|
+
).encode("utf-8")
|
|
295
|
+
return {
|
|
296
|
+
"version": 1,
|
|
297
|
+
"basis": HISTORICAL_CLOSE_BASIS,
|
|
298
|
+
"inferredModels": sorted(inferred),
|
|
299
|
+
"pricingDate": snapshot.snapshot_date,
|
|
300
|
+
"sourceHash": "sha256:" + hashlib.sha256(encoded).hexdigest(),
|
|
301
|
+
"entryCount": len(source),
|
|
302
|
+
"unpricedModels": [],
|
|
303
|
+
}
|