Files
palladium/tests/test_export.py
Robert Helewka a2420ed692 refactor: restructure repo into core/app modules with per-study folders
Reorganize Palladium codebase into a modular architecture with `core/`
shared logic and `app/` Streamlit UI, separating per-study assets into
`studies/YYYYMM_<Vendor>/` folders containing notebooks, seed data, and
configuration. Update README to reflect new structure, add `.gitignore`
entries for `.env` and study exports, and refresh component documentation.
2026-05-20 22:28:12 -04:00

99 lines
3.5 KiB
Python

"""Tests for core.export.report_data — envelope shape and computed totals."""
from __future__ import annotations
from unittest.mock import MagicMock
import pytest
from core.export import build_report_data
from core.export.report_data import _compute_summary, _yearly_totals
class TestComputeSummary:
def test_amazon_connect_totals(self, amazon_connect_seed):
s = _compute_summary(
amazon_connect_seed.BENEFITS,
amazon_connect_seed.COSTS,
0.10,
3,
)
assert s["total_benefits_pv"] == pytest.approx(101_696_791, abs=1500)
assert s["total_costs_pv"] == pytest.approx(22_983_076, abs=1500)
assert s["npv"] == pytest.approx(78_713_715, abs=2000)
assert s["roi_pct"] == pytest.approx(342, abs=1)
assert s["payback_months"] is not None and s["payback_months"] < 6
def test_yearly_breakdown_three_rows(self, amazon_connect_seed):
s = _compute_summary(
amazon_connect_seed.BENEFITS, amazon_connect_seed.COSTS, 0.10, 3
)
assert len(s["yearly_breakdown"]) == 3
assert [r["year"] for r in s["yearly_breakdown"]] == [1, 2, 3]
class TestYearlyTotals:
def test_only_within_horizon(self):
items = [
{"year_values": {"1": 100, "2": 200, "3": 300, "4": 999}},
]
assert _yearly_totals(items, 3) == [100.0, 200.0, 300.0]
def test_skips_invalid_keys(self):
items = [{"year_values": {"1": 50, "abc": 999}}]
assert _yearly_totals(items, 2) == [50.0, 0.0]
class TestBuildReportData:
def _stub_client(self, seed):
c = MagicMock()
c.get_tool_with_data.return_value = {
"tool": {"id": "pid", "name": "T", "report": "rid", "proposal": 7},
"fields": [],
"values": seed.BENEFITS + seed.COSTS,
}
c.get_report.return_value = {
"id": "rid",
"name": "Amazon Connect",
"vendor": "AWS",
"version": "1.0",
"discount_rate": "0.10",
"analysis_period_years": 3,
}
c.export.return_value = {"echoed": True}
return c
def test_envelope_shape(self, amazon_connect_seed):
client = self._stub_client(amazon_connect_seed)
env = build_report_data(client, "pid", study_slug="202602_AmazonConnect")
assert set(env) >= {
"metadata",
"report",
"fields",
"values",
"summary",
"athena_export",
"scenarios",
}
assert env["metadata"]["study_slug"] == "202602_AmazonConnect"
assert env["metadata"]["proposal"] == 7
assert env["values"]["benefits"]
assert env["values"]["costs"]
def test_scenarios_have_three_keys(self, amazon_connect_seed):
client = self._stub_client(amazon_connect_seed)
env = build_report_data(client, "pid")
assert set(env["scenarios"]) == {"conservative", "moderate", "aggressive"}
def test_no_scenarios_flag(self, amazon_connect_seed):
client = self._stub_client(amazon_connect_seed)
env = build_report_data(client, "pid", include_scenarios=False)
assert "scenarios" not in env
def test_local_summary_matches_seed(self, amazon_connect_seed):
client = self._stub_client(amazon_connect_seed)
env = build_report_data(client, "pid", include_scenarios=False)
assert env["summary"]["total_benefits_pv"] == pytest.approx(
101_696_791, abs=1500
)