diff --git a/backend/app/config.py b/backend/app/config.py index 58cd980..bab425a 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -84,6 +84,9 @@ class Settings(BaseSettings): # 매일 AI 예측 생성 시각 (KST). 기능정의서: 매일 0시 1회 생성. ai_generate_hour: int = 0 ai_generate_minute: int = 5 + # 투표 오픈(D-2) 선행 생성 시간(h). 1일 1회 생성이므로 다음 주기 전 오픈할 + # 경기를 미리 채우려면 ~24h 이상 필요. 30h = 하루치 + 여유. (only_missing 이라 총 호출 수 동일) + ai_generate_lookahead_hours: int = 30 # ── 결과 자동 정산(관리자 입력 불필요) ─────────────────── # 킥오프 + 이 시간(시) 경과 후, 외부 소스에서 스코어를 자동 수집해 채점·집계·메일. diff --git a/backend/app/domain.py b/backend/app/domain.py index d551c9d..3b8dacc 100644 --- a/backend/app/domain.py +++ b/backend/app/domain.py @@ -59,8 +59,9 @@ def compute_phase(m: Match, now: datetime | None = None) -> str: def is_votable(m: Match) -> bool: - """AI 예측·투표 대상 조인지. featured_group 만 투표 가능, 그 외는 일정/결과만.""" - return m.group == settings.featured_group + """투표 지원 경기 여부 — 전체 조 투표 가능. + 실제 오픈/마감은 is_open_for_voting 의 시간창(D-2 오픈 ~ 킥오프 60분 전 마감)이 결정.""" + return True def is_open_for_voting(m: Match, now: datetime | None = None) -> bool: diff --git a/backend/app/worker.py b/backend/app/worker.py index 5f43c3a..773125b 100644 --- a/backend/app/worker.py +++ b/backend/app/worker.py @@ -83,17 +83,18 @@ async def generate_ai_predictions(only_missing: bool = True) -> None: only_missing=False: 전부 재생성(덮어쓰기) — 수동 재생성 스크립트용. """ async with SessionLocal() as db: - # AI 예측·투표 대상 조(featured_group)만 생성. 그 외 조는 일정/결과만. - matches = ( + # 투표 오픈(킥오프 D-2)이 임박/도래한 미종료 경기 — 조 무관. + # lookahead_hours 만큼 선행 생성해 다음 생성 주기 전에 오픈할 경기도 미리 채운다 + # (1일 1회 생성 → 오픈 시점에 AI 가 비어 보이지 않도록). 더 먼 미래 경기는 제외. + horizon = now_utc() + timedelta(hours=settings.ai_generate_lookahead_hours) + rows = ( await db.execute( select(Match) - .where( - Match.result_outcome.is_(None), - Match.group == settings.featured_group, - ) + .where(Match.result_outcome.is_(None)) .options(selectinload(Match.predictions)) ) ).scalars().all() + matches = [m for m in rows if ensure_aware(m.opens_at) <= horizon] for m in matches: ctx = MatchContext( diff --git a/frontend/Dockerfile b/frontend/Dockerfile index d0ff5c8..fdcec4e 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,8 +1,10 @@ # TriplePick 프론트엔드 — Vite 빌드 → nginx 정적 서빙 + /api 프록시. -FROM node:20-alpine AS build +# lock은 macOS(arm64)에서 생성돼 rollup darwin-x64 optional dep가 version 없는 +# stub로 남는 npm 버그가 있어, linux 빌드에선 lock 없이 package.json만으로 설치한다. +FROM node:24-alpine AS build WORKDIR /app -COPY package.json package-lock.json* ./ -RUN npm install +COPY package.json ./ +RUN npm install --no-audit --no-fund COPY . . RUN npm run build diff --git a/frontend/src/components/Arena.tsx b/frontend/src/components/Arena.tsx index 8186b40..167f6ee 100644 --- a/frontend/src/components/Arena.tsx +++ b/frontend/src/components/Arena.tsx @@ -39,9 +39,9 @@ export default function Arena({ const t = dict(lang); const aShort = teamShort(match.teamA, lang); const bShort = teamShort(match.teamB, lang); - const [outcome, setOutcome] = useState(null); - const [scoreA, setScoreA] = useState(2); - const [scoreB, setScoreB] = useState(1); + const [outcome, setOutcome] = useState("DRAW"); + const [scoreA, setScoreA] = useState(0); + const [scoreB, setScoreB] = useState(0); const [step, setStep] = useState("pick"); const [email, setEmail] = useState(""); const [notify, setNotify] = useState(true);