Skip to main content

JunkBox apps/chess-api 가이드

1. 역할과 경계

  • apps/chess-api는 종료된 Lichess 대국을 수집·업로드·Stockfish 분석·복기에 제공하는 FastAPI 앱임.
  • Lichess에서 직접 대국을 생성하거나 착수하지 않으며 WebSocket 실시간 대국 기능을 제공하지 않음.
  • Lichess OAuth 연결 계정은 하나만 활성화함. 연결 이후 종료된 대국은 polling으로 수집함.
  • 주석 포함 Lichess PGN 파일은 수동 업로드할 수 있음. 연결 계정이 참가하지 않은 PGN은 관전 대국으로 저장함.
  • 모든 수의 Stockfish 분석 원본을 저장하고, 연결 계정의 수에만 Codex 코칭 데이터를 연결함.
  • 인증과 모듈 권한은 Hub JWT/Cookie 및 allowed_modulesCHESS 권한을 사용함.
  • Chess 도메인은 독립 SQLite 파일을 단일 writer로 소유하며 Hub SQLite를 직접 사용하지 않음.

2. 런타임과 배포

  • FastAPI 엔트리포인트는 junkbox_chess_api.main:app임.
  • 웹 화면은 GET /chess, 상태 확인은 GET /health임.
  • React 빌드 산출물은 /chess-ui, 공통 자산은 /shared-ui로 마운트함.
  • 로컬 개발 포트는 API 18002, Vite 15002임. Vite는 /api/chess, /chess, /health, /shared-ui 요청을 API로 프록시함.
  • 컨테이너 내부 포트는 8002이며 Chess SQLite는 /app/db/chess.sqlite3을 사용함.
  • junkbox-chess Compose 서비스는 JUNKBOX_CHESS_RESTART_POLICY가 비어 있을 때 unless-stopped 정책으로 기동함.
  • 컨테이너는 /sorc001/junkbox/db/app/db에 마운트하므로 DB 파일은 이미지와 분리되어 영속됨.
  • 앱은 polling task와 HTTP API만 실행함. Chess 전용 runtime YAML, 실시간 허브, WebSocket 라우트는 사용하지 않음.

3. 설정과 OAuth

  • 공통 Settings는 settings.lichesssettings.modules.chess 중첩 모델로 접근함.
  • Chess 전용 설정은 프로젝트 루트 .env.chess에 둠. Settings는 선택된 ENV_FILE과 같은 디렉터리의 .env.chess를 읽음.
  • 주요 설정 키는 LICHESS_OAUTH_CLIENT_ID, LICHESS_OAUTH_REDIRECT_URI, LICHESS_OAUTH_SYNC_INTERVAL_SECONDS, CHESS_STOCKFISH_PATH, CHESS_STOCKFISH_TIME_LIMIT_SECONDS, CHESS_STOCKFISH_DEPTH, CHESS_STOCKFISH_MULTIPV임.
  • OAuth 앱 등록값과 LICHESS_OAUTH_REDIRECT_URI는 반드시 일치해야 함.
  • GET /api/chess/oauth/connect는 PKCE authorization-code 흐름을 시작함. 현재 대국 export 수집에는 별도 OAuth scope를 요청하지 않음.
  • GET /api/chess/oauth/callback은 code와 state를 검증하고 토큰·계정명·수집 cursor를 SQLite에 저장한 뒤 /chess로 이동함.
  • DELETE /api/chess/account는 활성 연결을 해제하며 기존 대국·분석 데이터는 삭제하지 않음.
  • access token과 optional refresh token은 tb_lichess_account_m에 저장하며 외부 응답으로 노출하지 않음.

4. 대국 수집·업로드·분석 흐름

4.1 OAuth 계정 자동 수집

  1. 앱 lifespan이 polling task를 시작함.
  2. 활성 연결 계정이 있으면 Lichess 종료 대국 export API를 sync_cursor_ms 이후 범위로 조회함.
  3. Lichess game id가 아직 없을 때만 PGN, 게임 메타, 수별 SAN/UCI/FEN을 저장함.
  4. 저장 후 모든 ply를 Stockfish로 분석하고 엔진 결과를 저장함.
  5. 분석 상태는 pending, running, completed, failed 중 하나임.
  • 계정 연결 시 cursor는 현재 시각으로 초기화하므로 과거 대국을 자동 수집하지 않음.
  • 수집 루프는 game id 중복을 저장하지 않음.

4.2 수동 PGN 업로드

  • 업로드는 주석을 포함한 Lichess PGN만 허용함. 주석은 시계 정보와 Lichess 주석을 보존하는 원본 정책임.
  • 파일은 .pgn, UTF-8 또는 UTF-8 BOM, 최대 5MB만 허용함.
  • 유효한 수순, White, Black, GameId 또는 Lichess Site 태그가 필요함.
  • GameId 또는 Site에서 추출한 Lichess game id를 게임 식별자로 사용함. 자동 수집 대국과 수동 업로드 대국은 같은 식별자 기준으로 중복을 거부함.
  • 연결 계정명이 White 또는 Black과 일치하면 해당 색을 sideis_my_move 기준으로 사용함.
  • 두 선수 모두 연결 계정과 일치하지 않거나 연결 계정이 없으면 관전 대국으로 저장함. 관전 대국은 모든 is_my_move 값을 false로 저장하고 Codex 코칭 대상에 포함하지 않음.
  • 업로드 PGN도 수순 저장 트랜잭션이 완료된 뒤 자동 수집 대국과 같은 Stockfish 분석 흐름을 사용함.

4.3 엔진 분석과 코칭

  • Stockfish는 각 ply의 착수 전 fen_before와 실제 착수 후 fen_after를 기준으로 분석함.
  • 분석 결과는 착수자 관점의 최선·실제 평가, 메이트, centipawn_loss, 최선 UCI 수, principal variation, MultiPV를 포함함.
  • Stockfish 기본값은 초급자 복기 기준 수당 1초, 깊이 14, MultiPV 3개이며 환경 변수로 조절함.
  • Codex는 별도 작업에서 engine_status='completed'이고 코칭이 없는 연결 계정의 수만 조회해 코칭 행을 저장함.

5. API 계약

모든 /api/chess/* 경로는 require_module("CHESS")를 적용함. OAuth callback은 Lichess redirect 수신 경로임.

  • GET /api/chess/account: { username, connected } 반환함.
  • GET /api/chess/oauth/connect: Lichess OAuth authorization URL로 redirect함.
  • GET /api/chess/oauth/callback: OAuth code 교환과 계정 연결을 처리함.
  • DELETE /api/chess/account: 연결 계정을 해제함.
  • POST /api/chess/games/upload-pgn: multipart pgn_file을 검증·저장·분석함. { game_id, engine_status }를 반환함.
  • GET /api/chess/game-history?page=&size=: 등록 시각 최신순으로 완료 대국 목록과 engine_status를 반환함.
  • GET /api/chess/games/{game_id}: 게임 메타, 원본 PGN, 수순, 수별 엔진 결과, 해당 ply의 코칭 결과를 반환함.
  • 상세 Move 응답은 fen_before, fen_after, SAN, UCI, 내 수 여부, 엔진 평가·최선 수·PV, 코칭 필드를 포함함.

6. SQLite 저장 구조

  • tb_lichess_account_m: 단일 OAuth 연결 계정, 토큰, 만료 시각, 수집 cursor, 연결 상태를 저장함.
  • tb_chess_game_m: game id, 사용자명, 내 색상, 상대, 결과, 속도, variant, PGN, 초기·최종 FEN, 대국·등록 시각, 엔진 상태를 저장함. 관전 대국은 result='observed'로 저장함.
  • tb_chess_game_move_n: ply별 SAN, UCI, 착수 전후 FEN, 내 수 여부를 저장함. (game_id, ply)가 유일함.
  • tb_chess_engine_analysis_n: ply별 best_score_cp, best_mate_in, actual_score_cp, actual_mate_in, centipawn_loss, 최선 수, principal variation, MultiPV를 저장함.
  • tb_chess_coaching_n: 연결 계정의 수에 대한 severity, mistake_type, 한국어 코멘트, 복기 질문, 재복습 여부를 저장함.
  • 모든 자식 테이블은 game id FK와 ON DELETE CASCADE를 사용함.
  • SQLite 연결은 WAL, busy_timeout=5000, foreign key pragma를 적용함. AsyncSessionasync with 경계에서 commit 또는 rollback함.

7. Codex 코칭 반영 규칙

  • 프롬프트 원본은 prompts/prompt-chess-coaching-sqlite.md임.
  • Codex는 engine_status='completed' 게임의 is_my_move=1 ply 중 코칭 행이 없는 대상만 처리함.
  • 엔진 원본 데이터와 PGN·FEN을 수정하지 않음. centipawn_loss, 메이트 정보, 착수 전 최선 수·PV를 severity 판정의 우선 근거로 사용함.
  • severitybest, good, inaccuracy, mistake, blunder 중 하나임.
  • mistake_type은 전술, 체크·캡처·위협, 킹 안전, 계산, 전략, 오프닝, 엔드게임, 시간 관리 범주의 식별값임.
  • commentreflection_question은 사용자 노출 문구이므로 한국어 하십시오체와 문장 끝 마침표를 사용함.
  • Codex는 SQLite 단일 트랜잭션으로 INSERT하고 기존 (game_id, ply) 행을 변경하지 않음. created_at, updated_at은 KST timezone-aware ISO 값으로 기록함.
  • 반영 전후 대상 수와 게임별 ply를 검증하고, PRAGMA table_info(tb_chess_coaching_n)로 테이블 계약을 확인함.

8. 관련 문서

  • UI 계약은 docs/junkbox/apps/guide-apps-chess-ui.md를 따름.
  • 공통 설정·SQLite·인증 규칙은 docs/junkbox/libs/guide-lib-shared-core.md를 따름.
  • 공통 Chess 스타일 자산은 docs/junkbox/libs/guide-lib-shared-ui.md를 따름.
  • 프로젝트 공통 운영 원칙은 docs/junkbox/common/guide-jb-core.md를 따름.