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.
99 lines
3.5 KiB
Python
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
|
|
)
|