feat(schedule_type): minute-precision windows + variable maintenance length
Lifts the two hard restrictions in PR #18: * window bounds were `int hour` (0-23) → now `int minutes-since-UTC-midnight` (0-1439) * maintenance window was exactly 1 hour → now any duration in [1, 180] minutes ((maint_to - maint_from) mod 1440) ## Schema migration (additive) `_migrate_schema()` detects legacy "hours" rows (any row where MAX of the 6 window columns is ≤ 23) and multiplies each column by 60 to convert to minutes. Idempotent — post-conversion values are well above 23 so the guard never fires twice. ## Touched surfaces - `models/schedule_type.py` — column comments updated; new `compute_maintenance_duration()` helper (returns 1-1440 min, treats from==to as 1440 which is then rejected by validator) - `schemas/schedule_type.py` — `*_from`/`*_to` upper bound 23 → 1440; `_validate_maintenance_window` accepts 1-180min duration; response includes derived `maintenance_duration_minutes` - `schemas/schedule_type_special_slot.py` — `minute_in_window` max 59→179; `estimated_duration` max 60→180 - `routers/schedule_type.py` — PATCH re-validates merged maintenance pair (partial updates can put the row into an invalid combo the pydantic single-field validator can't catch); `_attach_derived` populates the new response field - `routers/schedule_type_special_slot.py` — `_validate_fits_window` now takes the parent's maintenance duration instead of hard-coded 60 - `routers/calendar.py` — `_scheduled_inside_window` arg renamed hour→min; the maintenance-window guard error message formats HH:MM not HH:00 - `services/special_slot_materialiser.py` — materialised `scheduled_at` derived from `(maint_from_min + tpl.minute_in_window)` with hour/minute split 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -145,11 +145,11 @@ def _build_time_slot_from_template(
|
||||
schedule_type: ScheduleType,
|
||||
template: ScheduleTypeSpecialSlot,
|
||||
) -> TimeSlot:
|
||||
scheduled_at = time_type(
|
||||
hour=schedule_type.maintenance_from,
|
||||
minute=template.minute_in_window,
|
||||
second=0,
|
||||
)
|
||||
# schedule_type.maintenance_from is minutes-since-UTC-midnight; the
|
||||
# template's minute_in_window is an offset inside that window. Combined
|
||||
# offset must fit in [0, 1440) and produce a wall-clock time_type.
|
||||
total_min = (schedule_type.maintenance_from + template.minute_in_window) % 1440
|
||||
scheduled_at = time_type(hour=total_min // 60, minute=total_min % 60, second=0)
|
||||
# Merge admin-supplied event_data with bookkeeping pointers so the
|
||||
# agent (and ARD) can identify the template at wake time.
|
||||
merged_event_data = dict(template.event_data or {})
|
||||
|
||||
Reference in New Issue
Block a user