Download app.py from FINAL-Bench/open-materials-challenge: direct link, hf CLI and curl.
- Browser
- Download file 19.1 kB
-
https://huggingface.co/spaces/FINAL-Bench/open-materials-challenge/resolve/main/app.py
- Command line
-
hf download hf://spaces/FINAL-Bench/open-materials-challenge/app.py
-
curl -L -o app.py https://huggingface.co/spaces/FINAL-Bench/open-materials-challenge/resolve/main/app.py
19.1 kB
| # -*- coding: utf-8 -*- | |
| """Open Materials Challenge โ ์ ์์ ์์ํ. | |
| **์ด ์๋น์ค๊ฐ ํ์ง ์๋ ์ผ**: ์ฑ์ . ์คํ์ด์ค์๋ Materials Project ์๋ฃ๋ GPU ๋ ์๋ค. | |
| ์ ์ถ์ ๋ฐ์ ์์ฅ(๋น๊ณต๊ฐ ๋ฐ์ดํฐ์ )์ ์ ๊ณ , ์์ปค๊ฐ ์ฑ์ ํด ๋ฃ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์ฌ์ค ๋ฟ์ด๋ค. | |
| ์ฌ๊ธฐ์ ๋ฌผ์ฑ์ ํ์ ํ๋ ์ฒํ๋ฉด ์ฐธ๊ฐ์๋ ๊ทผ๊ฑฐ ์๋ ๋ฐ๋ ค๋ฅผ ๋ฐ๊ฒ ๋๋ค. | |
| ์์ํ๋ TTL ๋น ํ ๋ฒ๋ง ๋ง๋ค์ด ๋ฏธ๋ฆฌ ์์ถํด ๋๊ณ , ์บ์๋ ๋จ์ผ ๊ฐฑ์ ์๋ก ๋๋ฆฐ๋ค. | |
| ์ฒซ ํ์ด์ง๋ async ๋ก ์๋นํ๊ณ , ํ๋ 500 ํ์ฉ ๋๋ ๊ทธ๋ฆฐ๋ค. | |
| """ | |
| import base64 | |
| import gzip | |
| import hashlib | |
| import hmac | |
| import json | |
| import os | |
| import re | |
| import secrets | |
| import time | |
| import urllib.parse | |
| import urllib.request | |
| import uuid | |
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.middleware.gzip import GZipMiddleware | |
| from fastapi.responses import (FileResponse, JSONResponse, RedirectResponse, | |
| Response) | |
| from pydantic import BaseModel | |
| import gates | |
| import seasons | |
| import store | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| DATA = os.path.join(HERE, "data") | |
| # ํ์ด์ง ํด๋ง ์ฃผ๊ธฐ(20์ด)๋ณด๋ค ํฌ๊ฒ ์ก๋๋ค. | |
| CACHE = store.Cached(ttl=int(os.environ.get("OMC_CACHE_TTL", "60"))) | |
| SALT = os.environ.get("OMC_SALT", "omc-season1") | |
| OAUTH_ID = os.environ.get("OAUTH_CLIENT_ID", "") | |
| OAUTH_SECRET = os.environ.get("OAUTH_CLIENT_SECRET", "") | |
| OAUTH_ISS = os.environ.get("OPENID_PROVIDER_URL", "https://huggingface.co") | |
| SPACE_HOST = os.environ.get("SPACE_HOST", "") | |
| COOKIE = "omc_session" | |
| IN_FRAME = bool(SPACE_HOST) | |
| COOKIE_KW = ({"samesite": "none", "secure": True} if IN_FRAME | |
| else {"samesite": "lax", "secure": False}) | |
| SESSION_KEY = os.environ.get("OMC_SESSION_KEY") or secrets.token_hex(16) | |
| DAILY_CAP = int(os.environ.get("OMC_DAILY_CAP", "30")) | |
| CAP_TZ_OFFSET = 9 * 3600 # ์ฐธ๊ฐ์๊ฐ ๊ฐ์ ํ ํ๋ฃจ ๊ฒฝ๊ณ (KST) | |
| app = FastAPI(title="Open Materials Challenge") | |
| app.add_middleware(GZipMiddleware, minimum_size=1024) | |
| class Submission(BaseModel): | |
| formula: str # ์กฐ์ฑ. ์: Li3YCl6, LiZr2(PO4)3 | |
| display_name: str # ์์ํ์ ํ์๋ ์ด๋ฆ | |
| model_name: str = "" # ์ด๋ค ๋ชจ๋ธ์ด ์ ์ํ๋์ง. ์์ ๊ธฐ์ฌ | |
| rationale: str = "" # ์ค๊ณ ๊ทผ๊ฑฐ (์ ํ) | |
| structure: str = "" # CIF (์ ํ) - ๋ด๋ฉด ์ฑ์ ์ด ๋น ๋ฅด๊ณ ์ ํํด์ง๋ค | |
| season: int = 0 | |
| # ์กฐ์ฑ์ ๊ณต๊ฐํ ์ง๋ ์ ์ถ์๊ฐ ์ ํ๋ค. ๋๋๋ฆด ์ ์๋ ์ชฝ์ด ๊ธฐ๋ณธ๊ฐ์ด์ด์ผ ํ๋ค - | |
| # ํ ๋ฒ ๊ณต๊ฐ๋ ์กฐ์ฑ์ ๋๊ฐ์ ์ ์๊ณ , ๊ณต๊ฐ๋ ํนํ์ฑ์ ๊น๋๋ค. | |
| visibility: str = "private" | |
| # ---------------------------------------------------------------- ์์ฅ ์ ๊ทผ | |
| def _submissions(s): | |
| return CACHE.get("ids:%d" % s["number"], | |
| lambda: store.listdir(seasons.path(s, "submissions"))) | |
| def _board(s): | |
| return CACHE.get("board:%d" % s["number"], | |
| lambda: store.read(seasons.path(s, "leaderboard.json"), | |
| default={}) or {}) | |
| def public_id(key): | |
| return "OMC-" + hashlib.sha256((SALT + key).encode()).hexdigest()[:10].upper() | |
| def mask(rec): | |
| """๋น๊ณต๊ฐ ์ ์ถ์์ ์กฐ์ฑ์ ๊ฐ๋ฆฐ๋ค. ์์ ์ข ๋ฅ๊น์ง๋ ๋ณด์ฌ์ค๋ค - ๊ทธ๊ฒ๋ง์ผ๋ก๋ | |
| ์กฐ์ฑ์ ๋๋๋ฆด ์ ์๊ณ , ์ด๋ค ํํ๊ณ๊ฐ ์์์ธ์ง๋ ๋ํ์ ๊ณต๊ฐ ์ง์์ด์ด์ผ ํ๋ค.""" | |
| out = dict(rec) | |
| if rec.get("visibility") != "public": | |
| comp = rec.get("composition") or {} | |
| out["formula"] = "ยท".join(sorted(comp)) if comp else "๋น๊ณต๊ฐ" | |
| out["masked"] = True | |
| out.pop("structure", None) | |
| # ๐ด formula ๋ง ๊ฐ๋ฆฌ๋ฉด ์ ๋๋ค. composition ์ ๊ธฐ๊ณ๊ฐ ์ฝ๋ ๊ฐ์ ์ ๋ณด์ด๊ณ , | |
| # ์ด๊ฒ์ ๋จ๊ฒจ๋๋ฉด ๊ฐ๋ฆผ์ด ํ๋ฉด์์๋ง ๋๊ณ API ๋ก๋ ์ ๋ถ ์์ด๋๊ฐ๋ค. | |
| # (2026-09-07 ์ฐธ๊ฐ์ ์ ๋ณด๋ก ๋ฐ๊ฒฌ - ๋น๊ณต๊ฐ 521๊ฑด์ ์กฐ์ฑ์ด ๋ฌด์ธ์ฆ์ผ๋ก ๋ ธ์ถ) | |
| out.pop("composition", None) | |
| return out | |
| # ---------------------------------------------------------------- ์ธ์ | |
| def _sign(payload): | |
| raw = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=") | |
| sig = hmac.new(SESSION_KEY.encode(), raw.encode(), hashlib.sha256).hexdigest()[:32] | |
| return raw + "." + sig | |
| def _unsign(token): | |
| try: | |
| raw, sig = (token or "").rsplit(".", 1) | |
| except ValueError: | |
| return None | |
| good = hmac.new(SESSION_KEY.encode(), raw.encode(), hashlib.sha256).hexdigest()[:32] | |
| if not hmac.compare_digest(sig, good): | |
| return None | |
| pad = "=" * (-len(raw) % 4) | |
| try: | |
| return json.loads(base64.urlsafe_b64decode(raw + pad)) | |
| except Exception: | |
| return None | |
| def current_user(request: Request): | |
| return _unsign(request.cookies.get(COOKIE)) | |
| def _redirect_uri(request: Request): | |
| if SPACE_HOST: | |
| return "https://%s/auth/callback" % SPACE_HOST | |
| return str(request.base_url).rstrip("/") + "/auth/callback" | |
| # ---------------------------------------------------------------- ํ๋ฉด | |
| async def index(): | |
| # ์ ์ ํ์ด์ง๋ ์์ฒญ ์ค๋ ๋ํ์ ํ์ง ์๊ฒ async ๋ก ๋๋ค. | |
| return FileResponse(os.path.join(HERE, "index.html")) | |
| def me(request: Request): | |
| u = current_user(request) | |
| return {"signed_in": bool(u), "user": u, | |
| "oauth_configured": bool(OAUTH_ID and OAUTH_SECRET)} | |
| def login(request: Request): | |
| if not (OAUTH_ID and OAUTH_SECRET): | |
| raise HTTPException(503, "OAuth ๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.") | |
| state = secrets.token_urlsafe(16) | |
| q = urllib.parse.urlencode({ | |
| "client_id": OAUTH_ID, "redirect_uri": _redirect_uri(request), | |
| "response_type": "code", "scope": "openid profile", "state": state}) | |
| r = RedirectResponse("%s/oauth/authorize?%s" % (OAUTH_ISS, q)) | |
| r.set_cookie("omc_state", state, max_age=600, httponly=True, **COOKIE_KW) | |
| return r | |
| def callback(request: Request, code: str = "", state: str = ""): | |
| if not code or state != request.cookies.get("omc_state"): | |
| return RedirectResponse("/?auth=failed") | |
| body = urllib.parse.urlencode({ | |
| "client_id": OAUTH_ID, "client_secret": OAUTH_SECRET, | |
| "grant_type": "authorization_code", "code": code, | |
| "redirect_uri": _redirect_uri(request)}).encode() | |
| try: | |
| req = urllib.request.Request("%s/oauth/token" % OAUTH_ISS, data=body) | |
| with urllib.request.urlopen(req, timeout=30) as x: | |
| tok = json.loads(x.read()) | |
| req = urllib.request.Request( | |
| "%s/oauth/userinfo" % OAUTH_ISS, | |
| headers={"Authorization": "Bearer " + tok["access_token"]}) | |
| with urllib.request.urlopen(req, timeout=30) as x: | |
| info = json.loads(x.read()) | |
| except Exception: | |
| return RedirectResponse("/?auth=failed") | |
| u = {"sub": info.get("sub"), "name": info.get("preferred_username") or info.get("name"), | |
| "picture": info.get("picture")} | |
| r = RedirectResponse("/?auth=ok") | |
| r.set_cookie(COOKIE, _sign(u), max_age=30 * 86400, httponly=True, **COOKIE_KW) | |
| r.delete_cookie("omc_state") | |
| return r | |
| def logout(): | |
| r = RedirectResponse("/") | |
| r.delete_cookie(COOKIE) | |
| return r | |
| def season_list(): | |
| return {"seasons": seasons.all_public()} | |
| def season(request: Request): | |
| return seasons.public(seasons.get(request.query_params.get("season"))) | |
| # ---------------------------------------------------------------- ์ ์ถ | |
| def submit(s: Submission, request: Request): | |
| season = seasons.get(s.season or None) | |
| if not season.get("open"): | |
| raise HTTPException(400, "์ด ์์ฆ์ ์ ์ถ์ ๋ฐ์ง ์์ต๋๋ค.") | |
| user = current_user(request) | |
| if (OAUTH_ID and OAUTH_SECRET) and not user: | |
| raise HTTPException(401, "์ ์ถํ๋ ค๋ฉด Hugging Face ๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.") | |
| g = seasons.gate(season) | |
| ok, why, info = gates.check(s.formula, cif=s.structure or None, | |
| require_elements=g.get("require_elements", ("Li",)), | |
| max_atoms=g.get("max_atoms", 60)) | |
| if not ok: | |
| raise HTTPException(400, why) | |
| comp = info["composition"] | |
| # ์ด์ ๋ **๊ธฐ์ฝ ์กฐ์ฑ**์ผ๋ก ๋ง๋ ๋ค. Li3YCl6 ์ Li9Y3Cl18 ์ ๊ฐ์ ๋ฌผ์ง์ด๋ค. | |
| # ์๋ณธ ์กฐ์ฑ์ผ๋ก ๋ง๋ค๋ฉด ๋ฐฐ์๋ง ๋ฐ๊ฟ ๊ฐ์ ๋ฌผ์ง์ ๋ช ๋ฒ์ด๊ณ ์ฌ๋ฆด ์ ์๋ค. | |
| red = info.get("reduced") or comp | |
| key = "-".join("%s%g" % (k, red[k]) for k in sorted(red)) | |
| cid = public_id(key) | |
| ids = _submissions(season) | |
| if cid in ids: | |
| raise HTTPException(409, "์ด๋ฏธ ์ ์ถ๋ ์กฐ์ฑ์ ๋๋ค (%s)." % cid) | |
| if user: | |
| today = int((time.time() + CAP_TZ_OFFSET) // 86400) | |
| mine = CACHE.get("cap:%s:%d:%d" % (user.get("sub"), season["number"], today), | |
| lambda: 0) | |
| if mine >= DAILY_CAP: | |
| raise HTTPException(429, "ํ๋ฃจ ์ ์ถ ์ํ(%d๊ฑด)์ ๋๋ฌํ์ต๋๋ค." % DAILY_CAP) | |
| rec = {"candidate_id": cid, "formula": s.formula.strip(), | |
| "composition": comp, "n_atoms": info["n_atoms"], | |
| "display_name": (s.display_name or "").strip()[:60], | |
| "model_name": (s.model_name or "").strip()[:80], | |
| "rationale": (s.rationale or "").strip()[:1000], | |
| "visibility": "public" if s.visibility == "public" else "private", | |
| "has_structure": bool(s.structure), | |
| "hf_user": (user or {}).get("name", ""), | |
| "hf_sub": (user or {}).get("sub", ""), | |
| "season": season["number"], | |
| "submitted_at": int(time.time()), | |
| "uuid": uuid.uuid4().hex} | |
| store.write(seasons.path(season, "submissions/%s.json" % cid), rec, | |
| summary="submit %s" % cid) | |
| if s.structure: | |
| # ๊ตฌ์กฐ๋ ๋ฐ๋ก ๋๋ค. ๋ชฉ๋ก์ ํ์ ๋ ๊ฐ์ด ๋๋ ค์ค๋ฉด ์์ํ๊ฐ ๋ฌด๊ฑฐ์์ง๋ค. | |
| store.write(seasons.path(season, "structures/%s.json" % cid), | |
| {"candidate_id": cid, "cif": s.structure[:400_000]}, | |
| summary="structure %s" % cid) | |
| CACHE.drop("ids:%d" % season["number"]) | |
| scored = set((_board(season).get("entries") or {}).keys()) | |
| ahead = len([i for i in _submissions(season) if i not in scored]) | |
| return {"accepted": True, "candidate_id": cid, "queue_position": ahead, | |
| "note": "์ฑ์ ์ ๋ณ๋ ๊ณ์ฐ ์์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ฉฐ ์๋ฃ๊น์ง ์๊ฐ์ด ๊ฑธ๋ฆฝ๋๋ค."} | |
| # ---------------------------------------------------------------- ์์ํ | |
| def _anchors(season): | |
| p = os.path.join(DATA, season.get("anchors") or "") | |
| if not os.path.exists(p): | |
| return [] | |
| out = [] | |
| for a in json.load(open(p, encoding="utf-8")): | |
| if not a.get("admitted"): | |
| continue | |
| out.append({"candidate_id": a["label"], "is_anchor": True, | |
| "display_name": "๊ธฐ์ค๋ฌผ์ง", "model_name": "", | |
| "formula": a.get("formula", ""), | |
| "total": a.get("total"), "total_max": a.get("total_max"), | |
| "tier": a.get("tier", 1), | |
| "axes": {k: v["points"] for k, v in (a.get("axes") or {}).items()}, | |
| "detail": {k: v["detail"] for k, v in (a.get("axes") or {}).items()}, | |
| "note": a.get("note", "")}) | |
| return out | |
| # ์์ด์จ ๊ณจ๊ฒฉ์ด ๋ฌผ์ง์ ์ฑ์ง์ ํฌ๊ฒ ๊ฐ๋ฅธ๋ค. ๊ฐ์ ๊ณ์ด๋ผ๋ฆฌ ๊ฒฌ์ฃผ์ด์ผ ํ๋ณด์ ์์น๊ฐ ์ฝํ๋ค. | |
| # ๋ถ๋ฌธ์ ๋๋๋ **์ ์๋ ๊ทธ๋๋ก ๋๋ค** - ์ด๋ค ์ ์ถ๋ ๋ฌดํจ๊ฐ ๋์ง ์๋๋ค. | |
| _ANION_ORDER = ["F", "Cl", "Br", "I", "S", "Se", "O", "N", "P", "H"] | |
| DIVISIONS = {"F": "fluoride", "Cl": "halide", "Br": "halide", "I": "halide", | |
| "S": "sulfide", "Se": "sulfide", "O": "oxide", "N": "nitride", | |
| "P": "oxide", "H": "hydride"} | |
| def division_of(comp): | |
| """๊ณจ๊ฒฉ ์์ด์จ์ผ๋ก ๋ถ๋ฌธ์ ์ ํ๋ค. | |
| ์์ฌ ์์ผ๋ฉด **๊ฐ์ฅ ๋ง์ ๊ฒ**์ ๊ณ ๋ฅธ๋ค. Li6PS5Cl ์ S ๊ฐ 5, Cl ์ด 1 ์ด๋ฏ๋ก ํฉํ๋ฌผ์ด๊ณ , | |
| ์ค์ ๋ก ์์ง๋ก๋ค์ดํธ๋ ํฉํ๋ฌผ๋ก ๋ถ๋ฅ๋๋ค. ๊ฐ์๊ฐ ๊ฐ์ผ๋ฉด ์ ์์์์ ์์ ๊ฒ์ ์ด๋ค. | |
| """ | |
| if not comp: | |
| return "other" | |
| best, bestn = None, -1 | |
| for el in _ANION_ORDER: | |
| n = comp.get(el, 0) | |
| if n > bestn: | |
| best, bestn = el, n | |
| if bestn <= 0: | |
| return "other" | |
| return DIVISIONS.get(best, "other") | |
| def _leaderboard_body(season): | |
| rows = [mask(r) for r in (_board(season).get("entries") or {}).values()] | |
| anchors = _anchors(season) | |
| merged = rows + anchors | |
| # ์ ์๊ฐ ์๋ ๊ฒ์ ๋งจ ๋ค๋ก. ๊ทธ ์์์๋ ๋ธ ์์๋ฅผ ์งํจ๋ค. | |
| # ์ ์ถ์ด ์ฑ์์ง ๊ฒ๋ถํฐ, ๊ทธ ์์์ ์ด์ ์. ์ถ์ด ๋น ๊ฒ์ ๋ค๋ก - ์ด์ ์ด ์์ด๋ | |
| # ๋ง์ ์ด ๋ฌ๋ผ ๊ฐ์ ์ค์ ์ธ์ธ ์ ์๋ค. | |
| merged.sort(key=lambda e: (e.get("tier", 1), | |
| 1 if (e.get("total") is None or e.get("held_axes")) else 0, | |
| -(e.get("total") if e.get("total") is not None else 0), | |
| e.get("submitted_at") or 0)) | |
| n = 0 | |
| div_n = {} | |
| for e in merged: | |
| comp = e.get("composition") or {} | |
| e["division"] = division_of(comp) | |
| # ๋ฑ์๋ **์ ์ถ์ด ์ฑ์์ง ๊ฒ**์๋ง ์ค๋ค. ์ถ์ด ํ๋๋ผ๋ ๋น๋ฉด ๋ง์ ์ด ๋ฌ๋ผ ์ด์ ์ | |
| # ๋๋ํ ์ธ์ธ ์ ์๋ค - ์ ์๋ ๋จ๊ธฐ๋ ์์์๋ ๋ฃ์ง ์๋๋ค. | |
| unscored = e.get("total") is None or bool(e.get("held_axes")) | |
| if e.get("is_anchor") or e.get("tier", 1) != 1 or unscored: | |
| e["rank"] = None # ๊ธฐ์ค๋ฌผ์ง์ ์ฒ๋๋ฅผ ํ์ํ ๋ฟ ๋ฑ์๋ฅผ ๊ฐ์ง ์๋๋ค | |
| e["div_rank"] = None | |
| else: | |
| n += 1 | |
| e["rank"] = n | |
| # ๋ถ๋ฌธ ์์๋ ์ ์ฒด ์์์ ๋ฐ๋ก ๋งค๊ธด๋ค. ์ ์๋ ์๋์ง ์๋๋ค. | |
| d = e["division"] | |
| div_n[d] = div_n.get(d, 0) + 1 | |
| e["div_rank"] = div_n[d] | |
| tally = {} | |
| for e in merged: | |
| if e.get("is_anchor"): | |
| continue | |
| tally[e["division"]] = tally.get(e["division"], 0) + 1 | |
| # ๐ด ๊ฑฐ๋ถ์ ๋ณด๋ฅ๋ฅผ ํ ์นธ์ ํฉ์น์ง ์๋๋ค. | |
| # ํฉ์ณ ๋์ผ๋ฉด ๊ด๋ฌธ์์ **ํ๋ฝํ ๊ฒ**์ด "์ฌ์ฌ ๋๊ธฐ ์ค"์ผ๋ก ์ฝํ๋ค. | |
| # ์ค์ธก(2026-08-30): ๊ฑฐ๋ถ 45 ยท ๋ณด๋ฅ 2 ์ธ๋ฐ ์์ฝ์ held 47 ๋ก ๋๊ฐ๋ค. | |
| # ๊ฐ๋ณ ํญ๋ชฉ์๋ status ๊ฐ ์ ๋๋ก ์ค๋ ค ์์์ผ๋ฏ๋ก, ํ๋ฆฐ ๊ฒ์ ์์ฝ๋ฟ์ด๋ค. | |
| real = [e for e in merged if not e.get("is_anchor")] | |
| n_rejected = sum(1 for e in real if e.get("status") == "rejected") | |
| n_held = sum(1 for e in real | |
| if e.get("status") != "rejected" | |
| and (e.get("total") is None or e.get("held_axes"))) | |
| return {"season": seasons.public(season), "entries": merged, | |
| "divisions": tally, | |
| "counts": {"scored": len(real) - n_held - n_rejected, | |
| "rejected": n_rejected, "held": n_held, | |
| "anchors": len(anchors)}} | |
| def _leaderboard_bytes(season): | |
| raw = json.dumps(_leaderboard_body(season), ensure_ascii=False).encode("utf-8") | |
| return raw, gzip.compress(raw, 6) | |
| def leaderboard(request: Request): | |
| """์๋ต์ ์์ฆ์๋ง ์์กดํ๋ฏ๋ก TTL ๋น ํ ๋ฒ๋ง ๋ง๋ค๊ณ ํ ๋ฒ๋ง ์์ถํ๋ค.""" | |
| season = seasons.get(request.query_params.get("season")) | |
| raw, gz = CACHE.get("lbresp:%d" % season["number"], | |
| lambda: _leaderboard_bytes(season)) | |
| if "gzip" in (request.headers.get("accept-encoding") or ""): | |
| return Response(content=gz, media_type="application/json", | |
| headers={"Content-Encoding": "gzip", "Vary": "Accept-Encoding"}) | |
| return Response(content=raw, media_type="application/json", | |
| headers={"Vary": "Accept-Encoding"}) | |
| _FILLER = {"ai", "model", "llm", "the", "v", "ver", "version", "latest", "preview", | |
| "chat", "instruct", "it", "api", "official", "new"} | |
| def canon_model(name): | |
| """ํ๊ธฐ๊ฐ ๊ฐ๋ฆฐ ๊ฐ์ ๋ชจ๋ธ์ ํ๋๋ก ๋ฌถ๋๋ค. | |
| 'Anthropic Claude Opus 5' ์ 'claude-opus-5' ๋ ๊ฐ์ ๊ฒ์ด๊ณ , ๋ฐ๋ก ์ธ๋ฉด ๋ ๋ค | |
| ์ค์ ๋ณด๋ค ์ ๊ฒ ์ฐ์ธ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค. ๋ค๋ง ๋ณ์ข ์ ๊ตฌ๋ถํ๋ ๋ฑ๋ง(Pro/Flash ๋ฑ)์ | |
| ์ง์ฐ์ง ์๋๋ค - ๊ทธ๊ฒ๊น์ง ๋ญ์น๋ฉด ๋ค๋ฅธ ๋ชจ๋ธ์ด ํ ์นธ์ ๋ค์ด๊ฐ๋ค. | |
| """ | |
| s = re.sub(r"[^a-z0-9]+", " ", (name or "").lower()).strip() | |
| toks = [t for t in s.split() if t and t not in _FILLER] | |
| return " ".join(toks) or (name or "").strip().lower() | |
| def models(request: Request): | |
| """๋ชจ๋ธ๋ณ ์ฑ์ : ์ด๋ ๋ชจ๋ธ์ด ๊ฐ์ฅ ์ข์ ํ๋ณด๋ฅผ ๋๋๊ฐ, ๊ทธ๋ฆฌ๊ณ ์ผ๋ง๋ ์ฐ์ด๋๊ฐ. | |
| ๋ ์๋ฅผ **๋ฐ๋ก** ๋ธ๋ค. ๋ง์ด ์ฐ์ธ๋ค๊ณ ์ํ๋ ๊ฒ์ด ์๋๋ค - ๋ค๋ค ์ง๋ ๋ชจ๋ธ์ ์ ์์ | |
| ๋ฌด๊ดํ๊ฒ ์ ์ถ ์๊ฐ ์์ด๊ณ , ์ธ ๋ฒ ์ฐ์ธ ๋ชจ๋ธ์ด 1์ ํ๋ณด๋ฅผ ๊ฐ๊ณ ์์ ์ ์๋ค. | |
| ํ๊ท ๋ง ๋ด๋ฉด ๋ ๋ค ๊ฐ๋ ค์ง๋ค. ๊ทธ๋์ ์ต๊ณ ยทํ๊ท ยท๊ฑด์๋ฅผ ๋๋ํ ๋๋ค. | |
| ๊ธฐ์ค๋ฌผ์ง์ ์ ์ธํ๋ค - ์ฐธ๊ฐ์์ ๋ชจ๋ธ์ด ๋ง๋ ๊ฒ์ด ์๋๋ค. | |
| """ | |
| season = seasons.get(request.query_params.get("season")) | |
| agg = {} | |
| for r in (_board(season).get("entries") or {}).values(): | |
| if r.get("total") is None: | |
| continue | |
| name = (r.get("model_name") or "").strip() or "๋ฏธ๊ธฐ์ฌ / unspecified" | |
| key = canon_model(name) | |
| a = agg.setdefault(key, {"n": 0, "best": None, "sum": 0.0, | |
| "top_candidate": None, "spellings": {}}) | |
| a["spellings"][name] = a["spellings"].get(name, 0) + 1 | |
| a["n"] += 1 | |
| a["sum"] += r["total"] | |
| if a["best"] is None or r["total"] > a["best"]: | |
| a["best"] = r["total"] | |
| a["top_candidate"] = r.get("candidate_id") | |
| out = [] | |
| for k, a in agg.items(): | |
| # ์ฐธ๊ฐ์๋ค์ด ์ค์ ๋ก ๊ฐ์ฅ ๋ง์ด ์ด ํ๊ธฐ๋ฅผ ์ด๋ฆ์ผ๋ก ์ผ๋๋ค | |
| label = max(a["spellings"].items(), key=lambda kv: kv[1])[0] | |
| out.append({"model": label, "n": a["n"], "best": round(a["best"], 2), | |
| "mean": round(a["sum"] / a["n"], 2), | |
| "top_candidate": a["top_candidate"]}) | |
| out.sort(key=lambda x: (-x["best"], -x["n"])) | |
| return {"models": out, "counts": {"models": len(out), | |
| "entries": sum(m["n"] for m in out)}} | |
| def queue(request: Request): | |
| season = seasons.get(request.query_params.get("season")) | |
| ids = _submissions(season) | |
| scored = set((_board(season).get("entries") or {}).keys()) | |
| return {"queued": len([i for i in ids if i not in scored]), | |
| "scored": len(scored), "total": len(ids)} | |
| def health(): | |
| return {"ok": True, "seasons": [s["number"] for s in seasons.SEASONS]} | |