feature/add_hf_auth

#3
by EmmaScharfmann HF Staff - opened
about.py CHANGED
@@ -5,12 +5,12 @@ from huggingface_hub import HfApi
5
  load_dotenv()
6
 
7
  TOKEN = os.environ.get("HF_TOKEN")
8
- CACHE_PATH=os.getenv("HF_HOME", ".")
9
  API = HfApi(token=TOKEN)
10
 
11
- CHALLENGE_NAME="MecCogChallenge"
12
- _ORGANIZATION="EmmaScharfmann"
13
 
14
- SUBMISSIONS_REPO = f'{_ORGANIZATION}/{CHALLENGE_NAME}Submissions'
15
- RESULTS_REPO = f'{_ORGANIZATION}/{CHALLENGE_NAME}Results'
16
- REGISTRATION_REPO = f'{_ORGANIZATION}/{CHALLENGE_NAME}Registrations'
 
5
  load_dotenv()
6
 
7
  TOKEN = os.environ.get("HF_TOKEN")
8
+ CACHE_PATH = os.getenv("HF_HOME", ".")
9
  API = HfApi(token=TOKEN)
10
 
11
+ CHALLENGE_NAME = "MecCogChallenge"
12
+ _ORGANIZATION = "MecCog"
13
 
14
+ SUBMISSIONS_REPO = f"{_ORGANIZATION}/{CHALLENGE_NAME}"
15
+ RESULTS_REPO = f"{_ORGANIZATION}/{CHALLENGE_NAME}"
16
+ REGISTRATION_REPO = f"{_ORGANIZATION}/{CHALLENGE_NAME}"
app.py CHANGED
@@ -7,16 +7,29 @@ from about import CHALLENGE_NAME
7
 
8
  PROBLEM_TYPES = ["hypothesis 1", "hypothesis 2", "hypothesis 3"]
9
 
 
10
  def gradio_interface() -> gr.Blocks:
11
  with gr.Blocks() as demo:
12
  gr.Markdown(f"## Welcome to the {CHALLENGE_NAME}!")
13
- with gr.Tabs(elem_classes="tab-buttons"):
 
 
 
14
  challenge_page.get_description(gr=gr)
15
- registration_page.get_registration_page(gr=gr)
16
- submission_page.get_submission_page(gr=gr)
17
  faq.get_faq(gr=gr)
18
  leaderboard_page.get_leaderboard(gr=gr)
19
-
 
 
 
 
 
 
 
 
 
20
  return demo
21
 
22
 
 
7
 
8
  PROBLEM_TYPES = ["hypothesis 1", "hypothesis 2", "hypothesis 3"]
9
 
10
+
11
  def gradio_interface() -> gr.Blocks:
12
  with gr.Blocks() as demo:
13
  gr.Markdown(f"## Welcome to the {CHALLENGE_NAME}!")
14
+
15
+ active_tab = gr.BrowserState(0)
16
+
17
+ with gr.Tabs(elem_classes="tab-buttons") as tabs:
18
  challenge_page.get_description(gr=gr)
19
+ registration_page.get_registration_page(gr=gr, demo=demo)
20
+ submission_page.get_submission_page(gr=gr, demo=demo)
21
  faq.get_faq(gr=gr)
22
  leaderboard_page.get_leaderboard(gr=gr)
23
+
24
+ def restore_tab(idx):
25
+ return gr.Tabs(selected=idx)
26
+
27
+ def save_tab(evt: gr.SelectData):
28
+ return evt.index
29
+
30
+ tabs.select(fn=save_tab, inputs=None, outputs=[active_tab])
31
+ demo.load(fn=restore_tab, inputs=[active_tab], outputs=[tabs])
32
+
33
  return demo
34
 
35
 
components/challenge_page.py CHANGED
@@ -16,5 +16,5 @@ CHALLENGE_DESCRIPTION = f"""
16
 
17
 
18
  def get_description(gr):
19
- with gr.TabItem(TAB_TITLE, elem_id="boundary-benchmark-tab-table"):
20
- gr.Markdown(CHALLENGE_DESCRIPTION)
 
16
 
17
 
18
  def get_description(gr):
19
+ with gr.TabItem(TAB_TITLE, elem_id="boundary-benchmark-tab-table", id=0):
20
+ gr.Markdown(CHALLENGE_DESCRIPTION)
components/registration/config.py CHANGED
@@ -38,13 +38,15 @@ TEAMS_FILE_NAME = "teams.csv"
38
  # Schema – column names and dtypes for each dataset
39
  # ---------------------------------------------------------------------------
40
  PARTICIPANT_COLUMNS: list[str] = [
 
41
  "email",
42
  "name",
43
  "affiliation",
44
  "discord",
45
  "self_description",
46
  "needs_manual_review",
47
- "team_memberships",
 
48
  "registered_at",
49
  "updated_at",
50
  ]
@@ -52,9 +54,9 @@ PARTICIPANT_COLUMNS: list[str] = [
52
  TEAM_COLUMNS: list[str] = [
53
  "team_name",
54
  "team_description",
55
- "leader_email",
56
- "member_emails", # JSON-encoded list[str]
57
  "second_team_reason",
58
  "created_at",
59
  "updated_at",
60
- ]
 
38
  # Schema – column names and dtypes for each dataset
39
  # ---------------------------------------------------------------------------
40
  PARTICIPANT_COLUMNS: list[str] = [
41
+ "username",
42
  "email",
43
  "name",
44
  "affiliation",
45
  "discord",
46
  "self_description",
47
  "needs_manual_review",
48
+ "team_name",
49
+ "is_leader",
50
  "registered_at",
51
  "updated_at",
52
  ]
 
54
  TEAM_COLUMNS: list[str] = [
55
  "team_name",
56
  "team_description",
57
+ "leader_username",
58
+ "members_username", # JSON-encoded list[str]
59
  "second_team_reason",
60
  "created_at",
61
  "updated_at",
62
+ ]
components/registration/registration_page.py CHANGED
@@ -4,6 +4,7 @@ import json
4
  import logging
5
  from datetime import datetime, timezone
6
 
 
7
  import pandas as pd
8
 
9
  from about import REGISTRATION_REPO
@@ -16,13 +17,17 @@ from components.registration.config import (
16
  TEAM_COLUMNS,
17
  )
18
  from components.registration.utils import (
19
- get_team_membership_entry,
20
  validate_email_domain,
21
  is_institutional_email,
22
  validate_email_format,
23
  get_user_teams,
24
  )
25
- from components.utils import push_data_to_dataset, load_data_from_dataset, get_team
 
 
 
 
 
26
 
27
  logger = logging.getLogger(__name__)
28
 
@@ -32,10 +37,12 @@ def _validate_registration_input(data: dict[str, str]) -> list[str]:
32
  errors: list[str] = []
33
  email = (data.get("email") or "").strip()
34
  name = (data.get("name") or "").strip()
 
35
  affiliation = (data.get("affiliation") or "").strip()
36
  role = (data.get("role") or "").strip()
37
 
38
- # Email is always required
 
39
  if not email:
40
  errors.append("An email address is required.")
41
  elif not validate_email_format(email):
@@ -79,6 +86,7 @@ def register_participant(data: dict) -> tuple[bool, str]:
79
  Persist a participant registration to HuggingFace datasets.
80
 
81
  ``data`` keys (all str unless noted):
 
82
  email – always required
83
  name – always required
84
  affiliation – always required
@@ -98,18 +106,19 @@ def register_participant(data: dict) -> tuple[bool, str]:
98
  if errors:
99
  return False, "❌ Validation failed:\n" + "\n".join(f" • {e}" for e in errors)
100
 
101
- email = data["email"].strip().lower()
102
- name = data["name"].strip().lower()
103
- affiliation = data["affiliation"].strip().lower()
104
- role = data["role"].strip().lower()
 
 
105
  self_description = (data.get("self_description") or "").strip()
106
  is_new_team: bool = bool(data.get("is_new_team", False))
107
- team_name = data["team_name"].strip().lower()
108
  team_description = (data.get("team_description") or "").strip()
109
  is_leader: bool = bool(data.get("is_leader", False))
110
  second_team_reason = (data.get("second_team_reason") or "").strip()
111
  institutional = is_institutional_email(email=email)
112
- needs_manual_review = not institutional
113
  now = datetime.now(timezone.utc).isoformat()
114
 
115
  # ── 2. Load both datasets ────────────────────────────────────────────────
@@ -133,34 +142,40 @@ def register_participant(data: dict) -> tuple[bool, str]:
133
  "❌ This team already exists. Please provide another Team name, or join an existing team. .",
134
  )
135
 
136
- # ── 3. Resolve participant row (upsert) ──────────────────────────────────
137
- existing_participant = participants_df[participants_df["email"] == email]
138
  is_new_participant = existing_participant.empty
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  if is_new_participant:
141
- existing_memberships: list[dict] = []
142
  registered_at = now
143
  else:
144
- existing_memberships = json.loads(
145
- existing_participant.iloc[0].get("team_memberships") or "[]"
146
- )
147
  registered_at = existing_participant.iloc[0].get("registered_at", now)
148
-
149
- already_has_team = len(existing_memberships) > 0
150
- if already_has_team and not second_team_reason:
151
- return (
152
- False,
153
- "❌ This user is already part of a team. Please provide a reason for joining a second team.",
154
- )
155
-
156
  # ── 4. Resolve team ─────���────────────────────────────────────────────────
157
  if is_new_team:
158
  # ── 4a. Creating a new team ──────────────────────────────────────────
159
  new_team_row = {
160
  "team_name": team_name,
161
  "team_description": team_description,
162
- "leader_email": email if is_leader else "",
163
- "member_emails": json.dumps([email]),
164
  "second_team_reason": second_team_reason,
165
  "created_at": now,
166
  "updated_at": now,
@@ -181,68 +196,59 @@ def register_participant(data: dict) -> tuple[bool, str]:
181
  )
182
 
183
  # Add participant to member list if not already there
184
- member_emails: list[str] = json.loads(team_row.get("member_emails") or "[]")
185
- if email not in member_emails:
186
- member_emails.append(email)
 
 
187
 
188
  # Assign leader if requested and slot is free
189
- current_leader = (team_row.get("leader_email") or "").strip()
190
  if is_leader:
191
- if current_leader and current_leader != email:
192
  return False, (
193
  f"❌ Team '{name}' already has a designated leader "
194
  f"({current_leader}). "
195
  "Please contact the current leader to transfer leadership."
196
  )
197
- new_leader = email
198
  else:
199
  new_leader = current_leader # unchanged
200
 
201
  # Update team row in-place
202
  mask = teams_df["team_name"] == team_name
203
- teams_df.loc[mask, "member_emails"] = json.dumps(member_emails)
204
- teams_df.loc[mask, "leader_email"] = new_leader
205
  teams_df.loc[mask, "updated_at"] = now
206
  action_msg = f"Joined existing team '{team_name}' ({team_name})."
207
 
208
- # ── 5. Build updated membership list for participant ─────────────────────
209
- # If participant is already a member of this team, update is_leader in place
210
- existing_team_names = [m["team_name"] for m in existing_memberships]
211
- if team_name in existing_team_names:
212
- updated_memberships = [
213
- get_team_membership_entry(
214
- team_name=m["team_name"],
215
- is_leader=is_leader if m["team_name"] == team_name else m["is_leader"],
216
- )
217
- for m in existing_memberships
218
- ]
219
- else:
220
- updated_memberships = existing_memberships + [
221
- get_team_membership_entry(team_name=team_name, is_leader=is_leader)
222
- ]
223
-
224
- # ── 6. Upsert participant row ────────────────────────────────────────────
225
  participant_row = {
226
  "email": email,
227
  "name": name,
 
228
  "affiliation": affiliation,
229
  "role": role,
230
  "self_description": self_description,
231
  "needs_manual_review": needs_manual_review,
232
- "team_memberships": json.dumps(updated_memberships),
 
233
  "registered_at": registered_at,
234
  "updated_at": now,
235
  }
236
 
237
- if is_new_participant:
 
 
 
 
 
 
238
  participants_df = pd.concat(
239
  [participants_df, pd.DataFrame([participant_row])], ignore_index=True
240
  )
241
- else:
242
- for col, val in participant_row.items():
243
- participants_df.loc[participants_df["email"] == email, col] = val
244
 
245
- # ── 7. Push both datasets back to HF ────────────────────────────────────
246
  try:
247
  push_data_to_dataset(participants_df, REGISTRATION_REPO, PARTICIPANTS_FILE_NAME)
248
  push_data_to_dataset(teams_df, REGISTRATION_REPO, TEAMS_FILE_NAME)
@@ -262,26 +268,34 @@ def register_participant(data: dict) -> tuple[bool, str]:
262
  else ""
263
  )
264
  message = (
265
- f"Registration {'created' if is_new_participant else 'updated'} for {email}. "
266
- f"{action_msg}{leader_note}{review_note}"
267
  )
268
  return True, message
269
 
270
 
271
- def get_registration_page(gr):
272
- with gr.TabItem("Register"):
273
  gr.Markdown(REGISTRATION_DESCRIPTION)
274
 
 
 
275
  with gr.Row():
276
  with gr.Column():
277
- email = gr.Textbox(
278
- label="Institutional / Company Email *",
279
- placeholder="you@university.edu or you@company.com",
280
- info="Academic (.edu, .ac.*) or company domains are accepted automatically.",
281
  )
282
  name = gr.Textbox(
283
- label="Full Name *",
284
- placeholder="Jane Smith",
 
 
 
 
 
 
285
  )
286
  affiliation = gr.Textbox(
287
  label="Affiliation / Institution *",
@@ -327,13 +341,14 @@ def get_registration_page(gr):
327
  visible=False,
328
  )
329
 
330
- def on_see_user_teams(user_email: str):
331
- if not user_email or not user_email.strip():
332
  return gr.Dataframe(visible=False), gr.Markdown(
333
- "❌ Please enter an email address.", visible=True
 
334
  )
335
 
336
- teams = get_user_teams(email=user_email)
337
 
338
  if len(teams) > 0:
339
  return gr.Dataframe(value=teams, visible=True), gr.Markdown(
@@ -346,7 +361,7 @@ def get_registration_page(gr):
346
 
347
  user_team_button.click(
348
  fn=on_see_user_teams,
349
- inputs=[email],
350
  outputs=[user_teams, user_teams_empty_msg],
351
  )
352
 
@@ -392,7 +407,6 @@ def get_registration_page(gr):
392
 
393
  def on_register(
394
  registration_email: str,
395
- registration_name: str,
396
  registration_affiliation: str,
397
  self_desc: str,
398
  is_new_team: bool,
@@ -401,15 +415,19 @@ def get_registration_page(gr):
401
  role: str,
402
  is_leader: bool,
403
  second_reason: str,
 
404
  ):
405
- errors = []
 
 
 
 
 
 
406
 
407
  if not registration_email.strip():
408
  errors.append("Email is required.")
409
 
410
- if not registration_name.strip():
411
- errors.append("Full name is required.")
412
-
413
  if not registration_affiliation.strip():
414
  errors.append("Affiliation is required.")
415
  if registration_email.strip() and not validate_email_domain(
@@ -433,8 +451,9 @@ def get_registration_page(gr):
433
  return gr.update(value=msg, visible=True)
434
 
435
  data = {
 
 
436
  "email": registration_email,
437
- "name": registration_name,
438
  "affiliation": registration_affiliation,
439
  "self_description": self_desc,
440
  "is_new_team": is_new_team,
@@ -457,7 +476,6 @@ def get_registration_page(gr):
457
  fn=on_register,
458
  inputs=[
459
  email,
460
- name,
461
  affiliation,
462
  description,
463
  is_new_team_checkbox,
@@ -469,3 +487,9 @@ def get_registration_page(gr):
469
  ],
470
  outputs=[registration_status],
471
  )
 
 
 
 
 
 
 
4
  import logging
5
  from datetime import datetime, timezone
6
 
7
+ import gradio
8
  import pandas as pd
9
 
10
  from about import REGISTRATION_REPO
 
17
  TEAM_COLUMNS,
18
  )
19
  from components.registration.utils import (
 
20
  validate_email_domain,
21
  is_institutional_email,
22
  validate_email_format,
23
  get_user_teams,
24
  )
25
+ from components.utils import (
26
+ push_data_to_dataset,
27
+ load_data_from_dataset,
28
+ get_team,
29
+ prefill_user_info,
30
+ )
31
 
32
  logger = logging.getLogger(__name__)
33
 
 
37
  errors: list[str] = []
38
  email = (data.get("email") or "").strip()
39
  name = (data.get("name") or "").strip()
40
+ username = (data.get("username") or "").strip()
41
  affiliation = (data.get("affiliation") or "").strip()
42
  role = (data.get("role") or "").strip()
43
 
44
+ if not name or not username:
45
+ return ["❌ You must be logged in with your HuggingFace account to register."]
46
  if not email:
47
  errors.append("An email address is required.")
48
  elif not validate_email_format(email):
 
86
  Persist a participant registration to HuggingFace datasets.
87
 
88
  ``data`` keys (all str unless noted):
89
+ username – always required, unique from huggingface account
90
  email – always required
91
  name – always required
92
  affiliation – always required
 
106
  if errors:
107
  return False, "❌ Validation failed:\n" + "\n".join(f" • {e}" for e in errors)
108
 
109
+ username = data["username"].strip() # unique
110
+
111
+ email = data["email"].strip()
112
+ name = data["name"].strip()
113
+ affiliation = data["affiliation"].strip()
114
+ role = data["role"].strip()
115
  self_description = (data.get("self_description") or "").strip()
116
  is_new_team: bool = bool(data.get("is_new_team", False))
117
+ team_name = data["team_name"].strip()
118
  team_description = (data.get("team_description") or "").strip()
119
  is_leader: bool = bool(data.get("is_leader", False))
120
  second_team_reason = (data.get("second_team_reason") or "").strip()
121
  institutional = is_institutional_email(email=email)
 
122
  now = datetime.now(timezone.utc).isoformat()
123
 
124
  # ── 2. Load both datasets ────────────────────────────────────────────────
 
142
  "❌ This team already exists. Please provide another Team name, or join an existing team. .",
143
  )
144
 
145
+ # ── 3. Resolve participant row (upsert by username + team_name) ──────────
146
+ existing_participant = participants_df[participants_df["username"] == username]
147
  is_new_participant = existing_participant.empty
148
 
149
+ existing_in_this_team = existing_participant[
150
+ existing_participant["team_name"] == team_name # fix: was "team"
151
+ ]
152
+ is_already_in_this_team = not existing_in_this_team.empty
153
+
154
+ is_second_team = not existing_participant.empty and not is_already_in_this_team
155
+
156
+ needs_manual_review = not institutional or is_second_team
157
+ if is_already_in_this_team:
158
+ needs_manual_review = existing_in_this_team.iloc[0].get(
159
+ "needs_manual_review", True
160
+ )
161
+
162
  if is_new_participant:
 
163
  registered_at = now
164
  else:
 
 
 
165
  registered_at = existing_participant.iloc[0].get("registered_at", now)
166
+ if is_second_team and not second_team_reason:
167
+ return (
168
+ False,
169
+ "This user is already part of a team. Please provide a reason for joining a second team.",
170
+ )
 
 
 
171
  # ── 4. Resolve team ─────���────────────────────────────────────────────────
172
  if is_new_team:
173
  # ── 4a. Creating a new team ──────────────────────────────────────────
174
  new_team_row = {
175
  "team_name": team_name,
176
  "team_description": team_description,
177
+ "leader_username": username if is_leader else "",
178
+ "members_username": json.dumps([username]),
179
  "second_team_reason": second_team_reason,
180
  "created_at": now,
181
  "updated_at": now,
 
196
  )
197
 
198
  # Add participant to member list if not already there
199
+ members_username: list[str] = json.loads(
200
+ team_row.get("members_username") or "[]"
201
+ )
202
+ if username not in members_username:
203
+ members_username.append(username)
204
 
205
  # Assign leader if requested and slot is free
206
+ current_leader = (team_row.get("leader_username") or "").strip()
207
  if is_leader:
208
+ if current_leader and current_leader != username:
209
  return False, (
210
  f"❌ Team '{name}' already has a designated leader "
211
  f"({current_leader}). "
212
  "Please contact the current leader to transfer leadership."
213
  )
214
+ new_leader = username
215
  else:
216
  new_leader = current_leader # unchanged
217
 
218
  # Update team row in-place
219
  mask = teams_df["team_name"] == team_name
220
+ teams_df.loc[mask, "members_username"] = json.dumps(members_username)
221
+ teams_df.loc[mask, "leader_username"] = new_leader
222
  teams_df.loc[mask, "updated_at"] = now
223
  action_msg = f"Joined existing team '{team_name}' ({team_name})."
224
 
225
+ # ── 5. Upsert participant row (keyed on username + team_name) ────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  participant_row = {
227
  "email": email,
228
  "name": name,
229
+ "username": username,
230
  "affiliation": affiliation,
231
  "role": role,
232
  "self_description": self_description,
233
  "needs_manual_review": needs_manual_review,
234
+ "team_name": team_name,
235
+ "is_leader": is_leader,
236
  "registered_at": registered_at,
237
  "updated_at": now,
238
  }
239
 
240
+ if is_already_in_this_team:
241
+ mask = (participants_df["username"] == username) & (
242
+ participants_df["team_name"] == team_name
243
+ )
244
+ for col, val in participant_row.items():
245
+ participants_df.loc[mask, col] = val
246
+ else:
247
  participants_df = pd.concat(
248
  [participants_df, pd.DataFrame([participant_row])], ignore_index=True
249
  )
 
 
 
250
 
251
+ # ── 6. Push both datasets back to HF ────────────────────────────────────
252
  try:
253
  push_data_to_dataset(participants_df, REGISTRATION_REPO, PARTICIPANTS_FILE_NAME)
254
  push_data_to_dataset(teams_df, REGISTRATION_REPO, TEAMS_FILE_NAME)
 
268
  else ""
269
  )
270
  message = (
271
+ f"Registration {'created' if is_new_participant else 'updated'} for {username}. "
272
+ f"{action_msg} {leader_note} {review_note}"
273
  )
274
  return True, message
275
 
276
 
277
+ def get_registration_page(gr, demo):
278
+ with gr.TabItem("Register", id=1):
279
  gr.Markdown(REGISTRATION_DESCRIPTION)
280
 
281
+ gr.LoginButton()
282
+
283
  with gr.Row():
284
  with gr.Column():
285
+ username = gr.Textbox(
286
+ label="Username from your Hugging Face account *",
287
+ placeholder="Fetching from HuggingFace...",
288
+ interactive=False,
289
  )
290
  name = gr.Textbox(
291
+ label="Full name from your Hugging Face account *",
292
+ placeholder="Fetching from HuggingFace...",
293
+ interactive=False,
294
+ )
295
+ email = gr.Textbox(
296
+ label="Email *",
297
+ placeholder="Fetching from HuggingFace...",
298
+ interactive=False,
299
  )
300
  affiliation = gr.Textbox(
301
  label="Affiliation / Institution *",
 
341
  visible=False,
342
  )
343
 
344
+ def on_see_user_teams(hf_username: str):
345
+ if not hf_username or not hf_username.strip():
346
  return gr.Dataframe(visible=False), gr.Markdown(
347
+ "❌ You must be logged in with your HuggingFace account to see your teams.",
348
+ visible=True,
349
  )
350
 
351
+ teams = get_user_teams(username=hf_username)
352
 
353
  if len(teams) > 0:
354
  return gr.Dataframe(value=teams, visible=True), gr.Markdown(
 
361
 
362
  user_team_button.click(
363
  fn=on_see_user_teams,
364
+ inputs=[username],
365
  outputs=[user_teams, user_teams_empty_msg],
366
  )
367
 
 
407
 
408
  def on_register(
409
  registration_email: str,
 
410
  registration_affiliation: str,
411
  self_desc: str,
412
  is_new_team: bool,
 
415
  role: str,
416
  is_leader: bool,
417
  second_reason: str,
418
+ oauth_profile: gradio.OAuthProfile | None,
419
  ):
420
+ if oauth_profile is None:
421
+ return gr.update(
422
+ value="❌ You must be logged in with your HuggingFace account to register.",
423
+ visible=True,
424
+ )
425
+
426
+ errors: list[str] = []
427
 
428
  if not registration_email.strip():
429
  errors.append("Email is required.")
430
 
 
 
 
431
  if not registration_affiliation.strip():
432
  errors.append("Affiliation is required.")
433
  if registration_email.strip() and not validate_email_domain(
 
451
  return gr.update(value=msg, visible=True)
452
 
453
  data = {
454
+ "username": oauth_profile.username,
455
+ "name": oauth_profile.name,
456
  "email": registration_email,
 
457
  "affiliation": registration_affiliation,
458
  "self_description": self_desc,
459
  "is_new_team": is_new_team,
 
476
  fn=on_register,
477
  inputs=[
478
  email,
 
479
  affiliation,
480
  description,
481
  is_new_team_checkbox,
 
487
  ],
488
  outputs=[registration_status],
489
  )
490
+
491
+ demo.load(
492
+ fn=prefill_user_info,
493
+ inputs=[],
494
+ outputs=[username, name, email],
495
+ )
components/registration/utils.py CHANGED
@@ -10,17 +10,21 @@ from components.utils import load_data_from_dataset
10
 
11
  logger = logging.getLogger(__name__)
12
 
 
13
  def validate_email_domain(email: str) -> bool:
14
  """Return True if email belongs to an academic or registered company domain."""
15
- academic_tlds = [".edu", ".ac.uk", ".ac.", "university", "univ.", "institute", ".gov"]
 
 
 
 
 
 
 
 
16
  return any(token in email.lower() for token in academic_tlds)
17
 
18
 
19
- def get_team_membership_entry(team_name: str, is_leader: bool) -> dict[str, str | bool]:
20
- """Return a dictionary with information about a team."""
21
- return {"team_name": team_name, "is_leader": is_leader}
22
-
23
-
24
  def validate_email_format(email: str) -> bool:
25
  """
26
  Validate the email format.
@@ -37,33 +41,22 @@ def is_institutional_email(email: str) -> bool:
37
  return any(p in email.lower() for p in patterns)
38
 
39
 
40
-
41
- def _participant_team_names(participant_row: pd.Series) -> list[str]:
42
- """Return list of team_name the participant already belongs to."""
43
- memberships = json.loads(participant_row.get("team_memberships") or "[]")
44
- return [m["team_name"] for m in memberships]
45
-
46
-
47
- def get_user_teams(email: str) -> pd.DataFrame:
48
  """
49
- Return the teams associated with a given email.
50
 
51
- :param email: The email address to query.
52
- :return: The teams associated with a given email as a DataFrame.
53
  """
54
- participants_df = load_data_from_dataset(REGISTRATION_REPO, PARTICIPANTS_FILE_NAME, PARTICIPANT_COLUMNS)
55
- existing_participant = participants_df[participants_df["email"] == email]
56
-
57
- if existing_participant.empty:
58
  return pd.DataFrame(columns=["team_name", "role"])
59
 
60
- existing_teams = json.loads(
61
- existing_participant.iloc[0].get("team_memberships") or "[]"
62
- )
63
 
64
- if not existing_teams:
65
  return pd.DataFrame(columns=["team_name", "role"])
66
 
67
- df = pd.DataFrame(existing_teams)
68
- df["role"] = df["is_leader"].apply(lambda x: "Leader" if x else "Member")
69
- return df[["team_name", "role"]]
 
10
 
11
  logger = logging.getLogger(__name__)
12
 
13
+
14
  def validate_email_domain(email: str) -> bool:
15
  """Return True if email belongs to an academic or registered company domain."""
16
+ academic_tlds = [
17
+ ".edu",
18
+ ".ac.uk",
19
+ ".ac.",
20
+ "university",
21
+ "univ.",
22
+ "institute",
23
+ ".gov",
24
+ ]
25
  return any(token in email.lower() for token in academic_tlds)
26
 
27
 
 
 
 
 
 
28
  def validate_email_format(email: str) -> bool:
29
  """
30
  Validate the email format.
 
41
  return any(p in email.lower() for p in patterns)
42
 
43
 
44
+ def get_user_teams(username: str) -> pd.DataFrame:
 
 
 
 
 
 
 
45
  """
46
+ Return the teams associated with a given username.
47
 
48
+ :param username:The unique hf username to query.
49
+ :return: The teams associated with a given username as a DataFrame.
50
  """
51
+ participants_df = load_data_from_dataset(
52
+ REGISTRATION_REPO, PARTICIPANTS_FILE_NAME, PARTICIPANT_COLUMNS
53
+ )
54
+ if participants_df.empty:
55
  return pd.DataFrame(columns=["team_name", "role"])
56
 
57
+ existing_participant = participants_df[participants_df["username"] == username]
 
 
58
 
59
+ if existing_participant.empty:
60
  return pd.DataFrame(columns=["team_name", "role"])
61
 
62
+ return existing_participant[["team_name", "is_leader"]]
 
 
components/submission/config.py CHANGED
@@ -23,10 +23,10 @@ You are allowed to submit up to {max} results per hypothesis.
23
  SUBMISSION_COLUMNS = [
24
  "submission_id",
25
  "team_name",
26
- "submission_email",
27
  "created_at",
28
  "hypothesis",
29
  "submission_note",
30
  "method",
31
  "file_path",
32
- ]
 
23
  SUBMISSION_COLUMNS = [
24
  "submission_id",
25
  "team_name",
26
+ "submission_username",
27
  "created_at",
28
  "hypothesis",
29
  "submission_note",
30
  "method",
31
  "file_path",
32
+ ]
components/submission/submission_page.py CHANGED
@@ -3,8 +3,9 @@ from components.submission.utils import (
3
  get_team_submission_count,
4
  submit_prediction,
5
  validate_user_registration,
 
6
  )
7
- from components.utils import check_team_has_leader
8
  from components.submission.config import (
9
  MAX_SUBMISSIONS_PER_HYPOTHESIS,
10
  SUBMISSION_PAGE_DESCRIPTION,
@@ -22,8 +23,8 @@ def validate_csv(uploaded_file: str) -> tuple[bool, list[str]]:
22
  return True, []
23
 
24
 
25
- def get_submission_page(gr):
26
- with gr.TabItem("✉️ Submit"):
27
 
28
  # ── Section header ──────────────────────────────────────────────────
29
  gr.Markdown(SUBMISSION_PAGE_DESCRIPTION)
@@ -32,18 +33,28 @@ def get_submission_page(gr):
32
 
33
  with gr.Row():
34
  with gr.Column():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  team_name = gr.Textbox(
36
  label="Team name *",
37
  placeholder="My Awesome Lab",
38
  info="Enter your team name.",
39
  value="",
40
  )
41
- uploader_email = gr.Textbox(
42
- label=" Email *",
43
- info="Enter your email.",
44
- placeholder="you@university.edu",
45
- value="",
46
- )
47
  hypothesis_dropdown = gr.Dropdown(
48
  label="Hypothesis *",
49
  choices=PROBLEM_TYPES,
@@ -92,22 +103,24 @@ def get_submission_page(gr):
92
 
93
  # -- Submission count display when team / hypothesis changes ---------
94
  def on_submission_team_or_hypothesis_change(
95
- submission_email: str, submission_team_name: str, hypothesis: str
96
  ):
 
 
 
 
97
  if (
98
  submission_team_name is None
99
- or submission_email is None
100
- or not submission_email.strip()
101
  or not submission_team_name.strip()
102
  or not hypothesis
103
  ):
104
  return "❌ Enter your team name and select a hypothesis to see the number of results already submitted."
105
 
106
- is_user_registered = validate_user_registration(
107
- email=submission_email, team_name=submission_team_name
108
  )
109
  if not is_user_registered:
110
- return "❌ The given email is not registered as part of the given team. Please provide a registered email and team or register on the registration panel."
111
 
112
  count = get_team_submission_count(submission_team_name, hypothesis)
113
  remaining = MAX_SUBMISSIONS_PER_HYPOTHESIS - count
@@ -125,14 +138,14 @@ def get_submission_page(gr):
125
  for widget in [team_name, hypothesis_dropdown]:
126
  widget.change(
127
  fn=on_submission_team_or_hypothesis_change,
128
- inputs=[uploader_email, team_name, hypothesis_dropdown],
129
  outputs=[submission_count_display],
130
  )
131
 
132
  # -- Prediction file upload & validation -----------------------------
133
  def on_submit(
134
  submission_team_name: str,
135
- submission_email: str,
136
  submission_hypothesis: str,
137
  submission_file: str,
138
  submission_note: str,
@@ -142,7 +155,7 @@ def get_submission_page(gr):
142
  success, status = on_validation(
143
  file=predictions_file,
144
  submission_team_name=submission_team_name,
145
- submission_email=submission_email,
146
  submission_hypothesis=submission_hypothesis,
147
  )
148
  if not success:
@@ -153,7 +166,7 @@ def get_submission_page(gr):
153
  team_name=submission_team_name,
154
  hypothesis=submission_hypothesis,
155
  file_path=submission_file,
156
- uploader_email=submission_email,
157
  note=submission_note,
158
  link_to_methods=report_link,
159
  )
@@ -180,7 +193,7 @@ def get_submission_page(gr):
180
  fn=on_submit,
181
  inputs=[
182
  team_name,
183
- uploader_email,
184
  hypothesis_dropdown,
185
  predictions_file,
186
  note,
@@ -191,29 +204,37 @@ def get_submission_page(gr):
191
 
192
  def on_validation(
193
  file: str | None,
194
- submission_email: str,
195
  submission_team_name: str,
196
  submission_hypothesis: str,
197
  ) -> tuple[bool, str]:
198
  """Validate the given file according to the challenge's requirements."""
199
  errors: list[str] = []
 
 
 
 
 
200
  if not submission_team_name.strip():
201
  errors.append("Team name is required.")
202
- if not submission_email.strip():
203
- errors.append("Your email is required for compliance notifications.")
204
  if submission_hypothesis is None:
205
  errors.append("Please select a hypothesis..")
206
  if errors:
207
  status = "❌ " + " | ".join(errors)
208
  return False, gr.update(value=status, visible=True)
209
 
210
- is_user_registered = validate_user_registration(
211
- email=submission_email, team_name=submission_team_name
212
  )
213
- if not is_user_registered:
214
  errors.append(
215
- "Please provide your email address and the team you're registered into and the hypothesis corresponding to your hypothesis."
216
  )
 
 
 
 
 
217
  if file is None:
218
  errors.append("Please upload a CSV file.")
219
 
@@ -256,6 +277,12 @@ def get_submission_page(gr):
256
 
257
  validation_button.click(
258
  fn=on_validation,
259
- inputs=[predictions_file, uploader_email, team_name, hypothesis_dropdown],
260
  outputs=[is_valid, validation_status],
261
  )
 
 
 
 
 
 
 
3
  get_team_submission_count,
4
  submit_prediction,
5
  validate_user_registration,
6
+ validate_user_team_registration,
7
  )
8
+ from components.utils import check_team_has_leader, prefill_user_info
9
  from components.submission.config import (
10
  MAX_SUBMISSIONS_PER_HYPOTHESIS,
11
  SUBMISSION_PAGE_DESCRIPTION,
 
23
  return True, []
24
 
25
 
26
+ def get_submission_page(gr, demo):
27
+ with gr.TabItem("✉️ Submit", id=2):
28
 
29
  # ── Section header ──────────────────────────────────────────────────
30
  gr.Markdown(SUBMISSION_PAGE_DESCRIPTION)
 
33
 
34
  with gr.Row():
35
  with gr.Column():
36
+ gr.LoginButton()
37
+ username = gr.Textbox(
38
+ label="Username from your Hugging Face account *",
39
+ placeholder="Fetching from HuggingFace...",
40
+ interactive=False,
41
+ )
42
+ name = gr.Textbox(
43
+ label="Full name from your Hugging Face account *",
44
+ placeholder="Fetching from HuggingFace...",
45
+ interactive=False,
46
+ )
47
+ email = gr.Textbox(
48
+ label="Email *",
49
+ placeholder="Fetching from HuggingFace...",
50
+ interactive=False,
51
+ )
52
  team_name = gr.Textbox(
53
  label="Team name *",
54
  placeholder="My Awesome Lab",
55
  info="Enter your team name.",
56
  value="",
57
  )
 
 
 
 
 
 
58
  hypothesis_dropdown = gr.Dropdown(
59
  label="Hypothesis *",
60
  choices=PROBLEM_TYPES,
 
103
 
104
  # -- Submission count display when team / hypothesis changes ---------
105
  def on_submission_team_or_hypothesis_change(
106
+ submission_username: str, submission_team_name: str, hypothesis: str
107
  ):
108
+ if submission_username is None or not submission_username.strip():
109
+ return (
110
+ " ❌You must be logged in with your HuggingFace account to register"
111
+ )
112
  if (
113
  submission_team_name is None
 
 
114
  or not submission_team_name.strip()
115
  or not hypothesis
116
  ):
117
  return "❌ Enter your team name and select a hypothesis to see the number of results already submitted."
118
 
119
+ is_user_registered = validate_user_team_registration(
120
+ username=submission_username, team_name=submission_team_name
121
  )
122
  if not is_user_registered:
123
+ return "❌ The username corresponding to the account you are logged in with is not registered as part of the given team. Please register on the registration panel."
124
 
125
  count = get_team_submission_count(submission_team_name, hypothesis)
126
  remaining = MAX_SUBMISSIONS_PER_HYPOTHESIS - count
 
138
  for widget in [team_name, hypothesis_dropdown]:
139
  widget.change(
140
  fn=on_submission_team_or_hypothesis_change,
141
+ inputs=[username, team_name, hypothesis_dropdown],
142
  outputs=[submission_count_display],
143
  )
144
 
145
  # -- Prediction file upload & validation -----------------------------
146
  def on_submit(
147
  submission_team_name: str,
148
+ submission_username: str,
149
  submission_hypothesis: str,
150
  submission_file: str,
151
  submission_note: str,
 
155
  success, status = on_validation(
156
  file=predictions_file,
157
  submission_team_name=submission_team_name,
158
+ submission_username=submission_username,
159
  submission_hypothesis=submission_hypothesis,
160
  )
161
  if not success:
 
166
  team_name=submission_team_name,
167
  hypothesis=submission_hypothesis,
168
  file_path=submission_file,
169
+ uploader_username=submission_username,
170
  note=submission_note,
171
  link_to_methods=report_link,
172
  )
 
193
  fn=on_submit,
194
  inputs=[
195
  team_name,
196
+ username,
197
  hypothesis_dropdown,
198
  predictions_file,
199
  note,
 
204
 
205
  def on_validation(
206
  file: str | None,
207
+ submission_username: str,
208
  submission_team_name: str,
209
  submission_hypothesis: str,
210
  ) -> tuple[bool, str]:
211
  """Validate the given file according to the challenge's requirements."""
212
  errors: list[str] = []
213
+ if not submission_username.strip():
214
+ return False, gr.update(
215
+ value=f"❌ You must be logged in with your HuggingFace account to register.",
216
+ visible=True,
217
+ )
218
  if not submission_team_name.strip():
219
  errors.append("Team name is required.")
 
 
220
  if submission_hypothesis is None:
221
  errors.append("Please select a hypothesis..")
222
  if errors:
223
  status = "❌ " + " | ".join(errors)
224
  return False, gr.update(value=status, visible=True)
225
 
226
+ user_validation = validate_user_registration(
227
+ username=submission_username, team_name=submission_team_name
228
  )
229
+ if user_validation == "not registered":
230
  errors.append(
231
+ "The username corresponding to the account you are logged in with is not registered as part of the given team. Please register on the registration panel."
232
  )
233
+ elif user_validation == "not validated":
234
+ errors.append(
235
+ "Please wait until your registration is validated or contact the challenge organizers.."
236
+ )
237
+
238
  if file is None:
239
  errors.append("Please upload a CSV file.")
240
 
 
277
 
278
  validation_button.click(
279
  fn=on_validation,
280
+ inputs=[predictions_file, username, team_name, hypothesis_dropdown],
281
  outputs=[is_valid, validation_status],
282
  )
283
+
284
+ demo.load(
285
+ fn=prefill_user_info,
286
+ inputs=[],
287
+ outputs=[username, name, email],
288
+ )
components/submission/utils.py CHANGED
@@ -5,6 +5,7 @@ import pandas as pd
5
  from huggingface_hub import HfApi
6
 
7
  from about import TOKEN, REGISTRATION_REPO
 
8
  from components.registration.utils import get_user_teams
9
  from components.submission.config import SUBMISSION_COLUMNS, SUBMISSIONS_FILE_NAME
10
  from components.utils import load_data_from_dataset, push_data_to_dataset
@@ -16,7 +17,7 @@ def submit_prediction(
16
  team_name: str,
17
  hypothesis: str,
18
  file_path: str,
19
- uploader_email: str,
20
  note: str | None,
21
  link_to_methods: str | None,
22
  ) -> tuple[bool, str]:
@@ -46,7 +47,7 @@ def submit_prediction(
46
  {
47
  "submission_id": submission_id,
48
  "team_name": team_name,
49
- "submission_email": uploader_email,
50
  "created_at": datetime.now(timezone.utc).isoformat(),
51
  "hypothesis": hypothesis,
52
  "submission_note": note or "",
@@ -79,13 +80,38 @@ def get_team_submission_count(team_name: str, hypothesis: str) -> int:
79
  ].shape[0]
80
 
81
 
82
- def validate_user_registration(email: str, team_name: str) -> bool:
83
  """
84
- Verify that the given email is registered as part of the team with the given team_name.
85
 
86
- :param email: The email to validate.
87
  :param team_name: The team to validate.
88
- :return: True is the email is registered as part of the team with the given team_name. False otherwise.
89
  """
90
- user_teams = get_user_teams(email=email)
91
  return len(user_teams[user_teams["team_name"] == team_name]) > 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from huggingface_hub import HfApi
6
 
7
  from about import TOKEN, REGISTRATION_REPO
8
+ from components.registration.config import PARTICIPANTS_FILE_NAME, PARTICIPANT_COLUMNS
9
  from components.registration.utils import get_user_teams
10
  from components.submission.config import SUBMISSION_COLUMNS, SUBMISSIONS_FILE_NAME
11
  from components.utils import load_data_from_dataset, push_data_to_dataset
 
17
  team_name: str,
18
  hypothesis: str,
19
  file_path: str,
20
+ uploader_username: str,
21
  note: str | None,
22
  link_to_methods: str | None,
23
  ) -> tuple[bool, str]:
 
47
  {
48
  "submission_id": submission_id,
49
  "team_name": team_name,
50
+ "submission_username": uploader_username,
51
  "created_at": datetime.now(timezone.utc).isoformat(),
52
  "hypothesis": hypothesis,
53
  "submission_note": note or "",
 
80
  ].shape[0]
81
 
82
 
83
+ def validate_user_team_registration(username: str, team_name: str) -> bool:
84
  """
85
+ Verify that the given username is registered as part of the team with the given team_name.
86
 
87
+ :param username: The username to validate.
88
  :param team_name: The team to validate.
89
+ :return: True is the username is registered as part of the team with the given team_name. False otherwise.
90
  """
91
+ user_teams = get_user_teams(username=username)
92
  return len(user_teams[user_teams["team_name"] == team_name]) > 0
93
+
94
+
95
+ def validate_user_registration(username: str, team_name: str) -> str:
96
+ """
97
+ Verify that the given username is validated as part of the team with the given team_name.
98
+
99
+ :param username: The username to validate.
100
+ :param team_name: The team to validate.
101
+ :return: True is the username is registered and validated as part of the team with the given team_name. False otherwise.
102
+ """
103
+ participants_df = load_data_from_dataset(
104
+ REGISTRATION_REPO, PARTICIPANTS_FILE_NAME, PARTICIPANT_COLUMNS
105
+ )
106
+ if participants_df.empty:
107
+ return "not registered"
108
+ record = participants_df[
109
+ (participants_df["username"] == username)
110
+ & (participants_df["team_name"] == team_name)
111
+ ]
112
+ if record.empty:
113
+ return "not registered"
114
+ if record.iloc[0]["needs_manual_review"]:
115
+ return "not validated"
116
+ else:
117
+ return "validated"
components/utils.py CHANGED
@@ -4,7 +4,10 @@ import pathlib
4
  import tempfile
5
  import json
6
  from pathlib import Path
 
7
 
 
 
8
  import gradio as gr
9
  import pandas as pd
10
  from huggingface_hub import hf_hub_download, HfApi
@@ -163,4 +166,27 @@ def check_team_has_leader(team_name: str) -> bool:
163
  team = get_team(teams_df, team_name)
164
  if team is None:
165
  return False
166
- return bool((team.get("leader_email") or "").strip())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import tempfile
5
  import json
6
  from pathlib import Path
7
+ import httpx
8
 
9
+
10
+ import gradio
11
  import gradio as gr
12
  import pandas as pd
13
  from huggingface_hub import hf_hub_download, HfApi
 
166
  team = get_team(teams_df, team_name)
167
  if team is None:
168
  return False
169
+ return bool((team.get("leader_username") or "").strip())
170
+
171
+
172
+ def prefill_user_info(
173
+ oauth_profile: gradio.OAuthProfile | None,
174
+ oauth_token: gradio.OAuthToken | None,
175
+ ):
176
+ empty = lambda: gr.update(value="", placeholder="Log in with HuggingFace first")
177
+ if oauth_profile is None or oauth_token is None:
178
+ return empty(), empty(), empty()
179
+
180
+ resp = httpx.get(
181
+ "https://huggingface.co/api/whoami-v2",
182
+ headers={"Authorization": f"Bearer {oauth_token.token}"},
183
+ timeout=5,
184
+ )
185
+ resp.raise_for_status()
186
+ email = resp.json().get("email", "")
187
+
188
+ return (
189
+ gr.update(value=oauth_profile.username),
190
+ gr.update(value=oauth_profile.name),
191
+ gr.update(value=email),
192
+ )
requirements.txt CHANGED
@@ -1,7 +1,9 @@
1
  gradio
2
  datasets
3
  huggingface_hub
 
4
  gradio-leaderboard
5
  plotly
6
  dotenv
7
- pandas
 
 
1
  gradio
2
  datasets
3
  huggingface_hub
4
+ httpx
5
  gradio-leaderboard
6
  plotly
7
  dotenv
8
+ pandas
9
+ gradio[oauth]