""" Research Persona Scheduler Handles scheduled generation of research personas after onboarding. """ from datetime import datetime, timedelta, timezone from typing import Dict, Any from loguru import logger from services.database import get_db_session from services.research.research_persona_service import ResearchPersonaService from models.scheduler_models import SchedulerEventLog async def generate_research_persona_task(user_id: str): """ Async task function to generate research persona for a user. This function is called by the scheduler 20 minutes after onboarding completion. Args: user_id: User ID (Clerk string) """ db = None try: logger.info(f"Scheduled research persona generation started for user {user_id}") # Get database session db = get_db_session(user_id) if not db: logger.error(f"Failed to get database session for research persona generation (user: {user_id})") return # Generate research persona persona_service = ResearchPersonaService(db_session=db) # Check if persona already exists to avoid unnecessary API calls persona_data = persona_service._get_persona_data_record(user_id) if persona_data and persona_data.research_persona: logger.info(f"Research persona already exists for user {user_id}, skipping generation") return start_time = datetime.utcnow() try: research_persona = persona_service.get_or_generate(user_id, force_refresh=False) execution_time = (datetime.utcnow() - start_time).total_seconds() if research_persona: logger.info(f"✅ Scheduled research persona generation completed for user {user_id}") # Log success to scheduler event log for dashboard try: event_log = SchedulerEventLog( event_type='job_completed', event_date=start_time, job_id=f"research_persona_{user_id}", job_type='one_time', user_id=user_id, event_data={ 'job_function': 'generate_research_persona_task', 'execution_time_seconds': execution_time, 'status': 'success' } ) db.add(event_log) db.commit() except Exception as log_error: logger.warning(f"Failed to log persona generation success to scheduler event log: {log_error}") if db: db.rollback() else: error_msg = ( f"Scheduled research persona generation FAILED for user {user_id}. " f"Expensive API call was made but generation failed. " f"Will NOT automatically retry to prevent wasteful API calls." ) logger.error(f"❌ {error_msg}") # Log failure to scheduler event log for dashboard visibility try: event_log = SchedulerEventLog( event_type='job_failed', event_date=start_time, job_id=f"research_persona_{user_id}", job_type='one_time', user_id=user_id, error_message=error_msg, event_data={ 'job_function': 'generate_research_persona_task', 'execution_time_seconds': execution_time, 'status': 'failed', 'failure_reason': 'generation_returned_none', 'expensive_api_call': True } ) db.add(event_log) db.commit() except Exception as log_error: logger.warning(f"Failed to log persona generation failure to scheduler event log: {log_error}") if db: db.rollback() # DO NOT reschedule - this prevents infinite retry loops # User can manually trigger generation from frontend if needed except Exception as gen_error: execution_time = (datetime.utcnow() - start_time).total_seconds() error_msg = ( f"Exception during scheduled research persona generation for user {user_id}: {str(gen_error)}. " f"Expensive API call may have been made. Will NOT automatically retry." ) logger.error(f"❌ {error_msg}") # Log exception to scheduler event log for dashboard visibility try: event_log = SchedulerEventLog( event_type='job_failed', event_date=start_time, job_id=f"research_persona_{user_id}", # Match scheduled job ID format job_type='one_time', user_id=user_id, error_message=error_msg, event_data={ 'job_function': 'generate_research_persona_task', 'execution_time_seconds': execution_time, 'status': 'failed', 'failure_reason': 'exception', 'exception_type': type(gen_error).__name__, 'exception_message': str(gen_error), 'expensive_api_call': True } ) db.add(event_log) db.commit() except Exception as log_error: logger.warning(f"Failed to log persona generation exception to scheduler event log: {log_error}") if db: db.rollback() # DO NOT reschedule - prevent infinite retry loops except Exception as e: logger.error(f"Error in scheduled research persona generation for user {user_id}: {e}") finally: if db: try: db.close() except Exception as e: logger.error(f"Error closing database session: {e}") def schedule_research_persona_generation(user_id: str, delay_minutes: int = 20) -> str: """ Schedule research persona generation for a user after a delay. Args: user_id: User ID (Clerk string) delay_minutes: Delay in minutes before generating persona (default: 20) Returns: Job ID """ try: from services.scheduler import get_scheduler scheduler = get_scheduler() # Calculate run date (current time + delay) - ensure UTC timezone-aware run_date = datetime.now(timezone.utc) + timedelta(minutes=delay_minutes) # Generate consistent job ID (without timestamp) for proper restoration # This allows restoration to find and restore the job with original scheduled time # Note: Clerk user_id already includes "user_" prefix, so we don't add it again job_id = f"research_persona_{user_id}" # Schedule the task scheduled_job_id = scheduler.schedule_one_time_task( func=generate_research_persona_task, run_date=run_date, job_id=job_id, kwargs={"user_id": user_id}, replace_existing=True ) logger.info( f"Scheduled research persona generation for user {user_id} " f"at {run_date} (job_id: {scheduled_job_id})" ) return scheduled_job_id except Exception as e: logger.error(f"Failed to schedule research persona generation for user {user_id}: {e}") raise