Solveur : le malus de chaînage n'est pas limité au même poste
Correction : le malus ajouté précédemment ne se déclenchait que si le bénévole avait tenu le créneau précédent du *même* poste. L'intention était plus générale — limiter les enchaînements de shifts d'un bénévole, quel que soit le poste, pas seulement faire tourner un poste en particulier. - `SolverShift.predecessor_shift_id: str | None` → `predecessor_shift_ids: frozenset[str]` : l'ensemble des créneaux (n'importe quel poste) du même jour dont la fin coïncide avec le début de celui-ci. Peut contenir plusieurs ids (plusieurs postes se terminant à la même heure). - Loader : le calcul du prédécesseur se fait maintenant sur tous les créneaux de l'édition regroupés par jour (comparaison des heures de fin), plus par poste. - Miroir côté mock (`buildPredecessorMap`, `runSolver`). - Nouveau test d'intégration (`test_api_solver.py`) : deux postes différents, confirme que le chaînage s'applique bien d'un poste à l'autre. - CLAUDE.md à jour. Vérifié : 185 tests backend (dont le nouveau), 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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
64e2f86647
commit
b97b95c606
@@ -7,9 +7,10 @@ contente de faire remonter ces candidats en tête de liste).
|
||||
|
||||
Contrainte dure : aucun chevauchement de créneaux pour un même bénévole (règle
|
||||
`SHIFT_OVERLAP`, cf. `app.validation`). Tout le reste (disponibilité, repos,
|
||||
chaînage sur le même poste, vœu de poste, vœu d'artiste, équité de charge) est
|
||||
un terme de l'objectif, pas une contrainte — comme le moteur de validation,
|
||||
qui reste seul juge au moment où la coordination confirme une affectation.
|
||||
chaînage entre créneaux consécutifs, vœu de poste, vœu d'artiste, équité de
|
||||
charge) est un terme de l'objectif, pas une contrainte — comme le moteur de
|
||||
validation, qui reste seul juge au moment où la coordination confirme une
|
||||
affectation.
|
||||
Pas de plafond de cumul journalier côté solveur : `DAILY_HOURS_EXCEEDED`
|
||||
reste vérifiée par la validation, à la confirmation.
|
||||
"""
|
||||
@@ -44,8 +45,9 @@ _REST_PENALTY = 60
|
||||
_FAIRNESS_PENALTY_PER_HOUR = 2
|
||||
# Le créneau lui ferait manquer le passage d'un artiste qu'il veut voir.
|
||||
_ARTIST_CONFLICT_PENALTY = 80
|
||||
# Le bénévole a déjà tenu le créneau précédent de ce même poste (même jour) :
|
||||
# favorise la rotation plutôt que d'enchaîner toujours la même personne.
|
||||
# Le bénévole a déjà tenu le créneau immédiatement précédent (même jour,
|
||||
# n'importe quel poste) : limite les enchaînements de shifts plutôt que de
|
||||
# faire tourner un poste en particulier.
|
||||
_CHAIN_PENALTY = 40
|
||||
|
||||
|
||||
@@ -70,11 +72,13 @@ def _has_insufficient_rest(volunteer: SolverVolunteer, shift: SolverShift) -> bo
|
||||
|
||||
|
||||
def _chained_from_predecessor(volunteer: SolverVolunteer, shift: SolverShift) -> bool:
|
||||
"""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."""
|
||||
if shift.predecessor_shift_id is None:
|
||||
"""Le bénévole a déjà (affectation décidée) l'un des créneaux qui
|
||||
précèdent immédiatement celui-ci, le même jour — n'importe quel poste :
|
||||
il s'agit de limiter les enchaînements de shifts, pas de faire tourner un
|
||||
poste en particulier."""
|
||||
if not shift.predecessor_shift_ids:
|
||||
return False
|
||||
return any(s.id == shift.predecessor_shift_id for s in volunteer.existing_shifts)
|
||||
return any(s.id in shift.predecessor_shift_ids for s in volunteer.existing_shifts)
|
||||
|
||||
|
||||
def _score(volunteer: SolverVolunteer, shift: SolverShift) -> int:
|
||||
|
||||
Reference in New Issue
Block a user