from datetime import timedelta from django.tasks import task from django.utils import timezone from games.models import EventGame from submissions.models import ImageCrop, SubmissionFile, OCRStatus from submissions.services.aandt_sync import ( AANDT_STATUS_IN_PROGRESS, sync_event_game_scopes, ) from PIL import Image import pytesseract # How often to repoll an EventGame's AAndT while it's still "in progress". SCOPE_SYNC_REPOLL_INTERVAL = timedelta(seconds=15) def _process_ocr_file(obj, file, crop: ImageCrop | None = None) -> bool: if obj.ocr_status != OCRStatus.TO_PROCESS: return False obj.ocr_status = OCRStatus.PROCESSING obj.save(update_fields=["ocr_status"]) try: image = Image.open(file) if crop: image = image.crop((crop.x1, crop.y1, crop.x2, crop.y2)) obj.ocr_raw_data = pytesseract.image_to_string(image) obj.ocr_status = OCRStatus.PROCESSED except Exception as e: obj.ocr_status = OCRStatus.ERROR raise e finally: obj.save(update_fields=["ocr_status", "ocr_raw_data"]) return True @task def process_submission_files(): """Process uploaded submission files (OCR, validation, etc.)""" for file in SubmissionFile.objects.filter(ocr_status=OCRStatus.TO_PROCESS): process_submission_file.enqueue(str(file.id)) @task def process_submission_file(file_id: str): file = SubmissionFile.objects.filter(id=file_id).first() if not file: return _process_ocr_file(file, file.file) process_crops.enqueue(file_id) @task def process_crops(file_id: str): file = SubmissionFile.objects.filter(id=file_id).first() if not file: return for crop in file.crops.all(): process_crop.enqueue(str(crop.id)) @task def process_crop(crop_id: str): crop = ImageCrop.objects.filter(id=crop_id).first() if not crop: return _process_ocr_file(crop, crop.submission_file.file, crop=crop) @task def sync_all_event_games_task(): """Queue a Scope sync for every EventGame with an AAndT token configured.""" event_games = EventGame.objects.exclude(apibot_token__isnull=True).exclude( apibot_token="" ) for event_game in event_games: sync_event_game_scopes_task.enqueue(event_game.id) @task def sync_event_game_scopes_task(event_game_id: int): """Sync an EventGame's Scopes, repolling every minute while matches are in progress. Each run re-checks the AAndT's status (from the `/` endpoint). While it's still "in progress" (`7inprog`), the task re-enqueues itself with a delay instead of relying on an external scheduler; once the status moves on, it simply doesn't reschedule and the loop stops. """ event_game = EventGame.objects.filter(id=event_game_id).first() if not event_game: return result = sync_event_game_scopes(event_game) if result.status == AANDT_STATUS_IN_PROGRESS: sync_event_game_scopes_task.using( run_after=timezone.now() + SCOPE_SYNC_REPOLL_INTERVAL ).enqueue(event_game_id)