Retrait des disponibilités et des limites de temps dans la journée
Disponibilités déclarées supprimées (table `availability`, migration 0022, API, éditeur de la fiche bénévole, badge de couverture du panneau d'affectation, colonnes des statistiques) ainsi que les règles OUTSIDE_AVAILABILITY, DAILY_HOURS_EXCEEDED et INSUFFICIENT_REST, les malus de disponibilité et de repos du solveur et le filtre « Surchargés » de la vue bénévoles. Seul le chevauchement reste contrôlé. La case « je suis disponible » du formulaire public est conservée. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f671bb85bf
commit
d722a81a87
@@ -20,7 +20,6 @@ colonne, route et variable utilise la colonne « Code / base ».
|
||||
| Poste | `Position` |
|
||||
| Référent d'un poste | `Position.lead_account_id` → `Account` |
|
||||
| Créneau | `Shift` |
|
||||
| Disponibilité (fenêtre horaire libre) | `Availability` (`date`, `start_time`, `end_time`) |
|
||||
| Affectation | `Assignment` |
|
||||
| Artiste programmé | `Artist` (`name`, `performance_date`, `start_time`, `end_time`) |
|
||||
| Vœu « je veux voir cet artiste » | `VolunteerArtist` (N-N `Volunteer` ↔ `Artist`) |
|
||||
@@ -39,14 +38,14 @@ colonne, route et variable utilise la colonne « Code / base ».
|
||||
| Rôle | `COORDINATOR` / `LEAD` / `VOLUNTEER` |
|
||||
| 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)
|
||||
### Pas de disponibilités déclarées
|
||||
|
||||
- **`Availability`** = une *déclaration* du bénévole : « le samedi, je peux
|
||||
venir de 14 h à 23 h » — fenêtre horaire libre, plusieurs par jour.
|
||||
- **`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.
|
||||
Les fenêtres de disponibilité (`Availability`) ont été retirées à l'étape 42
|
||||
(table supprimée, migration `0022`) : l'appli ne connaît que les
|
||||
**`Assignment`**, *décisions* de l'organisation (« tu tiens le bar samedi
|
||||
18 h – 22 h »). Seule subsiste la case informative « je suis disponible » du
|
||||
formulaire public (`Volunteer.self_declared_available`), sans aucun effet sur
|
||||
les affectations.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,7 +62,7 @@ les tables sauf les tables d'association.
|
||||
(nombre et bornes libres). `code`, `label`, `start_time`, `end_time`
|
||||
(`end_time < start_time` ⇒ passe minuit), `order`. Unique (`edition_id`,
|
||||
`code`) ; `code` figé après création. Sert à générer les créneaux 0/0 des
|
||||
postes (pas les disponibilités, qui sont des fenêtres libres).
|
||||
postes.
|
||||
CRUD `GET/POST/PATCH/DELETE /editions/{id}/day-parts` (COORDINATION pour
|
||||
l'écriture).
|
||||
- **`Volunteer`** : `email` (nullable, **pas unique** — plusieurs bénévoles
|
||||
@@ -81,7 +80,7 @@ les tables sauf les tables d'association.
|
||||
direct vers un `VolunteerCompanionWish` ; la coordination la résout à la
|
||||
main depuis la fiche une fois les deux bénévoles inscrits),
|
||||
`self_declared_available` (case à cocher **purement informative**, sans
|
||||
lien avec `Availability`) et `willing_to_pay_fee` (« je participe à la
|
||||
effet sur les affectations) et `willing_to_pay_fee` (« je participe à la
|
||||
cotisation » — déclenche, lui, un vrai paiement : voir `fee_payment_label`/
|
||||
`fee_amount`/`fee_checkout_id`/`fee_payment_status`/`fee_paid_at` et le
|
||||
paiement SumUp plus bas).
|
||||
@@ -131,14 +130,6 @@ les tables sauf les tables d'association.
|
||||
0/0 par jour de l'édition et par `DayPart` manquant (bouton « Générer les
|
||||
créneaux », idempotent — n'écrase jamais un créneau déjà généré, même
|
||||
modifié depuis).
|
||||
- **`Availability`** : `volunteer_id`, `date`, `start_time`, `end_time`
|
||||
(`end_time <= start_time` ⇒ la fenêtre se termine le lendemain). Fenêtre
|
||||
horaire **libre** ; plusieurs par (`volunteer`, `date`). Unique
|
||||
(`volunteer_id`, `date`, `start_time`, `end_time`). Éditée en bloc :
|
||||
`GET`/`PUT /editions/{id}/volunteers/{vid}/availability` (lecture : COORD /
|
||||
LEAD ou le bénévole lui-même ; `PUT` : COORDINATION, remplace tout).
|
||||
`GET /editions/{id}/availability` liste celles de toute l'édition (COORD /
|
||||
LEAD) — pour la grille des besoins.
|
||||
- **`Assignment`** : `volunteer_id`, `shift_id`, `source` (`AssignmentSource`,
|
||||
défaut `MANUAL`), `locked` (défaut `false`). Unique (`volunteer_id`,
|
||||
`shift_id`). **Pas de statut** : l'affectation est valide dès sa création
|
||||
@@ -203,8 +194,7 @@ les tables sauf les tables d'association.
|
||||
paiement — même règle que `lib/fee.ts:feeMode` —, montants par type,
|
||||
mécénat, `PendingFormSubmission` jamais payées), besoins face aux
|
||||
bénévoles : par **journée de festival** (§ 3), heures × personnes
|
||||
demandées (min/idéal) vs heures de disponibilité déclarées vs heures
|
||||
affectées ; par poste, places min/idéales, places **pourvues plafonnées
|
||||
demandées (min/idéal) vs heures affectées ; par poste, places min/idéales, places **pourvues plafonnées
|
||||
créneau par créneau à l'effectif idéal** (`filled` — un sureffectif ne
|
||||
compense jamais un trou ailleurs ; `assigned` garde le total brut) et
|
||||
nombre de vœux. Graphiques en SVG maison (`components/charts.tsx`,
|
||||
@@ -386,7 +376,7 @@ Conventions techniques :
|
||||
- Suppression en cascade au niveau base (`ON DELETE CASCADE`) sur tous les
|
||||
`edition_id` et sur les liens vers `volunteer`, `shift`, `position`,
|
||||
`artist`, `account`. La suppression complète d'un bénévole efface
|
||||
donc ses `Availability`, `Assignment`, vœux (artistes, postes, compagnons —
|
||||
donc ses `Assignment`, vœux (artistes, postes, compagnons —
|
||||
dans les deux sens : ceux qu'il a émis et ceux qui le visaient) et son
|
||||
`Account`.
|
||||
- Dates/heures **naïves**, heure locale Europe/Paris (`TZ` du conteneur +
|
||||
@@ -411,7 +401,6 @@ Matrice appliquée par les endpoints :
|
||||
| `assignments` écriture | ✔ | ses postes | — |
|
||||
| `assignments` lecture | ✔ | ✔ | les siennes |
|
||||
| `artists` lecture / écriture | ✔ / ✔ | ✔ / — | ✔ / — |
|
||||
| `availability` lecture / écriture | ✔ / ✔ | ✔ / — | la sienne / — |
|
||||
| `matrix`, `import`, `accounts` | ✔ / ✔ / ✔ | ✔ / — / — | — |
|
||||
| `badges` (config + génération) | ✔ | — | — |
|
||||
| `position-sheets` (génération) | ✔ | — | — |
|
||||
@@ -536,26 +525,28 @@ ni corps**). Config : `SMTP_HOST/PORT/USER/PASSWORD/STARTTLS/SSL/FROM`,
|
||||
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
|
||||
Retourne une liste de violations typées `BLOCKING` ou `WARNING` (aucune règle
|
||||
`WARNING` aujourd'hui : le type reste prévu par le contrat de l'API). 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` | Le créneau n'est pas entièrement couvert par les fenêtres de disponibilité déclarées ce jour-là (params : `date`, `uncovered_minutes`). |
|
||||
| `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. |
|
||||
|
||||
**Aucune limite de temps dans la journée** (étape 42) : plus de cumul
|
||||
journalier maximal (`DAILY_HOURS_EXCEEDED`, ex-8 h), plus de repos minimal
|
||||
entre deux créneaux (`INSUFFICIENT_REST`, ex-8 h), plus de contrôle de
|
||||
disponibilité (`OUTSIDE_AVAILABILITY`). Ni la validation ni le solveur n'en
|
||||
tiennent compte.
|
||||
|
||||
Sémantique figée :
|
||||
|
||||
- Toutes les affectations du bénévole comptent pour chevauchement / cumul /
|
||||
repos (une affectation = une décision, toujours active).
|
||||
- `OUTSIDE_AVAILABILITY` : on prend l'**union** des fenêtres `Availability` du
|
||||
bénévole (converties en instants, minuit géré) ; les minutes du créneau hors
|
||||
de cette union déclenchent l'avertissement. Aucune fenêtre déclarée ⇒
|
||||
créneau entièrement à découvert.
|
||||
- « Journée » (cumul 8 h) : commence au **début du plus long creux** que
|
||||
- Toutes les affectations du bénévole comptent pour le chevauchement (une
|
||||
affectation = une décision, toujours active).
|
||||
- « Journée de festival » (plus utilisée par la validation elle-même, mais par
|
||||
les grilles, les feuilles de poste, les statistiques et le chaînage du
|
||||
solveur) : commence au **début du plus long creux** que
|
||||
laissent les `DayPart` sur le cadran de 24 h, c.-à-d. à l'heure où le
|
||||
dernier créneau type de la journée se termine (Matin 08–13, Après-midi
|
||||
13–19, Soirée 19–02 ⇒ journée de 02:00 à 02:00). **Indépendant de
|
||||
@@ -574,8 +565,6 @@ Sémantique figée :
|
||||
`shiftDatesForFestivalDay`), une heure de début avant cette borne désigne
|
||||
la nuit qui suit (00:00 sur le samedi ⇒ dimanche 00:00), pas le petit
|
||||
matin du même jour calendaire.
|
||||
- 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
|
||||
@@ -607,13 +596,9 @@ affectation n'est jamais persistée par cet endpoint.
|
||||
moment où la coordination confirme une affectation :
|
||||
- un créneau encore sous son effectif **minimal** prime largement sur tout
|
||||
le reste (le solveur doit presque toujours proposer quelqu'un pour un
|
||||
trou urgent, quitte à ce que ce soit un pis-aller signalé ensuite par un
|
||||
avertissement) ;
|
||||
trou urgent, quitte à ce que ce soit un pis-aller) ;
|
||||
- bonus si le poste fait partie des vœux du bénévole
|
||||
(`VolunteerPositionWish`) ;
|
||||
- malus proportionnel aux minutes hors des fenêtres de disponibilité
|
||||
déclarées (réutilise `validation.engine.uncovered_minutes`), et si le
|
||||
repos avec une affectation déjà décidée serait `< 8 h 00` ;
|
||||
- malus si le bénévole a déjà (affectation décidée) le créneau
|
||||
immédiatement précédent, le même jour — **n'importe quel poste**
|
||||
(`SolverShift.predecessor_shift_ids`) : limite les enchaînements de
|
||||
@@ -626,11 +611,9 @@ affectation n'est jamais persistée par cet endpoint.
|
||||
au créneau — plusieurs compagnons présents cumulent le bonus ;
|
||||
- léger malus pour les bénévoles déjà bien chargés (répartition de la
|
||||
charge).
|
||||
- **Pas de plafond de cumul journalier côté solveur** : contrairement à la
|
||||
validation (`DAILY_HOURS_EXCEEDED`, § 3), le solveur ne pénalise pas un
|
||||
bénévole qui dépasserait 8 h sur la journée — seule la validation le
|
||||
signale, à la confirmation.
|
||||
- Simplification assumée : repos, chaînage et compagnons ne sont vérifiés
|
||||
- **Aucune limite de temps** : ni cumul journalier, ni repos minimal, ni
|
||||
disponibilité (§ 3).
|
||||
- Simplification assumée : chaînage et compagnons ne sont vérifiés
|
||||
que contre les affectations **déjà décidées**, pas entre deux suggestions
|
||||
du même calcul (deux compagnons suggérés ensemble dans le même calcul ne
|
||||
se renforcent pas mutuellement) — la validation finale à la confirmation
|
||||
@@ -638,9 +621,8 @@ affectation n'est jamais persistée par cet endpoint.
|
||||
|
||||
**Classement par défaut, sans lancer le solveur** — `frontend/src/lib/planning.ts`
|
||||
(`candidateChanceScore`) porte une version simplifiée du même scoring (vœu de
|
||||
poste, disponibilité y compris conflit d'artiste, compagnons souhaités déjà
|
||||
présents sur le créneau, charge déjà affectée ; pas de cumul 8 h / repos /
|
||||
chaînage, déjà couverts par les badges de validation). C'est ce
|
||||
poste, conflit d'artiste, compagnons souhaités déjà présents sur le créneau,
|
||||
charge déjà affectée ; pas de chaînage). C'est ce
|
||||
qui classe la colonne des candidats dans le panneau d'affectation dès
|
||||
l'ouverture du détail d'un poste, sans requête réseau. Si le solveur a
|
||||
effectivement tourné (`POST .../solver/suggestions`), ses bénévoles retenus
|
||||
@@ -693,7 +675,7 @@ adapté à son tableau bénévole × créneau.
|
||||
|
||||
1. Modèle de données complet + migrations Alembic.
|
||||
2. Import CSV/Excel (identité des bénévoles : nom, email, téléphone,
|
||||
commentaire — pas les disponibilités, qui se déclarent dans l'app), en deux
|
||||
commentaire), 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`, `last_name` + `first_name`) — l'email seul ne suffit pas,
|
||||
@@ -706,7 +688,7 @@ adapté à son tableau bénévole × créneau.
|
||||
5. Endpoint matrice d'une édition :
|
||||
`[{ position, shifts: [{ shift, min_staff, ideal_staff, assigned }] }]`.
|
||||
6. Jeu de données de démonstration : 120 bénévoles, 15 postes, 4 jours,
|
||||
quelques trous d'effectif, 2–3 avertissements actifs, programmation
|
||||
quelques trous d'effectif, programmation
|
||||
(artistes les jours de festival) et vœux des bénévoles (postes, artistes)
|
||||
pour que le solveur ait matière à arbitrer.
|
||||
7. Solveur de suggestions OR-Tools CP-SAT (section 4) — en cours.
|
||||
@@ -746,7 +728,7 @@ l'étape 21.)*
|
||||
│ │ ├── api/ # routers + schemas Pydantic + deps
|
||||
│ │ │ # health, auth, accounts, editions,
|
||||
│ │ │ # day_parts, imports, artists, volunteers,
|
||||
│ │ │ # availability, positions, shifts,
|
||||
│ │ │ # positions, shifts,
|
||||
│ │ │ # assignments, solver, matrix, notifications,
|
||||
│ │ │ # badges, position_sheets, forms, stats
|
||||
│ │ ├── auth/ # mot de passe + lien magique + JWT + rôles
|
||||
@@ -806,8 +788,8 @@ Aucune dépendance locale requise hors Docker.
|
||||
- `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.
|
||||
Europe/Paris (heure murale). La « journée de festival » est 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.
|
||||
@@ -892,6 +874,7 @@ Aucune dépendance locale requise hors Docker.
|
||||
| 39 | Mode de paiement de la cotisation visible partout (`lib/fee.ts:feeMode`, `app/exporting/xlsx.py:_payment_mode`) : en ligne (type, montant, statut, mécénat), sur place, ou rien — section « Cotisation » sur la fiche (case « Paiera sur place » modifiable à la main), colonne « Cotisation » dans la liste, colonne « Mode de paiement » dans l'export Excel | à valider |
|
||||
| 40 | Écran Statistiques (`/statistiques`, COORDINATION, `GET /editions/{id}/stats`) : inscriptions (nombre, évolution cumulée), réponses au formulaire, artistes demandés, cotisations (en ligne / sur place / aucune, par type, mécénat, paiements jamais aboutis), besoins par journée de festival face aux disponibilités déclarées et aux affectations, places pourvues et vœux par poste — graphiques SVG maison avec infobulles et vues tableau | à valider |
|
||||
| 41 | Début de « journée de festival » indépendant de l'ordre des créneaux types : début du plus long creux qu'ils laissent sur 24 h (`validation.engine.day_boundary`, miroir `lib/planning.ts:dayBoundaryFromDayParts`). Avant, un créneau type d'ordre 0 déplacé à 21:00 faisait commencer chaque journée des grilles (besoins, vue bénévoles) à 21:00, jusqu'à 03:00 le lendemain | à valider |
|
||||
| 42 | Retrait des disponibilités déclarées (table `availability` supprimée, migration `0022` ; API, éditeur de la fiche bénévole, badge de couverture du panneau d'affectation, colonnes des statistiques) et des limites de temps dans la journée (règles `OUTSIDE_AVAILABILITY`, `DAILY_HOURS_EXCEEDED`, `INSUFFICIENT_REST`, malus de disponibilité et de repos du solveur, filtre « Surchargés » de la vue bénévoles) — seul le chevauchement reste contrôlé. La case « je suis disponible » du formulaire public est conservée | à valider |
|
||||
|
||||
**Reste hors périmètre** (modèle prêt à les accueillir) : SMS, constructeur
|
||||
de formulaire, pointage sur site, IA. Frontend : squelette + 6 écrans +
|
||||
|
||||
Reference in New Issue
Block a user