feat: Enhance dashboard metrics and summary statistics

- Added new summary fields: variance, 5th percentile, 95th percentile, VaR (95%), and expected shortfall (95%) to the dashboard.
- Updated the display logic for summary metrics to handle non-finite values gracefully.
- Modified the chart rendering to include additional percentile points and tail risk metrics in tooltips.

test: Introduce unit tests for consumption, costs, and other modules

- Created a comprehensive test suite for consumption, costs, equipment, maintenance, production, reporting, and simulation modules.
- Implemented fixtures for database setup and teardown using an in-memory SQLite database for isolated testing.
- Added tests for creating, listing, and validating various entities, ensuring proper error handling and response validation.

refactor: Consolidate parameter tests and remove deprecated files

- Merged parameter-related tests into a new test file for better organization and clarity.
- Removed the old parameter test file that was no longer in use.
- Improved test coverage for parameter creation, listing, and validation scenarios.

fix: Ensure proper validation and error handling in API endpoints

- Added validation to reject negative amounts in consumption and production records.
- Implemented checks to prevent duplicate scenario creation and ensure proper error messages are returned.
- Enhanced reporting endpoint tests to validate input formats and expected outputs.
This commit is contained in:
2025-10-20 22:06:39 +02:00
parent 606cb64ff1
commit 434be86b76
28 changed files with 945 additions and 401 deletions

View File

@@ -1,4 +1,4 @@
from typing import Any, Dict, List
from typing import Any, Dict, List, cast
from fastapi import APIRouter, HTTPException, Request, status
from pydantic import BaseModel
@@ -25,14 +25,16 @@ def _validate_payload(payload: Any) -> List[Dict[str, float]]:
detail="Invalid input format",
)
typed_payload = cast(List[Any], payload)
validated: List[Dict[str, float]] = []
for index, item in enumerate(payload):
for index, item in enumerate(typed_payload):
if not isinstance(item, dict):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Entry at index {index} must be an object",
)
value = item.get("result")
value = cast(Dict[str, Any], item).get("result")
if not isinstance(value, (int, float)):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -49,8 +51,13 @@ class ReportSummary(BaseModel):
min: float
max: float
std_dev: float
variance: float
percentile_10: float
percentile_90: float
percentile_5: float
percentile_95: float
value_at_risk_95: float
expected_shortfall_95: float
@router.post("/summary", response_model=ReportSummary)
@@ -65,6 +72,11 @@ async def summary_report(request: Request):
min=float(summary["min"]),
max=float(summary["max"]),
std_dev=float(summary["std_dev"]),
variance=float(summary["variance"]),
percentile_10=float(summary["percentile_10"]),
percentile_90=float(summary["percentile_90"]),
percentile_5=float(summary["percentile_5"]),
percentile_95=float(summary["percentile_95"]),
value_at_risk_95=float(summary["value_at_risk_95"]),
expected_shortfall_95=float(summary["expected_shortfall_95"]),
)