445 lines
21 KiB
Python
445 lines
21 KiB
Python
"""
|
|
Monitoring Data Service
|
|
Handles saving and retrieving monitoring data from database and cache.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Dict, Any, List, Optional
|
|
from datetime import datetime
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import and_, desc
|
|
|
|
from models.monitoring_models import (
|
|
StrategyMonitoringPlan, MonitoringTask, TaskExecutionLog,
|
|
StrategyPerformanceMetrics, StrategyActivationStatus
|
|
)
|
|
from models.enhanced_strategy_models import EnhancedContentStrategy
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MonitoringDataService:
|
|
"""Service for managing monitoring data in database and cache."""
|
|
|
|
def __init__(self, db_session: Session):
|
|
self.db = db_session
|
|
|
|
async def save_monitoring_data(self, strategy_id: int, monitoring_plan: Dict[str, Any]) -> bool:
|
|
"""Save monitoring plan and tasks to database."""
|
|
try:
|
|
logger.info(f"Saving monitoring data for strategy {strategy_id}")
|
|
logger.info(f"Monitoring plan received: {monitoring_plan}")
|
|
|
|
# Save the complete monitoring plan
|
|
monitoring_plan_record = StrategyMonitoringPlan(
|
|
strategy_id=strategy_id,
|
|
plan_data=monitoring_plan
|
|
)
|
|
self.db.add(monitoring_plan_record)
|
|
|
|
# Save individual monitoring tasks
|
|
monitoring_tasks = monitoring_plan.get('monitoringTasks', [])
|
|
logger.info(f"Found {len(monitoring_tasks)} monitoring tasks to save")
|
|
|
|
for i, task_data in enumerate(monitoring_tasks):
|
|
logger.info(f"Saving task {i+1}: {task_data.get('title', 'Unknown')}")
|
|
task = MonitoringTask(
|
|
strategy_id=strategy_id,
|
|
component_name=task_data.get('component', ''),
|
|
task_title=task_data.get('title', ''),
|
|
task_description=task_data.get('description', ''),
|
|
assignee=task_data.get('assignee', 'ALwrity'),
|
|
frequency=task_data.get('frequency', 'Monthly'),
|
|
metric=task_data.get('metric', ''),
|
|
measurement_method=task_data.get('measurementMethod', ''),
|
|
success_criteria=task_data.get('successCriteria', ''),
|
|
alert_threshold=task_data.get('alertThreshold', ''),
|
|
status='active'
|
|
)
|
|
|
|
# Initialize next_execution based on frequency
|
|
from services.scheduler.utils.frequency_calculator import calculate_next_execution
|
|
task.next_execution = calculate_next_execution(
|
|
frequency=task.frequency,
|
|
base_time=datetime.utcnow()
|
|
)
|
|
|
|
self.db.add(task)
|
|
|
|
# Save activation status
|
|
activation_status = StrategyActivationStatus(
|
|
strategy_id=strategy_id,
|
|
user_id=1, # Default user ID
|
|
activation_date=datetime.utcnow(),
|
|
status='active'
|
|
)
|
|
self.db.add(activation_status)
|
|
|
|
# Save initial performance metrics
|
|
performance_metrics = StrategyPerformanceMetrics(
|
|
strategy_id=strategy_id,
|
|
user_id=1, # Default user ID
|
|
metric_date=datetime.utcnow(),
|
|
data_source='monitoring_plan',
|
|
confidence_score=85 # High confidence for monitoring plan data
|
|
)
|
|
self.db.add(performance_metrics)
|
|
|
|
self.db.commit()
|
|
logger.info(f"Successfully saved monitoring data for strategy {strategy_id}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error saving monitoring data for strategy {strategy_id}: {e}")
|
|
self.db.rollback()
|
|
return False
|
|
|
|
async def get_monitoring_data(self, strategy_id: int) -> Optional[Dict[str, Any]]:
|
|
"""Get monitoring data from database."""
|
|
try:
|
|
logger.info(f"Retrieving monitoring data for strategy {strategy_id}")
|
|
|
|
# Get the monitoring plan
|
|
monitoring_plan = self.db.query(StrategyMonitoringPlan).filter(
|
|
StrategyMonitoringPlan.strategy_id == strategy_id
|
|
).order_by(desc(StrategyMonitoringPlan.created_at)).first()
|
|
|
|
if not monitoring_plan:
|
|
logger.warning(f"No monitoring plan found for strategy {strategy_id}")
|
|
return None
|
|
|
|
# Get monitoring tasks
|
|
tasks = self.db.query(MonitoringTask).filter(
|
|
MonitoringTask.strategy_id == strategy_id
|
|
).all()
|
|
|
|
# Get activation status
|
|
activation_status = self.db.query(StrategyActivationStatus).filter(
|
|
StrategyActivationStatus.strategy_id == strategy_id
|
|
).first()
|
|
|
|
# Get performance metrics
|
|
performance_metrics = self.db.query(StrategyPerformanceMetrics).filter(
|
|
StrategyPerformanceMetrics.strategy_id == strategy_id
|
|
).order_by(desc(StrategyPerformanceMetrics.metric_date)).first()
|
|
|
|
# Build comprehensive monitoring data
|
|
monitoring_data = {
|
|
'strategy_id': strategy_id,
|
|
'monitoring_plan': monitoring_plan.plan_data,
|
|
'monitoring_tasks': [
|
|
{
|
|
'id': task.id,
|
|
'component': task.component_name,
|
|
'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,
|
|
'last_executed': task.last_executed.isoformat() if task.last_executed else None,
|
|
'next_execution': task.next_execution.isoformat() if task.next_execution else None
|
|
}
|
|
for task in tasks
|
|
],
|
|
'activation_status': {
|
|
'activation_date': activation_status.activation_date.isoformat() if activation_status else None,
|
|
'status': activation_status.status if activation_status else 'unknown',
|
|
'performance_score': activation_status.performance_score if activation_status else None
|
|
},
|
|
'performance_metrics': {
|
|
'traffic_growth': performance_metrics.traffic_growth_percentage if performance_metrics else None,
|
|
'engagement_rate': performance_metrics.engagement_rate_percentage if performance_metrics else None,
|
|
'conversion_rate': performance_metrics.conversion_rate_percentage if performance_metrics else None,
|
|
'roi_ratio': performance_metrics.roi_ratio if performance_metrics else None,
|
|
'content_quality_score': performance_metrics.content_quality_score if performance_metrics else None,
|
|
'data_source': performance_metrics.data_source if performance_metrics else None,
|
|
'confidence_score': performance_metrics.confidence_score if performance_metrics else None
|
|
},
|
|
'created_at': monitoring_plan.created_at.isoformat(),
|
|
'updated_at': monitoring_plan.updated_at.isoformat()
|
|
}
|
|
|
|
logger.info(f"Successfully retrieved monitoring data for strategy {strategy_id}")
|
|
return monitoring_data
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error retrieving monitoring data for strategy {strategy_id}: {e}")
|
|
return None
|
|
|
|
async def get_analytics_data(self, strategy_id: int) -> Dict[str, Any]:
|
|
"""Get analytics data from monitoring data (no external API calls)."""
|
|
try:
|
|
logger.info(f"Generating analytics data for strategy {strategy_id}")
|
|
|
|
# Get monitoring data from database
|
|
monitoring_data = await self.get_monitoring_data(strategy_id)
|
|
|
|
if not monitoring_data:
|
|
logger.warning(f"No monitoring data found for strategy {strategy_id}")
|
|
return self._get_empty_analytics_data()
|
|
|
|
# Extract analytics from monitoring data
|
|
monitoring_plan = monitoring_data['monitoring_plan']
|
|
tasks = monitoring_data['monitoring_tasks']
|
|
performance_metrics = monitoring_data['performance_metrics']
|
|
|
|
# Always use monitoring tasks from the plan for rich data, fallback to database tasks
|
|
monitoring_tasks = []
|
|
if monitoring_plan.get('monitoringTasks'):
|
|
# Use rich data from monitoring plan
|
|
monitoring_tasks = [
|
|
{
|
|
'id': i + 1,
|
|
'component': task.get('component', ''),
|
|
'title': task.get('title', ''),
|
|
'description': task.get('description', ''),
|
|
'assignee': task.get('assignee', 'ALwrity'),
|
|
'frequency': task.get('frequency', 'Monthly'),
|
|
'metric': task.get('metric', ''),
|
|
'measurementMethod': task.get('measurementMethod', ''),
|
|
'successCriteria': task.get('successCriteria', ''),
|
|
'alertThreshold': task.get('alertThreshold', ''),
|
|
'actionableInsights': task.get('actionableInsights', ''),
|
|
'status': 'active',
|
|
'last_executed': None,
|
|
'next_execution': None
|
|
}
|
|
for i, task in enumerate(monitoring_plan.get('monitoringTasks', []))
|
|
]
|
|
elif tasks:
|
|
# Fallback to database tasks if plan doesn't have them
|
|
monitoring_tasks = [
|
|
{
|
|
'id': task.id,
|
|
'component': task.component_name,
|
|
'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': '',
|
|
'status': task.status,
|
|
'last_executed': task.last_executed.isoformat() if task.last_executed else None,
|
|
'next_execution': task.next_execution.isoformat() if task.next_execution else None
|
|
}
|
|
for task in tasks
|
|
]
|
|
|
|
# Always use performance metrics from success metrics for rich data
|
|
extracted_metrics = {}
|
|
if monitoring_plan.get('successMetrics'):
|
|
success_metrics = monitoring_plan['successMetrics']
|
|
extracted_metrics = {
|
|
'traffic_growth': self._extract_percentage(success_metrics.get('trafficGrowth', {}).get('current', '0%')),
|
|
'engagement_rate': self._extract_percentage(success_metrics.get('engagementRate', {}).get('current', '0%')),
|
|
'conversion_rate': self._extract_percentage(success_metrics.get('conversionRate', {}).get('current', '0%')),
|
|
'roi_ratio': self._extract_ratio(success_metrics.get('roi', {}).get('current', '0:1')),
|
|
'content_quality_score': self._extract_percentage(success_metrics.get('contentQuality', {}).get('current', '0%')),
|
|
'data_source': 'monitoring_plan',
|
|
'confidence_score': 85
|
|
}
|
|
else:
|
|
# Fallback to database metrics if plan doesn't have them
|
|
extracted_metrics = {
|
|
'traffic_growth': performance_metrics.get('traffic_growth', 0),
|
|
'engagement_rate': performance_metrics.get('engagement_rate', 0),
|
|
'conversion_rate': performance_metrics.get('conversion_rate', 0),
|
|
'roi_ratio': performance_metrics.get('roi_ratio', 0),
|
|
'content_quality_score': performance_metrics.get('content_quality_score', 0),
|
|
'data_source': performance_metrics.get('data_source', 'database'),
|
|
'confidence_score': performance_metrics.get('confidence_score', 70)
|
|
}
|
|
|
|
# Build analytics data from monitoring plan
|
|
analytics_data = {
|
|
'performance_trends': {
|
|
'traffic_growth': extracted_metrics.get('traffic_growth', 0),
|
|
'engagement_rate': extracted_metrics.get('engagement_rate', 0),
|
|
'conversion_rate': extracted_metrics.get('conversion_rate', 0),
|
|
'roi_ratio': extracted_metrics.get('roi_ratio', 0),
|
|
'content_quality_score': extracted_metrics.get('content_quality_score', 0)
|
|
},
|
|
'content_evolution': {
|
|
'content_pillars': monitoring_plan.get('contentPillars', []),
|
|
'content_mix': monitoring_plan.get('contentMix', {}),
|
|
'publishing_frequency': monitoring_plan.get('publishingFrequency', ''),
|
|
'quality_metrics': monitoring_plan.get('qualityMetrics', [])
|
|
},
|
|
'engagement_patterns': {
|
|
'audience_segments': monitoring_plan.get('audienceSegments', []),
|
|
'engagement_metrics': monitoring_plan.get('engagementMetrics', {}),
|
|
'optimal_timing': monitoring_plan.get('optimalTiming', {}),
|
|
'platform_performance': monitoring_plan.get('platformPerformance', {})
|
|
},
|
|
'recommendations': monitoring_plan.get('recommendations', []),
|
|
'insights': monitoring_plan.get('insights', []),
|
|
'monitoring_data': monitoring_data,
|
|
'monitoring_tasks': monitoring_tasks,
|
|
'monitoring_plan': monitoring_plan, # Include full monitoring plan for rich data
|
|
'success_metrics': monitoring_plan.get('successMetrics', {}), # Include success metrics
|
|
'monitoring_schedule': monitoring_plan.get('monitoringSchedule', {}), # Include monitoring schedule
|
|
'_source': 'database_monitoring',
|
|
'data_freshness': monitoring_data['updated_at'],
|
|
'confidence_score': extracted_metrics.get('confidence_score', 85)
|
|
}
|
|
|
|
logger.info(f"Successfully generated analytics data for strategy {strategy_id}")
|
|
return analytics_data
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error generating analytics data for strategy {strategy_id}: {e}")
|
|
return self._get_empty_analytics_data()
|
|
|
|
def _get_empty_analytics_data(self) -> Dict[str, Any]:
|
|
"""Return empty analytics data structure."""
|
|
return {
|
|
'performance_trends': {},
|
|
'content_evolution': {},
|
|
'engagement_patterns': {},
|
|
'recommendations': [],
|
|
'insights': [],
|
|
'monitoring_data': None,
|
|
'monitoring_tasks': [],
|
|
'_source': 'empty',
|
|
'data_freshness': datetime.utcnow().isoformat(),
|
|
'confidence_score': 0
|
|
}
|
|
|
|
def _extract_percentage(self, value: str) -> float:
|
|
"""Extract percentage value from string like '15%'."""
|
|
try:
|
|
if isinstance(value, str) and '%' in value:
|
|
return float(value.replace('%', ''))
|
|
elif isinstance(value, (int, float)):
|
|
return float(value)
|
|
else:
|
|
return 0.0
|
|
except (ValueError, TypeError):
|
|
return 0.0
|
|
|
|
def _extract_ratio(self, value: str) -> float:
|
|
"""Extract ratio value from string like '3:1'."""
|
|
try:
|
|
if isinstance(value, str) and ':' in value:
|
|
parts = value.split(':')
|
|
if len(parts) == 2:
|
|
return float(parts[0]) / float(parts[1])
|
|
elif isinstance(value, (int, float)):
|
|
return float(value)
|
|
else:
|
|
return 0.0
|
|
except (ValueError, TypeError):
|
|
return 0.0
|
|
|
|
async def update_performance_metrics(self, strategy_id: int, metrics: Dict[str, Any]) -> bool:
|
|
"""Update performance metrics for a strategy."""
|
|
try:
|
|
logger.info(f"Updating performance metrics for strategy {strategy_id}")
|
|
|
|
performance_metrics = StrategyPerformanceMetrics(
|
|
strategy_id=strategy_id,
|
|
user_id=1, # Default user ID
|
|
metric_date=datetime.utcnow(),
|
|
traffic_growth_percentage=metrics.get('traffic_growth'),
|
|
engagement_rate_percentage=metrics.get('engagement_rate'),
|
|
conversion_rate_percentage=metrics.get('conversion_rate'),
|
|
roi_ratio=metrics.get('roi_ratio'),
|
|
content_quality_score=metrics.get('content_quality_score'),
|
|
data_source='manual_update',
|
|
confidence_score=metrics.get('confidence_score', 70)
|
|
)
|
|
|
|
self.db.add(performance_metrics)
|
|
self.db.commit()
|
|
|
|
logger.info(f"Successfully updated performance metrics for strategy {strategy_id}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error updating performance metrics for strategy {strategy_id}: {e}")
|
|
self.db.rollback()
|
|
return False
|
|
|
|
def get_user_execution_logs(
|
|
self,
|
|
user_id: int,
|
|
limit: Optional[int] = 50,
|
|
offset: Optional[int] = 0,
|
|
status_filter: Optional[str] = None
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Get execution logs for a specific user.
|
|
|
|
Args:
|
|
user_id: User ID to filter execution logs
|
|
limit: Maximum number of logs to return
|
|
offset: Number of logs to skip (for pagination)
|
|
status_filter: Optional status filter ('success', 'failed', 'running', 'skipped')
|
|
|
|
Returns:
|
|
List of execution log dictionaries with task details
|
|
"""
|
|
try:
|
|
logger.info(f"Getting execution logs for user {user_id}")
|
|
|
|
# Build query for execution logs filtered by user_id
|
|
query = self.db.query(TaskExecutionLog).filter(
|
|
TaskExecutionLog.user_id == user_id
|
|
)
|
|
|
|
# Apply status filter if provided
|
|
if status_filter:
|
|
query = query.filter(TaskExecutionLog.status == status_filter)
|
|
|
|
# Order by execution date (most recent first)
|
|
query = query.order_by(desc(TaskExecutionLog.execution_date))
|
|
|
|
# Apply pagination
|
|
if limit:
|
|
query = query.limit(limit)
|
|
if offset:
|
|
query = query.offset(offset)
|
|
|
|
logs = query.all()
|
|
|
|
# Convert to dictionaries with task details
|
|
logs_data = []
|
|
for log in logs:
|
|
# Get task details if available
|
|
task = self.db.query(MonitoringTask).filter(
|
|
MonitoringTask.id == log.task_id
|
|
).first()
|
|
|
|
log_data = {
|
|
"id": log.id,
|
|
"task_id": log.task_id,
|
|
"user_id": log.user_id,
|
|
"execution_date": log.execution_date.isoformat() if log.execution_date else None,
|
|
"status": log.status,
|
|
"result_data": log.result_data,
|
|
"error_message": log.error_message,
|
|
"execution_time_ms": log.execution_time_ms,
|
|
"created_at": log.created_at.isoformat() if log.created_at else None,
|
|
"task": {
|
|
"title": task.task_title if task else None,
|
|
"description": task.task_description if task else None,
|
|
"assignee": task.assignee if task else None,
|
|
"frequency": task.frequency if task else None,
|
|
"strategy_id": task.strategy_id if task else None
|
|
} if task else None
|
|
}
|
|
logs_data.append(log_data)
|
|
|
|
logger.info(f"Retrieved {len(logs_data)} execution logs for user {user_id}")
|
|
return logs_data
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error getting execution logs for user {user_id}: {e}")
|
|
return []
|