|  | from pathlib import Path | 
					
						
						|  | import tempfile | 
					
						
						|  | from typing import BinaryIO | 
					
						
						|  | import json | 
					
						
						|  |  | 
					
						
						|  | import gradio as gr | 
					
						
						|  | from datetime import datetime, timezone | 
					
						
						|  | import uuid | 
					
						
						|  |  | 
					
						
						|  | from constants import API, SUBMISSIONS_REPO, REGISTRATION_CODE | 
					
						
						|  | from validation import validate_csv_file, validate_username | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def upload_submission( | 
					
						
						|  | file_content: str, | 
					
						
						|  | user_state, | 
					
						
						|  | submission_type: str, | 
					
						
						|  | model_name: str, | 
					
						
						|  | model_description: str, | 
					
						
						|  | anonymous: bool = False, | 
					
						
						|  | ): | 
					
						
						|  | """Upload submission without validation (assumes validation already done)""" | 
					
						
						|  | timestamp = datetime.now(timezone.utc).isoformat() | 
					
						
						|  | date = datetime.now(timezone.utc).date().isoformat() | 
					
						
						|  | submission_id = str(uuid.uuid4()) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | filename = f"{user_state}/{date}_{model_name}_{submission_id}.json" | 
					
						
						|  |  | 
					
						
						|  | record = { | 
					
						
						|  | "submission_id": submission_id, | 
					
						
						|  | "submission_filename": filename, | 
					
						
						|  | "submission_time": timestamp, | 
					
						
						|  | "evaluated": False, | 
					
						
						|  | "user": user_state, | 
					
						
						|  | "model_name": model_name, | 
					
						
						|  | "model_description": model_description, | 
					
						
						|  | "csv_content": file_content, | 
					
						
						|  | "dataset": submission_type, | 
					
						
						|  | "anonymous": anonymous, | 
					
						
						|  | } | 
					
						
						|  | with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp: | 
					
						
						|  |  | 
					
						
						|  | json.dump(record, tmp) | 
					
						
						|  | tmp.flush() | 
					
						
						|  | tmp_name = tmp.name | 
					
						
						|  |  | 
					
						
						|  | API.upload_file( | 
					
						
						|  | path_or_fileobj=tmp_name, | 
					
						
						|  | path_in_repo=filename, | 
					
						
						|  | repo_id=SUBMISSIONS_REPO, | 
					
						
						|  | repo_type="dataset", | 
					
						
						|  | commit_message=f"Add submission for {user_state} at {timestamp}", | 
					
						
						|  | ) | 
					
						
						|  | Path(tmp_name).unlink() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def validate_file_requirements(file: BinaryIO, file_type: str) -> Path: | 
					
						
						|  | """Validate basic file requirements and return Path object""" | 
					
						
						|  | file_path = file.name | 
					
						
						|  | if not file_path: | 
					
						
						|  | raise gr.Error( | 
					
						
						|  | f"Uploaded {file_type} file object does not have a valid file path." | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | path_obj = Path(file_path) | 
					
						
						|  | if path_obj.suffix.lower() != ".csv": | 
					
						
						|  | raise gr.Error( | 
					
						
						|  | f"{file_type} file must be a CSV file. Please upload a .csv file." | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | return path_obj | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def make_submission( | 
					
						
						|  | cv_file: BinaryIO, | 
					
						
						|  | test_file: BinaryIO, | 
					
						
						|  | user_state, | 
					
						
						|  | model_name: str = "", | 
					
						
						|  | model_description: str = "", | 
					
						
						|  | anonymous: bool = False, | 
					
						
						|  | registration_code: str = "", | 
					
						
						|  |  | 
					
						
						|  | ): | 
					
						
						|  | """ | 
					
						
						|  | Make submissions for both GDPa1 cross-validation and private test set files. | 
					
						
						|  | Both files are required. Validates both files before making any submissions. | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | validate_username(user_state) | 
					
						
						|  |  | 
					
						
						|  | model_name = model_name.strip() | 
					
						
						|  | model_description = model_description.strip() | 
					
						
						|  |  | 
					
						
						|  | if not model_name: | 
					
						
						|  | raise gr.Error("Please provide a model name.") | 
					
						
						|  | if not model_description: | 
					
						
						|  | model_description = "" | 
					
						
						|  | if str(registration_code).strip().upper() != REGISTRATION_CODE: | 
					
						
						|  | raise gr.Error( | 
					
						
						|  | "Invalid registration code. Please register on the <a href='https://datapoints.ginkgo.bio/ai-competitions/2025-abdev-competition'>Competition Registration page</a> or email <a href='mailto:antibodycompetition@ginkgobioworks.com'>antibodycompetition@ginkgobioworks.com</a>." | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | if cv_file is None: | 
					
						
						|  | raise gr.Error( | 
					
						
						|  | "Please upload the GDPa1 Cross-Validation CSV file before submitting." | 
					
						
						|  | ) | 
					
						
						|  | if test_file is None: | 
					
						
						|  | raise gr.Error("Please upload the Private Test Set CSV file before submitting.") | 
					
						
						|  |  | 
					
						
						|  | files = {} | 
					
						
						|  |  | 
					
						
						|  | cv_path = validate_file_requirements(cv_file, "GDPa1 Cross-Validation") | 
					
						
						|  | with cv_path.open("rb") as f: | 
					
						
						|  | cv_content = f.read().decode("utf-8") | 
					
						
						|  | validate_csv_file(cv_content, "GDPa1_cross_validation") | 
					
						
						|  | files["cv"] = cv_content | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | test_path = validate_file_requirements(test_file, "Private Test Set") | 
					
						
						|  | with test_path.open("rb") as f: | 
					
						
						|  | test_content = f.read().decode("utf-8") | 
					
						
						|  | validate_csv_file(test_content, "Heldout Test Set") | 
					
						
						|  | files["test"] = test_content | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | messages = [] | 
					
						
						|  | for file_type, file_content in files.items(): | 
					
						
						|  | if file_type == "cv": | 
					
						
						|  | submission_type = "GDPa1_cross_validation" | 
					
						
						|  | display_name = "Cross-Validation" | 
					
						
						|  | else: | 
					
						
						|  | submission_type = "Heldout Test Set" | 
					
						
						|  | display_name = "Test Set" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | upload_submission( | 
					
						
						|  | file_content=file_content, | 
					
						
						|  | user_state=user_state, | 
					
						
						|  | submission_type=submission_type, | 
					
						
						|  | model_name=model_name, | 
					
						
						|  | model_description=model_description, | 
					
						
						|  | anonymous=anonymous, | 
					
						
						|  | ) | 
					
						
						|  | messages.append( | 
					
						
						|  | f"✅ {display_name}: Your submission has been received! Your results should appear on the leaderboard within a minute." | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | return "\n\n".join(messages) | 
					
						
						|  |  |