Étape 1 : squelette du projet

- Monorepo backend/ + frontend/ + deploy/
- Backend FastAPI/SQLModel/Alembic/uv qui démarre (health check, config,
  i18n, base de test PostgreSQL, pytest)
- docker-compose de développement (db + backend hot-reload)
- Chaîne CI/CD GitHub Actions : lint + tests, build image, push GHCR,
  déploiement SSH sur le VPS à chaque push main
- CLAUDE.md : vocabulaire du domaine, conventions, commandes

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
AdamThomas
2026-09-09 22:19:58 +02:00
co-authored by Claude Sonnet 5
commit 09d7d70076
34 changed files with 1621 additions and 0 deletions
+237
View File
@@ -0,0 +1,237 @@
# CLAUDE.md — Gestion des bénévoles
Application web de gestion des bénévoles pour un festival associatif (50–200
bénévoles, 4 jours montage/démontage inclus). Interface en français, code en
anglais.
Ce fichier est le contrat de travail. Il est tenu à jour à chaque étape.
---
## 1. Vocabulaire du domaine (glossaire FR ↔ EN)
Le métier parle français, le code et la base parlent anglais. Toute entité,
colonne, route et variable utilise la colonne « Code / base ».
| Métier (brief) | Code / base |
|------------------------------------|-----------------------------------------------|
| Édition | `Edition` |
| Bénévole | `Volunteer` |
| Compétence | `Skill` |
| Poste | `Position` |
| Référent d'un poste | `Position.lead_account_id` → `Account` |
| Créneau | `Shift` |
| Disponibilité | `Availability` |
| Affectation | `Assignment` |
| Compte (utilisateur connecté) | `Account` |
| Découpage de journée (paramétrable)| `DayPart` (`code`, `label`, `start_time`, `end_time`, `order`) |
| Lien magique | `MagicLinkToken` |
Énumérations :
| Métier | Code |
|------------------------------------|-----------------------------------------------|
| Statut d'affectation | `PROPOSED` / `CONFIRMED` / `DECLINED` / `CANCELLED` |
| Origine d'affectation | `SOLVER` / `MANUAL` |
| Verrouillée | `Assignment.locked: bool` |
| Rôle | `COORDINATOR` / `LEAD` / `VOLUNTEER` |
| Disponibilité (tri-état) | `AVAILABLE` / `IF_NEEDED` / `UNAVAILABLE` |
| Catégorie de poste | `BAR` / `TICKETING` / `WELCOME` / `PARKING` / `CLEANING` / `CATERING` / `SETUP` / `STAGE` / `OTHER` |
### Distinction critique (ne jamais confondre, ni en base ni dans l'UI)
- **`Availability`** = une *déclaration* du bénévole : « je peux venir tel
jour, telle demi-journée ».
- **`Assignment`** = une *décision* de l'organisation : « tu tiens le bar
samedi 18 h – 22 h ».
Deux tables distinctes, deux écrans distincts, deux vocabulaires distincts.
---
## 2. Modèle de domaine
- **`Edition`** : année, `start_date`, `end_date`, `retention_days`
(durée de rétention des données nominatives, paramétrable), liste de
`DayPart`.
- **`DayPart`** : découpage d'une journée propre à l'édition (matin,
après-midi, soirée… nombre et bornes libres). Une `Availability` référence
un `DayPart` + une date.
- **`Volunteer`** : `email` (identifiant fonctionnel unique **par édition**,
nullable pour les bénévoles créés à la main), `first_name`, `last_name`,
`phone`, `tshirt_size`, `diet`, `comment`. Pas de date de naissance.
- **`Skill`** : référentiel par édition (SST, permis B, régie son, langues…).
N-N avec `Volunteer`. N-N avec `Shift` (compétences requises).
- **`Position`** : `name`, `location`, `category`, `lead_account_id`.
- **`Shift`** : `position_id`, `start_at`, `end_at` (peut finir le lendemain),
`min_staff`, `ideal_staff`, compétences requises (N-N `Skill`).
- **`Assignment`** : `volunteer_id`, `shift_id`, `status`, `source`, `locked`.
- **`Account`** : `email`, `role`, `edition_id`, `volunteer_id` (nullable).
- **`MagicLinkToken`** : `account_id`, `token_hash`, `expires_at`, `used_at`.
Multi-édition : toute entité métier porte un `edition_id`.
### Rôles et accès
- **`COORDINATOR`** : accès complet, construit et publie les plannings.
- **`LEAD`** (référent) : lecture sur toute l'édition, écriture limitée à ses
propres postes.
- **`VOLUNTEER`** : accès à ses seules données.
Authentification par lien magique envoyé par email, sans mot de passe. L'envoi
d'email est **stubé** pour l'instant (le lien est écrit dans les logs). Un
bénévole sans email n'a pas de `Account` et ne se connecte pas ; il est géré
entièrement par la coordination.
---
## 3. Moteur de validation des affectations — cœur du produit
Module isolé `backend/app/validation/`. **Tests écrits avant l'implémentation**,
un cas passant + un cas déclenchant par règle.
Retourne une liste de violations typées `BLOCKING` ou `WARNING`. Chaque
violation est un **code** + des paramètres structurés, jamais une phrase (la
traduction se fait via `locales/fr.json`).
| Code | Sévérité | Règle |
|---------------------------|------------|-------------------------------------------------------------|
| `SHIFT_OVERLAP` | `BLOCKING` | Deux créneaux d'un même bénévole se chevauchent. |
| `OUTSIDE_AVAILABILITY` | `WARNING` | Un `DayPart` couvert par le créneau est `UNAVAILABLE` ou sans déclaration. |
| `DAILY_HOURS_EXCEEDED` | `WARNING` | > 8 h 00 d'affectations cumulées sur une même journée. |
| `INSUFFICIENT_REST` | `WARNING` | < 8 h 00 entre la fin d'un créneau et le début du suivant. |
| `MISSING_REQUIRED_SKILL` | `WARNING` | Le bénévole n'a pas une compétence requise par le créneau. |
Sémantique figée :
- Statuts pris en compte pour chevauchement / cumul / repos :
`PROPOSED` + `CONFIRMED`. `DECLINED` et `CANCELLED` sont ignorés.
- « Journée » (cumul 8 h) : du début du premier `DayPart` du jour au début du
premier `DayPart` du lendemain.
- Cumul : `> 8 h 00` strict (8 h 00 pile passe).
- Repos : `< 8 h 00` strict.
- Créneaux jointifs (`end_at == start_at`) : **pas** un chevauchement.
- Une violation `BLOCKING` empêche l'enregistrement, sans dérogation.
- Une violation `WARNING` est retournée à l'appelant mais n'empêche rien
(l'équipe connaît des exceptions légitimes).
---
## 4. Périmètre
### Premier incrément (en cours)
1. Modèle de données complet + migrations Alembic.
2. Import CSV/Excel (un seul fichier bénévoles + disponibilités), en deux
temps : analyse (colonnes détectées, aperçu) puis exécution avec mapping
validé. Rapport ligne par ligne. Détection des doublons : upsert sur
`email` ; sans email, détection sur (`last_name`, `first_name`) signalée
sans upsert automatique. Mode simulation qui ne persiste rien.
3. API CRUD sur `Position`, `Shift`, `Volunteer`, `Assignment`.
4. Moteur de validation (section 3).
5. Endpoint matrice d'une édition :
`[{ position, shifts: [{ shift, min_staff, ideal_staff, confirmed, proposed }] }]`.
6. Jeu de données de démonstration : 120 bénévoles, 15 postes, 4 jours,
quelques trous d'effectif, 2–3 avertissements actifs.
### Hors périmètre (le modèle doit juste pouvoir les accueillir sans refonte)
Solveur OR-Tools · envoi d'emails / SMS · constructeur de formulaire ·
pointage sur site · fonctions d'IA.
---
## 5. Arborescence
```
.
├── CLAUDE.md
├── README.md
├── docker-compose.yml # dev : db + backend (hot reload)
├── .env.example
├── .github/workflows/deploy.yml # CI/CD : build → GHCR → SSH sur le VPS
├── deploy/
│ ├── docker-compose.prod.yml
│ └── README.md # secrets requis, setup VPS
├── docker/postgres-init/ # création de la base de test au 1er boot
├── backend/
│ ├── pyproject.toml # géré par uv
│ ├── Dockerfile
│ ├── alembic.ini
│ ├── app/
│ │ ├── main.py # instance FastAPI
│ │ ├── config.py # settings (pydantic-settings)
│ │ ├── db.py # engine + session
│ │ ├── logging_config.py # RGPD : pas de PII dans les logs
│ │ ├── api/ # routers
│ │ ├── models/ # tables SQLModel
│ │ ├── validation/ # moteur de règles (cœur du produit)
│ │ ├── i18n/
│ │ │ ├── __init__.py # t(key, **params)
│ │ │ └── locales/fr.json
│ │ └── migrations/ # Alembic (env.py, versions/)
│ └── tests/
│ ├── conftest.py # vraie base de test PostgreSQL
│ └── ...
└── frontend/ # React + Vite + TS + Tailwind (étape ultérieure)
```
---
## 6. Commandes
Aucune dépendance locale requise hors Docker.
| Action | Commande |
|-------------------------------|-------------------------------------------------------------------------|
| Lancer en dev | `docker compose up --build` |
| API + docs interactives | http://localhost:8000/docs |
| Tests backend | `docker compose run --rm backend uv run pytest` |
| Couverture | `docker compose run --rm backend uv run pytest --cov=app` |
| Lint | `docker compose run --rm backend uv run ruff check .` |
| Format | `docker compose run --rm backend uv run ruff format .` |
| Créer une migration | `docker compose run --rm backend uv run alembic revision --autogenerate -m "..."` |
| Appliquer les migrations | `docker compose run --rm backend uv run alembic upgrade head` |
| Charger le jeu de démo | *(étape ultérieure)* `docker compose run --rm backend uv run python -m app.seed` |
---
## 7. Conventions
- **Code, base, noms de fichiers en anglais.** Le français vit dans les
fichiers de traduction et les commits.
- **Aucun texte visible par l'utilisateur en dur.** Backend : `app.i18n.t(key)`
+ `locales/fr.json`. Frontend : fichier i18n dédié. Vrai même en monolingue.
- **RGPD :**
- Pas de log des noms, emails, téléphones. On logue des identifiants.
- `Edition.retention_days` : durée de rétention paramétrable.
- Endpoint de suppression complète d'un bénévole : *hard delete* en cascade.
- **Dates et heures :** stockées naïves, interprétées en heure locale
Europe/Paris (heure murale). Les règles « 8 h », « journée », « demi-journée »
sont toutes en heure locale.
- **Validation TDD :** tests avant implémentation, un cas passant + un cas
déclenchant par règle.
- **Git :** un commit atomique par étape validée, message en français.
- **Étapes courtes :** après chaque étape, montrer le résultat et attendre la
validation avant de continuer.
---
## 8. Déploiement
- Dépôt GitHub. VPS avec Docker.
- `.github/workflows/deploy.yml` : sur chaque push `main` → build de l'image
backend → push sur `ghcr.io/<owner>/<repo>-backend` → SSH sur le VPS →
`docker compose -f docker-compose.prod.yml pull && up -d`.
- Secrets requis (voir `deploy/README.md`) : `SSH_HOST`, `SSH_USER`,
`SSH_KEY`, `DEPLOY_PATH`, `GHCR_TOKEN`.
- Le VPS porte un fichier `.env` (non versionné) avec `POSTGRES_USER`,
`POSTGRES_PASSWORD`, `POSTGRES_DB`, `JWT_SECRET`.
---
## 9. Journal des étapes
| Étape | Description | État |
|-------|-------------------------------------------------------|----------|
| 1 | Squelette : monorepo, Docker, CI/CD, backend qui démarre | à valider |