ymk00 commited on
Commit
ca11711
·
1 Parent(s): 7cea663

Add functionality to change primary metric for dataset

Browse files
backend/app/config/hf_config.py CHANGED
@@ -12,7 +12,7 @@ from app.utils.logging import LogFormatter
12
  logger = logging.getLogger(__name__)
13
 
14
  # Organization or user who owns the datasets
15
- HF_ORGANIZATION = os.environ.get("HF_ORGANIZATION", "Xueqing")
16
 
17
  # Get HF token directly from environment
18
  HF_TOKEN = os.environ.get("HF_TOKEN")
@@ -23,7 +23,7 @@ if not HF_TOKEN:
23
  API = HfApi(token=HF_TOKEN)
24
 
25
  # Repository configuration
26
- HF_AGGREGATED = os.environ.get("HF_AGGREGATED", "greek-contents")
27
  QUEUE_REPO = f"{HF_ORGANIZATION}/requests"
28
  AGGREGATED_REPO = f"{HF_ORGANIZATION}/{HF_AGGREGATED}"
29
  VOTES_REPO = f"{HF_ORGANIZATION}/votes"
 
12
  logger = logging.getLogger(__name__)
13
 
14
  # Organization or user who owns the datasets
15
+ HF_ORGANIZATION = os.environ.get("HF_ORGANIZATION", "ymk00")
16
 
17
  # Get HF token directly from environment
18
  HF_TOKEN = os.environ.get("HF_TOKEN")
 
23
  API = HfApi(token=HF_TOKEN)
24
 
25
  # Repository configuration
26
+ HF_AGGREGATED = os.environ.get("HF_AGGREGATED", "dataset-test")
27
  QUEUE_REPO = f"{HF_ORGANIZATION}/requests"
28
  AGGREGATED_REPO = f"{HF_ORGANIZATION}/{HF_AGGREGATED}"
29
  VOTES_REPO = f"{HF_ORGANIZATION}/votes"
backend/app/services/leaderboard.py CHANGED
@@ -9,33 +9,38 @@ from app.utils.logging import LogFormatter
9
 
10
  logger = logging.getLogger(__name__)
11
 
 
12
  class LeaderboardService:
13
  def __init__(self):
14
  pass
15
-
16
  async def fetch_raw_data(self) -> List[Dict[str, Any]]:
17
  """Fetch raw leaderboard data from HuggingFace dataset"""
18
  try:
19
  logger.info(LogFormatter.section("FETCHING LEADERBOARD DATA"))
20
- logger.info(LogFormatter.info(f"Loading dataset from {HF_ORGANIZATION}/{HF_AGGREGATED}"))
21
-
 
 
 
 
22
  dataset = datasets.load_dataset(
23
  f"{HF_ORGANIZATION}/{HF_AGGREGATED}",
24
- cache_dir=cache_config.get_cache_path("datasets")
25
  )["train"]
26
-
27
  df = dataset.to_pandas()
28
- data = df.to_dict('records')
29
-
30
  stats = {
31
  "Total_Entries": len(data),
32
- "Dataset_Size": f"{df.memory_usage(deep=True).sum() / 1024 / 1024:.1f}MB"
33
  }
34
  for line in LogFormatter.stats(stats, "Dataset Statistics"):
35
  logger.info(line)
36
-
37
  return data
38
-
39
  except Exception as e:
40
  logger.error(LogFormatter.error("Failed to fetch leaderboard data", e))
41
  raise HTTPException(status_code=500, detail=str(e))
@@ -44,127 +49,163 @@ class LeaderboardService:
44
  """Get formatted leaderboard data"""
45
  try:
46
  logger.info(LogFormatter.section("FORMATTING LEADERBOARD DATA"))
47
-
48
  raw_data = await self.fetch_raw_data()
49
  formatted_data = []
50
  type_counts = {}
51
  error_count = 0
52
-
53
  # Initialize progress tracking
54
  total_items = len(raw_data)
55
  logger.info(LogFormatter.info(f"Processing {total_items:,} entries..."))
56
-
57
  for i, item in enumerate(raw_data, 1):
58
  try:
59
  formatted_item = await self.transform_data(item)
60
  formatted_data.append(formatted_item)
61
-
62
  # Count model types
63
  model_type = formatted_item["model"]["type"]
64
  type_counts[model_type] = type_counts.get(model_type, 0) + 1
65
-
66
  except Exception as e:
67
  error_count += 1
68
- logger.error(LogFormatter.error(f"Failed to format entry {i}/{total_items}", e))
 
 
 
 
69
  continue
70
-
71
  # Log progress every 10%
72
  if i % max(1, total_items // 10) == 0:
73
  progress = (i / total_items) * 100
74
- logger.info(LogFormatter.info(f"Progress: {LogFormatter.progress_bar(i, total_items)}"))
75
-
 
 
 
 
76
  # Log final statistics
77
  stats = {
78
  "Total_Processed": total_items,
79
  "Successful": len(formatted_data),
80
- "Failed": error_count
81
  }
82
  logger.info(LogFormatter.section("PROCESSING SUMMARY"))
83
  for line in LogFormatter.stats(stats, "Processing Statistics"):
84
  logger.info(line)
85
-
86
  # Log model type distribution
87
  type_stats = {f"Type_{k}": v for k, v in type_counts.items()}
88
  logger.info(LogFormatter.subsection("MODEL TYPE DISTRIBUTION"))
89
  for line in LogFormatter.stats(type_stats):
90
  logger.info(line)
91
-
92
  return formatted_data
93
-
94
  except Exception as e:
95
  logger.error(LogFormatter.error("Failed to format leaderboard data", e))
96
  raise HTTPException(status_code=500, detail=str(e))
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  async def transform_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
99
  """Transform raw data into the format expected by the frontend"""
100
  try:
101
  # Extract model name for logging
102
  model_name = data.get("fullname", "Unknown")
103
- logger.debug(LogFormatter.info(f"Transforming data for model: {model_name}"))
104
-
 
 
105
  # Create unique ID combining model name, precision, sha and chat template status
106
  unique_id = f"{data.get('fullname', 'Unknown')}_{data.get('Precision', 'Unknown')}_{data.get('Model sha', 'Unknown')}_{str(data.get('Chat Template', False))}"
107
-
108
  evaluations = {
109
  "bc5cdr_chemical": {
110
  "name": "BC5CDR-chemical",
111
- "value": data.get("BC5CDR-chemical Raw", 0),
112
- "normalized_score": data.get("BC5CDR-chemical", 0),
113
  },
114
  "ncbi_disease": {
115
- "name": "NCBI Disease",
116
- "value": data.get("NCBI Disease Raw", 0),
117
- "normalized_score": data.get("NCBI Disease", 0),
118
  },
119
  "chemprot": {
120
  "name": "ChemProt",
121
- "value": data.get("ChemProt Raw", 0),
122
- "normalized_score": data.get("ChemProt", 0),
123
  },
124
  "ddi2013": {
125
  "name": "DDI2013",
126
- "value": data.get("DDI2013 Raw", 0),
127
- "normalized_score": data.get("DDI2013", 0),
128
  },
129
  "hoc": {
130
  "name": "HoC",
131
- "value": data.get("HoC Raw", 0),
132
- "normalized_score": data.get("HoC", 0),
133
  },
134
  "litcovid": {
135
  "name": "LitCovid",
136
- "value": data.get("LitCovid Raw", 0),
137
- "normalized_score": data.get("LitCovid", 0),
138
  },
139
  "medqa": {
140
  "name": "MedQA (5-Option)",
141
- "value": data.get("MedQA (5-Option) Raw", 0),
142
- "normalized_score": data.get("MedQA (5-Option)", 0),
143
  },
144
  "pubmedqa": {
145
  "name": "PubMedQA",
146
- "value": data.get("PubMedQA Raw", 0),
147
- "normalized_score": data.get("PubMedQA", 0),
148
  },
149
  "pubmed": {
150
  "name": "PubMed",
151
- "value": data.get("PubMed Raw", 0),
152
- "normalized_score": data.get("PubMed", 0),
153
  },
154
  "ms2": {
155
  "name": "MS^2",
156
- "value": data.get("MS^2 Raw", 0),
157
- "normalized_score": data.get("MS^2", 0),
158
  },
159
  "cochrane_pls": {
160
  "name": "Cochrane PLS",
161
- "value": data.get("Cochrane PLS Raw", 0),
162
- "normalized_score": data.get("Cochrane PLS", 0),
163
  },
164
  "plos": {
165
  "name": "PLOS",
166
- "value": data.get("PLOS Raw", 0),
167
- "normalized_score": data.get("PLOS", 0),
168
  },
169
  }
170
 
@@ -173,7 +214,7 @@ class LeaderboardService:
173
  "is_merged": data.get("Merged", False),
174
  "is_moe": data.get("MoE", False),
175
  "is_flagged": data.get("Flagged", False),
176
- "is_highlighted_by_maintainer": data.get("Official Providers", False)
177
  }
178
 
179
  metadata = {
@@ -184,18 +225,18 @@ class LeaderboardService:
184
  "hub_license": data.get("Hub License"),
185
  "hub_hearts": data.get("Hub ❤️"),
186
  "params_billions": data.get("#Params (B)"),
187
- "co2_cost": data.get("CO₂ cost (kg)", 0)
188
  }
189
 
190
  # Clean model type by removing emojis if present
191
  original_type = data.get("Type", "")
192
  model_type = original_type.lower().strip()
193
-
194
  # Remove emojis and parentheses
195
  if "(" in model_type:
196
  model_type = model_type.split("(")[0].strip()
197
- model_type = ''.join(c for c in model_type if not c in '🔶🟢🟩💬🤝🌸 ')
198
-
199
  # Map old model types to new ones
200
  model_type_mapping = {
201
  "fine-tuned": "fined-tuned-on-domain-specific-dataset",
@@ -205,14 +246,18 @@ class LeaderboardService:
205
  "ft": "fined-tuned-on-domain-specific-dataset",
206
  "finetuning": "fined-tuned-on-domain-specific-dataset",
207
  "fine tuning": "fined-tuned-on-domain-specific-dataset",
208
- "fine-tuning": "fined-tuned-on-domain-specific-dataset"
209
  }
210
 
211
  mapped_type = model_type_mapping.get(model_type.lower().strip(), model_type)
212
-
213
  if mapped_type != model_type:
214
- logger.debug(LogFormatter.info(f"Model type mapped: {original_type} -> {mapped_type}"))
215
-
 
 
 
 
216
  transformed_data = {
217
  "id": unique_id,
218
  "model": {
@@ -222,17 +267,23 @@ class LeaderboardService:
222
  "type": mapped_type,
223
  "weight_type": data.get("Weight type"),
224
  "architecture": data.get("Architecture"),
225
- "average_score": data.get("Average ⬆️"),
226
- "has_chat_template": data.get("Chat Template", False)
227
  },
228
  "evaluations": evaluations,
229
  "features": features,
230
- "metadata": metadata
231
  }
232
-
233
- logger.debug(LogFormatter.success(f"Successfully transformed data for {model_name}"))
 
 
234
  return transformed_data
235
-
236
  except Exception as e:
237
- logger.error(LogFormatter.error(f"Failed to transform data for {data.get('fullname', 'Unknown')}", e))
 
 
 
 
238
  raise
 
9
 
10
  logger = logging.getLogger(__name__)
11
 
12
+
13
  class LeaderboardService:
14
  def __init__(self):
15
  pass
16
+
17
  async def fetch_raw_data(self) -> List[Dict[str, Any]]:
18
  """Fetch raw leaderboard data from HuggingFace dataset"""
19
  try:
20
  logger.info(LogFormatter.section("FETCHING LEADERBOARD DATA"))
21
+ logger.info(
22
+ LogFormatter.info(
23
+ f"Loading dataset from {HF_ORGANIZATION}/{HF_AGGREGATED}"
24
+ )
25
+ )
26
+
27
  dataset = datasets.load_dataset(
28
  f"{HF_ORGANIZATION}/{HF_AGGREGATED}",
29
+ cache_dir=cache_config.get_cache_path("datasets"),
30
  )["train"]
31
+
32
  df = dataset.to_pandas()
33
+ data = df.to_dict("records")
34
+
35
  stats = {
36
  "Total_Entries": len(data),
37
+ "Dataset_Size": f"{df.memory_usage(deep=True).sum() / 1024 / 1024:.1f}MB",
38
  }
39
  for line in LogFormatter.stats(stats, "Dataset Statistics"):
40
  logger.info(line)
41
+
42
  return data
43
+
44
  except Exception as e:
45
  logger.error(LogFormatter.error("Failed to fetch leaderboard data", e))
46
  raise HTTPException(status_code=500, detail=str(e))
 
49
  """Get formatted leaderboard data"""
50
  try:
51
  logger.info(LogFormatter.section("FORMATTING LEADERBOARD DATA"))
52
+
53
  raw_data = await self.fetch_raw_data()
54
  formatted_data = []
55
  type_counts = {}
56
  error_count = 0
57
+
58
  # Initialize progress tracking
59
  total_items = len(raw_data)
60
  logger.info(LogFormatter.info(f"Processing {total_items:,} entries..."))
61
+
62
  for i, item in enumerate(raw_data, 1):
63
  try:
64
  formatted_item = await self.transform_data(item)
65
  formatted_data.append(formatted_item)
66
+
67
  # Count model types
68
  model_type = formatted_item["model"]["type"]
69
  type_counts[model_type] = type_counts.get(model_type, 0) + 1
70
+
71
  except Exception as e:
72
  error_count += 1
73
+ logger.error(
74
+ LogFormatter.error(
75
+ f"Failed to format entry {i}/{total_items}", e
76
+ )
77
+ )
78
  continue
79
+
80
  # Log progress every 10%
81
  if i % max(1, total_items // 10) == 0:
82
  progress = (i / total_items) * 100
83
+ logger.info(
84
+ LogFormatter.info(
85
+ f"Progress: {LogFormatter.progress_bar(i, total_items)}"
86
+ )
87
+ )
88
+
89
  # Log final statistics
90
  stats = {
91
  "Total_Processed": total_items,
92
  "Successful": len(formatted_data),
93
+ "Failed": error_count,
94
  }
95
  logger.info(LogFormatter.section("PROCESSING SUMMARY"))
96
  for line in LogFormatter.stats(stats, "Processing Statistics"):
97
  logger.info(line)
98
+
99
  # Log model type distribution
100
  type_stats = {f"Type_{k}": v for k, v in type_counts.items()}
101
  logger.info(LogFormatter.subsection("MODEL TYPE DISTRIBUTION"))
102
  for line in LogFormatter.stats(type_stats):
103
  logger.info(line)
 
104
  return formatted_data
105
+
106
  except Exception as e:
107
  logger.error(LogFormatter.error("Failed to format leaderboard data", e))
108
  raise HTTPException(status_code=500, detail=str(e))
109
 
110
+ def _calculate_average_score(self, data: Dict[str, Any]) -> float:
111
+ scores = []
112
+ print(data)
113
+ for key in [
114
+ "BC5CDR-chemical",
115
+ "NCBI-disease",
116
+ "ChemProt",
117
+ "DDI2013",
118
+ "HoC",
119
+ "LitCovid",
120
+ "MedQA",
121
+ "PubMedQA",
122
+ "PubMed",
123
+ "MS^2",
124
+ "Cochrane PLS",
125
+ "PLOS",
126
+ ]:
127
+ normalized = data.get(key, {})
128
+ if not isinstance(normalized, dict):
129
+ continue
130
+
131
+ for metric, value in normalized.items():
132
+ if metric in ["bart", "dcr", "fkg"]:
133
+ continue
134
+ if isinstance(value, (int, float)):
135
+ scores.append(value)
136
+ return sum(scores) / len(scores) if scores else 0.0
137
+
138
  async def transform_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
139
  """Transform raw data into the format expected by the frontend"""
140
  try:
141
  # Extract model name for logging
142
  model_name = data.get("fullname", "Unknown")
143
+ logger.debug(
144
+ LogFormatter.info(f"Transforming data for model: {model_name}")
145
+ )
146
+
147
  # Create unique ID combining model name, precision, sha and chat template status
148
  unique_id = f"{data.get('fullname', 'Unknown')}_{data.get('Precision', 'Unknown')}_{data.get('Model sha', 'Unknown')}_{str(data.get('Chat Template', False))}"
 
149
  evaluations = {
150
  "bc5cdr_chemical": {
151
  "name": "BC5CDR-chemical",
152
+ "value": data.get("BC5CDR-chemical Raw", {}),
153
+ "normalized_score": data.get("BC5CDR-chemical", {}),
154
  },
155
  "ncbi_disease": {
156
+ "name": "NCBI-disease",
157
+ "value": data.get("NCBI-disease Raw", {}),
158
+ "normalized_score": data.get("NCBI-disease", {}),
159
  },
160
  "chemprot": {
161
  "name": "ChemProt",
162
+ "value": data.get("ChemProt Raw", {}),
163
+ "normalized_score": data.get("ChemProt", {}),
164
  },
165
  "ddi2013": {
166
  "name": "DDI2013",
167
+ "value": data.get("DDI2013 Raw", {}),
168
+ "normalized_score": data.get("DDI2013", {}),
169
  },
170
  "hoc": {
171
  "name": "HoC",
172
+ "value": data.get("HoC Raw", {}),
173
+ "normalized_score": data.get("HoC", {}),
174
  },
175
  "litcovid": {
176
  "name": "LitCovid",
177
+ "value": data.get("LitCovid Raw", {}),
178
+ "normalized_score": data.get("LitCovid", {}),
179
  },
180
  "medqa": {
181
  "name": "MedQA (5-Option)",
182
+ "value": data.get("MedQA Raw", {}),
183
+ "normalized_score": data.get("MedQA", {}),
184
  },
185
  "pubmedqa": {
186
  "name": "PubMedQA",
187
+ "value": data.get("PubMedQA Raw", {}),
188
+ "normalized_score": data.get("PubMedQA", {}),
189
  },
190
  "pubmed": {
191
  "name": "PubMed",
192
+ "value": data.get("PubMed Raw", {}),
193
+ "normalized_score": data.get("PubMed", {}),
194
  },
195
  "ms2": {
196
  "name": "MS^2",
197
+ "value": data.get("MS^2 Raw", {}),
198
+ "normalized_score": data.get("MS^2", {}),
199
  },
200
  "cochrane_pls": {
201
  "name": "Cochrane PLS",
202
+ "value": data.get("Cochrane PLS Raw", {}),
203
+ "normalized_score": data.get("Cochrane PLS", {}),
204
  },
205
  "plos": {
206
  "name": "PLOS",
207
+ "value": data.get("PLOS Raw", {}),
208
+ "normalized_score": data.get("PLOS", {}),
209
  },
210
  }
211
 
 
214
  "is_merged": data.get("Merged", False),
215
  "is_moe": data.get("MoE", False),
216
  "is_flagged": data.get("Flagged", False),
217
+ "is_highlighted_by_maintainer": data.get("Official Providers", False),
218
  }
219
 
220
  metadata = {
 
225
  "hub_license": data.get("Hub License"),
226
  "hub_hearts": data.get("Hub ❤️"),
227
  "params_billions": data.get("#Params (B)"),
228
+ "co2_cost": data.get("CO₂ cost (kg)", 0),
229
  }
230
 
231
  # Clean model type by removing emojis if present
232
  original_type = data.get("Type", "")
233
  model_type = original_type.lower().strip()
234
+
235
  # Remove emojis and parentheses
236
  if "(" in model_type:
237
  model_type = model_type.split("(")[0].strip()
238
+ model_type = "".join(c for c in model_type if not c in "🔶🟢🟩💬🤝🌸 ")
239
+
240
  # Map old model types to new ones
241
  model_type_mapping = {
242
  "fine-tuned": "fined-tuned-on-domain-specific-dataset",
 
246
  "ft": "fined-tuned-on-domain-specific-dataset",
247
  "finetuning": "fined-tuned-on-domain-specific-dataset",
248
  "fine tuning": "fined-tuned-on-domain-specific-dataset",
249
+ "fine-tuning": "fined-tuned-on-domain-specific-dataset",
250
  }
251
 
252
  mapped_type = model_type_mapping.get(model_type.lower().strip(), model_type)
253
+
254
  if mapped_type != model_type:
255
+ logger.debug(
256
+ LogFormatter.info(
257
+ f"Model type mapped: {original_type} -> {mapped_type}"
258
+ )
259
+ )
260
+
261
  transformed_data = {
262
  "id": unique_id,
263
  "model": {
 
267
  "type": mapped_type,
268
  "weight_type": data.get("Weight type"),
269
  "architecture": data.get("Architecture"),
270
+ "average_score": self._calculate_average_score(data),
271
+ "has_chat_template": data.get("Chat Template", False),
272
  },
273
  "evaluations": evaluations,
274
  "features": features,
275
+ "metadata": metadata,
276
  }
277
+
278
+ logger.debug(
279
+ LogFormatter.success(f"Successfully transformed data for {model_name}")
280
+ )
281
  return transformed_data
282
+
283
  except Exception as e:
284
+ logger.error(
285
+ LogFormatter.error(
286
+ f"Failed to transform data for {data.get('fullname', 'Unknown')}", e
287
+ )
288
+ )
289
  raise
frontend/package.json CHANGED
@@ -24,7 +24,8 @@
24
  "react-router-dom": "^6.28.0",
25
  "react-scripts": "5.0.1",
26
  "serve-static": "^1.15.0",
27
- "web-vitals": "^2.1.4"
 
28
  },
29
  "scripts": {
30
  "start": "react-scripts start",
@@ -51,5 +52,5 @@
51
  "last 1 safari version"
52
  ]
53
  },
54
- "proxy": "http://backend:8000"
55
  }
 
24
  "react-router-dom": "^6.28.0",
25
  "react-scripts": "5.0.1",
26
  "serve-static": "^1.15.0",
27
+ "web-vitals": "^2.1.4",
28
+ "zustand": "^5.0.3"
29
  },
30
  "scripts": {
31
  "start": "react-scripts start",
 
52
  "last 1 safari version"
53
  ]
54
  },
55
+ "proxy": "http://localhost:8000"
56
  }
frontend/src/pages/LeaderboardPage/components/Leaderboard/components/Table/hooks/useDataProcessing.js CHANGED
@@ -13,6 +13,7 @@ import {
13
  useFilteredData,
14
  useColumnVisibility,
15
  } from "../../../hooks/useDataUtils";
 
16
 
17
  export const useDataProcessing = (
18
  data,
@@ -116,14 +117,14 @@ export const useDataProcessing = (
116
  defaultColumn: {
117
  sortingFn: (rowA, rowB, columnId) => {
118
  const isDesc = sorting?.[0]?.desc;
119
-
120
  if (rowA.original.isPinned && rowB.original.isPinned) {
121
  return (
122
  pinnedModels.indexOf(rowA.original.id) -
123
  pinnedModels.indexOf(rowB.original.id)
124
  );
125
  }
126
-
127
  if (isDesc) {
128
  if (rowA.original.isPinned) return -1;
129
  if (rowB.original.isPinned) return 1;
@@ -131,17 +132,31 @@ export const useDataProcessing = (
131
  if (rowA.original.isPinned) return -1;
132
  if (rowB.original.isPinned) return 1;
133
  }
134
-
135
- const aValue = rowA.getValue(columnId);
136
- const bValue = rowB.getValue(columnId);
137
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  if (typeof aValue === "number" && typeof bValue === "number") {
139
  return aValue - bValue;
140
  }
141
-
142
- return String(aValue).localeCompare(String(bValue));
143
  },
144
- },
145
  }),
146
  [filteredData, columns, sorting, columnVisibility, pinnedModels, setSorting]
147
  );
 
13
  useFilteredData,
14
  useColumnVisibility,
15
  } from "../../../hooks/useDataUtils";
16
+ import { useSubscoreStore } from "../../../hooks/useSubscoreStore";
17
 
18
  export const useDataProcessing = (
19
  data,
 
117
  defaultColumn: {
118
  sortingFn: (rowA, rowB, columnId) => {
119
  const isDesc = sorting?.[0]?.desc;
120
+
121
  if (rowA.original.isPinned && rowB.original.isPinned) {
122
  return (
123
  pinnedModels.indexOf(rowA.original.id) -
124
  pinnedModels.indexOf(rowB.original.id)
125
  );
126
  }
127
+
128
  if (isDesc) {
129
  if (rowA.original.isPinned) return -1;
130
  if (rowB.original.isPinned) return 1;
 
132
  if (rowA.original.isPinned) return -1;
133
  if (rowB.original.isPinned) return 1;
134
  }
135
+
136
+ let aValue = rowA.getValue(columnId);
137
+ let bValue = rowB.getValue(columnId);
138
+
139
+ if (aValue && typeof aValue === "object") {
140
+ const selected =
141
+ useSubscoreStore.getState().selections[columnId] ||
142
+ Object.keys(aValue)[0];
143
+ aValue = aValue[selected];
144
+ }
145
+
146
+ if (bValue && typeof bValue === "object") {
147
+ const selected =
148
+ useSubscoreStore.getState().selections[columnId] ||
149
+ Object.keys(bValue)[0];
150
+ bValue = bValue[selected];
151
+ }
152
+
153
  if (typeof aValue === "number" && typeof bValue === "number") {
154
  return aValue - bValue;
155
  }
156
+
157
+ return String(aValue || "").localeCompare(String(bValue || ""));
158
  },
159
+ },
160
  }),
161
  [filteredData, columns, sorting, columnVisibility, pinnedModels, setSorting]
162
  );
frontend/src/pages/LeaderboardPage/components/Leaderboard/constants/defaults.js CHANGED
@@ -78,7 +78,7 @@ const COLUMN_SIZES = {
78
  TYPE_ICON: 65,
79
  MODEL: 400,
80
  AVERAGE_SCORE: 150,
81
- BENCHMARK: 110,
82
  CO2_COST: 140,
83
  HUB_HEARTS: 140,
84
  ARCHITECTURE: 210,
 
78
  TYPE_ICON: 65,
79
  MODEL: 400,
80
  AVERAGE_SCORE: 150,
81
+ BENCHMARK: 300,
82
  CO2_COST: 140,
83
  HUB_HEARTS: 140,
84
  ARCHITECTURE: 210,
frontend/src/pages/LeaderboardPage/components/Leaderboard/hooks/useDataUtils.js CHANGED
@@ -1,274 +1,103 @@
1
  import { useMemo } from "react";
2
  import {
3
- looksLikeRegex,
4
- parseSearchQuery,
5
- getValueByPath,
6
  } from "../utils/searchUtils";
7
 
8
  // Calculate min/max averages
9
  export const useAverageRange = (data) => {
10
- return useMemo(() => {
11
- const averages = data.map((item) => item.model.average_score);
12
- return {
13
- minAverage: Math.min(...averages),
14
- maxAverage: Math.max(...averages),
15
- };
16
- }, [data]);
17
  };
18
 
19
  // Generate colors for scores
20
  export const useColorGenerator = (minAverage, maxAverage) => {
21
- return useMemo(() => {
22
- const colorCache = new Map();
23
- return (value) => {
24
- const cached = colorCache.get(value);
25
- if (cached) return cached;
26
-
27
- const normalizedValue = (value - minAverage) / (maxAverage - minAverage);
28
- const red = Math.round(255 * (1 - normalizedValue) * 1);
29
- const green = Math.round(255 * normalizedValue) * 1;
30
- // const color = `rgba(${red}, ${green}, 0, 1)`;
31
- const color = `rgba(${red}, 0, ${green}, 1)`;
32
- colorCache.set(value, color);
33
- return color;
34
- };
35
- }, [minAverage, maxAverage]);
 
36
  };
37
 
38
  // Process data with boolean standardization
39
  export const useProcessedData = (data, averageMode, visibleColumns) => {
40
- return useMemo(() => {
41
- let processed = data.map((item) => {
42
- const evaluationScores = Object.entries(item.evaluations)
43
- .filter(([key]) => {
44
- if (averageMode === "all") return true;
45
- return visibleColumns.includes(`evaluations.${key}.normalized_score`);
46
- })
47
- .map(([, value]) => value.normalized_score);
48
-
49
- const average =
50
- evaluationScores.length > 0
51
- ? evaluationScores.reduce((a, b) => a + b, 0) /
52
- evaluationScores.length
53
- : averageMode === "visible"
54
- ? null
55
- : 0;
56
-
57
- // Boolean standardization
58
- const standardizedFeatures = {
59
- ...item.features,
60
- is_moe: Boolean(item.features.is_moe),
61
- is_flagged: Boolean(item.features.is_flagged),
62
- is_highlighted_by_maintainer: Boolean(
63
- item.features.is_highlighted_by_maintainer
64
- ),
65
- is_merged: Boolean(item.features.is_merged),
66
- is_not_available_on_hub: Boolean(item.features.is_not_available_on_hub),
67
- };
68
-
69
- return {
70
- ...item,
71
- features: standardizedFeatures,
72
- model: {
73
- ...item.model,
74
- has_chat_template: Boolean(item.model.has_chat_template),
75
- average_score: average,
76
- },
77
- };
78
- });
79
-
80
- processed.sort((a, b) => {
81
- if (a.model.average_score === null && b.model.average_score === null)
82
- return 0;
83
- if (a.model.average_score === null) return 1;
84
- if (b.model.average_score === null) return -1;
85
- return b.model.average_score - a.model.average_score;
86
- });
87
-
88
- return processed.map((item, index) => ({
89
- ...item,
90
- static_rank: index + 1,
91
- }));
92
- }, [data, averageMode, visibleColumns]);
 
 
 
93
  };
94
 
95
  // Common filtering logic
96
  export const useFilteredData = (
97
- processedData,
98
- selectedPrecisions,
99
- selectedTypes,
100
- paramsRange,
101
- searchValue,
102
- selectedBooleanFilters,
103
- rankingMode,
104
- pinnedModels = [],
105
- isOfficialProviderActive = false
106
- ) => {
107
- return useMemo(() => {
108
- const pinnedData = processedData.filter((row) => {
109
- return pinnedModels.includes(row.id);
110
- });
111
- const unpinnedData = processedData.filter((row) => {
112
- return !pinnedModels.includes(row.id);
113
- });
114
-
115
- let filteredUnpinned = unpinnedData;
116
-
117
- // Filter by official providers
118
- if (isOfficialProviderActive) {
119
- filteredUnpinned = filteredUnpinned.filter(
120
- (row) =>
121
- row.features?.is_highlighted_by_maintainer ||
122
- row.metadata?.is_highlighted_by_maintainer
123
- );
124
- }
125
-
126
- // Filter by precision
127
- if (selectedPrecisions.length > 0) {
128
- filteredUnpinned = filteredUnpinned.filter((row) =>
129
- selectedPrecisions.includes(row.model.precision)
130
- );
131
- }
132
-
133
- // Filter by type
134
- if (selectedTypes.length > 0) {
135
- filteredUnpinned = filteredUnpinned.filter((row) => {
136
- const modelType = row.model.type?.toLowerCase().trim();
137
- return selectedTypes.some((type) => modelType?.includes(type));
138
- });
139
- }
140
-
141
- // Filter by parameters
142
- filteredUnpinned = filteredUnpinned.filter((row) => {
143
- // Skip parameter filtering if no filter is active
144
- if (paramsRange[0] === -1 && paramsRange[1] === 140) return true;
145
-
146
- const params =
147
- row.metadata?.params_billions || row.features?.params_billions;
148
- if (params === undefined || params === null) return false;
149
- return params >= paramsRange[0] && params < paramsRange[1];
150
- });
151
-
152
- // Filter by search
153
- if (searchValue) {
154
- const searchQueries = searchValue
155
- .split(";")
156
- .map((q) => q.trim())
157
- .filter((q) => q);
158
- if (searchQueries.length > 0) {
159
- filteredUnpinned = filteredUnpinned.filter((row) => {
160
- return searchQueries.some((query) => {
161
- const { specialSearches, textSearch } = parseSearchQuery(query);
162
-
163
- const specialSearchMatch = specialSearches.every(
164
- ({ field, value }) => {
165
- const fieldValue = getValueByPath(row, field)
166
- ?.toString()
167
- .toLowerCase();
168
- return fieldValue?.includes(value.toLowerCase());
169
- }
170
- );
171
-
172
- if (!specialSearchMatch) return false;
173
- if (!textSearch) return true;
174
-
175
- const modelName = row.model.name.toLowerCase();
176
- const searchLower = textSearch.toLowerCase();
177
-
178
- if (looksLikeRegex(textSearch)) {
179
- try {
180
- const regex = new RegExp(textSearch, "i");
181
- return regex.test(modelName);
182
- } catch (e) {
183
- return modelName.includes(searchLower);
184
- }
185
- } else {
186
- return modelName.includes(searchLower);
187
- }
188
- });
189
- });
190
- }
191
- }
192
-
193
- // Filter by booleans
194
- if (selectedBooleanFilters.length > 0) {
195
- filteredUnpinned = filteredUnpinned.filter((row) => {
196
- return selectedBooleanFilters.every((filter) => {
197
- const filterValue =
198
- typeof filter === "object" ? filter.value : filter;
199
-
200
- // Maintainer's Highlight keeps positive logic
201
- if (filterValue === "is_highlighted_by_maintainer") {
202
- return row.features[filterValue];
203
- }
204
-
205
- // For all other filters, invert the logic
206
- if (filterValue === "is_not_available_on_hub") {
207
- return row.features[filterValue];
208
- }
209
-
210
- return !row.features[filterValue];
211
- });
212
- });
213
- }
214
-
215
- // Create ordered array of pinned models respecting pinnedModels order
216
- const orderedPinnedData = pinnedModels
217
- .map((pinnedModelId) =>
218
- pinnedData.find((item) => item.id === pinnedModelId)
219
- )
220
- .filter(Boolean);
221
-
222
- // Combine all filtered data
223
- const allFilteredData = [...filteredUnpinned, ...orderedPinnedData];
224
-
225
- // Sort all data by average_score for dynamic_rank
226
- const sortedByScore = [...allFilteredData].sort((a, b) => {
227
- // Si les scores moyens sont différents, trier par score
228
- if (a.model.average_score !== b.model.average_score) {
229
- if (a.model.average_score === null && b.model.average_score === null)
230
- return 0;
231
- if (a.model.average_score === null) return 1;
232
- if (b.model.average_score === null) return -1;
233
- return b.model.average_score - a.model.average_score;
234
- }
235
-
236
- // Si les scores sont égaux, comparer le nom du modèle et la date de soumission
237
- if (a.model.name === b.model.name) {
238
- // Si même nom, trier par date de soumission (la plus récente d'abord)
239
- const dateA = new Date(a.metadata?.submission_date || 0);
240
- const dateB = new Date(b.metadata?.submission_date || 0);
241
- return dateB - dateA;
242
- }
243
-
244
- // Si noms différents, trier par nom
245
- return a.model.name.localeCompare(b.model.name);
246
- });
247
-
248
- // Create Map to store dynamic_ranks
249
- const dynamicRankMap = new Map();
250
- sortedByScore.forEach((item, index) => {
251
- dynamicRankMap.set(item.id, index + 1);
252
- });
253
-
254
- // Add ranks to final data
255
- const finalData = [...orderedPinnedData, ...filteredUnpinned].map(
256
- (item) => {
257
- return {
258
- ...item,
259
- dynamic_rank: dynamicRankMap.get(item.id),
260
- rank: item.isPinned
261
- ? pinnedModels.indexOf(item.id) + 1
262
- : rankingMode === "static"
263
- ? item.static_rank
264
- : dynamicRankMap.get(item.id),
265
- isPinned: pinnedModels.includes(item.id),
266
- };
267
- }
268
- );
269
-
270
- return finalData;
271
- }, [
272
  processedData,
273
  selectedPrecisions,
274
  selectedTypes,
@@ -276,32 +105,213 @@ export const useFilteredData = (
276
  searchValue,
277
  selectedBooleanFilters,
278
  rankingMode,
279
- pinnedModels,
280
- isOfficialProviderActive,
281
- ]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  };
283
 
284
  // Column visibility management
285
  export const useColumnVisibility = (visibleColumns = []) => {
286
- // Create secure visibility object
287
- const columnVisibility = useMemo(() => {
288
- // Check visible columns
289
- const safeVisibleColumns = Array.isArray(visibleColumns)
290
- ? visibleColumns
291
- : [];
292
-
293
- const visibility = {};
294
- try {
295
- safeVisibleColumns.forEach((columnKey) => {
296
- if (typeof columnKey === "string") {
297
- visibility[columnKey] = true;
 
 
 
 
298
  }
299
- });
300
- } catch (error) {
301
- console.warn("Error in useColumnVisibility:", error);
302
- }
303
- return visibility;
304
- }, [visibleColumns]);
305
-
306
- return columnVisibility;
307
  };
 
1
  import { useMemo } from "react";
2
  import {
3
+ looksLikeRegex,
4
+ parseSearchQuery,
5
+ getValueByPath,
6
  } from "../utils/searchUtils";
7
 
8
  // Calculate min/max averages
9
  export const useAverageRange = (data) => {
10
+ return useMemo(() => {
11
+ const averages = data.map((item) => item.model.average_score);
12
+ return {
13
+ minAverage: Math.min(...averages),
14
+ maxAverage: Math.max(...averages),
15
+ };
16
+ }, [data]);
17
  };
18
 
19
  // Generate colors for scores
20
  export const useColorGenerator = (minAverage, maxAverage) => {
21
+ return useMemo(() => {
22
+ const colorCache = new Map();
23
+ return (value) => {
24
+ const cached = colorCache.get(value);
25
+ if (cached) return cached;
26
+
27
+ const normalizedValue =
28
+ (value - minAverage) / (maxAverage - minAverage);
29
+ const red = Math.round(255 * (1 - normalizedValue) * 1);
30
+ const green = Math.round(255 * normalizedValue) * 1;
31
+ // const color = `rgba(${red}, ${green}, 0, 1)`;
32
+ const color = `rgba(${red}, 0, ${green}, 1)`;
33
+ colorCache.set(value, color);
34
+ return color;
35
+ };
36
+ }, [minAverage, maxAverage]);
37
  };
38
 
39
  // Process data with boolean standardization
40
  export const useProcessedData = (data, averageMode, visibleColumns) => {
41
+ return useMemo(() => {
42
+ let processed = data.map((item) => {
43
+ const evaluationScores = Object.entries(item.evaluations)
44
+ .filter(([key]) => {
45
+ if (averageMode === "all") return true;
46
+ return visibleColumns.includes(
47
+ `evaluations.${key}.normalized_score`
48
+ );
49
+ })
50
+ .flatMap(([, value]) =>
51
+ Object.entries(value.normalized_score || {})
52
+ .filter(([metric]) => metric !== "bert")
53
+ .map(([, val]) => val)
54
+ );
55
+
56
+ // Boolean standardization
57
+ const standardizedFeatures = {
58
+ ...item.features,
59
+ is_moe: Boolean(item.features.is_moe),
60
+ is_flagged: Boolean(item.features.is_flagged),
61
+ is_highlighted_by_maintainer: Boolean(
62
+ item.features.is_highlighted_by_maintainer
63
+ ),
64
+ is_merged: Boolean(item.features.is_merged),
65
+ is_not_available_on_hub: Boolean(
66
+ item.features.is_not_available_on_hub
67
+ ),
68
+ };
69
+
70
+ return {
71
+ ...item,
72
+ features: standardizedFeatures,
73
+ model: {
74
+ ...item.model,
75
+ has_chat_template: Boolean(item.model.has_chat_template),
76
+ average_score: item.model.average_score,
77
+ },
78
+ };
79
+ });
80
+
81
+ processed.sort((a, b) => {
82
+ if (
83
+ a.model.average_score === null &&
84
+ b.model.average_score === null
85
+ )
86
+ return 0;
87
+ if (a.model.average_score === null) return 1;
88
+ if (b.model.average_score === null) return -1;
89
+ return b.model.average_score - a.model.average_score;
90
+ });
91
+
92
+ return processed.map((item, index) => ({
93
+ ...item,
94
+ static_rank: index + 1,
95
+ }));
96
+ }, [data, averageMode, visibleColumns]);
97
  };
98
 
99
  // Common filtering logic
100
  export const useFilteredData = (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  processedData,
102
  selectedPrecisions,
103
  selectedTypes,
 
105
  searchValue,
106
  selectedBooleanFilters,
107
  rankingMode,
108
+ pinnedModels = [],
109
+ isOfficialProviderActive = false
110
+ ) => {
111
+ return useMemo(() => {
112
+ const pinnedData = processedData.filter((row) => {
113
+ return pinnedModels.includes(row.id);
114
+ });
115
+ const unpinnedData = processedData.filter((row) => {
116
+ return !pinnedModels.includes(row.id);
117
+ });
118
+
119
+ let filteredUnpinned = unpinnedData;
120
+
121
+ // Filter by official providers
122
+ if (isOfficialProviderActive) {
123
+ filteredUnpinned = filteredUnpinned.filter(
124
+ (row) =>
125
+ row.features?.is_highlighted_by_maintainer ||
126
+ row.metadata?.is_highlighted_by_maintainer
127
+ );
128
+ }
129
+
130
+ // Filter by precision
131
+ if (selectedPrecisions.length > 0) {
132
+ filteredUnpinned = filteredUnpinned.filter((row) =>
133
+ selectedPrecisions.includes(row.model.precision)
134
+ );
135
+ }
136
+
137
+ // Filter by type
138
+ if (selectedTypes.length > 0) {
139
+ filteredUnpinned = filteredUnpinned.filter((row) => {
140
+ const modelType = row.model.type?.toLowerCase().trim();
141
+ return selectedTypes.some((type) => modelType?.includes(type));
142
+ });
143
+ }
144
+
145
+ // Filter by parameters
146
+ filteredUnpinned = filteredUnpinned.filter((row) => {
147
+ // Skip parameter filtering if no filter is active
148
+ if (paramsRange[0] === -1 && paramsRange[1] === 140) return true;
149
+
150
+ const params =
151
+ row.metadata?.params_billions || row.features?.params_billions;
152
+ if (params === undefined || params === null) return false;
153
+ return params >= paramsRange[0] && params < paramsRange[1];
154
+ });
155
+
156
+ // Filter by search
157
+ if (searchValue) {
158
+ const searchQueries = searchValue
159
+ .split(";")
160
+ .map((q) => q.trim())
161
+ .filter((q) => q);
162
+ if (searchQueries.length > 0) {
163
+ filteredUnpinned = filteredUnpinned.filter((row) => {
164
+ return searchQueries.some((query) => {
165
+ const { specialSearches, textSearch } =
166
+ parseSearchQuery(query);
167
+
168
+ const specialSearchMatch = specialSearches.every(
169
+ ({ field, value }) => {
170
+ const fieldValue = getValueByPath(row, field)
171
+ ?.toString()
172
+ .toLowerCase();
173
+ return fieldValue?.includes(
174
+ value.toLowerCase()
175
+ );
176
+ }
177
+ );
178
+
179
+ if (!specialSearchMatch) return false;
180
+ if (!textSearch) return true;
181
+
182
+ const modelName = row.model.name.toLowerCase();
183
+ const searchLower = textSearch.toLowerCase();
184
+
185
+ if (looksLikeRegex(textSearch)) {
186
+ try {
187
+ const regex = new RegExp(textSearch, "i");
188
+ return regex.test(modelName);
189
+ } catch (e) {
190
+ return modelName.includes(searchLower);
191
+ }
192
+ } else {
193
+ return modelName.includes(searchLower);
194
+ }
195
+ });
196
+ });
197
+ }
198
+ }
199
+
200
+ // Filter by booleans
201
+ if (selectedBooleanFilters.length > 0) {
202
+ filteredUnpinned = filteredUnpinned.filter((row) => {
203
+ return selectedBooleanFilters.every((filter) => {
204
+ const filterValue =
205
+ typeof filter === "object" ? filter.value : filter;
206
+
207
+ // Maintainer's Highlight keeps positive logic
208
+ if (filterValue === "is_highlighted_by_maintainer") {
209
+ return row.features[filterValue];
210
+ }
211
+
212
+ // For all other filters, invert the logic
213
+ if (filterValue === "is_not_available_on_hub") {
214
+ return row.features[filterValue];
215
+ }
216
+
217
+ return !row.features[filterValue];
218
+ });
219
+ });
220
+ }
221
+
222
+ // Create ordered array of pinned models respecting pinnedModels order
223
+ const orderedPinnedData = pinnedModels
224
+ .map((pinnedModelId) =>
225
+ pinnedData.find((item) => item.id === pinnedModelId)
226
+ )
227
+ .filter(Boolean);
228
+
229
+ // Combine all filtered data
230
+ const allFilteredData = [...filteredUnpinned, ...orderedPinnedData];
231
+
232
+ // Sort all data by average_score for dynamic_rank
233
+ const sortedByScore = [...allFilteredData].sort((a, b) => {
234
+ // Si les scores moyens sont différents, trier par score
235
+ if (a.model.average_score !== b.model.average_score) {
236
+ if (
237
+ a.model.average_score === null &&
238
+ b.model.average_score === null
239
+ )
240
+ return 0;
241
+ if (a.model.average_score === null) return 1;
242
+ if (b.model.average_score === null) return -1;
243
+ return b.model.average_score - a.model.average_score;
244
+ }
245
+
246
+ // Si les scores sont égaux, comparer le nom du modèle et la date de soumission
247
+ if (a.model.name === b.model.name) {
248
+ // Si même nom, trier par date de soumission (la plus récente d'abord)
249
+ const dateA = new Date(a.metadata?.submission_date || 0);
250
+ const dateB = new Date(b.metadata?.submission_date || 0);
251
+ return dateB - dateA;
252
+ }
253
+
254
+ // Si noms différents, trier par nom
255
+ return a.model.name.localeCompare(b.model.name);
256
+ });
257
+
258
+ // Create Map to store dynamic_ranks
259
+ const dynamicRankMap = new Map();
260
+ sortedByScore.forEach((item, index) => {
261
+ dynamicRankMap.set(item.id, index + 1);
262
+ });
263
+
264
+ // Add ranks to final data
265
+ const finalData = [...orderedPinnedData, ...filteredUnpinned].map(
266
+ (item) => {
267
+ return {
268
+ ...item,
269
+ dynamic_rank: dynamicRankMap.get(item.id),
270
+ rank: item.isPinned
271
+ ? pinnedModels.indexOf(item.id) + 1
272
+ : rankingMode === "static"
273
+ ? item.static_rank
274
+ : dynamicRankMap.get(item.id),
275
+ isPinned: pinnedModels.includes(item.id),
276
+ };
277
+ }
278
+ );
279
+
280
+ return finalData;
281
+ }, [
282
+ processedData,
283
+ selectedPrecisions,
284
+ selectedTypes,
285
+ paramsRange,
286
+ searchValue,
287
+ selectedBooleanFilters,
288
+ rankingMode,
289
+ pinnedModels,
290
+ isOfficialProviderActive,
291
+ ]);
292
  };
293
 
294
  // Column visibility management
295
  export const useColumnVisibility = (visibleColumns = []) => {
296
+ // Create secure visibility object
297
+ const columnVisibility = useMemo(() => {
298
+ // Check visible columns
299
+ const safeVisibleColumns = Array.isArray(visibleColumns)
300
+ ? visibleColumns
301
+ : [];
302
+
303
+ const visibility = {};
304
+ try {
305
+ safeVisibleColumns.forEach((columnKey) => {
306
+ if (typeof columnKey === "string") {
307
+ visibility[columnKey] = true;
308
+ }
309
+ });
310
+ } catch (error) {
311
+ console.warn("Error in useColumnVisibility:", error);
312
  }
313
+ return visibility;
314
+ }, [visibleColumns]);
315
+
316
+ return columnVisibility;
 
 
 
 
317
  };
frontend/src/pages/LeaderboardPage/components/Leaderboard/hooks/useSubscoreStore.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { create } from "zustand";
2
+
3
+ // TODO: maybe subscore isn't the best name
4
+ const useSubscoreStore = create((set) => ({
5
+ selections: {},
6
+ setSelection: (key, value) =>
7
+ set((state) => ({
8
+ selections: {
9
+ ...state.selections,
10
+ [key]: value,
11
+ },
12
+ })),
13
+ }));
14
+
15
+ export { useSubscoreStore };
frontend/src/pages/LeaderboardPage/components/Leaderboard/utils/columnUtils.js CHANGED
@@ -1,5 +1,15 @@
1
- import React from "react";
2
- import { Box, Typography, Link, Tooltip, IconButton } from "@mui/material";
 
 
 
 
 
 
 
 
 
 
3
  import { getModelTypeIcon } from "../constants/modelTypes";
4
  import TrendingUpIcon from "@mui/icons-material/TrendingUp";
5
  import TrendingDownIcon from "@mui/icons-material/TrendingDown";
@@ -11,445 +21,567 @@ import { looksLikeRegex, extractTextSearch } from "./searchUtils";
11
  import { commonStyles } from "../styles/common";
12
  import { typeColumnSort } from "../components/Table/hooks/useSorting";
13
  import {
14
- COLUMN_TOOLTIPS,
15
- getTooltipStyle,
16
- TABLE_TOOLTIPS,
17
  } from "../constants/tooltips";
18
  import OpenInNewIcon from "@mui/icons-material/OpenInNew";
19
  import { alpha } from "@mui/material/styles";
20
  import InfoIconWithTooltip from "../../../../../components/shared/InfoIconWithTooltip";
 
21
 
22
  const DatabaseIcon = () => (
23
- <svg
24
- className="mr-1.5 text-gray-400 group-hover:text-red-500"
25
- xmlns="http://www.w3.org/2000/svg"
26
- aria-hidden="true"
27
- focusable="false"
28
- role="img"
29
- width="1.4em"
30
- height="1.4em"
31
- preserveAspectRatio="xMidYMid meet"
32
- viewBox="0 0 25 25"
33
- >
34
- <ellipse
35
- cx="12.5"
36
- cy="5"
37
- fill="currentColor"
38
- fillOpacity="0.25"
39
- rx="7.5"
40
- ry="2"
41
- ></ellipse>
42
- <path
43
- d="M12.5 15C16.6421 15 20 14.1046 20 13V20C20 21.1046 16.6421 22 12.5 22C8.35786 22 5 21.1046 5 20V13C5 14.1046 8.35786 15 12.5 15Z"
44
- fill="currentColor"
45
- opacity="0.5"
46
- ></path>
47
- <path
48
- d="M12.5 7C16.6421 7 20 6.10457 20 5V11.5C20 12.6046 16.6421 13.5 12.5 13.5C8.35786 13.5 5 12.6046 5 11.5V5C5 6.10457 8.35786 7 12.5 7Z"
49
- fill="currentColor"
50
- opacity="0.5"
51
- ></path>
52
- <path
53
- d="M5.23628 12C5.08204 12.1598 5 12.8273 5 13C5 14.1046 8.35786 15 12.5 15C16.6421 15 20 14.1046 20 13C20 12.8273 19.918 12.1598 19.7637 12C18.9311 12.8626 15.9947 13.5 12.5 13.5C9.0053 13.5 6.06886 12.8626 5.23628 12Z"
54
- fill="currentColor"
55
- ></path>
56
- </svg>
57
  );
58
 
59
  const HighlightedText = ({ text, searchValue }) => {
60
- if (!searchValue) return text;
61
 
62
- const searches = searchValue
63
- .split(";")
64
- .map((s) => s.trim())
65
- .filter(Boolean);
66
- let result = text;
67
- let fragments = [{ text: result, isMatch: false }];
68
 
69
- searches.forEach((search, searchIndex) => {
70
- if (!search) return;
71
 
72
- try {
73
- let regex;
74
- if (looksLikeRegex(search)) {
75
- regex = new RegExp(search, "gi");
76
- } else {
77
- regex = new RegExp(search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
78
- }
 
 
 
79
 
80
- const newFragments = [];
81
- fragments.forEach((fragment) => {
82
- if (fragment.isMatch) {
83
- newFragments.push(fragment);
84
- return;
85
- }
86
 
87
- const parts = fragment.text.split(regex);
88
- const matches = fragment.text.match(regex);
89
 
90
- if (!matches) {
91
- newFragments.push(fragment);
92
- return;
93
- }
94
 
95
- parts.forEach((part, i) => {
96
- if (part) newFragments.push({ text: part, isMatch: false });
97
- if (i < parts.length - 1) {
98
- newFragments.push({
99
- text: matches[i],
100
- isMatch: true,
101
- colorIndex: searchIndex % HIGHLIGHT_COLORS.length,
 
 
 
102
  });
103
- }
104
- });
105
- });
106
 
107
- fragments = newFragments;
108
- } catch (e) {
109
- console.warn("Invalid regex:", search);
110
- }
111
- });
112
 
113
- return (
114
- <>
115
- {fragments.map((fragment, i) =>
116
- fragment.isMatch ? (
117
- <Box
118
- key={i}
119
- component="span"
120
- sx={{
121
- backgroundColor: HIGHLIGHT_COLORS[fragment.colorIndex],
122
- color: (theme) =>
123
- theme.palette.getContrastText(
124
- HIGHLIGHT_COLORS[fragment.colorIndex]
125
- ),
126
- fontWeight: 500,
127
- px: 0.5,
128
- py: "2px",
129
- borderRadius: "3px",
130
- mx: "1px",
131
- overflow: "visible",
132
- display: "inline-block",
133
- }}
134
- >
135
- {fragment.text}
136
- </Box>
137
- ) : (
138
- <React.Fragment key={i}>{fragment.text}</React.Fragment>
139
- )
140
- )}
141
- </>
142
- );
 
143
  };
144
 
145
  const MEDAL_STYLES = {
146
- 1: {
147
- color: "#B58A1B",
148
- background: "linear-gradient(135deg, #FFF7E0 0%, #FFD700 100%)",
149
- borderColor: "rgba(212, 160, 23, 0.35)",
150
- shadowColor: "rgba(212, 160, 23, 0.8)",
151
- },
152
- 2: {
153
- color: "#667380",
154
- background: "linear-gradient(135deg, #FFFFFF 0%, #D8E3ED 100%)",
155
- borderColor: "rgba(124, 139, 153, 0.35)",
156
- shadowColor: "rgba(124, 139, 153, 0.8)",
157
- },
158
- 3: {
159
- color: "#B85C2F",
160
- background: "linear-gradient(135deg, #FDF0E9 0%, #FFBC8C 100%)",
161
- borderColor: "rgba(204, 108, 61, 0.35)",
162
- shadowColor: "rgba(204, 108, 61, 0.8)",
163
- },
164
  };
165
 
166
  const getMedalStyle = (rank) => {
167
- if (rank <= 3) {
168
- const medalStyle = MEDAL_STYLES[rank];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  return {
170
- color: medalStyle.color,
171
- fontWeight: 900,
172
- fontStretch: "150%",
173
- fontFamily: '"Inter", -apple-system, sans-serif',
174
- width: "24px",
175
- height: "24px",
176
- background: medalStyle.background,
177
- border: "1px solid",
178
- borderColor: medalStyle.borderColor,
179
- borderRadius: "50%",
180
- display: "flex",
181
- alignItems: "center",
182
- justifyContent: "center",
183
- fontSize: "0.95rem",
184
- lineHeight: 1,
185
- padding: 0,
186
- boxShadow: `1px 1px 0 ${medalStyle.shadowColor}`,
187
- position: "relative",
188
  };
189
- }
190
- return {
191
- color: "inherit",
192
- fontWeight: rank <= 10 ? 600 : 400,
193
- };
194
  };
195
 
196
  const getRankStyle = (rank) => getMedalStyle(rank);
197
 
198
  const RankIndicator = ({ rank, previousRank, mode }) => {
199
- const rankChange = previousRank ? previousRank - rank : 0;
200
 
201
- const RankChangeIndicator = ({ change }) => {
202
- if (!change || mode === "dynamic") return null;
203
 
204
- const getChangeColor = (change) => {
205
- if (change > 0) return "success.main";
206
- if (change < 0) return "error.main";
207
- return "grey.500";
208
- };
209
 
210
- const getChangeIcon = (change) => {
211
- if (change > 0) return <TrendingUpIcon sx={{ fontSize: "1rem" }} />;
212
- if (change < 0) return <TrendingDownIcon sx={{ fontSize: "1rem" }} />;
213
- return <RemoveIcon sx={{ fontSize: "1rem" }} />;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  };
215
 
216
  return (
217
- <Tooltip
218
- title={`${Math.abs(change)} position${
219
- Math.abs(change) > 1 ? "s" : ""
220
- } ${change > 0 ? "up" : "down"}`}
221
- arrow
222
- placement="right"
223
- >
224
  <Box
225
- sx={{
226
- display: "flex",
227
- alignItems: "center",
228
- color: getChangeColor(change),
229
- ml: 0.5,
230
- fontSize: "0.75rem",
231
- }}
232
  >
233
- {getChangeIcon(change)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  </Box>
235
- </Tooltip>
236
  );
237
- };
238
-
239
- return (
240
- <Box
241
- sx={{
242
- display: "flex",
243
- alignItems: "center",
244
- justifyContent: "center",
245
- width: "100%",
246
- }}
247
- >
248
- <Typography
249
- sx={{
250
- ...getRankStyle(rank),
251
- display: "flex",
252
- alignItems: "center",
253
- lineHeight: 1,
254
- position: "relative",
255
- }}
256
- >
257
- {rank <= 3 ? (
258
- <>
259
- <Box component="span" sx={{ position: "relative", zIndex: 1 }}>
260
- {rank}
261
- </Box>
262
- <RankChangeIndicator change={rankChange} />
263
- </>
264
- ) : (
265
- <>
266
- <Box component="span" sx={{ position: "relative", zIndex: 1 }}>
267
- {rank}
268
- </Box>
269
- <RankChangeIndicator change={rankChange} />
270
- </>
271
- )}
272
- </Typography>
273
- </Box>
274
- );
275
  };
276
 
277
  const getDetailsUrl = (modelName) => {
278
- const formattedName = modelName.replace("/", "__");
279
- return `https://huggingface.co/datasets/TheFinAI/lm-eval-results-private`;
280
  };
281
 
282
  const HeaderLabel = ({ label, tooltip, className, isSorted }) => (
283
- <Tooltip
284
- title={label}
285
- arrow
286
- placement="top"
287
- enterDelay={1000}
288
- componentsProps={getTooltipStyle}
289
- >
290
- <Typography
291
- className={className}
292
- sx={{
293
- fontWeight: 600,
294
- color: isSorted ? "primary.main" : "grey.700",
295
- flex: 1,
296
- transition: "max-width 0.2s ease",
297
- maxWidth: "100%",
298
- ...(label === "Rank" || label === "Type"
299
- ? {
300
- overflow: "visible",
301
- whiteSpace: "normal",
302
- textOverflow: "clip",
303
- textAlign: "center",
304
- }
305
- : {
306
- overflow: "hidden",
307
- textOverflow: "ellipsis",
308
- whiteSpace: "nowrap",
309
- }),
310
- "@media (hover: hover)": {
311
- ".MuiTableCell-root:hover &": {
312
- maxWidth: tooltip ? "calc(100% - 48px)" : "100%",
313
- },
314
- },
315
- }}
316
  >
317
- {label}
318
- </Typography>
319
- </Tooltip>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  );
321
 
322
  const InfoIcon = ({ tooltip }) => (
323
- <Box
324
- component="span"
325
- sx={{
326
- opacity: 0.5,
327
- display: "flex",
328
- alignItems: "center",
329
- ml: 0.5,
330
- }}
331
- >
332
- <InfoIconWithTooltip tooltip={tooltip} />
333
- </Box>
334
- );
335
-
336
- const createHeaderCell = (label, tooltip) => (header) =>
337
- (
338
  <Box
339
- className="header-content"
340
- sx={{
341
- display: "flex",
342
- alignItems: "center",
343
- width: "100%",
344
- position: "relative",
345
- }}
346
- >
347
- <HeaderLabel
348
- label={label}
349
- tooltip={tooltip}
350
- className="header-label"
351
- isSorted={header?.column?.getIsSorted()}
352
- />
353
-
354
- <Box
355
  sx={{
356
- display: "flex",
357
- alignItems: "center",
358
- gap: 0.5,
359
- ml: "auto",
360
- flexShrink: 0,
361
  }}
362
- >
363
- {tooltip && <InfoIcon tooltip={tooltip} />}
364
- </Box>
365
  </Box>
366
- );
367
 
368
- const createModelHeader =
369
- (totalModels, officialProvidersCount = 0, isOfficialProviderActive = false) =>
370
- ({ table }) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  return (
372
- <Box
373
- sx={{
374
- display: "flex",
375
- alignItems: "center",
376
- justifyContent: "space-between",
377
- width: "100%",
378
- }}
379
- >
380
  <Box
381
- sx={{
382
- display: "flex",
383
- alignItems: "center",
384
- gap: 1,
385
- }}
386
- >
387
- <Typography
388
  sx={{
389
- fontWeight: 600,
390
- color: "grey.700",
391
- overflow: "hidden",
392
- textOverflow: "ellipsis",
393
- whiteSpace: "nowrap",
394
  }}
395
- >
396
- Model
397
- </Typography>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  </Box>
399
- </Box>
400
  );
401
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
 
403
  const BooleanValue = ({ value }) => {
404
- if (value === null || value === undefined)
405
- return <Typography variant="body2">-</Typography>;
406
 
407
- return (
408
- <Box
409
- sx={(theme) => ({
410
- display: "flex",
411
- alignItems: "center",
412
- justifyContent: "center",
413
- borderRadius: "4px",
414
- px: 1,
415
- py: 0.5,
416
- backgroundColor: value
417
- ? theme.palette.mode === "dark"
418
- ? alpha(theme.palette.success.main, 0.1)
419
- : alpha(theme.palette.success.main, 0.1)
420
- : theme.palette.mode === "dark"
421
- ? alpha(theme.palette.error.main, 0.1)
422
- : alpha(theme.palette.error.main, 0.1),
423
- })}
424
- >
425
- <Typography
426
- variant="body2"
427
- sx={(theme) => ({
428
- color: value
429
- ? theme.palette.mode === "dark"
430
- ? theme.palette.success.light
431
- : theme.palette.success.dark
432
- : theme.palette.mode === "dark"
433
- ? theme.palette.error.light
434
- : theme.palette.error.dark,
435
- })}
436
- >
437
- {value ? "Yes" : "No"}
438
- </Typography>
439
- </Box>
440
- );
441
  };
442
 
443
  export const createColumns = (
444
- getColorForValue,
445
- scoreDisplay = "normalized",
446
- columnVisibility = {},
447
- totalModels,
448
- averageMode = "all",
449
- searchValue = "",
450
- rankingMode = "static",
451
- onTogglePin,
452
- hasPinnedRows = false
453
  ) => {
454
  // Ajuster les tailles des colonnes en fonction de la présence de lignes épinglées
455
  const getColumnSize = (defaultSize) =>
@@ -650,8 +782,13 @@ export const createColumns = (
650
  {
651
  accessorKey: "model.average_score",
652
  header: createHeaderCell("Average", COLUMN_TOOLTIPS.AVERAGE),
653
- cell: ({ row, getValue }) =>
654
- createScoreCell(getValue, row, "model.average_score"),
 
 
 
 
 
655
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES["model.average_score"],
656
  meta: {
657
  headerStyle: {
@@ -684,13 +821,23 @@ export const createColumns = (
684
  },
685
  },
686
  ];
687
- const createScoreCell = (getValue, row, field) => {
688
- const value = getValue();
689
  const rawValue = field.includes("normalized")
690
  ? row.original.evaluations[field.split(".")[1]]?.value
691
- : value;
 
 
 
 
 
 
692
 
693
  const isAverageColumn = field === "model.average_score";
 
 
 
 
694
  const hasNoValue = value === null || value === undefined;
695
 
696
  return (
@@ -738,7 +885,7 @@ export const createColumns = (
738
  "-"
739
  ) : (
740
  <>
741
- {isAverageColumn ? (
742
  <>
743
  {value.toFixed(2)}
744
  <span style={{ opacity: 0.5 }}> %</span>
@@ -764,14 +911,16 @@ export const createColumns = (
764
  accessorKey: "evaluations.bc5cdr_chemical.normalized_score",
765
  header: createHeaderCell(
766
  "BC5CDR-chemical",
767
- COLUMN_TOOLTIPS.BC5CDR_CHEMICAL
 
 
 
 
 
 
 
 
768
  ),
769
- cell: ({ row, getValue }) =>
770
- createScoreCell(
771
- getValue,
772
- row,
773
- "evaluations.bc5cdr_chemical.normalized_score"
774
- ),
775
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
776
  "evaluations.bc5cdr_chemical.normalized_score"
777
  ],
@@ -780,118 +929,160 @@ export const createColumns = (
780
  accessorKey: "evaluations.ncbi_disease.normalized_score",
781
  header: createHeaderCell(
782
  "NCBI Disease",
783
- COLUMN_TOOLTIPS.NCBI_DISEASE
 
 
 
 
 
 
 
 
784
  ),
785
- cell: ({ row, getValue }) =>
786
- createScoreCell(
787
- getValue,
788
- row,
789
- "evaluations.ncbi_disease.normalized_score"
790
- ),
791
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
792
  "evaluations.ncbi_disease.normalized_score"
793
  ],
794
  },
795
  {
796
  accessorKey: "evaluations.chemprot.normalized_score",
797
- header: createHeaderCell("ChemProt", COLUMN_TOOLTIPS.CHEMPROT),
798
- cell: ({ row, getValue }) =>
799
- createScoreCell(
800
- getValue,
801
- row,
802
- "evaluations.chemprot.normalized_score"
803
- ),
 
 
 
 
 
804
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
805
  "evaluations.chemprot.normalized_score"
806
  ],
807
  },
808
  {
809
  accessorKey: "evaluations.ddi2013.normalized_score",
810
- header: createHeaderCell("DDI2013", COLUMN_TOOLTIPS.DDI2013),
811
- cell: ({ row, getValue }) =>
812
- createScoreCell(
813
- getValue,
814
- row,
815
- "evaluations.ddi2013.normalized_score"
816
- ),
 
 
 
 
 
817
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
818
  "evaluations.ddi2013.normalized_score"
819
  ],
820
  },
821
  {
822
  accessorKey: "evaluations.hoc.normalized_score",
823
- header: createHeaderCell("HoC", COLUMN_TOOLTIPS.HOC),
824
- cell: ({ row, getValue }) =>
825
- createScoreCell(
826
- getValue,
827
- row,
828
- "evaluations.hoc.normalized_score"
829
- ),
 
 
 
 
 
830
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
831
  "evaluations.hoc.normalized_score"
832
  ],
833
  },
834
  {
835
  accessorKey: "evaluations.litcovid.normalized_score",
836
- header: createHeaderCell("LitCovid", COLUMN_TOOLTIPS.LITCOVID),
837
- cell: ({ row, getValue }) =>
838
- createScoreCell(
839
- getValue,
840
- row,
841
- "evaluations.litcovid.normalized_score"
842
- ),
 
 
 
 
 
843
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
844
  "evaluations.litcovid.normalized_score"
845
  ],
846
  },
847
  {
848
  accessorKey: "evaluations.medqa.normalized_score",
849
- header: createHeaderCell("MedQA (5-Option)", COLUMN_TOOLTIPS.MEDQA),
850
- cell: ({ row, getValue }) =>
851
- createScoreCell(
852
- getValue,
853
- row,
854
- "evaluations.medqa.normalized_score"
855
- ),
 
 
 
 
 
856
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
857
  "evaluations.medqa.normalized_score"
858
  ],
859
  },
860
  {
861
  accessorKey: "evaluations.pubmedqa.normalized_score",
862
- header: createHeaderCell("PubMedQA", COLUMN_TOOLTIPS.PUBMEDQA),
863
- cell: ({ row, getValue }) =>
864
- createScoreCell(
865
- getValue,
866
- row,
867
- "evaluations.pubmedqa.normalized_score"
868
- ),
 
 
 
 
 
869
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
870
  "evaluations.pubmedqa.normalized_score"
871
  ],
872
  },
873
  {
874
  accessorKey: "evaluations.pubmed.normalized_score",
875
- header: createHeaderCell("PubMed", COLUMN_TOOLTIPS.PUBMED),
876
- cell: ({ row, getValue }) =>
877
- createScoreCell(
878
- getValue,
879
- row,
880
- "evaluations.pubmed.normalized_score"
881
- ),
 
 
 
 
 
882
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
883
  "evaluations.pubmed.normalized_score"
884
  ],
885
  },
886
  {
887
  accessorKey: "evaluations.ms2.normalized_score",
888
- header: createHeaderCell("MS^2", COLUMN_TOOLTIPS.MS2),
889
- cell: ({ row, getValue }) =>
890
- createScoreCell(
891
- getValue,
892
- row,
893
- "evaluations.ms2.normalized_score"
894
- ),
 
 
 
 
 
895
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
896
  "evaluations.ms2.normalized_score"
897
  ],
@@ -900,27 +1091,34 @@ export const createColumns = (
900
  accessorKey: "evaluations.cochrane_pls.normalized_score",
901
  header: createHeaderCell(
902
  "Cochrane PLS",
903
- COLUMN_TOOLTIPS.COCHRANE_PLS
 
 
 
 
 
 
 
 
904
  ),
905
- cell: ({ row, getValue }) =>
906
- createScoreCell(
907
- getValue,
908
- row,
909
- "evaluations.cochrane_pls.normalized_score"
910
- ),
911
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
912
  "evaluations.cochrane_pls.normalized_score"
913
  ],
914
  },
915
  {
916
  accessorKey: "evaluations.plos.normalized_score",
917
- header: createHeaderCell("PLOS", COLUMN_TOOLTIPS.PLOS),
918
- cell: ({ row, getValue }) =>
919
- createScoreCell(
920
- getValue,
921
- row,
922
- "evaluations.plos.normalized_score"
923
- ),
 
 
 
 
 
924
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
925
  "evaluations.plos.normalized_score"
926
  ],
 
1
+ import React, { useState } from "react";
2
+ import {
3
+ Box,
4
+ Typography,
5
+ Link,
6
+ Tooltip,
7
+ IconButton,
8
+ Badge,
9
+ Chip,
10
+ Menu,
11
+ MenuItem,
12
+ } from "@mui/material";
13
  import { getModelTypeIcon } from "../constants/modelTypes";
14
  import TrendingUpIcon from "@mui/icons-material/TrendingUp";
15
  import TrendingDownIcon from "@mui/icons-material/TrendingDown";
 
21
  import { commonStyles } from "../styles/common";
22
  import { typeColumnSort } from "../components/Table/hooks/useSorting";
23
  import {
24
+ COLUMN_TOOLTIPS,
25
+ getTooltipStyle,
26
+ TABLE_TOOLTIPS,
27
  } from "../constants/tooltips";
28
  import OpenInNewIcon from "@mui/icons-material/OpenInNew";
29
  import { alpha } from "@mui/material/styles";
30
  import InfoIconWithTooltip from "../../../../../components/shared/InfoIconWithTooltip";
31
+ import { useSubscoreStore } from "../hooks/useSubscoreStore";
32
 
33
  const DatabaseIcon = () => (
34
+ <svg
35
+ className="mr-1.5 text-gray-400 group-hover:text-red-500"
36
+ xmlns="http://www.w3.org/2000/svg"
37
+ aria-hidden="true"
38
+ focusable="false"
39
+ role="img"
40
+ width="1.4em"
41
+ height="1.4em"
42
+ preserveAspectRatio="xMidYMid meet"
43
+ viewBox="0 0 25 25"
44
+ >
45
+ <ellipse
46
+ cx="12.5"
47
+ cy="5"
48
+ fill="currentColor"
49
+ fillOpacity="0.25"
50
+ rx="7.5"
51
+ ry="2"
52
+ ></ellipse>
53
+ <path
54
+ d="M12.5 15C16.6421 15 20 14.1046 20 13V20C20 21.1046 16.6421 22 12.5 22C8.35786 22 5 21.1046 5 20V13C5 14.1046 8.35786 15 12.5 15Z"
55
+ fill="currentColor"
56
+ opacity="0.5"
57
+ ></path>
58
+ <path
59
+ d="M12.5 7C16.6421 7 20 6.10457 20 5V11.5C20 12.6046 16.6421 13.5 12.5 13.5C8.35786 13.5 5 12.6046 5 11.5V5C5 6.10457 8.35786 7 12.5 7Z"
60
+ fill="currentColor"
61
+ opacity="0.5"
62
+ ></path>
63
+ <path
64
+ d="M5.23628 12C5.08204 12.1598 5 12.8273 5 13C5 14.1046 8.35786 15 12.5 15C16.6421 15 20 14.1046 20 13C20 12.8273 19.918 12.1598 19.7637 12C18.9311 12.8626 15.9947 13.5 12.5 13.5C9.0053 13.5 6.06886 12.8626 5.23628 12Z"
65
+ fill="currentColor"
66
+ ></path>
67
+ </svg>
68
  );
69
 
70
  const HighlightedText = ({ text, searchValue }) => {
71
+ if (!searchValue) return text;
72
 
73
+ const searches = searchValue
74
+ .split(";")
75
+ .map((s) => s.trim())
76
+ .filter(Boolean);
77
+ let result = text;
78
+ let fragments = [{ text: result, isMatch: false }];
79
 
80
+ searches.forEach((search, searchIndex) => {
81
+ if (!search) return;
82
 
83
+ try {
84
+ let regex;
85
+ if (looksLikeRegex(search)) {
86
+ regex = new RegExp(search, "gi");
87
+ } else {
88
+ regex = new RegExp(
89
+ search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
90
+ "gi"
91
+ );
92
+ }
93
 
94
+ const newFragments = [];
95
+ fragments.forEach((fragment) => {
96
+ if (fragment.isMatch) {
97
+ newFragments.push(fragment);
98
+ return;
99
+ }
100
 
101
+ const parts = fragment.text.split(regex);
102
+ const matches = fragment.text.match(regex);
103
 
104
+ if (!matches) {
105
+ newFragments.push(fragment);
106
+ return;
107
+ }
108
 
109
+ parts.forEach((part, i) => {
110
+ if (part) newFragments.push({ text: part, isMatch: false });
111
+ if (i < parts.length - 1) {
112
+ newFragments.push({
113
+ text: matches[i],
114
+ isMatch: true,
115
+ colorIndex: searchIndex % HIGHLIGHT_COLORS.length,
116
+ });
117
+ }
118
+ });
119
  });
 
 
 
120
 
121
+ fragments = newFragments;
122
+ } catch (e) {
123
+ console.warn("Invalid regex:", search);
124
+ }
125
+ });
126
 
127
+ return (
128
+ <>
129
+ {fragments.map((fragment, i) =>
130
+ fragment.isMatch ? (
131
+ <Box
132
+ key={i}
133
+ component="span"
134
+ sx={{
135
+ backgroundColor:
136
+ HIGHLIGHT_COLORS[fragment.colorIndex],
137
+ color: (theme) =>
138
+ theme.palette.getContrastText(
139
+ HIGHLIGHT_COLORS[fragment.colorIndex]
140
+ ),
141
+ fontWeight: 500,
142
+ px: 0.5,
143
+ py: "2px",
144
+ borderRadius: "3px",
145
+ mx: "1px",
146
+ overflow: "visible",
147
+ display: "inline-block",
148
+ }}
149
+ >
150
+ {fragment.text}
151
+ </Box>
152
+ ) : (
153
+ <React.Fragment key={i}>{fragment.text}</React.Fragment>
154
+ )
155
+ )}
156
+ </>
157
+ );
158
  };
159
 
160
  const MEDAL_STYLES = {
161
+ 1: {
162
+ color: "#B58A1B",
163
+ background: "linear-gradient(135deg, #FFF7E0 0%, #FFD700 100%)",
164
+ borderColor: "rgba(212, 160, 23, 0.35)",
165
+ shadowColor: "rgba(212, 160, 23, 0.8)",
166
+ },
167
+ 2: {
168
+ color: "#667380",
169
+ background: "linear-gradient(135deg, #FFFFFF 0%, #D8E3ED 100%)",
170
+ borderColor: "rgba(124, 139, 153, 0.35)",
171
+ shadowColor: "rgba(124, 139, 153, 0.8)",
172
+ },
173
+ 3: {
174
+ color: "#B85C2F",
175
+ background: "linear-gradient(135deg, #FDF0E9 0%, #FFBC8C 100%)",
176
+ borderColor: "rgba(204, 108, 61, 0.35)",
177
+ shadowColor: "rgba(204, 108, 61, 0.8)",
178
+ },
179
  };
180
 
181
  const getMedalStyle = (rank) => {
182
+ if (rank <= 3) {
183
+ const medalStyle = MEDAL_STYLES[rank];
184
+ return {
185
+ color: medalStyle.color,
186
+ fontWeight: 900,
187
+ fontStretch: "150%",
188
+ fontFamily: '"Inter", -apple-system, sans-serif',
189
+ width: "24px",
190
+ height: "24px",
191
+ background: medalStyle.background,
192
+ border: "1px solid",
193
+ borderColor: medalStyle.borderColor,
194
+ borderRadius: "50%",
195
+ display: "flex",
196
+ alignItems: "center",
197
+ justifyContent: "center",
198
+ fontSize: "0.95rem",
199
+ lineHeight: 1,
200
+ padding: 0,
201
+ boxShadow: `1px 1px 0 ${medalStyle.shadowColor}`,
202
+ position: "relative",
203
+ };
204
+ }
205
  return {
206
+ color: "inherit",
207
+ fontWeight: rank <= 10 ? 600 : 400,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  };
 
 
 
 
 
209
  };
210
 
211
  const getRankStyle = (rank) => getMedalStyle(rank);
212
 
213
  const RankIndicator = ({ rank, previousRank, mode }) => {
214
+ const rankChange = previousRank ? previousRank - rank : 0;
215
 
216
+ const RankChangeIndicator = ({ change }) => {
217
+ if (!change || mode === "dynamic") return null;
218
 
219
+ const getChangeColor = (change) => {
220
+ if (change > 0) return "success.main";
221
+ if (change < 0) return "error.main";
222
+ return "grey.500";
223
+ };
224
 
225
+ const getChangeIcon = (change) => {
226
+ if (change > 0) return <TrendingUpIcon sx={{ fontSize: "1rem" }} />;
227
+ if (change < 0)
228
+ return <TrendingDownIcon sx={{ fontSize: "1rem" }} />;
229
+ return <RemoveIcon sx={{ fontSize: "1rem" }} />;
230
+ };
231
+
232
+ return (
233
+ <Tooltip
234
+ title={`${Math.abs(change)} position${
235
+ Math.abs(change) > 1 ? "s" : ""
236
+ } ${change > 0 ? "up" : "down"}`}
237
+ arrow
238
+ placement="right"
239
+ >
240
+ <Box
241
+ sx={{
242
+ display: "flex",
243
+ alignItems: "center",
244
+ color: getChangeColor(change),
245
+ ml: 0.5,
246
+ fontSize: "0.75rem",
247
+ }}
248
+ >
249
+ {getChangeIcon(change)}
250
+ </Box>
251
+ </Tooltip>
252
+ );
253
  };
254
 
255
  return (
 
 
 
 
 
 
 
256
  <Box
257
+ sx={{
258
+ display: "flex",
259
+ alignItems: "center",
260
+ justifyContent: "center",
261
+ width: "100%",
262
+ }}
 
263
  >
264
+ <Typography
265
+ sx={{
266
+ ...getRankStyle(rank),
267
+ display: "flex",
268
+ alignItems: "center",
269
+ lineHeight: 1,
270
+ position: "relative",
271
+ }}
272
+ >
273
+ {rank <= 3 ? (
274
+ <>
275
+ <Box
276
+ component="span"
277
+ sx={{ position: "relative", zIndex: 1 }}
278
+ >
279
+ {rank}
280
+ </Box>
281
+ <RankChangeIndicator change={rankChange} />
282
+ </>
283
+ ) : (
284
+ <>
285
+ <Box
286
+ component="span"
287
+ sx={{ position: "relative", zIndex: 1 }}
288
+ >
289
+ {rank}
290
+ </Box>
291
+ <RankChangeIndicator change={rankChange} />
292
+ </>
293
+ )}
294
+ </Typography>
295
  </Box>
 
296
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  };
298
 
299
  const getDetailsUrl = (modelName) => {
300
+ const formattedName = modelName.replace("/", "__");
301
+ return `https://huggingface.co/datasets/TheFinAI/lm-eval-results-private`;
302
  };
303
 
304
  const HeaderLabel = ({ label, tooltip, className, isSorted }) => (
305
+ <Tooltip
306
+ title={label}
307
+ arrow
308
+ placement="top"
309
+ enterDelay={1000}
310
+ componentsProps={getTooltipStyle}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
  >
312
+ <Typography
313
+ className={className}
314
+ sx={{
315
+ fontWeight: 600,
316
+ color: isSorted ? "primary.main" : "grey.700",
317
+ flex: 1,
318
+ transition: "max-width 0.2s ease",
319
+ maxWidth: "100%",
320
+ ...(label === "Rank" || label === "Type"
321
+ ? {
322
+ overflow: "visible",
323
+ whiteSpace: "normal",
324
+ textOverflow: "clip",
325
+ textAlign: "center",
326
+ }
327
+ : {
328
+ overflow: "hidden",
329
+ textOverflow: "ellipsis",
330
+ whiteSpace: "nowrap",
331
+ }),
332
+ "@media (hover: hover)": {
333
+ ".MuiTableCell-root:hover &": {
334
+ maxWidth: tooltip ? "calc(100% - 48px)" : "100%",
335
+ },
336
+ },
337
+ }}
338
+ >
339
+ {label}
340
+ </Typography>
341
+ </Tooltip>
342
  );
343
 
344
  const InfoIcon = ({ tooltip }) => (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  <Box
346
+ component="span"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  sx={{
348
+ opacity: 0.5,
349
+ display: "flex",
350
+ alignItems: "center",
351
+ ml: 0.5,
 
352
  }}
353
+ >
354
+ <InfoIconWithTooltip tooltip={tooltip} />
 
355
  </Box>
356
+ );
357
 
358
+ const metricsMap = {
359
+ "evaluations.bc5cdr_chemical.normalized_score": ["Exact F1 entity"],
360
+ "evaluations.ncbi_disease.normalized_score": ["Exact F1 entity"],
361
+ "evaluations.chemprot.normalized_score": ["Macro F1", "Weighted F1"],
362
+ "evaluations.ddi2013.normalized_score": ["Macro F1", "Weighted F1"],
363
+ "evaluations.hoc.normalized_score": ["Macro F1", "Weighted F1"],
364
+ "evaluations.litcovid.normalized_score": ["Macro F1", "Weighted F1"],
365
+ "evaluations.pubmedqa.normalized_score": ["Accuracy", "Macro-F1"],
366
+ "evaluations.medqa.normalized_score": ["Accuracy", "Macro-F1"],
367
+ "evaluations.pubmed.normalized_score": [
368
+ // "bart",
369
+ "bert",
370
+ "rouge1",
371
+ "rougeL",
372
+ "rough2",
373
+ ],
374
+ "evaluations.ms2.normalized_score": [
375
+ // "bart",
376
+ "bert",
377
+ "rouge1",
378
+ "rougeL",
379
+ "rough2",
380
+ ],
381
+ "evaluations.cochrane_pls.normalized_score": [
382
+ // "bart",
383
+ "bert",
384
+ "dcr",
385
+ "fkg",
386
+ "rouge1",
387
+ "rougeL",
388
+ "rough2",
389
+ ],
390
+ "evaluations.plos.normalized_score": [
391
+ // "bart",
392
+ "bert",
393
+ "dcr",
394
+ "fkg",
395
+ "rouge1",
396
+ "rougeL",
397
+ "rough2",
398
+ ],
399
+ };
400
+
401
+ const createHeaderCell = (label, tooltip, accessorKey) => (header) => {
402
+ const [anchorEl, setAnchorEl] = useState(null);
403
+ const selected = useSubscoreStore(
404
+ (s) =>
405
+ s.selections[accessorKey] ||
406
+ (metricsMap[accessorKey] && metricsMap[accessorKey][0])
407
+ );
408
+ const setSelected = useSubscoreStore((s) => s.setSelection);
409
+
410
+ const handleClick = (event) => {
411
+ event.stopPropagation();
412
+ setAnchorEl(event.currentTarget);
413
+ };
414
+
415
+ const handleClose = (value) => {
416
+ if (value) setSelected(accessorKey, value);
417
+ setAnchorEl(null);
418
+ };
419
  return (
 
 
 
 
 
 
 
 
420
  <Box
421
+ className="header-content"
 
 
 
 
 
 
422
  sx={{
423
+ display: "flex",
424
+ alignItems: "center",
425
+ width: "100%",
426
+ overflow: "hidden",
 
427
  }}
428
+ >
429
+ <HeaderLabel
430
+ label={label}
431
+ tooltip={tooltip}
432
+ className="header-label"
433
+ isSorted={header?.column?.getIsSorted()}
434
+ sx={{
435
+ flexGrow: 1,
436
+ flexShrink: 0,
437
+ overflow: "hidden",
438
+ whiteSpace: "nowrap",
439
+ textOverflow: "ellipsis",
440
+ }}
441
+ />
442
+ <Box
443
+ sx={{
444
+ display: "flex",
445
+ alignItems: "center",
446
+ gap: 0.5,
447
+ ml: "auto",
448
+ flexShrink: 0,
449
+ maxWidth: "50%",
450
+ }}
451
+ >
452
+ {accessorKey in metricsMap && (
453
+ <>
454
+ <Chip
455
+ label={selected}
456
+ size="small"
457
+ onClick={handleClick}
458
+ sx={{
459
+ cursor: "pointer",
460
+ maxWidth: "100%",
461
+ minWidth: 0,
462
+ overflow: "hidden",
463
+ textOverflow: "ellipsis",
464
+ whiteSpace: "nowrap",
465
+ flexShrink: 1,
466
+ }}
467
+ />
468
+ <Menu
469
+ anchorEl={anchorEl}
470
+ open={Boolean(anchorEl)}
471
+ onClose={() => handleClose(null)}
472
+ >
473
+ {metricsMap[accessorKey].map((item) => (
474
+ <MenuItem
475
+ key={item}
476
+ onClick={() => handleClose(item)}
477
+ sx={{
478
+ fontWeight:
479
+ selected === item
480
+ ? "bold"
481
+ : "normal",
482
+ }}
483
+ >
484
+ {item}
485
+ </MenuItem>
486
+ ))}
487
+ </Menu>
488
+ </>
489
+ )}
490
+ {tooltip && <InfoIcon tooltip={tooltip} />}
491
+ </Box>
492
  </Box>
 
493
  );
494
+ };
495
+
496
+ const createModelHeader =
497
+ (
498
+ totalModels,
499
+ officialProvidersCount = 0,
500
+ isOfficialProviderActive = false
501
+ ) =>
502
+ ({ table }) => {
503
+ return (
504
+ <Box
505
+ sx={{
506
+ display: "flex",
507
+ alignItems: "center",
508
+ justifyContent: "space-between",
509
+ width: "100%",
510
+ }}
511
+ >
512
+ <Box
513
+ sx={{
514
+ display: "flex",
515
+ alignItems: "center",
516
+ gap: 1,
517
+ }}
518
+ >
519
+ <Typography
520
+ sx={{
521
+ fontWeight: 600,
522
+ color: "grey.700",
523
+ overflow: "hidden",
524
+ textOverflow: "ellipsis",
525
+ whiteSpace: "nowrap",
526
+ }}
527
+ >
528
+ Model
529
+ </Typography>
530
+ </Box>
531
+ </Box>
532
+ );
533
+ };
534
 
535
  const BooleanValue = ({ value }) => {
536
+ if (value === null || value === undefined)
537
+ return <Typography variant="body2">-</Typography>;
538
 
539
+ return (
540
+ <Box
541
+ sx={(theme) => ({
542
+ display: "flex",
543
+ alignItems: "center",
544
+ justifyContent: "center",
545
+ borderRadius: "4px",
546
+ px: 1,
547
+ py: 0.5,
548
+ backgroundColor: value
549
+ ? theme.palette.mode === "dark"
550
+ ? alpha(theme.palette.success.main, 0.1)
551
+ : alpha(theme.palette.success.main, 0.1)
552
+ : theme.palette.mode === "dark"
553
+ ? alpha(theme.palette.error.main, 0.1)
554
+ : alpha(theme.palette.error.main, 0.1),
555
+ })}
556
+ >
557
+ <Typography
558
+ variant="body2"
559
+ sx={(theme) => ({
560
+ color: value
561
+ ? theme.palette.mode === "dark"
562
+ ? theme.palette.success.light
563
+ : theme.palette.success.dark
564
+ : theme.palette.mode === "dark"
565
+ ? theme.palette.error.light
566
+ : theme.palette.error.dark,
567
+ })}
568
+ >
569
+ {value ? "Yes" : "No"}
570
+ </Typography>
571
+ </Box>
572
+ );
573
  };
574
 
575
  export const createColumns = (
576
+ getColorForValue,
577
+ scoreDisplay = "normalized",
578
+ columnVisibility = {},
579
+ totalModels,
580
+ averageMode = "all",
581
+ searchValue = "",
582
+ rankingMode = "static",
583
+ onTogglePin,
584
+ hasPinnedRows = false
585
  ) => {
586
  // Ajuster les tailles des colonnes en fonction de la présence de lignes épinglées
587
  const getColumnSize = (defaultSize) =>
 
782
  {
783
  accessorKey: "model.average_score",
784
  header: createHeaderCell("Average", COLUMN_TOOLTIPS.AVERAGE),
785
+ cell: ({ row, getValue }) => (
786
+ <ScoreCell
787
+ getValue={getValue}
788
+ row={row}
789
+ field="model.average_score"
790
+ />
791
+ ),
792
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES["model.average_score"],
793
  meta: {
794
  headerStyle: {
 
821
  },
822
  },
823
  ];
824
+
825
+ const ScoreCell = ({ getValue, row, field }) => {
826
  const rawValue = field.includes("normalized")
827
  ? row.original.evaluations[field.split(".")[1]]?.value
828
+ : getValue()[field];
829
+
830
+ const selected = useSubscoreStore(
831
+ (s) =>
832
+ s.selections[field] ||
833
+ (metricsMap[field] && metricsMap[field][0])
834
+ );
835
 
836
  const isAverageColumn = field === "model.average_score";
837
+ const value = isAverageColumn ? getValue() : getValue()[selected];
838
+ if (isAverageColumn) {
839
+ console.log(getValue.toString(), row, getValue());
840
+ }
841
  const hasNoValue = value === null || value === undefined;
842
 
843
  return (
 
885
  "-"
886
  ) : (
887
  <>
888
+ {isAverageColumn ? (
889
  <>
890
  {value.toFixed(2)}
891
  <span style={{ opacity: 0.5 }}> %</span>
 
911
  accessorKey: "evaluations.bc5cdr_chemical.normalized_score",
912
  header: createHeaderCell(
913
  "BC5CDR-chemical",
914
+ COLUMN_TOOLTIPS.BC5CDR_CHEMICAL,
915
+ "evaluations.bc5cdr_chemical.normalized_score"
916
+ ),
917
+ cell: ({ row, getValue }) => (
918
+ <ScoreCell
919
+ getValue={getValue}
920
+ row={row}
921
+ field="evaluations.bc5cdr_chemical.normalized_score"
922
+ />
923
  ),
 
 
 
 
 
 
924
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
925
  "evaluations.bc5cdr_chemical.normalized_score"
926
  ],
 
929
  accessorKey: "evaluations.ncbi_disease.normalized_score",
930
  header: createHeaderCell(
931
  "NCBI Disease",
932
+ COLUMN_TOOLTIPS.NCBI_DISEASE,
933
+ "evaluations.ncbi_disease.normalized_score"
934
+ ),
935
+ cell: ({ row, getValue }) => (
936
+ <ScoreCell
937
+ getValue={getValue}
938
+ row={row}
939
+ field="evaluations.ncbi_disease.normalized_score"
940
+ />
941
  ),
 
 
 
 
 
 
942
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
943
  "evaluations.ncbi_disease.normalized_score"
944
  ],
945
  },
946
  {
947
  accessorKey: "evaluations.chemprot.normalized_score",
948
+ header: createHeaderCell(
949
+ "ChemProt",
950
+ COLUMN_TOOLTIPS.CHEMPROT,
951
+ "evaluations.chemprot.normalized_score"
952
+ ),
953
+ cell: ({ row, getValue }) => (
954
+ <ScoreCell
955
+ getValue={getValue}
956
+ row={row}
957
+ field="evaluations.chemprot.normalized_score"
958
+ />
959
+ ),
960
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
961
  "evaluations.chemprot.normalized_score"
962
  ],
963
  },
964
  {
965
  accessorKey: "evaluations.ddi2013.normalized_score",
966
+ header: createHeaderCell(
967
+ "DDI2013",
968
+ COLUMN_TOOLTIPS.DDI2013,
969
+ "evaluations.ddi2013.normalized_score"
970
+ ),
971
+ cell: ({ row, getValue }) => (
972
+ <ScoreCell
973
+ getValue={getValue}
974
+ row={row}
975
+ field="evaluations.ddi2013.normalized_score"
976
+ />
977
+ ),
978
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
979
  "evaluations.ddi2013.normalized_score"
980
  ],
981
  },
982
  {
983
  accessorKey: "evaluations.hoc.normalized_score",
984
+ header: createHeaderCell(
985
+ "HoC",
986
+ COLUMN_TOOLTIPS.HOC,
987
+ "evaluations.hoc.normalized_score"
988
+ ),
989
+ cell: ({ row, getValue }) => (
990
+ <ScoreCell
991
+ getValue={getValue}
992
+ row={row}
993
+ field="evaluations.hoc.normalized_score"
994
+ />
995
+ ),
996
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
997
  "evaluations.hoc.normalized_score"
998
  ],
999
  },
1000
  {
1001
  accessorKey: "evaluations.litcovid.normalized_score",
1002
+ header: createHeaderCell(
1003
+ "LitCovid",
1004
+ COLUMN_TOOLTIPS.LITCOVID,
1005
+ "evaluations.litcovid.normalized_score"
1006
+ ),
1007
+ cell: ({ row, getValue }) => (
1008
+ <ScoreCell
1009
+ getValue={getValue}
1010
+ row={row}
1011
+ field="evaluations.litcovid.normalized_score"
1012
+ />
1013
+ ),
1014
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
1015
  "evaluations.litcovid.normalized_score"
1016
  ],
1017
  },
1018
  {
1019
  accessorKey: "evaluations.medqa.normalized_score",
1020
+ header: createHeaderCell(
1021
+ "MedQA (5-Option)",
1022
+ COLUMN_TOOLTIPS.MEDQA,
1023
+ "evaluations.medqa.normalized_score"
1024
+ ),
1025
+ cell: ({ row, getValue }) => (
1026
+ <ScoreCell
1027
+ getValue={getValue}
1028
+ row={row}
1029
+ field="evaluations.medqa.normalized_score"
1030
+ />
1031
+ ),
1032
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
1033
  "evaluations.medqa.normalized_score"
1034
  ],
1035
  },
1036
  {
1037
  accessorKey: "evaluations.pubmedqa.normalized_score",
1038
+ header: createHeaderCell(
1039
+ "PubMedQA",
1040
+ COLUMN_TOOLTIPS.PUBMEDQA,
1041
+ "evaluations.pubmedqa.normalized_score"
1042
+ ),
1043
+ cell: ({ row, getValue }) => (
1044
+ <ScoreCell
1045
+ getValue={getValue}
1046
+ row={row}
1047
+ field="evaluations.pubmedqa.normalized_score"
1048
+ />
1049
+ ),
1050
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
1051
  "evaluations.pubmedqa.normalized_score"
1052
  ],
1053
  },
1054
  {
1055
  accessorKey: "evaluations.pubmed.normalized_score",
1056
+ header: createHeaderCell(
1057
+ "PubMed",
1058
+ COLUMN_TOOLTIPS.PUBMED,
1059
+ "evaluations.pubmed.normalized_score"
1060
+ ),
1061
+ cell: ({ row, getValue }) => (
1062
+ <ScoreCell
1063
+ getValue={getValue}
1064
+ row={row}
1065
+ field="evaluations.pubmed.normalized_score"
1066
+ />
1067
+ ),
1068
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
1069
  "evaluations.pubmed.normalized_score"
1070
  ],
1071
  },
1072
  {
1073
  accessorKey: "evaluations.ms2.normalized_score",
1074
+ header: createHeaderCell(
1075
+ "MS^2",
1076
+ COLUMN_TOOLTIPS.MS2,
1077
+ "evaluations.ms2.normalized_score"
1078
+ ),
1079
+ cell: ({ row, getValue }) => (
1080
+ <ScoreCell
1081
+ getValue={getValue}
1082
+ row={row}
1083
+ field="evaluations.ms2.normalized_score"
1084
+ />
1085
+ ),
1086
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
1087
  "evaluations.ms2.normalized_score"
1088
  ],
 
1091
  accessorKey: "evaluations.cochrane_pls.normalized_score",
1092
  header: createHeaderCell(
1093
  "Cochrane PLS",
1094
+ COLUMN_TOOLTIPS.COCHRANE_PLS,
1095
+ "evaluations.cochrane_pls.normalized_score"
1096
+ ),
1097
+ cell: ({ row, getValue }) => (
1098
+ <ScoreCell
1099
+ getValue={getValue}
1100
+ row={row}
1101
+ field="evaluations.cochrane_pls.normalized_score"
1102
+ />
1103
  ),
 
 
 
 
 
 
1104
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
1105
  "evaluations.cochrane_pls.normalized_score"
1106
  ],
1107
  },
1108
  {
1109
  accessorKey: "evaluations.plos.normalized_score",
1110
+ header: createHeaderCell(
1111
+ "PLOS",
1112
+ COLUMN_TOOLTIPS.PLOS,
1113
+ "evaluations.plos.normalized_score"
1114
+ ),
1115
+ cell: ({ row, getValue }) => (
1116
+ <ScoreCell
1117
+ getValue={getValue}
1118
+ row={row}
1119
+ field="evaluations.plos.normalized_score"
1120
+ />
1121
+ ),
1122
  size: TABLE_DEFAULTS.COLUMNS.COLUMN_SIZES[
1123
  "evaluations.plos.normalized_score"
1124
  ],