- 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.
83 lines
2.5 KiB
Python
83 lines
2.5 KiB
Python
from typing import Any, Dict, List, cast
|
|
|
|
from fastapi import APIRouter, HTTPException, Request, status
|
|
from pydantic import BaseModel
|
|
|
|
from config.database import SessionLocal
|
|
from services.reporting import generate_report
|
|
|
|
|
|
router = APIRouter(prefix="/api/reporting", tags=["Reporting"])
|
|
|
|
|
|
def get_db():
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
def _validate_payload(payload: Any) -> List[Dict[str, float]]:
|
|
if not isinstance(payload, list):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid input format",
|
|
)
|
|
|
|
typed_payload = cast(List[Any], payload)
|
|
|
|
validated: List[Dict[str, float]] = []
|
|
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 = cast(Dict[str, Any], item).get("result")
|
|
if not isinstance(value, (int, float)):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail=f"Entry at index {index} must include numeric 'result'",
|
|
)
|
|
validated.append({"result": float(value)})
|
|
return validated
|
|
|
|
|
|
class ReportSummary(BaseModel):
|
|
count: int
|
|
mean: float
|
|
median: float
|
|
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)
|
|
async def summary_report(request: Request):
|
|
payload = await request.json()
|
|
validated_payload = _validate_payload(payload)
|
|
summary = generate_report(validated_payload)
|
|
return ReportSummary(
|
|
count=int(summary["count"]),
|
|
mean=float(summary["mean"]),
|
|
median=float(summary["median"]),
|
|
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"]),
|
|
)
|