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,13 +1,14 @@
from statistics import mean, median, pstdev
from typing import Dict, Iterable, List, Union
from typing import Any, Dict, Iterable, List, Mapping, Union, cast
def _extract_results(simulation_results: Iterable[Dict[str, float]]) -> List[float]:
def _extract_results(simulation_results: Iterable[object]) -> List[float]:
values: List[float] = []
for item in simulation_results:
if not isinstance(item, dict):
if not isinstance(item, Mapping):
continue
value = item.get("result")
mapping_item = cast(Mapping[str, Any], item)
value = mapping_item.get("result")
if isinstance(value, (int, float)):
values.append(float(value))
return values
@@ -39,8 +40,13 @@ def generate_report(simulation_results: List[Dict[str, float]]) -> Dict[str, Uni
"min": 0.0,
"max": 0.0,
"std_dev": 0.0,
"variance": 0.0,
"percentile_10": 0.0,
"percentile_90": 0.0,
"percentile_5": 0.0,
"percentile_95": 0.0,
"value_at_risk_95": 0.0,
"expected_shortfall_95": 0.0,
}
summary: Dict[str, Union[float, int]] = {
@@ -51,7 +57,21 @@ def generate_report(simulation_results: List[Dict[str, float]]) -> Dict[str, Uni
"max": max(values),
"percentile_10": _percentile(values, 10),
"percentile_90": _percentile(values, 90),
"percentile_5": _percentile(values, 5),
"percentile_95": _percentile(values, 95),
}
summary["std_dev"] = pstdev(values) if len(values) > 1 else 0.0
std_dev = pstdev(values) if len(values) > 1 else 0.0
summary["std_dev"] = std_dev
summary["variance"] = std_dev ** 2
var_95 = summary["percentile_5"]
summary["value_at_risk_95"] = var_95
tail_values = [value for value in values if value <= var_95]
if tail_values:
summary["expected_shortfall_95"] = mean(tail_values)
else:
summary["expected_shortfall_95"] = var_95
return summary