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>
70 lines
1.8 KiB
Python
70 lines
1.8 KiB
Python
"""Types d'entrée et de sortie du moteur de validation.
|
|
|
|
Découplé de la base : le moteur ne connaît que ces structures simples, jamais
|
|
les modèles SQLModel. La couche appelante construit l'entrée depuis la base.
|
|
"""
|
|
|
|
import datetime as dt
|
|
from dataclasses import dataclass
|
|
from enum import StrEnum
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class Severity(StrEnum):
|
|
BLOCKING = "BLOCKING"
|
|
WARNING = "WARNING"
|
|
|
|
|
|
class ViolationCode(StrEnum):
|
|
SHIFT_OVERLAP = "SHIFT_OVERLAP"
|
|
|
|
|
|
SEVERITY_BY_CODE: dict[ViolationCode, Severity] = {
|
|
ViolationCode.SHIFT_OVERLAP: Severity.BLOCKING,
|
|
}
|
|
|
|
|
|
class Violation(BaseModel):
|
|
"""Une violation : un **code** + des paramètres structurés, jamais de phrase
|
|
(traduction via `locales/fr.json`, clés `validation.<CODE>`)."""
|
|
|
|
code: ViolationCode
|
|
severity: Severity
|
|
params: dict[str, str | int] = Field(default_factory=dict)
|
|
|
|
@classmethod
|
|
def of(cls, code: ViolationCode, **params: str | int) -> "Violation":
|
|
return cls(code=code, severity=SEVERITY_BY_CODE[code], params=params)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ShiftInfo:
|
|
id: str
|
|
starts_at: dt.datetime
|
|
ends_at: dt.datetime
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DayPartWindow:
|
|
"""Créneau type. Ne sert plus qu'à fixer la borne de « journée de
|
|
festival » (`engine.day_boundary`)."""
|
|
|
|
code: str
|
|
start_time: dt.time
|
|
# end_time <= start_time ⇒ la demi-journée passe minuit.
|
|
end_time: dt.time
|
|
order: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ValidationInput:
|
|
"""Tout ce qu'il faut pour valider une affectation candidate.
|
|
|
|
`existing_shifts` = les créneaux des autres affectations du même bénévole
|
|
(une affectation = une décision, il n'y a pas d'étape de confirmation).
|
|
"""
|
|
|
|
candidate_shift: ShiftInfo
|
|
existing_shifts: tuple[ShiftInfo, ...] = ()
|