cctally 1.114.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 +25 -0
- package/bin/_cctally_account.py +32 -8
- package/bin/_cctally_cache.py +4394 -279
- package/bin/_cctally_completion.py +1287 -0
- package/bin/_cctally_config.py +37 -0
- package/bin/_cctally_core.py +178 -2
- package/bin/_cctally_dashboard.py +2 -2
- package/bin/_cctally_dashboard_conversation.py +292 -23
- package/bin/_cctally_dashboard_envelope.py +16 -3
- package/bin/_cctally_dashboard_sources.py +18 -9
- package/bin/_cctally_db.py +316 -5
- package/bin/_cctally_doctor.py +436 -27
- package/bin/_cctally_five_hour.py +82 -31
- package/bin/_cctally_forecast.py +81 -9
- package/bin/_cctally_journal.py +3884 -112
- package/bin/_cctally_journal_repair.py +101 -12
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_project.py +8 -2
- package/bin/_cctally_quarantine.py +652 -0
- package/bin/_cctally_quota.py +26 -4
- package/bin/_cctally_quota_model.py +360 -83
- package/bin/_cctally_record.py +677 -149
- package/bin/_cctally_rederive.py +297 -32
- package/bin/_cctally_refresh.py +84 -13
- package/bin/_cctally_source_analytics.py +23 -2
- package/bin/_cctally_statusline.py +6137 -517
- package/bin/_cctally_store.py +287 -48
- package/bin/_cctally_transcript.py +8 -7
- package/bin/_cctally_tui.py +57 -53
- 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_writer_lock.py +19 -3
- package/bin/_lib_clone_copy.py +142 -0
- package/bin/_lib_codex_conversation_query.py +133 -19
- 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 +11 -3
- package/bin/_lib_detector_access_registry.py +3817 -0
- package/bin/_lib_doctor.py +452 -1
- package/bin/_lib_five_hour.py +126 -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 +29 -302
- package/bin/_lib_meter_rate_change.py +10 -0
- package/bin/_lib_outcome_quarantine.py +690 -0
- package/bin/_lib_pricing.py +7 -2
- 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_rederive.py +94 -5
- package/bin/_lib_segment_summary.py +35 -2
- package/bin/_lib_statusline_candidates.py +292 -23
- package/bin/cctally +23 -1
- package/package.json +14 -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.
|
|
@@ -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")
|
|
@@ -175,7 +176,8 @@ def _week_rows_by_capture(cursor, *, by_id: bool) -> list:
|
|
|
175
176
|
def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime,
|
|
176
177
|
*, account_key: "str | None" = None,
|
|
177
178
|
include_held: bool = False,
|
|
178
|
-
include_account: bool = False
|
|
179
|
+
include_account: bool = False,
|
|
180
|
+
include_scope: bool = False):
|
|
179
181
|
"""Return (week_start_at, week_end_at, list[(captured_at, percent, five_hr)])
|
|
180
182
|
for the subscription week containing `now_utc`, or None if no snapshot
|
|
181
183
|
exists for the current week.
|
|
@@ -225,6 +227,14 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
|
|
|
225
227
|
``include_account`` appends the physical account key after the held flag
|
|
226
228
|
for the multi-account Current Week card only. The default wire-independent
|
|
227
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.
|
|
228
238
|
"""
|
|
229
239
|
# #341: optional account scoping — same predicate on every leg so the whole
|
|
230
240
|
# window resolution stays consistent (a real key or the `unattributed`
|
|
@@ -296,6 +306,8 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
|
|
|
296
306
|
samples = _shape_week_samples(rows, include_held=include_held,
|
|
297
307
|
include_account=include_account)
|
|
298
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)
|
|
299
311
|
return week_start_at, week_end_at, samples
|
|
300
312
|
row = chosen
|
|
301
313
|
week_start_at = parse_iso_datetime(row[0], "week_start_at")
|
|
@@ -327,6 +339,9 @@ def _fetch_current_week_snapshots(conn: sqlite3.Connection, now_utc: dt.datetime
|
|
|
327
339
|
samples = _shape_week_samples(rows, include_held=include_held,
|
|
328
340
|
include_account=include_account)
|
|
329
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)))
|
|
330
345
|
return week_start_at, week_end_at, samples
|
|
331
346
|
|
|
332
347
|
|
|
@@ -374,6 +389,42 @@ def _apply_midweek_reset_override(
|
|
|
374
389
|
return week_start_at, samples
|
|
375
390
|
|
|
376
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
|
+
|
|
377
428
|
def _resolve_current_budget_window(conn, now_utc, *, account_key=None):
|
|
378
429
|
"""Return ``(effective_week_start_dt, week_end_dt)`` for the subscription
|
|
379
430
|
week containing ``now_utc``, honoring a mid-week reset re-anchor; or
|
|
@@ -807,8 +858,13 @@ def _dpp_candidate_comparability(regime, week_start_at, week_end_at, *,
|
|
|
807
858
|
|
|
808
859
|
- `"comparable"` — in regime, and its own population passes the support
|
|
809
860
|
test.
|
|
810
|
-
- `"incomparable"` — in regime, population read,
|
|
811
|
-
|
|
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.
|
|
812
868
|
- `"excluded"` — on the far side of a rate boundary.
|
|
813
869
|
- `"unreadable"` — the population could not be read at all. Folding this
|
|
814
870
|
into `"excluded"` made a locked `cache.db` look like twelve weeks that
|
|
@@ -837,7 +893,8 @@ def _dpp_candidate_comparability(regime, week_start_at, week_end_at, *,
|
|
|
837
893
|
cache_conn_factory=cache_conn_factory)
|
|
838
894
|
except Exception: # noqa: BLE001
|
|
839
895
|
return "unreadable"
|
|
840
|
-
applied = qcg.apply_regime(regime, entries
|
|
896
|
+
applied = qcg.apply_regime(regime, entries,
|
|
897
|
+
interval=(week_start_at, week_end_at))
|
|
841
898
|
if isinstance(applied, qcg.ApplyRejection):
|
|
842
899
|
return "incomparable"
|
|
843
900
|
return "comparable"
|
|
@@ -1289,7 +1346,12 @@ def _calibrated_week_detail(
|
|
|
1289
1346
|
week_start_at, horizon, account_key=account_key)
|
|
1290
1347
|
except sqlite3.Error:
|
|
1291
1348
|
return CalibratedWeek(code="unavailable")
|
|
1292
|
-
|
|
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))
|
|
1293
1355
|
if isinstance(applied, qcg.ApplyRejection):
|
|
1294
1356
|
return CalibratedWeek(code=applied.value)
|
|
1295
1357
|
|
|
@@ -1392,12 +1454,14 @@ def _load_forecast_inputs(
|
|
|
1392
1454
|
c = _cctally()
|
|
1393
1455
|
# #769 S11 (#824): ONE read, held-inclusive, split per axis below.
|
|
1394
1456
|
fetched = _fetch_current_week_snapshots(
|
|
1395
|
-
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)
|
|
1396
1459
|
if fetched is None:
|
|
1397
1460
|
return None
|
|
1398
|
-
week_start_at, week_end_at, observations = fetched
|
|
1461
|
+
week_start_at, week_end_at, observations, week_scope = fetched
|
|
1399
1462
|
if not observations:
|
|
1400
1463
|
return None
|
|
1464
|
+
week_start_before_override = week_start_at
|
|
1401
1465
|
|
|
1402
1466
|
# Mid-week reset override: shift week_start_at to the effective
|
|
1403
1467
|
# reset moment and drop pre-reset samples so elapsed/remaining math
|
|
@@ -1421,8 +1485,16 @@ def _load_forecast_inputs(
|
|
|
1421
1485
|
p_now = latest[1]
|
|
1422
1486
|
# The five-hour axis, which is why the read is held-inclusive: a held row
|
|
1423
1487
|
# exists precisely because its five-hour reading GREW, so the newest
|
|
1424
|
-
#
|
|
1425
|
-
|
|
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)
|
|
1426
1498
|
|
|
1427
1499
|
elapsed_hours = (now_utc - week_start_at).total_seconds() / 3600.0
|
|
1428
1500
|
total_hours = (week_end_at - week_start_at).total_seconds() / 3600.0
|