""" SupraLabs Open Source Model Cup — Registration Portal. This Space is NOT the tournament itself. It is the official entry portal: it explains the rules, lets organizations register a model, validates submissions locally, and shows the real list of registered models — persisted to a Hugging Face Dataset repo, not mocked. The tournament (matches, standings, brackets) is a separate Space. REQUIRED SETUP (see the SERVICES section below for full detail): 1. Create a Dataset repo on huggingface.co (it MUST be private: it stores contact emails and, optionally, endpoint API keys), e.g. SupraLabs/model-cup-registrations. 2. Add a Space secret named HF_TOKEN with an HF access token that has WRITE permission on that dataset. Settings -> Variables and secrets -> New secret. Never hardcode a token in this file. 3. If your dataset repo id differs from the default below, set a second Space secret/variable named HF_DATASET_REPO. Without that setup (e.g. running this file locally), registrations still work end-to-end against a local ./data/registrations.jsonl fallback file, so development doesn't require any HF credentials. Everything lives in this single file for simple deployment, but is organized into clearly separated sections mirroring a normal package layout, so it can be split back into files later with minimal changes: 1. DATA -> static rules/FAQ/roadmap content 2. VALIDATORS -> pure functions, no side effects, fully testable 3. SERVICES -> HF Dataset read/write layer (real persistence) 4. MODELS -> typed registration record shape 5. COMPONENTS -> reusable HTML fragment builders 6. FORMS -> registration form submit handler 7. PAGES -> one render function per screen 8. APP -> Gradio wiring / navigation """ from __future__ import annotations import json import logging import os import random import re import threading import time from dataclasses import asdict, dataclass from html import escape from pathlib import Path import gradio as gr logger = logging.getLogger("model_cup") # ZeroGPU (the free Gradio hardware tier on HF Spaces) refuses to start # a Space unless at least one function is decorated with @spaces.GPU. # This app does no local GPU work (no model inference happens here — # it's a registration form), so this is a harmless no-op: the # decorator is only ever declared, never actually invoked. # # The import is wrapped in a broad except (not just ImportError): the # `spaces` package talks to HF's GPU scheduling infrastructure at # import time, and if that infra hiccups, an unhandled exception here # would take down the whole app before Gradio even starts — for a form # that never touches a GPU. Falling back to the dummy decorator keeps # the registration portal itself resilient to that failure mode, # independent of whatever HF's container/GPU allocation is doing. try: import spaces except Exception: class spaces: # type: ignore @staticmethod def GPU(func=None, **kwargs): def wrapper(f): return f return wrapper(func) if func else wrapper @spaces.GPU def _zerogpu_startup_probe(): """Unused placeholder so HF Spaces' ZeroGPU startup check passes. This app never runs model inference and never calls this function.""" pass # ===================================================================== # 1. DATA — static content (rules/FAQ/roadmap) + local fallback dir. # Registered models are NOT mocked here — they live in a real HF # Dataset repo (see SERVICES section below). This directory is only # used as an offline fallback when no HF token is configured. # ===================================================================== DATA_DIR = Path(__file__).resolve().parent / "data" DATA_DIR.mkdir(exist_ok=True) # Weight classes, boxing-style. Each entry is (name, min_params # inclusive, max_params exclusive or None for "and above"). Order # matters — categorize_by_parameters (below) walks this top-to-bottom, # and every page that lists categories (Rules, Tournament Structure, # FAQ) is generated from this single table so they can't drift apart. # # The last class's max is also the hard size limit for the whole # tournament (see MAX_PARAMETERS): a model above it has no class to # play in, so registration rejects it. Set it to None for an open top. WEIGHT_CLASSES = [ ("Atomweight", 100_000, 10_000_000), ("Featherweight", 10_000_000, 1_000_000_000), ("Lightweight", 1_000_000_000, 7_000_000_000), ] WEIGHT_CLASS_NAMES = [name for name, *_ in WEIGHT_CLASSES] MAX_PARAMETERS: int | None = WEIGHT_CLASSES[-1][2] # inclusive ceiling, None = no limit _NUMBER_WORDS = {1: "one", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", 7: "seven"} def _join_names(names: list[str], conjunction: str = "or") -> str: """['A', 'B', 'C'] -> 'A, B, or C' (or 'A, B, and C' with conjunction='and').""" if len(names) < 3: return f" {conjunction} ".join(names) return f"{', '.join(names[:-1])}, {conjunction} {names[-1]}" def _format_param_count(n: int) -> str: """Human-friendly shorthand for a parameter count, e.g. 1_000_000_000 -> '1B'.""" if n >= 1_000_000_000: v = n / 1_000_000_000 return f"{v:.0f}B" if v == int(v) else f"{v:.1f}B" if n >= 1_000_000: v = n / 1_000_000 return f"{v:.0f}M" if v == int(v) else f"{v:.1f}M" if n >= 1_000: v = n / 1_000 return f"{v:.0f}K" if v == int(v) else f"{v:.1f}K" return str(n) def _weight_class_range_label(min_params: int, max_params: int | None) -> str: low = _format_param_count(min_params) if max_params is None: return f"{low}+ parameters" high = _format_param_count(max_params) return f"{low}\u2013{high} parameters" MAX_PARAMETERS_LABEL = _format_param_count(MAX_PARAMETERS) if MAX_PARAMETERS else None STATUS_COLORS = { "Verified": "#2f9e68", "Pending Review": "#b8860b", "Rejected": "#c8404c", "Offline": "#7a828c", } MODEL_TYPES = ["Base", "Instruct", "Reasoning", "Code", "Multimodal", "Other"] PROVIDERS = ["Hugging Face Inference", "OpenRouter", "Together", "Groq", "Fireworks", "Organization Endpoint", "Other"] YES_NO = ["Yes", "No"] ALL_STATUSES = ["Pending Review", "Verified", "Rejected", "Offline"] _SIZE_LIMIT_RULES = ( [f"Models with more than {MAX_PARAMETERS_LABEL} parameters are not eligible: " f"the heaviest weight class tops out at {MAX_PARAMETERS_LABEL}."] if MAX_PARAMETERS_LABEL else [] ) RULES_SECTIONS = [ ("General Rules", [ "Every model competing in the tournament must be registered through this portal before the registration deadline.", "One organization may register multiple models — each is evaluated and scored independently.", "Each Hugging Face repository can be registered once. A rejected registration may be resubmitted.", "By registering, an organization agrees to keep its inference endpoint available for the duration of the tournament.", "Every registration is reviewed by the organizers and stays Pending Review until it is approved.", ]), ("Eligibility", [ "The model must be open source, with weights or a clearly documented open license.", "The model must be publicly accessible — no invite-only or gated access without a public request path.", "The model must have a valid Hugging Face repository (or equivalent public model card).", "Closed-source, fully proprietary models are not eligible for this tournament (see FAQ).", *_SIZE_LIMIT_RULES, "The reported parameter count is checked by the organizers during review, and a model is only approved " "once it matches.", ]), ("Inference Requirements", [ "The model must provide a stable inference method that organizers can call automatically, without manual intervention.", "Accepted inference providers include: Hugging Face Inference, a Dedicated Endpoint, an Organization API, " "OpenRouter, Together AI, Groq, Fireworks, or any other stable provider with comparable uptime guarantees.", "If the endpoint requires authentication, provide a dedicated, revocable API key (ideally with a spending " "limit) in the registration form. It is stored privately and is never shown publicly.", "The organizers must be able to execute benchmark questions against the endpoint automatically and reliably.", "Organizations are responsible for keeping their endpoint working throughout the competition.", ]), ("Tournament Rules", [ f"Models are automatically assigned to a weight class ({_join_names(WEIGHT_CLASS_NAMES)}) " "based on parameter count.", "Every eligible model competes in both its own weight category and the Total World Cup.", "Match outcomes are decided by an automated judge scoring each model's answers to a shared question set.", "Group standings follow standard football rules: 3 points for a win, 1 for a draw, 0 for a loss.", ]), ("Disqualification", [ "If a model's inference becomes unavailable during the tournament, organizers may temporarily disable it " "until the issue is resolved, without eliminating it from the bracket.", "Organizers reserve the right to reject malicious, broken, or clearly non-functional submissions at any stage.", "Repeated, unresolved endpoint outages during scheduled matches may result in forfeiture of those matches.", ]), ("Categories", [ f"{name} — {_weight_class_range_label(min_p, max_p)}." for name, min_p, max_p in WEIGHT_CLASSES ] + [ "Total World Cup — every eligible model, from the lightest class to the heaviest, competing together.", ]), ("Sportsmanship", [ "Organizations should submit accurate model information — misrepresenting parameter count, license, or " "capabilities to gain a category advantage is grounds for disqualification.", "Attempts to game or exploit the automated judge are treated as a rules violation.", ]), ("Updates", [ "Rules may be updated between seasons. Registered organizations will be notified of any material changes " "before the next tournament begins.", ]), ] FAQ_ITEMS = [ ("Can I register multiple models?", "Yes. One organization can register as many models as it wants — each is registered, validated, and scored " "independently. The same Hugging Face repository can only be registered once."), ("How are categories determined?", f"Automatically, based on parameter count, using {_NUMBER_WORDS[len(WEIGHT_CLASSES)]} boxing-style " f"weight classes: {_join_names(WEIGHT_CLASS_NAMES, 'and')} (see Tournament Structure for the exact " "ranges). Every model also competes in the Total World Cup."), ("What is the maximum model size?", (f"{MAX_PARAMETERS_LABEL} parameters. Larger models have no weight class to compete in, so they can't be registered." if MAX_PARAMETERS_LABEL else "There is no size limit.")), ("Do you verify the parameter count?", "Yes. The organizers review every registration, including the reported parameter count, before approving it."), ("My endpoint needs an API key. What should I do?", "Add it in the optional API key field of the registration form. Use a dedicated key that you can revoke, " "ideally with a spending limit. It is stored privately and never shown on the public list."), ("Can I update my endpoint?", "Yes — contact the organizers with your registration details and the updated endpoint. Self-service editing isn't available in this PoC yet (see Roadmap)."), ("Can closed-source models participate?", "No. This tournament is specifically for open-source models with publicly accessible weights or documentation."), ("What happens if my endpoint goes offline?", "Organizers may temporarily disable your model until the issue is resolved. Repeated unresolved outages during scheduled matches may result in forfeiture of those matches."), ("How are winners determined?", "Each match is a set of benchmark questions answered by both competing models, scored by an automated judge. Group stage follows standard football points (3/1/0); the knockout stage decides the champion of each cup."), ] ROADMAP_ITEMS = [ ("Season Rankings", "Persistent rankings carried across multiple tournament seasons."), ("Live Matches", "Real-time match progress as benchmark questions are answered."), ("Automatic Registration Verification", "Automated checks that ping the submitted endpoint and validate the HF repo before marking a model Verified."), ("Leaderboard", "A global leaderboard across all categories and seasons."), ("Organization Profiles", "A public profile page per organization with all their registered models."), ("Historical Seasons", "An archive of past tournaments, brackets, and results."), ("Model Statistics", "Deeper per-model analytics: response time, category-specific performance, judge score breakdowns."), ("Tournament API", "A public read API so third parties can build their own dashboards on top of tournament data."), ] TOURNAMENT_STRUCTURE = [ (f"{name} Cup", f"For models with {_weight_class_range_label(min_p, max_p)}.") for name, min_p, max_p in WEIGHT_CLASSES ] + [ ("Total World Cup", "Every eligible model, across all weight classes, competing together for one trophy."), ] # ===================================================================== # 2. VALIDATORS — pure functions, no side effects # ===================================================================== FIELD_LABELS = { "organization_name": "Organization Name", "organization_website": "Organization Website", "organization_email": "Organization Email", "contact_person": "Contact Person", "model_name": "Model Name", "model_hf_url": "Model Hugging Face URL", "license": "License", "parameters_raw": "Number of Parameters", "inference_endpoint": "Inference Endpoint", "api_docs_url": "API Documentation URL", "endpoint_api_key": "Endpoint API Key", "languages": "Languages", "tags": "Tags", "short_description": "Short Description", "long_description": "Long Description", } REQUIRED_FIELDS = ( "organization_name", "organization_website", "organization_email", "contact_person", "model_name", "model_hf_url", "license", "parameters_raw", "inference_endpoint", "short_description", ) # Basic anti-abuse limits: the registration list is public, so unbounded # text would let anyone flood the page. MAX_SHORT_TEXT = 300 MAX_LONG_TEXT = 3000 def validate_required(value: str) -> bool: return bool(value and value.strip()) def validate_email(email: str) -> bool: return bool(re.match(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", (email or "").strip())) def validate_url(url: str) -> bool: return bool(re.match(r"^https?://[^\s]+\.[^\s]+", (url or "").strip())) def validate_hf_repo_url(url: str) -> bool: """Accepts a full HF URL (https://huggingface.co/org/model) or an org/model shorthand.""" url = (url or "").strip() if re.match(r"^https?://huggingface\.co/[\w\-.]+/[\w\-.]+/?$", url): return True if re.match(r"^[\w\-.]+/[\w\-.]+$", url): return True return False def normalize_hf_repo(url: str) -> str: """Turns a full HF URL into the org/model shorthand used as hf_repo.""" url = (url or "").strip().rstrip("/") match = re.match(r"^https?://huggingface\.co/([\w\-.]+/[\w\-.]+)$", url) return match.group(1) if match else url def parse_parameters(raw: str) -> int | None: """Accepts plain integers (67000000) or shorthand with K/M/B suffix (67M, 1.3B).""" if raw is None: return None s = str(raw).strip().replace(",", "").replace(" ", "") if not s: return None m = re.match(r"^(\d+(\.\d+)?)([kKmMbB]?)$", s) if not m: return None number = float(m.group(1)) suffix = m.group(3).upper() multiplier = {"K": 1_000, "M": 1_000_000, "B": 1_000_000_000, "": 1}[suffix] value = int(number * multiplier) return value if value > 0 else None def categorize_by_parameters(parameters: int) -> str: """Maps a parameter count to a weight class name using WEIGHT_CLASSES. Walks the classes lightest-first and returns the first one whose max isn't exceeded, so anything below the first class's floor lands in the lightest class, and anything at/above the last class's max lands in the heaviest (registration rejects those above MAX_PARAMETERS before this is ever called).""" for name, _min_params, max_params in WEIGHT_CLASSES: if max_params is None or parameters < max_params: return name return WEIGHT_CLASSES[-1][0] def validate_registration(fields: dict) -> list[str]: """ Validates the full registration form. Returns a list of human-readable error messages (empty list = valid). """ errors = [] for key in REQUIRED_FIELDS: if not validate_required(fields.get(key, "")): errors.append(f"'{FIELD_LABELS[key]}' is required.") for key, label in FIELD_LABELS.items(): limit = MAX_LONG_TEXT if key == "long_description" else MAX_SHORT_TEXT if len(str(fields.get(key) or "").strip()) > limit: errors.append(f"'{label}' is too long (max {limit} characters).") # Only run format checks on fields that were actually filled in, # to avoid duplicate noise on top of the required-field errors. if validate_required(fields.get("organization_website", "")) and not validate_url(fields["organization_website"]): errors.append("'Organization Website' must be a valid URL (starting with http:// or https://).") if validate_required(fields.get("organization_email", "")) and not validate_email(fields["organization_email"]): errors.append("'Organization Email' must be a valid email address.") if validate_required(fields.get("model_hf_url", "")) and not validate_hf_repo_url(fields["model_hf_url"]): errors.append("'Model Hugging Face URL' must be a valid Hugging Face repository (e.g. https://huggingface.co/org/model).") if validate_required(fields.get("inference_endpoint", "")) and not validate_url(fields["inference_endpoint"]): errors.append("'Inference Endpoint' must be a valid URL.") api_docs = fields.get("api_docs_url", "") if validate_required(api_docs) and not validate_url(api_docs): errors.append("'API Documentation URL' must be a valid URL if provided.") if validate_required(fields.get("parameters_raw", "")): parameters = parse_parameters(fields["parameters_raw"]) if parameters is None: errors.append("'Number of Parameters' must be a positive number (e.g. 67000000, 67M, 1.3B).") elif MAX_PARAMETERS is not None and parameters > MAX_PARAMETERS: errors.append( f"'Number of Parameters' is above the {MAX_PARAMETERS_LABEL} limit — " f"the heaviest weight class tops out at {MAX_PARAMETERS_LABEL}." ) if not fields.get("model_type"): errors.append("'Model Type' is required.") if not fields.get("inference_provider"): errors.append("'Inference Provider' is required.") return errors # ===================================================================== # 3. SERVICES — registered models are stored as JSONL in a real HF # Dataset repo (not a mock). This is the swap-in point if you ever # move to a real database later — everything else in the app only # calls get_registered_models() / add_registered_model() below. # # Setup required on the Space (see the docstring at the top of this # file for the full explanation): # 1. Create a PRIVATE Dataset repo on huggingface.co, e.g. # SupraLabs/model-cup-registrations. # 2. Add an HF token with WRITE access as a Space *secret* named # HF_TOKEN (Settings -> Variables and secrets -> New secret). # Never hardcode a token in this file. # 3. Set HF_DATASET_REPO below (or via the HF_DATASET_REPO env # var) to that repo's id. # # If no token/repo is configured (e.g. running this file locally), # everything falls back to a local JSONL file under ./data so the # app still works end-to-end for development. # # Concurrency: adding a registration is a read-modify-write of one # file, so it is protected twice. A process-wide lock serializes # writers inside this Space, and every remote commit is pinned to # the revision it was computed from (parent_commit). If anything # else changed the dataset in the meantime — e.g. you editing a # status on the Hub while someone registers — the Hub rejects the # commit (HTTP 412) and the write is redone against the new state, # so no registration or manual edit is silently overwritten. # ===================================================================== HF_DATASET_REPO = os.environ.get("HF_DATASET_REPO", "SupraLabs/model-cup-registrations") HF_TOKEN = os.environ.get("HF_TOKEN") # set as a Space secret, never hardcoded REGISTRATIONS_FILENAME = "registrations.jsonl" MAX_WRITE_ATTEMPTS = 6 _LOCAL_FALLBACK_PATH = DATA_DIR / REGISTRATIONS_FILENAME _WRITE_LOCK = threading.Lock() try: from huggingface_hub import HfApi, hf_hub_download try: from huggingface_hub.errors import EntryNotFoundError, HfHubHTTPError, RepositoryNotFoundError except ImportError: # older huggingface_hub releases from huggingface_hub.utils import EntryNotFoundError, HfHubHTTPError, RepositoryNotFoundError _HUB_AVAILABLE = True except ImportError: _HUB_AVAILABLE = False _hub_api = HfApi(token=HF_TOKEN) if (_HUB_AVAILABLE and HF_TOKEN) else None class DuplicateRegistrationError(Exception): """The same Hugging Face repository is already registered.""" def __init__(self, existing: dict): super().__init__(existing.get("hf_repo", "")) self.existing = existing class RegistrationStorageError(Exception): """The registrations file couldn't be read or written safely.""" def _using_remote_dataset() -> bool: """True when this Space is properly configured to persist to a real HF Dataset. False means we're running against the local JSONL fallback (e.g. local dev, or misconfigured secrets).""" return _hub_api is not None def _parse_jsonl(text: str, strict: bool) -> list[dict]: """Parses a JSONL string. For display (strict=False) a malformed line — say, from a manual edit — is skipped so the page keeps working. For writes (strict=True) it aborts instead, because rewriting the file without that line would silently delete it.""" records = [] for number, line in enumerate(text.splitlines(), start=1): if not line.strip(): continue try: records.append(json.loads(line)) except json.JSONDecodeError: if strict: raise RegistrationStorageError( f"Line {number} of {REGISTRATIONS_FILENAME} is not valid JSON; refusing to overwrite the file." ) from None logger.warning("Skipping malformed line %d of %s", number, REGISTRATIONS_FILENAME) return records def _serialize_jsonl(records: list[dict]) -> str: return "".join(json.dumps(r, ensure_ascii=False) + "\n" for r in records) def _find_duplicate(records: list[dict], hf_repo: str) -> dict | None: """Same repo (case-insensitive) already registered. A Rejected entry doesn't block a new attempt.""" key = hf_repo.strip().lower() for r in records: if str(r.get("hf_repo", "")).strip().lower() == key and r.get("status") != "Rejected": return r return None def _download_remote_records(revision: str | None = None, strict: bool = False) -> list[dict]: """Reads the registrations file from the Dataset repo at a given revision (None = latest). A missing file just means no registrations yet.""" try: local_copy = hf_hub_download( repo_id=HF_DATASET_REPO, filename=REGISTRATIONS_FILENAME, repo_type="dataset", token=HF_TOKEN, revision=revision, ) except EntryNotFoundError: return [] return _parse_jsonl(Path(local_copy).read_text(encoding="utf-8"), strict=strict) def _read_registrations_jsonl() -> list[dict]: """Reads every registration record for display, remote Dataset if configured, otherwise the local fallback file.""" if _using_remote_dataset(): try: return _download_remote_records() except RepositoryNotFoundError: logger.warning("Dataset repo %s not found; showing no registrations.", HF_DATASET_REPO) return [] if not _LOCAL_FALLBACK_PATH.exists(): return [] return _parse_jsonl(_LOCAL_FALLBACK_PATH.read_text(encoding="utf-8"), strict=False) def _is_commit_conflict(exc: Exception) -> bool: """The Hub answers 412 (Precondition Failed) when parent_commit is no longer the head of the repo; 409 is treated the same way.""" status = getattr(getattr(exc, "response", None), "status_code", None) return status in (409, 412) def _append_remote(record: dict) -> None: last_error: Exception | None = None for attempt in range(MAX_WRITE_ATTEMPTS): # Pin everything to one revision: read it, change it, commit on top of it. head = _hub_api.repo_info(repo_id=HF_DATASET_REPO, repo_type="dataset").sha records = _download_remote_records(revision=head, strict=True) existing = _find_duplicate(records, record["hf_repo"]) if existing: raise DuplicateRegistrationError(existing) records.append(record) try: _hub_api.upload_file( path_or_fileobj=_serialize_jsonl(records).encode("utf-8"), path_in_repo=REGISTRATIONS_FILENAME, repo_id=HF_DATASET_REPO, repo_type="dataset", token=HF_TOKEN, commit_message=f"Register {record['hf_repo']}", parent_commit=head, ) return except HfHubHTTPError as exc: if not _is_commit_conflict(exc): raise last_error = exc logger.info("Registrations changed during write (attempt %d); retrying.", attempt + 1) time.sleep(random.uniform(0.2, 0.6) * (attempt + 1)) raise RegistrationStorageError("Too many concurrent changes to the registrations file.") from last_error def _append_local(record: dict) -> None: text = _LOCAL_FALLBACK_PATH.read_text(encoding="utf-8") if _LOCAL_FALLBACK_PATH.exists() else "" records = _parse_jsonl(text, strict=True) existing = _find_duplicate(records, record["hf_repo"]) if existing: raise DuplicateRegistrationError(existing) records.append(record) _LOCAL_FALLBACK_PATH.write_text(_serialize_jsonl(records), encoding="utf-8") def get_registered_models() -> list[dict]: """Returns every registered model. Always reads fresh from the Dataset (no in-memory cache) so multiple Space users/sessions see each other's registrations.""" return _read_registrations_jsonl() def add_registered_model(record: dict) -> None: """Atomically appends one new registration. Raises DuplicateRegistrationError if the repo is already registered, or RegistrationStorageError if it couldn't be saved safely.""" with _WRITE_LOCK: if _using_remote_dataset(): _append_remote(record) else: _append_local(record) # ===================================================================== # 4. MODELS — typed registration record shape # ===================================================================== @dataclass class RegistrationRecord: organization: str organization_website: str organization_email: str contact_person: str model: str hf_repo: str license: str parameters: int category: str model_type: str provider: str endpoint: str api_docs_url: str supports_chat: bool supports_streaming: bool languages: str tags: str short_description: str long_description: str endpoint_api_key: str = "" # private: never rendered publicly, masked in to_public_dict() status: str = "Pending Review" def to_dict(self) -> dict: return asdict(self) def to_public_dict(self) -> dict: """Same as to_dict() but with the API key masked — used for anything shown back to the submitter.""" data = self.to_dict() if data["endpoint_api_key"]: data["endpoint_api_key"] = "***" return data # ===================================================================== # 5. COMPONENTS — reusable HTML fragment builders # Everything that comes from a registration is user-supplied and the # list page is public, so it is always passed through escape() — # otherwise a model named "