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
package/bin/_cctally_rederive.py
CHANGED
|
@@ -14,6 +14,7 @@ import datetime as dt
|
|
|
14
14
|
import fcntl
|
|
15
15
|
import hashlib
|
|
16
16
|
import json
|
|
17
|
+
import math
|
|
17
18
|
import os
|
|
18
19
|
import shutil
|
|
19
20
|
import signal
|
|
@@ -102,7 +103,7 @@ def _cache_contract(cache_conn: sqlite3.Connection) -> dict[str, set[str]]:
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
|
|
105
|
-
def _cache_fingerprint(cache_conn: sqlite3.Connection) -> str:
|
|
106
|
+
def _cache_fingerprint(cache_conn: sqlite3.Connection, *, include_codex=False) -> str:
|
|
106
107
|
"""Hash every cost-bearing Claude cache row and its project metadata."""
|
|
107
108
|
entry_columns = (
|
|
108
109
|
"source_path", "line_offset", "timestamp_utc", "model", "input_tokens",
|
|
@@ -120,7 +121,276 @@ def _cache_fingerprint(cache_conn: sqlite3.Connection) -> str:
|
|
|
120
121
|
"SELECT " + ",".join(file_columns)
|
|
121
122
|
+ " FROM session_files ORDER BY path")
|
|
122
123
|
]
|
|
123
|
-
|
|
124
|
+
basis = {"sessionEntries": entries, "sessionFiles": files}
|
|
125
|
+
if include_codex:
|
|
126
|
+
if "codex_session_entries" not in _cache_contract(cache_conn):
|
|
127
|
+
raise _lib_rederive.RederiveDataGap(
|
|
128
|
+
"missing cache.db table codex_session_entries"
|
|
129
|
+
)
|
|
130
|
+
basis["codexEntries"] = [
|
|
131
|
+
list(row) for row in cache_conn.execute(
|
|
132
|
+
"SELECT source_path,line_offset,timestamp_utc,session_id,model,"
|
|
133
|
+
"input_tokens,cached_input_tokens,output_tokens,"
|
|
134
|
+
"reasoning_output_tokens,account_key "
|
|
135
|
+
"FROM codex_session_entries ORDER BY source_path,line_offset"
|
|
136
|
+
)
|
|
137
|
+
]
|
|
138
|
+
return _fingerprint(basis)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _split_matches(parts, total) -> bool:
|
|
142
|
+
"""A recorded cost split agrees with its total up to summation order.
|
|
143
|
+
|
|
144
|
+
The at-fire writer sums one interleaved entry stream into the total and
|
|
145
|
+
into per-model buckets, and the two association orders round differently
|
|
146
|
+
as spend grows. `fsum` removes the split's own rounding; the relative bound
|
|
147
|
+
absorbs the total's, while a material mismatch still fails (#869 F8).
|
|
148
|
+
"""
|
|
149
|
+
return math.isclose(
|
|
150
|
+
math.fsum(parts), total, rel_tol=1e-10, abs_tol=1e-9,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _desired_codex_budget_costs(selection, cache_conn, *, uncertain=None):
|
|
155
|
+
"""Correct only journaled fallback facts with an unchanged source set.
|
|
156
|
+
|
|
157
|
+
``uncertain`` collects the ids of crossings kept unchanged because their
|
|
158
|
+
retained source population no longer matches the recorded one.
|
|
159
|
+
"""
|
|
160
|
+
import _cctally_cache
|
|
161
|
+
import _lib_cost_provenance
|
|
162
|
+
import _lib_pricing
|
|
163
|
+
out = []
|
|
164
|
+
for selected in selection.by_id.values():
|
|
165
|
+
record = selected.record
|
|
166
|
+
if selected.status != "active" or record is None:
|
|
167
|
+
continue
|
|
168
|
+
payload = record.get("payload") or {}
|
|
169
|
+
if payload.get("kind") != "budget" or payload.get("vendor") != "codex":
|
|
170
|
+
continue
|
|
171
|
+
old = payload.get("_pricing") or {}
|
|
172
|
+
if old.get("version") != 1:
|
|
173
|
+
continue # legacy evidence is insufficient for automatic repricing
|
|
174
|
+
missing = old.get("fallbackModels") or []
|
|
175
|
+
if not missing:
|
|
176
|
+
out.append(record) # a genuinely priced at-fire fact is immutable
|
|
177
|
+
continue
|
|
178
|
+
old_model_costs = old.get("modelCostsUsd")
|
|
179
|
+
spent_value = payload.get("spent_usd")
|
|
180
|
+
if (
|
|
181
|
+
not isinstance(missing, list)
|
|
182
|
+
or not missing
|
|
183
|
+
or any(not isinstance(model, str) or not model for model in missing)
|
|
184
|
+
or len(set(missing)) != len(missing)
|
|
185
|
+
or not isinstance(old_model_costs, dict)
|
|
186
|
+
or any(
|
|
187
|
+
not isinstance(value, (int, float))
|
|
188
|
+
or not math.isfinite(value) or value < 0
|
|
189
|
+
for value in old_model_costs.values()
|
|
190
|
+
)
|
|
191
|
+
or any(model not in old_model_costs for model in missing)
|
|
192
|
+
or not isinstance(old.get("directCostUsd"), (int, float))
|
|
193
|
+
or not math.isfinite(old["directCostUsd"])
|
|
194
|
+
or old["directCostUsd"] < 0
|
|
195
|
+
or old.get("speed") not in ("standard", "fast")
|
|
196
|
+
or not isinstance(spent_value, (int, float))
|
|
197
|
+
or not math.isfinite(spent_value)
|
|
198
|
+
or not _split_matches(old_model_costs.values(), spent_value)
|
|
199
|
+
or not _split_matches(
|
|
200
|
+
[old["directCostUsd"],
|
|
201
|
+
*(old_model_costs[model] for model in missing)],
|
|
202
|
+
spent_value,
|
|
203
|
+
)
|
|
204
|
+
):
|
|
205
|
+
raise _lib_rederive.RederiveDataGap(
|
|
206
|
+
f"Codex budget {record['id']} has incomplete at-fire "
|
|
207
|
+
"pricing provenance"
|
|
208
|
+
)
|
|
209
|
+
if any(_lib_pricing._is_codex_fallback(model) for model in missing):
|
|
210
|
+
# #869 F1: a direct card has not shipped for every fallback model,
|
|
211
|
+
# so nothing about this crossing can change yet. Decided before
|
|
212
|
+
# the population comparison: drift in a fact this run cannot
|
|
213
|
+
# correct must not refuse every other correction in the family.
|
|
214
|
+
out.append(record)
|
|
215
|
+
continue
|
|
216
|
+
try:
|
|
217
|
+
start = dt.datetime.fromisoformat(
|
|
218
|
+
payload["period_start_at"].replace("Z", "+00:00")
|
|
219
|
+
)
|
|
220
|
+
end = dt.datetime.fromisoformat(
|
|
221
|
+
payload["crossed_at_utc"].replace("Z", "+00:00")
|
|
222
|
+
)
|
|
223
|
+
if start.tzinfo is None or end.tzinfo is None or start >= end:
|
|
224
|
+
raise ValueError("invalid budget cost bounds")
|
|
225
|
+
speed = old["speed"]
|
|
226
|
+
threshold = float(payload["threshold"])
|
|
227
|
+
target = float(payload["budget_usd"])
|
|
228
|
+
scope = payload.get("account_key")
|
|
229
|
+
if scope == "*":
|
|
230
|
+
scope = None
|
|
231
|
+
source = _cctally_cache.iter_codex_entries(
|
|
232
|
+
cache_conn, start, end, account_key=scope,
|
|
233
|
+
)
|
|
234
|
+
cost, newer = _lib_cost_provenance.codex_cost_with_provenance(
|
|
235
|
+
source, end=end, speed=speed,
|
|
236
|
+
)
|
|
237
|
+
except (KeyError, TypeError, ValueError, sqlite3.DatabaseError) as exc:
|
|
238
|
+
raise _lib_rederive.RederiveDataGap(
|
|
239
|
+
f"Codex budget {record['id']} lacks retained cost inputs: {exc}"
|
|
240
|
+
) from exc
|
|
241
|
+
if (
|
|
242
|
+
newer["sourceHash"] != old.get("sourceHash")
|
|
243
|
+
or newer["entryCount"] != old.get("entryCount")
|
|
244
|
+
):
|
|
245
|
+
# A changed population cannot license a new amount, but it does
|
|
246
|
+
# not invalidate the recorded crossing either: keep it exactly as
|
|
247
|
+
# fired and disclose it (#869 F1/Q2).
|
|
248
|
+
if uncertain is not None:
|
|
249
|
+
uncertain.add(record["id"])
|
|
250
|
+
out.append(record)
|
|
251
|
+
continue
|
|
252
|
+
if any(model not in newer["modelCostsUsd"] for model in missing):
|
|
253
|
+
raise _lib_rederive.RederiveDataGap(
|
|
254
|
+
f"Codex budget {record['id']} lacks a complete at-fire "
|
|
255
|
+
"priced-cost split"
|
|
256
|
+
)
|
|
257
|
+
if any(
|
|
258
|
+
model in newer["fallbackModels"]
|
|
259
|
+
for model in old_model_costs if model not in missing
|
|
260
|
+
):
|
|
261
|
+
# #869 V5: a model priced by a direct card at fire has lost that
|
|
262
|
+
# card, so this run cannot price the crossing truthfully. That is
|
|
263
|
+
# this crossing's gap alone: keep it exactly as fired and disclose
|
|
264
|
+
# it, rather than refusing every other correction in the family.
|
|
265
|
+
if uncertain is not None:
|
|
266
|
+
uncertain.add(record["id"])
|
|
267
|
+
out.append(record)
|
|
268
|
+
continue
|
|
269
|
+
# Only the fallback-priced models change. The direct-card contribution
|
|
270
|
+
# is a real at-fire fact even when those cards are revised later.
|
|
271
|
+
cost = math.fsum([
|
|
272
|
+
float(old["directCostUsd"]),
|
|
273
|
+
*(newer["modelCostsUsd"][model] for model in missing),
|
|
274
|
+
])
|
|
275
|
+
consumption_pct = cost / target * 100.0 if target > 0 else 0.0
|
|
276
|
+
if consumption_pct + 1e-9 < threshold:
|
|
277
|
+
# #869 F6: the fallback overpriced this crossing and the real card
|
|
278
|
+
# puts it below its threshold. Omitting it from the desired set
|
|
279
|
+
# tombstones it in the correction batch, so the natural key is
|
|
280
|
+
# free for a genuine later crossing (which re-fires under a new
|
|
281
|
+
# incarnation id, `bm2:`).
|
|
282
|
+
continue
|
|
283
|
+
revised = copy.deepcopy(record)
|
|
284
|
+
revised_payload = revised["payload"]
|
|
285
|
+
revised_payload["spent_usd"] = cost
|
|
286
|
+
revised_payload["consumption_pct"] = consumption_pct
|
|
287
|
+
revised_pricing = copy.deepcopy(newer)
|
|
288
|
+
revised_pricing["modelCostsUsd"] = {
|
|
289
|
+
model: (
|
|
290
|
+
newer["modelCostsUsd"][model] if model in missing else value
|
|
291
|
+
)
|
|
292
|
+
for model, value in old_model_costs.items()
|
|
293
|
+
}
|
|
294
|
+
revised_pricing["directCostUsd"] = cost
|
|
295
|
+
revised_payload["_pricing"] = revised_pricing
|
|
296
|
+
out.append(revised)
|
|
297
|
+
return out
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _historical_close_candidates(selection) -> set:
|
|
301
|
+
"""(account_key, window key) of each unmarked close with a $0 model."""
|
|
302
|
+
import _lib_accounts
|
|
303
|
+
|
|
304
|
+
out = set()
|
|
305
|
+
for selected in selection.by_id.values():
|
|
306
|
+
record = selected.record
|
|
307
|
+
if selected.status != "active" or record is None:
|
|
308
|
+
continue
|
|
309
|
+
payload = record.get("payload") or {}
|
|
310
|
+
if (
|
|
311
|
+
payload.get("kind") != "five_hour_block_close"
|
|
312
|
+
or payload.get("_pricing")
|
|
313
|
+
or payload.get("pricing_provenance_json") is not None
|
|
314
|
+
or not _lib_rederive.close_cost_is_uncertain(payload)
|
|
315
|
+
):
|
|
316
|
+
continue
|
|
317
|
+
try:
|
|
318
|
+
out.add((
|
|
319
|
+
payload.get("account_key") or _lib_accounts.UNATTRIBUTED,
|
|
320
|
+
int(payload["five_hour_window_key"]),
|
|
321
|
+
))
|
|
322
|
+
except (KeyError, TypeError, ValueError):
|
|
323
|
+
continue
|
|
324
|
+
return out
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _historical_close_markers(selection, desired_events, block_sources):
|
|
328
|
+
"""event id -> correction marker for each provable historical close (#869).
|
|
329
|
+
|
|
330
|
+
An unmarked close predates computation-time pricing evidence. It is
|
|
331
|
+
provable only from the retained entries the scratch replay assigned to the
|
|
332
|
+
same block, with the same account, no provider cost, and a positive current
|
|
333
|
+
card rate for every token class those entries used. The planner still
|
|
334
|
+
compares the replayed child populations before adopting the correction.
|
|
335
|
+
"""
|
|
336
|
+
import _lib_accounts
|
|
337
|
+
import _lib_cost_provenance
|
|
338
|
+
|
|
339
|
+
desired_closes = {
|
|
340
|
+
event.get("id"): event for event in desired_events
|
|
341
|
+
if (event.get("payload") or {}).get("kind") == "five_hour_block_close"
|
|
342
|
+
}
|
|
343
|
+
markers = {}
|
|
344
|
+
for event_id, selected in selection.by_id.items():
|
|
345
|
+
record = selected.record
|
|
346
|
+
if (
|
|
347
|
+
selected.status != "active"
|
|
348
|
+
or record is None
|
|
349
|
+
or event_id not in desired_closes
|
|
350
|
+
):
|
|
351
|
+
continue
|
|
352
|
+
payload = record.get("payload") or {}
|
|
353
|
+
if payload.get("kind") != "five_hour_block_close":
|
|
354
|
+
continue
|
|
355
|
+
desired = desired_closes[event_id].get("payload") or {}
|
|
356
|
+
key = (
|
|
357
|
+
desired.get("account_key") or _lib_accounts.UNATTRIBUTED,
|
|
358
|
+
desired.get("five_hour_window_key"),
|
|
359
|
+
)
|
|
360
|
+
try:
|
|
361
|
+
key = (key[0], int(key[1]))
|
|
362
|
+
except (TypeError, ValueError):
|
|
363
|
+
continue
|
|
364
|
+
marker = _lib_cost_provenance.historical_close_marker(
|
|
365
|
+
payload, block_sources.get(key),
|
|
366
|
+
unattributed=_lib_accounts.UNATTRIBUTED,
|
|
367
|
+
)
|
|
368
|
+
if marker is not None:
|
|
369
|
+
markers[event_id] = marker
|
|
370
|
+
return markers
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def _uncertain_cost_facts(selection) -> set:
|
|
374
|
+
"""Ids of retained Codex monetary events lacking proof of their inputs.
|
|
375
|
+
|
|
376
|
+
Closed Claude blocks are judged by the plan itself, because whether one
|
|
377
|
+
stays uncertain depends on the correction this plan makes to it.
|
|
378
|
+
"""
|
|
379
|
+
out = set()
|
|
380
|
+
for event_id, selected in selection.by_id.items():
|
|
381
|
+
if selected.status != "active" or selected.record is None:
|
|
382
|
+
continue
|
|
383
|
+
payload = selected.record.get("payload") or {}
|
|
384
|
+
kind = payload.get("kind")
|
|
385
|
+
if (kind == "budget" and payload.get("vendor") == "codex"
|
|
386
|
+
and "_pricing" not in payload):
|
|
387
|
+
out.add(event_id)
|
|
388
|
+
elif (kind == "projected"
|
|
389
|
+
and payload.get("metric") == "codex_budget_usd"):
|
|
390
|
+
# Historical projection inputs/config are not retained, so this
|
|
391
|
+
# at-fire alert value cannot be recomputed from a new rate card.
|
|
392
|
+
out.add(event_id)
|
|
393
|
+
return out
|
|
124
394
|
|
|
125
395
|
|
|
126
396
|
def _validate_cache_rows(cache_conn: sqlite3.Connection,
|
|
@@ -180,7 +450,7 @@ def _joined_entries(cache_conn, range_start, range_end, *,
|
|
|
180
450
|
"SELECT se.timestamp_utc, se.model, se.input_tokens, se.output_tokens, "
|
|
181
451
|
"se.cache_create_tokens, se.cache_read_tokens, se.source_path, "
|
|
182
452
|
"sf.session_id, sf.project_path, se.cost_usd_raw, se.speed, "
|
|
183
|
-
"se.cache_create_1h_tokens "
|
|
453
|
+
"se.cache_create_1h_tokens, se.line_offset, se.account_key "
|
|
184
454
|
"FROM session_entries se "
|
|
185
455
|
"LEFT JOIN session_files sf ON sf.path = se.source_path "
|
|
186
456
|
"WHERE se.timestamp_utc >= ? AND se.timestamp_utc <= ?"
|
|
@@ -203,7 +473,7 @@ def _joined_entries(cache_conn, range_start, range_end, *,
|
|
|
203
473
|
sql += " ORDER BY se.timestamp_utc ASC, se.id ASC"
|
|
204
474
|
out = []
|
|
205
475
|
for row in cache_conn.execute(sql, params):
|
|
206
|
-
|
|
476
|
+
entry = cache._JoinedClaudeEntry(
|
|
207
477
|
timestamp=dt.datetime.fromisoformat(row[0]),
|
|
208
478
|
model=row[1],
|
|
209
479
|
input_tokens=int(row[2] or 0),
|
|
@@ -216,7 +486,13 @@ def _joined_entries(cache_conn, range_start, range_end, *,
|
|
|
216
486
|
cost_usd=row[9],
|
|
217
487
|
usage_extra=({"speed": row[10]} if row[10] else None),
|
|
218
488
|
cache_1h_tokens=(None if row[11] is None else int(row[11])),
|
|
219
|
-
)
|
|
489
|
+
)
|
|
490
|
+
# #869: the retained source identity and account of the entry. The
|
|
491
|
+
# cost path never reads these; the historical-close proof fingerprints
|
|
492
|
+
# the exact population and requires every entry to be the close's.
|
|
493
|
+
entry.source_line_offset = row[12]
|
|
494
|
+
entry.source_account_key = row[13]
|
|
495
|
+
out.append(entry)
|
|
220
496
|
return out
|
|
221
497
|
|
|
222
498
|
|
|
@@ -691,7 +967,8 @@ def _normalize_legacy_accounts(records: list[dict]) -> None:
|
|
|
691
967
|
|
|
692
968
|
def _derive_desired_events(records: list[dict], cache_conn: sqlite3.Connection,
|
|
693
969
|
scratch_dir: Path, *,
|
|
694
|
-
held_weekly_observation_ids=frozenset()
|
|
970
|
+
held_weekly_observation_ids=frozenset(),
|
|
971
|
+
block_sources: "dict | None" = None) -> list[dict]:
|
|
695
972
|
# #386: this replays the whole ingest pipeline into a PRIVATE scratch index
|
|
696
973
|
# and is reached from `db rederive`'s PREVIEW, which takes no locks by
|
|
697
974
|
# design (its contract is zero persistent writes to the live family). The
|
|
@@ -705,12 +982,14 @@ def _derive_desired_events(records: list[dict], cache_conn: sqlite3.Connection,
|
|
|
705
982
|
with _cctally_store.stats_write_scope("rederive-derive"):
|
|
706
983
|
return _derive_desired_events_into(
|
|
707
984
|
records, cache_conn, str(scratch_path),
|
|
708
|
-
held_weekly_observation_ids=held_weekly_observation_ids
|
|
985
|
+
held_weekly_observation_ids=held_weekly_observation_ids,
|
|
986
|
+
block_sources=block_sources)
|
|
709
987
|
|
|
710
988
|
|
|
711
989
|
def _derive_desired_events_into(
|
|
712
990
|
records, cache_conn, scratch_path, *,
|
|
713
991
|
held_weekly_observation_ids=frozenset(),
|
|
992
|
+
block_sources: "dict | None" = None,
|
|
714
993
|
) -> list[dict]:
|
|
715
994
|
raw_records = _rederivable_raw_records(records)
|
|
716
995
|
held_ids = frozenset(held_weekly_observation_ids)
|
|
@@ -747,6 +1026,7 @@ def _derive_desired_events_into(
|
|
|
747
1026
|
held_weekly_observation_ids=held_ids,
|
|
748
1027
|
reviewed_weekly_basis_by_account=(
|
|
749
1028
|
reviewed_weekly_basis_by_account),
|
|
1029
|
+
block_source_evidence=block_sources,
|
|
750
1030
|
)
|
|
751
1031
|
conn.execute("BEGIN IMMEDIATE")
|
|
752
1032
|
try:
|
|
@@ -811,7 +1091,15 @@ def plan_claude_usage(
|
|
|
811
1091
|
_lib_rederive.validate_claude_cache_contract(_cache_contract(cache_conn))
|
|
812
1092
|
raw_records = _rederivable_raw_records(records)
|
|
813
1093
|
_validate_cache_rows(cache_conn, raw_records)
|
|
814
|
-
|
|
1094
|
+
codex_pricing_records = any(
|
|
1095
|
+
(record.get("payload") or {}).get("vendor") == "codex"
|
|
1096
|
+
and (record.get("payload") or {}).get("kind") == "budget"
|
|
1097
|
+
and (record.get("payload") or {}).get("_pricing")
|
|
1098
|
+
for record in records
|
|
1099
|
+
)
|
|
1100
|
+
cache_fingerprint = _cache_fingerprint(
|
|
1101
|
+
cache_conn, include_codex=codex_pricing_records,
|
|
1102
|
+
)
|
|
815
1103
|
held_ids, _accepted_ids, identity_pairs, reviewed_ops = (
|
|
816
1104
|
_reviewed_weekly_state(records)
|
|
817
1105
|
)
|
|
@@ -855,11 +1143,21 @@ def plan_claude_usage(
|
|
|
855
1143
|
raise _lib_rederive.RederiveConflict(
|
|
856
1144
|
"journal contains tainted correction batch(es): " + summary
|
|
857
1145
|
)
|
|
1146
|
+
# Only an unmarked close with a zero-cost model can need the historical
|
|
1147
|
+
# proof, so the scratch replay keeps retained entries for those blocks
|
|
1148
|
+
# alone rather than for every block in the journal.
|
|
1149
|
+
block_sources = dict.fromkeys(_historical_close_candidates(selection))
|
|
858
1150
|
with tempfile.TemporaryDirectory(prefix="cctally-rederive-") as tmp:
|
|
859
1151
|
desired = _derive_desired_events(
|
|
860
1152
|
records, cache_conn, Path(tmp),
|
|
861
1153
|
held_weekly_observation_ids=held_ids,
|
|
1154
|
+
block_sources=block_sources,
|
|
862
1155
|
)
|
|
1156
|
+
historical_markers = _historical_close_markers(
|
|
1157
|
+
selection, desired, block_sources)
|
|
1158
|
+
codex_uncertain: set = set()
|
|
1159
|
+
desired.extend(_desired_codex_budget_costs(
|
|
1160
|
+
selection, cache_conn, uncertain=codex_uncertain))
|
|
863
1161
|
# #426: the scratch replay only sees RETAINED sources, so it can never
|
|
864
1162
|
# reproduce the pre-cutover rows the journal exported as `b:<table>:<rowid>`
|
|
865
1163
|
# evt lines. Hold them out of the diff instead of retiring them. Evidence is
|
|
@@ -883,6 +1181,9 @@ def plan_claude_usage(
|
|
|
883
1181
|
raw_observations=raw_records,
|
|
884
1182
|
snapshot_identity_decisions=identity_pairs,
|
|
885
1183
|
reviewed_weekly_hold_ids=held_ids,
|
|
1184
|
+
uncertain_cost_fact_ids=(
|
|
1185
|
+
_uncertain_cost_facts(selection) | codex_uncertain),
|
|
1186
|
+
historical_close_markers=historical_markers,
|
|
886
1187
|
enforce_guard=enforce_guard,
|
|
887
1188
|
)
|
|
888
1189
|
|
|
@@ -1885,6 +2186,8 @@ def _command_payload(
|
|
|
1885
2186
|
}
|
|
1886
2187
|
if reviewed_decision_id is not None:
|
|
1887
2188
|
body["reviewedWeeklyDecisionId"] = reviewed_decision_id
|
|
2189
|
+
if plan is not None and plan.uncertain_cost_fact_count:
|
|
2190
|
+
body["uncertainCostFactCount"] = plan.uncertain_cost_fact_count
|
|
1888
2191
|
if preview is not None and preview.baseline_plan is not None:
|
|
1889
2192
|
body["baselinePlanHash"] = preview.baseline_plan.plan_hash
|
|
1890
2193
|
body["baselineActionCounts"] = dict(preview.baseline_plan.counts)
|
|
@@ -204,21 +204,33 @@ def _codex_alias_inventory_sql(identity_count: int) -> str:
|
|
|
204
204
|
)
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
def _sqlite_order_key(value: object) -> tuple:
|
|
208
|
+
"""One scalar's SQLite default ascending storage-class order."""
|
|
209
|
+
if value is None:
|
|
210
|
+
return (0, 0)
|
|
211
|
+
if isinstance(value, (int, float)):
|
|
212
|
+
return (1, value)
|
|
213
|
+
if isinstance(value, str):
|
|
214
|
+
return (2, value)
|
|
215
|
+
if isinstance(value, (bytes, bytearray, memoryview)):
|
|
216
|
+
return (3, bytes(value))
|
|
217
|
+
raise TypeError(f"unsupported SQLite value type: {type(value).__name__}")
|
|
218
|
+
|
|
219
|
+
|
|
207
220
|
def _alias_winner_sort_key(last_seen_utc: object, conversation_key: object) -> tuple:
|
|
208
221
|
"""Reproduce ``ORDER BY last_seen_utc DESC, conversation_key DESC``.
|
|
209
222
|
|
|
210
223
|
The retired statement ordered its joined rows that way and the qualifier
|
|
211
224
|
kept the FIRST row per ``(source_root_key, path)``, so the winner is the
|
|
212
|
-
maximum under this key. SQLite
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
225
|
+
maximum under this key. SQLite orders ``NULL < INTEGER/REAL < TEXT <
|
|
226
|
+
BLOB`` before comparing values inside a class. Both columns may carry any
|
|
227
|
+
storage class despite their declared affinity, so stringifying them changes
|
|
228
|
+
mixed-class winners. The pair below preserves ordinary TEXT ordering while
|
|
229
|
+
reproducing every storage-class boundary.
|
|
216
230
|
"""
|
|
217
|
-
has_last_seen = 0 if last_seen_utc is None else 1
|
|
218
231
|
return (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
str(conversation_key or ""),
|
|
232
|
+
_sqlite_order_key(last_seen_utc),
|
|
233
|
+
_sqlite_order_key(conversation_key),
|
|
222
234
|
)
|
|
223
235
|
|
|
224
236
|
|