"""Tests for issue #77: Show honest qualifying-day progress and confidence. These tests verify that: 1. Warming progress shows detailed qualifying day breakdown 2. Confidence state accurately reflects qualifying day progress 3. Edge cases like zero-delta intervals and unknown daily shares are handled 4. qualifying_days_progress shows honest qualifying day count """ 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, WARMING_MIN_DAYS, WARMING_COVERAGE_FLOOR, WARMING_MAX_LOW_COVERAGE, ) @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"): 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, "https://example.com/spec", "v1.0", "2026-01-01", model, 1024000000000, "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"): # Convert string to datetime if needed if isinstance(start, str): start = datetime.fromisoformat(start) ensure_period_open(conn, start) conn.commit() 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 TestHonestQualifyingProgress: """Issue #77: Show honest qualifying-day progress and confidence.""" def test_warming_shows_qualifying_vs_nonqualifying(self, store): """Warming progress shows both qualifying and non-qualifying days.""" _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 # 12 qualifying days + 2 non-qualifying (low coverage) for i in range(14): d = (datetime(2026, 9, 20) + timedelta(days=i)).strftime("%Y-%m-%d") cov = 0.30 if i < 2 else 0.95 # First 2 days have low coverage _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()) # After warming, qualifying_days_progress shows honest count assert result.warming_fact is None # Not warming (14 total, 2 below <= WARMING_MAX_LOW_COVERAGE) assert result.qualifying_days_progress is not None assert "12 of 14 qualifying days" in result.qualifying_days_progress assert "2 below coverage" in result.qualifying_days_progress def test_warming_progress_includes_nonqualifying_reason(self, store): """Warming progress indicates why days don't qualify.""" _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 # 13 qualifying days + 1 non-qualifying (zero samples) for i in range(14): d = (datetime(2026, 9, 20) + timedelta(days=i)).strftime("%Y-%m-%d") samples = 0 if i == 0 else 24 # First day has no samples _insert_day(store, d, bw=bw, samples=samples) _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()) # After warming, qualifying_days_progress shows honest count assert result.warming_fact is None # Not warming (14 total, 1 below <= WARMING_MAX_LOW_COVERAGE) assert result.qualifying_days_progress is not None assert "13 of 14 qualifying days" in result.qualifying_days_progress assert "1 below coverage" in result.qualifying_days_progress def test_confidence_updates_as_qualifying_days_increase(self, store): """Confidence state reflects actual qualifying day count.""" _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 # Start with 10 days (below minimum) 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 be warming assert result.warming_fact is not None assert result.confidence_state == ConfidenceState.LIMITED def test_zero_rate_with_qualifying_days(self, store): """Zero rate with qualifying days shows honest state.""" _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") # 14 days with zero bytes written for i in range(14): d = (datetime(2026, 9, 20) + timedelta(days=i)).strftime("%Y-%m-%d") _insert_day(store, d, bw=0, 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()) # Zero rate should be UNSUPPORTED assert result.confidence_state == ConfidenceState.UNSUPPORTED assert result.zero_rate_fact is not None assert "no finite projection" in result.zero_rate_fact def test_qualifying_days_excludes_low_coverage(self, store): """Days below 50% coverage don't count as qualifying.""" _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 # 14 days total, but 3 have low coverage for i in range(14): d = (datetime(2026, 9, 20) + 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()) # Should still be warming (3 days below coverage > WARMING_MAX_LOW_COVERAGE=2) assert result.warming_fact is not None assert "warming up" in result.warming_fact.lower() def test_qualifying_days_excludes_zero_samples(self, store): """Days with zero samples don't count as qualifying.""" _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 # 14 days total, but 3 have zero samples for i in range(14): d = (datetime(2026, 9, 20) + timedelta(days=i)).strftime("%Y-%m-%d") samples = 0 if i < 3 else 24 _insert_day(store, d, bw=bw, samples=samples) _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 still be warming assert result.warming_fact is not None assert "warming up" in result.warming_fact.lower() def test_qualifying_days_progress_after_warming(self, store): """After warming, qualifying_days_progress shows honest count.""" _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 # 14 total days, 12 qualifying (2 below coverage) 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 # First 2 days have low coverage _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()) # Should not be warming (14 total, 2 below coverage <= WARMING_MAX_LOW_COVERAGE) assert result.warming_fact is None # But qualifying_days_progress should show the honest count assert result.qualifying_days_progress is not None assert "12 of 14 qualifying days" in result.qualifying_days_progress assert "2 below coverage" in result.qualifying_days_progress def test_qualifying_days_progress_all_qualifying(self, store): """When all days qualify, qualifying_days_progress shows 100%.""" _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 # 14 days, all qualifying for i in range(14): d = (datetime(2026, 9, 17) + 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 not be warming assert result.warming_fact is None # qualifying_days_progress should show all qualifying assert result.qualifying_days_progress is not None assert "14 of 14 qualifying days" in result.qualifying_days_progress # Should not show "below coverage" since all qualify assert "below coverage" not in result.qualifying_days_progress