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