fix(projection): align projection with ADR 0002

Use lifetime written bytes from the current segment's newest published
sample in the headline formula, select the Percentage-Used-implied
baseline after two increments in the segment (otherwise show the
too-coarse fact), and judge horizon agreement across existing horizons.
Make the vacuous projection tests assert unconditionally and add
coverage for implied-baseline gating and single-horizon agreement.
This commit is contained in:
xavierk
2026-10-05 19:45:38 +05:30
parent 117082af8c
commit 96fd1702fa
5 changed files with 168 additions and 45 deletions
+110 -22
View File
@@ -74,12 +74,13 @@ def _insert_day(conn, day, bw=1024*1024*100, coverage=0.95, samples=24):
conn.commit()
def _insert_sample(conn, ts, pu=5):
def _insert_sample(conn, ts, pu=5, bytes_written=512000000000, segment_id=1):
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),
"percentage_used, bytes_written, bytes_read, power_on_hours, segment_id) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
(ts, "/dev/nvme0n1", bytes_written // 512000, 500000, pu, bytes_written,
256000000000, 8765, segment_id),
)
conn.commit()
@@ -223,6 +224,88 @@ class TestImpliedBaseline:
result = compute_projection(store, _clock())
assert result.baseline_tier == BaselineTier.VERIFIED
def _setup_implied(self, store, pus):
"""No override baseline; one sample per PU value inside the segment."""
_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)
for i, pu in enumerate(pus):
_insert_sample(store, "2026-09-%02dT10:00:00+00:00" % (20 + i), pu=pu)
_insert_complete_local_days(store, "2026-09-29", 1)
@pytest.mark.parametrize("pus", [[5], [5, 5, 5], [5, 6]])
def test_implied_unavailable_before_two_increments(self, store, pus):
self._setup_implied(store, pus)
result = compute_projection(store, _clock())
assert result.confidence_state == ConfidenceState.UNSUPPORTED
assert result.baseline_tier == BaselineTier.NONE
assert result.headline_remaining_seconds is None
assert "vendor wear estimate too coarse to imply endurance" in result.contributing_facts
def test_implied_unavailable_ignores_increments_in_other_segments(self, store):
_insert_segment(store, opened_at="2026-09-01T00:00:00+00:00")
_insert_segment(store, opened_at="2026-09-15T00:00:00+00:00", identity_key="nqn.new")
_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)
# Two increments in the old segment, none in the current one
_insert_sample(store, "2026-09-02T10:00:00+00:00", pu=3, segment_id=1)
_insert_sample(store, "2026-09-03T10:00:00+00:00", pu=4, segment_id=1)
_insert_sample(store, "2026-09-20T10:00:00+00:00", pu=5, segment_id=2)
_insert_complete_local_days(store, "2026-09-29", 1)
result = compute_projection(store, _clock())
assert result.baseline_tier == BaselineTier.NONE
assert "vendor wear estimate too coarse to imply endurance" in result.contributing_facts
def test_implied_unavailable_when_pu_out_of_range(self, store):
self._setup_implied(store, [0, 1, 2])
store.execute("UPDATE samples SET percentage_used = 255 WHERE id = 3")
store.commit()
result = compute_projection(store, _clock())
assert result.baseline_tier == BaselineTier.NONE
assert "vendor wear estimate too coarse to imply endurance" in result.contributing_facts
def test_implied_eligible_after_two_increments(self, store):
self._setup_implied(store, [5, 6, 7])
W_t = 512000000000
result = compute_projection(store, _clock())
assert result.baseline_tier == BaselineTier.IMPLIED
# Implied baseline is never Supported-eligible
assert result.confidence_state == ConfidenceState.LIMITED
assert "vendor wear estimate too coarse to imply endurance" not in result.contributing_facts
E_implied = 100 * W_t / 7
regime_bytes = 20 * 1024 * 1024 * 100
regime_start = datetime(2026, 9, 10, 0, 0, 0, tzinfo=timezone.utc)
rate = regime_bytes / int((_clock() - regime_start).total_seconds())
assert abs(result.headline_remaining_seconds - max(E_implied - W_t, 0) / rate) < 1.0
def test_override_wins_over_implied(self, store):
_insert_baseline(store, tbw_tb=1.0, verified=True)
self._setup_implied(store, [5, 6, 7])
result = compute_projection(store, _clock())
assert result.baseline_tier == BaselineTier.VERIFIED
class TestHorizonAgreement:
def test_single_horizon_passes_agreement(self, store):
"""ADR 0002 §8: agreement is judged across existing horizons; one passes."""
_insert_baseline(store, tbw_tb=1.0, verified=True)
_insert_segment(store)
_open_period(store)
# 14 days covers only the 7-day horizon
for i in range(14):
d = (datetime(2026, 9, 17) + 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 list(result.scenario_range.rates) == [7]
assert not any("regime only" in f for f in result.contributing_facts)
assert result.confidence_state == ConfidenceState.SUPPORTED
class TestZeroRate:
def test_zero_rate_fixed_phrase(self, store):
@@ -339,17 +422,19 @@ class TestArithmetic:
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)
W_t = 512000000000 # lifetime written bytes of the newest sample
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5, bytes_written=W_t)
_insert_complete_local_days(store, "2026-09-29", 1)
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
assert 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)
actual_wc = int((_clock() - period_start).total_seconds())
rate = regime_bytes / actual_wc
# W_t is lifetime written bytes, not the regime delta
expected = max(E_rated - W_t, 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)
@@ -359,6 +444,7 @@ class TestArithmetic:
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)
_insert_complete_local_days(store, "2026-09-29", 1)
r_slow = compute_projection(store, _clock())
store.execute("DELETE FROM day_aggregates")
@@ -368,9 +454,9 @@ class TestArithmetic:
_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
assert r_slow.headline_remaining_seconds is not None
assert r_fast.headline_remaining_seconds is not None
assert r_fast.headline_remaining_seconds < r_slow.headline_remaining_seconds
# ===========================================================================
@@ -394,17 +480,19 @@ class TestSustainedRegimeRate:
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)
W_t = 512000000000
_insert_sample(store, "2026-09-30T10:00:00+00:00", pu=5, bytes_written=W_t)
_insert_complete_local_days(store, "2026-09-29", 1)
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
assert result.headline_remaining_seconds is not None
E = 10.0 * TBW_TO_BYTES
expected_seconds = max(E - W_t, 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."""