chess diagnostic with claude & stockfish
  • Python 93.3%
  • CSS 4.4%
  • HCL 0.9%
  • Shell 0.6%
  • Dockerfile 0.6%
  • Other 0.2%
Find a file
chipster 3bca8734db
Some checks failed
Build & push Docker image / build (push) Failing after 3m48s
coach: notifications lisibles et une seule par jour de repos
« Coach démarré » affichait `fenêtre depuis 1784952023` — illisible sur un
téléphone. `_window_label` rend la borne en date locale (« 25/07 à 06h00 ») et
le message annonce le nombre de parties ingérées.

L'annonce passe APRÈS l'ingestion : elle prévient d'un run long (~50 min
d'analyse Stockfish), or un jour de repos n'en est pas un. La poser avant
produisait deux notifications pour un no-op de 3 s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NLgHtsF5eR32DDVT6YgkWy
2026-07-26 19:28:37 +02:00
.forgejo/workflows ci: container catthehacker (docker-cli préinstallé pour login-action) 2026-04-21 19:43:35 +02:00
chessia coach: notifications lisibles et une seule par jour de repos 2026-07-26 19:28:37 +02:00
docker chore(coach): base SQLite persistée sous /data/coach 2026-07-17 06:20:30 +02:00
docs/superpowers docs(coach): spec page Entraînement (hub de liens + boucle d'entraînement, phases) 2026-07-22 23:34:46 +02:00
infra/module feat(docker): volume persistant /home/chessia/.claude pour les refresh tokens 2026-04-21 13:22:25 +02:00
tests coach: notifications lisibles et une seule par jour de repos 2026-07-26 19:28:37 +02:00
.dockerignore feat: containerisation — Dockerfile + pipeline Forgejo Actions (v0.3.9) 2026-04-21 12:51:13 +02:00
.env.example feat: auth Basic pour ntfy (self-host) (v0.2.11) 2026-04-19 22:17:54 +02:00
.gitignore docs(coach): spec de refonte des pages /coach 2026-07-19 14:31:54 +02:00
CLAUDE.md Initial commit: ChessIA v0.2.4 2026-04-19 20:59:32 +02:00
Dockerfile fix: missed_win jamais atteint, hash Stockfish 512Mo, PromptError sur JSON non-objet 2026-07-17 06:41:43 +02:00
pyproject.toml chore: version 0.4.1 — consignes en accumulation 2026-07-21 20:09:34 +02:00
README.md fix(coach): toggle tolère un id hors bornes int64, doc à jour sur les 5 routes POST 2026-07-21 20:15:42 +02:00

ChessIA

Pipeline d'analyse pédagogique de parties d'échecs en français, cible FIDE 1600+.

Flux : PGN → Stockfish (multi-PV, seuils adaptatifs) → détection des coups critiques → Claude (routage Opus/Sonnet selon sévérité) → rapport HTML interactif avec graphique d'éval, mini-viewers et explications structurées.

Deux modes d'utilisation :

  • CLI (chessia partie.pgn -o rapport.html)
  • Web (chessia-web) — formulaire d'upload + historique + suivi live + notifications push

Installation

python -m venv .venv && source .venv/bin/activate
pip install -e ".[web]"                    # extras [web] pour le serveur
cp .env.example .env                       # renseigner STOCKFISH_PATH

Prérequis

  • Stockfish UCI avec NNUE (tablebases recommandées). vendor/stockfish/stockfish par défaut.
  • CLI claude (Claude Code) authentifiée via l'abonnement Max — le pipeline passe par claude -p (pas l'API Anthropic).

Variables d'environnement (.env)

Variable Défaut Rôle
STOCKFISH_PATH Chemin du binaire UCI (obligatoire)
STOCKFISH_HASH_MB 128 Hash table Stockfish en Mo (par process ; l'analyse force Threads=1 pour la reproductibilité, parallélisme par process)
CLAUDE_CONCURRENCY 3 Appels Claude parallèles
CLAUDE_MODEL_BLUNDER opus Modèle pour blunders / occasions manquées
CLAUDE_MODEL_FORCE Override tous les modèles (debug)
CHESSIA_REPORTS_DIR ~/.chessia-reports Dossier des rapports
CHESSIA_WEB_HOST / CHESSIA_WEB_PORT 127.0.0.1:8000 Écoute du serveur web
NTFY_SERVER / NTFY_TOPIC / NTFY_USER / NTFY_PASSWORD Notifs push début/fin de diag (voir ntfy.sh)

Usage

CLI

chessia partie.pgn -o rapport.html                   # analyse les deux camps
chessia partie.pgn --side white -o rapport.html      # limiter au côté Blancs

Serveur web

chessia-web                   # uvicorn sur 127.0.0.1:8000
  • / : formulaire d'upload (fichier PGN ou coller du texte, drag & drop)
  • /history : tableau des diagnostics avec filtres et polling live
  • /watch/<id> : suivi live d'un diag en cours (progress bar + timeline + logs colorés)
  • /report/<id> : rapport final avec TOC sticky, coups critiques repliables, export PDF
  • /example : redirige vers un rapport terminé aléatoire (démo sans uploader son PGN)

Régénérer des rapports existants

Quand l'UI évolue, régénérer les rapports terminés avec le nouveau template (le numéro de version d'origine est préservé dans le rapport) :

python -m chessia.regen                    # tous les jobs done
python -m chessia.regen --dry-run          # aperçu
python -m chessia.regen --job <id>         # un seul job

Le script crée un report.html.bak.<timestamp> avant chaque overwrite.

Coach

Deuxième pipeline, indépendant du premier : au lieu d'analyser un PGN ponctuel apporté par l'utilisateur, chessia-coach tourne en tâche de fond (cron), ingère automatiquement les parties rapides jouées sur Chess.com, les fait analyser par Stockfish, puis produit un rapport de coaching quotidien et cumulatif via un unique appel claude -p. Les pages sont montées sous /coach/* dans le même serveur FastAPI (chessia.web:app), avec leur propre base SQLite (CHESSIA_COACH_DB, par défaut /data/coach/coach.db).

chessia-coach run                # ingestion + analyse + rapport (cycle complet)
chessia-coach ingest --since <epoch>   # étapes séparées, pour debug
chessia-coach analyze
chessia-coach report

Pages /coach/*

Route Contenu
/coach/ Rapport du jour (le plus récent), navigation vers /coach/report/<id>
/coach/history Index chiffré de tous les rapports (date, parties, ΔElo, V/D/N)
/coach/postit Mur de pense-bêtes : les recommandations du coach, par catégorie, cochables
/coach/progress Série temporelle par jour (parties, victoires, Elo)
/coach/consignes Contraintes actives transmises au coach, gérées ligne à ligne (ajout/édition/archivage)

Toutes ces pages ne portent aucune authentification propre : elles sont restreintes au tailnet côté nginx, pas dans le code (chessia/coach/web.py).

Les cinq routes POST (/coach/consignes, /coach/consignes/<id>/edit, /coach/consignes/<id>/archive, /coach/consignes/<id>/reactivate, /coach/reco/<id>/toggle) vérifient en revanche l'origine de la requête (verifier_origine dans chessia/coach/web.py), car la liste d'IP nginx ne protège pas de la CSRF : c'est le navigateur du propriétaire, déjà sur le réseau autorisé, qui émettrait la requête depuis un site tiers — et le texte des consignes repart verbatim dans le prompt du coach. Sec-Fetch-Site fait foi (same-origin et none seuls acceptés), avec repli sur Origin vs Host ; une requête sans aucun des deux en-têtes (curl, script) est acceptée. Un refus renvoie 403.

Citations de coups

Dans le markdown produit par le modèle, un coup ne s'écrit jamais en texte libre : il doit utiliser le jeton [[m:<game_id>:<ply>]], où game_id (id complet ou ses 8 premiers caractères) et ply sont recopiés tels quels depuis les données Stockfish fournies dans le prompt. Le serveur (chessia/coach/render.py) résout chaque jeton en interrogeant la base et affiche à la place un bloc repliable (<details class="cite">) avec le coup, sa sévérité, le temps passé, un diagramme SVG (coup joué en rouge, meilleur coup Stockfish en vert) et un lien vers la partie sur Chess.com. Un jeton dont l'id ou le ply ne correspond à rien en base s'affiche « coup cité, partie inconnue » plutôt que de faire échouer le rendu. Les rapports produits avant cette fonctionnalité ne contiennent pas de jetons : ils continuent de s'afficher en texte brut, sans échiquier.

Recommandations : catégories et priorité

Chaque recommandation appartient à une catégorie parmi une liste ferméeopening / middlegame / endgame / general — imposée au modèle dans le prompt système ; toute autre valeur reçue est ramenée à general (normalize_category dans chessia/coach/store.py). Le modèle peut aussi marquer une recommandation comme prioritaire (priority: true) : ces recommandations s'affichent en tête du mur /coach/postit, dans un bandeau à part, mais le serveur ne garde jamais plus de 3 recommandations dans ce bandeau, même si le modèle en marque davantage.

Le prompt du run quotidien transmet toutes les recommandations, cochées comprises, avec leur status et leur checked_at : sans cela le modèle ne voit plus une recommandation cochée, ne peut plus la référencer par son id, et la recrée à l'identique au run suivant. Une recommandation cochée par le propriétaire n'est rouverte que si le modèle la signale délibérément persistante (still_failing: true à côté de refers_to_reco_id) ; une simple référence met seulement à jour la trace du dernier rapport et conserve checked_at et times_repeated (_apply_recommendations dans chessia/coach/pipeline.py).

Consignes utilisateur

Plusieurs consignes actives coexistent : toutes sont transmises au coach à chaque run de 6 h, sous forme de liste numérotée (queries.current_instructions), pas seulement la dernière saisie. Elles sont injectées telles quelles dans le prompt (chessia/coach/prompt.py::build_prompt), sous un en-tête qui précise au modèle qu'il s'agit de contraintes primant sur ses habitudes de coaching, pas de données à interpréter.

/coach/consignes les gère ligne à ligne (chessia/coach/web.py) :

  • Ajouter une nouvelle consigne (POST /coach/consignes) ;
  • Modifier le texte en place d'une consigne active (POST /coach/consignes/<id>/edit) — ce n'est pas un versionnement : le texte précédent n'est pas conservé ;
  • Archiver une consigne (POST /coach/consignes/<id>/archive) : elle disparaît de la liste active et n'est donc plus transmise au coach, mais aucune suppression dure — elle reste consultable et réactivable dans la section repliable en bas de page ;
  • Réactiver une consigne archivée (POST /coach/consignes/<id>/reactivate).

Chaque consigne porte un status (active / archived) et un updated_at. À l'initialisation d'une base (ou lors de sa migration vers ce schéma), une consigne inaugurale est semée automatiquement en active (volume minimal de parties à recommander par jour).

Pipeline

Phase 1 — Stockfish scan

Analyse chaque demi-coup avec multipv=2 (détecte les coups brillants : meilleur coup dans une position où les candidats sont proches).

Phase 2 — Re-analyse des coups critiques

Pour chaque coup flaggé blunder / mistake / inaccuracy / missed_win, ré-évalue avec multipv=3 pour fournir les 3 candidats à Claude.

Phase 3 — Explications Claude

Pour chaque coup critique, prompt structuré en 5 sections :

  1. Diagnostic — nom précis du motif tactique/positionnel
  2. Analyse comparative des 3 candidats Stockfish
  3. Ligne principale détaillée sur 6 à 10 demi-coups avec éval [+0.53], [+M3]
  4. Raison profonde (principe échiquéen violé)
  5. Exercice d'entraînement ciblé

Le prompt est enrichi par phase (ouverture / milieu / finale) et inclut :

  • Code ECO + nom d'ouverture (détection offline via chessia/data/eco.json)
  • Features positionnelles calculées (pions passés/doublés/isolés, paire de fous, contrôle centre)
  • Top 5 moments de tension de la partie
  • 15 derniers demi-coups d'historique

Catégories de coups

Catégorie Seuil NAG
blunder ≥ 200 cp de perte ?? ($4)
mistake ≥ 100 cp ? ($2)
inaccuracy ≥ 50 cp ?! ($6)
missed_win gain décisif lâché (+300 → <150) !? ($5)
brilliant meilleur coup dans position contestée (gap top1-top2 ≤ 60 cp) !! ($3)

Les seuils sont adaptatifs : ils montent avec la décisivité de la position via une sigmoid win_prob(cp) — moins de faux positifs sur positions déjà gagnées/perdues.

Rapport HTML

  • Autonome : styles inlinés, échiquier interactif via CDN lichess-pgn-viewer
  • Graphique d'éval SVG de la partie avec marqueurs colorés sur les coups notables
  • Coups critiques repliables (<details> natifs), sommaire sticky à gauche
  • Navigation clavier ↑ ↓ / j k pour sauter de coup en coup
  • Mini-viewer Lichess par bloc critique avec la variante Stockfish (PV 14 demi-coups)
  • Cases mentionnées par Claude mises en surbrillance sur le diagramme
  • Export PDF via window.print() (mise en page impression optimisée)
  • Light mode (toggle ☾/☀ avec persistance localStorage)

Architecture

chessia/
├── __main__.py            # CLI entrypoint
├── web.py                 # Serveur FastAPI (historique, watch, notifs, regen)
├── pipeline.py            # Orchestration : engine + claude + rapport
├── engine.py              # Wrapper Stockfish (python-chess UCI)
├── critical.py            # Détection (blunder/mistake/inaccuracy/missed_win/brilliant)
├── explain.py             # Appels Claude via subprocess claude -p
├── cache.py               # Cache FS des explanations (sha256 fen|ply|model)
├── eco.py                 # Détection offline ECO + nom d'ouverture
├── position_features.py   # Features positionnelles (pions, roi, centre, fous)
├── progress.py            # ETA (EMA) + cancel/pause contextvars
├── report_html.py         # Rendu rapport (graphique, TOC, mini-viewers, print CSS)
├── board_svg.py           # Diagramme SVG avec highlighted squares
├── regen.py               # Script CLI regen rapports (avec backup)
├── data/eco.json          # Base ECO (3700 entrées, lichess-org/chess-openings, CC0)
├── coach/                 # Pipeline coach (ingestion Chess.com + rapport quotidien), voir § Coach
│   ├── run.py             # CLI chessia-coach (run | ingest | analyze | report)
│   ├── store.py           # Schéma SQLite + migrations + Store
│   ├── queries.py         # Lecture seule pour les pages /coach
│   ├── prompt.py          # System prompt + appel claude -p + parse JSON
│   ├── render.py          # Markdown → HTML nettoyé + résolution des citations
│   └── web.py             # Routes FastAPI /coach/*
└── static/                # Design system partagé (styles.css + theme.js)

Voir CLAUDE.md pour les conventions de développement et les règles non-évidentes depuis le code.

Déploiement Docker

Image multi-stage (build Stockfish from source + Python + Node.js pour Claude Code CLI).

Build local

docker build -t chessia:dev .

Run

docker run -d \
  --name chessia \
  -p 8000:8000 \
  -v chessia-reports:/data/reports \
  -v chessia-cache:/data/cache \
  -v chessia-claude:/home/chessia/.claude \
  -e CLAUDE_CREDENTIALS_JSON="$(cat ~/.claude/.credentials.json)" \
  -e NTFY_SERVER=https://ntfy.exemple.com \
  -e NTFY_TOPIC=chessia-diag \
  -e NTFY_USER=chessia \
  -e NTFY_PASSWORD=... \
  git.greil.fr/chipster/chessia:latest

Volumes à persister

Mount Contenu
/data/reports Rapports HTML + meta.json des diagnostics
/data/cache Cache des explications Claude (évite les re-appels)
/home/chessia/.claude OAuth refresh tokens Claude Code (mis à jour automatiquement par le CLI). Persister ce volume évite d'avoir à ré-injecter CLAUDE_CREDENTIALS_JSON à chaque redémarrage tant que le refresh token reste valide

Secrets de run

  • CLAUDE_CREDENTIALS_JSON : contenu du fichier ~/.claude/.credentials.json de l'utilisateur Claude Code Max. L'entrypoint l'écrit dans /home/chessia/.claude/.credentials.json au démarrage.
  • NTFY_* : optionnel, pour les push mobiles (voir section Env ci-dessus).

Déploiement via tofu-maison

Le repo expose un module OpenTofu dans infra/module/ compatible avec le pattern du repo tofu-maison (provider kreuzwerker/docker en SSH vers le LXC cible).

Côté tofu-maison, dans docker/services.tf :

module "chessia" {
  source = "git::ssh://git@git.greil.fr/chipster/chessia.git//infra/module?ref=main"

  image         = "git.greil.fr/chipster/chessia:latest"
  external_port = 8000

  claude_credentials_json = var.chessia_claude_credentials_json
  ntfy_server             = "https://ntfy.exemple.com"
  ntfy_topic              = "chessia-diag"
  ntfy_user               = "chessia"
  ntfy_password           = var.chessia_ntfy_password
}

Puis tofu -chdir=docker init && tofu -chdir=docker apply.

Un workflow .forgejo/workflows/deploy.yml dans ce repo trigger automatiquement un apply de tofu-maison quand :

  • le module infra/module/** change (push main)
  • le build Docker termine avec succès (nouveau tag à tirer)
  • déclenché manuellement depuis l'UI Forgejo

Secret repo supplémentaire requis :

Secret Usage
TOFU_MAISON_TOKEN Token Forgejo avec write:repository sur mat/tofu-maison (pour déclencher son workflow apply.yml)

CI Forgejo Actions

Workflow dans .forgejo/workflows/docker-build.yml :

  • Trigger : push sur main, tag v*, ou déclenchement manuel
  • Build : image AMD64 (linux/amd64)
  • Push : git.greil.fr/chipster/chessia avec tags sha-<short>, latest (main), <version> (tags semver)
  • Cache layers : stockées en registry (:buildcache)
  • Runner : self-hosted

Secrets Forgejo à configurer dans les settings du repo :

Secret Usage
REGISTRY_USERNAME User pour push vers le registry Forgejo
REGISTRY_TOKEN Token d'accès avec scope write:package

Licence

Code : usage personnel. data/eco.json dérivé de lichess-org/chess-openings sous CC0.