cctally 1.110.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 +23 -0
- package/README.md +5 -6
- package/bin/_cctally_cache.py +14 -7
- package/bin/_cctally_core.py +12 -1
- package/bin/_cctally_dashboard.py +60 -154
- 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/_cctally_update.py +420 -14
- package/bin/_lib_cache_report.py +7 -10
- 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 +24 -263
- package/bin/_lib_rederive.py +704 -36
- package/bin/_lib_snapshot_cache.py +332 -9
- package/bin/cctally +18 -5
- 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/_lib_rederive.py
CHANGED
|
@@ -16,8 +16,13 @@ import dataclasses
|
|
|
16
16
|
import datetime as dt
|
|
17
17
|
import hashlib
|
|
18
18
|
import json
|
|
19
|
+
import math
|
|
19
20
|
from collections.abc import Iterable, Mapping
|
|
20
21
|
|
|
22
|
+
from _lib_cost_provenance import (
|
|
23
|
+
HISTORICAL_CLOSE_BASIS, RECORDED_CLOSE_CORRECTION_BASIS,
|
|
24
|
+
)
|
|
25
|
+
|
|
21
26
|
|
|
22
27
|
FAMILY = "claude-usage"
|
|
23
28
|
|
|
@@ -151,7 +156,11 @@ def _is_owned_event(record: Mapping) -> bool:
|
|
|
151
156
|
}:
|
|
152
157
|
return False
|
|
153
158
|
if kind == "budget":
|
|
154
|
-
return
|
|
159
|
+
return (
|
|
160
|
+
payload.get("vendor", "claude") == "claude"
|
|
161
|
+
or (payload.get("vendor") == "codex"
|
|
162
|
+
and (payload.get("_pricing") or {}).get("version") == 1)
|
|
163
|
+
)
|
|
155
164
|
if kind == "projected":
|
|
156
165
|
metric = str(payload.get("metric") or "")
|
|
157
166
|
return not metric.startswith("codex_")
|
|
@@ -274,6 +283,13 @@ def preserved_history(records: Iterable[Mapping], *,
|
|
|
274
283
|
continue
|
|
275
284
|
if not _is_owned_event(candidate):
|
|
276
285
|
continue
|
|
286
|
+
if _is_codex_budget(candidate):
|
|
287
|
+
# #869: a Codex crossing's desired state comes only from its own
|
|
288
|
+
# recorded provenance, never from Claude replay evidence, so it is
|
|
289
|
+
# never "un-re-derivable history". Holding one here would retain a
|
|
290
|
+
# crossing the correction retires below its threshold, and would
|
|
291
|
+
# revive that intentionally retired crossing on the next run.
|
|
292
|
+
continue
|
|
277
293
|
if evidence_retained and not event_id.startswith(
|
|
278
294
|
_CUTOVER_EXPORT_ID_PREFIX
|
|
279
295
|
):
|
|
@@ -287,6 +303,78 @@ def preserved_history(records: Iterable[Mapping], *,
|
|
|
287
303
|
return {event_id: entry[2] for event_id, entry in best.items()}
|
|
288
304
|
|
|
289
305
|
|
|
306
|
+
def _is_codex_budget(record: Mapping) -> bool:
|
|
307
|
+
payload = record.get("payload") or {}
|
|
308
|
+
return payload.get("kind") == "budget" and payload.get("vendor") == "codex"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
_BLOCK_TOKEN_KEYS = (
|
|
312
|
+
"input_tokens", "output_tokens", "cache_create_tokens", "cache_read_tokens",
|
|
313
|
+
)
|
|
314
|
+
#: The monetary fields a five-hour milestone snapshots from its block.
|
|
315
|
+
_FIVE_HOUR_MILESTONE_MONETARY_KEYS = frozenset({
|
|
316
|
+
"block_cost_usd", "marginal_cost_usd",
|
|
317
|
+
})
|
|
318
|
+
#: Milestone kinds whose ``alerted_at`` records an actual dispatch. Scratch
|
|
319
|
+
#: replay runs without alert config, so it can never reproduce that latch.
|
|
320
|
+
_ALERT_LATCHED_MILESTONE_KINDS = frozenset({
|
|
321
|
+
"percent_milestone", "five_hour_milestone",
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _positive_token_models(payload: Mapping) -> set:
|
|
326
|
+
return {
|
|
327
|
+
child.get("model")
|
|
328
|
+
for child in payload.get("_models", [])
|
|
329
|
+
if any(int(child.get(key) or 0) > 0 for key in _BLOCK_TOKEN_KEYS)
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _child_population(payload: Mapping, key: str) -> list:
|
|
334
|
+
"""A close's child set without its monetary field, order-independent."""
|
|
335
|
+
return sorted(
|
|
336
|
+
[
|
|
337
|
+
{k: v for k, v in child.items() if k != "cost_usd"}
|
|
338
|
+
for child in payload.get(key, [])
|
|
339
|
+
],
|
|
340
|
+
key=lambda item: json.dumps(item, sort_keys=True),
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _same_child_population(current: Mapping, desired: Mapping) -> bool:
|
|
345
|
+
return (
|
|
346
|
+
_child_population(current, "_models")
|
|
347
|
+
== _child_population(desired, "_models")
|
|
348
|
+
and _child_population(current, "_projects")
|
|
349
|
+
== _child_population(desired, "_projects")
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def close_cost_is_uncertain(payload: Mapping) -> bool:
|
|
354
|
+
"""A closed block's retained cost lacks proof that every model was priced.
|
|
355
|
+
|
|
356
|
+
A computation-time marker settles it except for an ambiguous model, or a
|
|
357
|
+
missing-card model beside a priced one (project children have no model
|
|
358
|
+
axis). An unmarked close is uncertain only while a positive-token model
|
|
359
|
+
carries a zero or unresolved cost; a historical correction marker (#869)
|
|
360
|
+
is proof in itself.
|
|
361
|
+
"""
|
|
362
|
+
pricing = payload.get("_pricing")
|
|
363
|
+
positive = _positive_token_models(payload)
|
|
364
|
+
if pricing:
|
|
365
|
+
if pricing.get("basis") == HISTORICAL_CLOSE_BASIS:
|
|
366
|
+
return False
|
|
367
|
+
missing = set(pricing.get("unpricedModels") or [])
|
|
368
|
+
return bool(
|
|
369
|
+
pricing.get("uncertainModels")
|
|
370
|
+
or (missing and missing != positive)
|
|
371
|
+
)
|
|
372
|
+
return any(
|
|
373
|
+
child.get("model") in positive and not child.get("cost_usd")
|
|
374
|
+
for child in payload.get("_models", [])
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
290
378
|
def _canonical_bytes(value) -> bytes:
|
|
291
379
|
return json.dumps(
|
|
292
380
|
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False,
|
|
@@ -305,17 +393,43 @@ def _semantic_event(record: Mapping) -> dict:
|
|
|
305
393
|
}
|
|
306
394
|
|
|
307
395
|
|
|
396
|
+
def _with_close_marker(record: Mapping, marker: Mapping) -> dict:
|
|
397
|
+
"""A close record carrying ``marker`` in both of its provenance fields."""
|
|
398
|
+
corrected = dict(record)
|
|
399
|
+
payload = dict(record.get("payload") or {})
|
|
400
|
+
payload["_pricing"] = dict(marker)
|
|
401
|
+
payload["pricing_provenance_json"] = json.dumps(
|
|
402
|
+
dict(marker), sort_keys=True, separators=(",", ":"),
|
|
403
|
+
)
|
|
404
|
+
corrected["payload"] = payload
|
|
405
|
+
return corrected
|
|
406
|
+
|
|
407
|
+
|
|
308
408
|
def _preserve_non_derivable_state(
|
|
309
|
-
current_record: Mapping, desired_record: Mapping
|
|
409
|
+
current_record: Mapping, desired_record: Mapping, *,
|
|
410
|
+
historical_marker: "Mapping | None" = None,
|
|
411
|
+
drifted_ids: "set | None" = None,
|
|
310
412
|
) -> Mapping:
|
|
311
413
|
"""Carry durable state that scratch replay cannot truthfully reconstruct.
|
|
312
414
|
|
|
313
|
-
|
|
314
|
-
crossing actually dispatched. Historical alert
|
|
315
|
-
retained, so scratch replay deliberately runs with
|
|
316
|
-
produces ``None``. Preserve the selected event's exact
|
|
317
|
-
rest of the milestone is retained and when another
|
|
318
|
-
higher-revision correction (#410 Task B
|
|
415
|
+
A percent or five-hour milestone's ``alerted_at`` is the latch recorded
|
|
416
|
+
when the first crossing actually dispatched. Historical alert
|
|
417
|
+
configuration is not retained, so scratch replay deliberately runs with
|
|
418
|
+
alerts disabled and produces ``None``. Preserve the selected event's exact
|
|
419
|
+
latch both when the rest of the milestone is retained and when another
|
|
420
|
+
field needs an audited higher-revision correction (#410 Task B; five-hour
|
|
421
|
+
milestones since #869 V3).
|
|
422
|
+
|
|
423
|
+
#869: ``historical_marker`` is the planner's proof that an UNMARKED close
|
|
424
|
+
priced every positive-token model at $0 only for want of a card; the close
|
|
425
|
+
then adopts the replay under that marker. A marked or historically proven
|
|
426
|
+
close whose replayed child population differs is kept unchanged and its id
|
|
427
|
+
added to ``drifted_ids``. A close corrected from its recorded missing-card
|
|
428
|
+
marker is stamped with its own basis, ``RECORDED_CLOSE_CORRECTION_BASIS``.
|
|
429
|
+
|
|
430
|
+
#875: the milestones of a frozen or replaced close are decided by
|
|
431
|
+
:func:`_resolve_closed_block_milestones`, not here. This function only
|
|
432
|
+
carries a milestone's ``alerted_at`` latch.
|
|
319
433
|
"""
|
|
320
434
|
current_payload = current_record.get("payload") or {}
|
|
321
435
|
desired_payload = desired_record.get("payload") or {}
|
|
@@ -355,7 +469,10 @@ def _preserve_non_derivable_state(
|
|
|
355
469
|
facts = {
|
|
356
470
|
key: value for key, value in payload.items()
|
|
357
471
|
if not key.startswith("total_")
|
|
358
|
-
and key not in {
|
|
472
|
+
and key not in {
|
|
473
|
+
"_models", "_projects", "_pricing",
|
|
474
|
+
"pricing_provenance_json",
|
|
475
|
+
}
|
|
359
476
|
}
|
|
360
477
|
# Live close used the display-zone offset; scratch replay writes
|
|
361
478
|
# UTC. Both name one frozen boundary and must compare as an instant.
|
|
@@ -373,18 +490,354 @@ def _preserve_non_derivable_state(
|
|
|
373
490
|
return facts
|
|
374
491
|
|
|
375
492
|
if structure(current_payload) == structure(desired_payload):
|
|
493
|
+
current_pricing = current_payload.get("_pricing") or {}
|
|
494
|
+
desired_pricing = desired_payload.get("_pricing") or {}
|
|
495
|
+
|
|
496
|
+
def keep_drifted():
|
|
497
|
+
# #869 F3: a changed retained population cannot prove the
|
|
498
|
+
# correction, but it does not falsify the recorded close
|
|
499
|
+
# either. Keep this close and disclose it; never refuse the
|
|
500
|
+
# family's other corrections over it.
|
|
501
|
+
if drifted_ids is not None:
|
|
502
|
+
drifted_ids.add(current_record.get("id"))
|
|
503
|
+
return current_record
|
|
504
|
+
|
|
505
|
+
if (
|
|
506
|
+
not current_pricing
|
|
507
|
+
and current_payload.get("pricing_provenance_json") is None
|
|
508
|
+
and historical_marker is not None
|
|
509
|
+
):
|
|
510
|
+
# #869 F2: an unmarked historical close the retained entries
|
|
511
|
+
# prove was $0 only because its model had no card. Adopt the
|
|
512
|
+
# replayed parent and BOTH child sets together, under a marker
|
|
513
|
+
# whose distinct basis freezes the result against later cards.
|
|
514
|
+
if not _same_child_population(current_payload, desired_payload):
|
|
515
|
+
return keep_drifted()
|
|
516
|
+
return _with_close_marker(desired_record, historical_marker)
|
|
517
|
+
missing = current_pricing.get("unpricedModels") or []
|
|
518
|
+
if missing:
|
|
519
|
+
# A closed amount is frozen unless its own durable close event
|
|
520
|
+
# says a model had no direct card. Verify the replay covers the
|
|
521
|
+
# same token population and now has direct cards for every
|
|
522
|
+
# missing model before allowing a monetary correction.
|
|
523
|
+
# The retained project children do not contain a model axis.
|
|
524
|
+
# With both direct and missing-card models, scratch replay
|
|
525
|
+
# cannot preserve the direct model's at-close contribution in
|
|
526
|
+
# every project. Leave that entire close unchanged.
|
|
527
|
+
if (
|
|
528
|
+
set(missing) != _positive_token_models(current_payload)
|
|
529
|
+
or current_pricing.get("uncertainModels")
|
|
530
|
+
):
|
|
531
|
+
return current_record
|
|
532
|
+
if any(
|
|
533
|
+
model in (desired_pricing.get("unpricedModels") or [])
|
|
534
|
+
for model in missing
|
|
535
|
+
):
|
|
536
|
+
return current_record
|
|
537
|
+
if not _same_child_population(current_payload, desired_payload):
|
|
538
|
+
return keep_drifted()
|
|
539
|
+
# #869 V4: record that this close was CORRECTED. The basis is
|
|
540
|
+
# provenance only: since #875 every frozen close freezes the
|
|
541
|
+
# block cost of its unchanged five-hour crossings, whatever
|
|
542
|
+
# its basis. A genuinely priced close's marker looks the same
|
|
543
|
+
# otherwise.
|
|
544
|
+
return _with_close_marker(desired_record, {
|
|
545
|
+
**desired_pricing, "basis": RECORDED_CLOSE_CORRECTION_BASIS,
|
|
546
|
+
})
|
|
376
547
|
return current_record
|
|
377
548
|
if (
|
|
378
|
-
current_payload.get("kind")
|
|
379
|
-
|
|
380
|
-
|
|
549
|
+
current_payload.get("kind") in _ALERT_LATCHED_MILESTONE_KINDS
|
|
550
|
+
and desired_payload.get("kind") == current_payload.get("kind")
|
|
551
|
+
and "alerted_at" in current_payload
|
|
381
552
|
):
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
553
|
+
# The recorded dispatch latch survives any correction of the rest of
|
|
554
|
+
# the milestone (#410 Task B; five-hour milestones since #869 V3).
|
|
555
|
+
merged = dict(desired_record)
|
|
556
|
+
merged_payload = dict(desired_payload)
|
|
557
|
+
merged_payload["alerted_at"] = current_payload["alerted_at"]
|
|
558
|
+
merged["payload"] = merged_payload
|
|
559
|
+
desired_record, desired_payload = merged, merged_payload
|
|
560
|
+
return desired_record
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
#: A resolved five-hour marginal within this many USD of its reference keeps
|
|
564
|
+
#: the reference's exact float, so float spelling never creates a correction
|
|
565
|
+
#: and a second rederive stays a no-op (#875).
|
|
566
|
+
_MARGINAL_TOLERANCE_USD = 1e-9
|
|
567
|
+
#: A dependency reference that names no event. For ``reset_event_ref`` these
|
|
568
|
+
#: are the spellings of the pre-credit ``"0"`` segment.
|
|
569
|
+
_EMPTY_REFERENCE_SENTINELS = frozenset({None, 0, "0", ""})
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def _milestone_segment_key(payload: Mapping) -> tuple:
|
|
573
|
+
"""A five-hour milestone's credit segment: account, window, reset ref.
|
|
574
|
+
|
|
575
|
+
``"0"`` (and every empty spelling) is the pre-credit segment.
|
|
576
|
+
"""
|
|
577
|
+
reset_ref = payload.get("reset_event_ref")
|
|
578
|
+
if reset_ref in _EMPTY_REFERENCE_SENTINELS:
|
|
579
|
+
reset_ref = "0"
|
|
580
|
+
return (
|
|
581
|
+
payload.get("account_key"),
|
|
582
|
+
payload.get("five_hour_window_key"),
|
|
583
|
+
str(reset_ref),
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def _milestone_identity(payload: Mapping) -> dict:
|
|
588
|
+
"""A five-hour milestone's non-monetary identity."""
|
|
589
|
+
return {
|
|
590
|
+
key: value for key, value in payload.items()
|
|
591
|
+
if key not in _FIVE_HOUR_MILESTONE_MONETARY_KEYS
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _five_hour_block(payload: Mapping) -> tuple:
|
|
596
|
+
"""The ``(account_key, five_hour_window_key)`` block a payload belongs to."""
|
|
597
|
+
return (payload.get("account_key"), payload.get("five_hour_window_key"))
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
def _usable_threshold(value):
|
|
601
|
+
"""A milestone threshold the marginal rule can order, or ``None``.
|
|
602
|
+
|
|
603
|
+
Only an ``int`` qualifies. ``int()`` would read ``True``, ``1.0`` and
|
|
604
|
+
``"1"`` as threshold 1 and make a malformed row a predecessor.
|
|
605
|
+
"""
|
|
606
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
607
|
+
return value
|
|
608
|
+
return None
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _usable_amount(value) -> bool:
|
|
612
|
+
"""True for a finite ``int`` or ``float`` that is not a ``bool``.
|
|
613
|
+
|
|
614
|
+
An ``int`` too large for a float is unusable rather than an error, so a
|
|
615
|
+
malformed row can never abort the plan (spec §4.3).
|
|
616
|
+
"""
|
|
617
|
+
if not isinstance(value, (int, float)) or isinstance(value, bool):
|
|
618
|
+
return False
|
|
619
|
+
try:
|
|
620
|
+
return math.isfinite(value)
|
|
621
|
+
except OverflowError:
|
|
622
|
+
return False
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
#: A close correction that moves ``total_cost_usd`` by at most this many USD
|
|
626
|
+
#: keeps the close's money, so its block stays frozen (#875, spec §4.1).
|
|
627
|
+
_CLOSE_TOTAL_TOLERANCE_USD = 1e-9
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def _close_keeps_its_money(current_payload: Mapping,
|
|
631
|
+
final_payload: Mapping) -> bool:
|
|
632
|
+
"""#875 (spec §4.1): a close correction that leaves the block's money.
|
|
633
|
+
|
|
634
|
+
True when the current close and the one the plan leaves are both closed
|
|
635
|
+
and both totals are usable (a finite ``int`` or ``float`` that is not a
|
|
636
|
+
``bool``) within 1e-9 USD of each other. The reviewed-hold helper's
|
|
637
|
+
weekly-axis update is the known case: it keeps the current payload and
|
|
638
|
+
replaces only the block's weekly axes. Such a correction is FROZEN like a
|
|
639
|
+
kept close. A correction that closes an open block, or whose totals are
|
|
640
|
+
unusable or differ by more, writes the close's money and REPLACES it.
|
|
641
|
+
"""
|
|
642
|
+
current_total = current_payload.get("total_cost_usd")
|
|
643
|
+
final_total = final_payload.get("total_cost_usd")
|
|
644
|
+
return (
|
|
645
|
+
current_payload.get("is_closed") == 1
|
|
646
|
+
and final_payload.get("is_closed") == 1
|
|
647
|
+
and _usable_amount(current_total)
|
|
648
|
+
and _usable_amount(final_total)
|
|
649
|
+
and abs(final_total - current_total) <= _CLOSE_TOTAL_TOLERANCE_USD
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _revived_by_this_family(selected) -> bool:
|
|
654
|
+
"""A preserved event this family's own earlier batch retired (#426)."""
|
|
655
|
+
return (
|
|
656
|
+
selected is not None
|
|
657
|
+
and selected.status == "tombstone"
|
|
658
|
+
and str(selected.batch_id or "").startswith(_FAMILY_BATCH_PREFIX)
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def _resolve_closed_block_milestones(
|
|
663
|
+
current: Mapping[str, Mapping], desired: Mapping[str, Mapping],
|
|
664
|
+
preserved: Mapping[str, Mapping], frozen_blocks, replaced_blocks, *,
|
|
665
|
+
selection,
|
|
666
|
+
) -> dict[str, dict]:
|
|
667
|
+
"""#875: target records of the five-hour milestones of closed blocks.
|
|
668
|
+
|
|
669
|
+
A block is classified by the close the plan's final state holds for it
|
|
670
|
+
(spec §4.1), by its money rather than by whether the plan acts on it. A
|
|
671
|
+
FROZEN close keeps the money it already had: a kept close, including a
|
|
672
|
+
forced-conflict reaffirmation; preserved history the plan retains or
|
|
673
|
+
revives; or a correction of a closed close that leaves its usable total
|
|
674
|
+
within 1e-9 USD (:func:`_close_keeps_its_money`), such as the reviewed
|
|
675
|
+
hold's weekly-axis update. Under it, an existing crossing whose ``at``
|
|
676
|
+
and non-monetary identity are both unchanged keeps its current
|
|
677
|
+
``block_cost_usd`` across any card edit, and a crossing whose ``at`` or
|
|
678
|
+
identity differs is a corrected crossing that takes replay's block cost.
|
|
679
|
+
A REPLACED close is one this plan adds, or corrects so that it writes the
|
|
680
|
+
close's money (it closes an open block, or its totals are unusable or
|
|
681
|
+
differ by more), and every matched milestone under it is a corrected
|
|
682
|
+
crossing. A block holding a frozen close and a replaced one is frozen.
|
|
683
|
+
Replay additions take replay's block cost under either. The milestones of
|
|
684
|
+
every other block follow replay and are not returned.
|
|
685
|
+
|
|
686
|
+
A present marginal is its final block cost minus the final block cost of
|
|
687
|
+
its predecessor: the highest lower threshold in the same (account,
|
|
688
|
+
window, reset_event_ref) segment of the plan's FINAL state. The final
|
|
689
|
+
state is every milestone the plan leaves active: these resolved records,
|
|
690
|
+
replay's milestones of the other blocks, and retained or preserved
|
|
691
|
+
current milestones. Tombstones are excluded. Block costs are settled
|
|
692
|
+
before any marginal, so the definition is not circular. A value within
|
|
693
|
+
1e-9 USD of its reference (the current marginal, or the desired one for
|
|
694
|
+
an addition) keeps the reference's bytes. A reference that is not a
|
|
695
|
+
finite number only disables that snap; it never aborts the plan.
|
|
696
|
+
|
|
697
|
+
Marginal presence comes from the CURRENT record for an unchanged
|
|
698
|
+
crossing: replay never reproduces preserved ``b:`` predecessors, and a
|
|
699
|
+
reviewed hold can remove a desired predecessor before the diff. It comes
|
|
700
|
+
from the desired record for a corrected crossing or an addition. A
|
|
701
|
+
candidate keeps its whole chosen monetary pair (the current pair for an
|
|
702
|
+
unchanged crossing, the desired pair otherwise) rather than inventing a
|
|
703
|
+
value when it has no usable predecessor, or when its own threshold or
|
|
704
|
+
block cost is unusable. Only an ``int`` threshold occupies a position,
|
|
705
|
+
and only a finite amount is usable. A row whose threshold is usable but
|
|
706
|
+
whose block cost is not still occupies its threshold, so a successor
|
|
707
|
+
right above it fails closed and never skips to a lower row.
|
|
708
|
+
|
|
709
|
+
A second plan is a no-op for a block whose close it takes no action on.
|
|
710
|
+
That close is frozen in the next plan, kept or preserved, whatever role
|
|
711
|
+
it had in this one. Every crossing this plan wrote from replay now
|
|
712
|
+
carries the ``at`` and identity of the raw replay records, and a record
|
|
713
|
+
that reconciliation or a hold carries into ``desired`` keeps the applied
|
|
714
|
+
identity and resolved values. So the next plan sees unchanged crossings
|
|
715
|
+
whose current block cost and presence are the values this plan wrote,
|
|
716
|
+
and it resolves the same marginals against the same final predecessors.
|
|
717
|
+
|
|
718
|
+
Returns ``{event_id: record}`` for every matched milestone and every
|
|
719
|
+
replay addition of a frozen or replaced block. The ``alerted_at`` latch
|
|
720
|
+
is carried before the identity comparison, exactly as the ordinary path
|
|
721
|
+
carries it.
|
|
722
|
+
"""
|
|
723
|
+
settled_blocks = frozenset(frozen_blocks) | frozenset(replaced_blocks)
|
|
724
|
+
resolved: dict[str, dict] = {}
|
|
725
|
+
roles: dict[str, str] = {}
|
|
726
|
+
final: dict[str, Mapping] = {}
|
|
727
|
+
for event_id in sorted(set(current) | set(desired) | set(preserved)):
|
|
728
|
+
current_record = current.get(event_id)
|
|
729
|
+
desired_record = desired.get(event_id)
|
|
730
|
+
preserved_record = preserved.get(event_id)
|
|
731
|
+
kind_payload = (
|
|
732
|
+
(desired_record or current_record or preserved_record).get(
|
|
733
|
+
"payload") or {}
|
|
734
|
+
)
|
|
735
|
+
if kind_payload.get("kind") != "five_hour_milestone":
|
|
736
|
+
continue
|
|
737
|
+
if preserved_record is not None:
|
|
738
|
+
# Held out of the diff (#426): retained as it stands, or revived
|
|
739
|
+
# when this family's own earlier batch retired it.
|
|
740
|
+
if current_record is not None:
|
|
741
|
+
final[event_id] = current_record
|
|
742
|
+
elif _revived_by_this_family(selection.by_id.get(event_id)):
|
|
743
|
+
final[event_id] = preserved_record
|
|
744
|
+
continue
|
|
745
|
+
block = _five_hour_block(kind_payload)
|
|
746
|
+
if block not in settled_blocks:
|
|
747
|
+
# An open block, or one whose close the plan tombstones: replay
|
|
748
|
+
# decides these milestones, exactly as it did before #875.
|
|
749
|
+
if desired_record is not None:
|
|
750
|
+
final[event_id] = desired_record
|
|
751
|
+
continue
|
|
752
|
+
if current_record is not None and desired_record is not None:
|
|
753
|
+
carried = _preserve_non_derivable_state(
|
|
754
|
+
current_record, desired_record)
|
|
755
|
+
current_payload = current_record.get("payload") or {}
|
|
756
|
+
payload = dict(carried.get("payload") or {})
|
|
757
|
+
# A moved ``at`` is a corrected crossing like a changed identity:
|
|
758
|
+
# copying replay's marginal for it would leave the next plan to
|
|
759
|
+
# re-resolve it against its final predecessor (spec §1.3).
|
|
760
|
+
if (
|
|
761
|
+
block in frozen_blocks
|
|
762
|
+
and current_record.get("at") == desired_record.get("at")
|
|
763
|
+
and _milestone_identity(current_payload)
|
|
764
|
+
== _milestone_identity(payload)
|
|
765
|
+
):
|
|
766
|
+
payload["block_cost_usd"] = current_payload.get(
|
|
767
|
+
"block_cost_usd")
|
|
768
|
+
roles[event_id] = "unchanged"
|
|
769
|
+
else:
|
|
770
|
+
roles[event_id] = "corrected"
|
|
771
|
+
record = {**carried, "payload": payload}
|
|
772
|
+
elif desired_record is not None:
|
|
773
|
+
roles[event_id] = "added"
|
|
774
|
+
record = {**desired_record,
|
|
775
|
+
"payload": dict(desired_record.get("payload") or {})}
|
|
776
|
+
else:
|
|
777
|
+
# Current only and not preserved: the plan tombstones it.
|
|
778
|
+
continue
|
|
779
|
+
resolved[event_id] = record
|
|
780
|
+
final[event_id] = record
|
|
781
|
+
|
|
782
|
+
# Every row with a usable threshold occupies it, usable cost or not.
|
|
783
|
+
occupied: dict[tuple, dict[int, list[str]]] = {}
|
|
784
|
+
for event_id, record in final.items():
|
|
785
|
+
payload = record.get("payload") or {}
|
|
786
|
+
threshold = _usable_threshold(payload.get("percent_threshold"))
|
|
787
|
+
if threshold is not None:
|
|
788
|
+
occupied.setdefault(
|
|
789
|
+
_milestone_segment_key(payload), {}
|
|
790
|
+
).setdefault(threshold, []).append(event_id)
|
|
791
|
+
|
|
792
|
+
def predecessor_cost(payload):
|
|
793
|
+
"""The final block cost at the nearest lower threshold, or None."""
|
|
794
|
+
rows = occupied.get(_milestone_segment_key(payload), {})
|
|
795
|
+
threshold = _usable_threshold(payload.get("percent_threshold"))
|
|
796
|
+
lower = [value for value in rows if value < threshold]
|
|
797
|
+
if not lower:
|
|
798
|
+
return None
|
|
799
|
+
nearest = rows[max(lower)]
|
|
800
|
+
costs = {
|
|
801
|
+
event_id: (final[event_id].get("payload") or {}).get(
|
|
802
|
+
"block_cost_usd")
|
|
803
|
+
for event_id in nearest
|
|
804
|
+
}
|
|
805
|
+
if not all(_usable_amount(cost) for cost in costs.values()):
|
|
806
|
+
return None # fails closed: never skips past an unusable row
|
|
807
|
+
return costs[max(nearest)]
|
|
808
|
+
|
|
809
|
+
for event_id, record in resolved.items():
|
|
810
|
+
payload = record["payload"]
|
|
811
|
+
current_payload = (current.get(event_id) or {}).get("payload") or {}
|
|
812
|
+
desired_payload = (desired.get(event_id) or {}).get("payload") or {}
|
|
813
|
+
chosen = (
|
|
814
|
+
current_payload if roles[event_id] == "unchanged"
|
|
815
|
+
else desired_payload
|
|
816
|
+
)
|
|
817
|
+
if chosen.get("marginal_cost_usd") is None:
|
|
818
|
+
payload["marginal_cost_usd"] = None
|
|
819
|
+
continue
|
|
820
|
+
prior = None
|
|
821
|
+
if (
|
|
822
|
+
_usable_threshold(payload.get("percent_threshold")) is not None
|
|
823
|
+
and _usable_amount(payload.get("block_cost_usd"))
|
|
824
|
+
):
|
|
825
|
+
prior = predecessor_cost(payload)
|
|
826
|
+
if prior is None:
|
|
827
|
+
payload["block_cost_usd"] = chosen.get("block_cost_usd")
|
|
828
|
+
payload["marginal_cost_usd"] = chosen.get("marginal_cost_usd")
|
|
829
|
+
continue
|
|
830
|
+
value = float(payload["block_cost_usd"]) - float(prior)
|
|
831
|
+
reference = (
|
|
832
|
+
current_payload if event_id in current else desired_payload
|
|
833
|
+
).get("marginal_cost_usd")
|
|
834
|
+
if (
|
|
835
|
+
_usable_amount(reference)
|
|
836
|
+
and abs(value - reference) <= _MARGINAL_TOLERANCE_USD
|
|
837
|
+
):
|
|
838
|
+
value = reference
|
|
839
|
+
payload["marginal_cost_usd"] = value
|
|
840
|
+
return resolved
|
|
388
841
|
|
|
389
842
|
|
|
390
843
|
_SNAPSHOT_IDENTITY_FIELDS = (
|
|
@@ -395,7 +848,6 @@ _SNAPSHOT_IDENTITY_FIELDS = (
|
|
|
395
848
|
_SNAPSHOT_DEPENDENT_KINDS = frozenset({
|
|
396
849
|
"percent_milestone", "five_hour_milestone",
|
|
397
850
|
})
|
|
398
|
-
_EMPTY_REFERENCE_SENTINELS = frozenset({None, 0, "0", ""})
|
|
399
851
|
_MILESTONE_BASE_DEPENDENCIES = {
|
|
400
852
|
"usage_snapshot_ref": "snapshot_accept",
|
|
401
853
|
"cost_snapshot_ref": "weekly_cost_snapshot",
|
|
@@ -825,12 +1277,16 @@ def _preserve_reviewed_hold_dependencies(
|
|
|
825
1277
|
"""Keep frozen five-hour facts reached from exact reviewed holds.
|
|
826
1278
|
|
|
827
1279
|
An exact weekly-axis hold must not erase the same observation's retained
|
|
828
|
-
five-hour crossing. Milestones are historical facts, so
|
|
829
|
-
accepted payload and
|
|
830
|
-
currently selected events.
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
1280
|
+
five-hour crossing. Milestones are historical facts, so ``desired``
|
|
1281
|
+
receives their whole accepted payload, and the usage/cost reference graph
|
|
1282
|
+
is closed with the exact currently selected events. Under a frozen or a
|
|
1283
|
+
replaced close (#875), :func:`_resolve_closed_block_milestones` still
|
|
1284
|
+
settles such a milestone's marginal against its final predecessor; its
|
|
1285
|
+
block cost stays the accepted one. A closed block may update only its
|
|
1286
|
+
weekly axes; its five-hour close boundary, reading, money, tokens and
|
|
1287
|
+
children stay the selected frozen fact, so that update keeps the close's
|
|
1288
|
+
money and its block stays frozen (spec §4.1). Any unfamiliar reference
|
|
1289
|
+
shape refuses rather than being guessed or rewritten.
|
|
834
1290
|
"""
|
|
835
1291
|
snapshot_ids = {
|
|
836
1292
|
f"sa:{raw_id}" for raw_id in reviewed_weekly_hold_ids
|
|
@@ -1046,12 +1502,13 @@ class RederivePlan:
|
|
|
1046
1502
|
actions: tuple[PlanAction, ...]
|
|
1047
1503
|
retained_event_count: int
|
|
1048
1504
|
preserved_event_count: int = 0
|
|
1505
|
+
uncertain_cost_fact_count: int = 0
|
|
1049
1506
|
action_counts_by_event_kind: Mapping[str, Mapping[str, int]] = dataclasses.field(
|
|
1050
1507
|
default_factory=dict
|
|
1051
1508
|
)
|
|
1052
1509
|
|
|
1053
1510
|
def _body(self) -> dict:
|
|
1054
|
-
|
|
1511
|
+
body = {
|
|
1055
1512
|
"schemaVersion": 1,
|
|
1056
1513
|
"family": self.family,
|
|
1057
1514
|
"journalHighWater": (
|
|
@@ -1068,6 +1525,9 @@ class RederivePlan:
|
|
|
1068
1525
|
"payloadHashes": sorted(action.payload_hash for action in self.actions),
|
|
1069
1526
|
"actions": [action.to_dict() for action in self.actions],
|
|
1070
1527
|
}
|
|
1528
|
+
if self.uncertain_cost_fact_count:
|
|
1529
|
+
body["uncertainCostFactCount"] = self.uncertain_cost_fact_count
|
|
1530
|
+
return body
|
|
1071
1531
|
|
|
1072
1532
|
@property
|
|
1073
1533
|
def plan_hash(self) -> str:
|
|
@@ -1106,6 +1566,8 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1106
1566
|
raw_observations=(),
|
|
1107
1567
|
snapshot_identity_decisions=(),
|
|
1108
1568
|
reviewed_weekly_hold_ids=(),
|
|
1569
|
+
uncertain_cost_fact_ids=(),
|
|
1570
|
+
historical_close_markers=None,
|
|
1109
1571
|
enforce_guard=True) -> RederivePlan:
|
|
1110
1572
|
"""Diff current effective events against one scratch-derived desired set.
|
|
1111
1573
|
|
|
@@ -1126,7 +1588,19 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1126
1588
|
append-only journal forever, which is precisely the case the first design
|
|
1127
1589
|
called already-correct. The resulting correction is a semantic no-op in
|
|
1128
1590
|
content and a revision advance in effect, and the revision advance is what
|
|
1129
|
-
suppresses the rev-0 group under the selector's revision filter.
|
|
1591
|
+
suppresses the rev-0 group under the selector's revision filter.
|
|
1592
|
+
|
|
1593
|
+
``historical_close_markers`` (#869) maps an unmarked close's event id to
|
|
1594
|
+
the planner's retained-entry proof that its $0 came from a missing card.
|
|
1595
|
+
``uncertain_cost_fact_ids`` names the non-close monetary events the caller
|
|
1596
|
+
already judged unprovable; closed blocks are judged here, against the
|
|
1597
|
+
state this plan leaves behind.
|
|
1598
|
+
|
|
1599
|
+
#875: the milestones of a block whose final close is frozen (kept,
|
|
1600
|
+
preserved history retained or revived, or corrected without moving its
|
|
1601
|
+
money) or replaced (added, or corrected in a way that writes its money)
|
|
1602
|
+
are decided by :func:`_resolve_closed_block_milestones` before the diff,
|
|
1603
|
+
the same way closes are decided before milestones."""
|
|
1130
1604
|
conflicted_event_ids = frozenset(conflicted_event_ids or ())
|
|
1131
1605
|
desired = _desired_by_id(desired_events)
|
|
1132
1606
|
current = {
|
|
@@ -1160,20 +1634,87 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1160
1634
|
for kind in sorted(_EVT_CLASSIFICATIONS)
|
|
1161
1635
|
}
|
|
1162
1636
|
actions: list[PlanAction] = []
|
|
1637
|
+
historical_close_markers = dict(historical_close_markers or {})
|
|
1638
|
+
drifted_close_ids: set = set()
|
|
1639
|
+
|
|
1640
|
+
def _kind(record):
|
|
1641
|
+
return (record.get("payload") or {}).get("kind")
|
|
1642
|
+
|
|
1643
|
+
# Every close is decided before any milestone, so a five-hour milestone can
|
|
1644
|
+
# follow the decision its block's close actually received (#869).
|
|
1645
|
+
close_decisions = {}
|
|
1646
|
+
for event_id in sorted(set(current) & set(desired)):
|
|
1647
|
+
if (
|
|
1648
|
+
_kind(current[event_id]) == "five_hour_block_close"
|
|
1649
|
+
and _kind(desired[event_id]) == "five_hour_block_close"
|
|
1650
|
+
):
|
|
1651
|
+
close_decisions[event_id] = _preserve_non_derivable_state(
|
|
1652
|
+
current[event_id], desired[event_id],
|
|
1653
|
+
historical_marker=historical_close_markers.get(event_id),
|
|
1654
|
+
drifted_ids=drifted_close_ids,
|
|
1655
|
+
)
|
|
1656
|
+
# #875 (spec §4.1): classify every block by the closed close the plan's
|
|
1657
|
+
# FINAL state holds for it, whatever its pricing basis. The class depends
|
|
1658
|
+
# on that close's MONEY, the block total its milestones must agree with,
|
|
1659
|
+
# never on whether the plan acts on it: kept and preserved are transient
|
|
1660
|
+
# roles (a hold can inject a preserved close, and the next plan can see
|
|
1661
|
+
# it preserved again), so both are frozen, a revival is frozen although
|
|
1662
|
+
# the plan writes it, and so is a correction that leaves the total where
|
|
1663
|
+
# it was. A frozen close freezes the block cost of its unchanged
|
|
1664
|
+
# crossings; a corrected crossing under it still takes replay's.
|
|
1665
|
+
frozen_close_blocks = set()
|
|
1666
|
+
replaced_close_blocks = set()
|
|
1667
|
+
for event_id, decision in close_decisions.items():
|
|
1668
|
+
payload = decision.get("payload") or {}
|
|
1669
|
+
if payload.get("is_closed") != 1:
|
|
1670
|
+
continue
|
|
1671
|
+
if decision is current[event_id] or _close_keeps_its_money(
|
|
1672
|
+
current[event_id].get("payload") or {}, payload,
|
|
1673
|
+
):
|
|
1674
|
+
# Kept, including a forced-conflict reaffirmation, which only
|
|
1675
|
+
# re-writes this same content at a higher revision; or a
|
|
1676
|
+
# money-preserving correction, such as the reviewed hold's
|
|
1677
|
+
# weekly-axis update (spec §1.7).
|
|
1678
|
+
frozen_close_blocks.add(_five_hour_block(payload))
|
|
1679
|
+
else:
|
|
1680
|
+
# The plan writes this close's money: it closes a block that was
|
|
1681
|
+
# open, or it moves or cannot compare the total.
|
|
1682
|
+
replaced_close_blocks.add(_five_hour_block(payload))
|
|
1683
|
+
for event_id in set(desired) - set(current):
|
|
1684
|
+
payload = desired[event_id].get("payload") or {}
|
|
1685
|
+
if (payload.get("kind") == "five_hour_block_close"
|
|
1686
|
+
and payload.get("is_closed") == 1):
|
|
1687
|
+
replaced_close_blocks.add(_five_hour_block(payload)) # an add
|
|
1688
|
+
for event_id, record in preserved.items():
|
|
1689
|
+
if event_id in current:
|
|
1690
|
+
final_close = current[event_id]
|
|
1691
|
+
elif _revived_by_this_family(selection.by_id.get(event_id)):
|
|
1692
|
+
final_close = record
|
|
1693
|
+
else:
|
|
1694
|
+
continue # stays retired: the final state holds no such close
|
|
1695
|
+
payload = final_close.get("payload") or {}
|
|
1696
|
+
if (payload.get("kind") == "five_hour_block_close"
|
|
1697
|
+
and payload.get("is_closed") == 1):
|
|
1698
|
+
frozen_close_blocks.add(_five_hour_block(payload))
|
|
1699
|
+
# Frozen wins (spec §4.1): a block whose final state holds a frozen close
|
|
1700
|
+
# and a replaced one is frozen. The causal block closure applies the same
|
|
1701
|
+
# rule (:func:`_replaced_block_closure`).
|
|
1702
|
+
replaced_close_blocks -= frozen_close_blocks
|
|
1703
|
+
milestone_decisions = _resolve_closed_block_milestones(
|
|
1704
|
+
current, desired, preserved, frozen_close_blocks,
|
|
1705
|
+
replaced_close_blocks, selection=selection,
|
|
1706
|
+
)
|
|
1163
1707
|
|
|
1164
1708
|
for event_id in sorted(set(current) | set(desired) | set(preserved)):
|
|
1165
1709
|
current_record = current.get(event_id)
|
|
1166
|
-
desired_record =
|
|
1710
|
+
desired_record = milestone_decisions.get(
|
|
1711
|
+
event_id, desired.get(event_id))
|
|
1167
1712
|
selected = selection.by_id.get(event_id)
|
|
1168
1713
|
preserved_record = preserved.get(event_id)
|
|
1169
1714
|
if preserved_record is not None:
|
|
1170
1715
|
# Un-re-derivable history: retain it, or restore it when this
|
|
1171
1716
|
# family's own earlier plan retired it (#426).
|
|
1172
|
-
revive = (
|
|
1173
|
-
selected is not None
|
|
1174
|
-
and selected.status == "tombstone"
|
|
1175
|
-
and str(selected.batch_id or "").startswith(_FAMILY_BATCH_PREFIX)
|
|
1176
|
-
)
|
|
1717
|
+
revive = _revived_by_this_family(selected)
|
|
1177
1718
|
reaffirm = (
|
|
1178
1719
|
selected is not None
|
|
1179
1720
|
and selected.status == "active"
|
|
@@ -1193,9 +1734,12 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1193
1734
|
at = str(source["at"])
|
|
1194
1735
|
payload = dict(source.get("payload") or {})
|
|
1195
1736
|
elif current_record is not None and desired_record is not None:
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1737
|
+
if event_id in close_decisions:
|
|
1738
|
+
desired_record = close_decisions[event_id]
|
|
1739
|
+
elif event_id not in milestone_decisions:
|
|
1740
|
+
desired_record = _preserve_non_derivable_state(
|
|
1741
|
+
current_record, desired_record,
|
|
1742
|
+
)
|
|
1199
1743
|
if (
|
|
1200
1744
|
_semantic_event(current_record) == _semantic_event(desired_record)
|
|
1201
1745
|
and event_id not in conflicted_event_ids
|
|
@@ -1248,6 +1792,27 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1248
1792
|
kind=kind,
|
|
1249
1793
|
))
|
|
1250
1794
|
|
|
1795
|
+
# #869 F7/Q9: a close counts only while its cost stays unprovable in the
|
|
1796
|
+
# state this plan leaves behind — not a provably priced close, not one this
|
|
1797
|
+
# plan corrects, and not one an earlier correction already proved.
|
|
1798
|
+
uncertain_closes = {
|
|
1799
|
+
event_id for event_id, record in current.items()
|
|
1800
|
+
if _kind(record) == "five_hour_block_close"
|
|
1801
|
+
and close_cost_is_uncertain(record.get("payload") or {})
|
|
1802
|
+
}
|
|
1803
|
+
for action in actions:
|
|
1804
|
+
if action.kind != "five_hour_block_close":
|
|
1805
|
+
continue
|
|
1806
|
+
if action.disposition == "tombstone":
|
|
1807
|
+
uncertain_closes.discard(action.event_id)
|
|
1808
|
+
elif close_cost_is_uncertain(action.payload or {}):
|
|
1809
|
+
uncertain_closes.add(action.event_id)
|
|
1810
|
+
else:
|
|
1811
|
+
uncertain_closes.discard(action.event_id)
|
|
1812
|
+
uncertain_ids = (
|
|
1813
|
+
set(uncertain_cost_fact_ids or ()) | uncertain_closes
|
|
1814
|
+
| drifted_close_ids
|
|
1815
|
+
)
|
|
1251
1816
|
plan = RederivePlan(
|
|
1252
1817
|
family=FAMILY,
|
|
1253
1818
|
journal_high_water=journal_high_water,
|
|
@@ -1257,6 +1822,7 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1257
1822
|
actions=tuple(actions),
|
|
1258
1823
|
retained_event_count=retained_event_count,
|
|
1259
1824
|
preserved_event_count=len(preserved),
|
|
1825
|
+
uncertain_cost_fact_count=len(uncertain_ids),
|
|
1260
1826
|
action_counts_by_event_kind=counts_by_kind,
|
|
1261
1827
|
)
|
|
1262
1828
|
if enforce_guard:
|
|
@@ -1264,6 +1830,99 @@ def build_claude_usage_plan(*, selection, desired_events: Iterable[Mapping],
|
|
|
1264
1830
|
return plan
|
|
1265
1831
|
|
|
1266
1832
|
|
|
1833
|
+
def _replaced_block_closure(
|
|
1834
|
+
included: list, reviewed_actions, current_events: Mapping[str, Mapping],
|
|
1835
|
+
) -> list:
|
|
1836
|
+
"""#875 (spec §4.5): keep a five-hour block whose close R replaces whole.
|
|
1837
|
+
|
|
1838
|
+
The B/R filter drops a close correction both plans make identically, yet
|
|
1839
|
+
a milestone under that close can still differ between them, for instance
|
|
1840
|
+
when R's hold keeps its predecessor whole while B reprices it. Keeping
|
|
1841
|
+
only the milestone would reprice it under a close the subset leaves as it
|
|
1842
|
+
stands (spec §1.5). So whenever ``included`` holds a five-hour milestone
|
|
1843
|
+
action whose block has a reviewed close action that REPLACES the close,
|
|
1844
|
+
the subset takes that close action and every reviewed action on the
|
|
1845
|
+
block's milestones, whether or not they survived the filter.
|
|
1846
|
+
|
|
1847
|
+
The classification is §4.1's, derived from R's actions and
|
|
1848
|
+
``current_events`` alone. A close action replaces when it adds a closed
|
|
1849
|
+
close, or supersedes to a closed record whose current record is open,
|
|
1850
|
+
whose totals are unusable, or whose totals differ by more than 1e-9 USD.
|
|
1851
|
+
A block R holds FROZEN never triggers, even beside such an action
|
|
1852
|
+
(frozen wins): a closed current close R takes no action on (kept, or
|
|
1853
|
+
preserved history retained), a revival (a supersede with no active
|
|
1854
|
+
current record), a forced-conflict reaffirmation (a supersede to the
|
|
1855
|
+
current content, whatever its total), or a money-preserving correction.
|
|
1856
|
+
A tombstone carries no payload; its block is read from its current
|
|
1857
|
+
record. Only reviewed actions are ever added, each once. The reverse
|
|
1858
|
+
direction, a causal close action whose milestones are identical in both
|
|
1859
|
+
plans, is #883's.
|
|
1860
|
+
"""
|
|
1861
|
+
def block(action):
|
|
1862
|
+
if action.disposition == "tombstone":
|
|
1863
|
+
record = current_events.get(action.event_id) or {}
|
|
1864
|
+
return _five_hour_block(record.get("payload") or {})
|
|
1865
|
+
return _five_hour_block(action.payload or {})
|
|
1866
|
+
|
|
1867
|
+
acted_ids = {action.event_id for action in reviewed_actions}
|
|
1868
|
+
frozen_blocks = set()
|
|
1869
|
+
for event_id, record in current_events.items():
|
|
1870
|
+
payload = record.get("payload") or {}
|
|
1871
|
+
if (
|
|
1872
|
+
event_id not in acted_ids
|
|
1873
|
+
and payload.get("kind") == "five_hour_block_close"
|
|
1874
|
+
and payload.get("is_closed") == 1
|
|
1875
|
+
):
|
|
1876
|
+
frozen_blocks.add(_five_hour_block(payload))
|
|
1877
|
+
replacing: dict[tuple, set] = {}
|
|
1878
|
+
for action in reviewed_actions:
|
|
1879
|
+
payload = action.payload or {}
|
|
1880
|
+
if (
|
|
1881
|
+
action.kind != "five_hour_block_close"
|
|
1882
|
+
or action.disposition not in {"add", "supersede"}
|
|
1883
|
+
or payload.get("is_closed") != 1
|
|
1884
|
+
):
|
|
1885
|
+
continue
|
|
1886
|
+
if action.disposition == "supersede":
|
|
1887
|
+
now = current_events.get(action.event_id)
|
|
1888
|
+
if (
|
|
1889
|
+
now is None # a revival of preserved history
|
|
1890
|
+
# A reaffirmation re-writes the kept content, whatever its
|
|
1891
|
+
# total, exactly as the resolver's ``decision is current``.
|
|
1892
|
+
or _semantic_event(now) == _semantic_event({
|
|
1893
|
+
"id": action.event_id, "at": action.at,
|
|
1894
|
+
"payload": payload,
|
|
1895
|
+
})
|
|
1896
|
+
or _close_keeps_its_money(now.get("payload") or {}, payload)
|
|
1897
|
+
):
|
|
1898
|
+
frozen_blocks.add(block(action))
|
|
1899
|
+
continue
|
|
1900
|
+
replacing.setdefault(block(action), set()).add(action.event_id)
|
|
1901
|
+
for key in frozen_blocks:
|
|
1902
|
+
replacing.pop(key, None)
|
|
1903
|
+
triggered = {
|
|
1904
|
+
block(action) for action in included
|
|
1905
|
+
if action.kind == "five_hour_milestone"
|
|
1906
|
+
and block(action) in replacing
|
|
1907
|
+
}
|
|
1908
|
+
if not triggered:
|
|
1909
|
+
return included
|
|
1910
|
+
close_ids = set().union(*(replacing[key] for key in triggered))
|
|
1911
|
+
expanded = list(included)
|
|
1912
|
+
included_ids = {action.event_id for action in included}
|
|
1913
|
+
for action in reviewed_actions:
|
|
1914
|
+
if action.event_id in included_ids:
|
|
1915
|
+
continue
|
|
1916
|
+
if (
|
|
1917
|
+
action.event_id in close_ids
|
|
1918
|
+
or (action.kind == "five_hour_milestone"
|
|
1919
|
+
and block(action) in triggered)
|
|
1920
|
+
):
|
|
1921
|
+
expanded.append(action)
|
|
1922
|
+
included_ids.add(action.event_id)
|
|
1923
|
+
return expanded
|
|
1924
|
+
|
|
1925
|
+
|
|
1267
1926
|
def causal_delta_plan(
|
|
1268
1927
|
baseline: RederivePlan, reviewed: RederivePlan, *,
|
|
1269
1928
|
current_events: Mapping[str, Mapping],
|
|
@@ -1276,6 +1935,12 @@ def causal_delta_plan(
|
|
|
1276
1935
|
reviewed-only or changed action is caused by the proposed operator record.
|
|
1277
1936
|
A baseline-only action means the reviewed target returns to C and requires
|
|
1278
1937
|
no correction action.
|
|
1938
|
+
|
|
1939
|
+
#875 (spec §4.5): a five-hour block whose close R replaces, by §4.1's
|
|
1940
|
+
money rule, is applied whole whenever the subset changes one of its
|
|
1941
|
+
milestones, and a block R holds frozen never is; see
|
|
1942
|
+
:func:`_replaced_block_closure`. The snapshot-dependency closure then runs
|
|
1943
|
+
over that expanded set.
|
|
1279
1944
|
"""
|
|
1280
1945
|
if (
|
|
1281
1946
|
baseline.journal_high_water != reviewed.journal_high_water
|
|
@@ -1306,6 +1971,8 @@ def causal_delta_plan(
|
|
|
1306
1971
|
)
|
|
1307
1972
|
)
|
|
1308
1973
|
]
|
|
1974
|
+
actions = _replaced_block_closure(
|
|
1975
|
+
actions, reviewed.actions, current_events)
|
|
1309
1976
|
action_by_id = {action.event_id: action for action in actions}
|
|
1310
1977
|
dependency_tombstones = set()
|
|
1311
1978
|
for action in actions:
|
|
@@ -1397,6 +2064,7 @@ def causal_delta_plan(
|
|
|
1397
2064
|
actions=actions,
|
|
1398
2065
|
retained_event_count=reviewed.retained_event_count,
|
|
1399
2066
|
preserved_event_count=reviewed.preserved_event_count,
|
|
2067
|
+
uncertain_cost_fact_count=reviewed.uncertain_cost_fact_count,
|
|
1400
2068
|
action_counts_by_event_kind=counts_by_kind,
|
|
1401
2069
|
)
|
|
1402
2070
|
|