SeaWolf-AI commited on
Commit
27fdab1
ยท
verified ยท
1 Parent(s): cfca820

guide: source-specific starter code; rank and display by each board's own metric

Browse files
app.py CHANGED
@@ -124,10 +124,16 @@ def _load_boards():
124
  # ๋ชจ๋ธ ์˜ค์ฐจ๊ฐ€ ์‹คํ—˜ ์˜ค์ฐจ์˜ ๋ช‡ ๋ฐฐ์ธ๊ฐ€. 1 ์— ๋ถ™์„์ˆ˜๋ก ์ธก์ • ํ•œ๊ณ„๋‹ค.
125
  if best and nf.get("sd_single") and d["metric"] == "mae":
126
  d["error_over_noise"] = round(base[best]["mae"] / nf["sd_single"], 2)
127
- if ("์ƒ์ˆ˜ ์˜ˆ์ธก" in base and "Morgan+LightGBM" in base
128
- and base["Morgan+LightGBM"].get("mae") is not None
129
- and base["์ƒ์ˆ˜ ์˜ˆ์ธก"].get("mae") is not None):
130
- d["beats_constant"] = base["Morgan+LightGBM"]["mae"] < base["์ƒ์ˆ˜ ์˜ˆ์ธก"]["mae"]
 
 
 
 
 
 
131
  if d.get("n_test"):
132
  d["near_pct"] = round(100.0 * d.get("near_threshold", 0) / d["n_test"], 1)
133
  m = meta.get(d["board"]) or {}
@@ -138,6 +144,22 @@ def _load_boards():
138
  return out
139
 
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  def boards():
142
  return cached("boards", 300, _load_boards)
143
 
@@ -268,7 +290,12 @@ def api_leaders():
268
  top = ent[0] if ent else None
269
  nf = (b.get("noise_floor") or {}).get("sd_single")
270
  base = b.get("baselines") or {}
 
 
271
  row = {"board": name, "n_test": b.get("n_test"),
 
 
 
272
  "blurb": b.get("blurb"), "blurb_en": b.get("blurb_en"),
273
  "split_grade": b.get("split_grade"), "answer_grade": b.get("answer_grade"),
274
  "noise_floor": nf, "entries": len(ent),
@@ -279,6 +306,8 @@ def api_leaders():
279
  "leader": None}
280
  if top:
281
  row["leader"] = {"method": top.get("method"), "user": top.get("user"),
 
 
282
  "mae": top.get("mae"), "auroc": top.get("auroc"),
283
  "verified": bool(top.get("verified")), "leak": top.get("leak")}
284
  out.append(row)
 
124
  # ๋ชจ๋ธ ์˜ค์ฐจ๊ฐ€ ์‹คํ—˜ ์˜ค์ฐจ์˜ ๋ช‡ ๋ฐฐ์ธ๊ฐ€. 1 ์— ๋ถ™์„์ˆ˜๋ก ์ธก์ • ํ•œ๊ณ„๋‹ค.
125
  if best and nf.get("sd_single") and d["metric"] == "mae":
126
  d["error_over_noise"] = round(base[best]["mae"] / nf["sd_single"], 2)
127
+ # ํ•™์Šต ๊ธฐ์ค€์„ ์ด ์ƒ์ˆ˜ ์˜ˆ์ธก์„ ์ด๊ธฐ๋Š”๊ฐ€ - **๊ทธ ๋ถ€๋ฌธ์˜ ์ฃผ์ง€ํ‘œ ์œ„์—์„œ** ๋ณธ๋‹ค.
128
+ # MAE ๋กœ๋งŒ ๋ณด๋ฉด ์ƒ๊ด€์œผ๋กœ ์ฑ„์ ๋˜๋Š” ๋ถ€๋ฌธ์€ ๋œป ์—†๋Š” ๋น„๊ต๊ฐ€ ๋˜๊ณ , AUROC ์ „์šฉ
129
+ # ๋ถ€๋ฌธ์€ mae ๊ฐ€ None ์ด๋ผ ๋น„๊ต ์ž์ฒด๊ฐ€ ์—†์—ˆ๋‹ค.
130
+ mk, mhb = primary_metric(d)
131
+ tr = (base.get("Morgan+LightGBM") or {}).get(mk)
132
+ co = (base.get("์ƒ์ˆ˜ ์˜ˆ์ธก") or {}).get(mk)
133
+ if tr is not None and co is not None:
134
+ d["beats_constant"] = (tr > co) if mhb else (tr < co)
135
+ # ๊ฒฉ์ฐจ๋ฅผ ํ•จ๊ป˜ ์‹ฃ๋Š”๋‹ค. "์ด๊ฒผ๋‹ค/์กŒ๋‹ค"๋งŒ์œผ๋กœ๋Š” 0.003 ๊ณผ 0.27 ์ด ๊ฐ™์•„ ๋ณด์ธ๋‹ค.
136
+ d["beats_constant_margin"] = round((tr - co) if mhb else (co - tr), 4)
137
  if d.get("n_test"):
138
  d["near_pct"] = round(100.0 * d.get("near_threshold", 0) / d["n_test"], 1)
139
  m = meta.get(d["board"]) or {}
 
144
  return out
145
 
146
 
147
+ def primary_metric(b):
148
+ """์ด ๋ถ€๋ฌธ์˜ ์ฃผ์ง€ํ‘œ์™€ ๋ฐฉํ–ฅ. (key, higher_better)
149
+
150
+ ํ‘œํ˜„ํ˜•์€ ํ”„๋กœํŒŒ์ผ ์ƒ๊ด€, ๋ถ„๋ฅ˜ ์ „์šฉ์€ AUROC, ๋‚˜๋จธ์ง€๋Š” MAE ๋‹ค. ํŒ์ •์„ ์—ฌ๊ธฐ ํ•œ ๊ณณ์—
151
+ ๋‘๋Š” ์ด์œ ๋Š” ์ฑ„์ ๊ธฐ์™€ ํ™”๋ฉด์ด ์„œ๋กœ ๋‹ค๋ฅธ ์ง€ํ‘œ๋ฅผ ๋ณด๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค - Phenotype ๋Š”
152
+ ์ƒ๊ด€์œผ๋กœ ์ฑ„์ ๋˜๋Š”๋ฐ ํ™”๋ฉด์€ MAE ๋งŒ ๋„์›Œ์„œ, ํ•™์Šต ๊ธฐ์ค€์„ ์ด ์ƒ์ˆ˜ ์˜ˆ์ธก์„ 0.003 ์ฐจ์ด๋กœ
153
+ ๊ฐ„์‹ ํžˆ ์ด๊ธฐ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์˜€๋‹ค(์‹ค์ œ ์ƒ๊ด€์œผ๋กœ๋Š” 0.092 ๋Œ€ 0.159, ์žก์Œ ๋ฐ”๋‹ฅ์˜ 7.6๋ฐฐ).
154
+ """
155
+ if (b or {}).get("metric") == "profile_corr":
156
+ return "profile_corr", True
157
+ bl = (b or {}).get("baselines") or {}
158
+ if bl and all(v.get("mae") is None for v in bl.values()) and any(v.get("auroc") is not None for v in bl.values()):
159
+ return "auroc", True
160
+ return "mae", False
161
+
162
+
163
  def boards():
164
  return cached("boards", 300, _load_boards)
165
 
 
290
  top = ent[0] if ent else None
291
  nf = (b.get("noise_floor") or {}).get("sd_single")
292
  base = b.get("baselines") or {}
293
+ mkey, mhb = primary_metric(b)
294
+ vals = [v.get(mkey) for v in base.values() if v.get(mkey) is not None]
295
  row = {"board": name, "n_test": b.get("n_test"),
296
+ "metric": mkey, "higher_better": mhb,
297
+ # ์ฃผ์ง€ํ‘œ ์œ„์—์„œ์˜ ์ตœ์„  ๊ธฐ์ค€์„ . ๋„˜์–ด์•ผ ํ•  ์„ ์€ ํ•˜๋‚˜๋ฟ์ด๋‹ค.
298
+ "baseline_best_score": (max(vals) if mhb else min(vals)) if vals else None,
299
  "blurb": b.get("blurb"), "blurb_en": b.get("blurb_en"),
300
  "split_grade": b.get("split_grade"), "answer_grade": b.get("answer_grade"),
301
  "noise_floor": nf, "entries": len(ent),
 
306
  "leader": None}
307
  if top:
308
  row["leader"] = {"method": top.get("method"), "user": top.get("user"),
309
+ # score ๋Š” ๊ทธ ๋ถ€๋ฌธ ์ฃผ์ง€ํ‘œ ์œ„์˜ ๊ฐ’์ด๋‹ค. mae ๋Š” ์˜› ํ‘œ์‹œ๋ถ€ ํ˜ธํ™˜.
310
+ "score": top.get("score", top.get("mae")),
311
  "mae": top.get("mae"), "auroc": top.get("auroc"),
312
  "verified": bool(top.get("verified")), "leak": top.get("leak")}
313
  out.append(row)
data/caco2_card.json CHANGED
@@ -42,5 +42,17 @@
42
  "axis": "A",
43
  "desc_ko": "์žฅ ์ƒํ”ผ ํˆฌ๊ณผ๋„",
44
  "desc_en": "Intestinal permeability",
45
- "built": "2026-08-22"
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
 
42
  "axis": "A",
43
  "desc_ko": "์žฅ ์ƒํ”ผ ํˆฌ๊ณผ๋„",
44
  "desc_en": "Intestinal permeability",
45
+ "built": "2026-08-22",
46
+ "stype": "Papp",
47
+ "units": {
48
+ "10^-6 cm/s": 1.0,
49
+ "ucm/s": 1.0,
50
+ "cm s-1": 1000000.0,
51
+ "nm/s": 0.0001
52
+ },
53
+ "log": true,
54
+ "clip": [
55
+ -4.0,
56
+ 4.0
57
+ ]
58
  }
data/halflife_card.json CHANGED
@@ -42,5 +42,16 @@
42
  "axis": "E",
43
  "desc_ko": "๋ฐ˜๊ฐ๊ธฐ โ€” ๋ชธ์— ์–ผ๋งˆ๋‚˜ ๋จธ๋ฌด๋‚˜",
44
  "desc_en": "Half-life โ€” how long it stays",
45
- "built": "2026-08-22"
 
 
 
 
 
 
 
 
 
 
 
46
  }
 
42
  "axis": "E",
43
  "desc_ko": "๋ฐ˜๊ฐ๊ธฐ โ€” ๋ชธ์— ์–ผ๋งˆ๋‚˜ ๋จธ๋ฌด๋‚˜",
44
  "desc_en": "Half-life โ€” how long it stays",
45
+ "built": "2026-08-22",
46
+ "stype": "T1/2",
47
+ "units": {
48
+ "hr": 1.0,
49
+ "min": 0.016666666666666666,
50
+ "day": 24.0
51
+ },
52
+ "log": true,
53
+ "clip": [
54
+ -3.0,
55
+ 4.0
56
+ ]
57
  }
data/ld50_card.json CHANGED
@@ -42,5 +42,16 @@
42
  "axis": "T",
43
  "desc_ko": "๊ธ‰์„ฑ๋…์„ฑ โ€” ์น˜์‚ฌ๋Ÿ‰",
44
  "desc_en": "Acute toxicity โ€” lethal dose",
45
- "built": "2026-08-22"
 
 
 
 
 
 
 
 
 
 
 
46
  }
 
42
  "axis": "T",
43
  "desc_ko": "๊ธ‰์„ฑ๋…์„ฑ โ€” ์น˜์‚ฌ๋Ÿ‰",
44
  "desc_en": "Acute toxicity โ€” lethal dose",
45
+ "built": "2026-08-22",
46
+ "stype": "LD50",
47
+ "units": {
48
+ "mg.kg-1": "mass",
49
+ "umol.kg-1": 1e-06,
50
+ "mol.kg-1": 1.0
51
+ },
52
+ "log": "neg",
53
+ "clip": [
54
+ 0.0,
55
+ 9.0
56
+ ]
57
  }
data/logp_card.json CHANGED
@@ -42,5 +42,14 @@
42
  "axis": "A",
43
  "desc_ko": "์นœ์œ ์„ฑ โ€” ๊ธฐ๋ฆ„๊ณผ ๋ฌผ ์‚ฌ์ด ๋ถ„๋ฐฐ",
44
  "desc_en": "Lipophilicity โ€” octanol/water partition",
45
- "built": "2026-08-22"
 
 
 
 
 
 
 
 
 
46
  }
 
42
  "axis": "A",
43
  "desc_ko": "์นœ์œ ์„ฑ โ€” ๊ธฐ๋ฆ„๊ณผ ๋ฌผ ์‚ฌ์ด ๋ถ„๋ฐฐ",
44
  "desc_en": "Lipophilicity โ€” octanol/water partition",
45
+ "built": "2026-08-22",
46
+ "stype": "LogP",
47
+ "units": {
48
+ "null": 1.0
49
+ },
50
+ "log": false,
51
+ "clip": [
52
+ -5.0,
53
+ 10.0
54
+ ]
55
  }
data/mtb_card.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "board": "MTB",
3
- "target": "โ€”",
4
  "type": "pMIC (์ „์„ธํฌ ์ตœ์†Œ์–ต์ œ๋†๋„)",
5
  "cut": 2020,
6
  "threshold": 4.5,
@@ -44,5 +44,18 @@
44
  "axis": "X",
45
  "desc_ko": "๊ฒฐํ•ต๊ท  - ํ‘œ์ค€๊ท ์ฃผ H37Rv ์ „์„ธํฌ ์‚ฌ๋ฉธ",
46
  "desc_en": "M. tuberculosis H37Rv whole-cell kill",
47
- "built": "2026-08-24"
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
 
1
  {
2
  "board": "MTB",
3
+ "target": "CHEMBL360",
4
  "type": "pMIC (์ „์„ธํฌ ์ตœ์†Œ์–ต์ œ๋†๋„)",
5
  "cut": 2020,
6
  "threshold": 4.5,
 
44
  "axis": "X",
45
  "desc_ko": "๊ฒฐํ•ต๊ท  - ํ‘œ์ค€๊ท ์ฃผ H37Rv ์ „์„ธํฌ ์‚ฌ๋ฉธ",
46
  "desc_en": "M. tuberculosis H37Rv whole-cell kill",
47
+ "built": "2026-08-24",
48
+ "stype": "MIC",
49
+ "units": {
50
+ "ug.mL-1": "mass",
51
+ "nM": 1e-09,
52
+ "uM": 1e-06,
53
+ "M": 1.0,
54
+ "nmol/L": 1e-09
55
+ },
56
+ "log": "neg",
57
+ "clip": [
58
+ 2.0,
59
+ 9.0
60
+ ]
61
  }
data/ppb_card.json CHANGED
@@ -42,5 +42,14 @@
42
  "axis": "D",
43
  "desc_ko": "ํ˜ˆ์žฅ๋‹จ๋ฐฑ๊ฒฐํ•ฉ โ€” ์œ ๋ฆฌ ์•ฝ๋ฌผ ๋น„์œจ์„ ์ •ํ•œ๋‹ค",
44
  "desc_en": "Plasma protein binding โ€” sets the free fraction",
45
- "built": "2026-08-22"
 
 
 
 
 
 
 
 
 
46
  }
 
42
  "axis": "D",
43
  "desc_ko": "ํ˜ˆ์žฅ๋‹จ๋ฐฑ๊ฒฐํ•ฉ โ€” ์œ ๋ฆฌ ์•ฝ๋ฌผ ๋น„์œจ์„ ์ •ํ•œ๋‹ค",
44
  "desc_en": "Plasma protein binding โ€” sets the free fraction",
45
+ "built": "2026-08-22",
46
+ "stype": "PPB",
47
+ "units": {
48
+ "%": 1.0
49
+ },
50
+ "log": false,
51
+ "clip": [
52
+ 0.0,
53
+ 100.0
54
+ ]
55
  }
data/saureus_card.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "board": "SAureus",
3
- "target": "โ€”",
4
  "type": "pMIC (์ „์„ธํฌ ์ตœ์†Œ์–ต์ œ๋†๋„)",
5
  "cut": 2023,
6
  "threshold": 4.5,
@@ -44,5 +44,18 @@
44
  "axis": "X",
45
  "desc_ko": "ํ™ฉ์ƒ‰ํฌ๋„์ƒ๊ตฌ๊ท  - ์ „์„ธํฌ ์‚ฌ๋ฉธ",
46
  "desc_en": "S. aureus whole-cell kill",
47
- "built": "2026-08-24"
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
 
1
  {
2
  "board": "SAureus",
3
+ "target": "CHEMBL352",
4
  "type": "pMIC (์ „์„ธํฌ ์ตœ์†Œ์–ต์ œ๋†๋„)",
5
  "cut": 2023,
6
  "threshold": 4.5,
 
44
  "axis": "X",
45
  "desc_ko": "ํ™ฉ์ƒ‰ํฌ๋„์ƒ๊ตฌ๊ท  - ์ „์„ธํฌ ์‚ฌ๋ฉธ",
46
  "desc_en": "S. aureus whole-cell kill",
47
+ "built": "2026-08-24",
48
+ "stype": "MIC",
49
+ "units": {
50
+ "ug.mL-1": "mass",
51
+ "nM": 1e-09,
52
+ "uM": 1e-06,
53
+ "M": 1.0,
54
+ "nmol/L": 1e-09
55
+ },
56
+ "log": "neg",
57
+ "clip": [
58
+ 2.0,
59
+ 9.0
60
+ ]
61
  }
data/sepidermidis_card.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "board": "SEpidermidis",
3
- "target": "โ€”",
4
  "type": "pMIC (์ „์„ธํฌ ์ตœ์†Œ์–ต์ œ๋†๋„)",
5
  "cut": 2018,
6
  "threshold": 4.5,
@@ -44,5 +44,18 @@
44
  "axis": "X",
45
  "desc_ko": "ํ‘œํ”ผํฌ๋„์ƒ๊ตฌ๊ท  - ์ „์„ธํฌ ์‚ฌ๋ฉธ",
46
  "desc_en": "S. epidermidis whole-cell kill",
47
- "built": "2026-08-24"
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
 
1
  {
2
  "board": "SEpidermidis",
3
+ "target": "CHEMBL353",
4
  "type": "pMIC (์ „์„ธํฌ ์ตœ์†Œ์–ต์ œ๋†๋„)",
5
  "cut": 2018,
6
  "threshold": 4.5,
 
44
  "axis": "X",
45
  "desc_ko": "ํ‘œํ”ผํฌ๋„์ƒ๊ตฌ๊ท  - ์ „์„ธํฌ ์‚ฌ๋ฉธ",
46
  "desc_en": "S. epidermidis whole-cell kill",
47
+ "built": "2026-08-24",
48
+ "stype": "MIC",
49
+ "units": {
50
+ "ug.mL-1": "mass",
51
+ "nM": 1e-09,
52
+ "uM": 1e-06,
53
+ "M": 1.0,
54
+ "nmol/L": 1e-09
55
+ },
56
+ "log": "neg",
57
+ "clip": [
58
+ 2.0,
59
+ 9.0
60
+ ]
61
  }
data/vdss_card.json CHANGED
@@ -42,5 +42,15 @@
42
  "axis": "D",
43
  "desc_ko": "๋ถ„ํฌ์šฉ์  โ€” ์กฐ์ง์œผ๋กœ ์–ผ๋งˆ๋‚˜ ํผ์ง€๋‚˜",
44
  "desc_en": "Volume of distribution",
45
- "built": "2026-08-22"
 
 
 
 
 
 
 
 
 
 
46
  }
 
42
  "axis": "D",
43
  "desc_ko": "๋ถ„ํฌ์šฉ์  โ€” ์กฐ์ง์œผ๋กœ ์–ผ๋งˆ๋‚˜ ํผ์ง€๋‚˜",
44
  "desc_en": "Volume of distribution",
45
+ "built": "2026-08-22",
46
+ "stype": "Vdss",
47
+ "units": {
48
+ "L.kg-1": 1.0,
49
+ "mL.kg-1": 0.001
50
+ },
51
+ "log": true,
52
+ "clip": [
53
+ -3.0,
54
+ 3.0
55
+ ]
56
  }
index.html CHANGED
@@ -298,10 +298,28 @@ function terrain(items,link){
298
  /* ๋ถ„๋ฅ˜ ์ „์šฉ ๋ถ€๋ฌธ์€ MAE ๊ธฐ์ค€์„ ์ด ์—†๋‹ค. ๊ทธ๋Ÿฐ ๋ถ€๋ฌธ์„ 0 ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ง€ํ˜•๋„ ๋งจ ์™ผ์ชฝ์—
299
  ๋ถ™์–ด "์ธก์ • ํ•œ๊ณ„์— ๋„๋‹ฌํ–ˆ๋‹ค"๋กœ ๋ณด์ธ๋‹ค. ์žฌ์ง€ ๋ชปํ•œ ๊ฒƒ๊ณผ ์žฌ์„œ ์ž‘์€ ๊ฒƒ์€ ๋‹ค๋ฅด๋ฏ€๋กœ ๋น„์šด๋‹ค. */
300
  const blurb=b=>esc(L==="ko"?(b.blurb||""):(b.blurb_en||b.blurb||""));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  const ratioOf=b=>{
302
  const nf=(b.noise_floor||{}).sd_single;
 
303
  const maes=Object.values(b.baselines||{}).map(v=>v.mae).filter(v=>v!=null);
304
- if(!nf||!maes.length) return null;
305
  return Math.min(...maes)/nf;
306
  };
307
 
@@ -405,13 +423,14 @@ async function paintLeaders(){
405
  const rows=d.rows.map(r=>{
406
  const ld=r.leader; // ์ „์—ญ L ์€ ์–ธ์–ด๋‹ค. ๊ฐ€๋ฆฌ์ง€ ์•Š๋Š”๋‹ค
407
  // ์ ์ˆ˜๋Š” ๋ถ€๋ฌธ๋งˆ๋‹ค ์ฃผ์ง€ํ‘œ๊ฐ€ ๋‹ค๋ฅด๋‹ค. ํšŒ๊ท€ ๋ถ€๋ฌธ์€ MAE, ๋ถ„๋ฅ˜ ์ „์šฉ ๋ถ€๋ฌธ์€ AUROC.
408
- const reg=r.baseline_best!=null;
409
- const score=ld?(reg?ld.mae:ld.auroc):null;
410
- const bas=reg?r.baseline_best:r.baseline_best_auroc;
 
411
  let vs="โ€”";
412
  if(ld&&score!=null&&bas!=null){
413
- const better=reg?score<bas:score>bas;
414
- const gap=reg?(bas-score):(score-bas);
415
  vs=`<span class="${better?"gain":"loss"}">${better?"โ–ฒ":"โ–ผ"} ${Math.abs(gap).toFixed(3)}</span>`;
416
  }
417
  return `<tr>
@@ -470,7 +489,7 @@ function boardMini(b){
470
  <div class="nm">${esc(b.board)}<span class="chip">${b.split_grade}/${b.answer_grade}</span></div>
471
  <div class="bl">${blurb(b)}</div>
472
  <div class="ds">${t.cardCompounds(b.n_test.toLocaleString())} ยท ${t.noiseH} ${f((b.noise_floor||{}).sd_single)}<br>
473
- ${esc(b.best_baseline)} ${bl.mae!=null?f(bl.mae):f(bl.auroc)}</div>
474
  <div class="mt"><span>${esc(b.source)}</span><span>${ratioOf(b)==null?"โ€”":f(ratioOf(b),2)+"ร—"}</span></div></a>`;
475
  }
476
 
@@ -527,8 +546,9 @@ async function paneRank(b){
527
  ๋Œ€๋ถ€๋ถ„ ๊ทธ ๋‹จ๊ณ„์—์„œ ๋–จ์–ด์ง„๋‹ค. */
528
  function promptFor(b){
529
  const nf=(b.noise_floor||{}).sd_single;
 
530
  const bl=Object.entries(b.baselines||{})
531
- .map(([k,v])=>` ${k}: ${v.mae!=null?("MAE "+f(v.mae)):("AUROC "+f(v.auroc))}`).join("\n");
532
  const unit = b.type && b.type.indexOf("logS")>=0
533
  ? "logS = log10(์šฉํ•ด๋„ in mol/L)"
534
  : b.threshold==null ? "๋ถ€๋ฌธ ์ •์˜๊ฐ’" : "p ์Šค์ผ€์ผ = -log10(๋†๋„ in M)";
@@ -552,7 +572,7 @@ b.cut ? ` โ˜… ${b.cut}๋…„ ์ดํ›„ ์ฒ˜์Œ ๋ณด๊ณ ๋œ ํ™”ํ•ฉ๋ฌผ์€ ํ•™์Šต์— ๋„ฃ์ง€
552
  ` ์ •๋‹ต์„ ์กฐํšŒํ•ด ์ฑ„์šฐ์ง€ ๋ง ๊ฒƒ. ์˜ˆ์ธก๊ฐ’์ด ์‹ค์ธก๊ณผ ์†Œ์ˆ˜ ์ž๋ฆฌ๊นŒ์ง€ ์ผ์น˜ํ•˜๋Š” ๋ฐ€๋„๋ฅผ ๊ฒ€์‚ฌํ•œ๋‹ค.`,
553
  ``,
554
  `[ํ‰๊ฐ€]`,
555
- ` ์ฃผ์ง€ํ‘œ ${b.metric==="auroc"?"AUROC (๋†’์„์ˆ˜๋ก ์ข‹์Œ)":"MAE (๋‚ฎ์„์ˆ˜๋ก ์ข‹์Œ)"}`,
556
  nf ? ` ์ด ๋ถ€๋ฌธ์˜ ์‹คํ—˜ ์žฌํ˜„ ์˜ค์ฐจ๋Š” ${f(nf)} ์ด๋‹ค. ๊ทธ๋ณด๋‹ค ์ž‘์€ ์ฐจ์ด๋Š” ์ˆœ์œ„๋กœ ์ธ์ •๋˜์ง€ ์•Š๋Š”๋‹ค.` : ``,
557
  ` ๋„˜์–ด์•ผ ํ•  ๊ธฐ์ค€์„ :`,
558
  bl,
@@ -562,9 +582,26 @@ bl,
562
  }
563
 
564
  function starterFor(b){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  return [
566
  `# ${b.board} ์ œ์ถœ ๋ผˆ๋Œ€`,
567
- `import json, math, requests, numpy as np`,
568
  `from rdkit import Chem`,
569
  `from rdkit.Chem import rdFingerprintGenerator`,
570
  `import lightgbm as lgb`,
@@ -573,31 +610,83 @@ function starterFor(b){
573
  `API = "https://www.ebi.ac.uk/chembl/api/data/activity.json"`,
574
  `rows, off = [], 0`,
575
  `while True:`,
576
- ` q = {"target_chembl_id": "${b.target}", "standard_type": "${b.type}",`,
577
- ` "standard_relation": "=", "limit": 1000, "offset": off}`,
578
  ` d = requests.get(API, params=q, timeout=120).json()`,
579
  ` rows += d["activities"]; off += 1000`,
580
  ` if off >= d["page_meta"]["total_count"]: break`,
581
  ``,
582
- `# 2) ์ •์ œ โ€” InChIKey ๋กœ ์ ‘๊ณ , ๊ธฐ์ค€์ผ ์ดํ›„๋Š” ๋ฒ„๋ฆฐ๋‹ค`,
 
 
 
583
  `rec = {}`,
584
  `for r in rows:`,
585
  ` s, v, u, y = r["canonical_smiles"], r["standard_value"], r["standard_units"], r["document_year"]`,
586
- ` if not (s and v and u == "nM" and y): continue`,
 
 
 
 
587
  ` m = Chem.MolFromSmiles(s)`,
588
  ` if m is None or float(v) <= 0: continue`,
589
  ` k = Chem.MolToInchiKey(m)`,
590
  ` rec.setdefault(k, {"y": [], "p": [], "m": m})`,
591
- ` rec[k]["y"].append(int(y)); rec[k]["p"].append(9.0 - math.log10(float(v)))`,
 
 
 
592
  `train = {k: v for k, v in rec.items() if min(v["y"]) < ${b.cut||2022}} # โ˜… ํ…Œ์ŠคํŠธ์…‹ ์ œ์™ธ`,
593
  ``,
594
- `# 3) ํŠน์ง• ยท ํ•™์Šต`,
595
  `gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)`,
596
  `X = np.array([gen.GetFingerprintAsNumPy(v["m"]) for v in train.values()])`,
597
  `Y = np.array([float(np.median(v["p"])) for v in train.values()])`,
598
  `model = lgb.LGBMRegressor(n_estimators=500, learning_rate=0.05).fit(X, Y)`,
599
  ``,
600
- `# 4) ์˜ˆ์ธก ยท ์ €์žฅ`,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601
  `test = json.load(open("${b.board.toLowerCase()}_test.json", encoding="utf-8"))`,
602
  `Xt = np.array([gen.GetFingerprintAsNumPy(Chem.MolFromSmiles(c["smiles"]))`,
603
  ` for c in test["compounds"]])`,
@@ -611,8 +700,9 @@ function starterFor(b){
611
 
612
  function paneGuide(b){
613
  const pr=promptFor(b), sk=starterFor(b);
 
614
  const bl=Object.entries(b.baselines||{}).sort((x,y)=>
615
- (x[1].mae!=null?x[1].mae:-x[1].auroc)-(y[1].mae!=null?y[1].mae:-y[1].auroc));
616
  $("#pane").innerHTML=`
617
  <p class="lede" style="margin:6px 0 18px">${t.gdIntro}</p>
618
  <h3>${t.gdWhat}</h3><p class="dim" style="font-size:14.5px">${t.gdWhatD}</p>
@@ -626,9 +716,9 @@ function paneGuide(b){
626
  <pre class="fig" style="white-space:pre;overflow-x:auto;margin:0;font-size:12px">${esc(sk)}</pre></div>
627
  <h3>${t.gdTarget}</h3><p class="dim" style="font-size:14px">${t.gdTargetD}</p>
628
  <div class="tw"><table><thead><tr><th style="text-align:left;width:auto">${t.baseH}</th>
629
- <th>MAE</th><th>AUROC</th></tr></thead><tbody>
630
  ${bl.map(([k,v])=>`<tr><td style="text-align:left">${esc(k)}</td>
631
- <td>${v.mae!=null?f(v.mae):"โ€”"}</td><td>${v.auroc!=null?f(v.auroc):"โ€”"}</td></tr>`).join("")}
632
  </tbody></table></div>
633
  <h3>${t.gdRules}</h3>
634
  <ol class="cl" style="grid-template-columns:1fr">
@@ -657,7 +747,9 @@ function paneInfo(b){
657
  <dt>${t.kOverlap}</dt><dd>${t.overlapNote}</dd>
658
  <dt>${t.kThr}</dt><dd>${b.threshold} ยท ${t.posRate((b.positive_rate*100).toFixed(1))}</dd>
659
  <dt>${t.kSha}</dt><dd>${esc(b.dataset_sha)}</dd>
660
- <dt>${t.kLicense}</dt><dd>ChEMBL ยท CC BY-SA 3.0</dd></dl>
 
 
661
  <h3>${t.noiseH} <span class="dim" style="font-weight:400;font-size:14px">(4)</span></h3>
662
  <div class="fig">${t.noiseDesc}<br>
663
  ${t.noisePairs(nf.pairs.toLocaleString(),nf.compounds.toLocaleString())}<br>
 
298
  /* ๋ถ„๋ฅ˜ ์ „์šฉ ๋ถ€๋ฌธ์€ MAE ๊ธฐ์ค€์„ ์ด ์—†๋‹ค. ๊ทธ๋Ÿฐ ๋ถ€๋ฌธ์„ 0 ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ง€ํ˜•๋„ ๋งจ ์™ผ์ชฝ์—
299
  ๋ถ™์–ด "์ธก์ • ํ•œ๊ณ„์— ๋„๋‹ฌํ–ˆ๋‹ค"๋กœ ๋ณด์ธ๋‹ค. ์žฌ์ง€ ๋ชปํ•œ ๊ฒƒ๊ณผ ์žฌ์„œ ์ž‘์€ ๊ฒƒ์€ ๋‹ค๋ฅด๋ฏ€๋กœ ๋น„์šด๋‹ค. */
300
  const blurb=b=>esc(L==="ko"?(b.blurb||""):(b.blurb_en||b.blurb||""));
301
+ /* ๋ถ€๋ฌธ๋งˆ๋‹ค ์ฃผ์ง€ํ‘œ๊ฐ€ ๋‹ค๋ฅด๋‹ค. ํŒ์ •์„ ํ•œ ๊ณณ์—๋งŒ ๋‘”๋‹ค.
302
+ ํ™”๋ฉด์ด MAE ๋งŒ ์•Œ๊ณ  ์žˆ์–ด์„œ, ํ”„๋กœํŒŒ์ผ ์ƒ๊ด€์œผ๋กœ ์ฑ„์ ๋˜๋Š” Phenotype ์ด "ํ•™์Šต ๊ธฐ์ค€์„ ์ด
303
+ ์ƒ์ˆ˜ ์˜ˆ์ธก์„ 0.003 ์ฐจ์ด๋กœ ๊ฐ„์‹ ํžˆ ์ด๊ธด๋‹ค"๋กœ ๋ณด์˜€๋‹ค. ์‹ค์ œ ์ง€ํ‘œ๋กœ๋Š” 0.092 ๋Œ€ 0.159 ๋กœ
304
+ ์žก์Œ ๋ฐ”๋‹ฅ์˜ 7.6๋ฐฐ ์ฐจ์ด๋‹ค. ์ง€ํ‘œ๋ฅผ ์ž˜๋ชป ๋„์šฐ๋ฉด ๋ถ€๋ฌธ์ด ๊ณ ์žฅ ๋‚œ ๊ฒƒ์ฒ˜๋Ÿผ ์ฝํžŒ๋‹ค. */
305
+ const metricOf=b=>{
306
+ if((b||{}).metric==="profile_corr") return {key:"profile_corr",hb:true,label:"ํ”„๋กœํŒŒ์ผ ์ƒ๊ด€"};
307
+ const bl=Object.values((b||{}).baselines||{});
308
+ if(bl.length&&bl.every(v=>v.mae==null)&&bl.some(v=>v.auroc!=null))
309
+ return {key:"auroc",hb:true,label:"AUROC"};
310
+ return {key:"mae",hb:false,label:"MAE"};
311
+ };
312
+ const metricLabel=b=>{const m=metricOf(b);
313
+ return L==="ko"?(m.label+" ("+(m.hb?"๋†’์„์ˆ˜๋ก":"๋‚ฎ์„์ˆ˜๋ก")+" ์ข‹์Œ)")
314
+ :(m.label+" ("+(m.hb?"higher":"lower")+" is better)");};
315
+ const blScore=(b,v)=>v?v[metricOf(b).key]:null;
316
+ /* ์ธก์ • ํ•œ๊ณ„๋น„๋Š” ์˜ค์ฐจ ์ง€ํ‘œ ์œ„์—์„œ๋งŒ ๋œป์ด ์žˆ๋‹ค. ์ƒ๊ด€ ๋ถ€๋ฌธ์—์„œ MAE ๋กœ ๊ณ„์‚ฐํ•˜๋ฉด
317
+ 3.40/0.0088 = 385 ๊ฐ™์€ ๋ฌด์˜๋ฏธํ•œ ์ˆ˜๊ฐ€ ๋‚˜์˜จ๋‹ค. ์žฌ์ง€ ๋ชปํ•œ ๊ฒƒ์€ ๋น„์šด๋‹ค. */
318
  const ratioOf=b=>{
319
  const nf=(b.noise_floor||{}).sd_single;
320
+ if(metricOf(b).key!=="mae"||!nf) return null;
321
  const maes=Object.values(b.baselines||{}).map(v=>v.mae).filter(v=>v!=null);
322
+ if(!maes.length) return null;
323
  return Math.min(...maes)/nf;
324
  };
325
 
 
423
  const rows=d.rows.map(r=>{
424
  const ld=r.leader; // ์ „์—ญ L ์€ ์–ธ์–ด๋‹ค. ๊ฐ€๋ฆฌ์ง€ ์•Š๋Š”๋‹ค
425
  // ์ ์ˆ˜๋Š” ๋ถ€๋ฌธ๋งˆ๋‹ค ์ฃผ์ง€ํ‘œ๊ฐ€ ๋‹ค๋ฅด๋‹ค. ํšŒ๊ท€ ๋ถ€๋ฌธ์€ MAE, ๋ถ„๋ฅ˜ ์ „์šฉ ๋ถ€๋ฌธ์€ AUROC.
426
+ const hb=!!r.higher_better;
427
+ const score=ld?(ld.score!=null?ld.score:(hb?ld.auroc:ld.mae)):null;
428
+ const bas=r.baseline_best_score!=null?r.baseline_best_score
429
+ :(hb?r.baseline_best_auroc:r.baseline_best);
430
  let vs="โ€”";
431
  if(ld&&score!=null&&bas!=null){
432
+ const better=hb?score>bas:score<bas;
433
+ const gap=hb?(score-bas):(bas-score);
434
  vs=`<span class="${better?"gain":"loss"}">${better?"โ–ฒ":"โ–ผ"} ${Math.abs(gap).toFixed(3)}</span>`;
435
  }
436
  return `<tr>
 
489
  <div class="nm">${esc(b.board)}<span class="chip">${b.split_grade}/${b.answer_grade}</span></div>
490
  <div class="bl">${blurb(b)}</div>
491
  <div class="ds">${t.cardCompounds(b.n_test.toLocaleString())} ยท ${t.noiseH} ${f((b.noise_floor||{}).sd_single)}<br>
492
+ ${esc(b.best_baseline)} ${f(blScore(b,bl))}</div>
493
  <div class="mt"><span>${esc(b.source)}</span><span>${ratioOf(b)==null?"โ€”":f(ratioOf(b),2)+"ร—"}</span></div></a>`;
494
  }
495
 
 
546
  ๋Œ€๋ถ€๋ถ„ ๊ทธ ๋‹จ๊ณ„์—์„œ ๋–จ์–ด์ง„๋‹ค. */
547
  function promptFor(b){
548
  const nf=(b.noise_floor||{}).sd_single;
549
+ const mk=metricOf(b);
550
  const bl=Object.entries(b.baselines||{})
551
+ .map(([k,v])=>` ${k}: ${mk.label} ${f(v[mk.key])}`).join("\n");
552
  const unit = b.type && b.type.indexOf("logS")>=0
553
  ? "logS = log10(์šฉํ•ด๋„ in mol/L)"
554
  : b.threshold==null ? "๋ถ€๋ฌธ ์ •์˜๊ฐ’" : "p ์Šค์ผ€์ผ = -log10(๋†๋„ in M)";
 
572
  ` ์ •๋‹ต์„ ์กฐํšŒํ•ด ์ฑ„์šฐ์ง€ ๋ง ๊ฒƒ. ์˜ˆ์ธก๊ฐ’์ด ์‹ค์ธก๊ณผ ์†Œ์ˆ˜ ์ž๋ฆฌ๊นŒ์ง€ ์ผ์น˜ํ•˜๋Š” ๋ฐ€๋„๋ฅผ ๊ฒ€์‚ฌํ•œ๋‹ค.`,
573
  ``,
574
  `[ํ‰๊ฐ€]`,
575
+ ` ์ฃผ์ง€ํ‘œ ${metricLabel(b)}`,
576
  nf ? ` ์ด ๋ถ€๋ฌธ์˜ ์‹คํ—˜ ์žฌํ˜„ ์˜ค์ฐจ๋Š” ${f(nf)} ์ด๋‹ค. ๊ทธ๋ณด๋‹ค ์ž‘์€ ์ฐจ์ด๋Š” ์ˆœ์œ„๋กœ ์ธ์ •๋˜์ง€ ์•Š๋Š”๋‹ค.` : ``,
577
  ` ๋„˜์–ด์•ผ ํ•  ๊ธฐ์ค€์„ :`,
578
  bl,
 
582
  }
583
 
584
  function starterFor(b){
585
+ /* ๋ถ€๋ฌธ์€ ์„ธ ์ข…๋ฅ˜๋‹ค. ํ•˜๋‚˜์˜ ๋ผˆ๋Œ€๋กœ ๋ฎ์œผ๋ฉด ๋‚˜๋จธ์ง€ ๋‘˜์€ ๋Œ์•„๊ฐ€์ง€ ์•Š๋Š”๋‹ค.
586
+
587
+ ChEMBL ํ‘œ์  ํ™œ์„ฑ target_chembl_id + standard_type ์œผ๋กœ ์กฐํšŒํ•œ๋‹ค.
588
+ ChEMBL ๋ฌผ์„ฑยท์ „์„ธํฌ ํ‘œ์ ์ด ์—†๊ฑฐ๋‚˜ ๊ท ์ด๋‹ค. standard_type ๊ณผ assay ์„ค๋ช…์œผ๋กœ ๊ณ ๋ฅธ๋‹ค.
589
+ ๊ทธ ์™ธ (JUMP ๋“ฑ) ChEMBL ์— ์—†๋‹ค. ์กฐํšŒ๋ฌธ์„ ์ง€์–ด๋‚ด์ง€ ์•Š๋Š”๋‹ค.
590
+
591
+ ์„ธ ๋ฒˆ์งธ๋ฅผ ์ฒซ ๋ฒˆ์งธ ๋ผˆ๋Œ€๋กœ ๋‚ด๋ณด๋‚ด๋‹ค๊ฐ€, ํ‘œ์  ์ž๋ฆฌ์— em dash ๊ฐ€ ๋ฐ•ํžŒ ์ฝ”๋“œ๋ฅผ
592
+ ์ฐธ๊ฐ€์ž๏ฟฝ๏ฟฝ๊ฒŒ ์ฃผ๊ณ  ์žˆ์—ˆ๋‹ค. ๊ทธ๋Œ€๋กœ ๋Œ๋ฆฌ๋ฉด ๋นˆ ํ›ˆ๋ จ์…‹์ด ๋‚˜์˜จ๋‹ค. */
593
+ const isChembl=/^CHEMBL[0-9]+$/.test(String(b.target||""));
594
+ const stype=b.stype||b.type;
595
+ if(!isChembl && !b.stype) return starterNoChembl(b);
596
+ const q=[` q = {"standard_type": "${stype}", "standard_relation": "=",`];
597
+ if(isChembl) q.push(` "target_chembl_id": "${b.target}",`);
598
+ q.push(` "limit": 1000, "offset": off}`);
599
+ const filt=[];
600
+ if(b.assay_keep) filt.push(`ak = re.compile(r"${b.assay_keep}", re.I)`);
601
+ if(b.assay_drop) filt.push(`ad = re.compile(r"${b.assay_drop}", re.I)`);
602
  return [
603
  `# ${b.board} ์ œ์ถœ ๋ผˆ๋Œ€`,
604
+ `import json, math, re, requests, numpy as np`,
605
  `from rdkit import Chem`,
606
  `from rdkit.Chem import rdFingerprintGenerator`,
607
  `import lightgbm as lgb`,
 
610
  `API = "https://www.ebi.ac.uk/chembl/api/data/activity.json"`,
611
  `rows, off = [], 0`,
612
  `while True:`,
613
+ ...q,
 
614
  ` d = requests.get(API, params=q, timeout=120).json()`,
615
  ` rows += d["activities"]; off += 1000`,
616
  ` if off >= d["page_meta"]["total_count"]: break`,
617
  ``,
618
+ filt.length?`# 2) ์‹คํ—˜๋ฒ• ์„ ๋ณ„ โ€” ๊ฐ™์€ ์ด๋ฆ„์˜ ์ธก์ •๋„ ์กฐ๊ฑด์ด ๋‹ค๋ฅด๋ฉด ๊ฐ’์ด ๋‹ค๋ฅด๋‹ค`:``,
619
+ ...filt,
620
+ ``,
621
+ `# 3) ์ •์ œ โ€” InChIKey ๋กœ ์ ‘๊ณ , ๊ธฐ์ค€์ผ ์ดํ›„๋Š” ๋ฒ„๋ฆฐ๋‹ค`,
622
  `rec = {}`,
623
  `for r in rows:`,
624
  ` s, v, u, y = r["canonical_smiles"], r["standard_value"], r["standard_units"], r["document_year"]`,
625
+ ` if not (s and v and y): continue`,
626
+ b.assay_keep?` if not ak.search(r.get("assay_description") or ""): continue`:``,
627
+ b.assay_drop?` if ad.search(r.get("assay_description") or ""): continue`:``,
628
+ b.log===false?` if u not in (None, ""): continue # ์ด ๋ถ€๋ฌธ ๊ฐ’์€ ๋ฌด๋‹จ์œ„๋‹ค`
629
+ :` if u != "nM": continue`,
630
  ` m = Chem.MolFromSmiles(s)`,
631
  ` if m is None or float(v) <= 0: continue`,
632
  ` k = Chem.MolToInchiKey(m)`,
633
  ` rec.setdefault(k, {"y": [], "p": [], "m": m})`,
634
+ b.log===false?` rec[k]["y"].append(int(y)); rec[k]["p"].append(float(v))`
635
+ :` rec[k]["y"].append(int(y)); rec[k]["p"].append(9.0 - math.log10(float(v)))`,
636
+ b.clip?`rec = {k: v for k, v in rec.items()`:``,
637
+ b.clip?` if ${b.clip[0]} <= float(np.median(v["p"])) <= ${b.clip[1]}} # ๋ฌผ๋ฆฌ์  ๋ฒ”์œ„ ๋ฐ–์€ ์ž…๋ ฅ ์˜ค๋ฅ˜๋‹ค`:``,
638
  `train = {k: v for k, v in rec.items() if min(v["y"]) < ${b.cut||2022}} # โ˜… ํ…Œ์ŠคํŠธ์…‹ ์ œ์™ธ`,
639
  ``,
640
+ `# 4) ํŠน์ง• ยท ํ•™์Šต`,
641
  `gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)`,
642
  `X = np.array([gen.GetFingerprintAsNumPy(v["m"]) for v in train.values()])`,
643
  `Y = np.array([float(np.median(v["p"])) for v in train.values()])`,
644
  `model = lgb.LGBMRegressor(n_estimators=500, learning_rate=0.05).fit(X, Y)`,
645
  ``,
646
+ `# 5) ์˜ˆ์ธก ยท ์ €์žฅ`,
647
+ `test = json.load(open("${b.board.toLowerCase()}_test.json", encoding="utf-8"))`,
648
+ `Xt = np.array([gen.GetFingerprintAsNumPy(Chem.MolFromSmiles(c["smiles"]))`,
649
+ ` for c in test["compounds"]])`,
650
+ `p = model.predict(Xt)`,
651
+ `with open("predictions.csv", "w") as f:`,
652
+ ` f.write("compound_id,prediction\n")`,
653
+ ` for c, v in zip(test["compounds"], p):`,
654
+ ` f.write("%s,%.4f\n" % (c["compound_id"], v))`
655
+ ].filter(x=>x!==``||true).join("\n");
656
+ }
657
+
658
+ /* ChEMBL ์—์„œ ๋‚˜์˜ค์ง€ ์•Š๋Š” ๋ถ€๋ฌธ. ์กฐํšŒ๋ฌธ์„ ์ง€์–ด๋‚ด์ง€ ์•Š๊ณ , ์ž๋ฃŒ๊ฐ€ ์–ด๋”” ์žˆ๋Š”์ง€ ๋ฐํžŒ๋‹ค.
659
+ ํ›ˆ๋ จ ๋ผ๋ฒจ์€ ์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ํŒ๋ณธ์ด๋ฏ€๋กœ ๋Œ€์‹  ๋งŒ๋“ค์–ด ์ค„ ์ˆ˜ ์—†๋‹ค - ๊ทœ์น™ ๊ทธ๋Œ€๋กœ,
660
+ ์ฐธ๊ฐ€์ž๊ฐ€ ๊ณต๊ฐœ ์ž๋ฃŒ๋ฅผ ์ง์ ‘ ํ๋ ˆ์ด์…˜ํ•œ๋‹ค. */
661
+ function starterNoChembl(b){
662
+ return [
663
+ `# ${b.board} ์ œ์ถœ ๋ผˆ๋Œ€`,
664
+ `#`,
665
+ `# ์ด ๋ถ€๋ฌธ์€ ChEMBL ํ™œ์„ฑ ์กฐํšŒ๋กœ ๋งŒ๋“ค์–ด์ง€์ง€ ์•Š๋Š”๋‹ค. ์ถœ์ฒ˜: ${b.source}`,
666
+ `# ํ›ˆ๋ จ ๋ผ๋ฒจ์€ ๋ณธ ์‹œํ—˜๋Œ€๊ฐ€ ๋งŒ๋“  ํŒ๋ณธ์ด๋ผ ๋ฐฐํฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ทœ์น™ 3์กฐ๋Œ€๋กœ`,
667
+ `# ๊ณต๊ฐœ ์›์ž๋ฃŒ์—์„œ ์ง์ ‘ ๋งŒ๋“ค์–ด ์จ์•ผ ํ•œ๋‹ค.`,
668
+ `#`,
669
+ `# ์•„๋ž˜๋Š” ํ•™์Šตยท์˜ˆ์ธกยท์ €์žฅ ๋ถ€๋ถ„์ด๋‹ค. load_training() ๋งŒ ์ฑ„์šฐ๋ฉด ๋œ๋‹ค.`,
670
+ ``,
671
+ `import json, numpy as np`,
672
+ `from rdkit import Chem`,
673
+ `from rdkit.Chem import rdFingerprintGenerator`,
674
+ `import lightgbm as lgb`,
675
+ ``,
676
+ `def load_training():`,
677
+ ` """(SMILES, ๋ชฉํ‘œ๊ฐ’) ๋ชฉ๋ก์„ ๋‚ธ๋‹ค.`,
678
+ ` ์ถœ์ฒ˜: ${b.source}`,
679
+ ` ${b.cut?("โ˜… "+b.cut+"๋…„ ์ดํ›„ ์ฒ˜์Œ ๋ณด๊ณ ๋œ ํ™”ํ•ฉ๋ฌผ์€ ๋„ฃ์ง€ ๋ง ๊ฒƒ โ€” ๊ทธ๊ฒƒ์ด ํ…Œ์ŠคํŠธ์…‹์ด๋‹ค."):"์‹œํ—˜ ํ™”ํ•ฉ๋ฌผ๊ณผ ๊ฒน์น˜์ง€ ์•Š๊ฒŒ ๊ตฌ์„ฑํ•  ๊ฒƒ."}`,
680
+ ` """`,
681
+ ` raise NotImplementedError("๊ณต๊ฐœ ์›์ž๋ฃŒ์—์„œ ์ง์ ‘ ๊ตฌ์„ฑํ•œ๋‹ค")`,
682
+ ``,
683
+ `pairs = load_training()`,
684
+ `gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)`,
685
+ `mols = [Chem.MolFromSmiles(s) for s, _ in pairs]`,
686
+ `X = np.array([gen.GetFingerprintAsNumPy(m) for m in mols if m is not None])`,
687
+ `Y = np.array([y for (s, y), m in zip(pairs, mols) if m is not None])`,
688
+ `model = lgb.LGBMRegressor(n_estimators=500, learning_rate=0.05).fit(X, Y)`,
689
+ ``,
690
  `test = json.load(open("${b.board.toLowerCase()}_test.json", encoding="utf-8"))`,
691
  `Xt = np.array([gen.GetFingerprintAsNumPy(Chem.MolFromSmiles(c["smiles"]))`,
692
  ` for c in test["compounds"]])`,
 
700
 
701
  function paneGuide(b){
702
  const pr=promptFor(b), sk=starterFor(b);
703
+ const mk=metricOf(b);
704
  const bl=Object.entries(b.baselines||{}).sort((x,y)=>
705
+ (mk.hb?-1:1)*((x[1][mk.key]??0)-(y[1][mk.key]??0)));
706
  $("#pane").innerHTML=`
707
  <p class="lede" style="margin:6px 0 18px">${t.gdIntro}</p>
708
  <h3>${t.gdWhat}</h3><p class="dim" style="font-size:14.5px">${t.gdWhatD}</p>
 
716
  <pre class="fig" style="white-space:pre;overflow-x:auto;margin:0;font-size:12px">${esc(sk)}</pre></div>
717
  <h3>${t.gdTarget}</h3><p class="dim" style="font-size:14px">${t.gdTargetD}</p>
718
  <div class="tw"><table><thead><tr><th style="text-align:left;width:auto">${t.baseH}</th>
719
+ <th>${esc(metricLabel(b))}</th></tr></thead><tbody>
720
  ${bl.map(([k,v])=>`<tr><td style="text-align:left">${esc(k)}</td>
721
+ <td>${v[mk.key]!=null?f(v[mk.key]):"โ€”"}</td></tr>`).join("")}
722
  </tbody></table></div>
723
  <h3>${t.gdRules}</h3>
724
  <ol class="cl" style="grid-template-columns:1fr">
 
747
  <dt>${t.kOverlap}</dt><dd>${t.overlapNote}</dd>
748
  <dt>${t.kThr}</dt><dd>${b.threshold} ยท ${t.posRate((b.positive_rate*100).toFixed(1))}</dd>
749
  <dt>${t.kSha}</dt><dd>${esc(b.dataset_sha)}</dd>
750
+ <dt>${t.kLicense}</dt><dd>${/JUMP/i.test(b.source||"")
751
+ ? "JUMP Cell Painting ยท CC0"
752
+ : "ChEMBL ยท CC BY-SA 3.0"}</dd></dl>
753
  <h3>${t.noiseH} <span class="dim" style="font-weight:400;font-size:14px">(4)</span></h3>
754
  <div class="fig">${t.noiseDesc}<br>
755
  ${t.noisePairs(nf.pairs.toLocaleString(),nf.compounds.toLocaleString())}<br>