Spaces:
Running
Running
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| import os | |
| from pathlib import Path | |
| from app.core.competitions import COMPETITIONS | |
| DEFAULT_SPORT_KEYS = tuple(COMPETITIONS.keys()) | |
| MODEL_VERSION = "2.2-precision" | |
| VALID_ODDS_REGIONS = frozenset({"us", "us2", "uk", "au", "eu"}) | |
| def _default_data_dir() -> Path: | |
| """Prefer the persistent volume mounted by Hugging Face Spaces.""" | |
| configured = os.getenv("DATA_DIR", "").strip() | |
| if configured: | |
| return Path(configured).expanduser() | |
| persistent_root = Path("/data") | |
| if persistent_root.is_dir(): | |
| return persistent_root / "safe-bet-ai" | |
| return Path("/app/data") if Path("/app").is_dir() else Path("./data") | |
| def _int(name: str, default: int, minimum: int, maximum: int) -> int: | |
| try: | |
| value = int(os.getenv(name, str(default))) | |
| except ValueError: | |
| value = default | |
| return max(minimum, min(maximum, value)) | |
| def _float(name: str, default: float, minimum: float, maximum: float) -> float: | |
| try: | |
| value = float(os.getenv(name, str(default))) | |
| except ValueError: | |
| value = default | |
| return max(minimum, min(maximum, value)) | |
| def _odds_regions() -> str: | |
| raw = os.getenv("ODDS_REGIONS") | |
| if raw is None: | |
| return "eu" | |
| unique = list(dict.fromkeys( | |
| part.strip().lower() for part in raw.split(",") if part.strip() | |
| )) | |
| if not unique or any(region not in VALID_ODDS_REGIONS for region in unique): | |
| return "" | |
| return ",".join(unique) | |
| class Settings: | |
| football_data_token: str = field(default_factory=lambda: os.getenv("FOOTBALL_DATA_TOKEN", "").strip()) | |
| odds_api_key: str = field(default_factory=lambda: os.getenv("ODDS_API_KEY", "").strip()) | |
| cron_secret: str = field(default_factory=lambda: os.getenv("CRON_SECRET", "").strip()) | |
| odds_regions: str = field(default_factory=_odds_regions) | |
| history_days: int = field(default_factory=lambda: _int("HISTORY_DAYS", 240, 120, 730)) | |
| scan_horizon_hours: int = field(default_factory=lambda: _int("SCAN_HORIZON_HOURS", 36, 6, 96)) | |
| min_scan_interval_minutes: int = field(default_factory=lambda: _int("MIN_SCAN_INTERVAL_MINUTES", 180, 15, 1440)) | |
| min_safe_score: float = field(default_factory=lambda: _float("MIN_SAFE_SCORE", 76.0, 55.0, 95.0)) | |
| min_probability: float = field(default_factory=lambda: _float("MIN_PROBABILITY", 0.64, 0.55, 0.90)) | |
| min_conservative_probability: float = field(default_factory=lambda: _float("MIN_CONSERVATIVE_PROBABILITY", 0.57, 0.50, 0.85)) | |
| min_bookmakers: int = field(default_factory=lambda: _int("MIN_BOOKMAKERS", 3, 1, 10)) | |
| min_name_score: float = field(default_factory=lambda: _float("MIN_NAME_SCORE", 82.0, 70.0, 98.0)) | |
| top_picks_limit: int = field(default_factory=lambda: _int("TOP_PICKS_LIMIT", 10, 1, 20)) | |
| tz_display: str = field(default_factory=lambda: os.getenv("TZ_DISPLAY", "America/Sao_Paulo").strip() or "America/Sao_Paulo") | |
| hf_token: str = field(default_factory=lambda: (os.getenv("HF_WRITE_TOKEN", "").strip() or os.getenv("HF_TOKEN", "").strip())) | |
| hf_dataset_repo: str = field(default_factory=lambda: os.getenv("HF_DATASET_REPO", "").strip()) | |
| data_dir: Path = field(default_factory=_default_data_dir) | |
| def sport_keys(self) -> tuple[str, ...]: | |
| raw = os.getenv("ODDS_SPORT_KEYS", "").strip() | |
| if not raw: | |
| return DEFAULT_SPORT_KEYS | |
| keys = tuple(dict.fromkeys(x.strip() for x in raw.split(",") if x.strip())) | |
| # Fail closed when any requested league cannot be joined safely to | |
| # football-data.org history; silently falling back would waste quota. | |
| if not keys or any(key not in COMPETITIONS for key in keys): | |
| return () | |
| return keys | |
| def required_ready(self) -> bool: | |
| return bool( | |
| self.football_data_token | |
| and self.odds_api_key | |
| and self.cron_secret | |
| and self.odds_regions | |
| and self.sport_keys | |
| ) | |
| settings = Settings() | |
| settings.data_dir.mkdir(parents=True, exist_ok=True) | |