from fastapi import APIRouter, HTTPException, Depends, Query, Body from typing import Dict, Any, Optional import logging from datetime import datetime, timedelta from sqlalchemy.orm import Session from sqlalchemy import and_, desc import json from services.monitoring_plan_generator import MonitoringPlanGenerator from services.strategy_service import StrategyService from services.monitoring_data_service import MonitoringDataService from services.database import get_db from models.monitoring_models import ( StrategyMonitoringPlan, MonitoringTask, TaskExecutionLog, StrategyPerformanceMetrics, StrategyActivationStatus ) from models.enhanced_strategy_models import EnhancedContentStrategy logger = logging.getLogger(__name__) router = APIRouter(prefix="/strategy", tags=["strategy-monitoring"]) @router.post("/{strategy_id}/generate-monitoring-plan") async def generate_monitoring_plan(strategy_id: int): """Generate monitoring plan for a strategy""" try: generator = MonitoringPlanGenerator() plan = await generator.generate_monitoring_plan(strategy_id) logger.info(f"Successfully generated monitoring plan for strategy {strategy_id}") return { "success": True, "data": plan, "message": "Monitoring plan generated successfully" } except Exception as e: logger.error(f"Error generating monitoring plan for strategy {strategy_id}: {e}") raise HTTPException( status_code=500, detail=f"Failed to generate monitoring plan: {str(e)}" ) @router.post("/{strategy_id}/activate-with-monitoring") async def activate_strategy_with_monitoring( strategy_id: int, monitoring_plan: Dict[str, Any] = Body(...), db: Session = Depends(get_db) ): """Activate strategy with monitoring plan""" try: strategy_service = StrategyService() monitoring_service = MonitoringDataService(db) # Activate strategy activation_success = await strategy_service.activate_strategy(strategy_id) if not activation_success: raise HTTPException( status_code=400, detail=f"Failed to activate strategy {strategy_id}" ) # Save monitoring data to database monitoring_success = await monitoring_service.save_monitoring_data(strategy_id, monitoring_plan) if not monitoring_success: logger.warning(f"Failed to save monitoring data for strategy {strategy_id}") # Trigger scheduler interval adjustment (scheduler will check more frequently now) try: from services.scheduler import get_scheduler scheduler = get_scheduler() await scheduler.trigger_interval_adjustment() logger.info(f"Triggered scheduler interval adjustment after strategy {strategy_id} activation") except Exception as e: logger.warning(f"Could not trigger scheduler interval adjustment: {e}") logger.info(f"Successfully activated strategy {strategy_id} with monitoring") return { "success": True, "message": "Strategy activated with monitoring successfully", "strategy_id": strategy_id } except HTTPException: raise except Exception as e: logger.error(f"Error activating strategy {strategy_id} with monitoring: {e}") raise HTTPException( status_code=500, detail=f"Failed to activate strategy with monitoring: {str(e)}" ) @router.get("/{strategy_id}/monitoring-plan") async def get_monitoring_plan(strategy_id: int, db: Session = Depends(get_db)): """Get monitoring plan for a strategy""" try: monitoring_service = MonitoringDataService(db) monitoring_data = await monitoring_service.get_monitoring_data(strategy_id) if monitoring_data: return { "success": True, "data": monitoring_data } else: raise HTTPException( status_code=404, detail=f"Monitoring plan not found for strategy {strategy_id}" ) except HTTPException: raise except Exception as e: logger.error(f"Error getting monitoring plan for strategy {strategy_id}: {e}") raise HTTPException( status_code=500, detail=f"Failed to get monitoring plan: {str(e)}" ) @router.get("/{strategy_id}/analytics-data") async def get_analytics_data(strategy_id: int, db: Session = Depends(get_db)): """Get analytics data from monitoring data (no external API calls)""" try: monitoring_service = MonitoringDataService(db) analytics_data = await monitoring_service.get_analytics_data(strategy_id) return { "success": True, "data": analytics_data, "message": "Analytics data retrieved from monitoring database" } except Exception as e: logger.error(f"Error getting analytics data for strategy {strategy_id}: {e}") raise HTTPException( status_code=500, detail=f"Failed to get analytics data: {str(e)}" ) @router.get("/{strategy_id}/performance-history") async def get_strategy_performance_history(strategy_id: int, days: int = 30): """Get performance history for a strategy""" try: strategy_service = StrategyService() performance_history = await strategy_service.get_strategy_performance_history(strategy_id, days) return { "success": True, "data": { "strategy_id": strategy_id, "performance_history": performance_history, "days": days } } except Exception as e: logger.error(f"Error getting performance history for strategy {strategy_id}: {e}") raise HTTPException( status_code=500, detail=f"Failed to get performance history: {str(e)}" ) @router.post("/{strategy_id}/deactivate") async def deactivate_strategy(strategy_id: int, user_id: int = 1): """Deactivate a strategy""" try: strategy_service = StrategyService() success = await strategy_service.deactivate_strategy(strategy_id, user_id) if success: return { "success": True, "message": f"Strategy {strategy_id} deactivated successfully" } else: raise HTTPException( status_code=400, detail=f"Failed to deactivate strategy {strategy_id}" ) except HTTPException: raise except Exception as e: logger.error(f"Error deactivating strategy {strategy_id}: {e}") raise HTTPException( status_code=500, detail=f"Failed to deactivate strategy: {str(e)}" ) @router.post("/{strategy_id}/pause") async def pause_strategy(strategy_id: int, user_id: int = 1): """Pause a strategy""" try: strategy_service = StrategyService() success = await strategy_service.pause_strategy(strategy_id, user_id) if success: return { "success": True, "message": f"Strategy {strategy_id} paused successfully" } else: raise HTTPException( status_code=400, detail=f"Failed to pause strategy {strategy_id}" ) except HTTPException: raise except Exception as e: logger.error(f"Error pausing strategy {strategy_id}: {e}") raise HTTPException( status_code=500, detail=f"Failed to pause strategy: {str(e)}" ) @router.post("/{strategy_id}/resume") async def resume_strategy(strategy_id: int, user_id: int = 1): """Resume a paused strategy""" try: strategy_service = StrategyService() success = await strategy_service.resume_strategy(strategy_id, user_id) if success: return { "success": True, "message": f"Strategy {strategy_id} resumed successfully" } else: raise HTTPException( status_code=400, detail=f"Failed to resume strategy {strategy_id}" ) except HTTPException: raise except Exception as e: logger.error(f"Error resuming strategy {strategy_id}: {e}") raise HTTPException( status_code=500, detail=f"Failed to resume strategy: {str(e)}" ) @router.get("/{strategy_id}/performance-metrics") async def get_performance_metrics( strategy_id: int, db: Session = Depends(get_db) ): """ Get performance metrics for a strategy """ try: # For now, return mock data - in real implementation, this would query the database mock_metrics = { "traffic_growth_percentage": 15.7, "engagement_rate_percentage": 8.3, "conversion_rate_percentage": 2.1, "roi_ratio": 3.2, "strategy_adoption_rate": 85, "content_quality_score": 92, "competitive_position_rank": 3, "audience_growth_percentage": 12.5, "confidence_score": 88, "last_updated": datetime.utcnow().isoformat() } return { "success": True, "data": mock_metrics, "message": "Performance metrics retrieved successfully" } except Exception as e: logger.error(f"Error getting performance metrics: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/{strategy_id}/trend-data") async def get_trend_data( strategy_id: int, time_range: str = Query("30d", description="Time range: 7d, 30d, 90d, 1y"), db: Session = Depends(get_db) ): """ Get trend data for a strategy over time """ try: # Mock trend data - in real implementation, this would query the database mock_trend_data = [ {"date": "2024-01-01", "traffic_growth": 5.2, "engagement_rate": 6.1, "conversion_rate": 1.8, "content_quality_score": 85, "strategy_adoption_rate": 70}, {"date": "2024-01-08", "traffic_growth": 7.8, "engagement_rate": 7.2, "conversion_rate": 2.0, "content_quality_score": 87, "strategy_adoption_rate": 75}, {"date": "2024-01-15", "traffic_growth": 9.1, "engagement_rate": 7.8, "conversion_rate": 2.1, "content_quality_score": 89, "strategy_adoption_rate": 78}, {"date": "2024-01-22", "traffic_growth": 11.3, "engagement_rate": 8.1, "conversion_rate": 2.0, "content_quality_score": 90, "strategy_adoption_rate": 82}, {"date": "2024-01-29", "traffic_growth": 12.7, "engagement_rate": 8.3, "conversion_rate": 2.1, "content_quality_score": 91, "strategy_adoption_rate": 85}, {"date": "2024-02-05", "traffic_growth": 14.2, "engagement_rate": 8.5, "conversion_rate": 2.2, "content_quality_score": 92, "strategy_adoption_rate": 87}, {"date": "2024-02-12", "traffic_growth": 15.7, "engagement_rate": 8.3, "conversion_rate": 2.1, "content_quality_score": 92, "strategy_adoption_rate": 85} ] return { "success": True, "data": mock_trend_data, "message": "Trend data retrieved successfully" } except Exception as e: logger.error(f"Error getting trend data: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/{strategy_id}/test-transparency") async def test_transparency_endpoint( strategy_id: int, db: Session = Depends(get_db) ): """ Simple test endpoint to check if transparency data endpoint works """ try: # Check if strategy exists strategy = db.query(EnhancedContentStrategy).filter( EnhancedContentStrategy.id == strategy_id ).first() if not strategy: return { "success": False, "data": None, "message": f"Strategy with ID {strategy_id} not found" } # Get monitoring plan monitoring_plan = db.query(StrategyMonitoringPlan).filter( StrategyMonitoringPlan.strategy_id == strategy_id ).first() # Get monitoring tasks count tasks_count = db.query(MonitoringTask).filter( MonitoringTask.strategy_id == strategy_id ).count() return { "success": True, "data": { "strategy_id": strategy_id, "strategy_name": strategy.strategy_name if hasattr(strategy, 'strategy_name') else "Unknown", "monitoring_plan_exists": monitoring_plan is not None, "tasks_count": tasks_count }, "message": "Test endpoint working" } except Exception as e: logger.error(f"Error in test endpoint: {str(e)}") return { "success": False, "data": None, "message": f"Error: {str(e)}" } @router.get("/{strategy_id}/monitoring-tasks") async def get_monitoring_tasks( strategy_id: int, db: Session = Depends(get_db) ): """ Get all monitoring tasks for a strategy with their execution status """ try: # Check if strategy exists strategy = db.query(EnhancedContentStrategy).filter( EnhancedContentStrategy.id == strategy_id ).first() if not strategy: raise HTTPException(status_code=404, detail="Strategy not found") # Get monitoring tasks with execution logs tasks = db.query(MonitoringTask).filter( MonitoringTask.strategy_id == strategy_id ).all() tasks_data = [] for task in tasks: # Get latest execution log latest_log = db.query(TaskExecutionLog).filter( TaskExecutionLog.task_id == task.id ).order_by(desc(TaskExecutionLog.execution_date)).first() task_data = { "id": task.id, "title": task.task_title, "description": task.task_description, "assignee": task.assignee, "frequency": task.frequency, "metric": task.metric, "measurementMethod": task.measurement_method, "successCriteria": task.success_criteria, "alertThreshold": task.alert_threshold, "actionableInsights": getattr(task, 'actionable_insights', None), "status": "active", # This would be determined by task execution status "lastExecuted": latest_log.execution_date.isoformat() if latest_log else None, "executionCount": db.query(TaskExecutionLog).filter( TaskExecutionLog.task_id == task.id ).count() } tasks_data.append(task_data) return { "success": True, "data": tasks_data, "message": "Monitoring tasks retrieved successfully" } except HTTPException: raise except Exception as e: logger.error(f"Error retrieving monitoring tasks: {str(e)}") raise HTTPException(status_code=500, detail="Internal server error") @router.get("/user/{user_id}/monitoring-tasks") async def get_user_monitoring_tasks( user_id: int, db: Session = Depends(get_db), status: Optional[str] = Query(None, description="Filter by task status"), limit: int = Query(50, description="Maximum number of tasks to return"), offset: int = Query(0, description="Number of tasks to skip") ): """ Get all monitoring tasks for a specific user with their execution status. Uses the scheduler's task loader to get tasks filtered by user_id for proper user isolation. """ try: logger.info(f"Getting monitoring tasks for user {user_id}") # Use scheduler task loader for user-specific tasks from services.scheduler.utils.task_loader import load_due_monitoring_tasks # Load all tasks for user (not just due tasks - we want all user tasks) # Join with strategy to filter by user tasks_query = db.query(MonitoringTask).join( EnhancedContentStrategy, MonitoringTask.strategy_id == EnhancedContentStrategy.id ).filter( EnhancedContentStrategy.user_id == user_id ) # Apply status filter if provided if status: tasks_query = tasks_query.filter(MonitoringTask.status == status) # Get tasks with pagination tasks = tasks_query.order_by(desc(MonitoringTask.created_at)).offset(offset).limit(limit).all() tasks_data = [] for task in tasks: # Get latest execution log latest_log = db.query(TaskExecutionLog).filter( TaskExecutionLog.task_id == task.id ).order_by(desc(TaskExecutionLog.execution_date)).first() # Get strategy info strategy = db.query(EnhancedContentStrategy).filter( EnhancedContentStrategy.id == task.strategy_id ).first() task_data = { "id": task.id, "strategy_id": task.strategy_id, "strategy_name": strategy.name if strategy else None, "title": task.task_title, "description": task.task_description, "assignee": task.assignee, "frequency": task.frequency, "metric": task.metric, "measurementMethod": task.measurement_method, "successCriteria": task.success_criteria, "alertThreshold": task.alert_threshold, "status": task.status, "lastExecuted": latest_log.execution_date.isoformat() if latest_log else None, "nextExecution": task.next_execution.isoformat() if task.next_execution else None, "executionCount": db.query(TaskExecutionLog).filter( TaskExecutionLog.task_id == task.id ).count(), "created_at": task.created_at.isoformat() if task.created_at else None } tasks_data.append(task_data) # Get total count for pagination total_count = db.query(MonitoringTask).join( EnhancedContentStrategy, MonitoringTask.strategy_id == EnhancedContentStrategy.id ).filter( EnhancedContentStrategy.user_id == user_id ) if status: total_count = total_count.filter(MonitoringTask.status == status) total_count = total_count.count() return { "success": True, "data": tasks_data, "pagination": { "total": total_count, "limit": limit, "offset": offset, "has_more": (offset + len(tasks_data)) < total_count }, "message": f"Retrieved {len(tasks_data)} monitoring tasks for user {user_id}" } except Exception as e: logger.error(f"Error retrieving user monitoring tasks: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to retrieve monitoring tasks: {str(e)}") @router.get("/user/{user_id}/execution-logs") async def get_user_execution_logs( user_id: int, db: Session = Depends(get_db), status: Optional[str] = Query(None, description="Filter by execution status"), limit: int = Query(50, description="Maximum number of logs to return"), offset: int = Query(0, description="Number of logs to skip") ): """ Get execution logs for a specific user. Provides user isolation by filtering execution logs by user_id. """ try: logger.info(f"Getting execution logs for user {user_id}") monitoring_service = MonitoringDataService(db) logs_data = monitoring_service.get_user_execution_logs( user_id=user_id, limit=limit, offset=offset, status_filter=status ) # Get total count for pagination count_query = db.query(TaskExecutionLog).filter( TaskExecutionLog.user_id == user_id ) if status: count_query = count_query.filter(TaskExecutionLog.status == status) total_count = count_query.count() return { "success": True, "data": logs_data, "pagination": { "total": total_count, "limit": limit, "offset": offset, "has_more": (offset + len(logs_data)) < total_count }, "message": f"Retrieved {len(logs_data)} execution logs for user {user_id}" } except Exception as e: logger.error(f"Error retrieving execution logs for user {user_id}: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to retrieve execution logs: {str(e)}") @router.get("/{strategy_id}/data-freshness") async def get_data_freshness( strategy_id: int, db: Session = Depends(get_db) ): """ Get data freshness information for all metrics """ try: # Check if strategy exists strategy = db.query(EnhancedContentStrategy).filter( EnhancedContentStrategy.id == strategy_id ).first() if not strategy: raise HTTPException(status_code=404, detail="Strategy not found") # Get latest task execution logs latest_logs = db.query(TaskExecutionLog).join(MonitoringTask).filter( MonitoringTask.strategy_id == strategy_id ).order_by(desc(TaskExecutionLog.execution_date)).limit(10).all() # Get performance metrics performance_metrics = db.query(StrategyPerformanceMetrics).filter( StrategyPerformanceMetrics.strategy_id == strategy_id ).order_by(desc(StrategyPerformanceMetrics.created_at)).first() freshness_data = { "lastUpdated": latest_logs[0].execution_date.isoformat() if latest_logs else datetime.now().isoformat(), "updateFrequency": "Every 4 hours", "dataSource": "Multiple Analytics APIs + AI Analysis", "confidence": 90, "metrics": [ { "name": "Traffic Growth", "lastUpdated": latest_logs[0].execution_date.isoformat() if latest_logs else datetime.now().isoformat(), "updateFrequency": "Every 4 hours", "dataSource": "Google Analytics + AI Analysis", "confidence": 92 }, { "name": "Engagement Rate", "lastUpdated": latest_logs[0].execution_date.isoformat() if latest_logs else datetime.now().isoformat(), "updateFrequency": "Every 2 hours", "dataSource": "Social Media Analytics + Website Analytics", "confidence": 88 }, { "name": "Conversion Rate", "lastUpdated": latest_logs[0].execution_date.isoformat() if latest_logs else datetime.now().isoformat(), "updateFrequency": "Every 6 hours", "dataSource": "Google Analytics + CRM Data", "confidence": 85 } ] } return { "success": True, "data": freshness_data, "message": "Data freshness information retrieved successfully" } except HTTPException: raise except Exception as e: logger.error(f"Error retrieving data freshness: {str(e)}") raise HTTPException(status_code=500, detail="Internal server error") @router.get("/{strategy_id}/transparency-data") async def get_transparency_data( strategy_id: int, db: Session = Depends(get_db) ): """ Get comprehensive transparency data for a strategy including: - Data freshness information - Measurement methodology - AI monitoring tasks - Strategy mapping - AI insights """ try: # Check if strategy exists strategy = db.query(EnhancedContentStrategy).filter( EnhancedContentStrategy.id == strategy_id ).first() if not strategy: return { "success": False, "data": None, "message": f"Strategy with ID {strategy_id} not found" } # Get monitoring plan and tasks monitoring_plan = db.query(StrategyMonitoringPlan).filter( StrategyMonitoringPlan.strategy_id == strategy_id ).first() if not monitoring_plan: return { "success": False, "data": None, "message": "No monitoring plan found for this strategy" } # Get all monitoring tasks monitoring_tasks = db.query(MonitoringTask).filter( MonitoringTask.strategy_id == strategy_id ).all() # Get task execution logs for data freshness task_logs = db.query(TaskExecutionLog).join(MonitoringTask).filter( MonitoringTask.strategy_id == strategy_id ).order_by(desc(TaskExecutionLog.execution_date)).all() # Get performance metrics for current values performance_metrics = db.query(StrategyPerformanceMetrics).filter( StrategyPerformanceMetrics.strategy_id == strategy_id ).order_by(desc(StrategyPerformanceMetrics.created_at)).first() # Build transparency data from actual monitoring tasks transparency_data = [] # Group tasks by component for better organization tasks_by_component = {} for task in monitoring_tasks: component = task.component_name or 'General' if component not in tasks_by_component: tasks_by_component[component] = [] tasks_by_component[component].append(task) # Create transparency data for each component for component, tasks in tasks_by_component.items(): component_data = { "metricName": component, "currentValue": len(tasks), "unit": "tasks", "dataFreshness": { "lastUpdated": task_logs[0].execution_date.isoformat() if task_logs else datetime.now().isoformat(), "updateFrequency": "Real-time", "dataSource": "Monitoring System", "confidence": 95 }, "measurementMethodology": { "description": f"AI-powered monitoring for {component} with {len(tasks)} active tasks", "calculationMethod": "Automated monitoring with real-time data collection and analysis", "dataPoints": [task.metric for task in tasks if task.metric], "validationProcess": "Cross-validated with multiple data sources and AI analysis" }, "monitoringTasks": [ { "title": task.task_title, "description": task.task_description, "assignee": task.assignee, "frequency": task.frequency, "metric": task.metric, "measurementMethod": task.measurement_method, "successCriteria": task.success_criteria, "alertThreshold": task.alert_threshold, "status": task.status, "lastExecuted": task.last_executed.isoformat() if task.last_executed else None } for task in tasks ], "strategyMapping": { "relatedComponents": [component], "impactAreas": ["Performance Monitoring", "Strategy Optimization", "Risk Management"], "dependencies": ["Data Collection", "AI Analysis", "Alert System"] }, "aiInsights": { "trendAnalysis": f"Active monitoring for {component} with {len(tasks)} configured tasks", "recommendations": [ "Monitor task execution status regularly", "Review performance metrics weekly", "Adjust thresholds based on performance trends" ], "riskFactors": ["Task execution failures", "Data collection issues", "System downtime"], "opportunities": ["Automated optimization", "Predictive analytics", "Enhanced monitoring"] } } transparency_data.append(component_data) # If no monitoring tasks found, create a default transparency entry if not transparency_data: transparency_data = [{ "metricName": "Strategy Monitoring", "currentValue": 0, "unit": "tasks", "dataFreshness": { "lastUpdated": datetime.now().isoformat(), "updateFrequency": "Real-time", "dataSource": "Monitoring System", "confidence": 0 }, "measurementMethodology": { "description": "No monitoring tasks configured yet", "calculationMethod": "Manual setup required", "dataPoints": [], "validationProcess": "Not applicable" }, "monitoringTasks": [], "strategyMapping": { "relatedComponents": ["Strategy"], "impactAreas": ["Monitoring"], "dependencies": ["Setup"] }, "aiInsights": { "trendAnalysis": "No monitoring data available", "recommendations": ["Set up monitoring tasks", "Configure alerts", "Enable data collection"], "riskFactors": ["No monitoring in place"], "opportunities": ["Implement comprehensive monitoring"] } }] # Return the transparency data return { "success": True, "data": transparency_data, "message": f"Transparency data retrieved successfully for strategy {strategy_id}" } except Exception as e: logger.error(f"Error retrieving transparency data: {str(e)}") return { "success": False, "data": None, "message": f"Error: {str(e)}" }