import os import shutil from datetime import datetime from typing import List import gradio as gr import pandas as pd from huggingface_hub import snapshot_download, upload_file from gradio_leaderboard import Leaderboard # 프로젝트 구조에 맞춰 import (src.*) from src.envs import RESULTS_REPO, EVAL_RESULTS_PATH from src.grader import grade # grade(submission_df, team_id) -> (score_df, report_dir) # --------------------------- # 팀 비밀번호 매핑 (요청 반영) # --------------------------- TEAM_PWD_MAP = { "02Aug#29112#h": "JN_HACK13", "02Aug!86113!g": "JN_HACK14", "02Aug#33114#h": "JN_HACK15", } # 저장 파일명 (리더보드 집계) HISTORY_NAME = "graded_results.csv" HISTORY_PATH = os.path.join(EVAL_RESULTS_PATH, HISTORY_NAME) # --------------------------- # 초기화: Space 재시작 대비 # --------------------------- def init_cache() -> pd.DataFrame: """ - RESULTS_REPO 스냅샷을 EVAL_RESULTS_PATH로 다운로드(동기화) - graded_results.csv 로드 (없으면 빈 DF 생성) """ os.makedirs(EVAL_RESULTS_PATH, exist_ok=True) try: snapshot_download( repo_id=RESULTS_REPO, repo_type="dataset", local_dir=EVAL_RESULTS_PATH, tqdm_class=None, etag_timeout=30, token=os.environ.get("HF_TOKEN"), ) except Exception as e: print(f"[WARN] snapshot_download failed: {e}") if os.path.exists(HISTORY_PATH): try: df = pd.read_csv(HISTORY_PATH) except Exception as e: print(f"[WARN] failed to read {HISTORY_NAME}: {e}") df = pd.DataFrame() else: df = pd.DataFrame() # 최소 스키마 보정 for col in ["TEAM", "TIMESTAMP"]: if col not in df.columns: df[col] = [] return df # 전역 히스토리 상태 (앱 부팅 시 로드) history_state = init_cache() def save_and_upload_history(history_df: pd.DataFrame) -> None: """ - 로컬 graded_results.csv 저장 - RESULTS_REPO에 덮어쓰기 업로드 """ history_df.to_csv(HISTORY_PATH, index=False) upload_file( path_or_fileobj=HISTORY_PATH, path_in_repo=HISTORY_NAME, repo_id=RESULTS_REPO, repo_type="dataset", token=os.environ.get("HF_TOKEN"), ) # --------------------------- # 채점 핸들러 # --------------------------- def grade_csv(passwd: str, file): global history_state if file is None: raise gr.Error("파일 업로드가 누락되었습니다.") team_id = TEAM_PWD_MAP.get((passwd or "").strip(), "UNKNOWN") # if not team_id: # raise gr.Error("비밀번호가 올바르지 않습니다.") # 제출 CSV 로드 submission_df = pd.read_csv(file.name) # 채점 및 리포트 생성 score_df, report_dir = grade(submission_df, team_id=team_id) # 메타 속성 부여 ts = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%SZ") score_df.insert(0, "TEAM", team_id) score_df.insert(1, "TIMESTAMP", ts) # 기존 히스토리 로드(없으면 빈 DF) if os.path.exists(HISTORY_PATH): try: saved_df = pd.read_csv(HISTORY_PATH) except Exception: saved_df = pd.DataFrame() else: saved_df = pd.DataFrame() # 최소 스키마 통일 for col in set(["TEAM", "TIMESTAMP"]) - set(saved_df.columns): saved_df[col] = [] # 히스토리 append merged_score = pd.concat([saved_df, score_df], ignore_index=True) history_state = merged_score.copy() # 전역 상태 갱신 # 로컬 저장 + Hub 업로드 save_and_upload_history(history_state) # 리포트 ZIP 생성 report_zip = f"report_{team_id}.zip" shutil.make_archive(f"report_{team_id}", "zip", report_dir) # 갤러리 이미지 파일 목록 image_files: List[str] = [ os.path.join(report_dir, f) for f in os.listdir(report_dir) if f.lower().endswith(".png") ] # UI 반환: 점수표, ZIP, 갤러리, 리더보드 갱신본 return score_df, report_zip, image_files, history_state def refresh_leaderboard(): """사용자 요청 시 Hub 재동기화 후 리더보드 갱신.""" global history_state history_state = init_cache() return history_state # --------------------------- # UI # --------------------------- with gr.Blocks() as demo: gr.Markdown("## Hackathon with ") with gr.Tabs(): with gr.Tab("평가 요청"): with gr.Row(): with gr.Column(): csv_input = gr.File(label="CSV 업로드", file_types=[".csv"]) password = gr.Textbox(label="팀 비밀번호", type="password", placeholder="팀별로 공지된 비밀번호") submit_button = gr.Button("평가 요청", variant="primary") df_output = gr.Dataframe(label="평가 지표 결과") with gr.Column(): report_output = gr.File(label="리포트 ZIP 다운로드", height="100px") image_gallery = gr.Gallery( label="Plant별 비교 그래프", show_label=True, height="auto" ) with gr.Tab("리더보드"): leaderboard_table = Leaderboard( value=history_state.rename(columns={"TOTAL": "TOTAL ⬆️"}), search_columns=["TEAM", "RMSE_AC", "RMSE_AC_SCALED", "NMAE_RANGE", "NMAE_MEAN", "TOTAL ⬆️"], hide_columns=[], # 숨길 게 있으면 여기에 추가 filter_columns=["TEAM"], label="Leaderboard", ) with gr.Row(): refresh_btn = gr.Button("리더보드 새로고침 (Hub 동기화)") # 새로고침: Hub → 로컬 재동기화 후 테이블 갱신 refresh_btn.click( fn=refresh_leaderboard, inputs=None, outputs=leaderboard_table ) # 업로드 → 채점 실행 submit_button.click( fn=grade_csv, inputs=[password, csv_input], outputs=[df_output, report_output, image_gallery, leaderboard_table], # 마지막은 리더보드 탭의 테이블에 연결 queue=True ) if __name__ == "__main__": demo.launch() # demo.launch(debug=True, show_error=True, enable_monitoring=True)