"""Pure, offline tests for target_revenue.metrics (WP-0006-T05). No Docker/Postgres required — metrics.compute_metrics is a pure function of (manifest, entries, as_of), like fold.py (TSD §6.1). """ from __future__ import annotations from datetime import datetime, timezone from conftest import golden_entries, golden_manifest from target_revenue import metrics def test_mandatory_facts_present_for_golden_phase(): manifest = golden_manifest() entries = golden_entries() as_of = datetime(2026, 12, 1, tzinfo=timezone.utc) result = metrics.compute_metrics(manifest, entries, as_of) facts = result["facts"] assert facts["initial_target_amount"] == manifest["phase"]["initial_target"]["amount"] assert facts["initial_target_currency"] == manifest["phase"]["initial_target"]["currency"] assert facts["last_ledger_entry_id"] == entries[-1]["id"] assert facts["longstop_at"] == manifest["phase"]["longstop_at"] assert "cumulative_development_credit" in facts assert "cumulative_remission_credit" in facts assert "outstanding_target" in facts assert "is_converted" in facts def test_calculations_and_forecast_are_separately_labeled(): manifest = golden_manifest() entries = golden_entries() as_of = datetime(2026, 12, 1, tzinfo=timezone.utc) result = metrics.compute_metrics(manifest, entries, as_of) assert set(result.keys()) == {"phase", "as_of", "facts", "calculations", "forecasts"} assert "target_satisfaction_percentage" in result["calculations"] assert "projected_conversion_date" in result["forecasts"] # A forecast must never appear inside the facts block. assert "projected_conversion_date" not in result["facts"] def test_target_satisfaction_percentage_matches_fold(): manifest = golden_manifest() entries = golden_entries() as_of = datetime(2026, 12, 1, tzinfo=timezone.utc) result = metrics.compute_metrics(manifest, entries, as_of) facts = result["facts"] initial = facts["initial_target_amount"] expected_pct = round( 100.0 * min( 1.0, (facts["cumulative_development_credit"] + facts["cumulative_remission_credit"]) / initial, ), 4, ) assert result["calculations"]["target_satisfaction_percentage"] == expected_pct def test_no_material_entries_yields_no_velocity_or_forecast(): manifest = golden_manifest() as_of = datetime(2026, 12, 1, tzinfo=timezone.utc) result = metrics.compute_metrics(manifest, [], as_of) assert result["calculations"]["development_credit_velocity_per_day"] is None assert result["calculations"]["remission_credit_velocity_per_day"] is None assert result["calculations"]["days_since_last_material_progress"] is None assert result["forecasts"]["projected_conversion_date"] is None assert result["facts"]["outstanding_target"] == manifest["phase"]["initial_target"]["amount"] def test_converted_phase_has_no_forecast(): manifest = golden_manifest() entries = golden_entries() as_of = datetime(2026, 12, 1, tzinfo=timezone.utc) result = metrics.compute_metrics(manifest, entries, as_of) if result["facts"]["is_converted"]: assert result["forecasts"]["projected_conversion_date"] is None def test_as_of_must_be_timezone_aware(): manifest = golden_manifest() entries = golden_entries() import pytest with pytest.raises(ValueError): metrics.compute_metrics(manifest, entries, datetime(2026, 12, 1))