# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
_results_football_data resolved team codes via code_for(full name), which
only covered Group A teams, so FINISHED matches in other groups (e.g.
Canada vs Bosnia-Herzegovina) were silently dropped and never settled.
Use tla -> code_for_tla (same as schedule ingestion; covers all 48), with
full-name fallback. Verified: source FINISHED 2 -> 3, CAN-BIH settles 1-1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GPT (data-driven), Claude (tactical/upset-aware), Gemini (attacking-minded)
each get a distinct perspective so the 3 picks genuinely diverge instead of
producing identical scorelines. Honest: still each model's own judgment.
Finished matches are untouched (generation filters result_outcome IS NULL).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vote_open_hours_before 48 → 120. opensAt is recomputed on schedule sync,
so existing matches shift to the wider window on next worker run. AI 30h
lookahead is relative to opensAt, so pre-fill-before-open still holds.
NOTE for deploy: set VOTE_OPEN_HOURS_BEFORE=120 in server .env (an existing
48 there would override the new default).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace hidden-scrollbar row (inaccessible on desktop without touch) with
a ChipScroller: single-row scroll + left/right arrow buttons that auto-hide
at each end and a bg fade. Resets to start on tab switch.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- domain: is_votable → all groups (voting gated by D-2 open ~ kickoff-60m time window, not group)
- worker: AI generation selects matches by opens_at <= now+lookahead (all groups), pre-fills before D-2 open so picks aren't empty; only_missing keeps total calls = 3/match
- config: ai_generate_lookahead_hours=30 (covers daily-only gen cadence)
- Arena: default pick 0:0 (무승부)
- frontend Dockerfile: install from package.json only (macOS-generated lock leaves rollup darwin-x64 stub w/o version → npm "Invalid Version" on linux)
NOTE for deploy: set DEMO_FORCE_OPEN=false and real PUBLIC_ORIGIN in server .env.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend
- teams_data.py: 48-team data (ko/short/en), TLA→FIFA code remap
- config: schedule_all_groups, featured_group("A") — only featured group is votable/AI-predicted
- schedule_fetch: fetch all GROUP_STAGE matches from football-data, KST serialize
- schedule_sync: ingest all groups, unordered-pair match to avoid Group A dup, derive votable from group
- worker: AI generation limited to featured group; settle still covers all
- domain/predictions: is_votable() + MatchOut.votable; reject non-votable submits with 403 MATCH_NOT_VOTABLE
Frontend
- types: Match.votable
- ScheduleBoard: Naver-style date/group tabs + chips; votable(A) cards link to detail with AI split + 참여수, others display-only
Verified: 72 matches (12×6), Group A not duplicated, votable A=6/others=66; A submit 200, B submit 403
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>