feat: Enhance currency handling and validation across scenarios
- Updated form template to prefill currency input with default value and added help text for clarity. - Modified integration tests to assert more descriptive error messages for invalid currency codes. - Introduced new tests for currency normalization and validation in various scenarios, including imports and exports. - Added comprehensive tests for pricing calculations, ensuring defaults are respected and overrides function correctly. - Implemented unit tests for pricing settings repository, ensuring CRUD operations and default settings are handled properly. - Enhanced scenario pricing evaluation tests to validate currency handling and metadata defaults. - Added simulation tests to ensure Monte Carlo runs are accurate and handle various distribution scenarios.
This commit is contained in:
120
tests/test_scenario_pricing.py
Normal file
120
tests/test_scenario_pricing.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from models import MiningOperationType, Project, Scenario, ScenarioStatus
|
||||
from services.pricing import PricingInput, PricingMetadata
|
||||
from services.scenario_evaluation import ScenarioPricingConfig, ScenarioPricingEvaluator
|
||||
|
||||
|
||||
def build_scenario() -> Scenario:
|
||||
project = Project(name="Test Project",
|
||||
operation_type=MiningOperationType.OPEN_PIT)
|
||||
scenario = Scenario(
|
||||
project=project,
|
||||
project_id=1,
|
||||
name="Scenario A",
|
||||
status=ScenarioStatus.ACTIVE,
|
||||
currency="USD",
|
||||
)
|
||||
scenario.id = 1 # simulate persisted entity
|
||||
return scenario
|
||||
|
||||
|
||||
def test_scenario_pricing_evaluator_uses_metadata_defaults() -> None:
|
||||
scenario = build_scenario()
|
||||
evaluator = ScenarioPricingEvaluator(
|
||||
ScenarioPricingConfig(
|
||||
metadata=PricingMetadata(
|
||||
default_currency="USD", default_payable_pct=95)
|
||||
)
|
||||
)
|
||||
inputs = [
|
||||
PricingInput(
|
||||
metal="copper",
|
||||
ore_tonnage=50_000,
|
||||
head_grade_pct=1.0,
|
||||
recovery_pct=90,
|
||||
payable_pct=None,
|
||||
reference_price=9_000,
|
||||
treatment_charge=50_000,
|
||||
smelting_charge=10_000,
|
||||
moisture_pct=9,
|
||||
moisture_threshold_pct=None,
|
||||
moisture_penalty_per_pct=None,
|
||||
impurity_ppm={"As": 120},
|
||||
premiums=10_000,
|
||||
fx_rate=1.0,
|
||||
currency_code=None,
|
||||
)
|
||||
]
|
||||
|
||||
snapshot = evaluator.evaluate(scenario, inputs=inputs)
|
||||
|
||||
assert snapshot.scenario_id == scenario.id
|
||||
assert len(snapshot.results) == 1
|
||||
result = snapshot.results[0]
|
||||
assert result.currency == "USD"
|
||||
assert result.net_revenue > 0
|
||||
|
||||
|
||||
def test_scenario_pricing_evaluator_override_metadata() -> None:
|
||||
scenario = build_scenario()
|
||||
evaluator = ScenarioPricingEvaluator(ScenarioPricingConfig())
|
||||
metadata_override = PricingMetadata(
|
||||
default_currency="CAD",
|
||||
default_payable_pct=90,
|
||||
moisture_threshold_pct=5,
|
||||
moisture_penalty_per_pct=500,
|
||||
)
|
||||
|
||||
inputs = [
|
||||
PricingInput(
|
||||
metal="copper",
|
||||
ore_tonnage=20_000,
|
||||
head_grade_pct=1.2,
|
||||
recovery_pct=88,
|
||||
payable_pct=None,
|
||||
reference_price=8_200,
|
||||
treatment_charge=15_000,
|
||||
smelting_charge=6_000,
|
||||
moisture_pct=6,
|
||||
moisture_threshold_pct=None,
|
||||
moisture_penalty_per_pct=None,
|
||||
premiums=5_000,
|
||||
fx_rate=1.0,
|
||||
currency_code="cad",
|
||||
),
|
||||
PricingInput(
|
||||
metal="gold",
|
||||
ore_tonnage=5_000,
|
||||
head_grade_pct=2.0,
|
||||
recovery_pct=90,
|
||||
payable_pct=None,
|
||||
reference_price=60_000,
|
||||
treatment_charge=10_000,
|
||||
smelting_charge=5_000,
|
||||
moisture_pct=4,
|
||||
moisture_threshold_pct=None,
|
||||
moisture_penalty_per_pct=None,
|
||||
premiums=15_000,
|
||||
fx_rate=1.0,
|
||||
currency_code="cad",
|
||||
),
|
||||
]
|
||||
|
||||
snapshot = evaluator.evaluate(
|
||||
scenario,
|
||||
inputs=inputs,
|
||||
metadata_override=metadata_override,
|
||||
)
|
||||
|
||||
assert len(snapshot.results) == 2
|
||||
assert all(result.currency ==
|
||||
scenario.currency for result in snapshot.results)
|
||||
|
||||
copper_result = snapshot.results[0]
|
||||
expected_payable = 20_000 * 0.012 * 0.88 * 0.90
|
||||
assert copper_result.payable_metal_tonnes == pytest.approx(
|
||||
expected_payable)
|
||||
assert sum(result.net_revenue for result in snapshot.results) > 0
|
||||
Reference in New Issue
Block a user