feat: persist calendar free windows with Week Ahead (Closes #1258)
CI / lint (pull_request) Successful in 3m40s
CI / build-release (pull_request) Successful in 8s
CI / browser-journey (pull_request) Successful in 5m58s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-22 09:49:04 +00:00
1 parent 15739f7370
commit fc93620118
10 files changed
+178 -7

No files matched your search

+7 -1
View File
@@ -727,11 +727,17 @@ class TomorrowPromotion(BaseModel):
today_revision: int = Field(ge=0)
class WeekFreeWindow(BaseModel):
start_time: str = Field(pattern=r"^(?:[01]\d|2[0-3]):[0-5]\d$")
end_time: str = Field(pattern=r"^(?:[01]\d|2[0-3]):[0-5]\d$")
class WeekPlanDay(BaseModel):
plan_date: str = Field(min_length=10, max_length=10)
ids: list[str] = Field(max_length=5)
capacity_minutes: int | None = Field(default=None, ge=15, le=1440)
estimates: dict[str, int] = Field(default_factory=dict, max_length=5)
free_windows: list[WeekFreeWindow] | None = Field(default=None, max_length=16)
class WeekPlanUpdate(BaseModel):
@@ -2829,7 +2835,7 @@ async def replace_week_plan(payload: WeekPlanUpdate):
return await asyncio.to_thread(
_today_store().replace_week, login,
base_revision=payload.base_revision,
days=[day.model_dump() for day in payload.days],
days=[day.model_dump(exclude_none=True) for day in payload.days],
timezone=payload.timezone,
availability_defaults=payload.availability_defaults,
)
+27
View File
@@ -551,6 +551,33 @@ class TodayStore:
ids=day.get("ids", []), capacity_minutes=day.get("capacity_minutes"),
estimates=day.get("estimates", {}), plan_date=plan_date, timezone=timezone,
)
if "free_windows" in day:
windows = day["free_windows"]
if not isinstance(windows, list) or len(windows) > 16:
raise ValueError("free windows must be a bounded list")
normalized_windows = []
previous_end = -1
for window in windows:
if not isinstance(window, dict) or set(window) != {"start_time", "end_time"}:
raise ValueError("free windows must contain only start and end times")
clock_values = []
for key in ("start_time", "end_time"):
value = window[key]
if (
not isinstance(value, str) or len(value) != 5 or value[2] != ":"
or not value[:2].isdigit() or not value[3:].isdigit()
or int(value[:2]) > 23 or int(value[3:]) > 59
):
raise ValueError("free windows must use HH:mm times")
clock_values.append(int(value[:2]) * 60 + int(value[3:]))
start, end = clock_values
if start < previous_end or end <= start:
raise ValueError("free windows must be ordered and non-overlapping")
normalized_windows.append({
"start_time": window["start_time"], "end_time": window["end_time"],
})
previous_end = end
plan["free_windows"] = normalized_windows
duplicate_ids = seen_ids.intersection(plan["ids"])
if duplicate_ids:
raise ValueError("work must be assigned to only one Week Ahead date")