Début de journée indépendant de l'ordre des créneaux types
La « journée de festival » commence désormais au début du plus long creux laissé par les créneaux types sur 24 h (l'heure où le dernier se termine), et non plus au début de celui de plus petit `order` : un créneau type d'ordre 0 déplacé à 21:00 faisait commencer chaque journée des grilles à 21:00, jusqu'à 03:00 le lendemain. Même règle côté backend (validation.engine.day_boundary, réutilisée par la génération de créneaux) et frontend (dayBoundaryFromDayParts). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ee1f6ed72e
commit
f671bb85bf
@@ -107,14 +107,50 @@ def _check_outside_availability(data: ValidationInput) -> list[Violation]:
|
||||
# --------------------------------------------------------------------------- #
|
||||
# DAILY_HOURS_EXCEEDED (avertissement) #
|
||||
# --------------------------------------------------------------------------- #
|
||||
_MINUTES_PER_DAY = 24 * 60
|
||||
|
||||
|
||||
def day_boundary(day_parts) -> dt.time:
|
||||
"""Début d'une « journée de festival » : début du premier créneau type
|
||||
(`DayPart`, par `order`), minuit à défaut — voir CLAUDE.md § 3. Accepte
|
||||
aussi bien les `DayPartInfo` de ce module que les lignes `DayPart`."""
|
||||
"""Début d'une « journée de festival » : début du plus long creux que
|
||||
laissent les créneaux types (`DayPart`) sur le cadran de 24 h — l'heure à
|
||||
laquelle le dernier de la journée se termine —, minuit à défaut — voir
|
||||
CLAUDE.md § 3. Avec Matin 08 h–13 h, Après-midi 13 h–19 h, Soirée
|
||||
19 h–02 h, le creux 02 h–08 h est le plus long : la journée
|
||||
commence à 02 h, la soirée qui passe minuit reste sur le jour où elle a
|
||||
commencé. Indépendant de `order`, qui n'est qu'un ordre d'affichage et
|
||||
ne suit pas les heures quand on les modifie (un créneau type d'ordre 0
|
||||
déplacé à 21 h faisait commencer chaque journée à 21 h). Sans creux (les
|
||||
créneaux types couvrent 24 h), début du premier par `order`. Accepte
|
||||
aussi bien les `DayPartInfo` de ce module que les lignes `DayPart`.
|
||||
Miroir front : `lib/planning.ts:dayBoundaryFromDayParts`."""
|
||||
day_parts = list(day_parts)
|
||||
if not day_parts:
|
||||
return dt.time(0, 0)
|
||||
return min(day_parts, key=lambda part: part.order).start_time
|
||||
covered = [False] * _MINUTES_PER_DAY
|
||||
for part in day_parts:
|
||||
start = part.start_time.hour * 60 + part.start_time.minute
|
||||
end = part.end_time.hour * 60 + part.end_time.minute
|
||||
length = (end - start) % _MINUTES_PER_DAY or _MINUTES_PER_DAY
|
||||
for offset in range(length):
|
||||
covered[(start + offset) % _MINUTES_PER_DAY] = True
|
||||
if all(covered):
|
||||
return min(day_parts, key=lambda part: part.order).start_time
|
||||
# Parcours circulaire à partir d'une minute couverte : chaque creux est
|
||||
# vu d'un seul tenant, même s'il enjambe minuit.
|
||||
origin = covered.index(True)
|
||||
best_start, best_length = 0, 0
|
||||
run_start, run_length = 0, 0
|
||||
for offset in range(1, _MINUTES_PER_DAY + 1):
|
||||
minute = (origin + offset) % _MINUTES_PER_DAY
|
||||
if covered[minute]:
|
||||
run_length = 0
|
||||
continue
|
||||
if run_length == 0:
|
||||
run_start = minute
|
||||
run_length += 1
|
||||
if run_length > best_length:
|
||||
best_start, best_length = run_start, run_length
|
||||
return dt.time(best_start // 60, best_start % 60)
|
||||
|
||||
|
||||
def festival_day(moment: dt.datetime, boundary: dt.time) -> dt.date:
|
||||
|
||||
Reference in New Issue
Block a user