| """ |
| Round 2 task distribution script for LLM Deployment System. |
| |
| This script handles distributing round 2 tasks to students who have |
| completed their round 1 repositories. |
| """ |
|
|
| import json |
| import time |
| import argparse |
| from datetime import datetime, timezone |
| from typing import Dict, List, Any, Optional |
| import requests |
|
|
| from .config import config |
| from .database import db_manager, TaskStatus, EvaluationStatus |
| from .logger import get_logger, log_request_info |
| from .task_generator import get_task_generator |
| from .db_utils import DatabaseUtils |
|
|
| logger = get_logger(__name__) |
|
|
|
|
| class Round2Distributor: |
| """Handles round 2 task distribution.""" |
|
|
| def __init__(self): |
| self.task_generator = get_task_generator() |
| self.distribution_log = [] |
|
|
| def get_eligible_students(self) -> List[Dict[str, Any]]: |
| """Get students who are eligible for round 2 tasks.""" |
| eligible_students = [] |
|
|
| try: |
| |
| with db_manager.get_session() as session: |
| |
| submissions = session.query(db_manager.Submission).all() |
|
|
| for submission in submissions: |
| |
| round1_tasks = DatabaseUtils.get_tasks_by_submission(session, submission.id, round=1) |
|
|
| completed_round1 = any( |
| task.status == TaskStatus.RECEIVED and task.repos |
| for task in round1_tasks |
| ) |
|
|
| if completed_round1: |
| |
| round2_tasks = DatabaseUtils.get_tasks_by_submission(session, submission.id, round=2) |
|
|
| if not round2_tasks: |
| eligible_students.append({ |
| 'submission_id': submission.id, |
| 'email': submission.email, |
| 'endpoint': submission.endpoint, |
| 'secret': submission.secret, |
| 'github_username': submission.github_username, |
| 'github_repo_url': submission.github_repo_url |
| }) |
|
|
| logger.info(f"Found {len(eligible_students)} students eligible for round 2") |
| return eligible_students |
|
|
| except Exception as e: |
| logger.error(f"Failed to get eligible students: {e}") |
| raise |
|
|
| def distribute_tasks(self, students: List[Dict[str, Any]], delay: float = 1.0) -> Dict[str, Any]: |
| """Distribute round 2 tasks to eligible students.""" |
| results = { |
| 'total': len(students), |
| 'successful': 0, |
| 'failed': 0, |
| 'errors': [] |
| } |
|
|
| logger.info(f"Starting round 2 distribution to {len(students)} students") |
|
|
| for i, student in enumerate(students): |
| try: |
| logger.info(f"Processing student {i+1}/{len(students)}: {student['email']}") |
|
|
| |
| task = self.task_generator.generate_task( |
| email=student['email'], |
| round_num=2 |
| ) |
|
|
| |
| success = self._send_task_to_student(student, task, delay) |
|
|
| if success: |
| results['successful'] += 1 |
|
|
| |
| db_task = db_manager.create_task(student['submission_id'], task) |
| db_manager.update_task_status(task['task_id'], TaskStatus.SENT) |
|
|
| self.distribution_log.append({ |
| 'email': student['email'], |
| 'task_id': task['task_id'], |
| 'status': 'sent', |
| 'timestamp': datetime.now(timezone.utc).isoformat() |
| }) |
|
|
| else: |
| results['failed'] += 1 |
| self.distribution_log.append({ |
| 'email': student['email'], |
| 'status': 'failed', |
| 'timestamp': datetime.now(timezone.utc).isoformat() |
| }) |
|
|
| except Exception as e: |
| logger.error(f"Error processing student {student['email']}: {e}") |
| results['failed'] += 1 |
| results['errors'].append({ |
| 'email': student['email'], |
| 'error': str(e) |
| }) |
|
|
| logger.info(f"Round 2 distribution completed: {results['successful']}/{results['total']} successful") |
| return results |
|
|
| def _send_task_to_student(self, student: Dict[str, Any], task: Dict[str, Any], delay: float) -> bool: |
| """Send round 2 task to student's API endpoint.""" |
| try: |
| |
| payload = { |
| 'email': student['email'], |
| 'secret': student['secret'] |
| } |
|
|
| headers = { |
| 'Content-Type': 'application/json', |
| 'User-Agent': 'LLM-Deployment-System/1.0' |
| } |
|
|
| |
| response = requests.post( |
| student['endpoint'], |
| json=payload, |
| headers=headers, |
| timeout=30 |
| ) |
|
|
| |
| if response.status_code == 200: |
| logger.info(f"Successfully sent round 2 task to {student['email']}") |
| time.sleep(delay) |
| return True |
| else: |
| logger.warning(f"Failed to send round 2 task to {student['email']}: HTTP {response.status_code}") |
| return False |
|
|
| except requests.RequestException as e: |
| logger.error(f"Request error sending round 2 task to {student['email']}: {e}") |
| return False |
| except Exception as e: |
| logger.error(f"Unexpected error sending round 2 task to {student['email']}: {e}") |
| return False |
|
|
| def save_distribution_log(self, log_file: str = 'round2_distribution_log.json'): |
| """Save distribution log to file.""" |
| try: |
| with open(log_file, 'w') as f: |
| json.dump(self.distribution_log, f, indent=2) |
| logger.info(f"Saved round 2 distribution log to {log_file}") |
| except Exception as e: |
| logger.error(f"Failed to save round 2 distribution log: {e}") |
|
|
| def generate_summary_report(self) -> str: |
| """Generate a summary report of the round 2 distribution.""" |
| if not self.distribution_log: |
| return "No round 2 distribution data available" |
|
|
| total = len(self.distribution_log) |
| sent = len([log for log in self.distribution_log if log['status'] == 'sent']) |
| failed = len([log for log in self.distribution_log if log['status'] == 'failed']) |
|
|
| report = f""" |
| Round 2 Task Distribution Summary |
| ================================ |
| |
| Total students eligible: {total} |
| Successfully sent: {sent} |
| Failed: {failed} |
| Success rate: {(sent/total)*100:.1f}% |
| |
| Timestamp: {datetime.now(timezone.utc).isoformat()} |
| """ |
|
|
| return report |
|
|
|
|
| def main(): |
| """Main function for command-line usage.""" |
| parser = argparse.ArgumentParser(description='Distribute Round 2 tasks to eligible students') |
| parser.add_argument('--delay', type=float, default=1.0, help='Delay between requests (seconds)') |
| parser.add_argument('--log-file', default='round2_distribution_log.json', help='Distribution log file') |
| parser.add_argument('--dry-run', action='store_true', help='Show what would be sent without actually sending') |
| parser.add_argument('--min-eligible', type=int, default=1, help='Minimum number of eligible students required') |
|
|
| args = parser.parse_args() |
|
|
| try: |
| distributor = Round2Distributor() |
|
|
| |
| students = distributor.get_eligible_students() |
|
|
| if len(students) < args.min_eligible: |
| logger.error(f"Only {len(students)} eligible students found, minimum {args.min_eligible} required") |
| exit(1) |
|
|
| if args.dry_run: |
| print(f"Dry run: Would process {len(students)} eligible students") |
| for student in students[:5]: |
| print(f" - {student['email']} -> {student['endpoint']}") |
| if len(students) > 5: |
| print(f" ... and {len(students) - 5} more") |
| return |
|
|
| |
| results = distributor.distribute_tasks(students, args.delay) |
|
|
| |
| distributor.save_distribution_log(args.log_file) |
|
|
| |
| print(distributor.generate_summary_report()) |
|
|
| |
| if results['failed'] == 0: |
| exit(0) |
| else: |
| exit(1) |
|
|
| except Exception as e: |
| logger.error(f"Round 2 distribution failed: {e}") |
| exit(1) |
|
|
|
|
| if __name__ == '__main__': |
| main() |
|
|