zsyJosh commited on
Commit
0f04a7a
·
1 Parent(s): 05db9a6

save submission also to local

Browse files
Files changed (2) hide show
  1. app.py +423 -388
  2. submissions/forum_posts.json +1 -21
app.py CHANGED
@@ -33,12 +33,14 @@ from stark_qa.evaluator import Evaluator
33
  from utils.hub_storage import HubStorage
34
  from utils.token_handler import TokenHandler
35
 
 
36
  class ForumPost:
37
  def __init__(self, message: str, timestamp: str, post_type: str):
38
  self.message = message
39
  self.timestamp = timestamp
40
  self.post_type = post_type # 'submission' or 'status_update'
41
 
 
42
  class SubmissionForum:
43
  def __init__(self, forum_file="submissions/forum_posts.json", hub_storage=None):
44
  self.forum_file = forum_file
@@ -62,22 +64,16 @@ class SubmissionForum:
62
  """Save posts to JSON file in the hub"""
63
  try:
64
  posts_data = [
65
- {
66
- "message": post.message,
67
- "timestamp": post.timestamp,
68
- "post_type": post.post_type
69
- }
70
  for post in self.posts
71
  ]
72
-
73
  # Convert to JSON string
74
  json_content = json.dumps(posts_data, indent=4)
75
-
76
  # Save to hub
77
  self.hub_storage.save_to_hub(
78
- file_content=json_content,
79
- path_in_repo=self.forum_file,
80
- commit_message="Update forum posts"
81
  )
82
  except Exception as e:
83
  print(f"Error saving forum posts: {e}")
@@ -99,27 +95,22 @@ class SubmissionForum:
99
 
100
  def get_recent_posts(self, limit=50):
101
  """Get recent posts, newest first"""
102
- return sorted(
103
- self.posts,
104
- key=lambda x: datetime.strptime(x.timestamp, "%Y-%m-%d %H:%M:%S"),
105
- reverse=True
106
- )[:limit]
107
 
108
  def format_posts_for_display(self, limit=50):
109
  """Format posts for Gradio Markdown display"""
110
  recent_posts = self.get_recent_posts(limit)
111
  if not recent_posts:
112
  return "No forum posts yet."
113
-
114
  formatted_posts = []
115
  for post in recent_posts:
116
- formatted_posts.append(
117
- f"**{post.timestamp}** \n"
118
- f"{post.message} \n"
119
- f"{'---'}"
120
- )
121
  return "\n\n".join(formatted_posts)
122
 
 
123
  # Initialize storage once at startup
124
  try:
125
  REPO_ID = "snap-stanford/stark-leaderboard" # Replace with your space name
@@ -136,17 +127,17 @@ def process_single_instance(args):
136
  try:
137
  # Get query data
138
  query, query_id, answer_ids, meta_info = qa_dataset[idx]
139
-
140
  # Get predictions
141
- matching_preds = eval_csv[eval_csv['query_id'] == query_id]['pred_rank']
142
  if len(matching_preds) == 0:
143
  print(f"Warning: No prediction found for query_id {query_id}")
144
  return None
145
  elif len(matching_preds) > 1:
146
  print(f"Warning: Multiple predictions found for query_id {query_id}, using first one")
147
-
148
  pred_rank = matching_preds.iloc[0]
149
-
150
  # Parse prediction
151
  if isinstance(pred_rank, str):
152
  try:
@@ -154,12 +145,12 @@ def process_single_instance(args):
154
  except Exception as e:
155
  print(f"Error parsing pred_rank for query_id {query_id}: {str(e)}")
156
  return None
157
-
158
  # Validate prediction format
159
  if not isinstance(pred_rank, list):
160
  print(f"Warning: pred_rank is not a list for query_id {query_id}")
161
  return None
162
-
163
  # # Validate and filter prediction values
164
  # valid_pred_rank = []
165
  # for rank in pred_rank[:100]: # Only use top 100 predictions
@@ -167,72 +158,70 @@ def process_single_instance(args):
167
  # valid_pred_rank.append(rank)
168
  # else:
169
  # print(f"Warning: Invalid prediction {rank} for query_id {query_id}")
170
-
171
  # if not valid_pred_rank:
172
  # print(f"Warning: No valid predictions for query_id {query_id}")
173
  # return None
174
-
175
  pred_dict = {pred_rank[i]: -i for i in range(min(100, len(pred_rank)))}
176
  answer_ids = torch.LongTensor(answer_ids)
177
  result = evaluator.evaluate(pred_dict, answer_ids, metrics=eval_metrics)
178
 
179
  result["idx"], result["query_id"] = idx, query_id
180
  return result
181
-
182
  except Exception as e:
183
  print(f"Error processing idx {idx}: {str(e)}")
184
  return None
185
 
 
186
  def compute_metrics(csv_path: str, dataset: str, split: str, num_workers: int = 4):
187
  """Compute metrics with improved thread safety and error handling"""
188
  start_time = time.time()
189
-
190
  # Dataset configuration
191
  candidate_ids_dict = {
192
- 'amazon': [i for i in range(957192)],
193
- 'mag': [i for i in range(1172724, 1872968)],
194
- 'prime': [i for i in range(129375)]
195
  }
196
-
197
  try:
198
  # Input validation
199
  if dataset not in candidate_ids_dict:
200
  raise ValueError(f"Invalid dataset '{dataset}'")
201
- if split not in ['test', 'test-0.1', 'human_generated_eval']:
202
  raise ValueError(f"Invalid split '{split}'")
203
-
204
  # Load and validate CSV
205
  print(f"\nLoading data for {dataset} {split}")
206
  eval_csv = pd.read_csv(csv_path)
207
- required_columns = ['query_id', 'pred_rank']
208
  if not all(col in eval_csv.columns for col in required_columns):
209
  raise ValueError(f"CSV must contain columns: {required_columns}")
210
-
211
  eval_csv = eval_csv[required_columns]
212
-
213
  # Initialize components
214
  evaluator = Evaluator(candidate_ids_dict[dataset])
215
- eval_metrics = ['hit@1', 'hit@5', 'recall@20', 'mrr']
216
- qa_dataset = load_qa(dataset, human_generated_eval=split == 'human_generated_eval')
217
  split_idx = qa_dataset.get_idx_split()
218
  all_indices = split_idx[split].tolist()
219
-
220
  print(f"Processing {len(all_indices)} instances with {num_workers} threads")
221
-
222
  # Process instances
223
  results_list = []
224
  valid_count = 0
225
  error_count = 0
226
-
227
  with ThreadPoolExecutor(max_workers=num_workers) as executor:
228
  futures = [
229
- executor.submit(
230
- process_single_instance,
231
- (idx, eval_csv, qa_dataset, evaluator, eval_metrics)
232
- )
233
  for idx in all_indices
234
  ]
235
-
236
  with tqdm(total=len(futures), desc="Processing") as pbar:
237
  for future in as_completed(futures):
238
  try:
@@ -246,76 +235,114 @@ def compute_metrics(csv_path: str, dataset: str, split: str, num_workers: int =
246
  print(f"Error in future: {str(e)}")
247
  error_count += 1
248
  pbar.update(1)
249
-
250
  # Compute final metrics
251
  if not results_list:
252
  raise ValueError("No valid results were produced")
253
-
254
  print(f"\nProcessing complete. Valid: {valid_count}, Errors: {error_count}")
255
-
256
  results_df = pd.DataFrame(results_list)
257
- final_results = {
258
- metric: results_df[metric].mean()
259
- for metric in eval_metrics
260
- }
261
-
262
  elapsed_time = time.time() - start_time
263
  print(f"Completed in {elapsed_time:.2f} seconds")
264
  return final_results
265
-
266
  except Exception as error:
267
  elapsed_time = time.time() - start_time
268
  error_msg = f"Error in compute_metrics ({elapsed_time:.2f}s): {str(error)}"
269
  print(error_msg)
270
  return error_msg
271
 
 
272
  # Data dictionaries for leaderboard
273
  data_synthesized_full = {
274
- 'Method': ['BM25', 'DPR (roberta)', 'ANCE (roberta)', 'QAGNN (roberta)', 'ada-002', 'voyage-l2-instruct', 'LLM2Vec', 'GritLM-7b', 'multi-ada-002', 'ColBERTv2', 'AvaTaR(claude-3-opus)', 'AvaTaR(gpt-4-turbo)'],
275
- 'STARK-AMAZON_Hit@1': [44.94, 15.29, 30.96, 26.56, 39.16, 40.93, 21.74, 42.08, 40.07, 46.10, 49.97, 48.82],
276
- 'STARK-AMAZON_Hit@5': [67.42, 47.93, 51.06, 50.01, 62.73, 64.37, 41.65, 66.87, 64.98, 66.02, 69.16, 72.03],
277
- 'STARK-AMAZON_R@20': [53.77, 44.49, 41.95, 52.05, 53.29, 54.28, 33.22, 56.52, 55.12, 53.44, 60.57, 56.04],
278
- 'STARK-AMAZON_MRR': [55.30, 30.20, 40.66, 37.75, 50.35, 51.60, 31.47, 53.46, 51.55, 55.51, 58.70, 57.17],
279
- 'STARK-MAG_Hit@1': [25.85, 10.51, 21.96, 12.88, 29.08, 30.06, 18.01, 37.90, 25.92, 31.18, 44.36, 46.08],
280
- 'STARK-MAG_Hit@5': [45.25, 35.23, 36.50, 39.01, 49.61, 50.58, 34.85, 56.74, 50.43, 46.42, 59.66, 59.32],
281
- 'STARK-MAG_R@20': [45.69, 42.11, 35.32, 46.97, 48.36, 50.49, 35.46, 46.40, 50.80, 43.94, 50.63, 49.70],
282
- 'STARK-MAG_MRR': [34.91, 21.34, 29.14, 29.12, 38.62, 39.66, 26.10, 47.25, 36.94, 38.39, 51.15, 52.01],
283
- 'STARK-PRIME_Hit@1': [12.75, 4.46, 6.53, 8.85, 12.63, 10.85, 10.10, 15.57, 15.10, 11.75, 18.44, 20.10],
284
- 'STARK-PRIME_Hit@5': [27.92, 21.85, 15.67, 21.35, 31.49, 30.23, 22.49, 33.42, 33.56, 23.85, 36.73, 39.89],
285
- 'STARK-PRIME_R@20': [31.25, 30.13, 16.52, 29.63, 36.00, 37.83, 26.34, 39.09, 38.05, 25.04, 39.31, 42.23],
286
- 'STARK-PRIME_MRR': [19.84, 12.38, 11.05, 14.73, 21.41, 19.99, 16.12, 24.11, 23.49, 17.39, 26.73, 29.18]
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  }
288
 
289
  data_synthesized_10 = {
290
- 'Method': ['BM25', 'DPR (roberta)', 'ANCE (roberta)', 'QAGNN (roberta)', 'ada-002', 'voyage-l2-instruct', 'LLM2Vec', 'GritLM-7b', 'multi-ada-002', 'ColBERTv2', 'Claude3 Reranker', 'GPT4 Reranker'],
291
- 'STARK-AMAZON_Hit@1': [42.68, 16.46, 30.09, 25.00, 39.02, 43.29, 18.90, 43.29, 40.85, 44.31, 45.49, 44.79],
292
- 'STARK-AMAZON_Hit@5': [67.07, 50.00, 49.27, 48.17, 64.02, 67.68, 37.80, 71.34, 62.80, 65.24, 71.13, 71.17],
293
- 'STARK-AMAZON_R@20': [54.48, 42.15, 41.91, 51.65, 49.30, 56.04, 34.73, 56.14, 52.47, 51.00, 53.77, 55.35],
294
- 'STARK-AMAZON_MRR': [54.02, 30.20, 39.30, 36.87, 50.32, 54.20, 28.76, 55.07, 51.54, 55.07, 55.91, 55.69],
295
- 'STARK-MAG_Hit@1': [27.81, 11.65, 22.89, 12.03, 28.20, 34.59, 19.17, 38.35, 25.56, 31.58, 36.54, 40.90],
296
- 'STARK-MAG_Hit@5': [45.48, 36.84, 37.26, 37.97, 52.63, 50.75, 33.46, 58.64, 50.37, 47.36, 53.17, 58.18],
297
- 'STARK-MAG_R@20': [44.59, 42.30, 44.16, 47.98, 49.25, 50.75, 29.85, 46.38, 53.03, 45.72, 48.36, 48.60],
298
- 'STARK-MAG_MRR': [35.97, 21.82, 30.00, 28.70, 38.55, 42.90, 26.06, 48.25, 36.82, 38.98, 44.15, 49.00],
299
- 'STARK-PRIME_Hit@1': [13.93, 5.00, 6.78, 7.14, 15.36, 12.14, 9.29, 16.79, 15.36, 15.00, 17.79, 18.28],
300
- 'STARK-PRIME_Hit@5': [31.07, 23.57, 16.15, 17.14, 31.07, 31.42, 20.7, 34.29, 32.86, 26.07, 36.90, 37.28],
301
- 'STARK-PRIME_R@20': [32.84, 30.50, 17.07, 32.95, 37.88, 37.34, 25.54, 41.11, 40.99, 27.78, 35.57, 34.05],
302
- 'STARK-PRIME_MRR': [21.68, 13.50, 11.42, 16.27, 23.50, 21.23, 15.00, 24.99, 23.70, 19.98, 26.27, 26.55]
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
304
 
305
  data_human_generated = {
306
- 'Method': ['BM25', 'DPR (roberta)', 'ANCE (roberta)', 'QAGNN (roberta)', 'ada-002', 'voyage-l2-instruct', 'LLM2Vec', 'GritLM-7b', 'multi-ada-002', 'ColBERTv2', 'Claude3 Reranker', 'GPT4 Reranker', 'AvaTaR(gpt-4-turbo)'],
307
- 'STARK-AMAZON_Hit@1': [27.16, 16.05, 25.93, 22.22, 39.50, 35.80, 29.63, 40.74, 46.91, 33.33, 53.09, 50.62, 58.32],
308
- 'STARK-AMAZON_Hit@5': [51.85, 39.51, 54.32, 49.38, 64.19, 62.96, 46.91, 71.60, 72.84, 55.56, 74.07, 75.31, 76.54],
309
- 'STARK-AMAZON_R@20': [29.23, 15.23, 23.69, 21.54, 35.46, 33.01, 21.21, 36.30, 40.22, 29.03, 35.46, 35.46, 42.43],
310
- 'STARK-AMAZON_MRR': [18.79, 27.21, 37.12, 31.33, 52.65, 47.84, 38.61, 53.21, 58.74, 43.77, 62.11, 61.06, 65.91],
311
- 'STARK-MAG_Hit@1': [32.14, 4.72, 25.00, 20.24, 28.57, 22.62, 16.67, 34.52, 23.81, 33.33, 38.10, 36.90, 33.33],
312
- 'STARK-MAG_Hit@5': [41.67, 9.52, 30.95, 26.19, 41.67, 36.90, 28.57, 44.04, 41.67, 36.90, 45.24, 46.43, 42.86],
313
- 'STARK-MAG_R@20': [32.46, 25.00, 27.24, 28.76, 35.95, 32.44, 21.74, 34.57, 39.85, 30.50, 35.95, 35.95, 35.94],
314
- 'STARK-MAG_MRR': [37.42, 7.90, 27.98, 25.53, 35.81, 29.68, 21.59, 38.72, 31.43, 35.97, 42.00, 40.65, 38.62],
315
- 'STARK-PRIME_Hit@1': [22.45, 2.04, 7.14, 6.12, 17.35, 16.33, 9.18, 25.51, 24.49, 15.31, 28.57, 28.57, 33.03],
316
- 'STARK-PRIME_Hit@5': [41.84, 9.18, 13.27, 13.27, 34.69, 32.65, 21.43, 41.84, 39.80, 26.53, 46.94, 44.90, 51.37],
317
- 'STARK-PRIME_R@20': [42.32, 10.69, 11.72, 17.62, 41.09, 39.01, 26.77, 48.10, 47.21, 25.56, 41.61, 41.61, 53.34],
318
- 'STARK-PRIME_MRR': [30.37, 7.05, 10.07, 9.39, 26.35, 24.33, 15.24, 34.28, 32.98, 19.67, 36.32, 34.82, 41.00]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  }
320
 
321
  # Initialize DataFrames
@@ -325,79 +352,81 @@ df_human_generated = pd.DataFrame(data_human_generated)
325
 
326
  # Model type definitions
327
  model_types = {
328
- 'Sparse Retriever': ['BM25'],
329
- 'Small Dense Retrievers': ['DPR (roberta)', 'ANCE (roberta)', 'QAGNN (roberta)'],
330
- 'LLM-based Dense Retrievers': ['ada-002', 'voyage-l2-instruct', 'LLM2Vec', 'GritLM-7b'],
331
- 'Multivector Retrievers': ['multi-ada-002', 'ColBERTv2'],
332
- 'LLM Rerankers': ['Claude3 Reranker', 'GPT4 Reranker', 'AvaTaR(gpt-4-turbo)', 'AvaTaR(claude-3-opus)'],
333
- 'Others': [] # Will be populated dynamically with submitted models
334
  }
335
 
 
336
  # Submission form validation functions
337
  def validate_email(email_str):
338
  """Validate email format(s)"""
339
- emails = [e.strip() for e in email_str.split(';')]
340
- email_pattern = re.compile(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
341
  return all(email_pattern.match(email) for email in emails)
342
 
 
343
  def validate_github_url(url):
344
  """Validate GitHub URL format"""
345
- github_pattern = re.compile(
346
- r'^https?:\/\/(?:www\.)?github\.com\/[\w-]+\/[\w.-]+\/?$'
347
- )
348
  return bool(github_pattern.match(url))
349
 
 
350
  def validate_csv(file_obj):
351
  """Validate CSV file format and content"""
352
  try:
353
  df = pd.read_csv(file_obj.name)
354
- required_cols = ['query_id', 'pred_rank']
355
-
356
  if not all(col in df.columns for col in required_cols):
357
  return False, "CSV must contain 'query_id' and 'pred_rank' columns"
358
-
359
  try:
360
- first_rank = eval(df['pred_rank'].iloc[0]) if isinstance(df['pred_rank'].iloc[0], str) else df['pred_rank'].iloc[0]
 
 
361
  if not isinstance(first_rank, list) or len(first_rank) < 20:
362
  return False, "pred_rank must be a list with at least 20 candidates"
363
  except:
364
  return False, "Invalid pred_rank format"
365
-
366
  return True, "Valid CSV file"
367
  except Exception as e:
368
  return False, f"Error processing CSV: {str(e)}"
369
 
 
370
  def sanitize_name(name):
371
  """Sanitize name for file system use"""
372
- return re.sub(r'[^a-zA-Z0-9]', '_', name)
 
373
 
374
  def read_json_from_hub(api: HfApi, repo_id: str, file_path: str) -> dict:
375
  """
376
  Read and parse JSON file from HuggingFace Hub.
377
-
378
  Args:
379
  api: HuggingFace API instance
380
  repo_id: Repository ID
381
  file_path: Path to file in repository
382
-
383
  Returns:
384
  dict: Parsed JSON content
385
  """
386
  try:
387
  # Download the file content as bytes
388
- content = api.hf_hub_download(
389
- repo_id=repo_id,
390
- filename=file_path,
391
- repo_type="space"
392
- )
393
-
394
  # Read and parse JSON
395
- with open(content, 'r') as f:
396
  return json.load(f)
397
  except Exception as e:
398
  print(f"Error reading JSON file {file_path}: {str(e)}")
399
  return None
400
 
 
401
  def scan_submissions_directory():
402
  """
403
  Scans the submissions directory and updates the model types dictionary
@@ -406,248 +435,238 @@ def scan_submissions_directory():
406
  try:
407
  # Initialize HuggingFace API
408
  api = HfApi()
409
-
410
  # Track submissions for each split
411
- submissions_by_split = {
412
- 'test': [],
413
- 'test-0.1': [],
414
- 'human_generated_eval': []
415
- }
416
-
417
  # Get all files from repository
418
  try:
419
- all_files = api.list_repo_files(
420
- repo_id=REPO_ID,
421
- repo_type="space"
422
- )
423
  # Filter for files in submissions directory
424
- repo_files = [f for f in all_files if f.startswith('submissions/')]
425
  except Exception as e:
426
  print(f"Error listing repository contents: {str(e)}")
427
  return submissions_by_split
428
-
429
  # Group files by team folders
430
  folder_files = {}
431
  for filepath in repo_files:
432
- parts = filepath.split('/')
433
  if len(parts) < 3: # Need at least submissions/team_folder/file
434
  continue
435
-
436
  folder_name = parts[1] # team_folder name
437
  if folder_name not in folder_files:
438
  folder_files[folder_name] = []
439
  folder_files[folder_name].append(filepath)
440
-
441
  # Process each team folder
442
  for folder_name, files in folder_files.items():
443
  try:
444
  # Find latest.json in this folder
445
- latest_file = next((f for f in files if f.endswith('latest.json')), None)
446
  if not latest_file:
447
  print(f"No latest.json found in {folder_name}")
448
  continue
449
-
450
  # Read latest.json
451
  latest_info = read_json_from_hub(api, REPO_ID, latest_file)
452
  if not latest_info:
453
  print(f"Failed to read latest.json for {folder_name}")
454
  continue
455
-
456
- timestamp = latest_info.get('latest_submission')
457
  if not timestamp:
458
  print(f"No timestamp found in latest.json for {folder_name}")
459
  continue
460
-
461
  # Find metadata file for latest submission
462
- metadata_file = next(
463
- (f for f in files if f.endswith(f'metadata_{timestamp}.json')),
464
- None
465
- )
466
  if not metadata_file:
467
  print(f"No matching metadata file found for {folder_name} timestamp {timestamp}")
468
  continue
469
-
470
  # Read metadata file
471
  submission_data = read_json_from_hub(api, REPO_ID, metadata_file)
472
  if not submission_data:
473
  print(f"Failed to read metadata for {folder_name}")
474
  continue
475
-
476
- if latest_info.get('status') != 'approved':
477
  print(f"Skipping unapproved submission in {folder_name}")
478
  continue
479
-
480
  # Add to submissions by split
481
- split = submission_data.get('Split')
482
  if split in submissions_by_split:
483
  submissions_by_split[split].append(submission_data)
484
-
485
  # Update model types if necessary
486
- method_name = submission_data.get('Method Name')
487
- model_type = submission_data.get('Model Type', 'Others')
488
-
489
  # Add to model type if it's a new method
490
  method_exists = any(method_name in methods for methods in model_types.values())
491
  if not method_exists and model_type in model_types:
492
  model_types[model_type].append(method_name)
493
-
494
  except Exception as e:
495
  print(f"Error processing folder {folder_name}: {str(e)}")
496
  continue
497
-
498
  return submissions_by_split
499
-
500
  except Exception as e:
501
  print(f"Error scanning submissions directory: {str(e)}")
502
  return None
503
 
 
504
  def initialize_leaderboard():
505
  """
506
  Initialize the leaderboard with baseline results and submitted results.
507
  """
508
  global df_synthesized_full, df_synthesized_10, df_human_generated
509
-
510
  try:
511
  # First, initialize with baseline results
512
  df_synthesized_full = pd.DataFrame(data_synthesized_full)
513
  df_synthesized_10 = pd.DataFrame(data_synthesized_10)
514
  df_human_generated = pd.DataFrame(data_human_generated)
515
-
516
  print("Initialized with baseline results")
517
-
518
  # Then scan and add submitted results
519
  submissions = scan_submissions_directory()
520
  if submissions:
521
  for split, split_submissions in submissions.items():
522
  for submission in split_submissions:
523
- if submission.get('results'): # Make sure we have results
524
  # Update appropriate DataFrame based on split
525
- if split == 'test':
526
  df_to_update = df_synthesized_full
527
- elif split == 'test-0.1':
528
  df_to_update = df_synthesized_10
529
  else: # human_generated_eval
530
  df_to_update = df_human_generated
531
-
532
  # Prepare new row data
533
  new_row = {
534
- 'Method': submission['Method Name'],
535
- f'STARK-{submission["Dataset"].upper()}_Hit@1': submission['results']['hit@1'],
536
- f'STARK-{submission["Dataset"].upper()}_Hit@5': submission['results']['hit@5'],
537
- f'STARK-{submission["Dataset"].upper()}_R@20': submission['results']['recall@20'],
538
- f'STARK-{submission["Dataset"].upper()}_MRR': submission['results']['mrr']
539
  }
540
-
541
  # Update existing row or add new one
542
- method_mask = df_to_update['Method'] == submission['Method Name']
543
  if method_mask.any():
544
  for col in new_row:
545
  df_to_update.loc[method_mask, col] = new_row[col]
546
  else:
547
  df_to_update.loc[len(df_to_update)] = new_row
548
-
549
  print("Leaderboard initialization complete")
550
-
551
  except Exception as e:
552
  print(f"Error initializing leaderboard: {str(e)}")
553
 
 
554
  def get_file_content(file_path):
555
  """
556
  Helper function to safely read file content from HuggingFace repository
557
  """
558
  try:
559
  api = HfApi()
560
- content_path = api.hf_hub_download(
561
- repo_id=REPO_ID,
562
- filename=file_path,
563
- repo_type="space"
564
- )
565
- with open(content_path, 'r') as f:
566
  return f.read()
567
  except Exception as e:
568
  print(f"Error reading file {file_path}: {str(e)}")
569
  return None
570
 
 
571
  def save_submission(submission_data, csv_file):
572
  """
573
  Save submission data and CSV file using model_name_team_name format
574
-
575
  Args:
576
  submission_data (dict): Metadata and results for the submission
577
  csv_file: The uploaded CSV file object
578
  """
579
  # Create folder name from model name and team name
580
- model_name_clean = sanitize_name(submission_data['Method Name'])
581
- team_name_clean = sanitize_name(submission_data['Team Name'])
582
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
583
-
584
  # Create folder name: model_name_team_name
585
  folder_name = f"{model_name_clean}_{team_name_clean}"
586
  submission_id = f"{folder_name}_{timestamp}"
587
-
588
  # Create submission directory structure
589
  base_dir = "submissions"
590
  submission_dir = os.path.join(base_dir, folder_name)
591
  os.makedirs(submission_dir, exist_ok=True)
592
-
593
  # Save CSV file with timestamp to allow multiple submissions
594
  csv_filename = f"predictions_{timestamp}.csv"
595
  csv_path = os.path.join(submission_dir, csv_filename)
596
- if hasattr(csv_file, 'name'):
597
- with open(csv_file.name, 'rb') as source, open(csv_path, 'wb') as target:
598
  target.write(source.read())
599
-
600
  # Add file paths to submission data
601
- submission_data.update({
602
- "csv_path": csv_path,
603
- "submission_id": submission_id,
604
- "folder_name": folder_name
605
- })
606
-
607
  # Save metadata as JSON with timestamp
608
  metadata_path = os.path.join(submission_dir, f"metadata_{timestamp}.json")
609
- with open(metadata_path, 'w') as f:
610
  json.dump(submission_data, f, indent=4)
611
-
612
  # Update latest.json to track most recent submission
613
  latest_path = os.path.join(submission_dir, "latest.json")
614
- with open(latest_path, 'w') as f:
615
- json.dump({
616
- "latest_submission": timestamp,
617
- "status": "pending_review",
618
- "method_name": submission_data['Method Name']
619
- }, f, indent=4)
620
-
 
 
 
 
621
  return submission_id
622
 
 
623
  def update_leaderboard_data(submission_data):
624
  """
625
  Update leaderboard data with new submission results
626
  Only uses model name in the displayed table
627
  """
628
  global df_synthesized_full, df_synthesized_10, df_human_generated
629
-
630
  # Determine which DataFrame to update based on split
631
  split_to_df = {
632
- 'test': df_synthesized_full,
633
- 'test-0.1': df_synthesized_10,
634
- 'human_generated_eval': df_human_generated
635
  }
636
-
637
- df_to_update = split_to_df[submission_data['Split']]
638
- submitted_dataset = submission_data['Dataset'].upper()
639
-
640
  # Prepare new row data
641
  new_row = {
642
- 'Method': submission_data['Method Name'],
643
- f'STARK-{submitted_dataset}_Hit@1': submission_data['results']['hit@1'],
644
- f'STARK-{submitted_dataset}_Hit@5': submission_data['results']['hit@5'],
645
- f'STARK-{submitted_dataset}_R@20': submission_data['results']['recall@20'],
646
- f'STARK-{submitted_dataset}_MRR': submission_data['results']['mrr']
647
  }
648
-
649
  # Check if method already exists
650
- method_mask = df_to_update['Method'] == submission_data['Method Name']
651
  if method_mask.any():
652
  # Update existing row
653
  for col in new_row:
@@ -659,19 +678,21 @@ def update_leaderboard_data(submission_data):
659
  full_row.update(new_row) # Update with the submitted dataset's values
660
  df_to_update.loc[len(df_to_update)] = full_row
661
 
 
662
  # Function to get emails from meta_data
663
  def get_emails_from_metadata(meta_data):
664
  """
665
  Extracts emails from the meta_data dictionary.
666
-
667
  Args:
668
  meta_data (dict): The metadata dictionary that contains the 'Contact Email(s)' field.
669
-
670
  Returns:
671
  list: A list of email addresses.
672
  """
673
  return [email.strip() for email in meta_data.get("Contact Email(s)", "").split(";")]
674
 
 
675
  # Function to format meta_data as an HTML table (without Prediction CSV)
676
  def format_metadata_as_table(meta_data):
677
  """
@@ -685,11 +706,11 @@ def format_metadata_as_table(meta_data):
685
  str: HTML string representing the metadata table.
686
  """
687
  table_rows = ""
688
-
689
  for key, value in meta_data.items():
690
  if key == "Contact Email(s)":
691
  # Ensure that contact emails are split by semicolon
692
- emails = value.split(';')
693
  formatted_emails = "; ".join([email.strip() for email in emails])
694
  table_rows += f"<tr><td><b>{key}</b></td><td>{formatted_emails}</td></tr>"
695
  elif key != "Prediction CSV": # Exclude the Prediction CSV field
@@ -702,19 +723,21 @@ def format_metadata_as_table(meta_data):
702
  """
703
  return table_html
704
 
 
705
  # Function to get emails from meta_data
706
  def get_emails_from_metadata(meta_data):
707
  """
708
  Extracts emails from the meta_data dictionary.
709
-
710
  Args:
711
  meta_data (dict): The metadata dictionary that contains the 'Contact Email(s)' field.
712
-
713
  Returns:
714
  list: A list of email addresses.
715
  """
716
  return [email.strip() for email in meta_data.get("Contact Email(s)", "").split(";")]
717
-
 
718
  def format_evaluation_results(results):
719
  """
720
  Formats the evaluation results dictionary into a readable string.
@@ -728,6 +751,7 @@ def format_evaluation_results(results):
728
  result_lines = [f"{metric}: {value}" for metric, value in results.items()]
729
  return "\n".join(result_lines)
730
 
 
731
  def get_model_type_for_method(method_name):
732
  """
733
  Find the model type category for a given method name.
@@ -736,7 +760,8 @@ def get_model_type_for_method(method_name):
736
  for type_name, methods in model_types.items():
737
  if method_name in methods:
738
  return type_name
739
- return 'Others'
 
740
 
741
  def validate_model_type(method_name, selected_type):
742
  """
@@ -749,30 +774,44 @@ def validate_model_type(method_name, selected_type):
749
  if method_name in methods:
750
  existing_type = type_name
751
  break
752
-
753
  # If method exists, it must be submitted under its predefined category
754
  if existing_type:
755
  if existing_type != selected_type:
756
- return False, f"This method name is already registered under '{existing_type}'. Please use the correct category."
 
 
 
757
  return True, "Valid model type"
758
-
759
  # For new methods, any category is valid
760
  return True, "Valid model type"
761
 
 
762
  def process_submission(
763
- method_name, team_name, dataset, split, contact_email,
764
- code_repo, csv_file, model_description, hardware, paper_link, model_type, honor_code
 
 
 
 
 
 
 
 
 
 
765
  ):
766
  """Process and validate submission"""
767
  if not honor_code:
768
  return "Error: Please accept the honor code to submit"
769
-
770
  temp_files = []
771
  try:
772
  # Input validation
773
  if not all([method_name, team_name, dataset, split, contact_email, code_repo, csv_file, model_type]):
774
  return "Error: Please fill in all required fields"
775
-
776
  # Validate model type
777
  is_valid, message = validate_model_type(method_name, model_type)
778
  if not is_valid:
@@ -789,20 +828,20 @@ def process_submission(
789
  "Model Description": model_description,
790
  "Hardware": hardware,
791
  "(Optional) Paper link": paper_link,
792
- "Model Type": model_type
793
  }
794
-
795
  # Generate folder name and timestamp
796
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
797
  folder_name = f"{sanitize_name(method_name)}_{sanitize_name(team_name)}"
798
-
799
  # Process CSV file
800
  csv_content = None
801
  if isinstance(csv_file, str):
802
- with open(csv_file, 'r') as f:
803
  csv_content = f.read()
804
- elif hasattr(csv_file, 'name'):
805
- with open(csv_file.name, 'r') as f:
806
  csv_content = f.read()
807
  else:
808
  return "Error: Invalid CSV file", forum.format_posts_for_display()
@@ -812,18 +851,18 @@ def process_submission(
812
  csv_path=csv_file if isinstance(csv_file, str) else csv_file.name,
813
  dataset=dataset.lower(),
814
  split=split,
815
- num_workers=4
816
  )
817
-
818
  if isinstance(results, str):
819
  return f"Evaluation error: {results}", forum.format_posts_for_display()
820
 
821
  # Process results
822
  processed_results = {
823
- "hit@1": round(results['hit@1'] * 100, 2),
824
- "hit@5": round(results['hit@5'] * 100, 2),
825
- "recall@20": round(results['recall@20'] * 100, 2),
826
- "mrr": round(results['mrr'] * 100, 2)
827
  }
828
 
829
  meta_data = {
@@ -839,76 +878,99 @@ def process_submission(
839
  "Model Type": model_type,
840
  "results": processed_results,
841
  "status": "pending_review",
842
- "submission_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
843
  }
844
-
845
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
  # Save files to HuggingFace Hub
847
  try:
848
- # 1. Save CSV file
849
  csv_filename = f"predictions_{timestamp}.csv"
850
  csv_path_in_repo = f"submissions/{folder_name}/{csv_filename}"
851
  hub_storage.save_to_hub(
852
  file_content=csv_content,
853
  path_in_repo=csv_path_in_repo,
854
- commit_message=f"Add submission: {method_name} by {team_name}"
855
  )
856
  meta_data["csv_path"] = csv_path_in_repo
857
 
858
- # 2. Save metadata
859
  metadata_path = f"submissions/{folder_name}/metadata_{timestamp}.json"
860
  metadata_content = json.dumps(meta_data, indent=4)
861
  hub_storage.save_to_hub(
862
- file_content=metadata_content, # Pass JSON string directly
863
  path_in_repo=metadata_path,
864
- commit_message=f"Add metadata: {method_name} by {team_name}"
865
  )
866
 
867
- # 3. Create or update latest.json
868
- latest_info = {
869
- "latest_submission": timestamp,
870
- "status": "pending_review", # or "approved"
871
- "method_name": method_name,
872
- "team_name": team_name
873
- }
874
-
875
  latest_path = f"submissions/{folder_name}/latest.json"
876
  latest_content = json.dumps(latest_info, indent=4)
877
  hub_storage.save_to_hub(
878
- file_content=latest_content, # Pass JSON string directly
879
  path_in_repo=latest_path,
880
- commit_message=f"Update latest submission info for {method_name}"
881
  )
882
 
883
  except Exception as e:
884
- raise RuntimeError(f"Failed to save files to HuggingFace Hub: {str(e)}")
885
-
 
886
  # Send confirmation email and update leaderboard data
887
  # send_submission_confirmation(meta_data, processed_results)
888
  update_leaderboard_data(meta_data)
889
 
890
  forum.add_submission_post(method_name, dataset, split)
891
  forum_display = forum.format_posts_for_display()
892
-
893
  # Return success message
894
- return f"""
895
- Submission successful!
896
-
 
897
  Evaluation Results:
898
  Hit@1: {processed_results['hit@1']:.2f}%
899
  Hit@5: {processed_results['hit@5']:.2f}%
900
  Recall@20: {processed_results['recall@20']:.2f}%
901
  MRR: {processed_results['mrr']:.2f}%
902
-
903
  Your submission has been saved and a confirmation email has been sent to {contact_email}.
904
  Once approved, your results will appear in the leaderboard under: {method_name}
905
-
906
  You can find your submission at:
907
  https://huggingface.co/spaces/{REPO_ID}/tree/main/submissions/{folder_name}
908
-
909
  Please refresh the page to see your submission in the leaderboard.
910
- """, forum_display
911
-
 
 
912
  except Exception as e:
913
  error_message = f"Error processing submission: {str(e)}"
914
  # send_error_notification(meta_data, error_message)
@@ -922,57 +984,61 @@ def process_submission(
922
  except Exception as e:
923
  print(f"Warning: Failed to delete temporary file {temp_file}: {str(e)}")
924
 
 
925
  # Modify the review script to add forum posts for status updates
926
  def update_json_file(file_path: str, content: dict, method_name: str = None, new_status: str = None) -> bool:
927
  """Update local JSON file and add forum post if status changed"""
928
  try:
929
- with open(file_path, 'w') as f:
930
  json.dump(content, f, indent=4)
931
-
932
  # Add forum post if this is a status update
933
  if method_name and new_status:
934
  forum.add_status_update(method_name, new_status)
935
-
936
  return True
937
  except Exception as e:
938
  print(f"Error updating {file_path}: {str(e)}")
939
  return False
940
-
 
941
  def filter_by_model_type(df, selected_types):
942
  """
943
  Filter DataFrame by selected model types, including submitted models.
944
  """
945
  if not selected_types:
946
  return df.head(0)
947
-
948
  # Get all models from selected types
949
  selected_models = []
950
  for type_name in selected_types:
951
  selected_models.extend(model_types[type_name])
952
-
953
  # Filter DataFrame to include only selected models
954
- return df[df['Method'].isin(selected_models)]
 
955
 
956
  def format_dataframe(df, dataset):
957
  """
958
  Format DataFrame for display, removing rows with no data for the specified dataset.
959
  """
960
  # Get relevant columns
961
- columns = ['Method'] + [col for col in df.columns if dataset in col]
962
  filtered_df = df[columns].copy()
963
-
964
  # Remove rows where all metric columns are NaN
965
- metric_columns = [col for col in filtered_df.columns if col != 'Method']
966
- filtered_df = filtered_df.dropna(subset=metric_columns, how='all')
967
-
968
  # Rename columns to remove dataset prefix
969
- filtered_df.columns = [col.split('_')[-1] if '_' in col else col for col in filtered_df.columns]
970
-
971
  # Sort by MRR
972
- filtered_df = filtered_df.sort_values('MRR', ascending=False)
973
-
974
  return filtered_df
975
 
 
976
  def update_tables(selected_types):
977
  """
978
  Update tables based on selected model types.
@@ -980,18 +1046,19 @@ def update_tables(selected_types):
980
  """
981
  if not selected_types:
982
  return [df.head(0) for df in [df_synthesized_full, df_synthesized_10, df_human_generated]]
983
-
984
  filtered_df_full = filter_by_model_type(df_synthesized_full, selected_types)
985
  filtered_df_10 = filter_by_model_type(df_synthesized_10, selected_types)
986
  filtered_df_human = filter_by_model_type(df_human_generated, selected_types)
987
-
988
  outputs = []
989
  for df in [filtered_df_full, filtered_df_10, filtered_df_human]:
990
- for dataset in ['AMAZON', 'MAG', 'PRIME']:
991
  outputs.append(format_dataframe(df, f"STARK-{dataset}"))
992
-
993
  return outputs
994
 
 
995
  css = """
996
  table > thead {
997
  white-space: normal
@@ -1014,8 +1081,10 @@ table > tbody > tr > td:nth-child(2) > div {
1014
  # Main application
1015
  with gr.Blocks(css=css) as demo:
1016
  gr.Markdown("# Semi-structured Retrieval Benchmark (STaRK) Leaderboard")
1017
- gr.Markdown("Refer to the [STaRK paper](https://arxiv.org/pdf/2404.13207) for details on metrics, tasks and models.")
1018
-
 
 
1019
  # Initialize leaderboard at startup
1020
  print("Starting leaderboard initialization...")
1021
  initialize_leaderboard()
@@ -1023,153 +1092,119 @@ with gr.Blocks(css=css) as demo:
1023
 
1024
  # Model type filter
1025
  model_type_filter = gr.CheckboxGroup(
1026
- choices=list(model_types.keys()),
1027
- value=list(model_types.keys()),
1028
- label="Model types",
1029
- interactive=True
1030
  )
1031
-
1032
  # Initialize dataframes list
1033
  all_dfs = []
1034
-
1035
  # Create nested tabs structure
1036
  with gr.Tabs() as outer_tabs:
1037
  with gr.TabItem("Synthesized (full)"):
1038
  with gr.Tabs() as inner_tabs1:
1039
- for dataset in ['AMAZON', 'MAG', 'PRIME']:
1040
  with gr.TabItem(dataset):
1041
  all_dfs.append(gr.DataFrame(interactive=False))
1042
-
1043
  with gr.TabItem("Synthesized (10%)"):
1044
  with gr.Tabs() as inner_tabs2:
1045
- for dataset in ['AMAZON', 'MAG', 'PRIME']:
1046
  with gr.TabItem(dataset):
1047
  all_dfs.append(gr.DataFrame(interactive=False))
1048
-
1049
  with gr.TabItem("Human-Generated"):
1050
  with gr.Tabs() as inner_tabs3:
1051
- for dataset in ['AMAZON', 'MAG', 'PRIME']:
1052
  with gr.TabItem(dataset):
1053
  all_dfs.append(gr.DataFrame(interactive=False))
1054
-
1055
  # Submission section
1056
  gr.Markdown("---")
1057
  gr.Markdown("## Submit Your Results")
1058
- gr.Markdown("""
 
1059
  Submit your results to be included in the leaderboard. Please ensure your submission meets all requirements.
1060
  For questions, contact stark-qa@cs.stanford.edu. Detailed instructions can be referred at [submission instructions](https://docs.google.com/document/d/11coGjTmOEi9p9-PUq1oy0eTOj8f_8CVQhDl5_0FKT14/edit?usp=sharing).
1061
- """)
1062
-
 
1063
  with gr.Row():
1064
  with gr.Column():
1065
- method_name = gr.Textbox(
1066
- label="Method Name (max 25 chars)*",
1067
- placeholder="e.g., MyRetrievalModel-v1"
1068
- )
1069
- dataset = gr.Dropdown(
1070
- choices=["amazon", "mag", "prime"],
1071
- label="Dataset*",
1072
- value="prime"
1073
- )
1074
  split = gr.Dropdown(
1075
- choices=["test", "test-0.1", "human_generated_eval"],
1076
- label="Split*",
1077
- value="human_generated_eval"
1078
- )
1079
- team_name = gr.Textbox(
1080
- label="Team Name (max 25 chars)*",
1081
- placeholder="e.g., Stanford NLP"
1082
- )
1083
- contact_email = gr.Textbox(
1084
- label="Contact Email(s)*",
1085
- placeholder="email@example.com; another@example.com"
1086
  )
 
 
1087
  model_type = gr.Dropdown(
1088
  choices=list(model_types.keys()),
1089
  label="Model Type*",
1090
  value="Others",
1091
- info="Select the appropriate category for your model"
1092
  )
1093
  model_description = gr.Textbox(
1094
- label="Model Description*",
1095
- lines=2,
1096
- placeholder="Briefly describe how your retriever model works..."
1097
  )
1098
-
1099
-
1100
  with gr.Column():
1101
  code_repo = gr.Textbox(
1102
- label="Code Repository*",
1103
- placeholder="https://github.com/snap-stanford/stark-leaderboard"
1104
- )
1105
- hardware = gr.Textbox(
1106
- label="Hardware Specifications*",
1107
- placeholder="e.g., 4x NVIDIA A100 80GB"
1108
  )
 
1109
  with gr.Row():
1110
  honor_code = gr.Checkbox(
1111
- label="By submitting these results, you confirm that they are truthful and reproducible, and you verify the integrity of your submission.",
1112
- value=False)
1113
- csv_file = gr.File(
1114
- label="Prediction CSV*",
1115
- file_types=[".csv"],
1116
- type="filepath"
1117
- )
1118
- paper_link = gr.Textbox(
1119
- label="Paper Link (Optional)",
1120
- placeholder="https://arxiv.org/abs/..."
1121
- )
1122
-
1123
  def update_submit_button(honor_checked):
1124
  """Update submit button state based on honor code checkbox"""
1125
  return gr.Button.update(interactive=honor_checked)
1126
 
1127
-
1128
  submit_btn = gr.Button("Submit", variant="primary")
1129
  result = gr.Textbox(label="Submission Status", interactive=False)
1130
-
1131
  # Set up event handlers
1132
- model_type_filter.change(
1133
- update_tables,
1134
- inputs=[model_type_filter],
1135
- outputs=all_dfs
1136
- )
1137
 
1138
  # Add forum section
1139
  gr.Markdown("---")
1140
  gr.Markdown("## Recent Submissions and Updates")
1141
-
1142
  forum_display = gr.Markdown(forum.format_posts_for_display())
1143
  refresh_btn = gr.Button("Refresh Forum")
1144
-
1145
  # Event handler for forum refresh
1146
- refresh_btn.click(
1147
- lambda: forum.format_posts_for_display(),
1148
- inputs=[],
1149
- outputs=[forum_display]
1150
- )
1151
-
1152
  # Event handler for submission button
1153
  submit_btn.click(
1154
  fn=process_submission,
1155
  inputs=[
1156
- method_name, team_name, dataset, split, contact_email,
1157
- code_repo, csv_file, model_description, hardware, paper_link, model_type, honor_code
 
 
 
 
 
 
 
 
 
 
1158
  ],
1159
- outputs=[result, forum_display]
1160
  ).then( # Chain the forum refresh after submission
1161
- fn=lambda: forum.format_posts_for_display(),
1162
- inputs=[],
1163
- outputs=[forum_display]
1164
  )
1165
-
1166
  # Initial table update
1167
- demo.load(
1168
- update_tables,
1169
- inputs=[model_type_filter],
1170
- outputs=all_dfs
1171
- )
1172
-
1173
 
1174
  # Launch the application
1175
- demo.launch()
 
33
  from utils.hub_storage import HubStorage
34
  from utils.token_handler import TokenHandler
35
 
36
+
37
  class ForumPost:
38
  def __init__(self, message: str, timestamp: str, post_type: str):
39
  self.message = message
40
  self.timestamp = timestamp
41
  self.post_type = post_type # 'submission' or 'status_update'
42
 
43
+
44
  class SubmissionForum:
45
  def __init__(self, forum_file="submissions/forum_posts.json", hub_storage=None):
46
  self.forum_file = forum_file
 
64
  """Save posts to JSON file in the hub"""
65
  try:
66
  posts_data = [
67
+ {"message": post.message, "timestamp": post.timestamp, "post_type": post.post_type}
 
 
 
 
68
  for post in self.posts
69
  ]
70
+
71
  # Convert to JSON string
72
  json_content = json.dumps(posts_data, indent=4)
73
+
74
  # Save to hub
75
  self.hub_storage.save_to_hub(
76
+ file_content=json_content, path_in_repo=self.forum_file, commit_message="Update forum posts"
 
 
77
  )
78
  except Exception as e:
79
  print(f"Error saving forum posts: {e}")
 
95
 
96
  def get_recent_posts(self, limit=50):
97
  """Get recent posts, newest first"""
98
+ return sorted(self.posts, key=lambda x: datetime.strptime(x.timestamp, "%Y-%m-%d %H:%M:%S"), reverse=True)[
99
+ :limit
100
+ ]
 
 
101
 
102
  def format_posts_for_display(self, limit=50):
103
  """Format posts for Gradio Markdown display"""
104
  recent_posts = self.get_recent_posts(limit)
105
  if not recent_posts:
106
  return "No forum posts yet."
107
+
108
  formatted_posts = []
109
  for post in recent_posts:
110
+ formatted_posts.append(f"**{post.timestamp}** \n" f"{post.message} \n" f"{'---'}")
 
 
 
 
111
  return "\n\n".join(formatted_posts)
112
 
113
+
114
  # Initialize storage once at startup
115
  try:
116
  REPO_ID = "snap-stanford/stark-leaderboard" # Replace with your space name
 
127
  try:
128
  # Get query data
129
  query, query_id, answer_ids, meta_info = qa_dataset[idx]
130
+
131
  # Get predictions
132
+ matching_preds = eval_csv[eval_csv["query_id"] == query_id]["pred_rank"]
133
  if len(matching_preds) == 0:
134
  print(f"Warning: No prediction found for query_id {query_id}")
135
  return None
136
  elif len(matching_preds) > 1:
137
  print(f"Warning: Multiple predictions found for query_id {query_id}, using first one")
138
+
139
  pred_rank = matching_preds.iloc[0]
140
+
141
  # Parse prediction
142
  if isinstance(pred_rank, str):
143
  try:
 
145
  except Exception as e:
146
  print(f"Error parsing pred_rank for query_id {query_id}: {str(e)}")
147
  return None
148
+
149
  # Validate prediction format
150
  if not isinstance(pred_rank, list):
151
  print(f"Warning: pred_rank is not a list for query_id {query_id}")
152
  return None
153
+
154
  # # Validate and filter prediction values
155
  # valid_pred_rank = []
156
  # for rank in pred_rank[:100]: # Only use top 100 predictions
 
158
  # valid_pred_rank.append(rank)
159
  # else:
160
  # print(f"Warning: Invalid prediction {rank} for query_id {query_id}")
161
+
162
  # if not valid_pred_rank:
163
  # print(f"Warning: No valid predictions for query_id {query_id}")
164
  # return None
165
+
166
  pred_dict = {pred_rank[i]: -i for i in range(min(100, len(pred_rank)))}
167
  answer_ids = torch.LongTensor(answer_ids)
168
  result = evaluator.evaluate(pred_dict, answer_ids, metrics=eval_metrics)
169
 
170
  result["idx"], result["query_id"] = idx, query_id
171
  return result
172
+
173
  except Exception as e:
174
  print(f"Error processing idx {idx}: {str(e)}")
175
  return None
176
 
177
+
178
  def compute_metrics(csv_path: str, dataset: str, split: str, num_workers: int = 4):
179
  """Compute metrics with improved thread safety and error handling"""
180
  start_time = time.time()
181
+
182
  # Dataset configuration
183
  candidate_ids_dict = {
184
+ "amazon": [i for i in range(957192)],
185
+ "mag": [i for i in range(1172724, 1872968)],
186
+ "prime": [i for i in range(129375)],
187
  }
188
+
189
  try:
190
  # Input validation
191
  if dataset not in candidate_ids_dict:
192
  raise ValueError(f"Invalid dataset '{dataset}'")
193
+ if split not in ["test", "test-0.1", "human_generated_eval"]:
194
  raise ValueError(f"Invalid split '{split}'")
195
+
196
  # Load and validate CSV
197
  print(f"\nLoading data for {dataset} {split}")
198
  eval_csv = pd.read_csv(csv_path)
199
+ required_columns = ["query_id", "pred_rank"]
200
  if not all(col in eval_csv.columns for col in required_columns):
201
  raise ValueError(f"CSV must contain columns: {required_columns}")
202
+
203
  eval_csv = eval_csv[required_columns]
204
+
205
  # Initialize components
206
  evaluator = Evaluator(candidate_ids_dict[dataset])
207
+ eval_metrics = ["hit@1", "hit@5", "recall@20", "mrr"]
208
+ qa_dataset = load_qa(dataset, human_generated_eval=split == "human_generated_eval")
209
  split_idx = qa_dataset.get_idx_split()
210
  all_indices = split_idx[split].tolist()
211
+
212
  print(f"Processing {len(all_indices)} instances with {num_workers} threads")
213
+
214
  # Process instances
215
  results_list = []
216
  valid_count = 0
217
  error_count = 0
218
+
219
  with ThreadPoolExecutor(max_workers=num_workers) as executor:
220
  futures = [
221
+ executor.submit(process_single_instance, (idx, eval_csv, qa_dataset, evaluator, eval_metrics))
 
 
 
222
  for idx in all_indices
223
  ]
224
+
225
  with tqdm(total=len(futures), desc="Processing") as pbar:
226
  for future in as_completed(futures):
227
  try:
 
235
  print(f"Error in future: {str(e)}")
236
  error_count += 1
237
  pbar.update(1)
238
+
239
  # Compute final metrics
240
  if not results_list:
241
  raise ValueError("No valid results were produced")
242
+
243
  print(f"\nProcessing complete. Valid: {valid_count}, Errors: {error_count}")
244
+
245
  results_df = pd.DataFrame(results_list)
246
+ final_results = {metric: results_df[metric].mean() for metric in eval_metrics}
247
+
 
 
 
248
  elapsed_time = time.time() - start_time
249
  print(f"Completed in {elapsed_time:.2f} seconds")
250
  return final_results
251
+
252
  except Exception as error:
253
  elapsed_time = time.time() - start_time
254
  error_msg = f"Error in compute_metrics ({elapsed_time:.2f}s): {str(error)}"
255
  print(error_msg)
256
  return error_msg
257
 
258
+
259
  # Data dictionaries for leaderboard
260
  data_synthesized_full = {
261
+ "Method": [
262
+ "BM25",
263
+ "DPR (roberta)",
264
+ "ANCE (roberta)",
265
+ "QAGNN (roberta)",
266
+ "ada-002",
267
+ "voyage-l2-instruct",
268
+ "LLM2Vec",
269
+ "GritLM-7b",
270
+ "multi-ada-002",
271
+ "ColBERTv2",
272
+ "AvaTaR(claude-3-opus)",
273
+ "AvaTaR(gpt-4-turbo)",
274
+ ],
275
+ "STARK-AMAZON_Hit@1": [44.94, 15.29, 30.96, 26.56, 39.16, 40.93, 21.74, 42.08, 40.07, 46.10, 49.97, 48.82],
276
+ "STARK-AMAZON_Hit@5": [67.42, 47.93, 51.06, 50.01, 62.73, 64.37, 41.65, 66.87, 64.98, 66.02, 69.16, 72.03],
277
+ "STARK-AMAZON_R@20": [53.77, 44.49, 41.95, 52.05, 53.29, 54.28, 33.22, 56.52, 55.12, 53.44, 60.57, 56.04],
278
+ "STARK-AMAZON_MRR": [55.30, 30.20, 40.66, 37.75, 50.35, 51.60, 31.47, 53.46, 51.55, 55.51, 58.70, 57.17],
279
+ "STARK-MAG_Hit@1": [25.85, 10.51, 21.96, 12.88, 29.08, 30.06, 18.01, 37.90, 25.92, 31.18, 44.36, 46.08],
280
+ "STARK-MAG_Hit@5": [45.25, 35.23, 36.50, 39.01, 49.61, 50.58, 34.85, 56.74, 50.43, 46.42, 59.66, 59.32],
281
+ "STARK-MAG_R@20": [45.69, 42.11, 35.32, 46.97, 48.36, 50.49, 35.46, 46.40, 50.80, 43.94, 50.63, 49.70],
282
+ "STARK-MAG_MRR": [34.91, 21.34, 29.14, 29.12, 38.62, 39.66, 26.10, 47.25, 36.94, 38.39, 51.15, 52.01],
283
+ "STARK-PRIME_Hit@1": [12.75, 4.46, 6.53, 8.85, 12.63, 10.85, 10.10, 15.57, 15.10, 11.75, 18.44, 20.10],
284
+ "STARK-PRIME_Hit@5": [27.92, 21.85, 15.67, 21.35, 31.49, 30.23, 22.49, 33.42, 33.56, 23.85, 36.73, 39.89],
285
+ "STARK-PRIME_R@20": [31.25, 30.13, 16.52, 29.63, 36.00, 37.83, 26.34, 39.09, 38.05, 25.04, 39.31, 42.23],
286
+ "STARK-PRIME_MRR": [19.84, 12.38, 11.05, 14.73, 21.41, 19.99, 16.12, 24.11, 23.49, 17.39, 26.73, 29.18],
287
  }
288
 
289
  data_synthesized_10 = {
290
+ "Method": [
291
+ "BM25",
292
+ "DPR (roberta)",
293
+ "ANCE (roberta)",
294
+ "QAGNN (roberta)",
295
+ "ada-002",
296
+ "voyage-l2-instruct",
297
+ "LLM2Vec",
298
+ "GritLM-7b",
299
+ "multi-ada-002",
300
+ "ColBERTv2",
301
+ "Claude3 Reranker",
302
+ "GPT4 Reranker",
303
+ ],
304
+ "STARK-AMAZON_Hit@1": [42.68, 16.46, 30.09, 25.00, 39.02, 43.29, 18.90, 43.29, 40.85, 44.31, 45.49, 44.79],
305
+ "STARK-AMAZON_Hit@5": [67.07, 50.00, 49.27, 48.17, 64.02, 67.68, 37.80, 71.34, 62.80, 65.24, 71.13, 71.17],
306
+ "STARK-AMAZON_R@20": [54.48, 42.15, 41.91, 51.65, 49.30, 56.04, 34.73, 56.14, 52.47, 51.00, 53.77, 55.35],
307
+ "STARK-AMAZON_MRR": [54.02, 30.20, 39.30, 36.87, 50.32, 54.20, 28.76, 55.07, 51.54, 55.07, 55.91, 55.69],
308
+ "STARK-MAG_Hit@1": [27.81, 11.65, 22.89, 12.03, 28.20, 34.59, 19.17, 38.35, 25.56, 31.58, 36.54, 40.90],
309
+ "STARK-MAG_Hit@5": [45.48, 36.84, 37.26, 37.97, 52.63, 50.75, 33.46, 58.64, 50.37, 47.36, 53.17, 58.18],
310
+ "STARK-MAG_R@20": [44.59, 42.30, 44.16, 47.98, 49.25, 50.75, 29.85, 46.38, 53.03, 45.72, 48.36, 48.60],
311
+ "STARK-MAG_MRR": [35.97, 21.82, 30.00, 28.70, 38.55, 42.90, 26.06, 48.25, 36.82, 38.98, 44.15, 49.00],
312
+ "STARK-PRIME_Hit@1": [13.93, 5.00, 6.78, 7.14, 15.36, 12.14, 9.29, 16.79, 15.36, 15.00, 17.79, 18.28],
313
+ "STARK-PRIME_Hit@5": [31.07, 23.57, 16.15, 17.14, 31.07, 31.42, 20.7, 34.29, 32.86, 26.07, 36.90, 37.28],
314
+ "STARK-PRIME_R@20": [32.84, 30.50, 17.07, 32.95, 37.88, 37.34, 25.54, 41.11, 40.99, 27.78, 35.57, 34.05],
315
+ "STARK-PRIME_MRR": [21.68, 13.50, 11.42, 16.27, 23.50, 21.23, 15.00, 24.99, 23.70, 19.98, 26.27, 26.55],
316
  }
317
 
318
  data_human_generated = {
319
+ "Method": [
320
+ "BM25",
321
+ "DPR (roberta)",
322
+ "ANCE (roberta)",
323
+ "QAGNN (roberta)",
324
+ "ada-002",
325
+ "voyage-l2-instruct",
326
+ "LLM2Vec",
327
+ "GritLM-7b",
328
+ "multi-ada-002",
329
+ "ColBERTv2",
330
+ "Claude3 Reranker",
331
+ "GPT4 Reranker",
332
+ "AvaTaR(gpt-4-turbo)",
333
+ ],
334
+ "STARK-AMAZON_Hit@1": [27.16, 16.05, 25.93, 22.22, 39.50, 35.80, 29.63, 40.74, 46.91, 33.33, 53.09, 50.62, 58.32],
335
+ "STARK-AMAZON_Hit@5": [51.85, 39.51, 54.32, 49.38, 64.19, 62.96, 46.91, 71.60, 72.84, 55.56, 74.07, 75.31, 76.54],
336
+ "STARK-AMAZON_R@20": [29.23, 15.23, 23.69, 21.54, 35.46, 33.01, 21.21, 36.30, 40.22, 29.03, 35.46, 35.46, 42.43],
337
+ "STARK-AMAZON_MRR": [18.79, 27.21, 37.12, 31.33, 52.65, 47.84, 38.61, 53.21, 58.74, 43.77, 62.11, 61.06, 65.91],
338
+ "STARK-MAG_Hit@1": [32.14, 4.72, 25.00, 20.24, 28.57, 22.62, 16.67, 34.52, 23.81, 33.33, 38.10, 36.90, 33.33],
339
+ "STARK-MAG_Hit@5": [41.67, 9.52, 30.95, 26.19, 41.67, 36.90, 28.57, 44.04, 41.67, 36.90, 45.24, 46.43, 42.86],
340
+ "STARK-MAG_R@20": [32.46, 25.00, 27.24, 28.76, 35.95, 32.44, 21.74, 34.57, 39.85, 30.50, 35.95, 35.95, 35.94],
341
+ "STARK-MAG_MRR": [37.42, 7.90, 27.98, 25.53, 35.81, 29.68, 21.59, 38.72, 31.43, 35.97, 42.00, 40.65, 38.62],
342
+ "STARK-PRIME_Hit@1": [22.45, 2.04, 7.14, 6.12, 17.35, 16.33, 9.18, 25.51, 24.49, 15.31, 28.57, 28.57, 33.03],
343
+ "STARK-PRIME_Hit@5": [41.84, 9.18, 13.27, 13.27, 34.69, 32.65, 21.43, 41.84, 39.80, 26.53, 46.94, 44.90, 51.37],
344
+ "STARK-PRIME_R@20": [42.32, 10.69, 11.72, 17.62, 41.09, 39.01, 26.77, 48.10, 47.21, 25.56, 41.61, 41.61, 53.34],
345
+ "STARK-PRIME_MRR": [30.37, 7.05, 10.07, 9.39, 26.35, 24.33, 15.24, 34.28, 32.98, 19.67, 36.32, 34.82, 41.00],
346
  }
347
 
348
  # Initialize DataFrames
 
352
 
353
  # Model type definitions
354
  model_types = {
355
+ "Sparse Retriever": ["BM25"],
356
+ "Small Dense Retrievers": ["DPR (roberta)", "ANCE (roberta)", "QAGNN (roberta)"],
357
+ "LLM-based Dense Retrievers": ["ada-002", "voyage-l2-instruct", "LLM2Vec", "GritLM-7b"],
358
+ "Multivector Retrievers": ["multi-ada-002", "ColBERTv2"],
359
+ "LLM Rerankers": ["Claude3 Reranker", "GPT4 Reranker", "AvaTaR(gpt-4-turbo)", "AvaTaR(claude-3-opus)"],
360
+ "Others": [], # Will be populated dynamically with submitted models
361
  }
362
 
363
+
364
  # Submission form validation functions
365
  def validate_email(email_str):
366
  """Validate email format(s)"""
367
+ emails = [e.strip() for e in email_str.split(";")]
368
+ email_pattern = re.compile(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")
369
  return all(email_pattern.match(email) for email in emails)
370
 
371
+
372
  def validate_github_url(url):
373
  """Validate GitHub URL format"""
374
+ github_pattern = re.compile(r"^https?:\/\/(?:www\.)?github\.com\/[\w-]+\/[\w.-]+\/?$")
 
 
375
  return bool(github_pattern.match(url))
376
 
377
+
378
  def validate_csv(file_obj):
379
  """Validate CSV file format and content"""
380
  try:
381
  df = pd.read_csv(file_obj.name)
382
+ required_cols = ["query_id", "pred_rank"]
383
+
384
  if not all(col in df.columns for col in required_cols):
385
  return False, "CSV must contain 'query_id' and 'pred_rank' columns"
386
+
387
  try:
388
+ first_rank = (
389
+ eval(df["pred_rank"].iloc[0]) if isinstance(df["pred_rank"].iloc[0], str) else df["pred_rank"].iloc[0]
390
+ )
391
  if not isinstance(first_rank, list) or len(first_rank) < 20:
392
  return False, "pred_rank must be a list with at least 20 candidates"
393
  except:
394
  return False, "Invalid pred_rank format"
395
+
396
  return True, "Valid CSV file"
397
  except Exception as e:
398
  return False, f"Error processing CSV: {str(e)}"
399
 
400
+
401
  def sanitize_name(name):
402
  """Sanitize name for file system use"""
403
+ return re.sub(r"[^a-zA-Z0-9]", "_", name)
404
+
405
 
406
  def read_json_from_hub(api: HfApi, repo_id: str, file_path: str) -> dict:
407
  """
408
  Read and parse JSON file from HuggingFace Hub.
409
+
410
  Args:
411
  api: HuggingFace API instance
412
  repo_id: Repository ID
413
  file_path: Path to file in repository
414
+
415
  Returns:
416
  dict: Parsed JSON content
417
  """
418
  try:
419
  # Download the file content as bytes
420
+ content = api.hf_hub_download(repo_id=repo_id, filename=file_path, repo_type="space")
421
+
 
 
 
 
422
  # Read and parse JSON
423
+ with open(content, "r") as f:
424
  return json.load(f)
425
  except Exception as e:
426
  print(f"Error reading JSON file {file_path}: {str(e)}")
427
  return None
428
 
429
+
430
  def scan_submissions_directory():
431
  """
432
  Scans the submissions directory and updates the model types dictionary
 
435
  try:
436
  # Initialize HuggingFace API
437
  api = HfApi()
438
+
439
  # Track submissions for each split
440
+ submissions_by_split = {"test": [], "test-0.1": [], "human_generated_eval": []}
441
+
 
 
 
 
442
  # Get all files from repository
443
  try:
444
+ all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="space")
 
 
 
445
  # Filter for files in submissions directory
446
+ repo_files = [f for f in all_files if f.startswith("submissions/")]
447
  except Exception as e:
448
  print(f"Error listing repository contents: {str(e)}")
449
  return submissions_by_split
450
+
451
  # Group files by team folders
452
  folder_files = {}
453
  for filepath in repo_files:
454
+ parts = filepath.split("/")
455
  if len(parts) < 3: # Need at least submissions/team_folder/file
456
  continue
457
+
458
  folder_name = parts[1] # team_folder name
459
  if folder_name not in folder_files:
460
  folder_files[folder_name] = []
461
  folder_files[folder_name].append(filepath)
462
+
463
  # Process each team folder
464
  for folder_name, files in folder_files.items():
465
  try:
466
  # Find latest.json in this folder
467
+ latest_file = next((f for f in files if f.endswith("latest.json")), None)
468
  if not latest_file:
469
  print(f"No latest.json found in {folder_name}")
470
  continue
471
+
472
  # Read latest.json
473
  latest_info = read_json_from_hub(api, REPO_ID, latest_file)
474
  if not latest_info:
475
  print(f"Failed to read latest.json for {folder_name}")
476
  continue
477
+
478
+ timestamp = latest_info.get("latest_submission")
479
  if not timestamp:
480
  print(f"No timestamp found in latest.json for {folder_name}")
481
  continue
482
+
483
  # Find metadata file for latest submission
484
+ metadata_file = next((f for f in files if f.endswith(f"metadata_{timestamp}.json")), None)
 
 
 
485
  if not metadata_file:
486
  print(f"No matching metadata file found for {folder_name} timestamp {timestamp}")
487
  continue
488
+
489
  # Read metadata file
490
  submission_data = read_json_from_hub(api, REPO_ID, metadata_file)
491
  if not submission_data:
492
  print(f"Failed to read metadata for {folder_name}")
493
  continue
494
+
495
+ if latest_info.get("status") != "approved":
496
  print(f"Skipping unapproved submission in {folder_name}")
497
  continue
498
+
499
  # Add to submissions by split
500
+ split = submission_data.get("Split")
501
  if split in submissions_by_split:
502
  submissions_by_split[split].append(submission_data)
503
+
504
  # Update model types if necessary
505
+ method_name = submission_data.get("Method Name")
506
+ model_type = submission_data.get("Model Type", "Others")
507
+
508
  # Add to model type if it's a new method
509
  method_exists = any(method_name in methods for methods in model_types.values())
510
  if not method_exists and model_type in model_types:
511
  model_types[model_type].append(method_name)
512
+
513
  except Exception as e:
514
  print(f"Error processing folder {folder_name}: {str(e)}")
515
  continue
516
+
517
  return submissions_by_split
518
+
519
  except Exception as e:
520
  print(f"Error scanning submissions directory: {str(e)}")
521
  return None
522
 
523
+
524
  def initialize_leaderboard():
525
  """
526
  Initialize the leaderboard with baseline results and submitted results.
527
  """
528
  global df_synthesized_full, df_synthesized_10, df_human_generated
529
+
530
  try:
531
  # First, initialize with baseline results
532
  df_synthesized_full = pd.DataFrame(data_synthesized_full)
533
  df_synthesized_10 = pd.DataFrame(data_synthesized_10)
534
  df_human_generated = pd.DataFrame(data_human_generated)
535
+
536
  print("Initialized with baseline results")
537
+
538
  # Then scan and add submitted results
539
  submissions = scan_submissions_directory()
540
  if submissions:
541
  for split, split_submissions in submissions.items():
542
  for submission in split_submissions:
543
+ if submission.get("results"): # Make sure we have results
544
  # Update appropriate DataFrame based on split
545
+ if split == "test":
546
  df_to_update = df_synthesized_full
547
+ elif split == "test-0.1":
548
  df_to_update = df_synthesized_10
549
  else: # human_generated_eval
550
  df_to_update = df_human_generated
551
+
552
  # Prepare new row data
553
  new_row = {
554
+ "Method": submission["Method Name"],
555
+ f'STARK-{submission["Dataset"].upper()}_Hit@1': submission["results"]["hit@1"],
556
+ f'STARK-{submission["Dataset"].upper()}_Hit@5': submission["results"]["hit@5"],
557
+ f'STARK-{submission["Dataset"].upper()}_R@20': submission["results"]["recall@20"],
558
+ f'STARK-{submission["Dataset"].upper()}_MRR': submission["results"]["mrr"],
559
  }
560
+
561
  # Update existing row or add new one
562
+ method_mask = df_to_update["Method"] == submission["Method Name"]
563
  if method_mask.any():
564
  for col in new_row:
565
  df_to_update.loc[method_mask, col] = new_row[col]
566
  else:
567
  df_to_update.loc[len(df_to_update)] = new_row
568
+
569
  print("Leaderboard initialization complete")
570
+
571
  except Exception as e:
572
  print(f"Error initializing leaderboard: {str(e)}")
573
 
574
+
575
  def get_file_content(file_path):
576
  """
577
  Helper function to safely read file content from HuggingFace repository
578
  """
579
  try:
580
  api = HfApi()
581
+ content_path = api.hf_hub_download(repo_id=REPO_ID, filename=file_path, repo_type="space")
582
+ with open(content_path, "r") as f:
 
 
 
 
583
  return f.read()
584
  except Exception as e:
585
  print(f"Error reading file {file_path}: {str(e)}")
586
  return None
587
 
588
+
589
  def save_submission(submission_data, csv_file):
590
  """
591
  Save submission data and CSV file using model_name_team_name format
592
+
593
  Args:
594
  submission_data (dict): Metadata and results for the submission
595
  csv_file: The uploaded CSV file object
596
  """
597
  # Create folder name from model name and team name
598
+ model_name_clean = sanitize_name(submission_data["Method Name"])
599
+ team_name_clean = sanitize_name(submission_data["Team Name"])
600
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
601
+
602
  # Create folder name: model_name_team_name
603
  folder_name = f"{model_name_clean}_{team_name_clean}"
604
  submission_id = f"{folder_name}_{timestamp}"
605
+
606
  # Create submission directory structure
607
  base_dir = "submissions"
608
  submission_dir = os.path.join(base_dir, folder_name)
609
  os.makedirs(submission_dir, exist_ok=True)
610
+
611
  # Save CSV file with timestamp to allow multiple submissions
612
  csv_filename = f"predictions_{timestamp}.csv"
613
  csv_path = os.path.join(submission_dir, csv_filename)
614
+ if hasattr(csv_file, "name"):
615
+ with open(csv_file.name, "rb") as source, open(csv_path, "wb") as target:
616
  target.write(source.read())
617
+
618
  # Add file paths to submission data
619
+ submission_data.update({"csv_path": csv_path, "submission_id": submission_id, "folder_name": folder_name})
620
+
 
 
 
 
621
  # Save metadata as JSON with timestamp
622
  metadata_path = os.path.join(submission_dir, f"metadata_{timestamp}.json")
623
+ with open(metadata_path, "w") as f:
624
  json.dump(submission_data, f, indent=4)
625
+
626
  # Update latest.json to track most recent submission
627
  latest_path = os.path.join(submission_dir, "latest.json")
628
+ with open(latest_path, "w") as f:
629
+ json.dump(
630
+ {
631
+ "latest_submission": timestamp,
632
+ "status": "pending_review",
633
+ "method_name": submission_data["Method Name"],
634
+ },
635
+ f,
636
+ indent=4,
637
+ )
638
+
639
  return submission_id
640
 
641
+
642
  def update_leaderboard_data(submission_data):
643
  """
644
  Update leaderboard data with new submission results
645
  Only uses model name in the displayed table
646
  """
647
  global df_synthesized_full, df_synthesized_10, df_human_generated
648
+
649
  # Determine which DataFrame to update based on split
650
  split_to_df = {
651
+ "test": df_synthesized_full,
652
+ "test-0.1": df_synthesized_10,
653
+ "human_generated_eval": df_human_generated,
654
  }
655
+
656
+ df_to_update = split_to_df[submission_data["Split"]]
657
+ submitted_dataset = submission_data["Dataset"].upper()
658
+
659
  # Prepare new row data
660
  new_row = {
661
+ "Method": submission_data["Method Name"],
662
+ f"STARK-{submitted_dataset}_Hit@1": submission_data["results"]["hit@1"],
663
+ f"STARK-{submitted_dataset}_Hit@5": submission_data["results"]["hit@5"],
664
+ f"STARK-{submitted_dataset}_R@20": submission_data["results"]["recall@20"],
665
+ f"STARK-{submitted_dataset}_MRR": submission_data["results"]["mrr"],
666
  }
667
+
668
  # Check if method already exists
669
+ method_mask = df_to_update["Method"] == submission_data["Method Name"]
670
  if method_mask.any():
671
  # Update existing row
672
  for col in new_row:
 
678
  full_row.update(new_row) # Update with the submitted dataset's values
679
  df_to_update.loc[len(df_to_update)] = full_row
680
 
681
+
682
  # Function to get emails from meta_data
683
  def get_emails_from_metadata(meta_data):
684
  """
685
  Extracts emails from the meta_data dictionary.
686
+
687
  Args:
688
  meta_data (dict): The metadata dictionary that contains the 'Contact Email(s)' field.
689
+
690
  Returns:
691
  list: A list of email addresses.
692
  """
693
  return [email.strip() for email in meta_data.get("Contact Email(s)", "").split(";")]
694
 
695
+
696
  # Function to format meta_data as an HTML table (without Prediction CSV)
697
  def format_metadata_as_table(meta_data):
698
  """
 
706
  str: HTML string representing the metadata table.
707
  """
708
  table_rows = ""
709
+
710
  for key, value in meta_data.items():
711
  if key == "Contact Email(s)":
712
  # Ensure that contact emails are split by semicolon
713
+ emails = value.split(";")
714
  formatted_emails = "; ".join([email.strip() for email in emails])
715
  table_rows += f"<tr><td><b>{key}</b></td><td>{formatted_emails}</td></tr>"
716
  elif key != "Prediction CSV": # Exclude the Prediction CSV field
 
723
  """
724
  return table_html
725
 
726
+
727
  # Function to get emails from meta_data
728
  def get_emails_from_metadata(meta_data):
729
  """
730
  Extracts emails from the meta_data dictionary.
731
+
732
  Args:
733
  meta_data (dict): The metadata dictionary that contains the 'Contact Email(s)' field.
734
+
735
  Returns:
736
  list: A list of email addresses.
737
  """
738
  return [email.strip() for email in meta_data.get("Contact Email(s)", "").split(";")]
739
+
740
+
741
  def format_evaluation_results(results):
742
  """
743
  Formats the evaluation results dictionary into a readable string.
 
751
  result_lines = [f"{metric}: {value}" for metric, value in results.items()]
752
  return "\n".join(result_lines)
753
 
754
+
755
  def get_model_type_for_method(method_name):
756
  """
757
  Find the model type category for a given method name.
 
760
  for type_name, methods in model_types.items():
761
  if method_name in methods:
762
  return type_name
763
+ return "Others"
764
+
765
 
766
  def validate_model_type(method_name, selected_type):
767
  """
 
774
  if method_name in methods:
775
  existing_type = type_name
776
  break
777
+
778
  # If method exists, it must be submitted under its predefined category
779
  if existing_type:
780
  if existing_type != selected_type:
781
+ return (
782
+ False,
783
+ f"This method name is already registered under '{existing_type}'. Please use the correct category.",
784
+ )
785
  return True, "Valid model type"
786
+
787
  # For new methods, any category is valid
788
  return True, "Valid model type"
789
 
790
+
791
  def process_submission(
792
+ method_name,
793
+ team_name,
794
+ dataset,
795
+ split,
796
+ contact_email,
797
+ code_repo,
798
+ csv_file,
799
+ model_description,
800
+ hardware,
801
+ paper_link,
802
+ model_type,
803
+ honor_code,
804
  ):
805
  """Process and validate submission"""
806
  if not honor_code:
807
  return "Error: Please accept the honor code to submit"
808
+
809
  temp_files = []
810
  try:
811
  # Input validation
812
  if not all([method_name, team_name, dataset, split, contact_email, code_repo, csv_file, model_type]):
813
  return "Error: Please fill in all required fields"
814
+
815
  # Validate model type
816
  is_valid, message = validate_model_type(method_name, model_type)
817
  if not is_valid:
 
828
  "Model Description": model_description,
829
  "Hardware": hardware,
830
  "(Optional) Paper link": paper_link,
831
+ "Model Type": model_type,
832
  }
833
+
834
  # Generate folder name and timestamp
835
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
836
  folder_name = f"{sanitize_name(method_name)}_{sanitize_name(team_name)}"
837
+
838
  # Process CSV file
839
  csv_content = None
840
  if isinstance(csv_file, str):
841
+ with open(csv_file, "r") as f:
842
  csv_content = f.read()
843
+ elif hasattr(csv_file, "name"):
844
+ with open(csv_file.name, "r") as f:
845
  csv_content = f.read()
846
  else:
847
  return "Error: Invalid CSV file", forum.format_posts_for_display()
 
851
  csv_path=csv_file if isinstance(csv_file, str) else csv_file.name,
852
  dataset=dataset.lower(),
853
  split=split,
854
+ num_workers=4,
855
  )
856
+
857
  if isinstance(results, str):
858
  return f"Evaluation error: {results}", forum.format_posts_for_display()
859
 
860
  # Process results
861
  processed_results = {
862
+ "hit@1": round(results["hit@1"] * 100, 2),
863
+ "hit@5": round(results["hit@5"] * 100, 2),
864
+ "recall@20": round(results["recall@20"] * 100, 2),
865
+ "mrr": round(results["mrr"] * 100, 2),
866
  }
867
 
868
  meta_data = {
 
878
  "Model Type": model_type,
879
  "results": processed_results,
880
  "status": "pending_review",
881
+ "submission_date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
882
  }
883
+
884
+ # Save files locally to submissions/ directory
885
+ base_dir = "submissions"
886
+ submission_dir = os.path.join(base_dir, folder_name)
887
+ os.makedirs(submission_dir, exist_ok=True)
888
+
889
+ # 1. Save CSV file locally
890
+ csv_filename = f"predictions_{timestamp}.csv"
891
+ csv_local_path = os.path.join(submission_dir, csv_filename)
892
+ with open(csv_local_path, "w") as f:
893
+ f.write(csv_content)
894
+
895
+ # 2. Save metadata locally
896
+ metadata_local_path = os.path.join(submission_dir, f"metadata_{timestamp}.json")
897
+ with open(metadata_local_path, "w") as f:
898
+ json.dump(meta_data, f, indent=4)
899
+
900
+ # 3. Create or update latest.json locally
901
+ latest_local_path = os.path.join(submission_dir, "latest.json")
902
+ latest_info = {
903
+ "latest_submission": timestamp,
904
+ "status": "pending_review",
905
+ "method_name": method_name,
906
+ "team_name": team_name,
907
+ }
908
+ with open(latest_local_path, "w") as f:
909
+ json.dump(latest_info, f, indent=4)
910
+
911
  # Save files to HuggingFace Hub
912
  try:
913
+ # 1. Save CSV file to hub
914
  csv_filename = f"predictions_{timestamp}.csv"
915
  csv_path_in_repo = f"submissions/{folder_name}/{csv_filename}"
916
  hub_storage.save_to_hub(
917
  file_content=csv_content,
918
  path_in_repo=csv_path_in_repo,
919
+ commit_message=f"Add submission: {method_name} by {team_name}",
920
  )
921
  meta_data["csv_path"] = csv_path_in_repo
922
 
923
+ # 2. Save metadata to hub
924
  metadata_path = f"submissions/{folder_name}/metadata_{timestamp}.json"
925
  metadata_content = json.dumps(meta_data, indent=4)
926
  hub_storage.save_to_hub(
927
+ file_content=metadata_content,
928
  path_in_repo=metadata_path,
929
+ commit_message=f"Add metadata: {method_name} by {team_name}",
930
  )
931
 
932
+ # 3. Create or update latest.json on hub
 
 
 
 
 
 
 
933
  latest_path = f"submissions/{folder_name}/latest.json"
934
  latest_content = json.dumps(latest_info, indent=4)
935
  hub_storage.save_to_hub(
936
+ file_content=latest_content,
937
  path_in_repo=latest_path,
938
+ commit_message=f"Update latest submission info for {method_name}",
939
  )
940
 
941
  except Exception as e:
942
+ print(f"Warning: Failed to save files to HuggingFace Hub: {str(e)}")
943
+ # Continue anyway since local files were saved successfully
944
+
945
  # Send confirmation email and update leaderboard data
946
  # send_submission_confirmation(meta_data, processed_results)
947
  update_leaderboard_data(meta_data)
948
 
949
  forum.add_submission_post(method_name, dataset, split)
950
  forum_display = forum.format_posts_for_display()
951
+
952
  # Return success message
953
+ return (
954
+ f"""
955
+ Submission successful!
956
+
957
  Evaluation Results:
958
  Hit@1: {processed_results['hit@1']:.2f}%
959
  Hit@5: {processed_results['hit@5']:.2f}%
960
  Recall@20: {processed_results['recall@20']:.2f}%
961
  MRR: {processed_results['mrr']:.2f}%
962
+
963
  Your submission has been saved and a confirmation email has been sent to {contact_email}.
964
  Once approved, your results will appear in the leaderboard under: {method_name}
965
+
966
  You can find your submission at:
967
  https://huggingface.co/spaces/{REPO_ID}/tree/main/submissions/{folder_name}
968
+
969
  Please refresh the page to see your submission in the leaderboard.
970
+ """,
971
+ forum_display,
972
+ )
973
+
974
  except Exception as e:
975
  error_message = f"Error processing submission: {str(e)}"
976
  # send_error_notification(meta_data, error_message)
 
984
  except Exception as e:
985
  print(f"Warning: Failed to delete temporary file {temp_file}: {str(e)}")
986
 
987
+
988
  # Modify the review script to add forum posts for status updates
989
  def update_json_file(file_path: str, content: dict, method_name: str = None, new_status: str = None) -> bool:
990
  """Update local JSON file and add forum post if status changed"""
991
  try:
992
+ with open(file_path, "w") as f:
993
  json.dump(content, f, indent=4)
994
+
995
  # Add forum post if this is a status update
996
  if method_name and new_status:
997
  forum.add_status_update(method_name, new_status)
998
+
999
  return True
1000
  except Exception as e:
1001
  print(f"Error updating {file_path}: {str(e)}")
1002
  return False
1003
+
1004
+
1005
  def filter_by_model_type(df, selected_types):
1006
  """
1007
  Filter DataFrame by selected model types, including submitted models.
1008
  """
1009
  if not selected_types:
1010
  return df.head(0)
1011
+
1012
  # Get all models from selected types
1013
  selected_models = []
1014
  for type_name in selected_types:
1015
  selected_models.extend(model_types[type_name])
1016
+
1017
  # Filter DataFrame to include only selected models
1018
+ return df[df["Method"].isin(selected_models)]
1019
+
1020
 
1021
  def format_dataframe(df, dataset):
1022
  """
1023
  Format DataFrame for display, removing rows with no data for the specified dataset.
1024
  """
1025
  # Get relevant columns
1026
+ columns = ["Method"] + [col for col in df.columns if dataset in col]
1027
  filtered_df = df[columns].copy()
1028
+
1029
  # Remove rows where all metric columns are NaN
1030
+ metric_columns = [col for col in filtered_df.columns if col != "Method"]
1031
+ filtered_df = filtered_df.dropna(subset=metric_columns, how="all")
1032
+
1033
  # Rename columns to remove dataset prefix
1034
+ filtered_df.columns = [col.split("_")[-1] if "_" in col else col for col in filtered_df.columns]
1035
+
1036
  # Sort by MRR
1037
+ filtered_df = filtered_df.sort_values("MRR", ascending=False)
1038
+
1039
  return filtered_df
1040
 
1041
+
1042
  def update_tables(selected_types):
1043
  """
1044
  Update tables based on selected model types.
 
1046
  """
1047
  if not selected_types:
1048
  return [df.head(0) for df in [df_synthesized_full, df_synthesized_10, df_human_generated]]
1049
+
1050
  filtered_df_full = filter_by_model_type(df_synthesized_full, selected_types)
1051
  filtered_df_10 = filter_by_model_type(df_synthesized_10, selected_types)
1052
  filtered_df_human = filter_by_model_type(df_human_generated, selected_types)
1053
+
1054
  outputs = []
1055
  for df in [filtered_df_full, filtered_df_10, filtered_df_human]:
1056
+ for dataset in ["AMAZON", "MAG", "PRIME"]:
1057
  outputs.append(format_dataframe(df, f"STARK-{dataset}"))
1058
+
1059
  return outputs
1060
 
1061
+
1062
  css = """
1063
  table > thead {
1064
  white-space: normal
 
1081
  # Main application
1082
  with gr.Blocks(css=css) as demo:
1083
  gr.Markdown("# Semi-structured Retrieval Benchmark (STaRK) Leaderboard")
1084
+ gr.Markdown(
1085
+ "Refer to the [STaRK paper](https://arxiv.org/pdf/2404.13207) for details on metrics, tasks and models."
1086
+ )
1087
+
1088
  # Initialize leaderboard at startup
1089
  print("Starting leaderboard initialization...")
1090
  initialize_leaderboard()
 
1092
 
1093
  # Model type filter
1094
  model_type_filter = gr.CheckboxGroup(
1095
+ choices=list(model_types.keys()), value=list(model_types.keys()), label="Model types", interactive=True
 
 
 
1096
  )
1097
+
1098
  # Initialize dataframes list
1099
  all_dfs = []
1100
+
1101
  # Create nested tabs structure
1102
  with gr.Tabs() as outer_tabs:
1103
  with gr.TabItem("Synthesized (full)"):
1104
  with gr.Tabs() as inner_tabs1:
1105
+ for dataset in ["AMAZON", "MAG", "PRIME"]:
1106
  with gr.TabItem(dataset):
1107
  all_dfs.append(gr.DataFrame(interactive=False))
1108
+
1109
  with gr.TabItem("Synthesized (10%)"):
1110
  with gr.Tabs() as inner_tabs2:
1111
+ for dataset in ["AMAZON", "MAG", "PRIME"]:
1112
  with gr.TabItem(dataset):
1113
  all_dfs.append(gr.DataFrame(interactive=False))
1114
+
1115
  with gr.TabItem("Human-Generated"):
1116
  with gr.Tabs() as inner_tabs3:
1117
+ for dataset in ["AMAZON", "MAG", "PRIME"]:
1118
  with gr.TabItem(dataset):
1119
  all_dfs.append(gr.DataFrame(interactive=False))
1120
+
1121
  # Submission section
1122
  gr.Markdown("---")
1123
  gr.Markdown("## Submit Your Results")
1124
+ gr.Markdown(
1125
+ """
1126
  Submit your results to be included in the leaderboard. Please ensure your submission meets all requirements.
1127
  For questions, contact stark-qa@cs.stanford.edu. Detailed instructions can be referred at [submission instructions](https://docs.google.com/document/d/11coGjTmOEi9p9-PUq1oy0eTOj8f_8CVQhDl5_0FKT14/edit?usp=sharing).
1128
+ """
1129
+ )
1130
+
1131
  with gr.Row():
1132
  with gr.Column():
1133
+ method_name = gr.Textbox(label="Method Name (max 25 chars)*", placeholder="e.g., MyRetrievalModel-v1")
1134
+ dataset = gr.Dropdown(choices=["amazon", "mag", "prime"], label="Dataset*", value="prime")
 
 
 
 
 
 
 
1135
  split = gr.Dropdown(
1136
+ choices=["test", "test-0.1", "human_generated_eval"], label="Split*", value="human_generated_eval"
 
 
 
 
 
 
 
 
 
 
1137
  )
1138
+ team_name = gr.Textbox(label="Team Name (max 25 chars)*", placeholder="e.g., Stanford NLP")
1139
+ contact_email = gr.Textbox(label="Contact Email(s)*", placeholder="email@example.com; another@example.com")
1140
  model_type = gr.Dropdown(
1141
  choices=list(model_types.keys()),
1142
  label="Model Type*",
1143
  value="Others",
1144
+ info="Select the appropriate category for your model",
1145
  )
1146
  model_description = gr.Textbox(
1147
+ label="Model Description*", lines=2, placeholder="Briefly describe how your retriever model works..."
 
 
1148
  )
1149
+
 
1150
  with gr.Column():
1151
  code_repo = gr.Textbox(
1152
+ label="Code Repository*", placeholder="https://github.com/snap-stanford/stark-leaderboard"
 
 
 
 
 
1153
  )
1154
+ hardware = gr.Textbox(label="Hardware Specifications*", placeholder="e.g., 4x NVIDIA A100 80GB")
1155
  with gr.Row():
1156
  honor_code = gr.Checkbox(
1157
+ label="By submitting these results, you confirm that they are truthful and reproducible, and you verify the integrity of your submission.",
1158
+ value=False,
1159
+ )
1160
+ csv_file = gr.File(label="Prediction CSV*", file_types=[".csv"], type="filepath")
1161
+ paper_link = gr.Textbox(label="Paper Link (Optional)", placeholder="https://arxiv.org/abs/...")
1162
+
 
 
 
 
 
 
1163
  def update_submit_button(honor_checked):
1164
  """Update submit button state based on honor code checkbox"""
1165
  return gr.Button.update(interactive=honor_checked)
1166
 
 
1167
  submit_btn = gr.Button("Submit", variant="primary")
1168
  result = gr.Textbox(label="Submission Status", interactive=False)
1169
+
1170
  # Set up event handlers
1171
+ model_type_filter.change(update_tables, inputs=[model_type_filter], outputs=all_dfs)
 
 
 
 
1172
 
1173
  # Add forum section
1174
  gr.Markdown("---")
1175
  gr.Markdown("## Recent Submissions and Updates")
1176
+
1177
  forum_display = gr.Markdown(forum.format_posts_for_display())
1178
  refresh_btn = gr.Button("Refresh Forum")
1179
+
1180
  # Event handler for forum refresh
1181
+ refresh_btn.click(lambda: forum.format_posts_for_display(), inputs=[], outputs=[forum_display])
1182
+
 
 
 
 
1183
  # Event handler for submission button
1184
  submit_btn.click(
1185
  fn=process_submission,
1186
  inputs=[
1187
+ method_name,
1188
+ team_name,
1189
+ dataset,
1190
+ split,
1191
+ contact_email,
1192
+ code_repo,
1193
+ csv_file,
1194
+ model_description,
1195
+ hardware,
1196
+ paper_link,
1197
+ model_type,
1198
+ honor_code,
1199
  ],
1200
+ outputs=[result, forum_display],
1201
  ).then( # Chain the forum refresh after submission
1202
+ fn=lambda: forum.format_posts_for_display(), inputs=[], outputs=[forum_display]
 
 
1203
  )
1204
+
1205
  # Initial table update
1206
+ demo.load(update_tables, inputs=[model_type_filter], outputs=all_dfs)
1207
+
 
 
 
 
1208
 
1209
  # Launch the application
1210
+ demo.launch()
submissions/forum_posts.json CHANGED
@@ -1,24 +1,4 @@
1
  [
2
- {
3
- "message": "\ud83d\udce5 New submission: debug_test on human_generated_eval/mag",
4
- "timestamp": "2024-11-21 01:16:54",
5
- "post_type": "submission"
6
- },
7
- {
8
- "message": "\ud83d\udce5 New submission: abc on human_generated_eval/mag",
9
- "timestamp": "2024-11-21 02:00:17",
10
- "post_type": "submission"
11
- },
12
- {
13
- "message": "\u274c Status update: abc has been rejected",
14
- "timestamp": "2024-11-20 17:09:16",
15
- "post_type": "status_update"
16
- },
17
- {
18
- "message": "\u274c Status update: debug_test has been rejected",
19
- "timestamp": "2024-11-20 17:09:52",
20
- "post_type": "status_update"
21
- },
22
  {
23
  "message": "\ud83d\udce5 New submission: Paprv1 on test-0.1/mag",
24
  "timestamp": "2025-02-05 06:50:41",
@@ -29,4 +9,4 @@
29
  "timestamp": "2025-02-12 19:49:39",
30
  "post_type": "status_update"
31
  }
32
- ]
 
1
  [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  {
3
  "message": "\ud83d\udce5 New submission: Paprv1 on test-0.1/mag",
4
  "timestamp": "2025-02-05 06:50:41",
 
9
  "timestamp": "2025-02-12 19:49:39",
10
  "post_type": "status_update"
11
  }
12
+ ]