# services/forecasting/app/api/forecasts.py """ Forecasts API - Atomic CRUD operations on Forecast model """ import structlog from fastapi import APIRouter, Depends, HTTPException, status, Query, Path from typing import List, Optional from datetime import date, datetime import uuid from app.services.forecasting_service import EnhancedForecastingService from app.schemas.forecasts import ForecastResponse from shared.database.base import create_database_manager from app.core.config import settings from shared.routing import RouteBuilder route_builder = RouteBuilder('forecasting') logger = structlog.get_logger() router = APIRouter(tags=["forecasts"]) def get_enhanced_forecasting_service(): """Dependency injection for EnhancedForecastingService""" database_manager = create_database_manager(settings.DATABASE_URL, "forecasting-service") return EnhancedForecastingService(database_manager) @router.get( route_builder.build_base_route("forecasts"), response_model=List[ForecastResponse] ) async def list_forecasts( tenant_id: str = Path(..., description="Tenant ID"), inventory_product_id: Optional[str] = Query(None, description="Filter by product ID"), start_date: Optional[date] = Query(None, description="Start date filter"), end_date: Optional[date] = Query(None, description="End date filter"), limit: int = Query(50, ge=1, le=1000), offset: int = Query(0, ge=0), enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service) ): """List forecasts with optional filters""" try: logger.info("Listing forecasts", tenant_id=tenant_id) forecasts = await enhanced_forecasting_service.list_forecasts( tenant_id=tenant_id, inventory_product_id=inventory_product_id, start_date=start_date, end_date=end_date, limit=limit, offset=offset ) return forecasts except Exception as e: logger.error("Failed to list forecasts", error=str(e), tenant_id=tenant_id) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve forecasts" ) @router.get( route_builder.build_resource_detail_route("forecasts", "forecast_id"), response_model=ForecastResponse ) async def get_forecast( tenant_id: str = Path(..., description="Tenant ID"), forecast_id: str = Path(..., description="Forecast ID"), enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service) ): """Get a specific forecast by ID""" try: logger.info("Getting forecast", tenant_id=tenant_id, forecast_id=forecast_id) forecast = await enhanced_forecasting_service.get_forecast( tenant_id=tenant_id, forecast_id=uuid.UUID(forecast_id) ) if not forecast: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Forecast not found" ) return forecast except HTTPException: raise except ValueError as e: logger.error("Invalid forecast ID", error=str(e)) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid forecast ID format" ) except Exception as e: logger.error("Failed to get forecast", error=str(e), tenant_id=tenant_id) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve forecast" ) @router.delete( route_builder.build_resource_detail_route("forecasts", "forecast_id") ) async def delete_forecast( tenant_id: str = Path(..., description="Tenant ID"), forecast_id: str = Path(..., description="Forecast ID"), enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service) ): """Delete a specific forecast""" try: logger.info("Deleting forecast", tenant_id=tenant_id, forecast_id=forecast_id) success = await enhanced_forecasting_service.delete_forecast( tenant_id=tenant_id, forecast_id=uuid.UUID(forecast_id) ) if not success: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Forecast not found" ) return {"message": "Forecast deleted successfully"} except HTTPException: raise except ValueError as e: logger.error("Invalid forecast ID", error=str(e)) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid forecast ID format" ) except Exception as e: logger.error("Failed to delete forecast", error=str(e), tenant_id=tenant_id) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to delete forecast" )