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:
2025-11-11 18:29:59 +01:00
parent 032e6d2681
commit 795a9f99f4
50 changed files with 5110 additions and 81 deletions

View File

@@ -7,6 +7,7 @@ from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from dependencies import (
get_pricing_metadata,
get_unit_of_work,
require_any_role,
require_project_resource,
@@ -15,6 +16,7 @@ from dependencies import (
from models import MiningOperationType, Project, ScenarioStatus, User
from schemas.project import ProjectCreate, ProjectRead, ProjectUpdate
from services.exceptions import EntityConflictError, EntityNotFoundError
from services.pricing import PricingMetadata
from services.unit_of_work import UnitOfWork
router = APIRouter(prefix="/projects", tags=["Projects"])
@@ -54,6 +56,7 @@ def create_project(
payload: ProjectCreate,
_: User = Depends(require_roles(*MANAGE_ROLES)),
uow: UnitOfWork = Depends(get_unit_of_work),
metadata: PricingMetadata = Depends(get_pricing_metadata),
) -> ProjectRead:
project = Project(**payload.model_dump())
try:
@@ -62,6 +65,9 @@ def create_project(
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail=str(exc)
) from exc
default_settings = uow.ensure_default_pricing_settings(
metadata=metadata).settings
uow.set_project_pricing_settings(created, default_settings)
return _to_read_model(created)
@@ -122,6 +128,7 @@ def create_project_submit(
operation_type: str = Form(...),
description: str | None = Form(None),
uow: UnitOfWork = Depends(get_unit_of_work),
metadata: PricingMetadata = Depends(get_pricing_metadata),
):
def _normalise(value: str | None) -> str | None:
if value is None:
@@ -152,7 +159,7 @@ def create_project_submit(
description=_normalise(description),
)
try:
_require_project_repo(uow).create(project)
created = _require_project_repo(uow).create(project)
except EntityConflictError as exc:
return templates.TemplateResponse(
request,
@@ -167,6 +174,10 @@ def create_project_submit(
status_code=status.HTTP_409_CONFLICT,
)
default_settings = uow.ensure_default_pricing_settings(
metadata=metadata).settings
uow.set_project_pricing_settings(created, default_settings)
return RedirectResponse(
request.url_for("projects.project_list_page"),
status_code=status.HTTP_303_SEE_OTHER,