"""Version management and submission recalculation.""" from datetime import datetime from typing import Optional from sqlalchemy import select from src.code_challenge import run_code_submission, CodeValidationError from src.database import DatabaseManager, VersionHistory, Score, Submission class VersionManager: """Manages project versioning and bulk recalculation.""" def __init__(self, db_manager: DatabaseManager): self.db = db_manager def bump_version( self, new_version: str, is_major: bool = False, notes: Optional[str] = None, ) -> dict: """ Bump version and optionally mark old submissions for recalculation. Args: new_version: Version string (e.g., "1.1.0") is_major: If True, mark all submissions from old version for recalculation notes: Optional release notes Returns: Dictionary with version record and recalculation status """ version_record = self.db.record_version( version=new_version, is_major=is_major, notes=notes, ) recalc_count = 0 if is_major: with self.db.get_session() as session: old_version = ( session.query(VersionHistory) .filter(VersionHistory.version != new_version) .order_by(VersionHistory.released_at.desc()) .first() ) if old_version: recalc_count = self.db.mark_for_recalculation(old_version.version) return { "new_version": new_version, "is_major": is_major, "submitted_at": version_record.released_at, "submissions_marked_for_recalc": recalc_count, "notes": notes, } def recalculate_submission( self, submission_id: int, new_version: str, ) -> dict: """ Recalculate a single submission against the current code. Args: submission_id: ID of submission to recalculate new_version: New version string for the score Returns: Dictionary with recalculation results """ with self.db.get_session() as session: submission = ( session.query(Submission) .filter(Submission.id == submission_id) .first() ) if not submission: return {"success": False, "error": f"Submission {submission_id} not found"} try: _, metrics, _, _ = run_code_submission( team=submission.username, model_name=submission.submission_name, dataset=submission.dataset, correction_code=submission.correction_code, model_code=submission.model_code, ) except (CodeValidationError, Exception) as exc: return { "success": False, "error": f"Recalculation failed: {type(exc).__name__}: {exc}", } old_score = ( session.query(Score) .filter(Score.submission_id == submission_id) .order_by(Score.created_at.desc()) .first() ) new_score = self.db.create_score( submission_id=submission_id, accuracy=float(metrics["accuracy"]), macro_f1=float(metrics["macro_f1"]), n_samples=int(metrics["n_samples"]), version=new_version, ) if old_score: old_score.needs_recalc = False session.commit() return { "success": True, "submission_id": submission_id, "username": submission.username, "dataset": submission.dataset, "submission_name": submission.submission_name, "old_accuracy": float(old_score.accuracy) if old_score else None, "new_accuracy": float(metrics["accuracy"]), "old_macro_f1": float(old_score.macro_f1) if old_score else None, "new_macro_f1": float(metrics["macro_f1"]), "recalculated_at": new_score.created_at, } def bulk_recalculate(self, new_version: str, max_count: Optional[int] = None) -> list[dict]: """ Recalculate all submissions marked for recalculation. Args: new_version: Version string to apply to new scores max_count: Maximum number of submissions to recalculate (None = all) Returns: List of recalculation results """ submissions_needing_recalc = self.db.get_submissions_needing_recalc() if max_count: submissions_needing_recalc = submissions_needing_recalc[:max_count] results = [] for submission_id in submissions_needing_recalc: result = self.recalculate_submission(submission_id, new_version) results.append(result) return results