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_modules의CHESS권한을 사용함. - Chess 도메인은 독립 SQLite 파일을 단일 writer로 소유하며 Hub SQLite를 직접 사용하지 않음.
2. 런타임과 배포
- FastAPI 엔트리포인트는
junkbox_chess_api.main:app임. - 웹 화면은
GET /chess, 상태 확인은GET /health임. - React 빌드 산출물은
/chess-ui, 공통 자산은/shared-ui로 마운트함. - 로컬 개발 포트는 API
18002, Vite15002임. Vite는/api/chess,/chess,/health,/shared-ui요청을 API로 프록시함. - 컨테이너 내부 포트는
8002이며 Chess SQLite는/app/db/chess.sqlite3을 사용함. junkbox-chessCompose 서비스는JUNKBOX_CHESS_RESTART_POLICY가 비어 있을 때unless-stopped정책으로 기동함.- 컨테이너는
/sorc001/junkbox/db를/app/db에 마운트하므로 DB 파일은 이미지와 분리되어 영속됨. - 앱은 polling task와 HTTP API만 실행함. Chess 전용 runtime YAML, 실시간 허브, WebSocket 라우트는 사용하지 않음.
3. 설정과 OAuth
- 공통 Settings는
settings.lichess와settings.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 계정 자동 수집
- 앱 lifespan이 polling task를 시작함.
- 활성 연결 계정이 있으면 Lichess 종료 대국 export API를
sync_cursor_ms이후 범위로 조회함. - Lichess game id가 아직 없을 때만 PGN, 게임 메타, 수별 SAN/UCI/FEN을 저장함.
- 저장 후 모든 ply를 Stockfish로 분석하고 엔진 결과를 저장함.
- 분석 상태는
pending,running,completed,failed중 하나임.
- 계정 연결 시 cursor는 현재 시각으로 초기화하므로 과거 대국을 자동 수집하지 않음.
- 수집 루프는 game id 중복을 저장하지 않음.
4.2 수동 PGN 업로드
- 업로드는 주석을 포함한 Lichess PGN만 허용함. 주석은 시계 정보와 Lichess 주석을 보존하는 원본 정책임.
- 파일은
.pgn, UTF-8 또는 UTF-8 BOM, 최대 5MB만 허용함. - 유효한 수순,
White,Black,GameId또는 LichessSite태그가 필요함. GameId또는Site에서 추출한 Lichess game id를 게임 식별자로 사용함. 자동 수집 대국과 수동 업로드 대국은 같은 식별자 기준으로 중복을 거부함.- 연결 계정명이
White또는Black과 일치하면 해당 색을side와is_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: multipartpgn_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를 적용함.AsyncSession은async with경계에서 commit 또는 rollback함.
7. Codex 코칭 반영 규칙
- 프롬프트 원본은
prompts/prompt-chess-coaching-sqlite.md임. - Codex는
engine_status='completed'게임의is_my_move=1ply 중 코칭 행이 없는 대상만 처리함. - 엔진 원본 데이터와 PGN·FEN을 수정하지 않음.
centipawn_loss, 메이트 정보, 착수 전 최선 수·PV를 severity 판정의 우선 근거로 사용함. severity는best,good,inaccuracy,mistake,blunder중 하나임.mistake_type은 전술, 체크·캡처·위협, 킹 안전, 계산, 전략, 오프닝, 엔드게임, 시간 관리 범주의 식별값임.comment와reflection_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를 따름.