feat(projection): show staged projection with evidence ladder
Report a projection stage, an evidence ladder and the sustained-regime write rate from compute_projection, per ADR 0012. A provisional lifespan appears after 3 observed hours and carries the hours observed and a short-horizon spread; the complete-local-day condition becomes a fact instead of a gate, and the write rate replaces the lifespan when no baseline applies. The TUI and CLI render the stage, ladder count and full ladder, and drop their own two-sample pre-gates.
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
"""Complete observation day gate tests (issue #94).
|
||||
"""Complete observation day condition tests (issues #94, #106).
|
||||
|
||||
Verifies that the endurance projection is withheld until at least one
|
||||
complete local calendar day has been observed within a monitoring period.
|
||||
Originally a gate (issue #94); ADR 0012 turned it into a contributing
|
||||
fact. The projection is no longer withheld until one complete local
|
||||
calendar day has been observed within a monitoring period, but it says
|
||||
so while that condition is unmet.
|
||||
|
||||
Seams:
|
||||
- compute_projection() → gate check via local_days table
|
||||
- ProjectionResult.contributing_facts → "waiting for a full local observation day"
|
||||
|
||||
Acceptance criteria:
|
||||
- Gate-1: No complete local day → UNSUPPORTED with waiting fact
|
||||
- Gate-2: One complete local day → Limited confidence (if other conditions met)
|
||||
- Gate-3: Partial days don't satisfy the gate
|
||||
- Gate-1: No complete local day → projection renders with waiting fact
|
||||
- Gate-2: One complete local day → no waiting fact
|
||||
- Gate-3: Partial days don't satisfy the condition
|
||||
- Gate-4: CLI and TUI share the same gate via compute_projection()
|
||||
"""
|
||||
import sqlite3
|
||||
@@ -111,14 +113,14 @@ def _open_period(conn, start="2026-09-01T00:00:00+00:00"):
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Gate-1: No complete local day → UNSUPPORTED with waiting fact
|
||||
# Gate-1: No complete local day → projection renders with waiting fact
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGateNoCompleteDay:
|
||||
"""Projection is unavailable before any complete local observation day."""
|
||||
"""Projection still renders before any complete local observation day."""
|
||||
|
||||
def test_no_local_days_unsupported(self, store):
|
||||
"""With no local_days entries, projection is UNSUPPORTED."""
|
||||
def test_no_local_days_states_waiting_fact(self, store):
|
||||
"""With no local_days entries, the projection renders and says it is waiting."""
|
||||
_insert_baseline(store)
|
||||
_insert_segment(store)
|
||||
_open_period(store)
|
||||
@@ -128,11 +130,11 @@ class TestGateNoCompleteDay:
|
||||
_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 result.confidence_state != ConfidenceState.UNSUPPORTED
|
||||
assert any("full local observation day" in f for f in result.contributing_facts)
|
||||
assert result.headline_remaining_seconds is None
|
||||
assert result.headline_remaining_seconds is not None
|
||||
|
||||
def test_only_partial_local_days_unsupported(self, store):
|
||||
def test_only_partial_local_days_state_waiting_fact(self, store):
|
||||
"""Partial (incomplete) local days don't satisfy the gate."""
|
||||
_insert_baseline(store)
|
||||
_insert_segment(store)
|
||||
@@ -146,7 +148,7 @@ class TestGateNoCompleteDay:
|
||||
d = (datetime(2026, 9, 25) + timedelta(days=i)).strftime("%Y-%m-%d")
|
||||
_insert_local_day(store, d, complete=False, coverage=0.3)
|
||||
result = compute_projection(store, _clock())
|
||||
assert result.confidence_state == ConfidenceState.UNSUPPORTED
|
||||
assert result.confidence_state != ConfidenceState.UNSUPPORTED
|
||||
assert any("full local observation day" in f for f in result.contributing_facts)
|
||||
|
||||
def test_gate_before_warming_check(self, store):
|
||||
@@ -166,7 +168,7 @@ class TestGateNoCompleteDay:
|
||||
# The key assertion: gate message should NOT appear when gate IS met
|
||||
assert not any("full local observation day" in f for f in result.contributing_facts)
|
||||
|
||||
def test_complete_legacy_day_without_trusted_activity_does_not_open_gate(
|
||||
def test_complete_legacy_day_without_trusted_activity_keeps_waiting_fact(
|
||||
self, store,
|
||||
):
|
||||
"""A complete flag cannot make unavailable local activity qualify."""
|
||||
@@ -183,11 +185,11 @@ class TestGateNoCompleteDay:
|
||||
"WHERE local_date = '2026-09-29'"
|
||||
)
|
||||
result = compute_projection(store, _clock())
|
||||
assert result.confidence_state == ConfidenceState.UNSUPPORTED
|
||||
assert result.confidence_state != ConfidenceState.UNSUPPORTED
|
||||
assert any("full local observation day" in fact
|
||||
for fact in result.contributing_facts)
|
||||
|
||||
def test_day_split_by_deliberate_pause_does_not_open_gate(self, store):
|
||||
def test_day_split_by_deliberate_pause_keeps_waiting_fact(self, store):
|
||||
"""A complete-looking summary cannot span separate monitoring periods."""
|
||||
_insert_baseline(store)
|
||||
_insert_segment(store)
|
||||
@@ -216,7 +218,7 @@ class TestGateNoCompleteDay:
|
||||
|
||||
result = compute_projection(store, _clock())
|
||||
|
||||
assert result.confidence_state == ConfidenceState.UNSUPPORTED
|
||||
assert result.confidence_state != ConfidenceState.UNSUPPORTED
|
||||
assert any("full local observation day" in fact
|
||||
for fact in result.contributing_facts)
|
||||
|
||||
@@ -290,7 +292,7 @@ class TestGateOneCompleteDay:
|
||||
class TestGatePartialFirstDay:
|
||||
"""Starting monitoring at noon means the partial first day doesn't count."""
|
||||
|
||||
def test_partial_first_day_not_enough(self, store):
|
||||
def test_partial_first_day_keeps_waiting_fact(self, store):
|
||||
"""A single incomplete local day (started at noon) doesn't open the gate."""
|
||||
_insert_baseline(store)
|
||||
_insert_segment(store, opened_at="2026-09-29T12:00:00+00:00")
|
||||
@@ -303,7 +305,7 @@ class TestGatePartialFirstDay:
|
||||
_insert_local_day(store, "2026-09-29", complete=False, coverage=0.5)
|
||||
_insert_local_day(store, "2026-09-30", complete=False, coverage=0.5)
|
||||
result = compute_projection(store, _clock())
|
||||
assert result.confidence_state == ConfidenceState.UNSUPPORTED
|
||||
assert result.confidence_state != ConfidenceState.UNSUPPORTED
|
||||
assert any("full local observation day" in f for f in result.contributing_facts)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user