1만 건 돌파 이정표 배너 — 숫자는 /api/totals 에서 실시간으로 읽는다
Browse files- app.py +34 -0
- index.html +26 -0
app.py
CHANGED
|
@@ -282,6 +282,40 @@ def season(request: Request):
|
|
| 282 |
return seasons.public(seasons.get(request.query_params.get("season")))
|
| 283 |
|
| 284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
@app.get("/api/seasons")
|
| 286 |
def season_list():
|
| 287 |
"""Every season, so the page can draw its tabs without knowing them in advance."""
|
|
|
|
| 282 |
return seasons.public(seasons.get(request.query_params.get("season")))
|
| 283 |
|
| 284 |
|
| 285 |
+
@app.get("/api/totals")
|
| 286 |
+
def api_totals():
|
| 287 |
+
"""전 시즌 누적 제출·참가자·경과일. 축하 문구가 읽는 값이다.
|
| 288 |
+
|
| 289 |
+
🔴 숫자를 문구에 박지 않는다. 제출은 시간당 수십 건씩 들어와서, 박아두면
|
| 290 |
+
반나절 만에 낡은 값이 공개면에 남는다. 여기서 세어 화면이 그때그때 읽는다.
|
| 291 |
+
|
| 292 |
+
캐시 5분: 원장 트리를 시즌마다 훑는 일이라 매 요청마다 하면 첫 화면이 느려진다.
|
| 293 |
+
"""
|
| 294 |
+
def build():
|
| 295 |
+
subs = 0
|
| 296 |
+
users = set()
|
| 297 |
+
first = None
|
| 298 |
+
for num in sorted(seasons.SEASONS):
|
| 299 |
+
pre = seasons.path(seasons.SEASONS[num], "")
|
| 300 |
+
rolled = ledger_read("%sleaderboard.json" % pre, {}) or {}
|
| 301 |
+
ent = rolled.get("entries") or {}
|
| 302 |
+
rows = list(ent.values() if isinstance(ent, dict) else ent)
|
| 303 |
+
subs += len(rows)
|
| 304 |
+
for r in rows:
|
| 305 |
+
if not r:
|
| 306 |
+
continue
|
| 307 |
+
u = r.get("hf_user")
|
| 308 |
+
if u:
|
| 309 |
+
users.add(u)
|
| 310 |
+
ts = r.get("submitted_at")
|
| 311 |
+
if isinstance(ts, (int, float)) and (first is None or ts < first):
|
| 312 |
+
first = ts
|
| 313 |
+
days = ((time.time() - first) / 86400.0) if first else None
|
| 314 |
+
return {"submissions": subs, "participants": len(users),
|
| 315 |
+
"opened_at": first, "days": round(days, 1) if days else None}
|
| 316 |
+
return cached("totals", 300, build)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
@app.get("/api/seasons")
|
| 320 |
def season_list():
|
| 321 |
"""Every season, so the page can draw its tabs without knowing them in advance."""
|
index.html
CHANGED
|
@@ -23,6 +23,11 @@
|
|
| 23 |
|
| 24 |
header{position:relative;background:linear-gradient(135deg,#ffffff 0%,#eef6f4 55%,#e7f0fb 100%);
|
| 25 |
border:1px solid var(--line);border-radius:16px;padding:26px 28px;box-shadow:var(--shadow)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
#cd{margin:0 0 14px}
|
| 27 |
#cd .cdlab{display:block;font-size:10.5px;font-weight:700;letter-spacing:2px;
|
| 28 |
text-transform:uppercase;color:var(--muted);margin-bottom:2px}
|
|
@@ -375,6 +380,7 @@
|
|
| 375 |
<div id="seasonbar"></div>
|
| 376 |
<a class="xlink" id="xlink" href="https://huggingface.co/spaces/FINAL-Bench/open-materials-challenge" target="_blank" rel="noopener"></a>
|
| 377 |
<a class="xlink" id="xlink2" href="https://huggingface.co/spaces/FINAL-Bench/leadboard" target="_blank" rel="noopener"></a>
|
|
|
|
| 378 |
<div id="cd"><span class="cdlab" id="cdlab"></span><span class="cdval" id="cdval"> </span></div>
|
| 379 |
<p class="eyebrow">Open Discovery Challenge</p>
|
| 380 |
<h1 id="seasonhead">#1 <em data-i18n="topic">Malaria</em></h1>
|
|
@@ -1600,6 +1606,25 @@ function overlayHTML(e){
|
|
| 1600 |
+ rows + pred + smi + flag + `<div class="ofoot">${d.ov_foot}</div>`;
|
| 1601 |
}
|
| 1602 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
// ---------------------------------------------------------------- countdown
|
| 1604 |
// end of the closing day, UTC. A deadline that lands at a different instant depending on
|
| 1605 |
// the entrant's timezone is not one, and the prize hangs off this date.
|
|
@@ -1760,6 +1785,7 @@ async function load(){
|
|
| 1760 |
DATA = lb; ME = me; MODELS = md;
|
| 1761 |
if(DATA.season && DATA.season.closes) startCountdown(DATA.season.closes);
|
| 1762 |
renderAuth(); render(); renderModels(MODELS);
|
|
|
|
| 1763 |
}
|
| 1764 |
catch(e){ $("#rows").innerHTML = `<tr><td colspan="11" class="who">${t().fail}${e}</td></tr>`; }
|
| 1765 |
}
|
|
|
|
| 23 |
|
| 24 |
header{position:relative;background:linear-gradient(135deg,#ffffff 0%,#eef6f4 55%,#e7f0fb 100%);
|
| 25 |
border:1px solid var(--line);border-radius:16px;padding:26px 28px;box-shadow:var(--shadow)}
|
| 26 |
+
.ms{margin:0 0 14px;padding:11px 15px;border-radius:10px;font-size:13.5px;line-height:1.65;
|
| 27 |
+
background:linear-gradient(120deg,#f4fbf6 0%,#eef7fb 100%);border:1px solid #cfe6d8;color:#1f4a35}
|
| 28 |
+
.ms b{font-weight:800}
|
| 29 |
+
.ms .n{font-family:var(--m,monospace);font-weight:850;letter-spacing:-.03em}
|
| 30 |
+
|
| 31 |
#cd{margin:0 0 14px}
|
| 32 |
#cd .cdlab{display:block;font-size:10.5px;font-weight:700;letter-spacing:2px;
|
| 33 |
text-transform:uppercase;color:var(--muted);margin-bottom:2px}
|
|
|
|
| 380 |
<div id="seasonbar"></div>
|
| 381 |
<a class="xlink" id="xlink" href="https://huggingface.co/spaces/FINAL-Bench/open-materials-challenge" target="_blank" rel="noopener"></a>
|
| 382 |
<a class="xlink" id="xlink2" href="https://huggingface.co/spaces/FINAL-Bench/leadboard" target="_blank" rel="noopener"></a>
|
| 383 |
+
<div id="milestone" class="ms" hidden></div>
|
| 384 |
<div id="cd"><span class="cdlab" id="cdlab"></span><span class="cdval" id="cdval"> </span></div>
|
| 385 |
<p class="eyebrow">Open Discovery Challenge</p>
|
| 386 |
<h1 id="seasonhead">#1 <em data-i18n="topic">Malaria</em></h1>
|
|
|
|
| 1606 |
+ rows + pred + smi + flag + `<div class="ofoot">${d.ov_foot}</div>`;
|
| 1607 |
}
|
| 1608 |
|
| 1609 |
+
// ---------------------------------------------------------------- 이정표
|
| 1610 |
+
// 🔴 숫자를 문구에 박지 않는다. 제출은 시간당 수십 건씩 들어와서, 박아두면
|
| 1611 |
+
// 반나절 만에 낡은 값이 공개면에 남는다. /api/totals 를 읽어 그때그때 그린다.
|
| 1612 |
+
async function paintMilestone(){
|
| 1613 |
+
const el = document.getElementById("milestone");
|
| 1614 |
+
if (!el) return;
|
| 1615 |
+
let d; try{ d = await j("/api/totals"); }catch(e){ return; }
|
| 1616 |
+
if (!d || !d.submissions || d.submissions < 10000) return; // 못 넘었으면 띄우지 않는다
|
| 1617 |
+
const n = d.submissions.toLocaleString();
|
| 1618 |
+
const p = (d.participants||0).toLocaleString();
|
| 1619 |
+
const days = d.days;
|
| 1620 |
+
el.innerHTML = (LANG==="ko")
|
| 1621 |
+
? `🎉 <b>후보물질 <span class="n">${n}</span>건을 넘었습니다.</b> 개막 ${days}일, 참가자 ${p}명이 낸 것입니다.
|
| 1622 |
+
서로 알지도 못하는 분들이 같은 기준으로 겨뤄 만든 숫자입니다. 고맙습니다.`
|
| 1623 |
+
: `🎉 <b>Past <span class="n">${n}</span> candidate molecules.</b> ${days} days in, from ${p} participants —
|
| 1624 |
+
people who mostly do not know each other, measured on one rubric. Thank you.`;
|
| 1625 |
+
el.hidden = false;
|
| 1626 |
+
}
|
| 1627 |
+
|
| 1628 |
// ---------------------------------------------------------------- countdown
|
| 1629 |
// end of the closing day, UTC. A deadline that lands at a different instant depending on
|
| 1630 |
// the entrant's timezone is not one, and the prize hangs off this date.
|
|
|
|
| 1785 |
DATA = lb; ME = me; MODELS = md;
|
| 1786 |
if(DATA.season && DATA.season.closes) startCountdown(DATA.season.closes);
|
| 1787 |
renderAuth(); render(); renderModels(MODELS);
|
| 1788 |
+
paintMilestone(); // 실패해도 본문은 그려져야 하므로 await 하지 않는다
|
| 1789 |
}
|
| 1790 |
catch(e){ $("#rows").innerHTML = `<tr><td colspan="11" class="who">${t().fail}${e}</td></tr>`; }
|
| 1791 |
}
|