- 네이버 preview fullLineUp 조회 — 양팀 타자 8명 이상이면 발표로 간주,
확정 라인업(타순·포지션 실명)을 가사 컨텍스트에 주입
- 생성 윈도우(킥오프 150분 전~) 안에서 발표까지 대기, 킥오프 40분 전까지
미발표면 라인업 없이 폴백 생성
- 라인업 없이 만든 곡은 발표 후 자동 재생성(업그레이드) — 기존 트랙을
유지한 채 새 곡 완성 시점에 교체, 실패 시 이전 곡 복귀
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 경기 맥락(전날 결과·순위·연전 차수·선발투수) 조립 → LLM 작사
(콜앤리스폰스·섹션 태그 형식) → Suno(sunoapi.org) 생성 → songs 테이블
- 워커: 킥오프 150분 전 윈도우 진입 시 생성 시작 + 2분 주기 폴링
- API: GET /api/songs/today · POST /api/songs/callback(싱크대)
· POST /api/songs/generate(관리자 강제 생성)
- 프론트: MusicBar 가 경기 상세·메인에서 오늘의 응원가를 동적 로드
(정적 플레이리스트는 폴백 유지)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- /match/:id OG 프리렌더가 STL/CIN 같은 코드 대신 DB 의 한글 short 팀명을
사용 (KBO·MLB·MLS 포함). 리그 태그도 제목에 표기
- 설명문을 멀티리그 기준 최신 카피로 갱신
- og:image 에 ?v=2 캐시버스터 — 메신저가 과거 실패/구버전 썸네일을
캐시한 경우 재수집 유도 (index.html 정적 OG 동일 적용)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
야구식 recheck 윈도우(3일)만으론 MLS 직전 라운드(1~2주 전)가 수집되지 않아
일정판이 비고 자체 DB 폼 데이터도 부족했음. 범위를 넓혀도 scoreboard 1콜.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 투구 호칭·eventType 한글 매핑, 타구 방향은 desc 의 야수 표현에서 추출
- 주자 이동(홈인·진루·주루사)은 정규식 파싱 — 선수명은 이니셜("J.P.")만
마침표 허용해 문장 경계를 넘지 않게 함
- 교체는 "A replaces B" 구문 분해 (투수 교체·대타·대주자·수비 교체)
- 득점 강조는 KBO 와 동일하게 플레이 종료 점수 합 변화로 판정 + 홈런 결과 라인
- 미지원 문장·이벤트는 영문 원문 폴백. 노이즈(타자 타임·수비 위치이동 등) 제외
- KBO relay 와 동일 스키마라 프론트(LiveField RelayFeed) 변경 없음
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
컨테이너(httpx)에서 풀 Chrome UA 를 보내면 ESPN WAF 가 403 차단
(TLS 핑거프린트-UA 불일치 감지로 추정). httpx 기본 UA 는 통과 —
일정 동기화가 계속 실패해 로컬에서 MLS 일정이 안 뜨던 원인.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- relay 응답에서 버리던 textRelays 를 타석 단위 그룹으로 서빙 (한국어 원문 그대로)
- 기본 응답은 현재 이닝만 담아, 지난 이닝은 ?inning=N 병렬 조회 후 캐시
(현재·직전 이닝은 매번 재조회 — 이닝 전환 직후 마지막 타석 누락 방지)
- 득점 하이라이트는 텍스트 패턴이 아니라 currentGameState 점수 합 변화로 판정
(홈런·적시타뿐 아니라 홈인·폭투/실책 득점·밀어내기까지 정확)
- 네이버 type 코드는 kind(pitch/result/run/sub/note)로 변환해 UI 에 노출하지 않음
- 진행 중엔 최근 40타석, 종료 후엔 전 경기 중계(다시보기)
- 프론트: 투구 접기/펼치기 토글 · 이닝 구분선 · 득점 강조
MLB 는 소스(feed/live allPlays)가 영문이라 별도 작업 — 이번 커밋은 KBO 만.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 일정·결과·컨퍼런스 순위·프리뷰(폼/시즌전적/맞대결/머니라인) 수집
- 라이브 카드: 스코어·경기시간·득점/카드·선발 라인업(피치 뷰)·교체 현황
- 문자중계: ESPN commentary 규칙 기반 한글 변환 (미지원 템플릿은 팀명만 한글화)
- MLS 레코드는 야구와 키 구조가 같아 sync_baseball_schedule/settle 재사용
- AI 예측 데이터 블록에 MLS 전용 컨텍스트 추가
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AI 예측
- 생성 시점을 킥오프 22시간 전으로 변경(선발투수 예고 이후), 잡 매시간 실행
- 경기 시작 후 생성 금지(사후 예측 방지), 경기 단위 커밋으로 중단 시 진행분 보존
일정·라이브 UI
- 야구 일정에 순위 탭 추가 (KBO 단일 순위표 · MLB 6개 디비전, /api/standings 신설)
- 경기중 카드에 현재 이닝·실시간 스코어 표시 (30s 폴링, 서버 15s 캐시)
- MLB 선발 누락 수정: statsapi 날짜 파라미터 미국 날짜 기준 보정(-1일)
- 라이브 대기타석 스크롤바 다크 테마 처리
운영 견고성
- 우천취소: 경기 삭제 → cancelled 상태 보존 (투표·예측 기록 유지, 정산/투표/AI 제외)
- 더블헤더: 시작시각순 seq로 1·2차전 분리 등록 (match_id _2, 정산·라이브·프리뷰 매핑)
- 축구 데이터 수집을 wc 리그로 한정 (야구 팀을 축구 API에 검색하던 쿼터 낭비 수정)
점수제
- 야구 판정 완화: 근접=득실차 ±1, 부분=한 팀 득점 ±1 (등급·배점은 리그 공통 유지)
- 채점 판정을 _grade_of() 단일 함수로 통합, 종료된 야구 경기 재채점
- 배점 안내 모달 야구 문구 분리, docs/SCORING.md 갱신
기타
- 야구 전용 댓글 닉네임 풀 (종목별 세션 캐시 분리)
- 리더보드 리그별 조회 연동 (?league=)
_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>
- 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>
같은 이메일은 같은 경기에 1픽만 — 다른 기기에서 같은 이메일로 제출 시
새 픽 추가 대신 기존 픽 수정 + crowd 보정. 이메일은 소문자 정규화 저장.
모델에 (match_id, email) 유니크 제약 추가(신규 DB 적용, 기존 테이블은 앱 로직 보장).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
settle 게이트를 '킥오프 지남'으로 변경. football-data가 FINISHED 보고 시
다음 폴링(최대 5분) 내 자동 종료·채점·메일. 경기 중(IN_PLAY)엔 자동 스킵.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- RESULT_SOURCE(비우면 SCHEDULE_SOURCE) 추가 — 일정=openfootball, 결과=football-data 가능
- fetch_results 가 result_source 사용
- 자동 정산 매칭을 홈/원정 순서 무관하게 보강(소스 순서 달라도 스코어 정합)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
page_visits 테이블((visit_date, device_id) 유니크) — 하루 1기기 1회 집계.
POST /api/visit(접속 기록), GET /api/admin/visits(일별 순수 방문자, Bearer).
프론트는 App 마운트 시 1회 recordVisit() 호출.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
generate_ai_predictions(only_missing=True 기본): 이미 있는 (경기×모델)
예측은 보존하고 누락분만 생성 — 매일/기동/새 경기 시 기존 데이터 불변,
API 비용↓, 실패했던 모델만 자동 보충. 수동 재생성 스크립트는 강제 전체(only_missing=False).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 킥오프+result_settle_hours 경과 경기를 외부 스코어로 자동 채점(settle_matches)
- schedule_fetch.fetch_results: openfootball/football-data 확정 스코어 수집
- 결과 메일을 승패 적중자에게만 발송하는 옵션(result_email_correct_only)
- 스케줄러에 settle 잡 추가 + 기동 시 1회 밀린 경기 정산
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
워커의 generate_ai_predictions() 를 즉시 1회 실행하는 수동 트리거.
미종료 경기 전부에 대해 GPT/Claude/Gemini 재생성(덮어쓰기).
실행: docker compose run --rm worker python -m app.regenerate_ai
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AI 프롬프트가 Team A를 home으로 단정해 "홈 경기 이점" 같은 오류 근거가
생성됨. 조별리그는 중립 경기장(개최국 멕시코/미국/캐나다만 홈)임을 명시.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 백엔드: FastAPI + SQLAlchemy(async) + PostgreSQL, 별도 워커(APScheduler)
- 프론트: React 19 + Vite + TS + Tailwind v4 (기존 UI 이식, API 연동)
- docker-compose: db · api · worker · frontend 일괄 실행
- 경기 일정: openfootball 매일 09:00 크롤링 → DB upsert (신규 경기 자동 삽입+예측 생성)
- AI 예측: GPT·Claude·Gemini 실 API 매일 생성 → DB 저장 → 조회 (목데이터 제거)
- 투표창: 오픈 킥오프 D-2 / 마감 킥오프 1시간 전, lockAt 백엔드 전달→프론트 카운트다운
- 채점/리더보드: docs/SCORING.md 기준 누적 포인트
- crowd: 0 시작, 실제 제출로만 증분
- 구 Next.js/Firebase 파일 제거, README/AGENTS 갱신
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>