from __future__ import annotations import pytest from collections.abc import Callable from fastapi.testclient import TestClient from services.unit_of_work import UnitOfWork def _create_project(client: TestClient, name: str) -> int: response = client.post( "/projects", json={ "name": name, "location": "Nevada", "operation_type": "open_pit", "description": "Project for capex testing", }, ) assert response.status_code == 201 return response.json()["id"] def _create_scenario(client: TestClient, project_id: int, name: str) -> int: response = client.post( f"/projects/{project_id}/scenarios", json={ "name": name, "description": "Capex scenario", "status": "draft", "currency": "usd", "primary_resource": "diesel", }, ) assert response.status_code == 201 return response.json()["id"] def test_capex_calculation_html_flow( client: TestClient, unit_of_work_factory: Callable[[], UnitOfWork], ) -> None: project_id = _create_project(client, "Capex HTML Project") scenario_id = _create_scenario(client, project_id, "Capex HTML Scenario") form_page = client.get( f"/calculations/capex?project_id={project_id}&scenario_id={scenario_id}" ) assert form_page.status_code == 200 assert "Capex Planner" in form_page.text response = client.post( f"/calculations/capex?project_id={project_id}&scenario_id={scenario_id}", data={ "components[0][name]": "Crusher", "components[0][category]": "equipment", "components[0][amount]": "1200000", "components[0][currency]": "USD", "components[0][spend_year]": "0", "components[1][name]": "Conveyor", "components[1][category]": "equipment", "components[1][amount]": "800000", "components[1][currency]": "USD", "components[1][spend_year]": "1", "parameters[currency_code]": "USD", "parameters[contingency_pct]": "5", "parameters[discount_rate_pct]": "8", "parameters[evaluation_horizon_years]": "5", "options[persist]": "1", }, ) assert response.status_code == 200 assert "Capex calculation completed successfully." in response.text assert "Capex Summary" in response.text assert "$1,200,000.00" in response.text or "1,200,000" in response.text assert "USD" in response.text with unit_of_work_factory() as uow: assert uow.project_capex is not None assert uow.scenario_capex is not None project_snapshots = uow.project_capex.list_for_project(project_id) scenario_snapshots = uow.scenario_capex.list_for_scenario(scenario_id) assert len(project_snapshots) == 1 assert len(scenario_snapshots) == 1 project_snapshot = project_snapshots[0] scenario_snapshot = scenario_snapshots[0] assert project_snapshot.total_capex is not None assert project_snapshot.contingency_pct is not None assert project_snapshot.total_with_contingency is not None assert float(project_snapshot.total_capex) == pytest.approx(2_000_000) assert float(project_snapshot.contingency_pct) == pytest.approx(5.0) assert float(project_snapshot.total_with_contingency) == pytest.approx( 2_100_000) assert project_snapshot.component_count == 2 assert project_snapshot.currency_code == "USD" assert scenario_snapshot.total_capex is not None assert scenario_snapshot.contingency_amount is not None assert scenario_snapshot.total_with_contingency is not None assert float(scenario_snapshot.total_capex) == pytest.approx(2_000_000) assert float( scenario_snapshot.contingency_amount) == pytest.approx(100_000) assert float(scenario_snapshot.total_with_contingency) == pytest.approx( 2_100_000) assert scenario_snapshot.component_count == 2 assert scenario_snapshot.currency_code == "USD" assert scenario_snapshot.payload is not None def test_capex_calculation_json_flow( client: TestClient, unit_of_work_factory: Callable[[], UnitOfWork], ) -> None: project_id = _create_project(client, "Capex JSON Project") scenario_id = _create_scenario(client, project_id, "Capex JSON Scenario") payload = { "components": [ { "name": "Camp", "category": "infrastructure", "amount": 600000, "currency": "USD", "spend_year": 0, }, { "name": "Power", "category": "infrastructure", "amount": 400000, "currency": "USD", "spend_year": 1, }, ], "parameters": { "currency_code": "USD", "contingency_pct": 12.5, "discount_rate_pct": 6.5, "evaluation_horizon_years": 4, }, "options": {"persist": True}, } response = client.post( f"/calculations/capex?project_id={project_id}&scenario_id={scenario_id}", json=payload, ) assert response.status_code == 200 data = response.json() assert data["currency"] == "USD" assert data["totals"]["overall"] == 1_000_000 assert data["totals"]["contingency_pct"] == pytest.approx(12.5) assert data["totals"]["contingency_amount"] == pytest.approx(125000) assert data["totals"]["with_contingency"] == pytest.approx(1_125_000) by_category = {row["category"]: row for row in data["totals"]["by_category"]} assert by_category["infrastructure"]["amount"] == pytest.approx(1_000_000) assert by_category["infrastructure"]["share"] == pytest.approx(100) assert len(data["timeline"]) == 2 assert data["timeline"][0]["year"] == 0 assert data["timeline"][0]["spend"] == pytest.approx(600_000) assert data["timeline"][1]["cumulative"] == pytest.approx(1_000_000) with unit_of_work_factory() as uow: assert uow.project_capex is not None assert uow.scenario_capex is not None scenario_snapshot = uow.scenario_capex.latest_for_scenario(scenario_id) project_snapshot = uow.project_capex.latest_for_project(project_id) assert scenario_snapshot is not None assert project_snapshot is not None assert scenario_snapshot.total_capex is not None assert project_snapshot.total_with_contingency is not None assert float(scenario_snapshot.total_capex) == pytest.approx(1_000_000) assert float(project_snapshot.total_with_contingency) == pytest.approx( 1_125_000) assert scenario_snapshot.payload is not None payload = scenario_snapshot.payload or {} result_payload = payload.get("result", {}) assert result_payload.get("currency") == "USD" assert result_payload.get("totals", {}).get( "with_contingency") == pytest.approx(1_125_000) assert result_payload.get("totals", {}).get( "overall") == pytest.approx(1_000_000)