Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
verena commited on
Commit Β·
efa3e66
1
Parent(s): 1112710
8/28: submission + scoring clarity; track 2 quota increase; FAQ updates
Browse files- app.py +1 -1
- config.py +2 -0
- tabs/about.py +8 -2
- tabs/faq.py +30 -7
- tabs/leaderboard.py +6 -2
- tabs/submit_track1.py +29 -11
- tabs/submit_track2.py +72 -94
- utils.py +32 -14
app.py
CHANGED
|
@@ -107,7 +107,7 @@ with gr.Blocks(title="Rare Disease, Real Kid: The MVA Hackathon 2026") as app:
|
|
| 107 |
quota_t1 = submit_track1.render()
|
| 108 |
status_t2 = submit_track2.render()
|
| 109 |
app.load(fn=submit_track1._quota_status, outputs=quota_t1)
|
| 110 |
-
app.load(fn=submit_track2.
|
| 111 |
faq.render()
|
| 112 |
rules.render()
|
| 113 |
|
|
|
|
| 107 |
quota_t1 = submit_track1.render()
|
| 108 |
status_t2 = submit_track2.render()
|
| 109 |
app.load(fn=submit_track1._quota_status, outputs=quota_t1)
|
| 110 |
+
app.load(fn=submit_track2._quota_status, outputs=status_t2)
|
| 111 |
faq.render()
|
| 112 |
rules.render()
|
| 113 |
|
config.py
CHANGED
|
@@ -5,6 +5,7 @@ In particular, the following constants are
|
|
| 5 |
LEADERBOARD_HEADERS - columns displayed on the leaderboard
|
| 6 |
CHALLENGE_ACTIVE - set to False to hide leaderboard submission tabs
|
| 7 |
MAX_TRACK1_SUBMISSIONS - how many Track 1 uploads each participant (a HF user) is allowed
|
|
|
|
| 8 |
"""
|
| 9 |
|
| 10 |
from dotenv import load_dotenv
|
|
@@ -43,3 +44,4 @@ LEADERBOARD_HEADERS = ["#", "Participant", "Rank Points", "F-max", "Model", "Sub
|
|
| 43 |
# Challenge control
|
| 44 |
CHALLENGE_ACTIVE = True
|
| 45 |
MAX_TRACK1_SUBMISSIONS = 6
|
|
|
|
|
|
| 5 |
LEADERBOARD_HEADERS - columns displayed on the leaderboard
|
| 6 |
CHALLENGE_ACTIVE - set to False to hide leaderboard submission tabs
|
| 7 |
MAX_TRACK1_SUBMISSIONS - how many Track 1 uploads each participant (a HF user) is allowed
|
| 8 |
+
MAX_TRACK2_SUBMISSIONS - how many Track 2 uploads each participant (a HF user) is allowed
|
| 9 |
"""
|
| 10 |
|
| 11 |
from dotenv import load_dotenv
|
|
|
|
| 44 |
# Challenge control
|
| 45 |
CHALLENGE_ACTIVE = True
|
| 46 |
MAX_TRACK1_SUBMISSIONS = 6
|
| 47 |
+
MAX_TRACK2_SUBMISSIONS = 3
|
tabs/about.py
CHANGED
|
@@ -93,7 +93,9 @@ def _build_html(img_child: str) -> str:
|
|
| 93 |
</ul>
|
| 94 |
<span class="sub-heading">Scoring</span>
|
| 95 |
<p>
|
| 96 |
-
|
|
|
|
|
|
|
| 97 |
</p>
|
| 98 |
<ul>
|
| 99 |
<li><strong>Rank points</strong> - How high did you rank the true variant(s). If your top-ranked
|
|
@@ -104,6 +106,10 @@ def _build_html(img_child: str) -> str:
|
|
| 104 |
any confidence threshold, rewarding submissions that pinpoint the right answer without burying it
|
| 105 |
in noise. Secondary or incidental findings won't hurt your automated score.</li>
|
| 106 |
</ul>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
</div>
|
| 108 |
|
| 109 |
<div class="info-card track-card">
|
|
@@ -124,7 +130,7 @@ def _build_html(img_child: str) -> str:
|
|
| 124 |
</p>
|
| 125 |
<ul class="note-small">
|
| 126 |
<li>What you submit: A detailed report, a Github link, and a 3-minute pitch video</li>
|
| 127 |
-
<li>Submission limit:
|
| 128 |
</ul>
|
| 129 |
<span class="sub-heading">Scoring</span>
|
| 130 |
<p>
|
|
|
|
| 93 |
</ul>
|
| 94 |
<span class="sub-heading">Scoring</span>
|
| 95 |
<p>
|
| 96 |
+
<strong>This is a foundational track</strong> β the goal is to validate that your pipeline can
|
| 97 |
+
recover a clinically confirmed variant from real patient data. Prediction files are scored automatically on
|
| 98 |
+
two primary metrics:
|
| 99 |
</p>
|
| 100 |
<ul>
|
| 101 |
<li><strong>Rank points</strong> - How high did you rank the true variant(s). If your top-ranked
|
|
|
|
| 106 |
any confidence threshold, rewarding submissions that pinpoint the right answer without burying it
|
| 107 |
in noise. Secondary or incidental findings won't hurt your automated score.</li>
|
| 108 |
</ul>
|
| 109 |
+
<p>
|
| 110 |
+
Your <strong>methods write-up</strong> is reviewed separately by a judging panel for scientific rigor and
|
| 111 |
+
depth of understanding.
|
| 112 |
+
</p>
|
| 113 |
</div>
|
| 114 |
|
| 115 |
<div class="info-card track-card">
|
|
|
|
| 130 |
</p>
|
| 131 |
<ul class="note-small">
|
| 132 |
<li>What you submit: A detailed report, a Github link, and a 3-minute pitch video</li>
|
| 133 |
+
<li>Submission limit: 3</li>
|
| 134 |
</ul>
|
| 135 |
<span class="sub-heading">Scoring</span>
|
| 136 |
<p>
|
tabs/faq.py
CHANGED
|
@@ -7,6 +7,7 @@ import gradio as gr
|
|
| 7 |
from config import DATASET_URL, DISCUSSIONS_URL
|
| 8 |
|
| 9 |
FAQ_ITEMS = [
|
|
|
|
| 10 |
(
|
| 11 |
"Who can participate?",
|
| 12 |
"Anyone with a Hugging Face account can participate - data scientists, ML engineers, clinicians, "
|
|
@@ -24,13 +25,29 @@ FAQ_ITEMS = [
|
|
| 24 |
"on the team's behalf - only one Track 2 submission per team is accepted, and additional submissions from "
|
| 25 |
"other team members will be ignored.",
|
| 26 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
(
|
| 28 |
"How is Track 1 scored?",
|
| 29 |
"Submissions are scored the way large-scale rare disease benchmarks (like Stenton et al., 2024) score solved "
|
| 30 |
"cases applied to this single, clinically confirmed answer.\n\nTwo metrics are computed automatically: **rank points** "
|
| 31 |
"(based on how high the true variant(s) land in your ranked list, with partial credit if you recover only one of "
|
| 32 |
-
"two compound-heterozygous variants) and **F-max** (the best precision/recall balance across your submitted"
|
| 33 |
-
"confidence thresholds)."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
),
|
| 35 |
(
|
| 36 |
"Are secondary or incidental findings scored?",
|
|
@@ -39,24 +56,30 @@ FAQ_ITEMS = [
|
|
| 39 |
"since there's no fixed \"correct\" answer for secondary findings the way there is for the primary causal variant.\n\n"
|
| 40 |
"Use the optional `notes` column to briefly explain why you're flagging it (e.g. \"well-established pathogenic "
|
| 41 |
"variant, unrelated to primary phenotype, recommend clinical follow-up\")",
|
| 42 |
-
|
| 43 |
),
|
| 44 |
(
|
| 45 |
"How is Track 2 scored?",
|
| 46 |
"Reports are reviewed by an independent panel, and will be judged on scientific rigor, potential impact, innovation, "
|
| 47 |
"and scalability.",
|
| 48 |
),
|
|
|
|
| 49 |
(
|
| 50 |
-
"
|
| 51 |
-
"
|
| 52 |
-
"
|
| 53 |
-
"them count.",
|
| 54 |
),
|
| 55 |
(
|
| 56 |
"What compute resources are available?",
|
| 57 |
"This Space runs on CPU-basic hardware. You are welcome to use your own compute for training, as only "
|
| 58 |
"the final submission files need to be uploaded here.",
|
| 59 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
(
|
| 61 |
"What is the data license?",
|
| 62 |
"The underlying dataset is **gated** and subject to the terms of the Hackathon Rules and Data Transfer Agreement "
|
|
|
|
| 7 |
from config import DATASET_URL, DISCUSSIONS_URL
|
| 8 |
|
| 9 |
FAQ_ITEMS = [
|
| 10 |
+
# Participation
|
| 11 |
(
|
| 12 |
"Who can participate?",
|
| 13 |
"Anyone with a Hugging Face account can participate - data scientists, ML engineers, clinicians, "
|
|
|
|
| 25 |
"on the team's behalf - only one Track 2 submission per team is accepted, and additional submissions from "
|
| 26 |
"other team members will be ignored.",
|
| 27 |
),
|
| 28 |
+
(
|
| 29 |
+
"How many submissions can I make?",
|
| 30 |
+
"Track 1 allows up to **6 submissions** per participant; only your highest-scoring submission appears on the "
|
| 31 |
+
"leaderboard.\n\nTrack 2 allows up to **3 submissions** per team. The independent expert panel will only review "
|
| 32 |
+
"your latest entry, so save your best for last.",
|
| 33 |
+
),
|
| 34 |
+
# Scoring
|
| 35 |
(
|
| 36 |
"How is Track 1 scored?",
|
| 37 |
"Submissions are scored the way large-scale rare disease benchmarks (like Stenton et al., 2024) score solved "
|
| 38 |
"cases applied to this single, clinically confirmed answer.\n\nTwo metrics are computed automatically: **rank points** "
|
| 39 |
"(based on how high the true variant(s) land in your ranked list, with partial credit if you recover only one of "
|
| 40 |
+
"two compound-heterozygous variants) and **F-max** (the best precision/recall balance across your submitted "
|
| 41 |
+
"confidence thresholds).\n\n"
|
| 42 |
+
"Your methods write-up is also reviewed by a judging panel for scientific rigor and depth of understanding.",
|
| 43 |
+
),
|
| 44 |
+
(
|
| 45 |
+
"Why are there perfect scores on the Track 1 leaderboard?",
|
| 46 |
+
"Track 1 is a **foundational track** β it's designed to be achievable. The leaderboard validates that your "
|
| 47 |
+
"pipeline can recover the clinically confirmed variant(s) from real patient data.\n\n"
|
| 48 |
+
"However, recovering the correct variant(s) is not the finish line for Track 1. All teams must also submit a "
|
| 49 |
+
"methods write-up explaining their approach and interpretation of the variants. This component cannot be scored "
|
| 50 |
+
"automatically and is where the judging panel evaluates scientific rigor and depth of understanding."
|
| 51 |
),
|
| 52 |
(
|
| 53 |
"Are secondary or incidental findings scored?",
|
|
|
|
| 56 |
"since there's no fixed \"correct\" answer for secondary findings the way there is for the primary causal variant.\n\n"
|
| 57 |
"Use the optional `notes` column to briefly explain why you're flagging it (e.g. \"well-established pathogenic "
|
| 58 |
"variant, unrelated to primary phenotype, recommend clinical follow-up\")",
|
|
|
|
| 59 |
),
|
| 60 |
(
|
| 61 |
"How is Track 2 scored?",
|
| 62 |
"Reports are reviewed by an independent panel, and will be judged on scientific rigor, potential impact, innovation, "
|
| 63 |
"and scalability.",
|
| 64 |
),
|
| 65 |
+
# Technical & Logistics
|
| 66 |
(
|
| 67 |
+
"Can I keep my GitHub repository private during the Hackathon?",
|
| 68 |
+
"Yes. Your repository may remain private while the Hackathon is running. However, it must be made public "
|
| 69 |
+
"once the Hackathon ends so the expert panel can review your code and methods.",
|
|
|
|
| 70 |
),
|
| 71 |
(
|
| 72 |
"What compute resources are available?",
|
| 73 |
"This Space runs on CPU-basic hardware. You are welcome to use your own compute for training, as only "
|
| 74 |
"the final submission files need to be uploaded here.",
|
| 75 |
),
|
| 76 |
+
(
|
| 77 |
+
"Are third-party LLMs allowed with this Hackathon's dataset?",
|
| 78 |
+
"Conditions apply. Please see our "
|
| 79 |
+
"[detailed answer in the Community discussion](https://huggingface.co/spaces/SageBio/rare-disease-real-kid-mva-hackathon-2026/discussions/2#6a8f68927f3975b1f61cc18a) "
|
| 80 |
+
"for full guidance on provider terms, data handling, and deletion requirements.",
|
| 81 |
+
),
|
| 82 |
+
# Data & Timeline
|
| 83 |
(
|
| 84 |
"What is the data license?",
|
| 85 |
"The underlying dataset is **gated** and subject to the terms of the Hackathon Rules and Data Transfer Agreement "
|
tabs/leaderboard.py
CHANGED
|
@@ -10,10 +10,14 @@ from utils import leaderboard_df
|
|
| 10 |
LEADERBOARD_MD = """
|
| 11 |
## Track 1 Leaderboard
|
| 12 |
|
| 13 |
-
Rankings update automatically as valid
|
| 14 |
-
|
| 15 |
|
| 16 |
**Metrics:** Rank Points & F-max (adapted from _Stenton et al., 2024_)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
"""
|
| 18 |
|
| 19 |
|
|
|
|
| 10 |
LEADERBOARD_MD = """
|
| 11 |
## Track 1 Leaderboard
|
| 12 |
|
| 13 |
+
Rankings update automatically as valid prediction files are scored. Only your team's highest-scoring prediction file
|
| 14 |
+
is displayed.
|
| 15 |
|
| 16 |
**Metrics:** Rank Points & F-max (adapted from _Stenton et al., 2024_)
|
| 17 |
+
|
| 18 |
+
> **Note:** Final Track 1 evaluation also includes your **methods write-up**, which is not reflected here. Your
|
| 19 |
+
> write-ups will be reviewed by a judging panel for scientific rigor after the Hackathon closes. See the FAQ
|
| 20 |
+
> for details.
|
| 21 |
"""
|
| 22 |
|
| 23 |
|
tabs/submit_track1.py
CHANGED
|
@@ -72,6 +72,10 @@ name your predictions file:
|
|
| 72 |
Each model must include a brief report (PDF or Markdown) describing your approach, rationale, and any supporting evidence.
|
| 73 |
This is required for judging, but is not scored automatically.
|
| 74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
Not sure what details to include? Please use the provided methods description template to organize your analysis, then
|
| 76 |
export your report as a PDF or Markdown file for submission.
|
| 77 |
|
|
@@ -82,7 +86,16 @@ is `jane-doe`, you might name your report file:
|
|
| 82 |
|
| 83 |
### 3. Prepare other supporting materials.
|
| 84 |
|
| 85 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
### 4. After submission:
|
| 88 |
|
|
@@ -116,31 +129,31 @@ def _score_csv_bytes(csv_bytes: bytes) -> tuple:
|
|
| 116 |
|
| 117 |
|
| 118 |
def _handle_submit(display_name_input: str, github_url: str, report_file, csv_file, request: gr.Request, oauth_profile: gr.OAuthProfile | None = None) -> tuple:
|
| 119 |
-
"""Gradio callback - returns (feedback_md, quota_md)."""
|
| 120 |
hf_username = get_hf_username(request, oauth_profile)
|
| 121 |
if not hf_username:
|
| 122 |
-
return "β οΈ Please sign in with your Hugging Face account to submit.", ""
|
| 123 |
|
| 124 |
display_name = (display_name_input or "").strip() or hf_username_to_display_slug(hf_username)
|
| 125 |
|
| 126 |
github_url = (github_url or "").strip()
|
| 127 |
if not github_url.startswith("https://github.com/"):
|
| 128 |
-
return "β οΈ GitHub URL must start with `https://github.com/`.", _quota_status(request)
|
| 129 |
|
| 130 |
if report_file is None:
|
| 131 |
-
return "β οΈ Please upload your report (PDF or Markdown).", _quota_status(request)
|
| 132 |
report_path = report_file if isinstance(report_file, str) else report_file.name
|
| 133 |
if Path(report_path).suffix.lower() not in {".pdf", ".md"}:
|
| 134 |
-
return "β οΈ Report must be a PDF (.pdf) or Markdown (.md) file.", _quota_status(request)
|
| 135 |
report_filename = Path(report_path).name
|
| 136 |
|
| 137 |
if csv_file is None:
|
| 138 |
-
return "β οΈ Please upload a CSV file.", _quota_status(request)
|
| 139 |
|
| 140 |
csv_path = csv_file if isinstance(csv_file, str) else csv_file.name
|
| 141 |
csv_filename = Path(csv_path).name
|
| 142 |
if Path(csv_path).suffix.lower() != ".csv":
|
| 143 |
-
return "β οΈ File must be a CSV (.csv).", _quota_status(request)
|
| 144 |
|
| 145 |
count = submissions_by_user(hf_username)
|
| 146 |
if count >= MAX_TRACK1_SUBMISSIONS:
|
|
@@ -148,6 +161,8 @@ def _handle_submit(display_name_input: str, github_url: str, report_file, csv_fi
|
|
| 148 |
f"β οΈ You have already used all {MAX_TRACK1_SUBMISSIONS} submissions. "
|
| 149 |
"Only your best-scoring submission counts toward final ranking.",
|
| 150 |
_quota_status(request),
|
|
|
|
|
|
|
| 151 |
)
|
| 152 |
model_n = count + 1
|
| 153 |
|
|
@@ -157,7 +172,7 @@ def _handle_submit(display_name_input: str, github_url: str, report_file, csv_fi
|
|
| 157 |
else:
|
| 158 |
csv_bytes = csv_file.read() if hasattr(csv_file, "read") else Path(csv_file.name).read_bytes()
|
| 159 |
except Exception as e:
|
| 160 |
-
return f"β οΈ Could not read uploaded file: {e}", _quota_status(request)
|
| 161 |
|
| 162 |
try:
|
| 163 |
result, proband_id = _score_csv_bytes(csv_bytes)
|
|
@@ -167,6 +182,8 @@ def _handle_submit(display_name_input: str, github_url: str, report_file, csv_fi
|
|
| 167 |
f"β **Scoring error:** {e}\n\n"
|
| 168 |
f"<details><summary>Technical details</summary>\n\n```\n{tb}\n```\n</details>",
|
| 169 |
_quota_status(request),
|
|
|
|
|
|
|
| 170 |
)
|
| 171 |
|
| 172 |
entry = {
|
|
@@ -211,7 +228,7 @@ def _handle_submit(display_name_input: str, github_url: str, report_file, csv_fi
|
|
| 211 |
|
| 212 |
{match_desc}
|
| 213 |
"""
|
| 214 |
-
return feedback.strip(), _quota_status(request)
|
| 215 |
|
| 216 |
|
| 217 |
def _quota_status(request: gr.Request, oauth_profile: gr.OAuthProfile | None = None) -> str:
|
|
@@ -234,6 +251,7 @@ def render() -> None:
|
|
| 234 |
quota_md = gr.HTML()
|
| 235 |
with gr.Accordion("π Submission Format & Instructions", open=True, elem_classes=["faq-accordion"]):
|
| 236 |
gr.Markdown("**Templates:**")
|
|
|
|
| 237 |
with gr.Row():
|
| 238 |
gr.DownloadButton(
|
| 239 |
label="π Submission template (CSV)",
|
|
@@ -272,7 +290,7 @@ def render() -> None:
|
|
| 272 |
submit_btn.click(
|
| 273 |
fn=_handle_submit,
|
| 274 |
inputs=[team_name_box, github_box, report_file, csv_file],
|
| 275 |
-
outputs=[result_md, quota_md],
|
| 276 |
)
|
| 277 |
tab.select(fn=_quota_status, inputs=[], outputs=quota_md)
|
| 278 |
return quota_md
|
|
|
|
| 72 |
Each model must include a brief report (PDF or Markdown) describing your approach, rationale, and any supporting evidence.
|
| 73 |
This is required for judging, but is not scored automatically.
|
| 74 |
|
| 75 |
+
> **Update 28 Aug 2026:** If you used an LLM or AI assistant, please record the provider, the plan or tier, and the
|
| 76 |
+
> relevant data-handling setting in your methods description. A line is enough, for example: *"Anthropic API, Claude
|
| 77 |
+
> Sonnet, commercial terms, no training on customer content."*
|
| 78 |
+
|
| 79 |
Not sure what details to include? Please use the provided methods description template to organize your analysis, then
|
| 80 |
export your report as a PDF or Markdown file for submission.
|
| 81 |
|
|
|
|
| 86 |
|
| 87 |
### 3. Prepare other supporting materials.
|
| 88 |
|
| 89 |
+
**GitHub Repository**
|
| 90 |
+
|
| 91 |
+
Your repository may remain private while the Hackathon is running. However, it must be made public once the
|
| 92 |
+
Hackathon ends so the judging panel can review your code and methods.
|
| 93 |
+
|
| 94 |
+
What to include:
|
| 95 |
+
- Documented, reproducible code (all scripts and configuration files needed to reproduce your results)
|
| 96 |
+
- Submission files *(optional)*
|
| 97 |
+
- Methods description *(optional but recommended)*
|
| 98 |
+
|
| 99 |
|
| 100 |
### 4. After submission:
|
| 101 |
|
|
|
|
| 129 |
|
| 130 |
|
| 131 |
def _handle_submit(display_name_input: str, github_url: str, report_file, csv_file, request: gr.Request, oauth_profile: gr.OAuthProfile | None = None) -> tuple:
|
| 132 |
+
"""Gradio callback - returns (feedback_md, quota_md, report_file, csv_file)."""
|
| 133 |
hf_username = get_hf_username(request, oauth_profile)
|
| 134 |
if not hf_username:
|
| 135 |
+
return "β οΈ Please sign in with your Hugging Face account to submit.", "", gr.update(), gr.update()
|
| 136 |
|
| 137 |
display_name = (display_name_input or "").strip() or hf_username_to_display_slug(hf_username)
|
| 138 |
|
| 139 |
github_url = (github_url or "").strip()
|
| 140 |
if not github_url.startswith("https://github.com/"):
|
| 141 |
+
return "β οΈ GitHub URL must start with `https://github.com/`.", _quota_status(request), gr.update(), gr.update()
|
| 142 |
|
| 143 |
if report_file is None:
|
| 144 |
+
return "β οΈ Please upload your report (PDF or Markdown).", _quota_status(request), gr.update(), gr.update()
|
| 145 |
report_path = report_file if isinstance(report_file, str) else report_file.name
|
| 146 |
if Path(report_path).suffix.lower() not in {".pdf", ".md"}:
|
| 147 |
+
return "β οΈ Report must be a PDF (.pdf) or Markdown (.md) file.", _quota_status(request), gr.update(), gr.update()
|
| 148 |
report_filename = Path(report_path).name
|
| 149 |
|
| 150 |
if csv_file is None:
|
| 151 |
+
return "β οΈ Please upload a CSV file.", _quota_status(request), gr.update(), gr.update()
|
| 152 |
|
| 153 |
csv_path = csv_file if isinstance(csv_file, str) else csv_file.name
|
| 154 |
csv_filename = Path(csv_path).name
|
| 155 |
if Path(csv_path).suffix.lower() != ".csv":
|
| 156 |
+
return "β οΈ File must be a CSV (.csv).", _quota_status(request), gr.update(), gr.update()
|
| 157 |
|
| 158 |
count = submissions_by_user(hf_username)
|
| 159 |
if count >= MAX_TRACK1_SUBMISSIONS:
|
|
|
|
| 161 |
f"β οΈ You have already used all {MAX_TRACK1_SUBMISSIONS} submissions. "
|
| 162 |
"Only your best-scoring submission counts toward final ranking.",
|
| 163 |
_quota_status(request),
|
| 164 |
+
gr.update(),
|
| 165 |
+
gr.update(),
|
| 166 |
)
|
| 167 |
model_n = count + 1
|
| 168 |
|
|
|
|
| 172 |
else:
|
| 173 |
csv_bytes = csv_file.read() if hasattr(csv_file, "read") else Path(csv_file.name).read_bytes()
|
| 174 |
except Exception as e:
|
| 175 |
+
return f"β οΈ Could not read uploaded file: {e}", _quota_status(request), gr.update(), gr.update()
|
| 176 |
|
| 177 |
try:
|
| 178 |
result, proband_id = _score_csv_bytes(csv_bytes)
|
|
|
|
| 182 |
f"β **Scoring error:** {e}\n\n"
|
| 183 |
f"<details><summary>Technical details</summary>\n\n```\n{tb}\n```\n</details>",
|
| 184 |
_quota_status(request),
|
| 185 |
+
gr.update(),
|
| 186 |
+
gr.update(),
|
| 187 |
)
|
| 188 |
|
| 189 |
entry = {
|
|
|
|
| 228 |
|
| 229 |
{match_desc}
|
| 230 |
"""
|
| 231 |
+
return feedback.strip(), _quota_status(request), None, None
|
| 232 |
|
| 233 |
|
| 234 |
def _quota_status(request: gr.Request, oauth_profile: gr.OAuthProfile | None = None) -> str:
|
|
|
|
| 251 |
quota_md = gr.HTML()
|
| 252 |
with gr.Accordion("π Submission Format & Instructions", open=True, elem_classes=["faq-accordion"]):
|
| 253 |
gr.Markdown("**Templates:**")
|
| 254 |
+
gr.Markdown("<small>*Last updated 28 Aug 2026*</small>")
|
| 255 |
with gr.Row():
|
| 256 |
gr.DownloadButton(
|
| 257 |
label="π Submission template (CSV)",
|
|
|
|
| 290 |
submit_btn.click(
|
| 291 |
fn=_handle_submit,
|
| 292 |
inputs=[team_name_box, github_box, report_file, csv_file],
|
| 293 |
+
outputs=[result_md, quota_md, report_file, csv_file],
|
| 294 |
)
|
| 295 |
tab.select(fn=_quota_status, inputs=[], outputs=quota_md)
|
| 296 |
return quota_md
|
tabs/submit_track2.py
CHANGED
|
@@ -2,25 +2,29 @@
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
-
import io
|
| 6 |
-
import json
|
| 7 |
-
import os
|
| 8 |
from datetime import datetime, timezone
|
| 9 |
from pathlib import Path
|
| 10 |
|
| 11 |
import gradio as gr
|
| 12 |
|
| 13 |
-
from config import
|
| 14 |
-
from utils import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
INTRO_MD = """
|
| 17 |
Upload your Track 2 proposal here for review by our independent expert judging panel. Unlike Track 1,
|
| 18 |
this track uses qualitative evaluation rather than automated scoring.
|
| 19 |
|
| 20 |
-
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
**Team Participation:** Please designate a single team member to submit on the team's behalf. Additional
|
| 23 |
-
|
| 24 |
"""
|
| 25 |
|
| 26 |
INSTRUCTIONS_MD = """
|
|
@@ -30,6 +34,10 @@ Prepare a written report (PDF or Markdown) proposing repositioned drug candidate
|
|
| 30 |
analysis. Include a characterization of the variant's mechanism (loss-of-function / gain-of-function,
|
| 31 |
pathway disrupted, downstream biological consequence) as the basis for your repurposing rationale.
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
Not sure what details to include? Please use the provided methods description template to organize your
|
| 34 |
analysis and supporting materials, then export your report as a PDF or Markdown file for submission.
|
| 35 |
|
|
@@ -40,9 +48,19 @@ HF username is `jane-doe`, you might name your report file:
|
|
| 40 |
|
| 41 |
### 2. Prepare other supporting materials.
|
| 42 |
|
| 43 |
-
*
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
### 3. After submission:
|
| 48 |
|
|
@@ -50,70 +68,6 @@ The independent panel will review all entries over a ~2-3 month window. Results
|
|
| 50 |
later date.
|
| 51 |
"""
|
| 52 |
|
| 53 |
-
def _save_track2_submission(entry: dict, report_path: str) -> None:
|
| 54 |
-
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S_%f")
|
| 55 |
-
safe_user = _sanitize(entry.get("hf_username", "unknown"))
|
| 56 |
-
subfolder = f"{TRACK2_SUBMISSIONS_FOLDER}{safe_user}/"
|
| 57 |
-
filename = f"sub_{timestamp}.json"
|
| 58 |
-
payload = json.dumps(entry, indent=2).encode()
|
| 59 |
-
token = os.environ.get("HF_TOKEN")
|
| 60 |
-
if token:
|
| 61 |
-
from huggingface_hub import upload_file
|
| 62 |
-
upload_file(
|
| 63 |
-
path_or_fileobj=io.BytesIO(payload),
|
| 64 |
-
path_in_repo=f"{subfolder}{filename}",
|
| 65 |
-
repo_id=LEADERBOARD_DATASET,
|
| 66 |
-
repo_type="dataset",
|
| 67 |
-
token=token,
|
| 68 |
-
commit_message=f"track2: {entry.get('display_name', entry.get('hf_username', '?'))}",
|
| 69 |
-
)
|
| 70 |
-
upload_file(
|
| 71 |
-
path_or_fileobj=report_path,
|
| 72 |
-
path_in_repo=f"{subfolder}{entry['report_filename']}",
|
| 73 |
-
repo_id=LEADERBOARD_DATASET,
|
| 74 |
-
repo_type="dataset",
|
| 75 |
-
token=token,
|
| 76 |
-
commit_message=f"track2 report: {entry.get('display_name', entry.get('hf_username', '?'))}",
|
| 77 |
-
)
|
| 78 |
-
else:
|
| 79 |
-
local_dir = _LOCAL_DEV_DIR / "track2_submissions" / safe_user
|
| 80 |
-
local_dir.mkdir(parents=True, exist_ok=True)
|
| 81 |
-
(local_dir / filename).write_bytes(payload)
|
| 82 |
-
import shutil
|
| 83 |
-
shutil.copy(report_path, local_dir / entry['report_filename'])
|
| 84 |
-
|
| 85 |
-
def _track2_submissions_by_user(hf_username: str) -> int:
|
| 86 |
-
"""Count Track 2 submissions for a given HF user."""
|
| 87 |
-
safe_user = _sanitize(hf_username)
|
| 88 |
-
token = os.environ.get("HF_TOKEN")
|
| 89 |
-
if token:
|
| 90 |
-
from huggingface_hub import HfApi
|
| 91 |
-
api = HfApi()
|
| 92 |
-
try:
|
| 93 |
-
return sum(
|
| 94 |
-
1 for f in api.list_repo_files(
|
| 95 |
-
LEADERBOARD_DATASET, repo_type="dataset", token=token
|
| 96 |
-
)
|
| 97 |
-
if f.startswith(f"{TRACK2_SUBMISSIONS_FOLDER}{safe_user}/sub_") and f.endswith(".json")
|
| 98 |
-
)
|
| 99 |
-
except Exception:
|
| 100 |
-
return 0
|
| 101 |
-
# Local dev fallback
|
| 102 |
-
local_dir = _LOCAL_DEV_DIR / "track2_submissions" / safe_user
|
| 103 |
-
if not local_dir.exists():
|
| 104 |
-
return 0
|
| 105 |
-
return sum(1 for _ in local_dir.glob("sub_*.json"))
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
def _submission_status(request: gr.Request, oauth_profile: gr.OAuthProfile | None = None) -> str:
|
| 109 |
-
hf_username = get_hf_username(request, oauth_profile)
|
| 110 |
-
if not hf_username:
|
| 111 |
-
return ""
|
| 112 |
-
count = _track2_submissions_by_user(hf_username)
|
| 113 |
-
if count >= 1:
|
| 114 |
-
return '<div class="info-card quota-card">β
<strong>Track 2 submission received.</strong> Only one submission per team is accepted.</div>'
|
| 115 |
-
return '<div class="info-card quota-card"><strong>0 / 1</strong> submissions used Β· <strong>1</strong> remaining</div>'
|
| 116 |
-
|
| 117 |
|
| 118 |
def _handle_submit(
|
| 119 |
display_name_input: str,
|
|
@@ -123,29 +77,37 @@ def _handle_submit(
|
|
| 123 |
notes: str,
|
| 124 |
request: gr.Request,
|
| 125 |
oauth_profile: gr.OAuthProfile | None = None,
|
| 126 |
-
) ->
|
|
|
|
| 127 |
hf_username = get_hf_username(request, oauth_profile)
|
| 128 |
if not hf_username:
|
| 129 |
-
return "β οΈ Please sign in with your Hugging Face account to submit."
|
| 130 |
|
| 131 |
display_name = (display_name_input or "").strip() or hf_username_to_display_slug(hf_username)
|
| 132 |
|
| 133 |
-
count = _track2_submissions_by_user(hf_username)
|
| 134 |
-
if count >= 1:
|
| 135 |
-
return "β οΈ You have already submitted a Track 2 entry. Only one submission per participant is accepted."
|
| 136 |
-
|
| 137 |
github_url = (github_url or "").strip()
|
| 138 |
if not github_url.startswith("https://github.com/"):
|
| 139 |
-
return "β οΈ GitHub URL must start with `https://github.com/`."
|
|
|
|
| 140 |
video_url = (video_url or "").strip()
|
| 141 |
if not video_url:
|
| 142 |
-
return "β οΈ A pitch video URL is required. Please upload your 3-minute video to YouTube or Vimeo and paste the link."
|
|
|
|
| 143 |
if report_file is None:
|
| 144 |
-
return "β οΈ Please upload your report (PDF or Markdown)."
|
| 145 |
|
| 146 |
report_path = report_file if isinstance(report_file, str) else report_file.name
|
| 147 |
if Path(report_path).suffix.lower() not in {".pdf", ".md"}:
|
| 148 |
-
return "β οΈ Report must be a PDF (.pdf) or Markdown (.md) file."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
report_name = Path(
|
| 151 |
report_file if isinstance(report_file, str) else report_file.name
|
|
@@ -160,14 +122,14 @@ def _handle_submit(
|
|
| 160 |
"notes": (notes or "").strip(),
|
| 161 |
"submitted_at": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC"),
|
| 162 |
}
|
| 163 |
-
|
| 164 |
|
| 165 |
video_line = f"[{entry['video_url']}]({entry['video_url']})" if entry["video_url"] else "_(not provided)_"
|
| 166 |
|
| 167 |
-
|
| 168 |
## Track 2 submission received β
|
| 169 |
|
| 170 |
-
**{display_name}** (`{hf_username}`)
|
| 171 |
|
| 172 |
Your submission has been logged and will be reviewed by the expert judging panel over the
|
| 173 |
~2-3 month judging window. Results will be announced after the judging period closes.
|
|
@@ -176,15 +138,31 @@ Your submission has been logged and will be reviewed by the expert judging panel
|
|
| 176 |
- GitHub repository: [{github_url}]({github_url})
|
| 177 |
- Video: {video_line}
|
| 178 |
- Report file: `{report_name}`
|
| 179 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
|
| 182 |
def render() -> None:
|
| 183 |
with gr.Tab("Submit - Track 2") as tab:
|
| 184 |
gr.Markdown(INTRO_MD)
|
| 185 |
-
|
| 186 |
with gr.Accordion("π Submission Format & Instructions", open=True, elem_classes=["faq-accordion"]):
|
| 187 |
gr.Markdown("**Templates:**")
|
|
|
|
| 188 |
with gr.Row():
|
| 189 |
gr.DownloadButton(
|
| 190 |
label="π Methods description template (Excel)",
|
|
@@ -225,7 +203,7 @@ def render() -> None:
|
|
| 225 |
submit_btn.click(
|
| 226 |
fn=_handle_submit,
|
| 227 |
inputs=[team_name_box, github_box, video_box, report_file, notes_box],
|
| 228 |
-
outputs=result_md,
|
| 229 |
)
|
| 230 |
-
tab.select(fn=
|
| 231 |
-
return
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
|
|
|
|
|
|
| 5 |
from datetime import datetime, timezone
|
| 6 |
from pathlib import Path
|
| 7 |
|
| 8 |
import gradio as gr
|
| 9 |
|
| 10 |
+
from config import MAX_TRACK2_SUBMISSIONS
|
| 11 |
+
from utils import (
|
| 12 |
+
append_submission,
|
| 13 |
+
get_hf_username,
|
| 14 |
+
hf_username_to_display_slug,
|
| 15 |
+
submissions_by_user,
|
| 16 |
+
)
|
| 17 |
|
| 18 |
+
INTRO_MD = f"""
|
| 19 |
Upload your Track 2 proposal here for review by our independent expert judging panel. Unlike Track 1,
|
| 20 |
this track uses qualitative evaluation rather than automated scoring.
|
| 21 |
|
| 22 |
+
You are allowed up to <u>{MAX_TRACK2_SUBMISSIONS} submissions</u> in case you need to make updates to your
|
| 23 |
+
findings/methods. The panel will only review your latest entry, so make sure your final submission is the one you
|
| 24 |
+
want reviewed.
|
| 25 |
|
| 26 |
+
**Team Participation:** Please designate a single team member to submit on the team's behalf. Additional or duplicate
|
| 27 |
+
submissions from other team members will not be reviewed.
|
| 28 |
"""
|
| 29 |
|
| 30 |
INSTRUCTIONS_MD = """
|
|
|
|
| 34 |
analysis. Include a characterization of the variant's mechanism (loss-of-function / gain-of-function,
|
| 35 |
pathway disrupted, downstream biological consequence) as the basis for your repurposing rationale.
|
| 36 |
|
| 37 |
+
> **Update 28 Aug 2026:** If you used an LLM or AI assistant, please record the provider, the plan or tier, and the
|
| 38 |
+
> relevant data-handling setting in your methods description. A line is enough, for example: *"Anthropic API, Claude
|
| 39 |
+
> Sonnet, commercial terms, no training on customer content."*
|
| 40 |
+
|
| 41 |
Not sure what details to include? Please use the provided methods description template to organize your
|
| 42 |
analysis and supporting materials, then export your report as a PDF or Markdown file for submission.
|
| 43 |
|
|
|
|
| 48 |
|
| 49 |
### 2. Prepare other supporting materials.
|
| 50 |
|
| 51 |
+
**GitHub Repository**
|
| 52 |
+
|
| 53 |
+
Your repository may remain private while the Hackathon is running. However, it must be made public once the
|
| 54 |
+
Hackathon ends so the panel can review your code and methods.
|
| 55 |
+
|
| 56 |
+
What to include:
|
| 57 |
+
- Documented, reproducible code (all scripts and configuration files needed to reproduce your results)
|
| 58 |
+
- Methods description *(optional but recommended)*
|
| 59 |
+
|
| 60 |
+
**Pitch Video**
|
| 61 |
+
|
| 62 |
+
Record a 3-minute pitch video walking through your reasoning and proposed candidates, then upload it to
|
| 63 |
+
YouTube or Vimeo.
|
| 64 |
|
| 65 |
### 3. After submission:
|
| 66 |
|
|
|
|
| 68 |
later date.
|
| 69 |
"""
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
def _handle_submit(
|
| 73 |
display_name_input: str,
|
|
|
|
| 77 |
notes: str,
|
| 78 |
request: gr.Request,
|
| 79 |
oauth_profile: gr.OAuthProfile | None = None,
|
| 80 |
+
) -> tuple:
|
| 81 |
+
"""Gradio callback - returns (feedback_md, quota_md, report_file)."""
|
| 82 |
hf_username = get_hf_username(request, oauth_profile)
|
| 83 |
if not hf_username:
|
| 84 |
+
return "β οΈ Please sign in with your Hugging Face account to submit.", "", gr.update()
|
| 85 |
|
| 86 |
display_name = (display_name_input or "").strip() or hf_username_to_display_slug(hf_username)
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
github_url = (github_url or "").strip()
|
| 89 |
if not github_url.startswith("https://github.com/"):
|
| 90 |
+
return "β οΈ GitHub URL must start with `https://github.com/`.", _quota_status(request), gr.update()
|
| 91 |
+
|
| 92 |
video_url = (video_url or "").strip()
|
| 93 |
if not video_url:
|
| 94 |
+
return "β οΈ A pitch video URL is required. Please upload your 3-minute video to YouTube or Vimeo and paste the link.", _quota_status(request), gr.update()
|
| 95 |
+
|
| 96 |
if report_file is None:
|
| 97 |
+
return "β οΈ Please upload your report (PDF or Markdown).", _quota_status(request), gr.update()
|
| 98 |
|
| 99 |
report_path = report_file if isinstance(report_file, str) else report_file.name
|
| 100 |
if Path(report_path).suffix.lower() not in {".pdf", ".md"}:
|
| 101 |
+
return "β οΈ Report must be a PDF (.pdf) or Markdown (.md) file.", _quota_status(request), gr.update()
|
| 102 |
+
|
| 103 |
+
count = submissions_by_user(hf_username, track=2)
|
| 104 |
+
if count >= MAX_TRACK2_SUBMISSIONS:
|
| 105 |
+
return (
|
| 106 |
+
f"β οΈ You have already used all {MAX_TRACK2_SUBMISSIONS} submissions.",
|
| 107 |
+
_quota_status(request),
|
| 108 |
+
gr.update(),
|
| 109 |
+
)
|
| 110 |
+
submission_n = count + 1
|
| 111 |
|
| 112 |
report_name = Path(
|
| 113 |
report_file if isinstance(report_file, str) else report_file.name
|
|
|
|
| 122 |
"notes": (notes or "").strip(),
|
| 123 |
"submitted_at": datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC"),
|
| 124 |
}
|
| 125 |
+
append_submission(entry, report_path, track=2)
|
| 126 |
|
| 127 |
video_line = f"[{entry['video_url']}]({entry['video_url']})" if entry["video_url"] else "_(not provided)_"
|
| 128 |
|
| 129 |
+
feedback = f"""
|
| 130 |
## Track 2 submission received β
|
| 131 |
|
| 132 |
+
**{display_name}** (`{hf_username}`) | **Submission:** {submission_n}
|
| 133 |
|
| 134 |
Your submission has been logged and will be reviewed by the expert judging panel over the
|
| 135 |
~2-3 month judging window. Results will be announced after the judging period closes.
|
|
|
|
| 138 |
- GitHub repository: [{github_url}]({github_url})
|
| 139 |
- Video: {video_line}
|
| 140 |
- Report file: `{report_name}`
|
| 141 |
+
"""
|
| 142 |
+
return feedback.strip(), _quota_status(request), None
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def _quota_status(request: gr.Request, oauth_profile: gr.OAuthProfile | None = None) -> str:
|
| 146 |
+
hf_username = get_hf_username(request, oauth_profile)
|
| 147 |
+
if not hf_username:
|
| 148 |
+
return ""
|
| 149 |
+
count = submissions_by_user(hf_username, track=2)
|
| 150 |
+
remaining = MAX_TRACK2_SUBMISSIONS - count
|
| 151 |
+
return (
|
| 152 |
+
f'<div class="info-card quota-card">'
|
| 153 |
+
f'<strong>{count} / {MAX_TRACK2_SUBMISSIONS}</strong> submissions used Β· '
|
| 154 |
+
f'<strong>{remaining}</strong> remaining'
|
| 155 |
+
f'</div>'
|
| 156 |
+
)
|
| 157 |
|
| 158 |
|
| 159 |
def render() -> None:
|
| 160 |
with gr.Tab("Submit - Track 2") as tab:
|
| 161 |
gr.Markdown(INTRO_MD)
|
| 162 |
+
quota_md = gr.HTML()
|
| 163 |
with gr.Accordion("π Submission Format & Instructions", open=True, elem_classes=["faq-accordion"]):
|
| 164 |
gr.Markdown("**Templates:**")
|
| 165 |
+
gr.Markdown("<small>*Last updated 28 Aug 2026*</small>")
|
| 166 |
with gr.Row():
|
| 167 |
gr.DownloadButton(
|
| 168 |
label="π Methods description template (Excel)",
|
|
|
|
| 203 |
submit_btn.click(
|
| 204 |
fn=_handle_submit,
|
| 205 |
inputs=[team_name_box, github_box, video_box, report_file, notes_box],
|
| 206 |
+
outputs=[result_md, quota_md, report_file],
|
| 207 |
)
|
| 208 |
+
tab.select(fn=_quota_status, inputs=[], outputs=quota_md)
|
| 209 |
+
return quota_md
|
utils.py
CHANGED
|
@@ -17,6 +17,7 @@ from pathlib import Path
|
|
| 17 |
from config import (
|
| 18 |
LEADERBOARD_DATASET,
|
| 19 |
TRACK1_SUBMISSIONS_FOLDER,
|
|
|
|
| 20 |
)
|
| 21 |
|
| 22 |
_LOCAL_DEV_DIR = Path("local_dev")
|
|
@@ -58,18 +59,34 @@ def hf_username_to_display_slug(username: str) -> str:
|
|
| 58 |
|
| 59 |
# ββ Submissions ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
|
| 65 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
"""
|
| 67 |
Store a single submission as its own JSON file in the HF dataset (or locally in dev).
|
| 68 |
Optionally also saves a report file alongside the JSON.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
"""
|
|
|
|
|
|
|
|
|
|
| 70 |
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S_%f")
|
| 71 |
safe_user = _sanitize(entry.get("hf_username", "unknown"))
|
| 72 |
-
subfolder = f"{
|
| 73 |
filename = f"sub_{timestamp}.json"
|
| 74 |
|
| 75 |
# Stamp the report filename with the same timestamp as the metadata.
|
|
@@ -89,7 +106,7 @@ def append_submission(entry: dict, report_path: str | None = None) -> None:
|
|
| 89 |
repo_id=LEADERBOARD_DATASET,
|
| 90 |
repo_type="dataset",
|
| 91 |
token=token,
|
| 92 |
-
commit_message=f"
|
| 93 |
)
|
| 94 |
if report_path and entry.get("report_filename"):
|
| 95 |
upload_file(
|
|
@@ -98,11 +115,11 @@ def append_submission(entry: dict, report_path: str | None = None) -> None:
|
|
| 98 |
repo_id=LEADERBOARD_DATASET,
|
| 99 |
repo_type="dataset",
|
| 100 |
token=token,
|
| 101 |
-
commit_message=f"
|
| 102 |
)
|
| 103 |
else:
|
| 104 |
import shutil
|
| 105 |
-
local_dir = _local_submissions_dir() / safe_user
|
| 106 |
local_dir.mkdir(parents=True, exist_ok=True)
|
| 107 |
(local_dir / filename).write_bytes(payload)
|
| 108 |
if report_path and entry.get("report_filename"):
|
|
@@ -150,7 +167,7 @@ def load_leaderboard(force_refresh: bool = False) -> list[dict]:
|
|
| 150 |
|
| 151 |
# Local dev fallback
|
| 152 |
rows = []
|
| 153 |
-
for f in sorted(_local_submissions_dir().glob("**/sub_*.json")):
|
| 154 |
try:
|
| 155 |
rows.append(json.loads(f.read_text()))
|
| 156 |
except Exception:
|
|
@@ -185,9 +202,10 @@ def best_per_team(rows: list[dict]) -> list[dict]:
|
|
| 185 |
return [{**r, "place": i} for i, r in enumerate(sorted_rows, 1)]
|
| 186 |
|
| 187 |
|
| 188 |
-
def _list_user_files(
|
| 189 |
-
"""Return sorted list of JSON submission paths for a user in the given
|
| 190 |
safe_user = _sanitize(hf_username)
|
|
|
|
| 191 |
prefix = f"{folder}{safe_user}/sub_"
|
| 192 |
token = os.environ.get("HF_TOKEN")
|
| 193 |
if token:
|
|
@@ -199,15 +217,15 @@ def _list_user_files(folder: str, hf_username: str) -> list[str]:
|
|
| 199 |
)
|
| 200 |
except Exception:
|
| 201 |
return []
|
| 202 |
-
user_dir = _local_submissions_dir() / safe_user
|
| 203 |
if not user_dir.exists():
|
| 204 |
return []
|
| 205 |
return sorted(str(f) for f in user_dir.glob("sub_*.json"))
|
| 206 |
|
| 207 |
|
| 208 |
-
def submissions_by_user(hf_username: str) -> int:
|
| 209 |
-
"""Return the number of
|
| 210 |
-
return len(_list_user_files(
|
| 211 |
|
| 212 |
|
| 213 |
def leaderboard_df() -> list[list]:
|
|
|
|
| 17 |
from config import (
|
| 18 |
LEADERBOARD_DATASET,
|
| 19 |
TRACK1_SUBMISSIONS_FOLDER,
|
| 20 |
+
TRACK2_SUBMISSIONS_FOLDER,
|
| 21 |
)
|
| 22 |
|
| 23 |
_LOCAL_DEV_DIR = Path("local_dev")
|
|
|
|
| 59 |
|
| 60 |
# ββ Submissions ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 61 |
|
| 62 |
+
# Track folder mapping
|
| 63 |
+
_TRACK_FOLDERS = {
|
| 64 |
+
1: TRACK1_SUBMISSIONS_FOLDER,
|
| 65 |
+
2: TRACK2_SUBMISSIONS_FOLDER,
|
| 66 |
+
}
|
| 67 |
|
| 68 |
|
| 69 |
+
def _local_submissions_dir(track: int = 1) -> Path:
|
| 70 |
+
folder_name = f"track{track}_submissions"
|
| 71 |
+
return _LOCAL_DEV_DIR / folder_name
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def append_submission(entry: dict, report_path: str | None = None, *, track: int = 1) -> None:
|
| 75 |
"""
|
| 76 |
Store a single submission as its own JSON file in the HF dataset (or locally in dev).
|
| 77 |
Optionally also saves a report file alongside the JSON.
|
| 78 |
+
|
| 79 |
+
Args:
|
| 80 |
+
entry: Submission metadata dict
|
| 81 |
+
report_path: Optional path to a report file to upload
|
| 82 |
+
track: Track number (1 or 2)
|
| 83 |
"""
|
| 84 |
+
submissions_folder = _TRACK_FOLDERS[track]
|
| 85 |
+
track_label = f"track{track}"
|
| 86 |
+
|
| 87 |
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S_%f")
|
| 88 |
safe_user = _sanitize(entry.get("hf_username", "unknown"))
|
| 89 |
+
subfolder = f"{submissions_folder}{safe_user}/"
|
| 90 |
filename = f"sub_{timestamp}.json"
|
| 91 |
|
| 92 |
# Stamp the report filename with the same timestamp as the metadata.
|
|
|
|
| 106 |
repo_id=LEADERBOARD_DATASET,
|
| 107 |
repo_type="dataset",
|
| 108 |
token=token,
|
| 109 |
+
commit_message=f"{track_label}: {entry.get('display_name', entry.get('hf_username', '?'))}",
|
| 110 |
)
|
| 111 |
if report_path and entry.get("report_filename"):
|
| 112 |
upload_file(
|
|
|
|
| 115 |
repo_id=LEADERBOARD_DATASET,
|
| 116 |
repo_type="dataset",
|
| 117 |
token=token,
|
| 118 |
+
commit_message=f"{track_label} report: {entry.get('display_name', entry.get('hf_username', '?'))}",
|
| 119 |
)
|
| 120 |
else:
|
| 121 |
import shutil
|
| 122 |
+
local_dir = _local_submissions_dir(track) / safe_user
|
| 123 |
local_dir.mkdir(parents=True, exist_ok=True)
|
| 124 |
(local_dir / filename).write_bytes(payload)
|
| 125 |
if report_path and entry.get("report_filename"):
|
|
|
|
| 167 |
|
| 168 |
# Local dev fallback
|
| 169 |
rows = []
|
| 170 |
+
for f in sorted(_local_submissions_dir(track=1).glob("**/sub_*.json")):
|
| 171 |
try:
|
| 172 |
rows.append(json.loads(f.read_text()))
|
| 173 |
except Exception:
|
|
|
|
| 202 |
return [{**r, "place": i} for i, r in enumerate(sorted_rows, 1)]
|
| 203 |
|
| 204 |
|
| 205 |
+
def _list_user_files(hf_username: str, *, track: int = 1) -> list[str]:
|
| 206 |
+
"""Return sorted list of JSON submission paths for a user in the given track."""
|
| 207 |
safe_user = _sanitize(hf_username)
|
| 208 |
+
folder = _TRACK_FOLDERS[track]
|
| 209 |
prefix = f"{folder}{safe_user}/sub_"
|
| 210 |
token = os.environ.get("HF_TOKEN")
|
| 211 |
if token:
|
|
|
|
| 217 |
)
|
| 218 |
except Exception:
|
| 219 |
return []
|
| 220 |
+
user_dir = _local_submissions_dir(track) / safe_user
|
| 221 |
if not user_dir.exists():
|
| 222 |
return []
|
| 223 |
return sorted(str(f) for f in user_dir.glob("sub_*.json"))
|
| 224 |
|
| 225 |
|
| 226 |
+
def submissions_by_user(hf_username: str, *, track: int = 1) -> int:
|
| 227 |
+
"""Return the number of submissions made by a given HF user for the specified track."""
|
| 228 |
+
return len(_list_user_files(hf_username, track=track))
|
| 229 |
|
| 230 |
|
| 231 |
def leaderboard_df() -> list[list]:
|