Artiste : heure de passage ; vœux d'artistes pris en compte par le solveur

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
This commit is contained in:
AdamThomas
2026-09-11 18:13:54 +02:00
co-authored by Claude Sonnet 5
parent e47c29d35b
commit fea9d37019
12 changed files with 212 additions and 35 deletions
+10 -5
View File
@@ -22,7 +22,7 @@ colonne, route et variable utilise la colonne « Code / base ».
| Créneau | `Shift` |
| Disponibilité (fenêtre horaire libre) | `Availability` (`date`, `start_time`, `end_time`) |
| Affectation | `Assignment` |
| Artiste programmé | `Artist` (`name`, `performance_date`) |
| Artiste programmé | `Artist` (`name`, `performance_date`, `start_time`, `end_time`) |
| Vœu « je veux voir cet artiste » | `VolunteerArtist` (N-N `Volunteer` ↔ `Artist`) |
| Vœu « je voudrais ce poste » | `VolunteerPositionWish` (N-N `Volunteer` ↔ `Position`) |
| Compte (utilisateur connecté) | `Account` |
@@ -71,10 +71,12 @@ les tables sauf les tables d'association.
t-shirt, régime, ni compétences. Porte deux listes de vœux, exposées par la
fiche (`artist_ids`, `wished_position_ids` ; `None`/absent = inchangé,
`[]` = vider).
- **`Artist`** : programmation de l'édition (`name`, `performance_date`).
Unique (`edition_id`, `name`, `performance_date`). La coordination gère la
liste ; elle est présentée par jour. Les bénévoles y choisissent les
artistes qu'ils veulent voir (`volunteer_artist`). CRUD
- **`Artist`** : programmation de l'édition (`name`, `performance_date`,
`start_time`, `end_time` — `end_time <= start_time` ⇒ le passage finit le
lendemain). Unique (`edition_id`, `name`, `performance_date`). La
coordination gère la liste ; elle est présentée par jour. Les bénévoles y
choisissent les artistes qu'ils veulent voir (`volunteer_artist`) — le
solveur (section 4) évite de les affecter pendant ce passage. CRUD
`GET/POST/PATCH/DELETE /editions/{id}/artists` (lecture : tout compte de
l'édition ; écriture : COORDINATION).
- Vœux de postes : `volunteer_position_wish` (« postes que le bénévole
@@ -267,6 +269,9 @@ affectation n'est jamais persistée par cet endpoint.
déclarées (réutilise `validation.engine.uncovered_minutes`), au dépassement
du cumul de 8 h ce jour-là, et si le repos avec une affectation déjà
décidée serait `< 8 h 00` ;
- malus si le créneau chevauche le passage d'un artiste que le bénévole
veut voir (`VolunteerArtist` + `Artist.start_time`/`end_time`) — lui
ferait manquer le passage ;
- léger malus pour les bénévoles déjà bien chargés (répartition de la
charge).
- Simplification assumée : cumul de 8 h et repos ne sont vérifiés que