"""애플리케이션 설정 — 환경변수(.env)에서 로드. pydantic-settings 로 타입 검증. 누락 시 조용한 실패 없이 명확하게 동작. 시간 윈도우(투표 오픈/마감), 스케줄러 시각, 외부 연동 키를 모두 여기서 관리. """ from __future__ import annotations from functools import lru_cache from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", extra="ignore" ) # ── 데이터베이스 ───────────────────────────────────────── # docker-compose 의 db 서비스를 가리키는 기본값. 로컬 단독 실행 시 .env 로 덮어쓴다. database_url: str = ( "postgresql+asyncpg://triplepick:triplepick@db:5432/triplepick" ) # ── 일반 ──────────────────────────────────────────────── timezone: str = "Asia/Seoul" # 모든 경기 시각의 기준 (KST) cors_origins: str = "*" # 콤마구분. nginx 프록시 사용 시 동일 출처라 보통 불필요. public_origin: str = "http://localhost:8080" # 공유 딥링크 베이스 # ── 투표 윈도우 (도메인 규칙) ───────────────────────────── # 오픈 = 킥오프 - VOTE_OPEN_HOURS_BEFORE (D-2 = 48h) vote_open_hours_before: int = 48 # 마감 = 킥오프 1시간 전 (경기 시작 1시간 전까지 투표). lock 오프셋(분). vote_lock_minutes_before: int = 60 # 데모: True 면 오픈 게이트 무시(항상 투표 가능). 운영 배포 시 False. demo_force_open: bool = True # ── 스케줄러 (별도 워커 프로세스 = "스케줄링 서버") ─────── # 경기 일정 동적 수집: 매일 KST 09:00 외부 소스 크롤링 → 경기·투표시간 갱신. schedule_sync_hour: int = 9 schedule_sync_minute: int = 0 # 소스: openfootball(키 불필요·기본) | football-data(토큰 필요) | fallback(하드코딩만) schedule_source: str = "openfootball" schedule_url: str = ( "https://raw.githubusercontent.com/openfootball/worldcup.json/master/2026/worldcup.json" ) schedule_group: str = "Group A" # 추적할 그룹 (openfootball 라벨) football_data_token: str = "" # schedule_source=football-data 일 때 football_data_competition: str = "WC" # 매일 AI 예측 생성 시각 (KST). 기능정의서: 매일 0시 1회 생성. ai_generate_hour: int = 0 ai_generate_minute: int = 5 # 결과 메일: 경기 종료(결과 입력) 후 이 시간(분) 경과하면 발송. 기본 180분(3시간). result_email_delay_minutes: int = 180 # 상태 전이 틱 주기(초): scheduled→open→locked 자동 갱신. status_tick_seconds: int = 60 # ── 관리자 ────────────────────────────────────────────── admin_api_token: str = "change-me-admin-token" # ── 외부 연동: AI 3모델 (실연동) ───────────────────────── openai_api_key: str = "" openai_model: str = "gpt-4o" anthropic_api_key: str = "" anthropic_model: str = "claude-opus-4-8" google_api_key: str = "" google_model: str = "gemini-2.5-flash" # ── 외부 연동: 이메일 (SMTP, 실연동) ───────────────────── smtp_host: str = "" smtp_port: int = 587 smtp_user: str = "" smtp_password: str = "" smtp_from: str = "TriplePick " smtp_starttls: bool = True @property def cors_origin_list(self) -> list[str]: if self.cors_origins.strip() == "*": return ["*"] return [o.strip() for o in self.cors_origins.split(",") if o.strip()] @lru_cache def get_settings() -> Settings: return Settings() settings = get_settings()