# ================================================================ # services/suppliers/app/api/performance.py # ================================================================ """ Supplier Performance Tracking API endpoints """ from datetime import datetime, timedelta from typing import List, Optional from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, Query, Path, status from sqlalchemy.ext.asyncio import AsyncSession import structlog from shared.auth.decorators import get_current_user_dep, get_current_tenant_id_dep from app.core.database import get_db from app.services.performance_service import PerformanceTrackingService, AlertService from app.services.dashboard_service import DashboardService from app.schemas.performance import ( PerformanceMetric, PerformanceMetricCreate, PerformanceMetricUpdate, Alert, AlertCreate, AlertUpdate, Scorecard, ScorecardCreate, ScorecardUpdate, PerformanceDashboardSummary, SupplierPerformanceInsights, PerformanceAnalytics, BusinessModelInsights, AlertSummary, DashboardFilter, AlertFilter, PerformanceReportRequest, ExportDataResponse ) from app.models.performance import PerformancePeriod, PerformanceMetricType, AlertType, AlertSeverity logger = structlog.get_logger() router = APIRouter(prefix="/performance", tags=["performance"]) # ===== Dependency Injection ===== async def get_performance_service() -> PerformanceTrackingService: """Get performance tracking service""" return PerformanceTrackingService() async def get_alert_service() -> AlertService: """Get alert service""" return AlertService() async def get_dashboard_service() -> DashboardService: """Get dashboard service""" return DashboardService() # ===== Performance Metrics Endpoints ===== @router.post("/tenants/{tenant_id}/suppliers/{supplier_id}/calculate", response_model=PerformanceMetric) async def calculate_supplier_performance( tenant_id: UUID = Path(...), supplier_id: UUID = Path(...), period: PerformancePeriod = Query(...), period_start: datetime = Query(...), period_end: datetime = Query(...), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), performance_service: PerformanceTrackingService = Depends(get_performance_service), db: AsyncSession = Depends(get_db) ): """Calculate performance metrics for a supplier""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) metric = await performance_service.calculate_supplier_performance( db, supplier_id, tenant_id, period, period_start, period_end ) if not metric: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Unable to calculate performance metrics" ) logger.info("Performance metrics calculated", tenant_id=str(tenant_id), supplier_id=str(supplier_id), period=period.value) return metric except Exception as e: logger.error("Error calculating performance metrics", tenant_id=str(tenant_id), supplier_id=str(supplier_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to calculate performance metrics" ) @router.get("/tenants/{tenant_id}/suppliers/{supplier_id}/metrics", response_model=List[PerformanceMetric]) async def get_supplier_performance_metrics( tenant_id: UUID = Path(...), supplier_id: UUID = Path(...), metric_type: Optional[PerformanceMetricType] = Query(None), period: Optional[PerformancePeriod] = Query(None), date_from: Optional[datetime] = Query(None), date_to: Optional[datetime] = Query(None), limit: int = Query(50, ge=1, le=500), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Get performance metrics for a supplier""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) # TODO: Implement get_supplier_performance_metrics in service # For now, return empty list metrics = [] return metrics except Exception as e: logger.error("Error getting performance metrics", tenant_id=str(tenant_id), supplier_id=str(supplier_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve performance metrics" ) # ===== Alert Management Endpoints ===== @router.post("/tenants/{tenant_id}/alerts/evaluate", response_model=List[Alert]) async def evaluate_performance_alerts( tenant_id: UUID = Path(...), supplier_id: Optional[UUID] = Query(None, description="Specific supplier to evaluate"), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), alert_service: AlertService = Depends(get_alert_service), db: AsyncSession = Depends(get_db) ): """Evaluate and create performance-based alerts""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) alerts = await alert_service.evaluate_performance_alerts(db, tenant_id, supplier_id) logger.info("Performance alerts evaluated", tenant_id=str(tenant_id), alerts_created=len(alerts)) return alerts except Exception as e: logger.error("Error evaluating performance alerts", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to evaluate performance alerts" ) @router.get("/tenants/{tenant_id}/alerts", response_model=List[Alert]) async def get_supplier_alerts( tenant_id: UUID = Path(...), supplier_id: Optional[UUID] = Query(None), alert_type: Optional[AlertType] = Query(None), severity: Optional[AlertSeverity] = Query(None), date_from: Optional[datetime] = Query(None), date_to: Optional[datetime] = Query(None), limit: int = Query(50, ge=1, le=500), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Get supplier alerts with filtering""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) # TODO: Implement get_supplier_alerts in service # For now, return empty list alerts = [] return alerts except Exception as e: logger.error("Error getting supplier alerts", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve supplier alerts" ) @router.patch("/tenants/{tenant_id}/alerts/{alert_id}", response_model=Alert) async def update_alert( alert_update: AlertUpdate, tenant_id: UUID = Path(...), alert_id: UUID = Path(...), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Update an alert (acknowledge, resolve, etc.)""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) # TODO: Implement update_alert in service raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Alert update not yet implemented" ) except Exception as e: logger.error("Error updating alert", tenant_id=str(tenant_id), alert_id=str(alert_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update alert" ) # ===== Dashboard Endpoints ===== @router.get("/tenants/{tenant_id}/dashboard/summary", response_model=PerformanceDashboardSummary) async def get_performance_dashboard_summary( tenant_id: UUID = Path(...), date_from: Optional[datetime] = Query(None), date_to: Optional[datetime] = Query(None), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), dashboard_service: DashboardService = Depends(get_dashboard_service), db: AsyncSession = Depends(get_db) ): """Get comprehensive performance dashboard summary""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) summary = await dashboard_service.get_performance_dashboard_summary( db, tenant_id, date_from, date_to ) logger.info("Performance dashboard summary retrieved", tenant_id=str(tenant_id)) return summary except Exception as e: logger.error("Error getting dashboard summary", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve dashboard summary" ) @router.get("/tenants/{tenant_id}/suppliers/{supplier_id}/insights", response_model=SupplierPerformanceInsights) async def get_supplier_performance_insights( tenant_id: UUID = Path(...), supplier_id: UUID = Path(...), days_back: int = Query(30, ge=1, le=365), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), dashboard_service: DashboardService = Depends(get_dashboard_service), db: AsyncSession = Depends(get_db) ): """Get detailed performance insights for a specific supplier""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) insights = await dashboard_service.get_supplier_performance_insights( db, tenant_id, supplier_id, days_back ) logger.info("Supplier performance insights retrieved", tenant_id=str(tenant_id), supplier_id=str(supplier_id)) return insights except Exception as e: logger.error("Error getting supplier insights", tenant_id=str(tenant_id), supplier_id=str(supplier_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve supplier insights" ) @router.get("/tenants/{tenant_id}/analytics", response_model=PerformanceAnalytics) async def get_performance_analytics( tenant_id: UUID = Path(...), period_days: int = Query(90, ge=1, le=365), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), dashboard_service: DashboardService = Depends(get_dashboard_service), db: AsyncSession = Depends(get_db) ): """Get advanced performance analytics""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) analytics = await dashboard_service.get_performance_analytics( db, tenant_id, period_days ) logger.info("Performance analytics retrieved", tenant_id=str(tenant_id), period_days=period_days) return analytics except Exception as e: logger.error("Error getting performance analytics", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve performance analytics" ) @router.get("/tenants/{tenant_id}/business-model", response_model=BusinessModelInsights) async def get_business_model_insights( tenant_id: UUID = Path(...), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), dashboard_service: DashboardService = Depends(get_dashboard_service), db: AsyncSession = Depends(get_db) ): """Get business model detection and insights""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) insights = await dashboard_service.get_business_model_insights(db, tenant_id) logger.info("Business model insights retrieved", tenant_id=str(tenant_id), detected_model=insights.detected_model) return insights except Exception as e: logger.error("Error getting business model insights", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve business model insights" ) @router.get("/tenants/{tenant_id}/alerts/summary", response_model=List[AlertSummary]) async def get_alert_summary( tenant_id: UUID = Path(...), date_from: Optional[datetime] = Query(None), date_to: Optional[datetime] = Query(None), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), dashboard_service: DashboardService = Depends(get_dashboard_service), db: AsyncSession = Depends(get_db) ): """Get alert summary by type and severity""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) summary = await dashboard_service.get_alert_summary(db, tenant_id, date_from, date_to) return summary except Exception as e: logger.error("Error getting alert summary", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve alert summary" ) # ===== Export and Reporting Endpoints ===== @router.post("/tenants/{tenant_id}/reports/generate", response_model=ExportDataResponse) async def generate_performance_report( report_request: PerformanceReportRequest, tenant_id: UUID = Path(...), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Generate a performance report""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) # TODO: Implement report generation raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Report generation not yet implemented" ) except Exception as e: logger.error("Error generating performance report", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to generate performance report" ) @router.get("/tenants/{tenant_id}/export") async def export_performance_data( tenant_id: UUID = Path(...), format: str = Query("json", description="Export format: json, csv, excel"), date_from: Optional[datetime] = Query(None), date_to: Optional[datetime] = Query(None), supplier_ids: Optional[List[UUID]] = Query(None), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep), db: AsyncSession = Depends(get_db) ): """Export performance data""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) if format.lower() not in ["json", "csv", "excel"]: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Unsupported export format. Use: json, csv, excel" ) # TODO: Implement data export raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Data export not yet implemented" ) except Exception as e: logger.error("Error exporting performance data", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to export performance data" ) # ===== Configuration and Health Endpoints ===== @router.get("/tenants/{tenant_id}/config") async def get_performance_config( tenant_id: UUID = Path(...), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep) ): """Get performance tracking configuration""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) from app.core.config import settings config = { "performance_tracking": { "enabled": settings.PERFORMANCE_TRACKING_ENABLED, "calculation_interval_minutes": settings.PERFORMANCE_CALCULATION_INTERVAL_MINUTES, "cache_ttl_seconds": settings.PERFORMANCE_CACHE_TTL }, "thresholds": { "excellent_delivery_rate": settings.EXCELLENT_DELIVERY_RATE, "good_delivery_rate": settings.GOOD_DELIVERY_RATE, "acceptable_delivery_rate": settings.ACCEPTABLE_DELIVERY_RATE, "poor_delivery_rate": settings.POOR_DELIVERY_RATE, "excellent_quality_rate": settings.EXCELLENT_QUALITY_RATE, "good_quality_rate": settings.GOOD_QUALITY_RATE, "acceptable_quality_rate": settings.ACCEPTABLE_QUALITY_RATE, "poor_quality_rate": settings.POOR_QUALITY_RATE }, "alerts": { "enabled": settings.ALERTS_ENABLED, "evaluation_interval_minutes": settings.ALERT_EVALUATION_INTERVAL_MINUTES, "retention_days": settings.ALERT_RETENTION_DAYS, "critical_delivery_delay_hours": settings.CRITICAL_DELIVERY_DELAY_HOURS, "critical_quality_rejection_rate": settings.CRITICAL_QUALITY_REJECTION_RATE }, "dashboard": { "cache_ttl_seconds": settings.DASHBOARD_CACHE_TTL, "refresh_interval_seconds": settings.DASHBOARD_REFRESH_INTERVAL, "default_analytics_period_days": settings.DEFAULT_ANALYTICS_PERIOD_DAYS }, "business_model": { "detection_enabled": settings.ENABLE_BUSINESS_MODEL_DETECTION, "central_bakery_threshold": settings.CENTRAL_BAKERY_THRESHOLD_SUPPLIERS, "individual_bakery_threshold": settings.INDIVIDUAL_BAKERY_THRESHOLD_SUPPLIERS } } return config except Exception as e: logger.error("Error getting performance config", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve performance configuration" ) @router.get("/tenants/{tenant_id}/health") async def get_performance_health( tenant_id: UUID = Path(...), current_tenant: str = Depends(get_current_tenant_id_dep), current_user: dict = Depends(get_current_user_dep) ): """Get performance service health status""" try: if str(tenant_id) != current_tenant: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Access denied to tenant data" ) return { "service": "suppliers-performance", "status": "healthy", "timestamp": datetime.now().isoformat(), "tenant_id": str(tenant_id), "features": { "performance_tracking": "enabled", "alerts": "enabled", "dashboard_analytics": "enabled", "business_model_detection": "enabled" } } except Exception as e: logger.error("Error getting performance health", tenant_id=str(tenant_id), error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get performance health status" )