Retrait des compétences ; disponibilités en fenêtres horaires libres

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
This commit is contained in:
AdamThomas
2026-09-10 22:46:45 +02:00
co-authored by Claude Sonnet 5
parent 81609eecc5
commit be51770b51
36 changed files with 800 additions and 1199 deletions
+12 -10
View File
@@ -10,8 +10,6 @@ from enum import StrEnum
from pydantic import BaseModel, Field
from app.models import AvailabilityStatus
class Severity(StrEnum):
BLOCKING = "BLOCKING"
@@ -23,7 +21,6 @@ class ViolationCode(StrEnum):
OUTSIDE_AVAILABILITY = "OUTSIDE_AVAILABILITY"
DAILY_HOURS_EXCEEDED = "DAILY_HOURS_EXCEEDED"
INSUFFICIENT_REST = "INSUFFICIENT_REST"
MISSING_REQUIRED_SKILL = "MISSING_REQUIRED_SKILL"
SEVERITY_BY_CODE: dict[ViolationCode, Severity] = {
@@ -31,7 +28,6 @@ SEVERITY_BY_CODE: dict[ViolationCode, Severity] = {
ViolationCode.OUTSIDE_AVAILABILITY: Severity.WARNING,
ViolationCode.DAILY_HOURS_EXCEEDED: Severity.WARNING,
ViolationCode.INSUFFICIENT_REST: Severity.WARNING,
ViolationCode.MISSING_REQUIRED_SKILL: Severity.WARNING,
}
@@ -53,11 +49,13 @@ class ShiftInfo:
id: str
starts_at: dt.datetime
ends_at: dt.datetime
required_skill_codes: frozenset[str] = frozenset()
@dataclass(frozen=True)
class DayPartWindow:
"""Créneau type. Ne sert plus qu'à fixer la borne de « journée » pour le
cumul des 8 h (début du premier créneau type)."""
code: str
start_time: dt.time
# end_time <= start_time ⇒ la demi-journée passe minuit.
@@ -66,10 +64,15 @@ class DayPartWindow:
@dataclass(frozen=True)
class AvailabilityDeclaration:
class AvailabilityWindow:
"""« Le <date>, je peux de <start_time> à <end_time>. »
`end_time <= start_time` ⇒ la fenêtre se termine le lendemain.
"""
date: dt.date
day_part_code: str
status: AvailabilityStatus
start_time: dt.time
end_time: dt.time
@dataclass(frozen=True)
@@ -81,7 +84,6 @@ class ValidationInput:
"""
candidate_shift: ShiftInfo
volunteer_skill_codes: frozenset[str] = frozenset()
existing_shifts: tuple[ShiftInfo, ...] = ()
availabilities: tuple[AvailabilityDeclaration, ...] = ()
availabilities: tuple[AvailabilityWindow, ...] = ()
day_parts: tuple[DayPartWindow, ...] = ()