Spaces:
Running
Running
jisubae
commited on
Commit
·
4a43fed
1
Parent(s):
cd13f52
feat: Add optional HF dataset sync for leaderboard
Browse files- README.md +9 -0
- app.py +9 -1
- config.py +3 -0
- env.example +11 -0
- src/leaderboard_manager.py +320 -202
- ui/leaderboard_tab.py +82 -3
README.md
CHANGED
|
@@ -48,6 +48,8 @@ hf_oauth: true
|
|
| 48 |
- Hugging Face Dataset repo
|
| 49 |
- 기준 데이터: `FRESHQA_DATA_REPO_ID` / `FRESHQA_DATA_FILENAME`
|
| 50 |
- (옵션) 제출 추적 저장소: `SUBMISSION_TRACKER_REPO_ID`
|
|
|
|
|
|
|
| 51 |
|
| 52 |
설치:
|
| 53 |
```bash
|
|
@@ -76,6 +78,10 @@ cp env.example .env
|
|
| 76 |
- UPSTAGE_API_KEY 또는 UPSTAGE_API_KEYS(콤마 구분)
|
| 77 |
- ENABLE_SUBMISSION_LIMIT (기본: true)
|
| 78 |
- SUBMISSION_TRACKER_REPO_ID (제출 제한 사용 시 필요)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
검증: 앱 시작 시 `Config.validate_required_configs()`가 누락된 필수 설정을 검사합니다.
|
| 81 |
|
|
@@ -108,6 +114,8 @@ Docker(옵션):
|
|
| 108 |
|
| 109 |
3) 리더보드 탭
|
| 110 |
- 제출 결과가 `data/leaderboard_results.csv`에 누적
|
|
|
|
|
|
|
| 111 |
- 검색/새로고침 가능
|
| 112 |
|
| 113 |
---
|
|
@@ -122,6 +130,7 @@ Docker(옵션):
|
|
| 122 |
- `freshqa/freshqa_acc.py::calculate_accuracy`, `process_freshqa_dataframe`
|
| 123 |
5) 저장:
|
| 124 |
- 리더보드: `src/leaderboard_manager.py::append_to_leaderboard_data`
|
|
|
|
| 125 |
- (옵션) 제출 이력: `src/submission_tracker.py` (ENABLE_SUBMISSION_LIMIT=true 일 때만)
|
| 126 |
|
| 127 |
주의: `ENABLE_SUBMISSION_LIMIT=false`인 경우, 제출 이력 추적용 Hugging Face 저장소 접근을 시도하지 않도록 코드가 반영되어 있습니다.
|
|
|
|
| 48 |
- Hugging Face Dataset repo
|
| 49 |
- 기준 데이터: `FRESHQA_DATA_REPO_ID` / `FRESHQA_DATA_FILENAME`
|
| 50 |
- (옵션) 제출 추적 저장소: `SUBMISSION_TRACKER_REPO_ID`
|
| 51 |
+
- (옵션) 리더보드를 Hugging Face dataset에 백업하려면 `UPLOAD_LEADERBOARD_TO_HF=true` 설정
|
| 52 |
+
|
| 53 |
|
| 54 |
설치:
|
| 55 |
```bash
|
|
|
|
| 78 |
- UPSTAGE_API_KEY 또는 UPSTAGE_API_KEYS(콤마 구분)
|
| 79 |
- ENABLE_SUBMISSION_LIMIT (기본: true)
|
| 80 |
- SUBMISSION_TRACKER_REPO_ID (제출 제한 사용 시 필요)
|
| 81 |
+
- UPLOAD_LEADERBOARD_TO_HF
|
| 82 |
+
- true: 리더보드를 HF Private Dataset에도 백업(권장: 운영 환경)
|
| 83 |
+
- false: 로컬 CSV에만 저장(권장: 로컬 개발)
|
| 84 |
+
|
| 85 |
|
| 86 |
검증: 앱 시작 시 `Config.validate_required_configs()`가 누락된 필수 설정을 검사합니다.
|
| 87 |
|
|
|
|
| 114 |
|
| 115 |
3) 리더보드 탭
|
| 116 |
- 제출 결과가 `data/leaderboard_results.csv`에 누적
|
| 117 |
+
- (옵션) `UPLOAD_LEADERBOARD_TO_HF=true`인 경우 Hugging Face Dataset에도
|
| 118 |
+
`leaderboard_results.csv`로 자동 업로드됩니다.
|
| 119 |
- 검색/새로고침 가능
|
| 120 |
|
| 121 |
---
|
|
|
|
| 130 |
- `freshqa/freshqa_acc.py::calculate_accuracy`, `process_freshqa_dataframe`
|
| 131 |
5) 저장:
|
| 132 |
- 리더보드: `src/leaderboard_manager.py::append_to_leaderboard_data`
|
| 133 |
+
- (옵션) 리더보드 HF 저장소 백업: `UPLOAD_LEADERBOARD_TO_HF=true`일 때만
|
| 134 |
- (옵션) 제출 이력: `src/submission_tracker.py` (ENABLE_SUBMISSION_LIMIT=true 일 때만)
|
| 135 |
|
| 136 |
주의: `ENABLE_SUBMISSION_LIMIT=false`인 경우, 제출 이력 추적용 Hugging Face 저장소 접근을 시도하지 않도록 코드가 반영되어 있습니다.
|
app.py
CHANGED
|
@@ -40,7 +40,8 @@ def create_interface():
|
|
| 40 |
with gr.Tabs():
|
| 41 |
# 리더보드 탭
|
| 42 |
with gr.Tab("🏆 리더보드"):
|
| 43 |
-
|
|
|
|
| 44 |
|
| 45 |
# 제출 및 평가 탭
|
| 46 |
with gr.Tab("📤 제출 및 평가"):
|
|
@@ -49,6 +50,13 @@ def create_interface():
|
|
| 49 |
# 데이터셋 다운로드 탭
|
| 50 |
with gr.Tab("💾 데이터셋"):
|
| 51 |
create_dataset_tab()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
return app
|
| 54 |
|
|
|
|
| 40 |
with gr.Tabs():
|
| 41 |
# 리더보드 탭
|
| 42 |
with gr.Tab("🏆 리더보드"):
|
| 43 |
+
# ✅ 리더보드 컴포넌트와 새로고침 함수 받아오기
|
| 44 |
+
relaxed_table, strict_table, refresh_leaderboard = create_leaderboard_tab()
|
| 45 |
|
| 46 |
# 제출 및 평가 탭
|
| 47 |
with gr.Tab("📤 제출 및 평가"):
|
|
|
|
| 50 |
# 데이터셋 다운로드 탭
|
| 51 |
with gr.Tab("💾 데이터셋"):
|
| 52 |
create_dataset_tab()
|
| 53 |
+
|
| 54 |
+
# ✅ 앱이 로드될 때마다(사용자가 페이지 처음 열 때마다) 한 번 자동으로 새로고침
|
| 55 |
+
app.load(
|
| 56 |
+
fn=refresh_leaderboard,
|
| 57 |
+
inputs=None,
|
| 58 |
+
outputs=[relaxed_table, strict_table],
|
| 59 |
+
)
|
| 60 |
|
| 61 |
return app
|
| 62 |
|
config.py
CHANGED
|
@@ -49,6 +49,9 @@ class Config:
|
|
| 49 |
ENABLE_SUBMISSION_LIMIT = os.getenv('ENABLE_SUBMISSION_LIMIT', 'true').lower() == 'true'
|
| 50 |
SUBMISSION_TRACKER_REPO_ID = os.getenv('SUBMISSION_TRACKER_REPO_ID')
|
| 51 |
|
|
|
|
|
|
|
|
|
|
| 52 |
# 환경 설정
|
| 53 |
IS_HUGGINGFACE_SPACES = os.getenv("SPACE_ID") is not None
|
| 54 |
|
|
|
|
| 49 |
ENABLE_SUBMISSION_LIMIT = os.getenv('ENABLE_SUBMISSION_LIMIT', 'true').lower() == 'true'
|
| 50 |
SUBMISSION_TRACKER_REPO_ID = os.getenv('SUBMISSION_TRACKER_REPO_ID')
|
| 51 |
|
| 52 |
+
# 리더보드 HF 업로드 설정
|
| 53 |
+
UPLOAD_LEADERBOARD_TO_HF = os.getenv('UPLOAD_LEADERBOARD_TO_HF', 'true').lower() == 'true'
|
| 54 |
+
|
| 55 |
# 환경 설정
|
| 56 |
IS_HUGGINGFACE_SPACES = os.getenv("SPACE_ID") is not None
|
| 57 |
|
env.example
CHANGED
|
@@ -40,6 +40,17 @@ SUBMISSION_TRACKER_REPO_ID=james-demo-leaderboard-backend/submission-tracker
|
|
| 40 |
# - false: 제출 제한 기능 비활성화 (로컬 테스트용)
|
| 41 |
ENABLE_SUBMISSION_LIMIT=true
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
# ===========================================
|
| 44 |
# AI 평가 API 설정
|
| 45 |
# ===========================================
|
|
|
|
| 40 |
# - false: 제출 제한 기능 비활성화 (로컬 테스트용)
|
| 41 |
ENABLE_SUBMISSION_LIMIT=true
|
| 42 |
|
| 43 |
+
# ===========================================
|
| 44 |
+
# 리더보드 저장 설정
|
| 45 |
+
# ===========================================
|
| 46 |
+
|
| 47 |
+
# 리더보드를 HuggingFace private dataset에도 저장할지 여부
|
| 48 |
+
# - true : 로컬 CSV 저장 + HF dataset에도 업로드 (권장: 운영/배포 환경)
|
| 49 |
+
# - false: 로컬 CSV에만 저장 (권장: 로컬 개발 환경)
|
| 50 |
+
UPLOAD_LEADERBOARD_TO_HF=false
|
| 51 |
+
|
| 52 |
+
# (참고) 리더보드는 기준 데이터와 동일한 Repository(FRESHQA_DATA_REPO_ID)에 leaderboard_results.csv 파일명으로 저장됩니다.
|
| 53 |
+
|
| 54 |
# ===========================================
|
| 55 |
# AI 평가 API 설정
|
| 56 |
# ===========================================
|
src/leaderboard_manager.py
CHANGED
|
@@ -1,228 +1,346 @@
|
|
| 1 |
"""
|
| 2 |
리더보드 관리 모듈
|
| 3 |
리더보드 데이터의 로드, 저장, 표시 준비를 담당합니다.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
-
import pandas as pd
|
| 7 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
from src.utils import file_lock
|
| 9 |
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
try:
|
| 13 |
-
# 프로젝트 루트에서 data 디렉토리 찾기
|
| 14 |
-
current_dir = os.path.dirname(os.path.abspath(__file__)) # src/ 폴더
|
| 15 |
-
project_root = os.path.dirname(current_dir) # 프로젝트 루트
|
| 16 |
-
data_path = os.path.join(project_root, 'data', 'leaderboard_results.csv')
|
| 17 |
df = pd.read_csv(data_path)
|
| 18 |
-
|
| 19 |
-
# 기존 데이터에 evaluation_mode 컬럼이 없으면 추가
|
| 20 |
-
if 'evaluation_mode' not in df.columns:
|
| 21 |
-
df['evaluation_mode'] = 'Unknown'
|
| 22 |
-
|
| 23 |
-
text_columns = ['model', 'description']
|
| 24 |
-
for col in text_columns:
|
| 25 |
-
if col not in df.columns:
|
| 26 |
-
df[col] = pd.Series(dtype='object')
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
# 새로운 상세 분석 컬럼들이 없으면 추가
|
| 30 |
-
detailed_columns = [
|
| 31 |
-
'acc_test', 'acc_dev', 'acc_vp', 'acc_fp', 'acc_vp_one_hop', 'acc_vp_two_hop',
|
| 32 |
-
'acc_fp_one_hop', 'acc_fp_two_hop', 'acc_vp_old', 'acc_vp_new', 'acc_fp_old', 'acc_fp_new'
|
| 33 |
-
]
|
| 34 |
-
|
| 35 |
-
for col in detailed_columns:
|
| 36 |
-
if col not in df.columns:
|
| 37 |
-
df[col] = 0.0
|
| 38 |
-
|
| 39 |
-
# 도메인별 정확도 컬럼들이 없으면 추가 (freshqa_acc.py와 일치)
|
| 40 |
-
domain_columns = [
|
| 41 |
-
'acc_politics', 'acc_sports', 'acc_entertainment',
|
| 42 |
-
'acc_weather', 'acc_world', 'acc_economy',
|
| 43 |
-
'acc_society', 'acc_it_science', 'acc_life_culture', 'acc_unknown'
|
| 44 |
-
]
|
| 45 |
-
|
| 46 |
-
for col in domain_columns:
|
| 47 |
-
if col not in df.columns:
|
| 48 |
-
df[col] = 0.0
|
| 49 |
-
|
| 50 |
-
# accuracy 기준으로 정렬 (랭킹 기준) - 빈 데이터프레임이 아닐 때만
|
| 51 |
-
if not df.empty and 'accuracy' in df.columns:
|
| 52 |
-
df = df.sort_values('accuracy', ascending=False).reset_index(drop=True)
|
| 53 |
-
|
| 54 |
-
# rank 컬럼은 저장하지 않고 표시 시에만 계산
|
| 55 |
-
# 숫자 컬럼들은 원본 그대로 저장 (반올림하지 않음)
|
| 56 |
-
|
| 57 |
-
# 컬럼 순서를 헤더와 맞춰서 정렬 (rank 제외)
|
| 58 |
-
column_order = [
|
| 59 |
-
'id', 'model', 'description', 'accuracy', 'fast_changing_accuracy',
|
| 60 |
-
'slow_changing_accuracy', 'never_changing_accuracy', 'acc_vp', 'acc_fp',
|
| 61 |
-
'acc_vp_one_hop', 'acc_vp_two_hop', 'acc_fp_one_hop', 'acc_fp_two_hop',
|
| 62 |
-
'acc_vp_old', 'acc_vp_new', 'acc_fp_old', 'acc_fp_new',
|
| 63 |
-
'acc_politics', 'acc_sports', 'acc_entertainment', 'acc_weather',
|
| 64 |
-
'acc_world', 'acc_economy', 'acc_society', 'acc_it_science',
|
| 65 |
-
'acc_life_culture', 'acc_unknown', 'total_questions', 'evaluation_date', 'evaluation_mode'
|
| 66 |
-
]
|
| 67 |
-
|
| 68 |
-
# 존재하는 컬럼만 선택하여 순서대로 정렬
|
| 69 |
-
available_columns = [col for col in column_order if col in df.columns]
|
| 70 |
-
df = df[available_columns]
|
| 71 |
-
|
| 72 |
-
return df
|
| 73 |
except FileNotFoundError:
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
|
| 108 |
def append_to_leaderboard_data(new_data_list):
|
| 109 |
-
"""
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
if os.path.exists(data_path):
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
|
|
|
| 122 |
else:
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
|
|
|
| 127 |
new_df = pd.DataFrame(new_data_list)
|
|
|
|
|
|
|
| 128 |
|
| 129 |
-
# FutureWarning 방지: 빈 DataFrame은 제외하고 결합
|
| 130 |
frames_to_concat = []
|
| 131 |
-
if
|
| 132 |
frames_to_concat.append(existing_df)
|
| 133 |
-
if
|
| 134 |
frames_to_concat.append(new_df)
|
| 135 |
-
|
| 136 |
if len(frames_to_concat) == 0:
|
| 137 |
-
# 둘 다 비어있으면 기존 스키마 유지
|
| 138 |
combined_df = existing_df.copy()
|
| 139 |
elif len(frames_to_concat) == 1:
|
| 140 |
combined_df = frames_to_concat[0].copy()
|
| 141 |
else:
|
| 142 |
combined_df = pd.concat(frames_to_concat, ignore_index=True)
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
combined_df.to_csv(data_path, index=False)
|
| 161 |
-
|
| 162 |
-
return combined_df
|
| 163 |
-
|
| 164 |
-
def prepare_display_data(df, global_ranking=None):
|
| 165 |
-
"""테이블 표시용 데이터 준비 (rank 계산 및 반올림 적용)"""
|
| 166 |
-
# 빈 데이터프레임인 경우 그대로 반환
|
| 167 |
-
if df.empty:
|
| 168 |
-
return df
|
| 169 |
-
|
| 170 |
-
display_df = df.copy()
|
| 171 |
-
if 'model' in display_df.columns:
|
| 172 |
-
display_df['model'] = display_df['model'].fillna('Anonymous Model')
|
| 173 |
-
display_df['model'] = display_df['model'].replace('', 'Anonymous Model')
|
| 174 |
-
if 'description' in display_df.columns:
|
| 175 |
-
display_df['description'] = display_df['description'].replace({None: '', pd.NA: ''}).fillna('')
|
| 176 |
-
|
| 177 |
-
# rank 컬럼 추가
|
| 178 |
-
if 'accuracy' in display_df.columns:
|
| 179 |
-
if global_ranking is not None:
|
| 180 |
-
# 전체 랭킹 정보가 제공된 경우 사용
|
| 181 |
-
display_df['rank'] = display_df.index.map(global_ranking)
|
| 182 |
-
else:
|
| 183 |
-
# 전체 랭킹 정보가 없는 경우 accuracy 기준으로 정렬하여 rank 계산
|
| 184 |
-
display_df = display_df.sort_values('accuracy', ascending=False).reset_index(drop=True)
|
| 185 |
-
|
| 186 |
-
# rank 컬럼 추가 (1~3위는 아이콘, 나머지는 숫자)
|
| 187 |
-
def get_rank_display(rank):
|
| 188 |
-
if rank == 1:
|
| 189 |
-
return "🥇"
|
| 190 |
-
elif rank == 2:
|
| 191 |
-
return "🥈"
|
| 192 |
-
elif rank == 3:
|
| 193 |
-
return "🥉"
|
| 194 |
-
else:
|
| 195 |
-
return str(rank)
|
| 196 |
-
|
| 197 |
-
display_df['rank'] = [get_rank_display(i+1) for i in range(len(display_df))]
|
| 198 |
-
|
| 199 |
-
# 숫자 컬럼들을 소숫점 2번째에서 반올림 (표시용으로만)
|
| 200 |
-
numeric_columns = [
|
| 201 |
-
'accuracy', 'fast_changing_accuracy', 'slow_changing_accuracy', 'never_changing_accuracy',
|
| 202 |
-
'acc_vp', 'acc_fp', 'acc_vp_one_hop', 'acc_vp_two_hop', 'acc_fp_one_hop', 'acc_fp_two_hop',
|
| 203 |
-
'acc_vp_old', 'acc_vp_new', 'acc_fp_old', 'acc_fp_new',
|
| 204 |
-
'acc_politics', 'acc_sports', 'acc_entertainment', 'acc_weather',
|
| 205 |
-
'acc_world', 'acc_economy', 'acc_society', 'acc_it_science',
|
| 206 |
-
'acc_life_culture', 'acc_unknown'
|
| 207 |
-
]
|
| 208 |
-
|
| 209 |
-
for col in numeric_columns:
|
| 210 |
-
if col in display_df.columns:
|
| 211 |
-
display_df[col] = display_df[col].round(2)
|
| 212 |
-
|
| 213 |
-
# 컬럼 순서 재정렬 (rank를 맨 앞에)
|
| 214 |
-
column_order = [
|
| 215 |
-
'rank', 'id', 'model', 'description', 'accuracy', 'fast_changing_accuracy',
|
| 216 |
-
'slow_changing_accuracy', 'never_changing_accuracy', 'acc_vp', 'acc_fp',
|
| 217 |
-
'acc_vp_one_hop', 'acc_vp_two_hop', 'acc_fp_one_hop', 'acc_fp_two_hop',
|
| 218 |
-
'acc_vp_old', 'acc_vp_new', 'acc_fp_old', 'acc_fp_new',
|
| 219 |
-
'acc_politics', 'acc_sports', 'acc_entertainment', 'acc_weather',
|
| 220 |
-
'acc_world', 'acc_economy', 'acc_society', 'acc_it_science',
|
| 221 |
-
'acc_life_culture', 'acc_unknown', 'total_questions', 'evaluation_date', 'evaluation_mode'
|
| 222 |
-
]
|
| 223 |
-
|
| 224 |
-
# 존재하는 컬럼만 선택하여 순서대로 정렬
|
| 225 |
-
available_columns = [col for col in column_order if col in display_df.columns]
|
| 226 |
-
display_df = display_df[available_columns]
|
| 227 |
-
|
| 228 |
-
return display_df
|
|
|
|
| 1 |
"""
|
| 2 |
리더보드 관리 모듈
|
| 3 |
리더보드 데이터의 로드, 저장, 표시 준비를 담당합니다.
|
| 4 |
+
|
| 5 |
+
- 로컬 CSV: 프로젝트 루트의 data/leaderboard_results.csv
|
| 6 |
+
- 선택적 HF 연동:
|
| 7 |
+
- repo_id: Config.FRESHQA_DATA_REPO_ID
|
| 8 |
+
- token : Config.HF_TOKEN
|
| 9 |
+
- 파일명 : leaderboard_results.csv (repo 루트)
|
| 10 |
+
- Config.UPLOAD_LEADERBOARD_TO_HF == True 일 때만 HF를 읽고/쓴다.
|
| 11 |
"""
|
| 12 |
|
|
|
|
| 13 |
import os
|
| 14 |
+
import time
|
| 15 |
+
import tempfile
|
| 16 |
+
from typing import Optional
|
| 17 |
+
|
| 18 |
+
import pandas as pd
|
| 19 |
+
from huggingface_hub import HfApi, hf_hub_download
|
| 20 |
+
|
| 21 |
+
from config import Config
|
| 22 |
from src.utils import file_lock
|
| 23 |
|
| 24 |
+
|
| 25 |
+
# -------------------------
|
| 26 |
+
# 상수 및 설정
|
| 27 |
+
# -------------------------
|
| 28 |
+
|
| 29 |
+
HF_LEADERBOARD_FILENAME = "leaderboard_results.csv" # HF dataset 내 파일명 (루트)
|
| 30 |
+
LOCAL_LEADERBOARD_FILENAME = "leaderboard_results.csv" # 로컬 data 폴더 내 파일명 (기존 유지)
|
| 31 |
+
|
| 32 |
+
HF_REPO_ID = Config.FRESHQA_DATA_REPO_ID
|
| 33 |
+
HF_ADMIN_TOKEN = Config.HF_TOKEN
|
| 34 |
+
UPLOAD_LEADERBOARD_TO_HF = Config.UPLOAD_LEADERBOARD_TO_HF
|
| 35 |
+
|
| 36 |
+
hf_api = HfApi()
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
# -------------------------
|
| 40 |
+
# 경로/초기 스키마/정규화 헬퍼
|
| 41 |
+
# -------------------------
|
| 42 |
+
|
| 43 |
+
def _get_local_leaderboard_path() -> str:
|
| 44 |
+
"""프로젝트 루트 기준 로컬 리더보드 CSV 경로 반환."""
|
| 45 |
+
current_dir = os.path.dirname(os.path.abspath(__file__)) # src/ 폴더
|
| 46 |
+
project_root = os.path.dirname(current_dir) # 프로젝트 루트
|
| 47 |
+
return os.path.join(project_root, "data", LOCAL_LEADERBOARD_FILENAME)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _init_empty_leaderboard_df() -> pd.DataFrame:
|
| 51 |
+
"""초기 빈 리더보드 스키마 DataFrame."""
|
| 52 |
+
return pd.DataFrame({
|
| 53 |
+
"id": [],
|
| 54 |
+
"model": [],
|
| 55 |
+
"description": [],
|
| 56 |
+
"accuracy": [],
|
| 57 |
+
"fast_changing_accuracy": [],
|
| 58 |
+
"slow_changing_accuracy": [],
|
| 59 |
+
"never_changing_accuracy": [],
|
| 60 |
+
"acc_vp": [],
|
| 61 |
+
"acc_fp": [],
|
| 62 |
+
"acc_vp_one_hop": [],
|
| 63 |
+
"acc_vp_two_hop": [],
|
| 64 |
+
"acc_fp_one_hop": [],
|
| 65 |
+
"acc_fp_two_hop": [],
|
| 66 |
+
"acc_vp_old": [],
|
| 67 |
+
"acc_vp_new": [],
|
| 68 |
+
"acc_fp_old": [],
|
| 69 |
+
"acc_fp_new": [],
|
| 70 |
+
"acc_politics": [],
|
| 71 |
+
"acc_sports": [],
|
| 72 |
+
"acc_entertainment": [],
|
| 73 |
+
"acc_weather": [],
|
| 74 |
+
"acc_world": [],
|
| 75 |
+
"acc_economy": [],
|
| 76 |
+
"acc_society": [],
|
| 77 |
+
"acc_it_science": [],
|
| 78 |
+
"acc_life_culture": [],
|
| 79 |
+
"acc_unknown": [],
|
| 80 |
+
"total_questions": [],
|
| 81 |
+
"evaluation_date": [],
|
| 82 |
+
"evaluation_mode": [],
|
| 83 |
+
})
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _normalize_leaderboard_df(df: pd.DataFrame) -> pd.DataFrame:
|
| 87 |
+
"""
|
| 88 |
+
리더보드 DF를 스키마/정렬/컬럼 순서 기준에 맞춰 정규화한다.
|
| 89 |
+
(기존 load_leaderboard_data의 로직을 함수로 분리)
|
| 90 |
+
"""
|
| 91 |
+
if df is None or df.empty:
|
| 92 |
+
return _init_empty_leaderboard_df()
|
| 93 |
+
|
| 94 |
+
df = df.copy()
|
| 95 |
+
|
| 96 |
+
# evaluation_mode가 없으면 추가
|
| 97 |
+
if "evaluation_mode" not in df.columns:
|
| 98 |
+
df["evaluation_mode"] = "Unknown"
|
| 99 |
+
|
| 100 |
+
# 텍스트 컬럼 보정
|
| 101 |
+
text_columns = ["model", "description"]
|
| 102 |
+
for col in text_columns:
|
| 103 |
+
if col not in df.columns:
|
| 104 |
+
df[col] = pd.Series(dtype="object")
|
| 105 |
+
|
| 106 |
+
# 상세 분석 컬럼 없으면 추가
|
| 107 |
+
detailed_columns = [
|
| 108 |
+
"acc_test", "acc_dev", "acc_vp", "acc_fp", "acc_vp_one_hop", "acc_vp_two_hop",
|
| 109 |
+
"acc_fp_one_hop", "acc_fp_two_hop", "acc_vp_old", "acc_vp_new", "acc_fp_old", "acc_fp_new",
|
| 110 |
+
]
|
| 111 |
+
for col in detailed_columns:
|
| 112 |
+
if col not in df.columns:
|
| 113 |
+
df[col] = 0.0
|
| 114 |
+
|
| 115 |
+
# 도메인별 정확도 컬럼 없으면 추가
|
| 116 |
+
domain_columns = [
|
| 117 |
+
"acc_politics", "acc_sports", "acc_entertainment",
|
| 118 |
+
"acc_weather", "acc_world", "acc_economy",
|
| 119 |
+
"acc_society", "acc_it_science", "acc_life_culture", "acc_unknown",
|
| 120 |
+
]
|
| 121 |
+
for col in domain_columns:
|
| 122 |
+
if col not in df.columns:
|
| 123 |
+
df[col] = 0.0
|
| 124 |
+
|
| 125 |
+
# accuracy 기준 정렬
|
| 126 |
+
if "accuracy" in df.columns and not df.empty:
|
| 127 |
+
df = df.sort_values("accuracy", ascending=False).reset_index(drop=True)
|
| 128 |
+
|
| 129 |
+
# 컬럼 순서 정렬 (rank 제외)
|
| 130 |
+
column_order = [
|
| 131 |
+
"id", "model", "description", "accuracy", "fast_changing_accuracy",
|
| 132 |
+
"slow_changing_accuracy", "never_changing_accuracy", "acc_vp", "acc_fp",
|
| 133 |
+
"acc_vp_one_hop", "acc_vp_two_hop", "acc_fp_one_hop", "acc_fp_two_hop",
|
| 134 |
+
"acc_vp_old", "acc_vp_new", "acc_fp_old", "acc_fp_new",
|
| 135 |
+
"acc_politics", "acc_sports", "acc_entertainment", "acc_weather",
|
| 136 |
+
"acc_world", "acc_economy", "acc_society", "acc_it_science",
|
| 137 |
+
"acc_life_culture", "acc_unknown", "total_questions",
|
| 138 |
+
"evaluation_date", "evaluation_mode",
|
| 139 |
+
]
|
| 140 |
+
available_columns = [col for col in column_order if col in df.columns]
|
| 141 |
+
df = df[available_columns]
|
| 142 |
+
|
| 143 |
+
return df
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def _load_local_leaderboard_df() -> pd.DataFrame:
|
| 147 |
+
"""로컬 CSV에서 리더보드 로드 (없으면 빈 ��키마)."""
|
| 148 |
+
data_path = _get_local_leaderboard_path()
|
| 149 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
df = pd.read_csv(data_path)
|
| 151 |
+
return _normalize_leaderboard_df(df)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
except FileNotFoundError:
|
| 153 |
+
return _init_empty_leaderboard_df()
|
| 154 |
+
except Exception as e:
|
| 155 |
+
print(f"⚠️ 로컬 리더보드 로드 실패: {e}")
|
| 156 |
+
return _init_empty_leaderboard_df()
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
# -------------------------
|
| 160 |
+
# HF 연동 헬퍼
|
| 161 |
+
# -------------------------
|
| 162 |
+
|
| 163 |
+
def _can_use_hf() -> bool:
|
| 164 |
+
"""HF 연동이 가능한 상태인지 여부 (Config 기반)."""
|
| 165 |
+
if not UPLOAD_LEADERBOARD_TO_HF:
|
| 166 |
+
return False
|
| 167 |
+
if not HF_REPO_ID or not HF_ADMIN_TOKEN:
|
| 168 |
+
# 설정이 없으면 HF는 건너뜀
|
| 169 |
+
return False
|
| 170 |
+
return True
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def _load_leaderboard_from_hf(retries: int = 3, delay: float = 1.0) -> Optional[pd.DataFrame]:
|
| 174 |
+
"""
|
| 175 |
+
HF dataset에서 리더보드 CSV를 다운로드하여 DataFrame으로 반환.
|
| 176 |
+
실패 시 None 반환. 재시도 로직 포함.
|
| 177 |
+
"""
|
| 178 |
+
if not _can_use_hf():
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
last_err: Optional[Exception] = None
|
| 182 |
+
for attempt in range(1, retries + 1):
|
| 183 |
+
try:
|
| 184 |
+
with tempfile.TemporaryDirectory() as tmpdir:
|
| 185 |
+
file_path = hf_hub_download(
|
| 186 |
+
repo_id=HF_REPO_ID,
|
| 187 |
+
filename=HF_LEADERBOARD_FILENAME,
|
| 188 |
+
repo_type="dataset",
|
| 189 |
+
local_dir=tmpdir,
|
| 190 |
+
token=HF_ADMIN_TOKEN,
|
| 191 |
+
)
|
| 192 |
+
df = pd.read_csv(file_path)
|
| 193 |
+
return _normalize_leaderboard_df(df)
|
| 194 |
+
except Exception as e:
|
| 195 |
+
last_err = e
|
| 196 |
+
print(f"⚠️ HF 리더보드 로드 실패 (시도 {attempt}/{retries}): {e}")
|
| 197 |
+
if attempt < retries:
|
| 198 |
+
time.sleep(delay)
|
| 199 |
+
delay *= 2
|
| 200 |
+
print("❌ HF 리더보드 로드 재시도 모두 실패")
|
| 201 |
+
return None
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def _save_leaderboard_to_hf(df: pd.DataFrame, retries: int = 3, delay: float = 1.0) -> bool:
|
| 205 |
+
"""
|
| 206 |
+
HF dataset에 리더보드 CSV 업로드.
|
| 207 |
+
실패 시 False 반환. 재시도 로직 포함.
|
| 208 |
+
"""
|
| 209 |
+
if not _can_use_hf():
|
| 210 |
+
return False
|
| 211 |
+
|
| 212 |
+
df = _normalize_leaderboard_df(df)
|
| 213 |
+
|
| 214 |
+
last_err: Optional[Exception] = None
|
| 215 |
+
for attempt in range(1, retries + 1):
|
| 216 |
+
try:
|
| 217 |
+
with tempfile.NamedTemporaryFile(
|
| 218 |
+
mode="w",
|
| 219 |
+
encoding="utf-8",
|
| 220 |
+
suffix=".csv",
|
| 221 |
+
delete=False,
|
| 222 |
+
) as tmpfile:
|
| 223 |
+
df.to_csv(tmpfile.name, index=False)
|
| 224 |
+
tmp_path = tmpfile.name
|
| 225 |
+
|
| 226 |
+
hf_api.upload_file(
|
| 227 |
+
path_or_fileobj=tmp_path,
|
| 228 |
+
path_in_repo=HF_LEADERBOARD_FILENAME,
|
| 229 |
+
repo_id=HF_REPO_ID,
|
| 230 |
+
repo_type="dataset",
|
| 231 |
+
token=HF_ADMIN_TOKEN,
|
| 232 |
+
commit_message="Update leaderboard results",
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
os.unlink(tmp_path)
|
| 236 |
+
return True
|
| 237 |
+
|
| 238 |
+
except Exception as e:
|
| 239 |
+
last_err = e
|
| 240 |
+
print(f"⚠️ HF 리더보드 업로드 실패 (시도 {attempt}/{retries}): {e}")
|
| 241 |
+
if attempt < retries:
|
| 242 |
+
time.sleep(delay)
|
| 243 |
+
delay *= 2
|
| 244 |
+
|
| 245 |
+
print(f"❌ HF 리더보드 업로드 재시도 모두 실패: {last_err}")
|
| 246 |
+
return False
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
# -------------------------
|
| 250 |
+
# 공개 API: 로드 / 추가
|
| 251 |
+
# -------------------------
|
| 252 |
+
|
| 253 |
+
def load_leaderboard_data() -> pd.DataFrame:
|
| 254 |
+
"""
|
| 255 |
+
리더보드 데이터 로드.
|
| 256 |
+
|
| 257 |
+
동작 우선순위:
|
| 258 |
+
1) Config.UPLOAD_LEADERBOARD_TO_HF == True && HF 설정 OK:
|
| 259 |
+
- HF에서 최신 리더보드 로드 시도
|
| 260 |
+
- 성공 시: 그 내용을 로컬 CSV에 덮어쓴 뒤 반환
|
| 261 |
+
- 실패 시: 로컬 CSV를 사용 (없으면 빈 스키마)
|
| 262 |
+
2) 그 외:
|
| 263 |
+
- 로컬 CSV만 사용 (없으면 빈 스키마)
|
| 264 |
+
"""
|
| 265 |
+
data_path = _get_local_leaderboard_path()
|
| 266 |
+
lock_path = data_path + ".lock"
|
| 267 |
+
|
| 268 |
+
# HF를 사용할 수 있는 경우에만 HF 우선 시도
|
| 269 |
+
if _can_use_hf():
|
| 270 |
+
with file_lock(lock_path):
|
| 271 |
+
hf_df = _load_leaderboard_from_hf()
|
| 272 |
+
if hf_df is not None:
|
| 273 |
+
# HF가 소스 오브 트루스: 로컬 CSV도 HF 기준으로 동기화
|
| 274 |
+
try:
|
| 275 |
+
os.makedirs(os.path.dirname(data_path), exist_ok=True)
|
| 276 |
+
hf_df.to_csv(data_path, index=False)
|
| 277 |
+
except Exception as e:
|
| 278 |
+
print(f"⚠️ 로컬 리더보드 동기화 실패: {e}")
|
| 279 |
+
return hf_df
|
| 280 |
+
|
| 281 |
+
# HF에서 못 가져오면 로컬로 폴백
|
| 282 |
+
local_df = _load_local_leaderboard_df()
|
| 283 |
+
return local_df
|
| 284 |
+
|
| 285 |
+
# HF를 사용하지 않는 경우: 로컬만
|
| 286 |
+
return _load_local_leaderboard_df()
|
| 287 |
+
|
| 288 |
|
| 289 |
def append_to_leaderboard_data(new_data_list):
|
| 290 |
+
"""
|
| 291 |
+
리더보드 데이터에 새로운 결과 추가 (파일 잠금 사용).
|
| 292 |
+
|
| 293 |
+
- 항상 로컬 CSV를 업데이트
|
| 294 |
+
- Config.UPLOAD_LEADERBOARD_TO_HF == True 이고 HF 설정이 유효하면,
|
| 295 |
+
업데이트된 전체 DF를 HF에도 업로드 (재시도 포함).
|
| 296 |
+
"""
|
| 297 |
+
data_path = _get_local_leaderboard_path()
|
| 298 |
+
lock_path = data_path + ".lock"
|
| 299 |
+
|
| 300 |
+
with file_lock(lock_path):
|
| 301 |
+
# 1) 로컬 기존 데이터 로드
|
| 302 |
if os.path.exists(data_path):
|
| 303 |
+
try:
|
| 304 |
+
existing_df = pd.read_csv(data_path)
|
| 305 |
+
except Exception as e:
|
| 306 |
+
print(f"⚠️ 로컬 리더보드 읽��� 실패, 빈 스키마로 진행: {e}")
|
| 307 |
+
existing_df = _init_empty_leaderboard_df()
|
| 308 |
else:
|
| 309 |
+
existing_df = _init_empty_leaderboard_df()
|
| 310 |
+
|
| 311 |
+
existing_df = _normalize_leaderboard_df(existing_df)
|
| 312 |
+
|
| 313 |
+
# 2) 새로운 데이터 추가
|
| 314 |
new_df = pd.DataFrame(new_data_list)
|
| 315 |
+
if not new_df.empty:
|
| 316 |
+
new_df = _normalize_leaderboard_df(new_df)
|
| 317 |
|
|
|
|
| 318 |
frames_to_concat = []
|
| 319 |
+
if not existing_df.empty:
|
| 320 |
frames_to_concat.append(existing_df)
|
| 321 |
+
if not new_df.empty:
|
| 322 |
frames_to_concat.append(new_df)
|
| 323 |
+
|
| 324 |
if len(frames_to_concat) == 0:
|
|
|
|
| 325 |
combined_df = existing_df.copy()
|
| 326 |
elif len(frames_to_concat) == 1:
|
| 327 |
combined_df = frames_to_concat[0].copy()
|
| 328 |
else:
|
| 329 |
combined_df = pd.concat(frames_to_concat, ignore_index=True)
|
| 330 |
+
|
| 331 |
+
combined_df = _normalize_leaderboard_df(combined_df)
|
| 332 |
+
|
| 333 |
+
# 3) 로컬 저장
|
| 334 |
+
try:
|
| 335 |
+
os.makedirs(os.path.dirname(data_path), exist_ok=True)
|
| 336 |
+
combined_df.to_csv(data_path, index=False)
|
| 337 |
+
except Exception as e:
|
| 338 |
+
print(f"❌ 로컬 리더보드 저장 실패: {e}")
|
| 339 |
+
|
| 340 |
+
# 4) HF에도 업로드 (옵션)
|
| 341 |
+
if _can_use_hf():
|
| 342 |
+
ok = _save_leaderboard_to_hf(combined_df)
|
| 343 |
+
if not ok:
|
| 344 |
+
print("⚠️ 리더보드 HF 업로드 실패 (로컬에는 저장됨)")
|
| 345 |
+
|
| 346 |
+
return combined_df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ui/leaderboard_tab.py
CHANGED
|
@@ -6,7 +6,7 @@
|
|
| 6 |
|
| 7 |
import gradio as gr
|
| 8 |
import pandas as pd
|
| 9 |
-
from src.leaderboard_manager import load_leaderboard_data
|
| 10 |
|
| 11 |
|
| 12 |
def create_leaderboard_tab():
|
|
@@ -91,6 +91,83 @@ def create_leaderboard_tab():
|
|
| 91 |
'acc_life_culture': 'Life/Culture',
|
| 92 |
'acc_unknown': 'Unknown'
|
| 93 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
def format_leaderboard(df: pd.DataFrame) -> pd.DataFrame:
|
| 96 |
"""리더보드에 노출할 컬럼 선택 및 헤더명 변환"""
|
|
@@ -122,6 +199,7 @@ def create_leaderboard_tab():
|
|
| 122 |
is_empty = relaxed_df.empty and strict_df.empty
|
| 123 |
return formatted_relaxed, formatted_strict, is_empty
|
| 124 |
|
|
|
|
| 125 |
leaderboard_data = load_leaderboard_data()
|
| 126 |
relaxed_initial, strict_initial, is_initial_empty = build_leaderboard_state(leaderboard_data)
|
| 127 |
|
|
@@ -166,7 +244,6 @@ def create_leaderboard_tab():
|
|
| 166 |
""")
|
| 167 |
|
| 168 |
|
| 169 |
-
|
| 170 |
# 통합 검색 필터 함수 (Relaxed와 Strict 모드 모두 필터링)
|
| 171 |
def filter_leaderboard_data(search_text):
|
| 172 |
"""Relaxed와 Strict 모드 리더보드 데이터 필터링 (CSV 기반)"""
|
|
@@ -216,7 +293,6 @@ def create_leaderboard_tab():
|
|
| 216 |
try:
|
| 217 |
all_df = load_leaderboard_data()
|
| 218 |
formatted_relaxed, formatted_strict, is_empty = build_leaderboard_state(all_df)
|
| 219 |
-
|
| 220 |
return formatted_relaxed, formatted_strict
|
| 221 |
except Exception as e:
|
| 222 |
print(f"❌ 리더보드 새로고침 실패: {e}")
|
|
@@ -227,3 +303,6 @@ def create_leaderboard_tab():
|
|
| 227 |
fn=refresh_leaderboard,
|
| 228 |
outputs=[relaxed_leaderboard_table, strict_leaderboard_table]
|
| 229 |
)
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
import gradio as gr
|
| 8 |
import pandas as pd
|
| 9 |
+
from src.leaderboard_manager import load_leaderboard_data
|
| 10 |
|
| 11 |
|
| 12 |
def create_leaderboard_tab():
|
|
|
|
| 91 |
'acc_life_culture': 'Life/Culture',
|
| 92 |
'acc_unknown': 'Unknown'
|
| 93 |
}
|
| 94 |
+
|
| 95 |
+
def prepare_display_data(df: pd.DataFrame, global_ranking=None) -> pd.DataFrame:
|
| 96 |
+
"""테이블 표시용 데이터 준비 (rank 계산 및 반올림 적용)"""
|
| 97 |
+
# 빈 데이터프레임인 경우 그대로 반환
|
| 98 |
+
if df is None or df.empty:
|
| 99 |
+
return df if df is not None else pd.DataFrame()
|
| 100 |
+
|
| 101 |
+
display_df = df.copy()
|
| 102 |
+
|
| 103 |
+
# model / description 기본값 처리
|
| 104 |
+
if "model" in display_df.columns:
|
| 105 |
+
display_df["model"] = display_df["model"].fillna("Anonymous Model")
|
| 106 |
+
display_df["model"] = display_df["model"].replace("", "Anonymous Model")
|
| 107 |
+
if "description" in display_df.columns:
|
| 108 |
+
display_df["description"] = (
|
| 109 |
+
display_df["description"]
|
| 110 |
+
.replace({None: "", pd.NA: ""})
|
| 111 |
+
.fillna("")
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
# rank 컬럼 추가
|
| 115 |
+
if "accuracy" in display_df.columns:
|
| 116 |
+
if global_ranking is not None:
|
| 117 |
+
# 외부에서 전체 랭킹 정보를 제공하는 경우
|
| 118 |
+
display_df["rank"] = display_df.index.map(global_ranking)
|
| 119 |
+
else:
|
| 120 |
+
# accuracy 기준으로 정렬하여 rank 계산
|
| 121 |
+
display_df = display_df.sort_values("accuracy", ascending=False).reset_index(
|
| 122 |
+
drop=True
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
def get_rank_display(rank: int) -> str:
|
| 126 |
+
if rank == 1:
|
| 127 |
+
return "🥇"
|
| 128 |
+
elif rank == 2:
|
| 129 |
+
return "🥈"
|
| 130 |
+
elif rank == 3:
|
| 131 |
+
return "🥉"
|
| 132 |
+
else:
|
| 133 |
+
return str(rank)
|
| 134 |
+
|
| 135 |
+
display_df["rank"] = [get_rank_display(i + 1) for i in range(len(display_df))]
|
| 136 |
+
|
| 137 |
+
# 숫자 컬럼들을 소숫점 2번째에서 반올림 (표시용으로만)
|
| 138 |
+
numeric_columns = [
|
| 139 |
+
"accuracy",
|
| 140 |
+
"fast_changing_accuracy",
|
| 141 |
+
"slow_changing_accuracy",
|
| 142 |
+
"never_changing_accuracy",
|
| 143 |
+
"acc_vp",
|
| 144 |
+
"acc_fp",
|
| 145 |
+
"acc_vp_one_hop",
|
| 146 |
+
"acc_vp_two_hop",
|
| 147 |
+
"acc_fp_one_hop",
|
| 148 |
+
"acc_fp_two_hop",
|
| 149 |
+
"acc_vp_old",
|
| 150 |
+
"acc_vp_new",
|
| 151 |
+
"acc_fp_old",
|
| 152 |
+
"acc_fp_new",
|
| 153 |
+
"acc_politics",
|
| 154 |
+
"acc_sports",
|
| 155 |
+
"acc_entertainment",
|
| 156 |
+
"acc_weather",
|
| 157 |
+
"acc_world",
|
| 158 |
+
"acc_economy",
|
| 159 |
+
"acc_society",
|
| 160 |
+
"acc_it_science",
|
| 161 |
+
"acc_life_culture",
|
| 162 |
+
"acc_unknown",
|
| 163 |
+
]
|
| 164 |
+
|
| 165 |
+
for col in numeric_columns:
|
| 166 |
+
if col in display_df.columns:
|
| 167 |
+
display_df[col] = display_df[col].round(2)
|
| 168 |
+
|
| 169 |
+
return display_df
|
| 170 |
+
|
| 171 |
|
| 172 |
def format_leaderboard(df: pd.DataFrame) -> pd.DataFrame:
|
| 173 |
"""리더보드에 노출할 컬럼 선택 및 헤더명 변환"""
|
|
|
|
| 199 |
is_empty = relaxed_df.empty and strict_df.empty
|
| 200 |
return formatted_relaxed, formatted_strict, is_empty
|
| 201 |
|
| 202 |
+
# ✅ 초기 값 (앱 빌드 시점 기준)
|
| 203 |
leaderboard_data = load_leaderboard_data()
|
| 204 |
relaxed_initial, strict_initial, is_initial_empty = build_leaderboard_state(leaderboard_data)
|
| 205 |
|
|
|
|
| 244 |
""")
|
| 245 |
|
| 246 |
|
|
|
|
| 247 |
# 통합 검색 필터 함수 (Relaxed와 Strict 모드 모두 필터링)
|
| 248 |
def filter_leaderboard_data(search_text):
|
| 249 |
"""Relaxed와 Strict 모드 리더보드 데이터 필터링 (CSV 기반)"""
|
|
|
|
| 293 |
try:
|
| 294 |
all_df = load_leaderboard_data()
|
| 295 |
formatted_relaxed, formatted_strict, is_empty = build_leaderboard_state(all_df)
|
|
|
|
| 296 |
return formatted_relaxed, formatted_strict
|
| 297 |
except Exception as e:
|
| 298 |
print(f"❌ 리더보드 새로고침 실패: {e}")
|
|
|
|
| 303 |
fn=refresh_leaderboard,
|
| 304 |
outputs=[relaxed_leaderboard_table, strict_leaderboard_table]
|
| 305 |
)
|
| 306 |
+
|
| 307 |
+
# ✅ app.py에서 초기 로딩 시에도 재사용할 수 있도록 return
|
| 308 |
+
return relaxed_leaderboard_table, strict_leaderboard_table, refresh_leaderboard
|