SeaWolf-AI commited on
Commit
e517cb0
·
verified ·
1 Parent(s): 4a99b68

1만 건 돌파 이정표 배너 — 숫자는 /api/totals 에서 실시간으로 읽는다

Browse files
Files changed (2) hide show
  1. app.py +34 -0
  2. 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">&nbsp;</span></div>
379
  <p class="eyebrow">Open Discovery Challenge</p>
380
  <h1 id="seasonhead">#1 &nbsp;<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">&nbsp;</span></div>
385
  <p class="eyebrow">Open Discovery Challenge</p>
386
  <h1 id="seasonhead">#1 &nbsp;<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
  }