cctally 1.113.0 → 1.115.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 +47 -0
- package/README.md +4 -6
- package/bin/_cctally_account.py +32 -8
- package/bin/_cctally_cache.py +6035 -722
- package/bin/_cctally_completion.py +1287 -0
- package/bin/_cctally_config.py +37 -0
- package/bin/_cctally_core.py +400 -2
- package/bin/_cctally_dashboard.py +98 -20
- package/bin/_cctally_dashboard_conversation.py +292 -23
- package/bin/_cctally_dashboard_envelope.py +16 -3
- package/bin/_cctally_dashboard_perf.py +128 -9
- package/bin/_cctally_dashboard_sources.py +285 -213
- package/bin/_cctally_db.py +1177 -23
- package/bin/_cctally_diagnosis_sources.py +22 -4
- package/bin/_cctally_doctor.py +572 -45
- package/bin/_cctally_five_hour.py +84 -33
- package/bin/_cctally_forecast.py +108 -15
- package/bin/_cctally_journal.py +3950 -116
- package/bin/_cctally_journal_repair.py +101 -12
- package/bin/_cctally_milestone_history.py +82 -9
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_percent_breakdown.py +3 -2
- package/bin/_cctally_project.py +8 -2
- package/bin/_cctally_quarantine.py +652 -0
- package/bin/_cctally_quota.py +227 -37
- package/bin/_cctally_quota_model.py +360 -83
- package/bin/_cctally_record.py +691 -149
- package/bin/_cctally_rederive.py +320 -41
- package/bin/_cctally_refresh.py +84 -13
- package/bin/_cctally_source_analytics.py +83 -17
- package/bin/_cctally_statusline.py +6154 -517
- package/bin/_cctally_store.py +367 -53
- package/bin/_cctally_transcript.py +8 -7
- package/bin/_cctally_tui.py +61 -56
- package/bin/_cctally_weekrefs.py +128 -21
- package/bin/_lib_account_binding.py +152 -0
- package/bin/_lib_account_protocol.py +386 -0
- package/bin/_lib_aggregators.py +4 -0
- package/bin/_lib_cache_coverage.py +13 -1
- package/bin/_lib_cache_report.py +35 -6
- package/bin/_lib_cache_writer_lock.py +19 -3
- package/bin/_lib_clone_copy.py +142 -0
- package/bin/_lib_codex_conversation.py +32 -0
- package/bin/_lib_codex_conversation_query.py +297 -49
- package/bin/_lib_codex_conversation_watch.py +59 -14
- package/bin/_lib_codex_drain_admission.py +214 -0
- package/bin/_lib_codex_metadata.py +18 -1
- package/bin/_lib_codex_publication_barrier.py +277 -0
- package/bin/_lib_codex_rollout_container.py +502 -0
- package/bin/_lib_conversation_dispatch.py +3 -0
- package/bin/_lib_conversation_query.py +106 -59
- package/bin/_lib_conversation_retention.py +1605 -640
- package/bin/_lib_cost_provenance.py +53 -2
- package/bin/_lib_detector_access_registry.py +3817 -0
- package/bin/_lib_doctor.py +714 -69
- package/bin/_lib_five_hour.py +126 -0
- package/bin/_lib_fmt.py +11 -0
- package/bin/_lib_ingest_frontier.py +414 -20
- package/bin/_lib_journal.py +141 -10
- package/bin/_lib_jsonl.py +5 -4
- package/bin/_lib_legacy_spool.py +126 -0
- package/bin/_lib_legacy_writer_evidence.py +136 -0
- package/bin/_lib_merge_gate.py +231 -86
- package/bin/_lib_meter_rate_change.py +10 -0
- package/bin/_lib_outcome_quarantine.py +690 -0
- package/bin/_lib_perf.py +27 -1
- package/bin/_lib_pricing.py +199 -9
- package/bin/_lib_pricing_check.py +20 -4
- package/bin/_lib_quarantine.py +154 -0
- package/bin/_lib_quota_calibration.py +53 -8
- package/bin/_lib_quota_copy.py +2 -1
- package/bin/_lib_quota_model.py +288 -46
- package/bin/_lib_reclaim_planner.py +792 -0
- package/bin/_lib_rederive.py +319 -82
- package/bin/_lib_segment_summary.py +35 -2
- package/bin/_lib_snapshot_cache.py +21 -0
- package/bin/_lib_sqlite_close.py +81 -0
- package/bin/_lib_statusline_candidates.py +292 -23
- package/bin/_lib_subscription_weeks.py +100 -17
- package/bin/_lib_tick_stats.py +217 -17
- package/bin/_lib_wal_checkpoint.py +1077 -0
- package/bin/_lib_write_budget.py +483 -0
- package/bin/_lib_write_io.py +703 -0
- package/bin/cctally +31 -1
- package/dashboard/static/assets/{ConversationsView-CEZ-BUyi.js → ConversationsView-Bc8LCOAZ.js} +1 -1
- package/dashboard/static/assets/{DoctorModal-CE1oH_Zy.js → DoctorModal-D--qVBwe.js} +1 -1
- package/dashboard/static/assets/ModalRoot-DLPgyAM_.js +1 -0
- package/dashboard/static/assets/{ProjectsDrillPanel-B3DZoCeD.js → ProjectsDrillPanel-z53a4v4M.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-CHp9EgLP.js → SourceDetailModal-Bw0T8W-W.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-5iKST6jo.js → UpdateModal-D5AKx4Yw.js} +1 -1
- package/dashboard/static/assets/index-BKITk1CT.css +1 -0
- package/dashboard/static/assets/{index-Cm0VTxJC.js → index-BXDOzSng.js} +3 -3
- package/dashboard/static/assets/{outlineNavigation-DkXGL8J6.js → outlineNavigation-JObY7cUY.js} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +19 -1
- package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +0 -1
- package/dashboard/static/assets/index-Dt4tqf43.css +0 -1
|
@@ -45,6 +45,7 @@ import sqlite3
|
|
|
45
45
|
import sys
|
|
46
46
|
|
|
47
47
|
import _lib_accounts # pure stdlib kernel; UNATTRIBUTED sentinel (#341)
|
|
48
|
+
from _lib_five_hour import latest_five_hour_reading
|
|
48
49
|
from _cctally_core import _command_as_of, eprint, now_utc_iso, open_db, parse_iso_datetime
|
|
49
50
|
|
|
50
51
|
|
|
@@ -983,9 +984,10 @@ def _latest_seven_day_and_window(
|
|
|
983
984
|
overlooked (#769 S11, #824). A `weekly_observation_held` row's five-hour
|
|
984
985
|
fields are the writing tick's own, so it is the freshest statement of which
|
|
985
986
|
window is current; excluding it would resolve an older window and report the
|
|
986
|
-
live block closed.
|
|
987
|
-
|
|
988
|
-
NULL
|
|
987
|
+
live block closed. It is the key of the newest row that CARRIES a five-hour
|
|
988
|
+
reading (#825/#832 spec 4.7 revision 7, ``latest_five_hour_reading``): a
|
|
989
|
+
retired replica or a weekly-only reading can be the newest row, and its NULL
|
|
990
|
+
key would report the live block closed and leave its weekly end stale.
|
|
989
991
|
|
|
990
992
|
**The weekly value is CREDIT-AWARE**, which is the #835 change. It comes from
|
|
991
993
|
the most recent row that is not a post-floor stale replica — the same band
|
|
@@ -1065,8 +1067,15 @@ def _latest_seven_day_and_window(
|
|
|
1065
1067
|
row = cur.fetchone()
|
|
1066
1068
|
if row is None:
|
|
1067
1069
|
return None, None
|
|
1068
|
-
# Axis 1: the five-hour window key, from the
|
|
1069
|
-
|
|
1070
|
+
# Axis 1: the five-hour window key, held-inclusive, from the newest
|
|
1071
|
+
# five-hour READING of the newest row's own account (#825/#832 spec
|
|
1072
|
+
# 4.7 revision 7, the one shared selection). The newest row can be a
|
|
1073
|
+
# retired replica or a weekly-only reading, which names no window.
|
|
1074
|
+
# Merged mode scopes the weekly walk to that account too, so an
|
|
1075
|
+
# account-blind selection here would pair another account's window
|
|
1076
|
+
# with this account's weekly value.
|
|
1077
|
+
reading = latest_five_hour_reading(conn, account_key=row["account_key"])
|
|
1078
|
+
key = None if reading is None else reading.window_key
|
|
1070
1079
|
# Axis 2: the weekly value. The scope, the bands and the floor all come
|
|
1071
1080
|
# from the NEWEST row and are resolved ONCE, because the walk may not
|
|
1072
1081
|
# leave that scope — so there is nothing to memoize per candidate.
|
|
@@ -2689,10 +2698,10 @@ def cmd_five_hour_breakdown(args: argparse.Namespace) -> int:
|
|
|
2689
2698
|
])
|
|
2690
2699
|
continue
|
|
2691
2700
|
m = ev
|
|
2692
|
-
cum =
|
|
2701
|
+
cum = _c._fmt_usd_accounting(m['blockCostUSD'], 6)
|
|
2693
2702
|
marg = (
|
|
2694
2703
|
"n/a" if m["marginalCostUSD"] is None
|
|
2695
|
-
else
|
|
2704
|
+
else _c._fmt_usd_accounting(m['marginalCostUSD'], 6)
|
|
2696
2705
|
)
|
|
2697
2706
|
# #834 S1 (#836): the EFFECTIVE weekly value, never the raw one. A
|
|
2698
2707
|
# weekly-clamped tick stored a reading no reader ever saw, and this
|
|
@@ -2718,6 +2727,7 @@ def cmd_five_hour_breakdown(args: argparse.Namespace) -> int:
|
|
|
2718
2727
|
|
|
2719
2728
|
def _backfill_five_hour_blocks(
|
|
2720
2729
|
conn: sqlite3.Connection, *, only_missing: bool = False,
|
|
2730
|
+
journaled_first: "dict | None" = None,
|
|
2721
2731
|
) -> int:
|
|
2722
2732
|
"""One-shot historical backfill of five_hour_blocks from existing
|
|
2723
2733
|
weekly_usage_snapshots data. Idempotent via UNIQUE(five_hour_window_key)
|
|
@@ -2740,9 +2750,58 @@ def _backfill_five_hour_blocks(
|
|
|
2740
2750
|
re-materializes only the open block (block-only, no milestones — the
|
|
2741
2751
|
journaled 5h-milestone evts carry their own values and resolve block_id
|
|
2742
2752
|
against this row), keeping the pass O(open windows) instead of O(all blocks).
|
|
2753
|
+
|
|
2754
|
+
#825/#832 fix round J4 (#954): the rebuild projection reproduces the open
|
|
2755
|
+
block live ingest left, on its accounting columns too.
|
|
2756
|
+
|
|
2757
|
+
* ``journaled_first`` maps ``(account_key, five_hour_window_key)`` to the
|
|
2758
|
+
window's FIRST WRITER — its first effective `snapshot_accept` decision
|
|
2759
|
+
in journal order (`_cctally_journal.
|
|
2760
|
+
journaled_block_first_observations`) — and supplies the block's
|
|
2761
|
+
immutable start values (`first_observed_at_utc`,
|
|
2762
|
+
`five_hour_resets_at`, `block_start_at`,
|
|
2763
|
+
`seven_day_pct_at_block_start`) whenever it is present. Live ingest's
|
|
2764
|
+
upsert never updates those columns: the writer that materializes the
|
|
2765
|
+
row owns them. Two things separate that writer from the earliest
|
|
2766
|
+
surviving snapshot. A five-hour credit's stale-replica removal can
|
|
2767
|
+
delete its snapshot (the band starts at floor10 of the arming tick, so
|
|
2768
|
+
it reaches the genuine pre-credit reading); the snapshot still observed
|
|
2769
|
+
the block, because the removal retires its weekly VALUE, not the
|
|
2770
|
+
observation (J4). And a reading captured earlier can be appended, and
|
|
2771
|
+
so derived, later (fix round J5, 825-832-CD4-V01): its row survives
|
|
2772
|
+
with the earlier capture, but it updated a block another reading had
|
|
2773
|
+
already opened. Neither survivor-derived pick reproduces live. A
|
|
2774
|
+
window without an entry (a payload without an explicit account) falls
|
|
2775
|
+
back to the earliest surviving snapshot.
|
|
2776
|
+
* The projection stamps the computation-time pricing provenance of the
|
|
2777
|
+
totals it computes, through the live upsert's own helper
|
|
2778
|
+
(`_block_pricing_provenance_json`), so a successor's
|
|
2779
|
+
`five_hour_block_close` freezes it exactly as after live ingest. The
|
|
2780
|
+
ordinary (legacy, pre-journal) backfill keeps NULL: its blocks predate
|
|
2781
|
+
provenance, which `_lib_cost_provenance.historical_close_marker` reads
|
|
2782
|
+
as unproven.
|
|
2743
2783
|
"""
|
|
2744
2784
|
_c = _cctally()
|
|
2745
2785
|
inserted = 0
|
|
2786
|
+
journaled_first = (journaled_first or {}) if only_missing else {}
|
|
2787
|
+
|
|
2788
|
+
def _first_row(key, acct):
|
|
2789
|
+
"""The window's start row: its first writer (J4/J5), else the
|
|
2790
|
+
earliest surviving snapshot."""
|
|
2791
|
+
journaled = journaled_first.get((acct, key))
|
|
2792
|
+
if journaled is not None:
|
|
2793
|
+
return journaled
|
|
2794
|
+
return conn.execute(
|
|
2795
|
+
"""
|
|
2796
|
+
SELECT five_hour_resets_at, captured_at_utc, weekly_percent
|
|
2797
|
+
FROM weekly_usage_snapshots
|
|
2798
|
+
WHERE five_hour_window_key = ? AND account_key = ?
|
|
2799
|
+
AND five_hour_percent IS NOT NULL
|
|
2800
|
+
ORDER BY captured_at_utc ASC, id ASC LIMIT 1
|
|
2801
|
+
""",
|
|
2802
|
+
(key, acct),
|
|
2803
|
+
).fetchone()
|
|
2804
|
+
|
|
2746
2805
|
try:
|
|
2747
2806
|
# Iterate distinct windows that have BOTH a canonical key AND a
|
|
2748
2807
|
# 5h percent. The percent guard is critical: MAX(five_hour_percent)
|
|
@@ -2812,18 +2871,9 @@ def _backfill_five_hour_blocks(
|
|
|
2812
2871
|
for key, acct in keys:
|
|
2813
2872
|
if (acct, key) in seen_keys:
|
|
2814
2873
|
continue
|
|
2815
|
-
# Same
|
|
2874
|
+
# Same start-row pick the insert loop makes below, so the
|
|
2816
2875
|
# ownership interval and the inserted row cannot disagree.
|
|
2817
|
-
anchor_row =
|
|
2818
|
-
"""
|
|
2819
|
-
SELECT five_hour_resets_at
|
|
2820
|
-
FROM weekly_usage_snapshots
|
|
2821
|
-
WHERE five_hour_window_key = ? AND account_key = ?
|
|
2822
|
-
AND five_hour_percent IS NOT NULL
|
|
2823
|
-
ORDER BY captured_at_utc ASC, id ASC LIMIT 1
|
|
2824
|
-
""",
|
|
2825
|
-
(key, acct),
|
|
2826
|
-
).fetchone()
|
|
2876
|
+
anchor_row = _first_row(key, acct)
|
|
2827
2877
|
if anchor_row is None:
|
|
2828
2878
|
continue
|
|
2829
2879
|
try:
|
|
@@ -2849,22 +2899,14 @@ def _backfill_five_hour_blocks(
|
|
|
2849
2899
|
conn.execute("BEGIN IMMEDIATE")
|
|
2850
2900
|
try:
|
|
2851
2901
|
for key, acct in keys:
|
|
2852
|
-
#
|
|
2853
|
-
#
|
|
2854
|
-
#
|
|
2855
|
-
#
|
|
2902
|
+
# The start row (the first writer, else the MIN-captured
|
|
2903
|
+
# survivor) defines the immutable block boundary values
|
|
2904
|
+
# (deterministic — picking "any in-window row" would be
|
|
2905
|
+
# nondeterministic under seconds-level Anthropic ISO jitter
|
|
2906
|
+
# that the canonical key collapses). Scoped to
|
|
2856
2907
|
# (window_key, account_key) so a shared window's per-account
|
|
2857
2908
|
# blocks each read their own account's boundary rows (#341).
|
|
2858
|
-
min_row =
|
|
2859
|
-
"""
|
|
2860
|
-
SELECT five_hour_resets_at, captured_at_utc, weekly_percent
|
|
2861
|
-
FROM weekly_usage_snapshots
|
|
2862
|
-
WHERE five_hour_window_key = ? AND account_key = ?
|
|
2863
|
-
AND five_hour_percent IS NOT NULL
|
|
2864
|
-
ORDER BY captured_at_utc ASC, id ASC LIMIT 1
|
|
2865
|
-
""",
|
|
2866
|
-
(key, acct),
|
|
2867
|
-
).fetchone()
|
|
2909
|
+
min_row = _first_row(key, acct)
|
|
2868
2910
|
max_row = conn.execute(
|
|
2869
2911
|
"""
|
|
2870
2912
|
SELECT captured_at_utc, weekly_percent, five_hour_percent
|
|
@@ -2941,9 +2983,15 @@ def _backfill_five_hour_blocks(
|
|
|
2941
2983
|
# JSONL if cache.db is unreadable.
|
|
2942
2984
|
# skip_sync=True: backfill runs inside open_db(); a sync_cache
|
|
2943
2985
|
# cascade here would reopen cache.db recursively.
|
|
2986
|
+
pricing_evidence = {} if only_missing else None
|
|
2944
2987
|
totals = _c._compute_block_totals(
|
|
2945
2988
|
block_start_dt, last_obs_dt, skip_sync=True,
|
|
2946
2989
|
owner_key=key, windows=ownership.get(acct, []),
|
|
2990
|
+
pricing_evidence=pricing_evidence,
|
|
2991
|
+
)
|
|
2992
|
+
pricing_provenance_json = (
|
|
2993
|
+
_c._block_pricing_provenance_json(pricing_evidence)
|
|
2994
|
+
if pricing_evidence is not None else None
|
|
2947
2995
|
)
|
|
2948
2996
|
|
|
2949
2997
|
cur = conn.execute(
|
|
@@ -2963,12 +3011,14 @@ def _backfill_five_hour_blocks(
|
|
|
2963
3011
|
total_cache_create_tokens,
|
|
2964
3012
|
total_cache_read_tokens,
|
|
2965
3013
|
total_cost_usd,
|
|
3014
|
+
pricing_provenance_json,
|
|
2966
3015
|
is_closed,
|
|
2967
3016
|
created_at_utc,
|
|
2968
3017
|
last_updated_at_utc,
|
|
2969
3018
|
account_key
|
|
2970
3019
|
)
|
|
2971
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
3020
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
3021
|
+
?)
|
|
2972
3022
|
""",
|
|
2973
3023
|
(
|
|
2974
3024
|
key,
|
|
@@ -2985,6 +3035,7 @@ def _backfill_five_hour_blocks(
|
|
|
2985
3035
|
totals["cache_create_tokens"],
|
|
2986
3036
|
totals["cache_read_tokens"],
|
|
2987
3037
|
totals["cost_usd"],
|
|
3038
|
+
pricing_provenance_json,
|
|
2988
3039
|
is_closed,
|
|
2989
3040
|
projection_created_at,
|
|
2990
3041
|
projection_updated_at,
|
package/bin/_cctally_forecast.py
CHANGED
|
@@ -97,6 +97,7 @@ from _lib_forecast import (
|
|
|
97
97
|
# exercises). doctor's _iso_z deliberately keeps its own divergent copy.
|
|
98
98
|
_ensure_sibling_loaded("_lib_json_envelope")
|
|
99
99
|
from _lib_json_envelope import _iso_z
|
|
100
|
+
from _lib_five_hour import latest_five_hour_reading
|
|
100
101
|
|
|
101
102
|
# #620 S1 D11: the one affordance shape every warning state renders.
|
|
102
103
|
_ensure_sibling_loaded("_lib_alert_scope")
|
|
@@ -153,10 +154,30 @@ def _shape_week_samples(rows, *, include_held: bool,
|
|
|
153
154
|
return out
|
|
154
155
|
|
|
155
156
|
|
|
157
|
+
def _week_rows_by_capture(cursor, *, by_id: bool) -> list:
|
|
158
|
+
"""One week's sample rows in capture order: ``captured_at_utc`` ascending,
|
|
159
|
+
then ``id`` when ``by_id`` (the row's trailing column, which this strips).
|
|
160
|
+
|
|
161
|
+
#901 Amendment 19 T1 (spec I1): the reader returns every row of the week
|
|
162
|
+
anyway, so ordering them here costs no structure beyond its own result,
|
|
163
|
+
where ``ORDER BY captured_at_utc`` sorted the whole week through a temp
|
|
164
|
+
b-tree — and one subscription week is not a bound. ``list.sort`` is stable
|
|
165
|
+
over the same scan SQLite's in-memory sorter consumed, so rows tied on one
|
|
166
|
+
capture instant keep the order they had.
|
|
167
|
+
"""
|
|
168
|
+
rows = cursor.fetchall()
|
|
169
|
+
if by_id:
|
|
170
|
+
rows.sort(key=lambda row: (row[0], row[-1]))
|
|
171
|
+
return [row[:-1] for row in rows]
|
|
172
|
+
rows.sort(key=lambda row: row[0])
|
|
173
|
+
return rows
|
|
174
|
+
|
|
175
|
+
|
|
156
176
|
def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime,
|
|
157
177
|
*, account_key: "str | None" = None,
|
|
158
178
|
include_held: bool = False,
|
|
159
|
-
include_account: bool = False
|
|
179
|
+
include_account: bool = False,
|
|
180
|
+
include_scope: bool = False):
|
|
160
181
|
"""Return (week_start_at, week_end_at, list[(captured_at, percent, five_hr)])
|
|
161
182
|
for the subscription week containing `now_utc`, or None if no snapshot
|
|
162
183
|
exists for the current week.
|
|
@@ -206,6 +227,14 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
|
|
|
206
227
|
``include_account`` appends the physical account key after the held flag
|
|
207
228
|
for the multi-account Current Week card only. The default wire-independent
|
|
208
229
|
sample shape and merged forecast/cost reads remain unchanged.
|
|
230
|
+
|
|
231
|
+
``include_scope`` (#825/#832 spec 4.7 revision 7) appends a fourth element,
|
|
232
|
+
``(week_start_date, week_start_at_texts)``: the week scope the samples were
|
|
233
|
+
read under, in the form ``latest_five_hour_reading`` takes
|
|
234
|
+
(``week_start_at_texts`` is ``None`` on the date-only fallback). A caller
|
|
235
|
+
reading the five-hour axis selects its reading under exactly this scope
|
|
236
|
+
rather than from the newest sample, which a retired replica or a
|
|
237
|
+
weekly-only reading leaves without a five-hour value.
|
|
209
238
|
"""
|
|
210
239
|
# #341: optional account scoping — same predicate on every leg so the whole
|
|
211
240
|
# window resolution stays consistent (a real key or the `unattributed`
|
|
@@ -264,6 +293,8 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
|
|
|
264
293
|
we_date = dt.date.fromisoformat(drow[1])
|
|
265
294
|
week_start_at = dt.datetime.combine(ws_date, dt.time(0, 0), local_tz).astimezone(dt.timezone.utc)
|
|
266
295
|
week_end_at = dt.datetime.combine(we_date + dt.timedelta(days=1), dt.time(0, 0), local_tz).astimezone(dt.timezone.utc)
|
|
296
|
+
# One date: `idx_usage_week_time` (week_start_date, captured_at_utc
|
|
297
|
+
# DESC, id DESC) serves this order outright, with no sorter.
|
|
267
298
|
rows = conn.execute(
|
|
268
299
|
"SELECT captured_at_utc, weekly_percent, five_hour_percent, "
|
|
269
300
|
" weekly_observation_held" + _owner_column + " "
|
|
@@ -275,6 +306,8 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
|
|
|
275
306
|
samples = _shape_week_samples(rows, include_held=include_held,
|
|
276
307
|
include_account=include_account)
|
|
277
308
|
samples = [s for s in samples if s[0] <= now_utc]
|
|
309
|
+
if include_scope:
|
|
310
|
+
return week_start_at, week_end_at, samples, (drow[0], None)
|
|
278
311
|
return week_start_at, week_end_at, samples
|
|
279
312
|
row = chosen
|
|
280
313
|
week_start_at = parse_iso_datetime(row[0], "week_start_at")
|
|
@@ -293,19 +326,22 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
|
|
|
293
326
|
matching_texts.append(r[0])
|
|
294
327
|
chosen_date = chosen[2]
|
|
295
328
|
placeholders = ",".join("?" * len(matching_texts))
|
|
296
|
-
rows = conn.execute(
|
|
329
|
+
rows = _week_rows_by_capture(conn.execute(
|
|
297
330
|
f"SELECT captured_at_utc, weekly_percent, five_hour_percent, "
|
|
298
|
-
f" weekly_observation_held{_owner_column}
|
|
331
|
+
f" weekly_observation_held{_owner_column}"
|
|
332
|
+
f"{', id' if include_account else ''} "
|
|
299
333
|
f"FROM weekly_usage_snapshots "
|
|
300
334
|
f"WHERE (week_start_at IN ({placeholders}) "
|
|
301
335
|
f" OR (week_start_at IS NULL AND week_start_date = ?))"
|
|
302
|
-
f"{_acct_pred}{_held_pred}
|
|
303
|
-
f"ORDER BY captured_at_utc{_sample_order}",
|
|
336
|
+
f"{_acct_pred}{_held_pred}",
|
|
304
337
|
tuple(matching_texts) + (chosen_date,) + _acct_p,
|
|
305
|
-
)
|
|
338
|
+
), by_id=include_account)
|
|
306
339
|
samples = _shape_week_samples(rows, include_held=include_held,
|
|
307
340
|
include_account=include_account)
|
|
308
341
|
samples = [s for s in samples if s[0] <= now_utc]
|
|
342
|
+
if include_scope:
|
|
343
|
+
return (week_start_at, week_end_at, samples,
|
|
344
|
+
(chosen_date, tuple(matching_texts)))
|
|
309
345
|
return week_start_at, week_end_at, samples
|
|
310
346
|
|
|
311
347
|
|
|
@@ -353,6 +389,42 @@ def _apply_midweek_reset_override(
|
|
|
353
389
|
return week_start_at, samples
|
|
354
390
|
|
|
355
391
|
|
|
392
|
+
def _current_week_five_hour_percent(conn, now_utc, week_scope, *,
|
|
393
|
+
account_key, week_start_at,
|
|
394
|
+
week_start_before_override):
|
|
395
|
+
"""The percentage of ``_current_week_five_hour_reading``, or ``None``."""
|
|
396
|
+
reading = _current_week_five_hour_reading(
|
|
397
|
+
conn, now_utc, week_scope, account_key=account_key,
|
|
398
|
+
week_start_at=week_start_at,
|
|
399
|
+
week_start_before_override=week_start_before_override)
|
|
400
|
+
return None if reading is None else reading.percent
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _current_week_five_hour_reading(conn, now_utc, week_scope, *,
|
|
404
|
+
account_key, week_start_at,
|
|
405
|
+
week_start_before_override):
|
|
406
|
+
"""The current week's five-hour reading: the newest five-hour READING
|
|
407
|
+
under the current-week read's own scope (#825/#832 spec 4.7 revision 7).
|
|
408
|
+
Its percent and reset come from one row, so a caller that shows both
|
|
409
|
+
shows one reading.
|
|
410
|
+
|
|
411
|
+
``week_scope`` is ``_fetch_current_week_snapshots(include_scope=True)``'s
|
|
412
|
+
fourth element. When ``_apply_midweek_reset_override`` moved the week's
|
|
413
|
+
start, its new start bounds the captures exactly as it bounded the samples.
|
|
414
|
+
``account_key`` ``None`` keeps the merged read's account-blind contract.
|
|
415
|
+
"""
|
|
416
|
+
week_start_date, week_start_at_texts = week_scope
|
|
417
|
+
floor = (week_start_at if week_start_at != week_start_before_override
|
|
418
|
+
else None)
|
|
419
|
+
reading = latest_five_hour_reading(
|
|
420
|
+
conn, account_key=account_key,
|
|
421
|
+
as_of_utc=_iso_z(now_utc),
|
|
422
|
+
captured_at_or_after_utc=None if floor is None else _iso_z(floor),
|
|
423
|
+
week_start_date=week_start_date,
|
|
424
|
+
week_start_at_in=week_start_at_texts)
|
|
425
|
+
return reading
|
|
426
|
+
|
|
427
|
+
|
|
356
428
|
def _resolve_current_budget_window(conn, now_utc, *, account_key=None):
|
|
357
429
|
"""Return ``(effective_week_start_dt, week_end_dt)`` for the subscription
|
|
358
430
|
week containing ``now_utc``, honoring a mid-week reset re-anchor; or
|
|
@@ -786,8 +858,13 @@ def _dpp_candidate_comparability(regime, week_start_at, week_end_at, *,
|
|
|
786
858
|
|
|
787
859
|
- `"comparable"` — in regime, and its own population passes the support
|
|
788
860
|
test.
|
|
789
|
-
- `"incomparable"` — in regime, population read,
|
|
790
|
-
|
|
861
|
+
- `"incomparable"` — in regime, population read, and `apply_regime`
|
|
862
|
+
rejected it. That is drift (the support test failed), or a week outside
|
|
863
|
+
the regime's coefficient era (`unvalidated-coefficient-era`, #978 spec
|
|
864
|
+
4.6), whose units were weighted with another era's coefficients and so
|
|
865
|
+
cannot be compared with this regime's rate either, or a week with no
|
|
866
|
+
local population to weigh (`no-local-history`). All three mean the
|
|
867
|
+
same thing to the selector: the candidate is not comparable.
|
|
791
868
|
- `"excluded"` — on the far side of a rate boundary.
|
|
792
869
|
- `"unreadable"` — the population could not be read at all. Folding this
|
|
793
870
|
into `"excluded"` made a locked `cache.db` look like twelve weeks that
|
|
@@ -816,7 +893,8 @@ def _dpp_candidate_comparability(regime, week_start_at, week_end_at, *,
|
|
|
816
893
|
cache_conn_factory=cache_conn_factory)
|
|
817
894
|
except Exception: # noqa: BLE001
|
|
818
895
|
return "unreadable"
|
|
819
|
-
applied = qcg.apply_regime(regime, entries
|
|
896
|
+
applied = qcg.apply_regime(regime, entries,
|
|
897
|
+
interval=(week_start_at, week_end_at))
|
|
820
898
|
if isinstance(applied, qcg.ApplyRejection):
|
|
821
899
|
return "incomparable"
|
|
822
900
|
return "comparable"
|
|
@@ -1268,7 +1346,12 @@ def _calibrated_week_detail(
|
|
|
1268
1346
|
week_start_at, horizon, account_key=account_key)
|
|
1269
1347
|
except sqlite3.Error:
|
|
1270
1348
|
return CalibratedWeek(code="unavailable")
|
|
1271
|
-
|
|
1349
|
+
# The interval is the whole week the projection extrapolates over, not
|
|
1350
|
+
# the entries' span: a week straddling the regime's coefficient era is
|
|
1351
|
+
# refused as `unvalidated-coefficient-era` even when every retained entry
|
|
1352
|
+
# so far is in-era (#978 spec 4.6).
|
|
1353
|
+
applied = qcg.apply_regime(read.regime, entries,
|
|
1354
|
+
interval=(week_start_at, week_end_at))
|
|
1272
1355
|
if isinstance(applied, qcg.ApplyRejection):
|
|
1273
1356
|
return CalibratedWeek(code=applied.value)
|
|
1274
1357
|
|
|
@@ -1371,12 +1454,14 @@ def _load_forecast_inputs(
|
|
|
1371
1454
|
c = _cctally()
|
|
1372
1455
|
# #769 S11 (#824): ONE read, held-inclusive, split per axis below.
|
|
1373
1456
|
fetched = _fetch_current_week_snapshots(
|
|
1374
|
-
conn, now_utc, account_key=account_key, include_held=True
|
|
1457
|
+
conn, now_utc, account_key=account_key, include_held=True,
|
|
1458
|
+
include_scope=True)
|
|
1375
1459
|
if fetched is None:
|
|
1376
1460
|
return None
|
|
1377
|
-
week_start_at, week_end_at, observations = fetched
|
|
1461
|
+
week_start_at, week_end_at, observations, week_scope = fetched
|
|
1378
1462
|
if not observations:
|
|
1379
1463
|
return None
|
|
1464
|
+
week_start_before_override = week_start_at
|
|
1380
1465
|
|
|
1381
1466
|
# Mid-week reset override: shift week_start_at to the effective
|
|
1382
1467
|
# reset moment and drop pre-reset samples so elapsed/remaining math
|
|
@@ -1400,8 +1485,16 @@ def _load_forecast_inputs(
|
|
|
1400
1485
|
p_now = latest[1]
|
|
1401
1486
|
# The five-hour axis, which is why the read is held-inclusive: a held row
|
|
1402
1487
|
# exists precisely because its five-hour reading GREW, so the newest
|
|
1403
|
-
#
|
|
1404
|
-
|
|
1488
|
+
# READING of either kind is the current five-hour percentage. It is
|
|
1489
|
+
# selected through the one shared selection (#825/#832 spec 4.7 revision
|
|
1490
|
+
# 7) under this read's own scope: the account, the week, `now_utc`, and
|
|
1491
|
+
# the mid-week reset's lower bound when the override applied. The newest
|
|
1492
|
+
# observation is not the answer, because a retired replica or a
|
|
1493
|
+
# weekly-only reading carries no five-hour value.
|
|
1494
|
+
five_hr = _current_week_five_hour_percent(
|
|
1495
|
+
conn, now_utc, week_scope, account_key=account_key,
|
|
1496
|
+
week_start_at=week_start_at,
|
|
1497
|
+
week_start_before_override=week_start_before_override)
|
|
1405
1498
|
|
|
1406
1499
|
elapsed_hours = (now_utc - week_start_at).total_seconds() / 3600.0
|
|
1407
1500
|
total_hours = (week_end_at - week_start_at).total_seconds() / 3600.0
|
|
@@ -2518,7 +2611,7 @@ def cmd_report(args: argparse.Namespace) -> int:
|
|
|
2518
2611
|
m_rows: list[list[str]] = []
|
|
2519
2612
|
for idx, m in enumerate(milestone_rows, start=1):
|
|
2520
2613
|
pct = f"{int(m['percent_threshold'])}%"
|
|
2521
|
-
cum =
|
|
2614
|
+
cum = c._fmt_usd_accounting(float(m['cumulative_cost_usd']), 6)
|
|
2522
2615
|
marg = c.observation_gap_marginal_cell(
|
|
2523
2616
|
m["marginal_cost_usd"],
|
|
2524
2617
|
withheld=(idx - 1) in gap_disclosure.withheld_indexes,
|