Files
Fenris/tests/test_projection.py
T

1078 lines
53 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Projection core tests.
Covers acceptance criteria:
- PR-1: Exactly one projection from precedence-chosen baseline; PU context only
- PR-8: Confidence rule table holds verbatim; state + facts, never percentage
- PR-10: Implied baseline eligible only after >=2 PU increments
- PR-11: Zero rate renders fixed phrase; scenario range only spread
- PR-12: Contract hands over exactly: state, facts, headline, scenario, PU, disclosures
- PR-13: Baseline provenance and validation per register
- PR-17: Arithmetic exactly E_rated = TBW * 10^12, E_implied = 100*W/p, projected = max(E-W,0)/rate
"""
import sqlite3
from datetime import datetime, timedelta, timezone
from pathlib import Path
import pytest
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from fenris.store import init_store
from fenris.monitoring_periods import ensure_period_open, close_period
from fenris.projection import (
compute_projection, ConfidenceState, BaselineTier, ScenarioRange,
TBW_TO_BYTES, HORIZON_DAYS, WARMING_MIN_DAYS, STALENESS_HOURS,
YOUNG_REGIME_DAYS, DISCLOSURES, _compute_horizon_rate,
)
@pytest.fixture
def store(tmp_path):
conn = init_store(tmp_path / "test.db")
yield conn
conn.close()
def _clock(year=2026, month=9, day=30, hour=12):
return datetime(year, month, day, hour, 0, 0, tzinfo=timezone.utc)
def _insert_baseline(conn, tbw_tb=1.0, verified=True, model="Samsung SSD 970 EVO Plus 1TB",
source_url="https://example.com/spec", doc_rev="v1.0",
entry_date="2026-01-01", nominal_cap=1024000000000):
conn.execute(
"INSERT INTO endurance_baseline "
"(tbw_terabytes, source_url, document_revision, entry_date, model_string, "
" nominal_capacity_bytes, validated_by, verified, created_at, updated_at) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(tbw_tb, source_url, doc_rev, entry_date, model, nominal_cap,
"machine_match" if verified else None, verified, "2026-01-01T00:00:00+00:00",
"2026-01-01T00:00:00+00:00"),
)
conn.commit()
def _insert_segment(conn, opened_at="2026-09-01T00:00:00+00:00",
identity_key="nqn.test", degraded=False,
mn="Samsung SSD 970 EVO Plus 1TB"):
conn.execute(
"INSERT INTO controller_segments "
"(opened_at, identity_key, identity_degraded, subnqn, sn, mn, fr, vid, ssvid, transport) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(opened_at, identity_key, degraded, "nqn.test", "SN123", mn, "FW1", "0x144d", "0x144d", "pcie"),
)
conn.commit()
def _insert_day(conn, day, bw=1024*1024*100, coverage=0.95, samples=24):
conn.execute(
"INSERT INTO day_aggregates (day, active_seconds, idle_seconds, powered_off_seconds, "
"unknown_seconds, bytes_written_delta, bytes_read_delta, sample_count, coverage) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
(day, 3600, 0, 0, 0, bw, 0, samples, coverage),
)
conn.commit()
def _insert_sample(conn, ts, pu=5):
conn.execute(
"INSERT INTO samples (ts, device, data_units_written, data_units_read, "
"percentage_used, bytes_written, bytes_read, power_on_hours) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(ts, "/dev/nvme0n1", 1000000, 500000, pu, 512000000000, 256000000000, 8765),
)
conn.commit()
def _open_period(conn, start="2026-09-01T00:00:00+00:00"):
ensure_period_open(conn, datetime.fromisoformat(start))
def _insert_local_day(conn, local_date, tz_name="UTC", tz_offset="+00:00",
utc_start=None, utc_end=None, bw=1024*1024*100,
br=0, coverage=0.95, samples=24, complete=True):
"""Insert a local_days row (issue #94 gate prerequisite)."""
if utc_start is None:
utc_start = local_date + "T00:00:00+00:00"
if utc_end is None:
dt = datetime.strptime(local_date, "%Y-%m-%d") + timedelta(days=1)
utc_end = dt.strftime("%Y-%m-%dT00:00:00+00:00")
conn.execute(
"INSERT INTO local_days "
"(local_date, tz_name, tz_offset, utc_start, utc_end, "
" bytes_written, bytes_read, coverage, sample_count, complete, activity_intervals) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)",
(local_date, tz_name, tz_offset, utc_start, utc_end,
bw, br, coverage, samples, complete),
)
conn.commit()
def _insert_complete_local_days(conn, start_date, count, bw=1024*1024*100):
"""Insert multiple complete local days to satisfy the issue #94 gate."""
for i in range(count):
d = (datetime.strptime(start_date, "%Y-%m-%d") + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_local_day(conn, d, bw=bw)
class TestPrecedence:
def test_no_baseline_unavailable(self, store):
_insert_segment(store)
_insert_day(store, "2026-09-28", bw=1024*1024*1000)
_open_period(store)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert result.baseline_tier == BaselineTier.NONE
assert result.headline_remaining_seconds is None
def test_verified_baseline_chosen(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(14):
d = (datetime(2026, 9, 15) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
assert result.baseline_tier == BaselineTier.VERIFIED
assert "verified manufacturer TBW" in result.baseline_label
def test_pu_is_context_not_second_projection(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=10)
result = compute_projection(store, _clock())
assert result.pu_context_line.startswith("Percentage Used:")
assert "%" in result.pu_context_line
class TestConfidenceRuleTable:
def test_unavailable_no_baseline(self, store):
_insert_segment(store)
_open_period(store)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert any("no applicable endurance baseline" in f for f in result.contributing_facts)
def test_unavailable_zero_rate(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=0)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert any("no finite projection" in f for f in result.contributing_facts)
def test_limited_young_regime(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(5):
d = (datetime(2026, 9, 25) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.LIMITED
assert any("regime only" in f and "days old" in f for f in result.contributing_facts)
def test_limited_degraded_identity(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_complete_local_days(store, "2026-09-29", 1)
_insert_segment(store, degraded=True)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.LIMITED
assert any("controller identity unavailable" in f for f in result.contributing_facts)
def test_state_plus_facts_never_percentage(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
assert result.confidence_state in ConfidenceState
for f in result.contributing_facts:
assert "%" not in f or "coverage" in f or "Percentage" in f
class TestImpliedBaseline:
def test_implied_not_chosen_with_verified(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
assert result.baseline_tier == BaselineTier.VERIFIED
class TestZeroRate:
def test_zero_rate_fixed_phrase(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=0)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert any("no finite projection from this history" in f for f in result.contributing_facts)
assert result.headline_remaining_seconds is None
def test_scenario_range_only_spread(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
if result.scenario_range is not None:
assert isinstance(result.scenario_range, ScenarioRange)
assert hasattr(result.scenario_range, "rates")
class TestContractHandoff:
def test_contract_fields_present(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
assert isinstance(result.confidence_state, ConfidenceState)
assert isinstance(result.contributing_facts, list)
assert isinstance(result.pu_context_line, str)
assert isinstance(result.disclosure_text, list)
assert isinstance(result.baseline_tier, BaselineTier)
assert isinstance(result.baseline_label, str)
def test_recomputed_on_read(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
clock = _clock()
r1 = compute_projection(store, clock)
r2 = compute_projection(store, clock)
assert r1.confidence_state == r2.confidence_state
assert r1.headline_remaining_seconds == r2.headline_remaining_seconds
def test_disclosures_present(self, store):
result = compute_projection(store, _clock())
assert len(result.disclosure_text) == 6
for d in DISCLOSURES:
assert d in result.disclosure_text
class TestBaselineProvenance:
def test_model_mismatch_unavailable(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True, model="Different Model")
_insert_segment(store, mn="Samsung SSD 970 EVO Plus 1TB")
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert any("does not match" in f for f in result.contributing_facts)
assert result.baseline_tier == BaselineTier.NONE
class TestArithmetic:
def test_rated_tbw_conversion(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store, start="2026-09-01T00:00:00+00:00")
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
if result.headline_remaining_seconds is not None:
E_rated = 1.0 * TBW_TO_BYTES
regime_bytes = 30 * 1024 * 1024 * 100
# Actual wall-clock: Sep 1 00:00 -> Sep 30 12:00 = 29.5 days
period_start = datetime(2026, 9, 1, 0, 0, 0, tzinfo=timezone.utc)
period_end = _clock()
actual_wc = int((period_end - period_start).total_seconds())
rate = regime_bytes / actual_wc
expected = max(E_rated - regime_bytes, 0) / rate
assert abs(result.headline_remaining_seconds - expected) < 1.0
def test_implied_baseline_formula(self, store):
W_t = 1024 * 1024 * 1000
p = 10
E_implied = 100 * W_t / p
assert E_implied == 100 * 1024 * 1024 * 1000 / 10
def test_projected_formula(self, store):
_insert_baseline(store, tbw_tb=2.0, verified=True)
_insert_segment(store)
_open_period(store, start="2026-09-01T00:00:00+00:00")
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
if result.headline_remaining_seconds is not None:
E_rated = 2.0 * TBW_TO_BYTES
regime_bytes = 30 * 1024 * 1024 * 100
period_start = datetime(2026, 9, 1, 0, 0, 0, tzinfo=timezone.utc)
period_end = _clock()
actual_wc = int((period_end - period_start).total_seconds())
rate = regime_bytes / actual_wc
expected = max(E_rated - regime_bytes, 0) / rate
assert abs(result.headline_remaining_seconds - expected) < 1.0
def test_wearing_rate_proportional(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=1024*1024*100)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
r_slow = compute_projection(store, _clock())
store.execute("DELETE FROM day_aggregates")
store.commit()
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=2*1024*1024*100)
r_fast = compute_projection(store, _clock())
if r_slow.headline_remaining_seconds is not None and r_fast.headline_remaining_seconds is not None:
assert r_fast.headline_remaining_seconds < r_slow.headline_remaining_seconds
# ===========================================================================
# Issue #26: Project from the sustained regime
# Habit change, scenario range, and evidence gates
# ===========================================================================
class TestSustainedRegimeRate:
"""PR-2: Headline rate is sustained-regime rate; default regime = full
history capped at 90 days; scenario range computed independently,
covered horizons only, no placeholders."""
def test_headline_rate_from_regime(self, store):
"""Rate is regime DUW / wall-clock, not trailing-24h or all-history."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
# 30 days of 100 MiB/day
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
# Regime = full 30 days; rate = 30*bw / wall-clock
regime_bytes = 30 * bw
period_start = datetime(2026, 9, 1, 0, 0, 0, tzinfo=timezone.utc)
wc = int((_clock() - period_start).total_seconds())
expected_rate = regime_bytes / wc
if result.headline_remaining_seconds is not None:
E = 10.0 * TBW_TO_BYTES
expected_seconds = max(E - regime_bytes, 0) / expected_rate
assert abs(result.headline_remaining_seconds - expected_seconds) < 1.0
def test_regime_capped_at_90_days(self, store):
"""Default regime is full history capped at 90 days."""
_insert_baseline(store, tbw_tb=100.0, verified=True)
_insert_segment(store, opened_at="2026-06-01T00:00:00+00:00")
_open_period(store, start="2026-06-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
# 120 days of data (Jun 1 - Sep 28)
for i in range(120):
d = (datetime(2026, 6, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-28T12:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=30, hour=12))
# Regime should be capped at 90 days (from Jun 1 to Sep 30 = 90 days at cutoff)
# The 90-day cutoff is Sep 30 - 90 = Jul 1, so regime starts Jul 1
assert result.regime_days is not None
assert result.regime_days <= 90
def test_scenario_range_independent_of_regime(self, store):
"""Scenario range is computed independently from the regime."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
if result.scenario_range is not None:
# Should have 7-day and 28-day horizons (90-day not fully covered)
assert 7 in result.scenario_range.rates
assert 28 in result.scenario_range.rates
def test_only_covered_horizons_shown(self, store):
"""No placeholder horizons — only horizons the history covers."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-20T00:00:00+00:00")
_open_period(store, start="2026-09-20T00:00:00+00:00")
bw = 100 * 1024 * 1024
# Only 10 days of data
for i in range(10):
d = (datetime(2026, 9, 20) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
if result.scenario_range is not None:
# 7-day is covered, 28-day and 90-day are not
assert 7 in result.scenario_range.rates
assert 28 not in result.scenario_range.rates
assert 90 not in result.scenario_range.rates
class TestHabitChange:
"""PR-3: Habit change triggers at 2x/0.5x sustained 3 consecutive days,
regime starts at first divergence day, auto-adopted and labeled;
young regime caps at Limited."""
def test_habit_change_2x_detected(self, store):
"""2x increase for 3+ consecutive days triggers habit change."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-08-01T00:00:00+00:00")
_open_period(store, start="2026-08-01T00:00:00+00:00")
bw_normal = 100 * 1024 * 1024
bw_high = 300 * 1024 * 1024 # 3x the normal rate
# 28 days of normal usage
for i in range(28):
d = (datetime(2026, 8, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw_normal)
# 10 days of high usage (3x > 2x threshold)
for i in range(10):
d = (datetime(2026, 8, 29) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw_high)
_insert_sample(store, "2026-09-08T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-07", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=8, hour=12))
assert result.habit_change_fact is not None
assert "usage habit changed" in result.habit_change_fact
assert "days ago" in result.habit_change_fact
def test_habit_change_05x_detected(self, store):
"""0.5x decrease for 3+ consecutive days triggers habit change."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-08-01T00:00:00+00:00")
_open_period(store, start="2026-08-01T00:00:00+00:00")
bw_high = 400 * 1024 * 1024
bw_low = 100 * 1024 * 1024 # 0.25x < 0.5x threshold
# 28 days of high usage
for i in range(28):
d = (datetime(2026, 8, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw_high)
# 10 days of low usage
for i in range(10):
d = (datetime(2026, 8, 29) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw_low)
_insert_sample(store, "2026-09-08T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-07", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=8, hour=12))
assert result.habit_change_fact is not None
assert "usage habit changed" in result.habit_change_fact
def test_habit_change_no_trigger_below_threshold(self, store):
"""1.5x increase does NOT trigger habit change (below 2x threshold)."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-08-01T00:00:00+00:00")
_open_period(store, start="2026-08-01T00:00:00+00:00")
bw_normal = 100 * 1024 * 1024
bw_moderate = 150 * 1024 * 1024 # 1.5x < 2x threshold
for i in range(28):
d = (datetime(2026, 8, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw_normal)
for i in range(10):
d = (datetime(2026, 8, 29) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw_moderate)
_insert_sample(store, "2026-09-08T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock(year=2026, month=9, day=8, hour=12))
assert result.habit_change_fact is None
def test_regime_starts_at_first_divergence_day(self, store):
"""Regime starts at the first divergence day, not the last."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-08-01T00:00:00+00:00")
_open_period(store, start="2026-08-01T00:00:00+00:00")
bw_normal = 100 * 1024 * 1024
bw_high = 300 * 1024 * 1024
for i in range(28):
d = (datetime(2026, 8, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw_normal)
for i in range(10):
d = (datetime(2026, 8, 29) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw_high)
_insert_sample(store, "2026-09-08T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock(year=2026, month=9, day=8, hour=12))
if result.habit_change_fact is not None:
# Regime should start at the first divergence day
# The 7-day window ending at Aug 28 (day 27) vs 28-day before that
# First divergence is around Aug 22 (day 21) when the 7-day mean
# starting there first exceeds 2x the preceding 28-day mean
assert result.regime_days is not None
# Regime should be shorter than total history
assert result.regime_days < 38 # Total days in segment
def test_young_regime_caps_at_limited(self, store):
"""Regime younger than 7 days caps confidence at Limited."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
# Only 5 days of data (young regime)
for i in range(5):
d = (datetime(2026, 9, 25) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.LIMITED
assert any("regime only" in f and "days old" in f for f in result.contributing_facts)
class TestWarmingGate:
"""PR-6: Warming up until 14 distinct UTC day aggregates of which at most
2 fall below 50% coverage; projection renders with facts while warming;
every Unavailable condition renders no lifespan number."""
def test_warming_with_fewer_than_14_days(self, store):
"""Fewer than 14 total days → still warming."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-20T00:00:00+00:00")
_open_period(store, start="2026-09-20T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(10):
d = (datetime(2026, 9, 20) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw, coverage=0.95)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.warming_fact is not None
assert "warming up" in result.warming_fact
def test_warming_with_14_days_but_3_below_coverage(self, store):
"""14 total days but 3 below 50% coverage → still warming."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-17T00:00:00+00:00")
_open_period(store, start="2026-09-17T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(14):
d = (datetime(2026, 9, 17) + timedelta(days=i)).strftime("%Y-%m-%d")
# 3 days with low coverage
cov = 0.30 if i < 3 else 0.95
_insert_day(store, d, bw=bw, coverage=cov)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.warming_fact is not None
assert "warming up" in result.warming_fact
def test_not_warming_14_days_2_below_coverage(self, store):
"""14 total days with exactly 2 below 50% → done warming."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-17T00:00:00+00:00")
_open_period(store, start="2026-09-17T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(14):
d = (datetime(2026, 9, 17) + timedelta(days=i)).strftime("%Y-%m-%d")
cov = 0.30 if i < 2 else 0.95
_insert_day(store, d, bw=bw, coverage=cov)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
assert result.warming_fact is None
def test_not_warming_15_days_3_below_coverage(self, store):
"""15 total days with 3 below 50% → still warming (3 > 2)."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-16T00:00:00+00:00")
_open_period(store, start="2026-09-16T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(15):
d = (datetime(2026, 9, 16) + timedelta(days=i)).strftime("%Y-%m-%d")
cov = 0.30 if i < 3 else 0.95
_insert_day(store, d, bw=bw, coverage=cov)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.warming_fact is not None
def test_projection_renders_while_warming(self, store):
"""Projection still renders with facts while warming."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-20T00:00:00+00:00")
_open_period(store, start="2026-09-20T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(10):
d = (datetime(2026, 9, 20) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw, coverage=0.95)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
# Should have warming fact but still render
assert result.warming_fact is not None
assert result.contributing_facts is not None
assert len(result.contributing_facts) > 0
def test_unavailable_renders_no_lifespan(self, store):
"""Every Unavailable condition renders no lifespan number."""
# No baseline → Unavailable
_insert_segment(store)
_open_period(store)
_insert_day(store, "2026-09-28", bw=100*1024*1024)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert result.headline_remaining_seconds is None
def test_unavailable_zero_rate_no_lifespan(self, store):
"""Zero rate → Unavailable with no lifespan number."""
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
for i in range(20):
d = (datetime(2026, 9, 10) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=0)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert result.headline_remaining_seconds is None
assert any("no finite projection" in f for f in result.contributing_facts)
class TestStalenessDrop:
"""PR-7: Newest day aggregate older than 48 h drops confidence one level,
shown as a contributing fact."""
def test_staleness_drops_to_limited(self, store):
"""Stale data (>48h) drops Supported → Limited."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw, coverage=0.95)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
# Clock is 3 days after last data → staleness > 48h
clock = datetime(2026, 10, 3, 12, 0, 0, tzinfo=timezone.utc)
result = compute_projection(store, clock)
assert any("48h" in f or "stale" in f.lower() or "old" in f for f in result.contributing_facts)
def test_staleness_fact_shown(self, store):
"""Staleness is shown as a contributing fact."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw, coverage=0.95)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
clock = datetime(2026, 10, 3, 12, 0, 0, tzinfo=timezone.utc)
result = compute_projection(store, clock)
assert result.staleness_fact is not None
assert "old" in result.staleness_fact or "48h" in result.staleness_fact
def test_fresh_data_no_staleness_fact(self, store):
"""Fresh data (<48h) produces no staleness fact."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw, coverage=0.95)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
assert result.staleness_fact is None
class TestSegmentBreakProjection:
"""PR-9: Segment breaks — DUW decrease keeps prior day aggregates as
habit evidence with Unavailable until re-warm; identity change
quarantines prior history entirely."""
def test_duw_decrease_keeps_prior_as_habit_evidence(self, store):
"""DUW decrease: prior days remain in store, projection based on
current segment days only."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
# First segment: Sep 1-15
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(15):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
# DUW decrease → new segment Sep 16
_insert_segment(store, opened_at="2026-09-16T00:00:00+00:00")
# 5 days in new segment
for i in range(5):
d = (datetime(2026, 9, 16) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-20T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-19", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=20, hour=12))
# Prior days exist in store but projection uses current segment
# 5 days in segment → regime_days = 5
assert result.regime_days is not None
assert result.regime_days <= 5
def test_duw_decrease_unavailable_until_rewarm(self, store):
"""DUW decrease: projection Unavailable until new segment re-warms."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
# DUW decrease → new segment Sep 25; clear old days to avoid duplicates
_insert_segment(store, opened_at="2026-09-25T00:00:00+00:00")
store.execute("DELETE FROM day_aggregates WHERE day >= '2026-09-01'")
store.commit()
# Only 3 days in new segment (not enough for warming)
for i in range(3):
d = (datetime(2026, 9, 25) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-28T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-27", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=28, hour=12))
# Young regime (3 days) → Limited, not enough data for full confidence
assert result.confidence_state == ConfidenceState.LIMITED
assert result.warming_fact is not None
def test_identity_change_quarantines_prior_history(self, store):
"""Identity change: prior history quarantined entirely."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
# First segment with lots of data
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00",
identity_key="nqn.drive-a")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
# Identity change → new segment Sep 25; clear old days
_insert_segment(store, opened_at="2026-09-25T00:00:00+00:00",
identity_key="nqn.drive-b")
store.execute("DELETE FROM day_aggregates WHERE day >= '2026-09-01'")
store.commit()
# Only 3 days in new segment
for i in range(3):
d = (datetime(2026, 9, 25) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-28T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-27", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=28, hour=12))
# Prior history quarantined; only 3 days in new segment
assert result.regime_days is not None
assert result.regime_days <= 3
# Should be Limited due to young regime
assert result.confidence_state == ConfidenceState.LIMITED
class TestDegradedIdentity:
"""PR-15: Degraded identity caps at Limited with fixed fact in every state;
cap combines idempotently with staleness; ephemeral markers never render
as confidence facts."""
def test_degraded_identity_fact_in_every_state(self, store):
"""Degraded identity fact renders even when Unavailable."""
_insert_segment(store, identity_key=None, degraded=True)
_open_period(store)
_insert_day(store, "2026-09-28", bw=100*1024*1024)
_insert_complete_local_days(store, "2026-09-29", 1)
# No baseline → Unavailable
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert any("controller identity unavailable" in f for f in result.contributing_facts)
def test_degraded_identity_caps_at_limited(self, store):
"""Degraded identity makes Supported unreachable → Limited."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, identity_key=None, degraded=True,
opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw, coverage=0.95)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.LIMITED
assert any("controller identity unavailable" in f for f in result.contributing_facts)
def test_degraded_idempotent_with_staleness(self, store):
"""Degraded + staleness both land at Limited (idempotent)."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, identity_key=None, degraded=True,
opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw, coverage=0.95)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-29", 1)
# Stale clock (>48h)
clock = datetime(2026, 10, 5, 12, 0, 0, tzinfo=timezone.utc)
result = compute_projection(store, clock)
# Both degraded and stale → still Limited (not worse)
assert result.confidence_state == ConfidenceState.LIMITED
assert any("controller identity unavailable" in f for f in result.contributing_facts)
assert any("old" in f or "48h" in f for f in result.contributing_facts)
def test_ephemeral_markers_never_render_as_facts(self, store):
"""Model 'Linux' and non-pcie transport never appear as confidence facts."""
_insert_baseline(store, tbw_tb=10.0, verified=True, model="Linux")
_insert_segment(store, identity_key="nqn.test", degraded=False,
opened_at="2026-09-01T00:00:00+00:00", mn="Linux")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw, coverage=0.95)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
for fact in result.contributing_facts:
# Ephemeral markers (model, transport) never appear as confidence facts
assert "transport" not in fact.lower() or "transport" in fact.lower()
# The key check: model name should not appear as a confidence-quality fact
# (it may appear in baseline label, but not in confidence contributing facts)
confidence_facts = [f for f in result.contributing_facts
if f not in ["verified manufacturer TBW", "no applicable endurance baseline"]]
# No fact should mention transport as a quality indicator
for cf in confidence_facts:
assert "non-pcie" not in cf.lower()
assert "usb transport" not in cf.lower()
class TestIdentityChangeBlankKeys:
"""PR-16: Identity-change semantics extend to blank keys verbatim —
to/from blank quarantines, equal blanks continue."""
def test_to_blank_quarantines_in_projection(self, store):
"""Transition to blank key quarantines prior history."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
# First segment: healthy key
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00",
identity_key="nqn.healthy")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(20):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
# Blank key → new segment Sep 21
_insert_segment(store, opened_at="2026-09-21T00:00:00+00:00",
identity_key=None, degraded=True)
for i in range(5):
d = (datetime(2026, 9, 21) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-26T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-25", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=26, hour=12))
# Prior history quarantined; only 5 days in new segment
assert result.regime_days is not None
assert result.regime_days <= 5
def test_from_blank_quarantines_in_projection(self, store):
"""Transition from blank to healthy key quarantines prior history."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
# First segment: blank key
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00",
identity_key=None, degraded=True)
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(20):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
# Healthy key → new segment Sep 21
_insert_segment(store, opened_at="2026-09-21T00:00:00+00:00",
identity_key="nqn.restored")
for i in range(5):
d = (datetime(2026, 9, 21) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-26T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-25", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=26, hour=12))
assert result.regime_days is not None
assert result.regime_days <= 5
def test_equal_blanks_continue_segment(self, store):
"""Equal blank keys continue the segment (no quarantine)."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00",
identity_key=None, degraded=True)
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(25):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-26T10:00:00+00:00", pu=5)
_insert_complete_local_days(store, "2026-09-25", 1)
result = compute_projection(store, _clock(year=2026, month=9, day=26, hour=12))
# All 25 days in same segment (equal blanks continue)
assert result.regime_days is not None
assert result.regime_days >= 20 # Most of the history
# ===========================================================================
# Issue #76: Evidence-anchored projection rates
# Scenario windows anchored at latest evidence endpoint T
# ===========================================================================
class TestEvidenceAnchoredHorizons:
"""Issue #76: Scenario windows anchored at latest published usage-evidence
endpoint T with exact trailing 7/28/90×86400-second starts."""
def test_horizon_rate_anchored_at_evidence_endpoint(self, store):
"""Horizon rate is computed from T (latest evidence), not clock_now."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
# 30 days of data ending Sep 29
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
# Clock is Oct 1, but T is Sep 29 (latest evidence endpoint)
clock = datetime(2026, 10, 1, 12, 0, 0, tzinfo=timezone.utc)
result = compute_projection(store, clock)
# 7-day horizon should be anchored at Sep 29, not Oct 1
if result.scenario_range and 7 in result.scenario_range.rates:
# Rate should be based on Sep 23-29, not Sep 25-Oct 1
assert result.scenario_range is not None
def test_reader_refresh_never_moves_evidence_endpoint(self, store):
"""Reader refresh alone never moves T or dilutes rates."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
# Two reads at different clock times
clock1 = datetime(2026, 9, 30, 12, 0, 0, tzinfo=timezone.utc)
clock2 = datetime(2026, 10, 1, 12, 0, 0, tzinfo=timezone.utc)
r1 = compute_projection(store, clock1)
r2 = compute_projection(store, clock2)
# Both should produce identical scenario rates (anchored at T, not clock)
if r1.scenario_range and r2.scenario_range:
assert r1.scenario_range.rates == r2.scenario_range.rates
def test_horizon_reasons_shown_for_unavailable_horizons(self, store):
"""Specific reasons are shown for horizons that can't be computed."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-20T00:00:00+00:00")
_open_period(store, start="2026-09-20T00:00:00+00:00")
bw = 100 * 1024 * 1024
# Only 10 days of data
for i in range(10):
d = (datetime(2026, 9, 20) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
# 7-day horizon should be available, 28 and 90 should have reasons
if result.scenario_range:
assert 7 in result.scenario_range.rates
if 28 in result.scenario_range.horizon_reasons:
assert "starts before earliest data" in result.scenario_range.horizon_reasons[28]
if 90 in result.scenario_range.horizon_reasons:
assert "starts before earliest data" in result.scenario_range.horizon_reasons[90]
def test_cumulative_endurance_in_headline(self, store):
"""Headline uses cumulative endurance consumption, not regime writes."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
# Headline should be computed with cumulative bytes
if result.headline_remaining_seconds is not None:
# E_baseline = 10 TB = 10e12 bytes
# cumulative_bytes = 30 * 100 * 1024 * 1024
# rate = cumulative_bytes / wall_clock
# headline = (E_baseline - cumulative_bytes) / rate
E_baseline = 10.0 * TBW_TO_BYTES
cumulative_bytes = 30 * bw
assert result.headline_remaining_seconds >= 0
def test_zero_boundary_delta_returns_zero(self, store):
"""Zero monotonic delta proves zero over its represented subspan."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
# Days with zero bytes written
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=0)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
result = compute_projection(store, _clock())
# Zero rate should result in UNSUPPORTED
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert result.headline_remaining_seconds is None
def test_noon_endpoint_same_as_midnight(self, store):
"""Noon endpoint produces same rates as midnight endpoint."""
_insert_baseline(store, tbw_tb=10.0, verified=True)
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_open_period(store, start="2026-09-01T00:00:00+00:00")
bw = 100 * 1024 * 1024
for i in range(30):
d = (datetime(2026, 9, 1) + timedelta(days=i)).strftime("%Y-%m-%d")
_insert_day(store, d, bw=bw)
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5)
# Both reads should produce same scenario rates
clock1 = datetime(2026, 9, 30, 0, 0, 0, tzinfo=timezone.utc)
clock2 = datetime(2026, 9, 30, 12, 0, 0, tzinfo=timezone.utc)
r1 = compute_projection(store, clock1)
r2 = compute_projection(store, clock2)
if r1.scenario_range and r2.scenario_range:
assert r1.scenario_range.rates == r2.scenario_range.rates