Spaces:
Running
Running
| """ | |
| ์ฌ์ฉ์ ์ ์ถ ์ถ์ ๋ชจ๋ | |
| ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ user_id๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ฃจ 3๋ฒ ์ ํ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. | |
| ์ ์ถ ์ ๋ณด๋ ๋ณ๋์ HuggingFace repository์์ ๊ด๋ฆฌ๋ฉ๋๋ค. | |
| """ | |
| import os | |
| import json | |
| import pandas as pd | |
| import tempfile | |
| from datetime import datetime | |
| from typing import Dict, List, Optional, Tuple | |
| from huggingface_hub import hf_hub_download, login, HfApi | |
| import pytz | |
| from src.utils import file_lock, get_current_date_str, get_current_datetime_str | |
| # ํ๊ตญ ์๊ฐ๋ ์ค์ | |
| KOREA_TZ = pytz.timezone('Asia/Seoul') | |
| class SubmissionTracker: | |
| """์ฌ์ฉ์ ์ ์ถ ์ถ์ ํด๋์ค - HuggingFace Repository ๊ธฐ๋ฐ""" | |
| def __init__( | |
| self, | |
| filename: str = "user_submissions.json", | |
| ): | |
| """ | |
| Args: | |
| filename: ์ ์ถ ๊ธฐ๋ก ํ์ผ๋ช | |
| """ | |
| # ํ๊ฒฝ๋ณ์์์ ์ค์ ๊ฐ์ ธ์ค๊ธฐ | |
| self.repo_id = os.getenv("SUBMISSION_TRACKER_REPO_ID") | |
| self.admin_token = os.getenv("HF_TOKEN") | |
| self.filename = filename | |
| if not self.repo_id: | |
| raise ValueError( | |
| "SUBMISSION_TRACKER_REPO_ID ํ๊ฒฝ๋ณ์๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค. " | |
| ) | |
| if not self.admin_token: | |
| raise ValueError( | |
| "HuggingFace Admin ํ ํฐ์ด ํ์ํฉ๋๋ค. " | |
| "HF_TOKEN ํ๊ฒฝ๋ณ์๋ฅผ ์ค์ ํ์ธ์." | |
| ) | |
| # HuggingFace API ์ด๊ธฐํ | |
| self.api = HfApi() | |
| try: | |
| # ๊ด๋ฆฌ์ ํ ํฐ์ผ๋ก ๋ก๊ทธ์ธ (dataset read/write ์ฉ) | |
| login(token=self.admin_token) | |
| except Exception as e: | |
| print(f"โ HuggingFace ๋ก๊ทธ์ธ ์คํจ: {e}") | |
| raise | |
| # ์ ์ถ ๊ธฐ๋ก ๋ก๋ | |
| self.submissions: Dict = self.load_submissions() | |
| def load_submissions(self) -> Dict: | |
| """HuggingFace repository์์ ์ ์ถ ๊ธฐ๋ก ๋ก๋""" | |
| try: | |
| # ์์ ๋๋ ํ ๋ฆฌ์ ํ์ผ ๋ค์ด๋ก๋ | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| file_path = hf_hub_download( | |
| repo_id=self.repo_id, | |
| filename=self.filename, | |
| local_dir=temp_dir, | |
| repo_type="dataset", | |
| token=self.admin_token, | |
| ) | |
| # JSON ํ์ผ ๋ก๋ | |
| with open(file_path, "r", encoding="utf-8") as f: | |
| submissions = json.load(f) | |
| return submissions | |
| except Exception as e: | |
| print(f"โ ๏ธ ์ ์ถ ๊ธฐ๋ก ๋ก๋ ์คํจ (์๋ก ์์): {e}") | |
| return {} | |
| def get_today_submissions(self, user_id: str) -> List[Dict]: | |
| """์ค๋ ์ฌ์ฉ์์ ์ ์ถ ๊ธฐ๋ก ๊ฐ์ ธ์ค๊ธฐ""" | |
| if not user_id: | |
| return [] | |
| today = get_current_date_str() | |
| user_submissions = self.submissions.get(user_id, {}) | |
| return user_submissions.get(today, []) | |
| def can_submit( | |
| self, | |
| user_id: str, | |
| submissions_data: Optional[Dict] = None, | |
| ) -> Tuple[bool, str, int]: | |
| """ | |
| ์ฌ์ฉ์๊ฐ ์ ์ถํ ์ ์๋์ง ํ์ธ. | |
| Args: | |
| user_id: ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ๊ณ ์ ID (HF ๊ณ์ ID ๋ฑ) | |
| submissions_data: ๊ฒ์ฌ์ ์ฌ์ฉํ ์ ์ถ ๋ฐ์ดํฐ(ํ ์คํธ/๋ฝ ๋ด๋ถ ์ฌ๊ฒ์ฌ์ฉ). | |
| None์ด๋ฉด self.submissions ์ฌ์ฉ. | |
| """ | |
| if not user_id: | |
| raise ValueError("โ HuggingFace ๋ก๊ทธ์ธ ์ํ์์๋ง ์ ์ถ ๊ฐ๋ฅํฉ๋๋ค. ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ํ์ธํด์ฃผ์ธ์.") | |
| data = submissions_data if submissions_data is not None else self.submissions | |
| today = get_current_date_str() | |
| today_submissions = data.get(user_id, {}).get(today, []) | |
| successful_count = len([s for s in today_submissions if s.get("success", False)]) | |
| if successful_count >= 3: | |
| raise Exception("โ ์ค๋ ์ ์ถ ํ๋๋ฅผ ์ด๊ณผํ์ต๋๋ค. ๋ด์ผ ๋ค์ ์๋ํด์ฃผ์ธ์.") | |
| remaining = 3 - successful_count | |
| return True, f"โ ์ ์ถ ๊ฐ๋ฅํฉ๋๋ค. (์ค๋ {successful_count}/3ํ ์ฌ์ฉ, {remaining}ํ ๋จ์)", remaining | |
| def record_submission( | |
| self, | |
| user_id: str, | |
| submitter_name: str, | |
| file_name: str, | |
| success: bool, | |
| error_message: Optional[str] = None, | |
| submit_model: Optional[str] = None, | |
| submit_description: Optional[str] = None, | |
| ) -> bool: | |
| """ | |
| ์ ์ถ ๊ธฐ๋ก ์ถ๊ฐ (ํ์ผ ์ ๊ธ์ผ๋ก ๋ณดํธ) | |
| Args: | |
| user_id: ๋ก๊ทธ์ธํ ์ฌ์ฉ์์ ๊ณ ์ ID (HF ๊ณ์ ID ๋ฑ) | |
| """ | |
| if not user_id: | |
| raise ValueError("โ HuggingFace ๋ก๊ทธ์ธ ์ํ์์๋ง ์ ์ถ ๊ฐ๋ฅํฉ๋๋ค. ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ํ์ธํด์ฃผ์ธ์.") | |
| # ์ ๊ธ ํ์ผ ๊ฒฝ๋ก ์์ฑ | |
| lock_file_path = tempfile.gettempdir() + f'/{self.repo_id.replace("/", "_")}.lock' | |
| # ํ์ผ ์ ๊ธ์ผ๋ก ์ ์ฒด ๊ณผ์ ์ atomicํ๊ฒ ๋ณดํธ | |
| with file_lock(lock_file_path): | |
| try: | |
| # ์ต์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ก๋ (๋ค๋ฅธ ํ๋ก์ธ์ค์์ ์ ๋ฐ์ดํธํ์ ์ ์์) | |
| latest_submissions = self.load_submissions() | |
| # Lock ๋ด๋ถ์์ ์ต์ ๋ฐ์ดํฐ ๊ธฐ์ค์ผ๋ก ์ ์ถ ๊ฐ๋ฅ ์ฌ๋ถ ์ฌํ์ธ | |
| try: | |
| self.can_submit( | |
| user_id=user_id, | |
| submissions_data=latest_submissions, | |
| ) | |
| except Exception as e: | |
| # ์ ์ถ ์ ํ ์ด๊ณผ ์ | |
| print(f"์ ์ถ ์ ํ ์ด๊ณผ: {e}") | |
| # ๋ฉ๋ชจ๋ฆฌ๋ง ์ต์ ์ผ๋ก ๋ง์ถ๊ณ ์ ์ฅํ์ง ์์ | |
| self.submissions = latest_submissions | |
| return False | |
| # ์๋ก์ด ์ ์ถ ๊ธฐ๋ก ์ถ๊ฐ | |
| current_datetime = get_current_datetime_str() | |
| if user_id not in latest_submissions: | |
| latest_submissions[user_id] = {} | |
| today = get_current_date_str() | |
| if today not in latest_submissions[user_id]: | |
| latest_submissions[user_id][today] = [] | |
| submission_record = { | |
| "timestamp": current_datetime, | |
| "submitter_name": submitter_name, | |
| "file_name": file_name, | |
| "success": success, | |
| "error_message": error_message, | |
| "submit_model": submit_model, | |
| "submit_description": submit_description, | |
| } | |
| latest_submissions[user_id][today].append(submission_record) | |
| # ๋ฉ๋ชจ๋ฆฌ ์ ๋ฐ์ดํธ | |
| self.submissions = latest_submissions | |
| # ์ ์ฅ | |
| return self._save_submissions_internal(latest_submissions) | |
| except Exception as e: | |
| print(f"โ ์ ์ถ ๊ธฐ๋ก ์ถ๊ฐ ์คํจ: {e}") | |
| return False | |
| def _save_submissions_internal(self, submissions_data: Dict) -> bool: | |
| """๋ด๋ถ ์ ์ฅ ํจ์ (lock์ ์ด๋ฏธ ํ๋๋ ์ํ)""" | |
| try: | |
| # ์์ ํ์ผ์ JSON ๋ฐ์ดํฐ ์ ์ฅ | |
| with tempfile.NamedTemporaryFile( | |
| mode="w", | |
| encoding="utf-8", | |
| suffix=".json", | |
| delete=False, | |
| ) as temp_file: | |
| json.dump(submissions_data, temp_file, ensure_ascii=False, indent=2) | |
| temp_file_path = temp_file.name | |
| # HuggingFace repository์ ํ์ผ ์ ๋ก๋ | |
| self.api.upload_file( | |
| path_or_fileobj=temp_file_path, | |
| path_in_repo=self.filename, | |
| repo_id=self.repo_id, | |
| repo_type="dataset", | |
| token=self.admin_token, | |
| commit_message=( | |
| "Update submission records - " | |
| f"{datetime.now(KOREA_TZ).strftime('%Y-%m-%d %H:%M:%S')}" | |
| ), | |
| ) | |
| # ์์ ํ์ผ ์ญ์ | |
| os.unlink(temp_file_path) | |
| return True | |
| except Exception as e: | |
| print(f"โ ์ ์ถ ๊ธฐ๋ก ์ ์ฅ ์คํจ: {e}") | |
| return False | |
| def get_user_submission_history(self, user_id: str, days: int = 7) -> Dict: | |
| """์ฌ์ฉ์์ ์ต๊ทผ ์ ์ถ ๊ธฐ๋ก ๊ฐ์ ธ์ค๊ธฐ""" | |
| if not user_id or user_id not in self.submissions: | |
| return {} | |
| user_submissions = self.submissions[user_id] | |
| today = datetime.now(KOREA_TZ).date() | |
| history: Dict[str, List[Dict]] = {} | |
| for i in range(days): | |
| check_date = today - pd.Timedelta(days=i) | |
| date_str = check_date.strftime("%Y-%m-%d") | |
| if date_str in user_submissions: | |
| history[date_str] = user_submissions[date_str] | |
| return history | |
| def get_submission_stats(self, user_id: str) -> Dict: | |
| """์ฌ์ฉ์ ์ ์ถ ํต๊ณ ๊ฐ์ ธ์ค๊ธฐ""" | |
| if not user_id: | |
| return {} | |
| today_submissions = self.get_today_submissions(user_id) | |
| successful_today_count = len([s for s in today_submissions if s.get("success", False)]) | |
| history = self.get_user_submission_history(user_id, 7) | |
| # ํต๊ณ ๊ณ์ฐ | |
| total_submissions = sum(len(day_submissions) for day_submissions in history.values()) | |
| successful_submissions = sum( | |
| len([s for s in day_submissions if s.get("success", False)]) | |
| for day_submissions in history.values() | |
| ) | |
| failed_submissions = total_submissions - successful_submissions | |
| return { | |
| "today_count": len(today_submissions), | |
| "today_remaining": max(0, 3 - successful_today_count), | |
| "week_total": total_submissions, | |
| "week_successful": successful_submissions, | |
| "week_failed": failed_submissions, | |
| "history": history, | |
| } | |
| def cleanup_old_records(self, days_to_keep: int = 30) -> int: | |
| """์ค๋๋ ์ ์ถ ๊ธฐ๋ก ์ ๋ฆฌ (ํ์ผ ์ ๊ธ ์ฌ์ฉ)""" | |
| # ์ ๊ธ ํ์ผ ๊ฒฝ๋ก ์์ฑ | |
| lock_file_path = tempfile.gettempdir() + f'/{self.repo_id.replace("/", "_")}.lock' | |
| # ํ์ผ ์ ๊ธ์ผ๋ก ์ ์ฒด ๊ณผ์ ์ atomicํ๊ฒ ๋ณดํธ | |
| with file_lock(lock_file_path): | |
| try: | |
| # ์ต์ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ๋ก๋ | |
| latest_submissions = self.load_submissions() | |
| cutoff_date = datetime.now(KOREA_TZ) - pd.Timedelta(days=days_to_keep) | |
| cutoff_str = cutoff_date.strftime("%Y-%m-%d") | |
| cleaned_count = 0 | |
| for uid in list(latest_submissions.keys()): | |
| user_submissions = latest_submissions[uid] | |
| for date_str in list(user_submissions.keys()): | |
| if date_str < cutoff_str: | |
| del user_submissions[date_str] | |
| cleaned_count += 1 | |
| # ๋น ์ฌ์ฉ์ ๊ธฐ๋ก ์ ๊ฑฐ | |
| if not user_submissions: | |
| del latest_submissions[uid] | |
| # ๋ฉ๋ชจ๋ฆฌ ์ ๋ฐ์ดํธ | |
| self.submissions = latest_submissions | |
| if cleaned_count > 0: | |
| if self._save_submissions_internal(latest_submissions): | |
| print(f"๐งน {cleaned_count}๊ฐ์ ์ค๋๋ ์ ์ถ ๊ธฐ๋ก์ ์ ๋ฆฌํ์ต๋๋ค.") | |
| else: | |
| print(f"โ ๏ธ {cleaned_count}๊ฐ์ ์ค๋๋ ์ ์ถ ๊ธฐ๋ก์ ์ ๋ฆฌํ์ง๋ง ์ ์ฅ์ ์คํจํ์ต๋๋ค.") | |
| return cleaned_count | |
| except Exception as e: | |
| print(f"โ ์ค๋๋ ๊ธฐ๋ก ์ ๋ฆฌ ์คํจ: {e}") | |
| return 0 | |
| def get_submission_tracker() -> Optional[SubmissionTracker]: | |
| """SubmissionTracker ์ธ์คํด์ค ๋ฐํ""" | |
| try: | |
| return SubmissionTracker() | |
| except Exception as e: | |
| print(f"โ SubmissionTracker ์ด๊ธฐํ ์คํจ: {e}") | |
| return None | |