- 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.
78 lines
2.4 KiB
Python
78 lines
2.4 KiB
Python
from statistics import mean, median, pstdev
|
|
from typing import Any, Dict, Iterable, List, Mapping, Union, cast
|
|
|
|
|
|
def _extract_results(simulation_results: Iterable[object]) -> List[float]:
|
|
values: List[float] = []
|
|
for item in simulation_results:
|
|
if not isinstance(item, Mapping):
|
|
continue
|
|
mapping_item = cast(Mapping[str, Any], item)
|
|
value = mapping_item.get("result")
|
|
if isinstance(value, (int, float)):
|
|
values.append(float(value))
|
|
return values
|
|
|
|
|
|
def _percentile(values: List[float], percentile: float) -> float:
|
|
if not values:
|
|
return 0.0
|
|
sorted_values = sorted(values)
|
|
if len(sorted_values) == 1:
|
|
return sorted_values[0]
|
|
index = (percentile / 100) * (len(sorted_values) - 1)
|
|
lower = int(index)
|
|
upper = min(lower + 1, len(sorted_values) - 1)
|
|
weight = index - lower
|
|
return sorted_values[lower] * (1 - weight) + sorted_values[upper] * weight
|
|
|
|
|
|
def generate_report(simulation_results: List[Dict[str, float]]) -> Dict[str, Union[float, int]]:
|
|
"""Aggregate basic statistics for simulation outputs."""
|
|
|
|
values = _extract_results(simulation_results)
|
|
|
|
if not values:
|
|
return {
|
|
"count": 0,
|
|
"mean": 0.0,
|
|
"median": 0.0,
|
|
"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]] = {
|
|
"count": len(values),
|
|
"mean": mean(values),
|
|
"median": median(values),
|
|
"min": min(values),
|
|
"max": max(values),
|
|
"percentile_10": _percentile(values, 10),
|
|
"percentile_90": _percentile(values, 90),
|
|
"percentile_5": _percentile(values, 5),
|
|
"percentile_95": _percentile(values, 95),
|
|
}
|
|
|
|
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
|