Spaces:
Running
Running
| # -*- coding: utf-8 -*- | |
| """LEADBOARD โ ์ ์ฝ ์์ธก ๋๊ตฌ๋ฅผ ๋ถ์ผ๋ณ๋ก ๊ฐ์ ์ฃ๋์ ์ธ์ฐ๋ ์ํ๋. | |
| **์ด ์๋น์ค๊ฐ ํ๋ ์ผ** | |
| ยท ์นดํ ๊ณ ๋ฆฌยท๋ถ๋ฌธ ์นด๋๋ฅผ ์๋นํ๋ค | |
| ยท ํ ์คํธ์ (๊ตฌ์กฐ๋ง)์ ๋ด๋ ค์ค๋ค | |
| ยท ์ ์ถ์ ๋ฐ์ ๋น๊ณต๊ฐ ์์ฅ์ ์ ๋๋ค | |
| ยท ์์ปค๊ฐ ๊ตด๋ ค ๋์ ์์ํ๋ฅผ ๋ณด์ฌ์ค๋ค | |
| **ํ์ง ์๋ ์ผ: ์ฑ์ .** | |
| ์ ๋ต์ ์ด ์ปจํ ์ด๋์ ์๋ค. ์ปจํ ์ด๋ ์ด๋ฏธ์ง๋ ๋๊ตฌ๋ ๋ฐ์ ์ ์์ผ๋ฏ๋ก, ์ฌ๊ธฐ์ ๋ผ๋ฒจ์ ๋๋ฉด | |
| ๊ทธ ์๊ฐ 1์กฐ๊ฐ ๋ฌด๋์ง๋ค. ์ฑ์ ์ ์์ฅ์ ํด๋งํ๋ ๋ณ๋ ์์ปค๊ฐ ๋ก์ปฌ ์ ๋ต ํ์ผ๋ก ํ๋ค. | |
| ๋ถ๋ฌธ ์นด๋์๋ **์ฑ์ ์ ๊ฒ์ฆํ๋ ๋ฐ ํ์ํ ๋ชจ๋ ๊ณต๊ฐ ์ ๋ณด**๊ฐ ๋ค์ด ์๋ค โ | |
| ๋ถํ ๋ฑ๊ธ ยท ์ ๋ต ๋ฑ๊ธ ยท ์ก์ ๋ฐ๋ฅ ยท ๊ธฐ์ค์ ์ฑ์ ยท ๋ฐ์ดํฐ ์ง๋ฌธ. | |
| ์ฐ๋ฆฌ ์ ์๋ฅผ ๋ฏฟ์ด๋ฌ๋ผ๊ณ ํ์ง ์๊ธฐ ์ํด์๋ค. | |
| """ | |
| import base64 | |
| import glob | |
| import hashlib | |
| import hmac | |
| import io | |
| import json | |
| import os | |
| import re | |
| import secrets | |
| import time | |
| import urllib.error | |
| import urllib.parse | |
| import urllib.request | |
| from fastapi import FastAPI, HTTPException, Request | |
| from fastapi.middleware.gzip import GZipMiddleware | |
| from fastapi.responses import (FileResponse, JSONResponse, RedirectResponse, | |
| Response) | |
| from pydantic import BaseModel | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| DATA = os.path.join(HERE, "data") | |
| SPEC = "v1.1" | |
| LEDGER = os.environ.get("LB_LEDGER_REPO", "FINAL-Bench/leadboard-submissions") | |
| LEDGER_API = "https://huggingface.co/api/datasets/%s" % LEDGER | |
| LEDGER_RAW = "https://huggingface.co/datasets/%s/resolve/main" % LEDGER | |
| HF_TOKEN = os.environ.get("HF_TOKEN", "") | |
| 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 = "lb_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("LB_SESSION_KEY") or secrets.token_hex(16) | |
| DAILY_CAP = int(os.environ.get("LB_DAILY_CAP", "5")) | |
| app = FastAPI(title="LEADBOARD") | |
| app.add_middleware(GZipMiddleware, minimum_size=1024) | |
| _C = {} | |
| # ํ๋ฃจ ํ๋ ์นด์ดํฐ. ์ปจํ ์ด๋ ์ฌ๊ธฐ๋์ด๋ฉด ๋น๋๋ฐ, ๊ทธ๋๋ ํญ์ฃผ๋ ๋ง๋๋ค. | |
| # ์ ํํ ํ๊ณ๊ฐ ํ์ํด์ง๋ฉด ์์ฅ ์ชฝ์ผ๋ก ์ฎ๊ธด๋ค. | |
| _CAP = {} | |
| def cached(key, ttl, produce): | |
| hit = _C.get(key) | |
| if hit and time.time() - hit[0] < ttl: | |
| return hit[1] | |
| try: | |
| v = produce() | |
| except Exception: | |
| if hit: | |
| return hit[1] | |
| raise | |
| _C[key] = (time.time(), v) | |
| return v | |
| # ------------------------------------------------------------------ ๋ถ๋ฌธ ์นด๋ | |
| def _sha(path): | |
| h = hashlib.sha256() | |
| with open(path, "rb") as f: | |
| for b in iter(lambda: f.read(65536), b""): | |
| h.update(b) | |
| return h.hexdigest()[:16] | |
| def _board_meta(): | |
| """๋ถ๋ฌธ ํ ์ค ์ค๋ช . ์ด๋ฆ๊ณผ ํ์ ๊ธฐํธ๋ง์ผ๋ก๋ ๋ฌด์์ ์ฌ๋์ง ์ ์ ์๋ค.""" | |
| p = os.path.join(HERE, "board_meta.json") | |
| try: | |
| return json.load(io.open(p, encoding="utf-8")).get("boards", {}) | |
| except Exception: | |
| return {} | |
| def _load_boards(): | |
| meta = _board_meta() | |
| out = {} | |
| for p in sorted(glob.glob(os.path.join(DATA, "*_card.json"))): | |
| try: | |
| d = json.load(io.open(p, encoding="utf-8")) | |
| except Exception: | |
| continue | |
| nf = d.get("noise_floor") or {} | |
| base = d.get("baselines") or {} | |
| # ๋ถ๋ฅ ์ ์ฉ ๋ถ๋ฌธ์๋ MAE ๊ธฐ์ค์ ์ด ์๋ค. ๊ทธ๋ด ๋๋ AUROC ๊ฐ ๊ฐ์ฅ ๋์ ๊ฒ์ ์ต์ ์ผ๋ก ๋ณธ๋ค. | |
| # ์๋ ๊ฐ์ 0 ์ผ๋ก ์น๋ฉด ๊ทธ ๋ถ๋ฌธ์ด "๊ฐ์ฅ ์ ํํ ๊ณณ"์ผ๋ก ํ์ ์ค๋ฅธ๋ค. | |
| with_mae = {k: v for k, v in base.items() if v.get("mae") is not None} | |
| if with_mae: | |
| best = min(with_mae, key=lambda k: with_mae[k]["mae"]) | |
| d["metric"] = "mae" | |
| elif base: | |
| best = max(base, key=lambda k: base[k].get("auroc") or 0) | |
| d["metric"] = "auroc" | |
| else: | |
| best = None | |
| d["metric"] = None | |
| d["dataset_sha"] = _sha(p) | |
| d["spec"] = SPEC | |
| d["best_baseline"] = best | |
| # ๋ชจ๋ธ ์ค์ฐจ๊ฐ ์คํ ์ค์ฐจ์ ๋ช ๋ฐฐ์ธ๊ฐ. 1 ์ ๋ถ์์๋ก ์ธก์ ํ๊ณ๋ค. | |
| if best and nf.get("sd_single") and d["metric"] == "mae": | |
| d["error_over_noise"] = round(base[best]["mae"] / nf["sd_single"], 2) | |
| if ("์์ ์์ธก" in base and "Morgan+LightGBM" in base | |
| and base["Morgan+LightGBM"].get("mae") is not None | |
| and base["์์ ์์ธก"].get("mae") is not None): | |
| d["beats_constant"] = base["Morgan+LightGBM"]["mae"] < base["์์ ์์ธก"]["mae"] | |
| if d.get("n_test"): | |
| d["near_pct"] = round(100.0 * d.get("near_threshold", 0) / d["n_test"], 1) | |
| m = meta.get(d["board"]) or {} | |
| d["blurb"] = m.get("ko", "") | |
| d["blurb_en"] = m.get("en", "") | |
| d["open"] = True | |
| out[d["board"]] = d | |
| return out | |
| def boards(): | |
| return cached("boards", 300, _load_boards) | |
| def _load_references(): | |
| """ํ์ ๋๊ตฌ๋ฅผ ์ด ๋ถ๋ฌธ ํ๋ จ ์๋ฃ๋ก ์ฌํ์ตํด ์ป์ ์ฑ์ . | |
| ๋ค๋ฅธ ์๋ฃ๋ก ํ์ตํ ๋ชจ๋ธ์ ๊ทธ๋๋ก ์ฎ๊ฒจ ์ฌ๋ฉด, ํ๋ จ ์๋ฃ์ ์ฐจ์ด๊ฐ ๋ฐฉ๋ฒ์ ์ฐจ์ด๋ก ์ฝํ๋ค. | |
| ๊ทธ๋์ **๋ฐฉ๋ฒ๋ง ๊ฐ์ ธ์ค๊ณ ๋ฐ์ดํฐ๋ ์ด ๋ถ๋ฌธ ๊ฒ์ ์ด๋ค.** ๊ทธ๋์ผ ํ์ ์ค๋ฅธ ์ซ์๊ฐ | |
| "์ด ๋ฐฉ๋ฒ์ด ์ด ๋ฌธ์ ์์ ์ด๋๊น์ง ๊ฐ๋๊ฐ"๋ฅผ ๋ปํ๋ค. | |
| """ | |
| # ์ฐธ์กฐ ๋ฐฉ๋ฒ์ ์ฌ๋ฟ์ผ ์ ์๋ค. ํ์ผ ํ๋์ ๋ฐฉ๋ฒ ํ๋๋ฅผ ๋ด๊ณ , ์ฌ๊ธฐ์ ๋ค ๋ชจ์๋ค. | |
| out = {} | |
| for p in sorted(glob.glob(os.path.join(DATA, "reference_*.json"))): | |
| try: | |
| d = json.load(io.open(p, encoding="utf-8")) | |
| except Exception: | |
| continue | |
| for e in d.get("entries", []): | |
| out.setdefault(e["board"], []).append( | |
| dict(e, method=d.get("method", "reference"), | |
| method_en=d.get("method_en") or d.get("method", "reference"), | |
| ref_note=d.get("note"), ref_note_en=d.get("note_en"))) | |
| return out | |
| def references(): | |
| return cached("refs", 300, _load_references) | |
| def categories(): | |
| def build(): | |
| cats = json.load(io.open(os.path.join(HERE, "categories.json"), encoding="utf-8")) | |
| bd = boards() | |
| for c in cats["categories"]: | |
| c["open"] = len([b for b in c["boards"] if b in bd]) | |
| c["board_cards"] = [bd[b] for b in c["boards"] if b in bd] | |
| return cats | |
| return cached("cats", 300, build) | |
| # ------------------------------------------------------------------ ์์ฅ | |
| def _hdr(): | |
| return {"Authorization": "Bearer " + HF_TOKEN, "User-Agent": "LEADBOARD/1.0"} | |
| def ledger_read(path, default=None): | |
| try: | |
| with urllib.request.urlopen(urllib.request.Request( | |
| "%s/%s" % (LEDGER_RAW, path), headers=_hdr()), timeout=60) as r: | |
| return json.loads(r.read()) | |
| except Exception: | |
| return default | |
| def ledger_write(path, obj, summary): | |
| blob = base64.b64encode(json.dumps(obj, ensure_ascii=False).encode()).decode() | |
| lines = [json.dumps({"key": "header", "value": {"summary": summary}}), | |
| json.dumps({"key": "file", "value": {"path": path, "content": blob, | |
| "encoding": "base64"}})] | |
| req = urllib.request.Request(LEDGER_API + "/commit/main", | |
| data=("\n".join(lines) + "\n").encode(), | |
| headers=dict(_hdr(), **{"Content-Type": "application/x-ndjson"})) | |
| with urllib.request.urlopen(req, timeout=180) as r: | |
| return json.loads(r.read()) | |
| # ------------------------------------------------------------------ ์ธ์ | |
| def sign(v): | |
| return hmac.new(SESSION_KEY.encode(), v.encode(), hashlib.sha256).hexdigest()[:32] | |
| def set_session(resp, user): | |
| raw = json.dumps(user, ensure_ascii=False) | |
| b = base64.urlsafe_b64encode(raw.encode()).decode() | |
| resp.set_cookie(COOKIE, "%s.%s" % (b, sign(b)), max_age=86400 * 7, | |
| httponly=True, **COOKIE_KW) | |
| def who(req: Request): | |
| c = req.cookies.get(COOKIE) or "" | |
| if "." not in c: | |
| return None | |
| b, sg = c.rsplit(".", 1) | |
| if not hmac.compare_digest(sg, sign(b)): | |
| return None | |
| try: | |
| return json.loads(base64.urlsafe_b64decode(b.encode()).decode()) | |
| except Exception: | |
| return None | |
| # ------------------------------------------------------------------ ๋ผ์ฐํธ | |
| def index(): | |
| return FileResponse(os.path.join(HERE, "index.html")) | |
| def i18n(): | |
| """๋ฌธ์์ด ์ฌ์ . ํ๋ฉด ์ฝ๋์ ๋ถ๋ฆฌํด ๋์ด์ผ ์์ดํ์ด ์กฐ์ฉํ ๋ค์ฒ์ง์ง ์๋๋ค.""" | |
| return FileResponse(os.path.join(HERE, "i18n.js"), | |
| media_type="application/javascript") | |
| def api_categories(): | |
| c = categories() | |
| bd = boards() | |
| return {"spec": SPEC, "categories": c["categories"], | |
| "totals": {"planned": sum(x["planned"] for x in c["categories"]), | |
| "open": len(bd), "ledger": bool(HF_TOKEN)}} | |
| def api_leaders(): | |
| """๋ถ๋ฌธ๋ง๋ค ํ์ฌ 1์ ํ ์ค. ์ฒซ ํ๋ฉด์์ ์ ์ฒด๋ฅผ ํ๋์ ๋ณด๊ธฐ ์ํ ๊ฒ์ด๋ค. | |
| ๋ถ๋ฌธ๋ณ๋ก ์์ํ๋ฅผ ๋ฐ๋ก ๋ถ๋ฅด๋ฉด ์๋ณต์ด ๋ถ๋ฌธ ์๋งํผ ๋์ด๋๋ค. ์ฌ๊ธฐ์ ํ ๋ฒ์ ๋ชจ์๋ค. | |
| **์ฐธ๊ฐ ์ ์ถ์ด ์์ผ๋ฉด ๋น์์ ๋ณด๋ธ๋ค** - ๊ธฐ์ค์ ์ 1์ ์๋ฆฌ์ ์ํ์ง ์๋๋ค. | |
| ๊ธฐ์ค์ ์ ๋์ด์ผ ํ ์ ์ด์ง ์ฐ์น์๊ฐ ์๋๋ค. | |
| """ | |
| def build(): | |
| out = [] | |
| for name, b in boards().items(): | |
| rolled = ledger_read("leaderboard/%s.json" % name.lower(), {}) or {} | |
| ent = (rolled.get("entries") or []) | |
| top = ent[0] if ent else None | |
| nf = (b.get("noise_floor") or {}).get("sd_single") | |
| base = b.get("baselines") or {} | |
| row = {"board": name, "n_test": b.get("n_test"), | |
| "blurb": b.get("blurb"), "blurb_en": b.get("blurb_en"), | |
| "split_grade": b.get("split_grade"), "answer_grade": b.get("answer_grade"), | |
| "noise_floor": nf, "entries": len(ent), | |
| "baseline_best": (min((v["mae"] for v in base.values() if v.get("mae") is not None), | |
| default=None)), | |
| "baseline_best_auroc": (max((v["auroc"] for v in base.values() | |
| if v.get("auroc") is not None), default=None)), | |
| "leader": None} | |
| if top: | |
| row["leader"] = {"method": top.get("method"), "user": top.get("user"), | |
| "mae": top.get("mae"), "auroc": top.get("auroc"), | |
| "verified": bool(top.get("verified")), "leak": top.get("leak")} | |
| out.append(row) | |
| out.sort(key=lambda r: (r["leader"] is None, -(r["n_test"] or 0))) | |
| return out | |
| rows = cached("leaders", 60, build) | |
| return {"spec": SPEC, "n": len(rows), "rows": rows, | |
| "held": sum(1 for r in rows if r["leader"])} | |
| def api_board(name: str): | |
| b = boards().get(name) | |
| if not b: | |
| raise HTTPException(404, "๊ทธ๋ฐ ๋ถ๋ฌธ์ด ์๋ค") | |
| return b | |
| def api_testset(name: str): | |
| """ํ ์คํธ์ . ๊ตฌ์กฐ๋ง ๋๊ฐ๋ค - ๋ผ๋ฒจ์ ์ด ์ปจํ ์ด๋์ ์กด์ฌํ์ง ์๋๋ค.""" | |
| p = os.path.join(DATA, "%s_test.json" % name.lower()) | |
| if not os.path.exists(p): | |
| raise HTTPException(404, "ํ ์คํธ์ ์ด ์์ง ์๋ค") | |
| return FileResponse(p, media_type="application/json", | |
| filename="%s_test.json" % name.lower()) | |
| def api_leaderboard(name: str): | |
| """์์ํ. ๊ธฐ์ค์ ์ ํญ์ ํฌํจ๋๋ค (3์กฐ). | |
| ์ฐธ๊ฐ์ ํญ๋ชฉ์ ์์ปค๊ฐ ๊ตด๋ ค ๋์ ๊ฒ์ ๊ทธ๋๋ก ๋ณด์ฌ์ค๋ค. ์ฌ๊ธฐ์ ๊ณ์ฐํ์ง ์๋๋ค - | |
| ๊ณ์ฐํ๋ ค๋ฉด ์ ๋ต์ด ์์ด์ผ ํ๊ณ , ์ ๋ต์ ์ฌ๊ธฐ ์๋ค. | |
| """ | |
| b = boards().get(name) | |
| if not b: | |
| raise HTTPException(404, "๊ทธ๋ฐ ๋ถ๋ฌธ์ด ์๋ค") | |
| rolled = cached("lb:" + name, 60, | |
| lambda: ledger_read("leaderboard/%s.json" % name.lower(), | |
| {"entries": [], "updated": None})) or {} | |
| rows = [] | |
| for k, v in (b.get("baselines") or {}).items(): | |
| rows.append({"method": k, "user": "โ", "kind": "baseline", | |
| "mae": v["mae"], "auroc": v["auroc"], "prauc": v["prauc"]}) | |
| # ํ์ ๋๊ตฌ ์ฐธ์กฐ ํญ๋ชฉ. ๊ธฐ์ค์ ๊ณผ ์ฐธ๊ฐ ์ ์ถ ์ฌ์ด์ ๋๋๋ค - ํ์ตํ์ง ์์ ์ ๋ ์๋๊ณ | |
| # ์ด๋ฒ ํ์ฐจ์ ์ฐธ๊ฐ์๋ ์๋๋ค. ์ฐธ๊ฐ์๊ฐ ์๊ธฐ ์์น๋ฅผ ๊ฐ๋ ํ ์ธ ๋ฒ์งธ ์ขํ๋ค. | |
| for r in references().get(name, []): | |
| rows.append({"method": r["method"], "method_en": r.get("method_en"), | |
| "user": "โ", "kind": "reference", | |
| "mae": r.get("mae"), "auroc": r.get("auroc"), | |
| "prauc": r.get("prauc"), "leak": r.get("leak"), | |
| "note": r.get("ref_note"), "note_en": r.get("ref_note_en")}) | |
| for e in rolled.get("entries", []): | |
| rows.append(dict(e, kind=e.get("kind", "entry"))) | |
| # ๋ถ๋ฌธ ์ฃผ์งํ๋ก ์ธ์ด๋ค. ๋ถ๋ฅ ์ ์ฉ ๋ถ๋ฌธ์์ mae ๋ก ์ธ์ฐ๋ฉด ์ ๋ถ ๋๋ฅ ์ด ๋๋ค. | |
| if b.get("metric") == "auroc": | |
| rows.sort(key=lambda r: -(r.get("auroc") or 0)) | |
| else: | |
| rows.sort(key=lambda r: (r.get("mae") is None, r.get("mae") or 9e9)) | |
| nf = (b.get("noise_floor") or {}).get("sd_single") | |
| best = rows[0].get("mae") if rows else None | |
| # 4์กฐ: ์ต๊ณ ์ ์์ ์ก์ ๋ฐ๋ฅ ์์ ๋ ํญ๋ชฉ์ ๊ฐ์ ๊ณ๋จ์ผ๋ก ๋ฌถ๋๋ค. | |
| for r in rows: | |
| r["within_noise"] = bool(nf and best is not None and r.get("mae") is not None | |
| and r["mae"] - best < nf) | |
| rank = 0 | |
| for r in rows: | |
| # ์์๋ ์ฐธ๊ฐ ์ ์ถ์๋ง ๋งค๊ธด๋ค. ๊ธฐ์ค์ ๊ณผ ์ฐธ์กฐ ๋๊ตฌ๋ ํ์ ์๋ ๋ฑ์๋ฅผ ๊ฐ์ง ์๋๋ค - | |
| # ์ฐ๋ฆฌ๊ฐ ์ฌ๋ฆฐ ๊ฒ์ด 1์ ์๋ฆฌ๋ฅผ ์ฐจ์งํ๋ฉด ์ฐธ๊ฐ์์๊ฒ ๊ฒจ๋ฃฐ ์๋ฆฌ๊ฐ ์๋ค. | |
| if r["kind"] in ("baseline", "reference"): | |
| r["rank"] = None | |
| else: | |
| rank += 1 | |
| r["rank"] = rank | |
| return {"board": name, "noise_floor": nf, "rows": rows, | |
| "updated": rolled.get("updated"), | |
| "note": "์ก์ ๋ฐ๋ฅ ์์ ๋ ํญ๋ชฉ์ ์์ ์ฐจ์ด๋ก ์ฃผ์ฅํ์ง ์๋๋ค (4์กฐ)"} | |
| # ------------------------------------------------------------------ ๋ก๊ทธ์ธ | |
| def login(request: Request): | |
| if not (OAUTH_ID and OAUTH_SECRET): | |
| return _err_page("์ด ์ํ๋์ ๋ก๊ทธ์ธ์ด ์์ง ๊ตฌ์ฑ๋์ง ์์์ต๋๋ค.") | |
| nxt = request.query_params.get("next", "/") | |
| st = base64.urlsafe_b64encode(json.dumps({"n": nxt, "r": secrets.token_hex(8)}).encode()).decode() | |
| q = urllib.parse.urlencode({ | |
| "client_id": OAUTH_ID, "redirect_uri": _redirect(request), | |
| "response_type": "code", "scope": "openid profile", "state": st}) | |
| return RedirectResponse("%s/oauth/authorize?%s" % (OAUTH_ISS, q)) | |
| def _redirect(request: Request): | |
| """ํ๋ซํผ์ด ๋ฑ๋กํด ์ฃผ๋ ์ฝ๋ฐฑ ์ฃผ์๋ **/auth/callback** ์ด๋ค. | |
| ์ฌ๊ธฐ๋ฅผ /auth ๋ก ๋๋ฉด ํ ํฐ ๊ตํ์์ redirect_uri ๋ถ์ผ์น๋ก ๊ฑฐ๋ถ๋๊ณ , ๊ทธ ์์ธ๊ฐ | |
| ๊ทธ๋๋ก 500 ์ด ๋์ด ํ๋ฉด ์ ์ฒด๊ฐ ์ฃฝ๋๋ค. ๋ก๊ทธ์ธ ํ ๋ฒ ๋๋ ๋ค๊ฐ ์ฌ์ดํธ๊ฐ ์ฌ๋ผ์ง๋ค. | |
| """ | |
| if SPACE_HOST: | |
| return "https://%s/auth/callback" % SPACE_HOST | |
| return str(request.base_url).rstrip("/") + "/auth/callback" | |
| def _err_page(msg, detail=""): | |
| """๋ก๊ทธ์ธ์ด ์คํจํด๋ ํ๋ฉด์ ์ด์ ์์ด์ผ ํ๋ค. ํฐ ๋ฐฐ๊ฒฝ์ Internal Server Error ๋ง | |
| ๋จ์ผ๋ฉด ์ด์ฉ์๋ ์ฌ์ดํธ๊ฐ ์ฃฝ์ ์ค ์๋ค.""" | |
| return Response( | |
| "<!doctype html><html lang=ko><head><meta charset=utf-8>" | |
| "<title>๋ก๊ทธ์ธ ์คํจ</title><style>body{font-family:system-ui,sans-serif;" | |
| "max-width:38em;margin:14vh auto;padding:0 24px;line-height:1.7;color:#15171c}" | |
| "code{background:#f0f3f7;padding:2px 6px;border-radius:5px;font-size:13px}" | |
| "a{color:#1f4fa3}</style></head><body>" | |
| "<h2>๋ก๊ทธ์ธ์ ๋ง์น์ง ๋ชปํ์ต๋๋ค</h2><p>%s</p>%s" | |
| "<p><a href='/'>โ ์ํ๋๋ก ๋์๊ฐ๊ธฐ</a></p></body></html>" | |
| % (msg, ("<p><code>%s</code></p>" % detail[:300]) if detail else ""), | |
| media_type="text/html; charset=utf-8", status_code=200) | |
| def auth(request: Request): | |
| code = request.query_params.get("code") | |
| st = request.query_params.get("state") or "" | |
| nxt = "/" | |
| try: | |
| nxt = json.loads(base64.urlsafe_b64decode(st.encode()).decode()).get("n", "/") | |
| except Exception: | |
| pass | |
| if not code: | |
| return RedirectResponse(nxt) | |
| try: | |
| body = urllib.parse.urlencode({ | |
| "client_id": OAUTH_ID, "client_secret": OAUTH_SECRET, | |
| "grant_type": "authorization_code", "code": code, | |
| "redirect_uri": _redirect(request)}).encode() | |
| with urllib.request.urlopen(urllib.request.Request( | |
| OAUTH_ISS + "/oauth/token", data=body, | |
| headers={"Content-Type": "application/x-www-form-urlencoded"}), | |
| timeout=60) as r: | |
| tok = json.loads(r.read()) | |
| with urllib.request.urlopen(urllib.request.Request( | |
| OAUTH_ISS + "/oauth/userinfo", | |
| headers={"Authorization": "Bearer " + tok["access_token"]}), | |
| timeout=60) as r: | |
| ui = json.loads(r.read()) | |
| except urllib.error.HTTPError as e: | |
| return _err_page("์ธ์ฆ ์ ๊ณต์๊ฐ ์์ฒญ์ ๊ฑฐ๋ถํ์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ญ์์ค.", | |
| "%d %s" % (e.code, e.read().decode("utf-8", "replace"))) | |
| except Exception as e: | |
| return _err_page("์ธ์ฆ ์ค ํต์ ์ ์คํจํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด ์ฃผ์ญ์์ค.", | |
| "%s: %s" % (type(e).__name__, e)) | |
| resp = RedirectResponse(nxt) | |
| set_session(resp, {"user": ui.get("preferred_username") or ui.get("sub"), | |
| "name": ui.get("name", ""), "pic": ui.get("picture", "")}) | |
| return resp | |
| def auth_legacy(request: Request): | |
| """์์ ์ฃผ์๋ก ๋ค์ด์จ ์ฝ๋ฐฑ๋ ๋ฐ์ ์ค๋ค. ๋งํฌ๊ฐ ์ด๋๊ฐ ๋จ์ ์์ ์ ์๋ค.""" | |
| return auth(request) | |
| def logout(): | |
| r = RedirectResponse("/") | |
| r.delete_cookie(COOKIE, **COOKIE_KW) | |
| return r | |
| def api_me(request: Request): | |
| u = who(request) | |
| return {"user": u, "login_enabled": bool(OAUTH_ID), "daily_cap": DAILY_CAP} | |
| # ------------------------------------------------------------------ ์ ์ถ | |
| class Submission(BaseModel): | |
| board: str | |
| method: str # ๋ฐฉ๋ฒ ์ด๋ฆ. ์์ํ์ ์ด๋ ๊ฒ ํ์๋๋ค | |
| predictions: dict # {compound_id: ์์ธก๊ฐ} | |
| training_data: str = "" # 5์กฐ ๋์ถ๊ฒ์ฌ์ ์ด๋ค | |
| pretrained_on: str = "" | |
| code_url: str = "" | |
| container: str = "" # ์์ผ๋ฉด [๊ฒ์ฆ๋จ] ์ฌ์ฌ ๋์ (6์กฐ) | |
| def submit(s: Submission, request: Request): | |
| u = who(request) | |
| if not u: | |
| raise HTTPException(401, "์ ์ถํ๋ ค๋ฉด ๋ก๊ทธ์ธํด์ผ ํ๋ค") | |
| if not HF_TOKEN: | |
| raise HTTPException(503, "์์ฅ์ด ๊ตฌ์ฑ๋์ง ์์๋ค") | |
| b = boards().get(s.board) | |
| if not b: | |
| raise HTTPException(404, "๊ทธ๋ฐ ๋ถ๋ฌธ์ด ์๋ค") | |
| if not re.fullmatch(r"[\w .\-+/()]{2,60}", s.method or ""): | |
| raise HTTPException(400, "๋ฐฉ๋ฒ ์ด๋ฆ์ 2~60์์ฌ์ผ ํ๋ค") | |
| # ํ๋ฃจ ํ๋. 8์กฐ๊ฐ ์ ์ ๊ณต๊ฐ๋ฅผ ๋ง์๋ ์ ์ถ ์์ฒด๋ ๋น์ฉ์ด ๋ค๊ณ , ๋ฌด์ ํ์ด๋ฉด | |
| # ์์ฅ์ด ์ ๊ธด๋ค. ๊ฒฝ๊ณ๋ ์ฐธ๊ฐ์๊ฐ ๊ฐ์ ํ ํ๋ฃจ(KST)๋ก ์ก๋๋ค. | |
| day = int((time.time() + 9 * 3600) // 86400) | |
| key = "cap:%s:%d" % (u["user"], day) | |
| used = _CAP.get(key, 0) | |
| if used >= DAILY_CAP: | |
| raise HTTPException(429, "์ค๋ ์ ์ถ ํ๋ %dํ๋ฅผ ๋ค ์ผ๋ค. ํ๊ตญ์๊ฐ ์์ ์ ์ด๊ธฐํ๋๋ค" | |
| % DAILY_CAP) | |
| n = b.get("n_test", 0) | |
| got = len(s.predictions or {}) | |
| if got < n: | |
| raise HTTPException(400, "์์ธก์ด %d๊ฐ ํ์ํ๋ฐ %d๊ฐ๋ค. ๋น ํญ๋ชฉ์ ์ฑ์ ๋ณด๋ด๋ผ " | |
| "- ์์๋ก ๋ฉ์ฐ๋ฉด ๊ทธ ๊ฐ์ด ์ ์์ ๋ค์ด๊ฐ๋ค" % (n, got)) | |
| # **๋ชจ์๋ ๋ณธ๋ค.** ์ถ์ด ์ฌ๋ฟ์ธ ๋ถ๋ฌธ์ ์ซ์ ํ๋๋ฅผ ๋ณด๋ด๋ฉด ์ฑ์ ๊ธฐ๋ ์ ๋ถ missing ์ผ๋ก | |
| # ์ฒ๋ฆฌํ๊ณ ์ ์๊ฐ ๋น์ด์ ๋์์จ๋ค - ์ฐธ๊ฐ์๋ ๋ฌด์์ด ํ๋ ธ๋์ง ์ ๊ธธ์ด ์๋ค. | |
| # ๊ฐ์๋ง ์ธ๊ณ ํต๊ณผ์ํค๋ฉด ๊ทธ ์นจ๋ฌต์ด ์ฐ๋ฆฌ ๋ชซ์ด ๋๋ค. | |
| ax = b.get("axes") | |
| if ax: | |
| bad = [k for k, v in (s.predictions or {}).items() | |
| if not (isinstance(v, (list, tuple)) and len(v) == ax)] | |
| if bad: | |
| raise HTTPException(400, "์ด ๋ถ๋ฌธ์ ํํฉ๋ฌผ๋ง๋ค ์ซ์ %d๊ฐ์ง๋ฆฌ ๋ฐฐ์ด์ด ํ์ํ๋ค. " | |
| "%d๊ฑด์ด ๊ทธ ๋ชจ์์ด ์๋๋ค (์: %s). ํ์์ ๋ถ๋ฌธ ์๋ด๋ฅผ ๋ณด๋ผ" | |
| % (ax, len(bad), bad[0])) | |
| else: | |
| bad = [k for k, v in (s.predictions or {}).items() | |
| if isinstance(v, (list, tuple))] | |
| if bad: | |
| raise HTTPException(400, "์ด ๋ถ๋ฌธ์ ํํฉ๋ฌผ๋ง๋ค ์ซ์ ํ๋๊ฐ ํ์ํ๋ฐ ๋ฐฐ์ด์ด ์๋ค " | |
| "(%d๊ฑด, ์: %s)" % (len(bad), bad[0])) | |
| sid = hashlib.sha256(("%s|%s|%s|%f" % (s.board, u["user"], s.method, time.time())) | |
| .encode()).hexdigest()[:16].upper() | |
| rec = {"submission_id": sid, "board": s.board, "hf_user": u["user"], | |
| "method": s.method, "predictions": s.predictions, | |
| "training_data": s.training_data, "pretrained_on": s.pretrained_on, | |
| "code_url": s.code_url, "container": s.container, | |
| "submitted_at": int(time.time()), "spec": SPEC, | |
| "dataset_sha": b.get("dataset_sha")} | |
| ledger_write("submissions/%s/%s.json" % (s.board.lower(), sid), rec, | |
| "submit %s %s" % (s.board, sid)) | |
| _CAP[key] = used + 1 | |
| return {"ok": True, "submission_id": sid, | |
| "remaining_today": DAILY_CAP - _CAP[key], | |
| "note": "์ฑ์ ์ ์์ปค๊ฐ ํ๋ค. 8์กฐ์ ๋ฐ๋ผ ์ง์ ์ต๊ณ ์ ์ ์ก์ ๋ฐ๋ฅ ์ด์์ผ๋ก " | |
| "๋์์ ๋๋ง ์ ์ ์๊ฐ ๊ณต๊ฐ๋๋ค."} | |
| def health(): | |
| return {"ok": True, "spec": SPEC, "boards": len(boards()), | |
| "ledger": bool(HF_TOKEN), "login": bool(OAUTH_ID), | |
| "scoring_here": False} | |