Spaces:
Running
Running
상단에 총 제출 수 · 참가는 한 부문이면 된다는 점 명시
Browse files- app.py +31 -0
- i18n.js +4 -2
- index.html +5 -0
app.py
CHANGED
|
@@ -237,6 +237,36 @@ def _hdr():
|
|
| 237 |
return {"Authorization": "Bearer " + HF_TOKEN, "User-Agent": "LEADBOARD/1.0"}
|
| 238 |
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
def ledger_read(path, default=None):
|
| 241 |
try:
|
| 242 |
with urllib.request.urlopen(urllib.request.Request(
|
|
@@ -370,6 +400,7 @@ def api_leaders():
|
|
| 370 |
"eligible": len(bs) >= n_boards})
|
| 371 |
standing.sort(key=lambda x: (not x["eligible"], -x["firsts"], -x["submitted"]))
|
| 372 |
return {"spec": SPEC, "n": len(rows), "rows": rows,
|
|
|
|
| 373 |
"held": sum(1 for r in rows if r["leader"]),
|
| 374 |
"prize": dict(PRIZE, n_boards=n_boards), "standing": standing,
|
| 375 |
"host_accounts": sorted(HOST_ACCOUNTS)}
|
|
|
|
| 237 |
return {"Authorization": "Bearer " + HF_TOKEN, "User-Agent": "LEADBOARD/1.0"}
|
| 238 |
|
| 239 |
|
| 240 |
+
def submission_count():
|
| 241 |
+
"""원장에 들어온 제출 수. **주최측 기준 제출도 포함한 전체다.**
|
| 242 |
+
|
| 243 |
+
화면 상단에 올리는 값이라 매 요청마다 원장을 훑으면 안 된다 - 트리 조회는
|
| 244 |
+
페이지당 1000건이고 부문이 46개다. 5분 캐시로 충분하다: 이 숫자는 사람이
|
| 245 |
+
제출할 때만 움직이고, 5분 늦게 보인다고 잘못된 값이 되지는 않는다.
|
| 246 |
+
"""
|
| 247 |
+
def build():
|
| 248 |
+
n, cursor = 0, ""
|
| 249 |
+
for _ in range(20): # 20페이지 = 2만 건. 그 위는 지금 없다.
|
| 250 |
+
url = (LEDGER_API + "/tree/main?recursive=1&limit=1000"
|
| 251 |
+
+ ("&cursor=" + cursor if cursor else ""))
|
| 252 |
+
try:
|
| 253 |
+
with urllib.request.urlopen(
|
| 254 |
+
urllib.request.Request(url, headers=_hdr()), timeout=60) as r:
|
| 255 |
+
rows = json.loads(r.read())
|
| 256 |
+
link = r.headers.get("Link") or ""
|
| 257 |
+
except Exception:
|
| 258 |
+
return None # 🔴 실패는 0 이 아니다. 모르는 것이다.
|
| 259 |
+
n += sum(1 for x in rows
|
| 260 |
+
if str(x.get("path", "")).startswith("submissions/")
|
| 261 |
+
and str(x.get("path", "")).endswith(".json"))
|
| 262 |
+
m = re.search(r"cursor=([^&>]+)", link) if 'rel="next"' in link else None
|
| 263 |
+
if not m:
|
| 264 |
+
break
|
| 265 |
+
cursor = m.group(1)
|
| 266 |
+
return n
|
| 267 |
+
return cached("subcount", 300, build)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
def ledger_read(path, default=None):
|
| 271 |
try:
|
| 272 |
with urllib.request.urlopen(urllib.request.Request(
|
|
|
|
| 400 |
"eligible": len(bs) >= n_boards})
|
| 401 |
standing.sort(key=lambda x: (not x["eligible"], -x["firsts"], -x["submitted"]))
|
| 402 |
return {"spec": SPEC, "n": len(rows), "rows": rows,
|
| 403 |
+
"submissions": submission_count(),
|
| 404 |
"held": sum(1 for r in rows if r["leader"]),
|
| 405 |
"prize": dict(PRIZE, n_boards=n_boards), "standing": standing,
|
| 406 |
"host_accounts": sorted(HOST_ACCOUNTS)}
|
i18n.js
CHANGED
|
@@ -30,7 +30,8 @@ ko: {
|
|
| 30 |
secBrowse: "분야", secBrowseLead: "분야를 선택하면 소속 부문의 순위표와 제출 창구로 이동합니다.",
|
| 31 |
prizeH: "상금",
|
| 32 |
prizeLead: (amt,close)=>`마감일에 <b>한 분</b>에게 미화 ${amt}달러를 드립니다. 마감 ${close}.`,
|
| 33 |
-
prizeRule: (n)=>`<b>자격
|
|
|
|
| 34 |
prizeElig: "자격 충족",
|
| 35 |
prizeMissing: (n)=>`${n}개 부문 남음`,
|
| 36 |
prizeHost: "주최측 계정(SeaWolf-AI 등)은 수상 대상이 아닙니다. 눈금 역할로 표에 남되 겨루지 않습니다.",
|
|
@@ -236,7 +237,8 @@ en: {
|
|
| 236 |
secBrowse: "Disciplines",
|
| 237 |
prizeH: "Prize",
|
| 238 |
prizeLead: (amt,close)=>`At the closing date <b>one entrant</b> receives USD ${amt}. Closes ${close}.`,
|
| 239 |
-
prizeRule: (n)=>`<b>
|
|
|
|
| 240 |
prizeElig: "qualified",
|
| 241 |
prizeMissing: (n)=>`${n} to go`,
|
| 242 |
prizeHost: "Host accounts (SeaWolf-AI and others) are not eligible. They stay on the tables as a scale to read against, but they do not compete.",
|
|
|
|
| 30 |
secBrowse: "분야", secBrowseLead: "분야를 선택하면 소속 부문의 순위표와 제출 창구로 이동합니다.",
|
| 31 |
prizeH: "상금",
|
| 32 |
prizeLead: (amt,close)=>`마감일에 <b>한 분</b>에게 미화 ${amt}달러를 드립니다. 마감 ${close}.`,
|
| 33 |
+
prizeRule: (n)=>`<b>참가는 한 부문이면 됩니다.</b> 부문 하나에 예측을 내면 그 부문 순위표에 바로 오릅니다.<br><b>상금 자격만</b> ${n}개 전 부문 제출입니다 — 한 부문 특화가 아니라 전체를 감당하는 예측기에 드리는 상이기 때문입니다. 전 부문을 채운 분들 중 <b>1위 부문의 개수</b>로 정하고, 동수이면 먼저 도달한 분이 우선합니다.`,
|
| 34 |
+
statSubs: "제출",
|
| 35 |
prizeElig: "자격 충족",
|
| 36 |
prizeMissing: (n)=>`${n}개 부문 남음`,
|
| 37 |
prizeHost: "주최측 계정(SeaWolf-AI 등)은 수상 대상이 아닙니다. 눈금 역할로 표에 남되 겨루지 않습니다.",
|
|
|
|
| 237 |
secBrowse: "Disciplines",
|
| 238 |
prizeH: "Prize",
|
| 239 |
prizeLead: (amt,close)=>`At the closing date <b>one entrant</b> receives USD ${amt}. Closes ${close}.`,
|
| 240 |
+
prizeRule: (n)=>`<b>One board is enough to enter.</b> Submit predictions for a single board and you appear on that board's table straight away.<br><b>The prize alone</b> requires all ${n} boards — it goes to a predictor that handles the whole range, not one that wins a single board. Among those who cover every board, ranking is by <b>the number of boards you lead</b>, ties going to whoever got there first.`,
|
| 241 |
+
statSubs: "submissions",
|
| 242 |
prizeElig: "qualified",
|
| 243 |
prizeMissing: (n)=>`${n} to go`,
|
| 244 |
prizeHost: "Host accounts (SeaWolf-AI and others) are not eligible. They stay on the tables as a scale to read against, but they do not compete.",
|
index.html
CHANGED
|
@@ -363,6 +363,7 @@ function hub(){
|
|
| 363 |
<p class="tag">${t.heroTag}</p>
|
| 364 |
<p class="lede">${t.heroLede}</p>
|
| 365 |
<div class="stats">
|
|
|
|
| 366 |
<div class="stat"><b>${T0.open}</b><span>${t.statBoards}</span></div>
|
| 367 |
<div class="stat"><b>${bs.reduce((s,b)=>s+b.n_test,0).toLocaleString()}</b><span>${t.statCompounds}</span></div>
|
| 368 |
<div class="stat"><b>${CATS.categories.filter(c=>c.open).length}</b><span>${t.statFields}</span></div>
|
|
@@ -446,6 +447,10 @@ async function paintLeaders(){
|
|
| 446 |
const el=$("#leaders"); if(!el)return;
|
| 447 |
let d; try{ d=await j("/api/leaders"); }catch(e){ el.innerHTML=""; return; }
|
| 448 |
renderPrize(d);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
const rows=d.rows.map(r=>{
|
| 450 |
const ld=r.leader; // 전역 L 은 언어다. 가리지 않는다
|
| 451 |
// 점수는 부문마다 주지표가 다르다. 회귀 부문은 MAE, 분류 전용 부문은 AUROC.
|
|
|
|
| 363 |
<p class="tag">${t.heroTag}</p>
|
| 364 |
<p class="lede">${t.heroLede}</p>
|
| 365 |
<div class="stats">
|
| 366 |
+
<div class="stat"><b id="st_subs">—</b><span>${t.statSubs}</span></div>
|
| 367 |
<div class="stat"><b>${T0.open}</b><span>${t.statBoards}</span></div>
|
| 368 |
<div class="stat"><b>${bs.reduce((s,b)=>s+b.n_test,0).toLocaleString()}</b><span>${t.statCompounds}</span></div>
|
| 369 |
<div class="stat"><b>${CATS.categories.filter(c=>c.open).length}</b><span>${t.statFields}</span></div>
|
|
|
|
| 447 |
const el=$("#leaders"); if(!el)return;
|
| 448 |
let d; try{ d=await j("/api/leaders"); }catch(e){ el.innerHTML=""; return; }
|
| 449 |
renderPrize(d);
|
| 450 |
+
// 제출 수는 원장을 훑어야 나오므로 순위 응답에 실려 온다. 실패하면 - 로 둔다.
|
| 451 |
+
// 0 으로 두면 "아무도 안 냈다"로 읽히는데, 그것과 "못 셌다"는 다르다.
|
| 452 |
+
const se = $("#st_subs");
|
| 453 |
+
if (se) se.textContent = (d.submissions==null) ? "—" : d.submissions.toLocaleString();
|
| 454 |
const rows=d.rows.map(r=>{
|
| 455 |
const ld=r.leader; // 전역 L 은 언어다. 가리지 않는다
|
| 456 |
// 점수는 부문마다 주지표가 다르다. 회귀 부문은 MAE, 분류 전용 부문은 AUROC.
|