- Python 93.3%
- CSS 4.4%
- HCL 0.9%
- Shell 0.6%
- Dockerfile 0.6%
- Other 0.2%
|
Some checks failed
Build & push Docker image / build (push) Failing after 3m48s
« 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 |
||
|---|---|---|
| .forgejo/workflows | ||
| chessia | ||
| docker | ||
| docs/superpowers | ||
| infra/module | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
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/stockfishpar défaut. - CLI
claude(Claude Code) authentifiée via l'abonnement Max — le pipeline passe parclaude -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ée —
opening / 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 :
- Diagnostic — nom précis du motif tactique/positionnel
- Analyse comparative des 3 candidats Stockfish
- Ligne principale détaillée sur 6 à 10 demi-coups avec éval
[+0.53],[+M3] - Raison profonde (principe échiquéen violé)
- 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 kpour 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.jsonde l'utilisateur Claude Code Max. L'entrypoint l'écrit dans/home/chessia/.claude/.credentials.jsonau 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, tagv*, ou déclenchement manuel - Build : image AMD64 (
linux/amd64) - Push :
git.greil.fr/chipster/chessiaavec tagssha-<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.