"""Instructor API endpoint for receiving repo submissions.""" from datetime import datetime from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse from instructor.database import Database from shared.logger import setup_logger from shared.models import RepoSubmission logger = setup_logger(__name__) app = FastAPI( title="LLM Code Deployment - Instructor API", description="API endpoint for receiving and validating repo submissions", version="1.0.0", ) # Initialize database db = Database() @app.on_event("startup") async def startup_event(): """Create database tables on startup.""" db.create_tables() logger.info("Database initialized") @app.post("/api/evaluate") async def evaluate_submission(submission: RepoSubmission) -> JSONResponse: """Receive and validate repo submission. Args: submission: Repository submission from student Returns: HTTP 200 on success, HTTP 400 on validation failure """ logger.info( f"Received submission: {submission.email}, " f"task {submission.task}, round {submission.round}" ) # Validate submission against task record task = db.get_task_by_nonce(submission.nonce) if not task: logger.error(f"Invalid nonce: {submission.nonce}") raise HTTPException(status_code=400, detail="Invalid nonce") # Validate fields match if task.email != submission.email: logger.error(f"Email mismatch: expected {task.email}, got {submission.email}") raise HTTPException(status_code=400, detail="Email mismatch") if task.task != submission.task: logger.error(f"Task mismatch: expected {task.task}, got {submission.task}") raise HTTPException(status_code=400, detail="Task mismatch") if task.round != submission.round: logger.error(f"Round mismatch: expected {task.round}, got {submission.round}") raise HTTPException(status_code=400, detail="Round mismatch") # Check if already submitted try: existing = db.get_session().query(db.Repo).filter_by(nonce=submission.nonce).first() if existing: logger.warning(f"Duplicate submission for nonce {submission.nonce}") return JSONResponse( status_code=200, content={ "status": "duplicate", "message": "Submission already received", }, ) finally: db.get_session().close() # Add to repos table try: repo = db.add_repo( { "timestamp": datetime.utcnow(), "email": submission.email, "task": submission.task, "round": submission.round, "nonce": submission.nonce, "repo_url": submission.repo_url, "commit_sha": submission.commit_sha, "pages_url": submission.pages_url, } ) logger.info( f"Added repo submission: {submission.task}, round {submission.round}, " f"repo {submission.repo_url}" ) return JSONResponse( status_code=200, content={ "status": "success", "message": "Submission received and queued for evaluation", "repo_id": repo.id, }, ) except Exception as e: logger.error(f"Failed to add repo submission: {e}", exc_info=True) raise HTTPException(status_code=500, detail="Internal server error") @app.get("/api/submissions/{email}") async def get_submissions(email: str) -> JSONResponse: """Get all submissions for an email. Args: email: Student email Returns: List of submissions """ repos = db.get_repos(email=email) return JSONResponse(content=[repo.to_dict() for repo in repos]) @app.get("/api/results/{email}") async def get_results(email: str) -> JSONResponse: """Get all evaluation results for an email. Args: email: Student email Returns: List of evaluation results """ results = db.get_results(email=email) return JSONResponse(content=[result.to_dict() for result in results]) @app.get("/health") async def health_check() -> JSONResponse: """Health check endpoint. Returns: Health status """ return JSONResponse( content={ "status": "healthy", "timestamp": datetime.utcnow().isoformat(), } ) if __name__ == "__main__": import uvicorn from shared.config import settings logger.info(f"Starting Instructor API on port {settings.instructor_api_port}") uvicorn.run( "instructor.api:app", host="0.0.0.0", port=settings.instructor_api_port, reload=True, )