194 lines
7.1 KiB
Python
194 lines
7.1 KiB
Python
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 "Initial 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 "Initial 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)
|