""" Conflict resolution system for content scheduling. """ import logging from datetime import datetime, timedelta from typing import Dict, List, Any, Optional, Tuple from dataclasses import dataclass # Use unified database models from lib.database.models import ContentItem, Schedule, ScheduleStatus logger = logging.getLogger(__name__) @dataclass class ConflictInfo: """Information about a scheduling conflict.""" schedule_1: Schedule schedule_2: Schedule conflict_type: str severity: str description: str suggested_resolution: str class ConflictResolver: """Resolve scheduling conflicts automatically.""" def __init__(self): """Initialize the conflict resolver.""" self.logger = logger self.resolution_strategies = { 'time_overlap': self._resolve_time_overlap, 'platform_conflict': self._resolve_platform_conflict, 'resource_conflict': self._resolve_resource_conflict, 'priority_conflict': self._resolve_priority_conflict } def detect_conflicts(self, schedules: List[Schedule]) -> List[ConflictInfo]: """Detect conflicts between schedules. Args: schedules: List of Schedule objects to check Returns: List of detected conflicts """ try: conflicts = [] # Sort schedules by time sorted_schedules = sorted(schedules, key=lambda x: x.scheduled_time) for i in range(len(sorted_schedules)): for j in range(i + 1, len(sorted_schedules)): schedule_1 = sorted_schedules[i] schedule_2 = sorted_schedules[j] # Check for time overlap conflicts time_conflicts = self._check_time_overlap(schedule_1, schedule_2) conflicts.extend(time_conflicts) # Check for platform conflicts platform_conflicts = self._check_platform_conflict(schedule_1, schedule_2) conflicts.extend(platform_conflicts) # Check for priority conflicts priority_conflicts = self._check_priority_conflict(schedule_1, schedule_2) conflicts.extend(priority_conflicts) return conflicts except Exception as e: self.logger.error(f"Error detecting conflicts: {str(e)}") return [] def _check_time_overlap(self, schedule_1: Schedule, schedule_2: Schedule) -> List[ConflictInfo]: """Check for time overlap conflicts.""" conflicts = [] try: # Assume each schedule takes 1 hour (can be made configurable) duration = timedelta(hours=1) end_1 = schedule_1.scheduled_time + duration end_2 = schedule_2.scheduled_time + duration # Check for overlap if (schedule_1.scheduled_time < end_2 and end_1 > schedule_2.scheduled_time): time_diff = abs((schedule_2.scheduled_time - schedule_1.scheduled_time).total_seconds() / 60) severity = 'high' if time_diff < 30 else 'medium' conflicts.append(ConflictInfo( schedule_1=schedule_1, schedule_2=schedule_2, conflict_type='time_overlap', severity=severity, description=f"Schedules overlap by {60 - time_diff:.0f} minutes", suggested_resolution=f"Move one schedule by at least {60 - time_diff + 15:.0f} minutes" )) except Exception as e: self.logger.error(f"Error checking time overlap: {str(e)}") return conflicts def _check_platform_conflict(self, schedule_1: Schedule, schedule_2: Schedule) -> List[ConflictInfo]: """Check for platform conflicts.""" conflicts = [] try: # This is a placeholder - platform conflicts would depend on specific platform limitations # For now, we'll check if schedules are too close on the same platform time_diff = abs((schedule_2.scheduled_time - schedule_1.scheduled_time).total_seconds() / 60) # If schedules are within 15 minutes, it might be a platform conflict if time_diff < 15: conflicts.append(ConflictInfo( schedule_1=schedule_1, schedule_2=schedule_2, conflict_type='platform_conflict', severity='medium', description=f"Schedules too close for optimal platform performance", suggested_resolution="Space schedules at least 15 minutes apart" )) except Exception as e: self.logger.error(f"Error checking platform conflict: {str(e)}") return conflicts def _check_priority_conflict(self, schedule_1: Schedule, schedule_2: Schedule) -> List[ConflictInfo]: """Check for priority conflicts.""" conflicts = [] try: # Check if high priority items are scheduled too close to low priority items if schedule_1.priority > 7 and schedule_2.priority < 4: time_diff = abs((schedule_2.scheduled_time - schedule_1.scheduled_time).total_seconds() / 60) if time_diff < 60: # Within 1 hour conflicts.append(ConflictInfo( schedule_1=schedule_1, schedule_2=schedule_2, conflict_type='priority_conflict', severity='low', description="High priority content scheduled close to low priority content", suggested_resolution="Consider spacing high and low priority content further apart" )) except Exception as e: self.logger.error(f"Error checking priority conflict: {str(e)}") return conflicts def resolve_conflicts(self, conflicts: List[ConflictInfo]) -> Dict[str, Any]: """Resolve detected conflicts automatically. Args: conflicts: List of conflicts to resolve Returns: Dictionary containing resolution results """ try: resolved_conflicts = [] unresolved_conflicts = [] schedule_adjustments = {} for conflict in conflicts: try: # Get resolution strategy strategy = self.resolution_strategies.get(conflict.conflict_type) if strategy: resolution = strategy(conflict) if resolution['success']: resolved_conflicts.append({ 'conflict': conflict, 'resolution': resolution }) # Track schedule adjustments for schedule_id, adjustments in resolution.get('adjustments', {}).items(): if schedule_id not in schedule_adjustments: schedule_adjustments[schedule_id] = {} schedule_adjustments[schedule_id].update(adjustments) else: unresolved_conflicts.append(conflict) else: unresolved_conflicts.append(conflict) except Exception as e: self.logger.error(f"Error resolving conflict: {str(e)}") unresolved_conflicts.append(conflict) return { 'resolved_conflicts': resolved_conflicts, 'unresolved_conflicts': unresolved_conflicts, 'schedule_adjustments': schedule_adjustments, 'success_rate': len(resolved_conflicts) / len(conflicts) if conflicts else 1.0 } except Exception as e: self.logger.error(f"Error resolving conflicts: {str(e)}") return { 'resolved_conflicts': [], 'unresolved_conflicts': conflicts, 'schedule_adjustments': {}, 'success_rate': 0.0 } def _resolve_time_overlap(self, conflict: ConflictInfo) -> Dict[str, Any]: """Resolve time overlap conflicts.""" try: # Strategy: Move the lower priority schedule schedule_1 = conflict.schedule_1 schedule_2 = conflict.schedule_2 # Determine which schedule to move if schedule_1.priority >= schedule_2.priority: schedule_to_move = schedule_2 anchor_schedule = schedule_1 else: schedule_to_move = schedule_1 anchor_schedule = schedule_2 # Calculate new time (move 1.5 hours after anchor) new_time = anchor_schedule.scheduled_time + timedelta(hours=1.5) return { 'success': True, 'strategy': 'move_lower_priority', 'adjustments': { str(schedule_to_move.id): { 'new_scheduled_time': new_time, 'reason': 'Resolved time overlap conflict' } }, 'description': f"Moved schedule {schedule_to_move.id} to {new_time}" } except Exception as e: self.logger.error(f"Error resolving time overlap: {str(e)}") return {'success': False, 'error': str(e)} def _resolve_platform_conflict(self, conflict: ConflictInfo) -> Dict[str, Any]: """Resolve platform conflicts.""" try: # Strategy: Space schedules 20 minutes apart schedule_1 = conflict.schedule_1 schedule_2 = conflict.schedule_2 # Move the later schedule if schedule_1.scheduled_time < schedule_2.scheduled_time: schedule_to_move = schedule_2 anchor_time = schedule_1.scheduled_time else: schedule_to_move = schedule_1 anchor_time = schedule_2.scheduled_time new_time = anchor_time + timedelta(minutes=20) return { 'success': True, 'strategy': 'space_schedules', 'adjustments': { str(schedule_to_move.id): { 'new_scheduled_time': new_time, 'reason': 'Resolved platform conflict' } }, 'description': f"Spaced schedule {schedule_to_move.id} to {new_time}" } except Exception as e: self.logger.error(f"Error resolving platform conflict: {str(e)}") return {'success': False, 'error': str(e)} def _resolve_resource_conflict(self, conflict: ConflictInfo) -> Dict[str, Any]: """Resolve resource conflicts.""" try: # This is a placeholder for resource conflict resolution return { 'success': False, 'reason': 'Resource conflict resolution not implemented' } except Exception as e: self.logger.error(f"Error resolving resource conflict: {str(e)}") return {'success': False, 'error': str(e)} def _resolve_priority_conflict(self, conflict: ConflictInfo) -> Dict[str, Any]: """Resolve priority conflicts.""" try: # Strategy: Move low priority content away from high priority content schedule_1 = conflict.schedule_1 schedule_2 = conflict.schedule_2 # Identify high and low priority schedules if schedule_1.priority > schedule_2.priority: high_priority = schedule_1 low_priority = schedule_2 else: high_priority = schedule_2 low_priority = schedule_1 # Move low priority content 2 hours away new_time = high_priority.scheduled_time + timedelta(hours=2) return { 'success': True, 'strategy': 'separate_priorities', 'adjustments': { str(low_priority.id): { 'new_scheduled_time': new_time, 'reason': 'Resolved priority conflict' } }, 'description': f"Moved low priority schedule {low_priority.id} to {new_time}" } except Exception as e: self.logger.error(f"Error resolving priority conflict: {str(e)}") return {'success': False, 'error': str(e)} def suggest_optimal_schedule( self, new_schedule: Schedule, existing_schedules: List[Schedule] ) -> Dict[str, Any]: """Suggest optimal scheduling for new content. Args: new_schedule: New schedule to optimize existing_schedules: List of existing schedules Returns: Dictionary containing optimization suggestions """ try: suggestions = [] # Check for conflicts with proposed time all_schedules = existing_schedules + [new_schedule] conflicts = self.detect_conflicts(all_schedules) if not conflicts: return { 'optimal_time': new_schedule.scheduled_time, 'conflicts': [], 'suggestions': ['Current time is optimal'] } # Generate alternative times base_time = new_schedule.scheduled_time alternative_times = [] # Try different time slots for hours_offset in [1, 2, 3, -1, -2, -3]: alt_time = base_time + timedelta(hours=hours_offset) alt_schedule = Schedule( content_item_id=new_schedule.content_item_id, scheduled_time=alt_time, status=new_schedule.status, recurrence=new_schedule.recurrence, priority=new_schedule.priority ) # Check conflicts for this alternative alt_conflicts = self.detect_conflicts(existing_schedules + [alt_schedule]) alternative_times.append({ 'time': alt_time, 'conflicts': len(alt_conflicts), 'severity': max([c.severity for c in alt_conflicts], default='none') }) # Sort by number of conflicts and severity alternative_times.sort(key=lambda x: (x['conflicts'], x['severity'])) optimal_time = alternative_times[0]['time'] if alternative_times else new_schedule.scheduled_time return { 'optimal_time': optimal_time, 'conflicts': conflicts, 'alternatives': alternative_times[:3], # Top 3 alternatives 'suggestions': [ f"Consider scheduling at {optimal_time}", f"Current time has {len(conflicts)} conflicts", "Review alternative times for better optimization" ] } except Exception as e: self.logger.error(f"Error suggesting optimal schedule: {str(e)}") return { 'optimal_time': new_schedule.scheduled_time, 'conflicts': [], 'suggestions': ['Error occurred during optimization'] }