관문 거부와 미산출을 갈라 표시 — 거부 45건이 '심사 대기'로 보이던 것 수정
Browse files- app.py +18 -86
- index.html +8 -33
app.py
CHANGED
|
@@ -82,61 +82,18 @@ def public_id(key):
|
|
| 82 |
return "OMC-" + hashlib.sha256((SALT + key).encode()).hexdigest()[:10].upper()
|
| 83 |
|
| 84 |
|
| 85 |
-
# 공개 표에 나가도 되는 것만 적는다.
|
| 86 |
-
# **차단목록이 아니라 허용목록이다.** 기록에 새 필드가 늘어도 여기 적지 않는 한
|
| 87 |
-
# 밖으로 나가지 않는다. 반대로 하면 필드가 늘 때마다 누군가 지우는 것을 기억해야 하고,
|
| 88 |
-
# 한 번 잊으면 그대로 새어 나간다.
|
| 89 |
-
_SAFE_KEYS = (
|
| 90 |
-
"candidate_id", "display_name", "hf_user", "model_name", "visibility",
|
| 91 |
-
"submitted_at", "season", "scored_at", "status", "gate", "hold_kind",
|
| 92 |
-
"axes", "detail", "notes", "note", "total", "total_max", "held_axes",
|
| 93 |
-
"known_in_mp", "is_anchor", "tier", "rank", "div_rank", "division",
|
| 94 |
-
"track", "track_rank",
|
| 95 |
-
)
|
| 96 |
-
|
| 97 |
-
# 공개를 고른 제출에만 함께 내보낸다. 비공개면 이 목록은 통째로 빠진다.
|
| 98 |
-
_PUBLIC_ONLY = ("composition", "measured", "n_atoms", "structure", "rationale")
|
| 99 |
-
|
| 100 |
-
# 두 번째 그물. 허용목록이 이미 막고 있지만, 나중에 누가 다른 경로로 항목을 만들어도
|
| 101 |
-
# 응답 직전에 한 번 더 걷어낸다. 한 겹이 뚫려도 다른 한 겹이 남는다.
|
| 102 |
-
_MASK_DROP = _PUBLIC_ONLY
|
| 103 |
-
|
| 104 |
-
|
| 105 |
def mask(rec):
|
| 106 |
-
"""공개
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
조성을 그대로 남기면 안 되는 것은 물론이고, 계산값(생성에너지 등)도 남기면 안 된다 -
|
| 112 |
-
원소 집합과 함께 두면 후보 조성을 훑어 맞출 수 있어 사실상 조성을 알려주는 것과 같다.
|
| 113 |
-
"""
|
| 114 |
-
pub = rec.get("visibility") == "public"
|
| 115 |
-
comp = rec.get("composition") or {}
|
| 116 |
-
out = {k: rec.get(k) for k in _SAFE_KEYS if k in rec}
|
| 117 |
-
# 구역은 조성에서 뽑아 두고, 조성 자체는 내보내지 않는다.
|
| 118 |
-
out["division"] = division_of(comp)
|
| 119 |
-
if pub:
|
| 120 |
-
out["formula"] = rec.get("formula")
|
| 121 |
-
for k in _PUBLIC_ONLY:
|
| 122 |
-
if k in rec:
|
| 123 |
-
out[k] = rec[k]
|
| 124 |
-
out["masked"] = False
|
| 125 |
-
else:
|
| 126 |
out["formula"] = "·".join(sorted(comp)) if comp else "비공개"
|
| 127 |
out["masked"] = True
|
|
|
|
| 128 |
return out
|
| 129 |
|
| 130 |
|
| 131 |
-
def strip_masked(entries):
|
| 132 |
-
"""마스킹된 항목에서 조성을 되돌릴 수 있는 필드를 전부 없앤다."""
|
| 133 |
-
for e in entries:
|
| 134 |
-
if e.get("masked"):
|
| 135 |
-
for k in _MASK_DROP:
|
| 136 |
-
e.pop(k, None)
|
| 137 |
-
return entries
|
| 138 |
-
|
| 139 |
-
|
| 140 |
# ---------------------------------------------------------------- 세션
|
| 141 |
def _sign(payload):
|
| 142 |
raw = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
|
|
@@ -318,27 +275,10 @@ def _anchors(season):
|
|
| 318 |
"tier": a.get("tier", 1),
|
| 319 |
"axes": {k: v["points"] for k, v in (a.get("axes") or {}).items()},
|
| 320 |
"detail": {k: v["detail"] for k, v in (a.get("axes") or {}).items()},
|
| 321 |
-
"division": _division_from_formula(a.get("formula", "")),
|
| 322 |
"note": a.get("note", "")})
|
| 323 |
return out
|
| 324 |
|
| 325 |
|
| 326 |
-
def _division_from_formula(f):
|
| 327 |
-
"""기준물질에는 조성 사전이 없고 화학식 문자열만 있다.
|
| 328 |
-
|
| 329 |
-
직접 파싱하지 않는다 - Li10Ge(PS6)2 같은 괄호 표기에서 순진한 정규식은 P1S6 을
|
| 330 |
-
내놓는다. 그러면 이 물질이 황화물이 아닌 것으로 분류된다.
|
| 331 |
-
"""
|
| 332 |
-
if not f:
|
| 333 |
-
return "other"
|
| 334 |
-
try:
|
| 335 |
-
from pymatgen.core import Composition
|
| 336 |
-
return division_of({str(el): n for el, n
|
| 337 |
-
in Composition(f).get_el_amt_dict().items()})
|
| 338 |
-
except Exception:
|
| 339 |
-
return "other"
|
| 340 |
-
|
| 341 |
-
|
| 342 |
# 음이온 골격이 물질의 성질을 크게 가른다. 같은 계열끼리 견주어야 후보의 위치가 읽힌다.
|
| 343 |
# 부문을 나누되 **점수는 그대로 둔다** - 어떤 제출도 무효가 되지 않는다.
|
| 344 |
_ANION_ORDER = ["F", "Cl", "Br", "I", "S", "Se", "O", "N", "P", "H"]
|
|
@@ -378,20 +318,15 @@ def _leaderboard_body(season):
|
|
| 378 |
e.get("submitted_at") or 0))
|
| 379 |
n = 0
|
| 380 |
div_n = {}
|
| 381 |
-
trk_n = {}
|
| 382 |
for e in merged:
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
if not e.get("division"):
|
| 386 |
-
e["division"] = division_of(e.get("composition") or {})
|
| 387 |
# 등수는 **전 축이 채워진 것**에만 준다. 축이 하나라도 비면 만점이 달라 총점을
|
| 388 |
# 나란히 세울 수 없다 - 점수는 남기되 순위에는 넣지 않는다.
|
| 389 |
unscored = e.get("total") is None or bool(e.get("held_axes"))
|
| 390 |
if e.get("is_anchor") or e.get("tier", 1) != 1 or unscored:
|
| 391 |
e["rank"] = None # 기준물질은 척도를 표시할 뿐 등수를 갖지 않는다
|
| 392 |
e["div_rank"] = None
|
| 393 |
-
e["track"] = None if e.get("is_anchor") else ("mp" if e.get("known_in_mp") else "novel")
|
| 394 |
-
e["track_rank"] = None
|
| 395 |
else:
|
| 396 |
n += 1
|
| 397 |
e["rank"] = n
|
|
@@ -399,27 +334,24 @@ def _leaderboard_body(season):
|
|
| 399 |
d = e["division"]
|
| 400 |
div_n[d] = div_n.get(d, 0) + 1
|
| 401 |
e["div_rank"] = div_n[d]
|
| 402 |
-
# **트랙 순위.** 같은 조성을 두 경로로 채점해 재보니 신규 조성 쪽이
|
| 403 |
-
# 계통적으로 손해였다(32건 중 28건, 평균 1~2점, 최대 3.5점). MP 에 값이
|
| 404 |
-
# 있으면 그 값을 쓰고 없으면 우리가 계산하는데, 두 자의 눈금이 다르다.
|
| 405 |
-
# 눈금이 다른 것을 한 줄로 세우면 "새로운 조성일수록 불리한" 순위표가 된다 -
|
| 406 |
-
# 신규성에 배점을 걸어 놓고 그러면 그 배점이 거짓말이 된다.
|
| 407 |
-
tr = "mp" if e.get("known_in_mp") else "novel"
|
| 408 |
-
e["track"] = tr
|
| 409 |
-
trk_n[tr] = trk_n.get(tr, 0) + 1
|
| 410 |
-
e["track_rank"] = trk_n[tr]
|
| 411 |
tally = {}
|
| 412 |
for e in merged:
|
| 413 |
if e.get("is_anchor"):
|
| 414 |
continue
|
| 415 |
tally[e["division"]] = tally.get(e["division"], 0) + 1
|
| 416 |
-
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
and (e.get("total") is None or e.get("held_axes")))
|
| 419 |
-
strip_masked(merged) # 구역까지 매긴 뒤, 응답으로 나가기 직전에 걷어낸다
|
| 420 |
return {"season": seasons.public(season), "entries": merged,
|
| 421 |
"divisions": tally,
|
| 422 |
-
"counts": {"scored": len(
|
|
|
|
| 423 |
"anchors": len(anchors)}}
|
| 424 |
|
| 425 |
|
|
|
|
| 82 |
return "OMC-" + hashlib.sha256((SALT + key).encode()).hexdigest()[:10].upper()
|
| 83 |
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
def mask(rec):
|
| 86 |
+
"""비공개 제출에서 조성을 가린다. 원소 종류까지는 보여준다 - 그것만으로는
|
| 87 |
+
조성을 되돌릴 수 없고, 어떤 화학계가 상위인지는 대회의 공개 지식이어야 한다."""
|
| 88 |
+
out = dict(rec)
|
| 89 |
+
if rec.get("visibility") != "public":
|
| 90 |
+
comp = rec.get("composition") or {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
out["formula"] = "·".join(sorted(comp)) if comp else "비공개"
|
| 92 |
out["masked"] = True
|
| 93 |
+
out.pop("structure", None)
|
| 94 |
return out
|
| 95 |
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
# ---------------------------------------------------------------- 세션
|
| 98 |
def _sign(payload):
|
| 99 |
raw = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip("=")
|
|
|
|
| 275 |
"tier": a.get("tier", 1),
|
| 276 |
"axes": {k: v["points"] for k, v in (a.get("axes") or {}).items()},
|
| 277 |
"detail": {k: v["detail"] for k, v in (a.get("axes") or {}).items()},
|
|
|
|
| 278 |
"note": a.get("note", "")})
|
| 279 |
return out
|
| 280 |
|
| 281 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
# 음이온 골격이 물질의 성질을 크게 가른다. 같은 계열끼리 견주어야 후보의 위치가 읽힌다.
|
| 283 |
# 부문을 나누되 **점수는 그대로 둔다** - 어떤 제출도 무효가 되지 않는다.
|
| 284 |
_ANION_ORDER = ["F", "Cl", "Br", "I", "S", "Se", "O", "N", "P", "H"]
|
|
|
|
| 318 |
e.get("submitted_at") or 0))
|
| 319 |
n = 0
|
| 320 |
div_n = {}
|
|
|
|
| 321 |
for e in merged:
|
| 322 |
+
comp = e.get("composition") or {}
|
| 323 |
+
e["division"] = division_of(comp)
|
|
|
|
|
|
|
| 324 |
# 등수는 **전 축이 채워진 것**에만 준다. 축이 하나라도 비면 만점이 달라 총점을
|
| 325 |
# 나란히 세울 수 없다 - 점수는 남기되 순위에는 넣지 않는다.
|
| 326 |
unscored = e.get("total") is None or bool(e.get("held_axes"))
|
| 327 |
if e.get("is_anchor") or e.get("tier", 1) != 1 or unscored:
|
| 328 |
e["rank"] = None # 기준물질은 척도를 표시할 뿐 등수를 갖지 않는다
|
| 329 |
e["div_rank"] = None
|
|
|
|
|
|
|
| 330 |
else:
|
| 331 |
n += 1
|
| 332 |
e["rank"] = n
|
|
|
|
| 334 |
d = e["division"]
|
| 335 |
div_n[d] = div_n.get(d, 0) + 1
|
| 336 |
e["div_rank"] = div_n[d]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
tally = {}
|
| 338 |
for e in merged:
|
| 339 |
if e.get("is_anchor"):
|
| 340 |
continue
|
| 341 |
tally[e["division"]] = tally.get(e["division"], 0) + 1
|
| 342 |
+
# 🔴 거부와 보류를 한 칸에 합치지 않는다.
|
| 343 |
+
# 합쳐 놓으면 관문에서 **탈락한 것**이 "심사 대기 중"으로 읽힌다.
|
| 344 |
+
# 실측(2026-08-30): 거부 45 · 보류 2 인데 요약은 held 47 로 나갔다.
|
| 345 |
+
# 개별 항목에는 status 가 제대로 실려 있었으므로, 틀린 것은 요약뿐이다.
|
| 346 |
+
real = [e for e in merged if not e.get("is_anchor")]
|
| 347 |
+
n_rejected = sum(1 for e in real if e.get("status") == "rejected")
|
| 348 |
+
n_held = sum(1 for e in real
|
| 349 |
+
if e.get("status") != "rejected"
|
| 350 |
and (e.get("total") is None or e.get("held_axes")))
|
|
|
|
| 351 |
return {"season": seasons.public(season), "entries": merged,
|
| 352 |
"divisions": tally,
|
| 353 |
+
"counts": {"scored": len(real) - n_held - n_rejected,
|
| 354 |
+
"rejected": n_rejected, "held": n_held,
|
| 355 |
"anchors": len(anchors)}}
|
| 356 |
|
| 357 |
|
index.html
CHANGED
|
@@ -167,7 +167,6 @@
|
|
| 167 |
<div id="seasonbar" style="margin:14px 0 0"></div>
|
| 168 |
<div class="sinfo" id="sinfo"></div>
|
| 169 |
<a class="xlink" id="xlink" href="https://huggingface.co/spaces/FINAL-Bench/open-discovery-challenge" target="_blank" rel="noopener"></a>
|
| 170 |
-
<a class="xlink" id="xlink2" href="https://huggingface.co/spaces/FINAL-Bench/leadboard" target="_blank" rel="noopener"></a>
|
| 171 |
<div id="tabbar"></div>
|
| 172 |
|
| 173 |
<section data-tab="board">
|
|
@@ -206,7 +205,6 @@
|
|
| 206 |
<div class="card">
|
| 207 |
<h2><span id="t_board_h"></span><span class="cnt" id="counts"></span></h2>
|
| 208 |
<div id="divbar"></div>
|
| 209 |
-
<div id="trackbar"></div>
|
| 210 |
<div class="scroll">
|
| 211 |
<table>
|
| 212 |
<thead><tr id="head"></tr></thead>
|
|
@@ -286,13 +284,10 @@ const T = {
|
|
| 286 |
mcounts:(m,n)=>`모델 ${m} · 제출 ${n}`,
|
| 287 |
mlegend:`<span><i></i>최고 점수</span><span><u></u>평균</span><span>제출 수</span>`,
|
| 288 |
tabs:{board:"대회 · 순위표", guide:"참가 가이드", models:"모델별 성과"},
|
| 289 |
-
tracks:{all:"전체", mp:"MP 수록 (MP 값으로 채점)", novel:"신규 조성 (직접 계산)"},
|
| 290 |
-
tracknote:"MP 에 값이 있으면 그 값을, 없으면 저희가 직접 계산합니다. 두 자의 눈금이 달라 (같은 조성 40건 실측, 직접 계산 쪽이 88%에서 평균 1.0점 낮음) 순위를 따로 매깁니다.",
|
| 291 |
divs:{all:"전체", fluoride:"불화물", halide:"염화·브롬화·요오드화", sulfide:"황화물",
|
| 292 |
oxide:"산화물", nitride:"질화물", hydride:"수소화물", other:"기타"},
|
| 293 |
divnote:"음이온 골격에 따라 부문을 나눴습니다. <b>점수는 부문과 무관하게 동일한 기준</b>으로 매겨지며, 부문은 같은 화학 계열 안에서 비교하시라고 둔 것입니다.",
|
| 294 |
xlink:`🧪 <b>Open Discovery Challenge</b> <em>— 신약 후보 물질을 찾는 자매 대회</em> ↗`,
|
| 295 |
-
xlink2:`🎯 <b>LEADBOARD</b> <em>— 신약 예측 도구를 분야별로 재는 시험대</em> ↗`,
|
| 296 |
sinfo:(o,c)=>[`시즌 기간 <b>${o} ~ ${c}</b>`,`상금 <b>없음</b>`,`제출 <b>조성 하나</b>`],
|
| 297 |
axhelp:[
|
| 298 |
["산화 안정성","충전 전압이 높아졌을 때 전해질 자신이 분해되지 않고 버티는 정도."],
|
|
@@ -327,7 +322,7 @@ const T = {
|
|
| 327 |
제출자의 신원이나 연락처는 <b>본인 동의 없이 전달하지 않습니다.</b></p>`,
|
| 328 |
cols:{rank:"#",cand:"후보",user:"제출자",who:"모델",date:"제출일",
|
| 329 |
oxidation:"산화",li_stability:"Li안정",migration:"이동",novelty:"신규",total:"총점"},
|
| 330 |
-
counts:(a,b,h)=>`채점 ${a} · 기준물질 ${b}` + (h ? ` · 미산출 ${h}` : ""),
|
| 331 |
prange:(a,b,n)=>`${a.toLocaleString()}–${b.toLocaleString()} / 전체 ${n.toLocaleString()}`,
|
| 332 |
empty:"아직 채점된 제출이 없습니다.",
|
| 333 |
fail:"순위표를 불러오지 못했습니다: ",
|
|
@@ -370,13 +365,10 @@ const T = {
|
|
| 370 |
mcounts:(m,n)=>`${m} models · ${n} entries`,
|
| 371 |
mlegend:`<span><i></i>Best score</span><span><u></u>Mean</span><span>Entries</span>`,
|
| 372 |
tabs:{board:"Challenge · Board", guide:"How to take part", models:"By model"},
|
| 373 |
-
tracks:{all:"All", mp:"In MP (scored on MP values)", novel:"Novel (computed here)"},
|
| 374 |
-
tracknote:"Where Materials Project has a value we use it; where it does not, we compute one. The two rulers disagree (40 identical compositions measured; the computed path scores 1.0 point lower on 88% of them), so the two are ranked separately.",
|
| 375 |
divs:{all:"All", fluoride:"Fluoride", halide:"Chloride / bromide / iodide", sulfide:"Sulfide",
|
| 376 |
oxide:"Oxide", nitride:"Nitride", hydride:"Hydride", other:"Other"},
|
| 377 |
divnote:"Divisions follow the anion framework. <b>Scores are identical across divisions</b> - the split is there so you can compare within a chemical family.",
|
| 378 |
xlink:`🧪 <b>Open Discovery Challenge</b> <em>— the sister challenge, for drug candidates</em> ↗`,
|
| 379 |
-
xlink2:`🎯 <b>LEADBOARD</b> <em>— the benchmark for drug prediction tools</em> ↗`,
|
| 380 |
sinfo:(o,c)=>[`Season <b>${o} – ${c}</b>`,`Prize <b>none</b>`,`Submit <b>one composition</b>`],
|
| 381 |
axhelp:[
|
| 382 |
["Oxidation","How well the electrolyte itself resists decomposing as the cell voltage rises."],
|
|
@@ -414,7 +406,7 @@ const T = {
|
|
| 414 |
submitter's identity or contact details <b>without their consent.</b></p>`,
|
| 415 |
cols:{rank:"#",cand:"Entry",user:"Submitter",who:"Model",date:"Date",
|
| 416 |
oxidation:"Oxid.",li_stability:"Li-stab.",migration:"Migration",novelty:"Novelty",total:"Total"},
|
| 417 |
-
counts:(a,b,h)=>`${a} scored · ${b} references` + (h ? ` · ${h} not scored` : ""),
|
| 418 |
prange:(a,b,n)=>`${a.toLocaleString()}–${b.toLocaleString()} of ${n.toLocaleString()}`,
|
| 419 |
empty:"No entries have been scored yet.",
|
| 420 |
fail:"Could not load the leaderboard: ",
|
|
@@ -526,7 +518,7 @@ const COLS = ["rank","cand","user","who","date","oxidation","li_stability","nove
|
|
| 526 |
// 표가 커지면 한꺼번에 ��리지 않고 한 페이지씩 그린다.
|
| 527 |
const PAGE_SIZE = 500;
|
| 528 |
let LANG = "ko", DATA = null, SEASONS = [], SEASON = 1;
|
| 529 |
-
let sortKey = "total", sortDir = -1, page = 0, DIV = "all"
|
| 530 |
const t = () => T[LANG];
|
| 531 |
|
| 532 |
let ME = null;
|
|
@@ -572,7 +564,6 @@ function setLang(l){
|
|
| 572 |
$("#t_broker").innerHTML = d.broker;
|
| 573 |
$("#t_cifnote").innerHTML = d.cifnote;
|
| 574 |
$("#xlink").innerHTML = d.xlink;
|
| 575 |
-
{ const x2 = document.querySelector("#xlink2"); if(x2 && d.xlink2) x2.innerHTML = d.xlink2; }
|
| 576 |
renderAuth();
|
| 577 |
$("#t_guide_h").textContent = d.guide_h;
|
| 578 |
$("#t_guide_lead").innerHTML = d.guide_lead;
|
|
@@ -718,21 +709,6 @@ function renderDivBar(){
|
|
| 718 |
});
|
| 719 |
}
|
| 720 |
|
| 721 |
-
function renderTrackBar(){
|
| 722 |
-
const d = t(), host = $("#trackbar");
|
| 723 |
-
if(!host) return;
|
| 724 |
-
const tally = {all:0, mp:0, novel:0};
|
| 725 |
-
(DATA.entries||[]).forEach(e=>{ if(e.is_anchor) return; tally.all++;
|
| 726 |
-
if(e.track==="mp") tally.mp++; else if(e.track==="novel") tally.novel++; });
|
| 727 |
-
if(!tally.mp && !tally.novel){ host.innerHTML=""; return; }
|
| 728 |
-
const btn=(k,n)=>`<button data-k="${k}" class="${k===TRK?"on":""}">${esc(d.tracks[k]||k)}<span class="dn">${n}</span></button>`;
|
| 729 |
-
host.innerHTML = btn("all",tally.all)+btn("mp",tally.mp)+btn("novel",tally.novel)
|
| 730 |
-
+ `<div class="sub" style="margin-top:6px">${esc(d.tracknote||"")}</div>`;
|
| 731 |
-
host.querySelectorAll("button[data-k]").forEach(b=>b.onclick=()=>{
|
| 732 |
-
TRK=b.dataset.k; page=0; render();
|
| 733 |
-
});
|
| 734 |
-
}
|
| 735 |
-
|
| 736 |
function renderPager(total,pages){
|
| 737 |
const host = $("#pager"), d = t();
|
| 738 |
if(pages<=1){ host.innerHTML=""; return; }
|
|
@@ -763,7 +739,9 @@ function render(){
|
|
| 763 |
const d = t(), W = (DATA.season && DATA.season.weights) || {};
|
| 764 |
$("#wts").innerHTML = Object.entries(W)
|
| 765 |
.map(([k,v])=>`<span>${esc(d.cols[k]||k)} <b>${v}</b></span>`).join("");
|
| 766 |
-
|
|
|
|
|
|
|
| 767 |
|
| 768 |
$("#head").innerHTML = COLS.map(k=>{
|
| 769 |
const on = sortKey===k, ar = on ? (sortDir<0?"▼":"▲") : "▾";
|
|
@@ -777,12 +755,9 @@ function render(){
|
|
| 777 |
});
|
| 778 |
|
| 779 |
renderDivBar();
|
| 780 |
-
renderTrackBar();
|
| 781 |
// 부문을 고르면 그 부문 + 기준물질만 남긴다. 기준물질은 척도라 항상 보여야 한다.
|
| 782 |
-
|
| 783 |
: DATA.entries.filter(e=>e.is_anchor || e.division===DIV);
|
| 784 |
-
// 기준물질은 척도라 어느 트랙을 골라도 남긴다.
|
| 785 |
-
if(TRK!=="all") pool = pool.filter(e=>e.is_anchor || e.track===TRK);
|
| 786 |
const all = pool.slice().sort((a,b)=>{
|
| 787 |
const x=sortVal(a,sortKey), y=sortVal(b,sortKey);
|
| 788 |
if(x<y) return -sortDir; if(x>y) return sortDir; return 0;
|
|
@@ -812,7 +787,7 @@ function render(){
|
|
| 812 |
+ (unscored ? ` <span class="pill warn">${esc(hk || d.hold)}</span>` : "");
|
| 813 |
const has = (e.detail && Object.keys(e.detail).length) || (unscored && e.gate);
|
| 814 |
return `<tr class="${cls}${has?" pick":""}"${has?` data-i="${e.candidate_id}"`:""}>
|
| 815 |
-
<td class="num">${(
|
| 816 |
<td>${esc(e.formula||e.candidate_id||"")}${e.masked?' <span class="pill">🔒</span>':""}</td>
|
| 817 |
<td>${who}</td><td>${esc(e.model_name||"")}</td><td>${dt}</td>
|
| 818 |
${num("oxidation")}${num("li_stability")}${num("novelty")}
|
|
|
|
| 167 |
<div id="seasonbar" style="margin:14px 0 0"></div>
|
| 168 |
<div class="sinfo" id="sinfo"></div>
|
| 169 |
<a class="xlink" id="xlink" href="https://huggingface.co/spaces/FINAL-Bench/open-discovery-challenge" target="_blank" rel="noopener"></a>
|
|
|
|
| 170 |
<div id="tabbar"></div>
|
| 171 |
|
| 172 |
<section data-tab="board">
|
|
|
|
| 205 |
<div class="card">
|
| 206 |
<h2><span id="t_board_h"></span><span class="cnt" id="counts"></span></h2>
|
| 207 |
<div id="divbar"></div>
|
|
|
|
| 208 |
<div class="scroll">
|
| 209 |
<table>
|
| 210 |
<thead><tr id="head"></tr></thead>
|
|
|
|
| 284 |
mcounts:(m,n)=>`모델 ${m} · 제출 ${n}`,
|
| 285 |
mlegend:`<span><i></i>최고 점수</span><span><u></u>평균</span><span>제출 수</span>`,
|
| 286 |
tabs:{board:"대회 · 순위표", guide:"참가 가이드", models:"모델별 성과"},
|
|
|
|
|
|
|
| 287 |
divs:{all:"전체", fluoride:"불화물", halide:"염화·브롬화·요오드화", sulfide:"황화물",
|
| 288 |
oxide:"산화물", nitride:"질화물", hydride:"수소화물", other:"기타"},
|
| 289 |
divnote:"음이온 골격에 따라 부문을 나눴습니다. <b>점수는 부문과 무관하게 동일한 기준</b>으로 매겨지며, 부문은 같은 화학 계열 안에서 비교하시라고 둔 것입니다.",
|
| 290 |
xlink:`🧪 <b>Open Discovery Challenge</b> <em>— 신약 후보 물질을 찾는 자매 대회</em> ↗`,
|
|
|
|
| 291 |
sinfo:(o,c)=>[`시즌 기간 <b>${o} ~ ${c}</b>`,`상금 <b>없음</b>`,`제출 <b>조성 하나</b>`],
|
| 292 |
axhelp:[
|
| 293 |
["산화 안정성","충전 전압이 높아졌을 때 전해질 자신이 분해되지 않고 버티는 정도."],
|
|
|
|
| 322 |
제출자의 신원이나 연락처는 <b>본인 동의 없이 전달하지 않습니다.</b></p>`,
|
| 323 |
cols:{rank:"#",cand:"후보",user:"제출자",who:"모델",date:"제출일",
|
| 324 |
oxidation:"산화",li_stability:"Li안정",migration:"이동",novelty:"신규",total:"총점"},
|
| 325 |
+
counts:(a,b,h,r)=>`채점 ${a} · 기준물질 ${b}` + (r ? ` · 관문 거부 ${r}` : "") + (h ? ` · 미산출 ${h}` : ""),
|
| 326 |
prange:(a,b,n)=>`${a.toLocaleString()}–${b.toLocaleString()} / 전체 ${n.toLocaleString()}`,
|
| 327 |
empty:"아직 채점된 제출이 없습니다.",
|
| 328 |
fail:"순위표를 불러오지 못했습니다: ",
|
|
|
|
| 365 |
mcounts:(m,n)=>`${m} models · ${n} entries`,
|
| 366 |
mlegend:`<span><i></i>Best score</span><span><u></u>Mean</span><span>Entries</span>`,
|
| 367 |
tabs:{board:"Challenge · Board", guide:"How to take part", models:"By model"},
|
|
|
|
|
|
|
| 368 |
divs:{all:"All", fluoride:"Fluoride", halide:"Chloride / bromide / iodide", sulfide:"Sulfide",
|
| 369 |
oxide:"Oxide", nitride:"Nitride", hydride:"Hydride", other:"Other"},
|
| 370 |
divnote:"Divisions follow the anion framework. <b>Scores are identical across divisions</b> - the split is there so you can compare within a chemical family.",
|
| 371 |
xlink:`🧪 <b>Open Discovery Challenge</b> <em>— the sister challenge, for drug candidates</em> ↗`,
|
|
|
|
| 372 |
sinfo:(o,c)=>[`Season <b>${o} – ${c}</b>`,`Prize <b>none</b>`,`Submit <b>one composition</b>`],
|
| 373 |
axhelp:[
|
| 374 |
["Oxidation","How well the electrolyte itself resists decomposing as the cell voltage rises."],
|
|
|
|
| 406 |
submitter's identity or contact details <b>without their consent.</b></p>`,
|
| 407 |
cols:{rank:"#",cand:"Entry",user:"Submitter",who:"Model",date:"Date",
|
| 408 |
oxidation:"Oxid.",li_stability:"Li-stab.",migration:"Migration",novelty:"Novelty",total:"Total"},
|
| 409 |
+
counts:(a,b,h,r)=>`${a} scored · ${b} references` + (r ? ` · ${r} rejected at the gate` : "") + (h ? ` · ${h} not scored` : ""),
|
| 410 |
prange:(a,b,n)=>`${a.toLocaleString()}–${b.toLocaleString()} of ${n.toLocaleString()}`,
|
| 411 |
empty:"No entries have been scored yet.",
|
| 412 |
fail:"Could not load the leaderboard: ",
|
|
|
|
| 518 |
// 표가 커지면 한꺼번에 ��리지 않고 한 페이지씩 그린다.
|
| 519 |
const PAGE_SIZE = 500;
|
| 520 |
let LANG = "ko", DATA = null, SEASONS = [], SEASON = 1;
|
| 521 |
+
let sortKey = "total", sortDir = -1, page = 0, DIV = "all";
|
| 522 |
const t = () => T[LANG];
|
| 523 |
|
| 524 |
let ME = null;
|
|
|
|
| 564 |
$("#t_broker").innerHTML = d.broker;
|
| 565 |
$("#t_cifnote").innerHTML = d.cifnote;
|
| 566 |
$("#xlink").innerHTML = d.xlink;
|
|
|
|
| 567 |
renderAuth();
|
| 568 |
$("#t_guide_h").textContent = d.guide_h;
|
| 569 |
$("#t_guide_lead").innerHTML = d.guide_lead;
|
|
|
|
| 709 |
});
|
| 710 |
}
|
| 711 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
function renderPager(total,pages){
|
| 713 |
const host = $("#pager"), d = t();
|
| 714 |
if(pages<=1){ host.innerHTML=""; return; }
|
|
|
|
| 739 |
const d = t(), W = (DATA.season && DATA.season.weights) || {};
|
| 740 |
$("#wts").innerHTML = Object.entries(W)
|
| 741 |
.map(([k,v])=>`<span>${esc(d.cols[k]||k)} <b>${v}</b></span>`).join("");
|
| 742 |
+
// 거부와 보류를 갈라 보인다. 합치면 탈락한 건이 "심사 대기"로 읽힌다.
|
| 743 |
+
$("#counts").textContent = d.counts(DATA.counts.scored, DATA.counts.anchors,
|
| 744 |
+
DATA.counts.held || 0, DATA.counts.rejected || 0);
|
| 745 |
|
| 746 |
$("#head").innerHTML = COLS.map(k=>{
|
| 747 |
const on = sortKey===k, ar = on ? (sortDir<0?"▼":"▲") : "▾";
|
|
|
|
| 755 |
});
|
| 756 |
|
| 757 |
renderDivBar();
|
|
|
|
| 758 |
// 부문을 고르면 그 부문 + 기준물질만 남긴다. 기준물질은 척도라 항상 보여야 한다.
|
| 759 |
+
const pool = DIV==="all" ? DATA.entries
|
| 760 |
: DATA.entries.filter(e=>e.is_anchor || e.division===DIV);
|
|
|
|
|
|
|
| 761 |
const all = pool.slice().sort((a,b)=>{
|
| 762 |
const x=sortVal(a,sortKey), y=sortVal(b,sortKey);
|
| 763 |
if(x<y) return -sortDir; if(x>y) return sortDir; return 0;
|
|
|
|
| 787 |
+ (unscored ? ` <span class="pill warn">${esc(hk || d.hold)}</span>` : "");
|
| 788 |
const has = (e.detail && Object.keys(e.detail).length) || (unscored && e.gate);
|
| 789 |
return `<tr class="${cls}${has?" pick":""}"${has?` data-i="${e.candidate_id}"`:""}>
|
| 790 |
+
<td class="num">${(DIV==="all" ? e.rank : e.div_rank) ?? "—"}</td>
|
| 791 |
<td>${esc(e.formula||e.candidate_id||"")}${e.masked?' <span class="pill">🔒</span>':""}</td>
|
| 792 |
<td>${who}</td><td>${esc(e.model_name||"")}</td><td>${dt}</td>
|
| 793 |
${num("oxidation")}${num("li_stability")}${num("novelty")}
|