- Retiré : le solveur ne pénalise plus un bénévole qui dépasserait 8 h sur
la journée (`_daily_overage_minutes`/`_DAILY_OVERAGE_PENALTY_PER_MIN`,
côté back et mock). Seule la validation le signale encore, à la
confirmation (`DAILY_HOURS_EXCEEDED`, inchangée). `SolverInput.day_parts`
et son chargement sont retirés au passage : ils ne servaient qu'à ça.
- Ajouté : malus (-40) si le bénévole a déjà (affectation décidée) le
créneau précédent de ce même poste, le même jour
(`SolverShift.predecessor_shift_id`, calculé par le loader à partir de
tous les créneaux du poste — pas seulement ceux à compléter). Favorise la
rotation plutôt que d'enchaîner toujours la même personne sur un poste.
Reste un malus, pas une exclusion : le seul candidat pour un créneau
urgent est quand même proposé.
- Miroir côté mock (`runSolver`) pour que la démonstration se comporte pareil.
- CLAUDE.md à jour (§4).
Tests : remplacé le test du plafond journalier par un test de non-régression
explicite (le cumul n'est plus pénalisé) + un test isolant le repos (qui,
lui, reste vérifié) ; ajouté 3 tests pour le chaînage (préfère un bénévole
frais, reste proposé si c'est le seul candidat urgent, un créneau existant
non-prédécesseur ne déclenche pas le malus).
Vérifié : 183 tests backend, ruff, alembic check ; typecheck/lint/build/smoke
frontend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Root cause du « je n'ajoute un bénévole avec son mail, je ne reçois aucun
mail » : renseigner un email sur la fiche d'un bénévole ne crée jamais de
compte (`Account`). `POST .../auth/magic-link` cherche un compte par email
et ne fait rien s'il n'en trouve pas (202 quoi qu'il arrive, par conception,
pour ne jamais révéler si un compte existe) — silencieusement, sans erreur.
L'API `POST/GET/DELETE /editions/{id}/accounts` existait déjà côté backend
(testée) mais n'avait jamais été branchée au frontend.
- `api/schema.ts` / `types.ts` / `http-client.ts` / `hooks.ts` : nouvelle
surface `accounts` (list/create/remove) + `useAccounts`, `useCreateAccount`,
`useDeleteAccount`.
- `mock-client.ts` : même surface (règles miroir du backend : email unique,
mot de passe interdit hors COORDINATOR) ; la suppression d'un bénévole
supprime aussi son compte (cascade RGPD, alignée sur le backend).
- `ConfigVolunteers.tsx` : section « Accès de connexion » sur la fiche du
bénévole — bouton pour donner l'accès (crée un compte VOLUNTEER, lien
magique) si un email est renseigné, ou le révoquer si un accès existe déjà.
- Corrigé au passage : `config.vol.addHint` affirmait qu'un email donnait
l'accès automatiquement — c'était faux, texte mis à jour.
- CLAUDE.md à jour (section authentification).
Vérifié : typecheck, lint, build, smoke (7 routes) OK.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Les explications sur comment utiliser un écran (grille des besoins, vue
bénévoles, artistes, import, disponibilités, créneaux types, seuils,
notification par email…) étaient affichées en permanence dans la page,
prenant de la place pour un contenu qu'on ne lit qu'une fois. Elles passent
dans une bulle affichée au survol (ou au focus clavier/tactile) d'un petit
repère « ? » posé à côté du titre ou du champ concerné.
- `HelpTip` (frontend/src/components/ui.tsx) : nouveau composant partagé,
cohérent avec le design (bordures franches, pas d'arrondi) ; `side`
('top'/'bottom') pour éviter que la bulle ne sorte du cadre visible selon
que le repère est en haut ou en bas de son conteneur.
- Appliqué à GrillePage, VolunteerViewPage, ConfigArtists, ConfigVolunteers
(import, ajout manuel, disponibilités), ConfigGrid, ConfigRules, ConfigMail.
- Aucun texte déplacé n'a changé : mêmes clés i18n, seul l'endroit où elles
s'affichent change.
Laissé en l'état (pas des indications d'usage mais des messages d'état) :
bannières identifiants de démo, liens de connexion en dev, états vides, et
la description des rôles (contenu propre des cartes, pas un mode d'emploi).
Vérifié : typecheck, lint, build, smoke (7 routes) OK.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Plusieurs bénévoles peuvent légitimement partager une même adresse email —
l'architecture l'interdisait : l'import ignorait silencieusement le second
(« duplicate_email_in_file »), et l'ajout manuel renvoyait un 409. Les deux
sont corrigés.
- Migration 0009 : retrait de la contrainte d'unicité (edition_id, email) sur
`volunteer` (l'index sur `email` seul est conservé).
- Import (`app/importing/service.py`) : l'upsert se fait désormais sur
(email, nom + prénom), pas sur l'email seul. Un même email revu avec un nom
différent dans le fichier crée un second bénévole (nouveau code
`import.row.shared_email`, WARNING) au lieu d'être ignoré ;
`duplicate_email_in_file` ne se déclenche plus que sur une ligne
littéralement répétée (même email ET même nom).
- API (`POST`/`PATCH /volunteers`) : retrait du rejet 409
`volunteer.duplicate_email`, devenu sans objet (le mock ne l'a d'ailleurs
jamais appliqué à la création manuelle).
- Mock frontend (`mock-client.ts`) : même correction de l'upsert par import,
pour rester cohérent avec le backend.
- CLAUDE.md à jour (modèle de domaine, règles d'import, journal).
Contrepartie assumée (documentée) : un email re-importé avec un nom
légèrement différent (faute de frappe corrigée) crée désormais un bénévole
de plus signalé, plutôt que de renommer silencieusement l'existant — même
philosophie que la détection sans email, qui ne fusionne jamais rien
automatiquement.
Vérifié : 179 tests backend, ruff, alembic check ; typecheck/lint/build/smoke
frontend ; reseed de démonstration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
En cherchant pourquoi les logs d'envoi d'email semblaient invisibles, on a
plutôt trouvé un problème RGPD : `SMTPRecipientsRefused` embarque l'adresse
du destinataire dans ses `args` (`{adresse: (code, message)}`), et
`logger.error(..., exc)` la journalisait telle quelle via `str(exc)` — en
violation directe de la règle « pas de log des noms/emails/téléphones ».
`_smtp_error_summary()` n'en extrait que les codes/messages serveur.
Constaté au passage (pas un bug) : le mécanisme de logs lui-même fonctionne
bien — `docker compose logs -f backend` affiche bien les lignes `app.email`
dès qu'une vraie requête HTTP déclenche l'envoi (testé en direct sur
l'édition de démo).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Remplace la chaîne GitHub Actions (jamais réellement branchée : aucun
remote git n'existait) par une chaîne GitLab CI/CD, et comble un manque
qu'elle avait déjà : le frontend n'était jusqu'ici jamais construit ni
déployé en production.
- `.gitlab-ci.yml` : tests (backend + frontend) à chaque push ; sur `main`,
build des images backend et frontend (registre de conteneurs GitLab) puis
déploiement SSH sur le VPS (pull, migration Alembic, up -d).
- `frontend/Dockerfile.prod` + `frontend/nginx.conf` : build Vite statique
servi par nginx (repli SPA sur index.html). `VITE_API_MODE`/
`VITE_EDITION_ID` sont des variables de build, passées en `--build-arg`
depuis la variable CI/CD `VITE_EDITION_ID`.
- `deploy/docker-compose.prod.yml` : ajout du service `frontend` ; `backend`
et `frontend` ne publient plus que sur `127.0.0.1` (Caddy, déjà installé
sur le serveur, devient l'unique porte d'entrée publique).
- `deploy/Caddyfile.benevoles` : bloc de vhost à ajouter au Caddy existant
(`/api/*` vers le backend avec préfixe retiré, le reste vers le frontend).
- `deploy/README.md` : runbook complet (remote GitLab, variables CI/CD,
préparation du VPS, création de l'édition de production avant le premier
build frontend, vhost Caddy).
- CLAUDE.md et README.md mis à jour en conséquence.
Vérifié : `docker compose -f deploy/docker-compose.prod.yml config`,
build réel de l'image frontend de prod, / et une route SPA (/grille)
répondent 200 via nginx.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- `buildLattice` (frontend/src/lib/planning.ts) identifie une colonne par
(date, heure de début, heure de fin) et non plus par la seule heure de
début : deux créneaux qui démarrent en même temps mais durent
différemment ont désormais chacun leur colonne, avec un en-tête « début
→ fin » au lieu du seul début.
- Largeur adaptative par jour (`columnWidthPx`) : peu de créneaux dans une
journée ⇒ colonnes larges, beaucoup de créneaux ⇒ largeur compacte
(défilement horizontal) — fini les colonnes étroites par défaut.
- `VolunteerViewPage` mis à jour pour rester synchronisé avec la nouvelle
clé de colonne (fonction `buildLattice` partagée).
- CLAUDE.md à jour.
Corrige au passage `bg-hatch-mauve` (case en sureffectif) : la classe était
utilisée depuis le commit précédent mais l'utilitaire Tailwind
correspondant n'avait jamais été ajouté à `backgroundImage` — la case
retombait silencieusement sur un fond uni.
typecheck + lint + build + smoke OK.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Dans le panneau d'affectation, la liste des candidats d'un créneau
n'était triée que par disponibilité puis par nombre d'heures déjà
assignées — les vœux n'y jouaient aucun rôle tant qu'on ne cliquait pas
sur « Suggestions (solveur) ».
Nouveau `candidateChanceScore` (`lib/planning.ts`), portage local et
synchrone du scoring du solveur : vœu de poste (bonus), disponibilité
déclarée y compris conflit avec un artiste souhaité (malus), charge déjà
assignée (malus). C'est ce score qui classe désormais la liste par
défaut, dès l'ouverture du détail d'un poste — sans requête réseau ni
clic sur le solveur. Si celui-ci a effectivement tourné, ses candidats
retenus (vraie optimisation jointe) restent prioritaires en tête.
Chaque candidat affiche maintenant, en plus de sa disponibilité, des
badges « Vœu de poste » et « Manquerait un artiste souhaité » quand
applicable — pour que le classement reste lisible.
typecheck / lint / build / smoke au vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Le seed créait déjà des disponibilités, mais aucun Artist ni vœu
(VolunteerPositionWish, VolunteerArtist) — la démo ne montrait donc rien
du solveur ni des vœux : postes/artistes jamais choisis, programmation
vide.
- 3 artistes par jour de festival (pas montage/démontage), horaires
échelonnés en soirée (18h-19h / 19h30-21h / 21h30-23h).
- ~40 % des bénévoles ont un vœu de poste ; ~15 % de chance par artiste
d'être un vœu (~1 par bénévole en moyenne sur 6 artistes).
- Summary affiche désormais artists / position_wishes / artist_wishes ;
tests étendus (présence + déterminisme).
Vérifié en conditions réelles (`python -m app.seed` + appels API) :
6 artistes, 47 vœux de poste, 95 vœux d'artiste sur 120 bénévoles ; le
solveur tourne toujours en ~0,3 s dessus.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- ArtistRead/Create/Update gagnent start_time/end_time.
- Onglet Programmation : chaque artiste a désormais deux champs heure
(début/fin de passage), éditables comme le nom.
- Mock du solveur : porte le même malus de conflit d'artiste que le
back-end (un créneau qui chevauche un passage souhaité est pénalisé),
pour un comportement de démo cohérent.
- Jeu de démo : 2 artistes par jour avec des horaires distincts
(18h-19h / 20h30-22h) au lieu d'un horaire par défaut implicite.
typecheck / lint / build / smoke au vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Artist gagne start_time/end_time (end <= start ⇒ finit le lendemain,
même convention que Shift/DayPart/Availability). Migration 0008.
Le solveur (app/solver/) résout maintenant les fenêtres de passage des
artistes que chaque bénévole veut voir (VolunteerArtist) et pénalise
toute suggestion qui chevaucherait un passage souhaité — le bénévole
raterait l'artiste. Comme le reste du scoring, c'est un terme de
l'objectif, pas un blocage : pour un créneau urgent sans autre
candidat, le solveur propose quand même, quitte à ce que ce soit un
pis-aller.
- SolverVolunteer.wished_artist_windows (fenêtres ShiftInfo précalculées
par le loader depuis Artist + VolunteerArtist).
- Nouveau malus _ARTIST_CONFLICT_PENALTY dans engine.py.
- Tests : moteur (conflit détecté/ignoré hors chevauchement/toujours
suggéré en dernier recours) + intégration API bout en bout.
176 tests, ruff et alembic check au vert. CLAUDE.md mis à jour (§2 et §4).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- Surface API `solver.run(editionId)` (HTTP + mock) et hooks
`useRunSolver` (mutation, alimente le cache) / `useSolverSuggestions`
(lecture seule du cache, ne déclenche jamais de calcul).
- Grille des besoins : bouton « Suggestions (solveur) », affiche le
nombre de suggestions calculées.
- Panneau d'affectation d'un créneau : les bénévoles suggérés par le
solveur passent en tête de la liste des candidats (avant le tri par
disponibilité / charge), avec un badge « Suggéré ». Ne pré-affecte
jamais personne — la coordination choisit et confirme comme avant.
- Mock : portage glouton (par créneau, du plus urgent au moins urgent)
du scoring du moteur back-end, pour une démonstration cohérente sans
CP-SAT côté navigateur.
typecheck / lint / build / smoke au vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Nouveau module `backend/app/solver/` (mêmes conventions que
`validation/` : models purs, engine pur testé, loader seul lien avec la
base). Pour chaque créneau encore sous son effectif idéal, calcule une
liste ordonnée de bénévoles candidats — c'est la coordination qui
affecte, dans l'écran habituel ; le solveur fait juste remonter ces
candidats en tête de liste (voir le câblage frontend à suivre).
- Seule contrainte dure : aucun chevauchement de créneaux pour un même
bénévole (règle SHIFT_OVERLAP), à la fois contre l'existant et entre
deux suggestions du même calcul. L'effectif idéal est un plafond dur.
- Tout le reste est un terme de l'objectif, pas un blocage : priorité
forte aux créneaux sous l'effectif minimal, bonus vœu de poste, malus
disponibilité manquante (réutilise `validation.engine.uncovered_minutes`),
cumul de 8 h et repos (contre les affectations déjà décidées),
répartition de la charge.
- `POST /editions/{id}/solver/suggestions` (COORDINATION/référent) :
recalcule à la demande, ne persiste jamais d'affectation.
Dépendance `ortools`. 18 nouveaux tests (moteur + API), 172 tests au
vert, `ruff` et `alembic check` verts. Testé sur le jeu de démo (120
bénévoles, 100 créneaux) : ~0,3 s.
CLAUDE.md : nouvelle section 4 (solveur), renumérotation 5-10,
compétences retirées de « hors périmètre », journal étape 15.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Aligne le frontend sur le back-end (étape 14).
- Types : suppression de `skill_codes` / `required_skill_codes` /
`AvailabilityStatus` / `MISSING_REQUIRED_SKILL` ; ajout de
`AvailabilityRead` / `AvailabilitySet` et de la surface API
`availability` (list / replace / listEdition) + hooks associés.
- Fiche bénévole : bloc « compétences » remplacé par un éditeur de
disponibilités (plages date + heures libres, `PUT` en bloc).
- Assistant d'import : plus de colonnes compétences ni disponibilités
(mapping réduit à l'identité) ; rapport simplifié.
- Grille des besoins : chaque candidat porte un badge de couverture
(disponible / partiel / non déclaré) calculé sur ses fenêtres, et la
liste est triée « disponibles d'abord ». Nouvel util
`availabilityCoverage` / `uncoveredMinutes` dans `lib/planning`,
portage du moteur back-end.
- Mock : `state.availability` passe à des fenêtres `AvailabilityRead[]`,
validation `OUTSIDE_AVAILABILITY` recalculée sur l'union des fenêtres,
jeu de démo et import CSV adaptés.
typecheck / lint / build / smoke au vert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Compétences supprimées de bout en bout :
- tables `skill`, `volunteer_skill`, `shift_required_skill` ;
- `Volunteer.skill_codes`, `Shift.required_skill_codes`, colonne `skills`
de l'import, règle de validation `MISSING_REQUIRED_SKILL`, jeu de démo.
Disponibilités repensées : `Availability` passe de (`day_part_id`,
`status` tri-état) à une **fenêtre horaire libre** (`date`, `start_time`,
`end_time` ; `end <= start` ⇒ passe minuit), plusieurs par jour.
- Nouveaux endpoints `GET`/`PUT /editions/{id}/volunteers/{vid}/availability`
(PUT = remplacement complet, COORDINATION) et
`GET /editions/{id}/availability` (toute l'édition, pour la grille).
- `OUTSIDE_AVAILABILITY` (toujours WARNING) : compare le créneau à l'union
des fenêtres du bénévole ce jour-là ; renvoie `date` + `uncovered_minutes`.
`DayPart` ne sert plus qu'à la borne de « journée » du cumul 8 h.
- L'import ne gère plus les disponibilités (fenêtres saisies dans l'app) :
`values.py` supprimé, mapping réduit à nom/email/téléphone/commentaire.
Migration 0007 (recrée `availability`). `AvailabilityStatus` supprimé.
158 tests, `alembic check` et `ruff` au vert. Front à suivre.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Aligne le frontend sur le back-end (étape 13).
- Types : retrait de `tshirt_size` / `diet` (Volunteer et mapping
d'import) ; ajout de `ArtistRead/Create/Update` et des champs
`artist_ids` / `wished_position_ids` sur la fiche bénévole.
- Surface API `artists` (HTTP + mock) et hooks
`useArtists` / `useCreateArtist` / `useUpdateArtist` / `useDeleteArtist`.
- Nouvel onglet Configuration « Programmation » : liste d'artistes
éditable, colonne par jour de l'édition (création, renommage,
suppression ; 409 doublon, 422 nom vide).
- Fiche bénévole & formulaire d'ajout manuel : champs t-shirt / régime
retirés, sélecteurs « Artistes à voir » et « Postes souhaités »
(puces retirables, 422 si id étranger).
- Mock : jeu de démo enrichi (2 artistes par jour, vœux aléatoires),
cascade sur suppression d'artiste / de poste, import CSV sans
t-shirt / régime.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Nouvelle entité `Artist` (nom + jour de passage), CRUD
`/editions/{id}/artists` (lecture ouverte à tout compte de l'édition,
écriture COORDINATION), unicité (édition, nom, date).
La fiche bénévole porte deux listes de vœux, sur le modèle de
`skill_codes` (None = inchangé, [] = vider) :
- `artist_ids` : artistes que le bénévole veut voir (`volunteer_artist`) ;
- `wished_position_ids` : postes qu'il voudrait tenir
(`volunteer_position_wish`), distinct de l'affectation.
Un id étranger à l'édition renvoie 422 (`artist.unknown` /
`position.unknown`). Suppression d'un artiste : cascade sur les vœux.
Migration 0006. Jeu de démonstration et frontend inchangés (le front
suivra).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Champs `tshirt_size` et `diet` supprimés du modèle `Volunteer`, des schémas
d'API, de l'import CSV/Excel (mapping, suggestions, exécution) et du jeu de
démonstration. Énum `TshirtSize` et helper `parse_tshirt_size` supprimés.
Migration 0005. Clé i18n `import.row.invalid_tshirt_size` retirée.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Écran « Bénévoles & import » remanié autour des trois méthodes d'ajout
demandées (la 3e — formulaire d'inscription en ligne — reste à faire) :
- Ajout manuel : formulaire complet (prénom, nom, email, téléphone,
taille de t-shirt, régime, commentaire, compétences) ; l'erreur 409
`volunteer.duplicate_email` est affichée.
- Import de fichier : assistant en 3 étapes (fichier → association des
colonnes + colonnes de disponibilité optionnelles → simulation puis
import réel) avec aperçu et rapport ligne par ligne.
- Gestion : recherche par nom / email, fiche éditable étendue (régime et
commentaire ajoutés), suppression RGPD en cascade inchangée.
Le bouton « créer 4 exemples » (maquette) est retiré. Traductions
`config.vol.*` et miroir des messages `import.*` du back-end.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- components/state : composant EmptyNote (encadré pointillés).
- GrillePage : si aucun créneau, message au lieu de la grille vide.
- VolunteerViewPage : si aucun bénévole, message ; si un filtre ne
renvoie rien, message à la place du tableau (avant, l'en-tête collant
restait affiché).
typecheck + lint + build + smoke OK.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- make demo : db + backend + frontend + jeu de démonstration
- make blank : idem mais édition vierge + 1er compte coordination (params
surchargeables : EMAIL, PASSWORD, NAME, START, END)
Chaque cible applique les migrations, prépare les données et bascule le
frontend sur l'API réelle (VITE_* dans .env). `make down` / `make logs`.
app/bootstrap.py : commande sous-jacente de `make blank`.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Crée une édition + le premier compte coordination (mot de passe) + 3 créneaux
types par défaut, et affiche l'UUID à mettre dans VITE_EDITION_ID. Documenté
dans README (« Vraie instance, sans jeu de démo »).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Les créneaux viennent des créneaux types (génération à la création d'un poste
+ « Générer les créneaux »). Le bouton par jour n'a plus lieu d'être.
Nettoyage : hooks/clés i18n devenus inutiles.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Le paramétrage des créneaux se fait dans « Règles & édition » : la coordination
définit des créneaux types (DayPart). À la création d'un poste, un créneau à
besoin 0/0 est généré pour chaque jour de l'édition et chaque créneau type ;
les coordinateurs ajustent ensuite min/idéal.
Back-end :
- DayPart CRUD (GET/POST/PATCH/DELETE /editions/{id}/day-parts). Code figé
après création (slugifié depuis le libellé si absent), unique par édition.
- Shift.day_part_id (nullable, SET NULL) + migration 0004.
- create_position génère les créneaux ; POST .../positions/{pid}/generate-shifts
recrée les manquants (idempotent, ne touche pas aux besoins déjà réglés).
- 13 tests (day-parts, génération, extension d'édition, ajout de type).
Front-end :
- api/hooks/mock : dayParts CRUD + positions.generateShifts.
- ConfigRules : panneau « Créneaux types » (libellé + horaires éditables,
ajout, suppression).
- ConfigGrid : bouton « Générer les créneaux » sur l'en-tête du poste.
- GrillePage : case « à définir » (gris, glyphe ·) quand min=idéal=0, avec
entrée de légende.
155 tests back OK ; typecheck + lint + build + smoke front OK.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- api : PlanningNotificationRequest/Result dans schema.ts, méthode
notifications.sendPlanning (http + mock), hook useSendPlanningNotification
- mock : compte les destinataires avec/sans email, émet des liens magiques
quand {lien_connexion} est présent
- ConfigMail : plus une maquette. Éditeur objet/corps, variables cliquables
({prenom} {nom} {nb_creneaux} {nb_heures} {creneaux} {lien_connexion}),
boutons « Envoyer le récapitulatif » et « Envoyer avec lien de connexion »,
retour d'envoi (queued / sans adresse) et ErrorNote
- i18n config.mail.* mis à jour ; onglet « MESSAGES » (plus « (MAQUETTE) »)
typecheck + lint + build + smoke : OK
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- app/email/ : sender (smtplib, STARTTLS ou SSL, connexion réutilisée pour un
lot) + templates (rendu, textes dans locales/fr.json). EMAIL_ENABLED=false
(défaut) ⇒ journalisé seulement, sans destinataire ni corps (RGPD).
- Lien magique : POST .../auth/magic-link envoie désormais l'email
(PUBLIC_BASE_URL + magic_link_path + ?token=). Helper app/auth/magic_link.py
partagé. Sans SMTP, le jeton non nominatif reste journalisé pour le dev.
- Récapitulatif de planning : POST /editions/{id}/notifications/planning
(COORDINATION). subject/body sont des modèles ({prenom}, {nom}, {nb_creneaux},
{nb_heures}, {creneaux}, {lien_connexion} → lien magique personnel émis).
Cible tous les bénévoles avec email ou une liste. Envoi en BackgroundTasks.
- Config : PUBLIC_BASE_URL, EMAIL_ENABLED, SMTP_* ; .env.example,
docker-compose (dev + prod), deploy/README.
- 9 tests (sender activé/désactivé, rendu, notifications : contenu, gabarit par
défaut, lien magique embarqué, sous-ensemble, réservé COORDINATION).
137 tests au total ; ruff + alembic check verts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Le gros de la suppression a été fait dans 8d333de ; il restait des libellés :
- i18n : « CONFIRMÉS / IDÉAL » → « AFFECTÉS / IDÉAL », note de bas de grille
sans « proposés », clé mobileVol.slotConfirmed → slotAssigned
- components/ui.tsx : tons de Badge « confirmed » / « proposed » (inutilisés)
- ConfigMail : texte d'email par défaut sans « confirmer chaque créneau »
typecheck + lint + build + smoke : OK
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Le back-end (Étape 9) a retiré AssignmentStatus : une affectation est valide
dès sa création, la retirer = supprimer la ligne.
- schema.ts : AssignmentRead/Create/Update sans `status` ;
MatrixShift.{confirmed, proposed} → `assigned`.
- mock : générateur sans statut (plus de rollStatus / DECLINED) ; matrice
= compte unique ; create → validation toujours exécutée ; PATCH ne porte
plus que source/locked ; runValidation compte toutes les affectations.
- lib/planning : suppression de isActive() et statusLabel().
- Grille + panneau : cellule « affectés / idéal », plus de +proposés ni de
badge confirmé/proposé ni de bouton « confirmer ».
- Vue par bénévole : une seule trame d'affectation (bleu).
- Mobile bénévole : plus de bouton « JE CONFIRME » ni de bannière.
- Mobile référent : plus de libellé de statut par équipier.
- Onglet 03 « Confirmations » supprimé (route, page, nav, ROUTE_ROLES) —
il n'a plus d'objet. Coordination : onglets 01, 02, 06.
- i18n : clés de statut / de l'écran Confirmations retirées.
- Vérifs : build tsc + vite, eslint (0 problème), npm run smoke.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwswqSChc3Rwtu61pRXiY2
Authentification alignée sur le back-end (Étapes 7/8) :
- src/api : schema auth (AccountRead, TokenResponse…), session.ts (jeton
Bearer partagé + callback 401), méthodes auth du client HTTP
(POST .../auth/login, .../auth/magic-link, .../auth/verify, GET /auth/me)
et du mock (comptes coordination + référents + bénévoles, tokens magiques,
sessions).
- src/auth : AuthProvider/useAuth (jeton en localStorage, réhydratation via
/auth/me, déconnexion sur 401), RequireAuth (garde + contrôle de rôle),
HomeRedirect, table ROUTE_ROLES.
- Pages : /connexion (lien magique, avec lien de démo affiché en mode mock),
/connexion/coordination (email + mot de passe).
- Router : toutes les routes métier passées derrière <RequireAuth> ;
redirection vers l'accueil du rôle.
- AppShell : onglets filtrés par rôle (01/02/03/06 coordination, 04 bénévole,
05 référent), email + rôle + bouton Déconnexion.
- Mobile bénévole : cible le bénévole du compte connecté ; dégradation
propre quand l'API refuse le détail des créneaux à un bénévole.
- Mobile référent : cible le poste dont le compte est référent.
- i18n : clés auth / rôles.
- Vérifs : build tsc + vite, eslint (0 problème), npm run smoke (flux
login / lien magique + rendu des 6 écrans par rôle et des pages publiques).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwswqSChc3Rwtu61pRXiY2
Une affectation est valide dès sa création ; la retirer = supprimer la ligne.
- Modèle : suppression de AssignmentStatus, ACTIVE_ASSIGNMENT_STATUSES et de
Assignment.status ; migration 0003 (drop_column, alembic check vert)
- Loader de validation : toutes les affectations du bénévole comptent, plus de
filtre par statut
- API : AssignmentCreate/Update/Read sans status ; création → validation
toujours exécutée (blocage si violation bloquante) ; PATCH ne porte plus que
source/locked
- Matrice : MatrixShift.{confirmed, proposed} → {assigned} (compte unique)
- Seed : affectations sans statut, résumé « Affectations » (260)
- Tests mis à jour (matrice, loader, modèle, API) ; 128 tests au total
- CLAUDE.md : glossaire, modèle, sémantique de validation, périmètre
Frontend : les références au statut (propositions, badges confirmé/proposé,
écran de confirmation mobile, mock, schema.ts) restent à retirer séparément.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
Deux systèmes d'authentification, une même session JWT :
- COORDINATION : login e-mail + mot de passe (bcrypt), POST .../auth/login ;
POST .../auth/change-password pour le compte courant
- RÉFÉRENTS / BÉNÉVOLES : lien magique inchangé, mais refusé pour un compte
qui a un mot de passe
- Account.password_hash (nullable) + migration 0002 ; propriété auth_method
("password" / "magic_link") exposée dans AccountRead
- app/auth/passwords.py : hash/verify bcrypt, longueur mini 10
- Création de compte : password requis pour COORDINATOR, interdit sinon
- seed : la coordination de démo a un mot de passe, affiché dans le résumé
- 12 tests (login ok/ko, séparation des deux systèmes, change-password,
validation à la création) ; 129 tests au total ; alembic check vert
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- app/auth/ : jetons (lien magique aléatoire haché SHA-256, JWT HS256),
dépendances get_current_account / current_account_in_edition / require_roles
+ require_coordinator / require_staff, et ensure_can_write_position/_shift
- Endpoints app/api/auth.py : POST .../auth/magic-link (202 constant, pas de
divulgation), POST .../auth/verify (JWT, jeton à usage unique), GET /auth/me
- Envoi d'email stubé : le lien est journalisé (logger app.auth, identifiants
seulement, aucune donnée nominative)
- app/api/accounts.py : CRUD des comptes réservé à la COORDINATION
- Rôles appliqués sur toute l'API :
COORDINATION accès complet ; RÉFÉRENT lecture globale + écriture sur ses
seuls postes/créneaux/affectations ; BÉNÉVOLE sa fiche et ses affectations,
aucune écriture
- imports.py aligné sur la dépendance get_edition (404 avant 403)
- Secret JWT porté à >= 32 octets (config, .env.example, docker-compose)
- seed : crée un compte VOLUNTEER pour chaque bénévole avec email (104 comptes)
- conftest : fixtures make_account / coordinator / as_account / raw_client ;
la suite existante s'exécute en COORDINATION
- 13 tests d'authentification + de rôles ; 119 tests au total ; alembic check
toujours vert (Account / MagicLinkToken déjà dans la migration 0001)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- Nouveau service `frontend` (build ./frontend, Vite --host sur 5173, bind
mount + volume nommé frontend_node_modules, depends_on backend).
- VITE_API_MODE=mock par défaut (aucun backend requis) ; VITE_API_TARGET
pointe sur le service backend pour le mode http.
- .env.example : VITE_API_MODE / VITE_EDITION_ID.
- frontend/.dockerignore ; README : URL http://localhost:5173.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwswqSChc3Rwtu61pRXiY2
Scaffold de base en attendant l'intégration du design Claude Design
(« Bénévoles Festival.dc.html ») : routage, plomberie i18n (t() +
locales/fr.json), client API avec proxy Vite vers le backend. Build et
lint passent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwswqSChc3Rwtu61pRXiY2
- app/seed.py, exécutable via python -m app.seed, idempotent et déterministe
(graine fixe), sans donnée nominative réelle
- Édition « Festival des Lucioles » 2026 : 120 bénévoles, 15 postes,
4 jours (montage/festival/démontage), 3 demi-journées, 8 compétences,
~100 créneaux, ~280 affectations actives, disponibilités tri-état
- ~12 créneaux volontairement sous l'effectif minimal (trous d'effectif)
- 3 affectations à avertissement (compétence absente, hors disponibilité,
repos insuffisant), 0 violation bloquante
- Passe de vérification finale via le moteur de validation : le résumé imprime
le décompte des avertissements par règle et le nombre de bloquantes
- covered_slots rendue publique dans validation.engine (réutilisée par le seed)
- 2 tests (dataset réaliste, déterminisme/idempotence) ; 106 tests au total
Fin du premier incrément backend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- CRUD complet, scopé par édition, sur Position, Shift, Volunteer, Assignment
(routers app/api/*, schémas Pydantic, dépendance get_edition partagée)
- Volunteer : gestion des compétences par codes (création à la volée),
suppression = effacement complet en cascade (RGPD)
- Shift : contrôle fin > début, poste de la même édition, compétences requises
- Assignment : toute écriture active passe par le moteur de validation ;
violation bloquante -> 422 error.validation_failed, avertissements renvoyés
dans la réponse. Endpoint POST .../assignments/validate en simulation.
app/validation/loader.py construit la ValidationInput depuis la base.
- GET /editions/{id}/matrix : postes -> créneaux -> {min, ideal, confirmed,
proposed}, source de l'écran principal
- 30 tests d'API (CRUD, 404/409/422, cascade, blocage, avertissements, matrice,
loader) ; 104 tests au total ; alembic check toujours vert
- Clés de traduction shift.* / assignment.* / volunteer.* ajoutées
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
- Fichier unique, processus en deux temps : POST /editions/{id}/import/analyze
(colonnes détectées, aperçu, mapping suggéré, demi-journées de l'édition)
puis POST /editions/{id}/import/execute (mapping validé)
- Parser CSV (délimiteur et encodage devinés, BOM) et XLSX (openpyxl),
détection XLSX par nombre magique
- Vocabulaire des valeurs de disponibilité (tri-état, accents/casse) et des
tailles de t-shirt ; compétences créées à la volée dans le référentiel
- Rapport ligne par ligne : codes de traduction + paramètres, jamais de
phrase. Doublons : upsert sur email, signalement sur (nom, prénom) sans
email, doublons intra-fichier. Mode simulation (rollback, rien de persisté)
- 40 tests : parser, valeurs, service (tous les cas), endpoints
- Dépendances : python-multipart, openpyxl
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM