Files
bakery-ia/services/forecasting/app/api/forecasts.py

146 lines
4.9 KiB
Python
Raw Normal View History

2025-10-06 15:27:01 +02:00
# services/forecasting/app/api/forecasts.py
2025-07-21 19:48:56 +02:00
"""
2025-10-06 15:27:01 +02:00
Forecasts API - Atomic CRUD operations on Forecast model
2025-07-21 19:48:56 +02:00
"""
import structlog
2025-10-06 15:27:01 +02:00
from fastapi import APIRouter, Depends, HTTPException, status, Query, Path
2025-07-21 19:48:56 +02:00
from typing import List, Optional
2025-08-02 09:41:50 +02:00
from datetime import date, datetime
import uuid
2025-07-21 19:48:56 +02:00
2025-08-08 09:08:41 +02:00
from app.services.forecasting_service import EnhancedForecastingService
2025-10-06 15:27:01 +02:00
from app.schemas.forecasts import ForecastResponse
2025-08-08 09:08:41 +02:00
from shared.database.base import create_database_manager
from app.core.config import settings
2025-10-06 15:27:01 +02:00
from shared.routing import RouteBuilder
2025-07-21 19:48:56 +02:00
2025-10-06 15:27:01 +02:00
route_builder = RouteBuilder('forecasting')
2025-07-21 19:48:56 +02:00
logger = structlog.get_logger()
2025-10-06 15:27:01 +02:00
router = APIRouter(tags=["forecasts"])
2025-07-21 19:48:56 +02:00
2025-08-08 09:08:41 +02:00
def get_enhanced_forecasting_service():
"""Dependency injection for EnhancedForecastingService"""
database_manager = create_database_manager(settings.DATABASE_URL, "forecasting-service")
return EnhancedForecastingService(database_manager)
2025-07-21 19:48:56 +02:00
2025-10-06 15:27:01 +02:00
@router.get(
route_builder.build_base_route("forecasts"),
response_model=List[ForecastResponse]
)
async def list_forecasts(
2025-08-08 09:08:41 +02:00
tenant_id: str = Path(..., description="Tenant ID"),
2025-10-06 15:27:01 +02:00
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),
2025-08-08 09:08:41 +02:00
enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service)
2025-07-21 19:48:56 +02:00
):
2025-10-06 15:27:01 +02:00
"""List forecasts with optional filters"""
2025-07-21 19:48:56 +02:00
try:
2025-10-06 15:27:01 +02:00
logger.info("Listing forecasts", tenant_id=tenant_id)
2025-10-03 14:09:34 +02:00
2025-10-06 15:27:01 +02:00
forecasts = await enhanced_forecasting_service.list_forecasts(
2025-07-29 17:50:01 +02:00
tenant_id=tenant_id,
2025-10-06 15:27:01 +02:00
inventory_product_id=inventory_product_id,
start_date=start_date,
end_date=end_date,
limit=limit,
offset=offset
2025-07-21 19:48:56 +02:00
)
2025-09-20 22:11:05 +02:00
2025-10-06 15:27:01 +02:00
return forecasts
2025-07-21 19:48:56 +02:00
except Exception as e:
2025-10-06 15:27:01 +02:00
logger.error("Failed to list forecasts", error=str(e), tenant_id=tenant_id)
2025-07-21 19:48:56 +02:00
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
2025-10-06 15:27:01 +02:00
detail="Failed to retrieve forecasts"
2025-07-21 19:48:56 +02:00
)
2025-08-08 09:08:41 +02:00
2025-10-06 15:27:01 +02:00
@router.get(
route_builder.build_resource_detail_route("forecasts", "forecast_id"),
response_model=ForecastResponse
)
async def get_forecast(
2025-09-20 22:11:05 +02:00
tenant_id: str = Path(..., description="Tenant ID"),
2025-10-06 15:27:01 +02:00
forecast_id: str = Path(..., description="Forecast ID"),
2025-09-20 22:11:05 +02:00
enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service)
):
2025-10-06 15:27:01 +02:00
"""Get a specific forecast by ID"""
2025-09-20 22:11:05 +02:00
try:
2025-10-06 15:27:01 +02:00
logger.info("Getting forecast", tenant_id=tenant_id, forecast_id=forecast_id)
2025-09-20 22:11:05 +02:00
2025-10-06 15:27:01 +02:00
forecast = await enhanced_forecasting_service.get_forecast(
2025-09-20 22:11:05 +02:00
tenant_id=tenant_id,
2025-10-06 15:27:01 +02:00
forecast_id=uuid.UUID(forecast_id)
2025-09-20 22:11:05 +02:00
)
2025-10-06 15:27:01 +02:00
if not forecast:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Forecast not found"
)
2025-09-20 22:11:05 +02:00
2025-10-06 15:27:01 +02:00
return forecast
2025-09-20 22:11:05 +02:00
2025-10-06 15:27:01 +02:00
except HTTPException:
raise
2025-09-20 22:11:05 +02:00
except ValueError as e:
2025-10-06 15:27:01 +02:00
logger.error("Invalid forecast ID", error=str(e))
2025-09-20 22:11:05 +02:00
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
2025-10-06 15:27:01 +02:00
detail="Invalid forecast ID format"
2025-09-20 22:11:05 +02:00
)
except Exception as e:
2025-10-06 15:27:01 +02:00
logger.error("Failed to get forecast", error=str(e), tenant_id=tenant_id)
2025-09-20 22:11:05 +02:00
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
2025-10-06 15:27:01 +02:00
detail="Failed to retrieve forecast"
2025-09-20 22:11:05 +02:00
)
2025-10-06 15:27:01 +02:00
@router.delete(
route_builder.build_resource_detail_route("forecasts", "forecast_id")
)
async def delete_forecast(
2025-07-29 13:02:42 +02:00
tenant_id: str = Path(..., description="Tenant ID"),
2025-10-06 15:27:01 +02:00
forecast_id: str = Path(..., description="Forecast ID"),
2025-08-08 09:08:41 +02:00
enhanced_forecasting_service: EnhancedForecastingService = Depends(get_enhanced_forecasting_service)
2025-07-21 19:48:56 +02:00
):
2025-10-06 15:27:01 +02:00
"""Delete a specific forecast"""
2025-07-21 19:48:56 +02:00
try:
2025-10-06 15:27:01 +02:00
logger.info("Deleting forecast", tenant_id=tenant_id, forecast_id=forecast_id)
2025-10-03 14:09:34 +02:00
2025-10-06 15:27:01 +02:00
success = await enhanced_forecasting_service.delete_forecast(
2025-08-08 09:08:41 +02:00
tenant_id=tenant_id,
2025-10-06 15:27:01 +02:00
forecast_id=uuid.UUID(forecast_id)
2025-07-21 19:48:56 +02:00
)
2025-10-06 15:27:01 +02:00
if not success:
2025-08-08 09:08:41 +02:00
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Forecast not found"
2025-07-21 19:48:56 +02:00
)
2025-10-06 15:27:01 +02:00
return {"message": "Forecast deleted successfully"}
2025-08-08 09:08:41 +02:00
2025-07-21 19:48:56 +02:00
except HTTPException:
raise
2025-10-06 15:27:01 +02:00
except ValueError as e:
logger.error("Invalid forecast ID", error=str(e))
2025-07-21 19:48:56 +02:00
raise HTTPException(
2025-10-06 15:27:01 +02:00
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid forecast ID format"
2025-08-02 09:41:50 +02:00
)
2025-08-02 17:09:53 +02:00
except Exception as e:
2025-10-06 15:27:01 +02:00
logger.error("Failed to delete forecast", error=str(e), tenant_id=tenant_id)
2025-08-02 17:09:53 +02:00
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
2025-10-06 15:27:01 +02:00
detail="Failed to delete forecast"
2025-08-08 09:08:41 +02:00
)