Files
dgtlmoonandClaude Opus 5.5 ab06c89c73 Watch time_schedule_limit - Typed, frozen pydantic model with shared defaults, halves per-watch memory
Every watch carried its own copy of the 7-day schedule (15 nested dicts, ~30 strings),
about 3.7KiB of a ~7.6KiB watch, even though nearly every schedule is the untouched
default. At 40,000 watches that is ~146MB.

time_schedule_limit is now a slots + frozen pydantic dataclass (model/TimeScheduleLimit.py)
built on a small generic base (model/shared_defaults.py):

- Anything equal to the default is the one shared default instance, recursively, so an
  untouched day or the whole default schedule costs nothing per watch.
- watch.json only stores what differs from the default (exclude_defaults), an enabled
  schedule with default days is {"enabled": true}, a disabled custom week is kept.
- Frozen, so the shared default can never be mutated in place, assign a new value.
- Every write path (loading, edit form, API, clone) converts in watch_base __setitem__/update.
- Blank/None fields fall back per field, one empty value no longer loses the whole week.
- hours/minutes are ints, in the model, the form and the API spec (no upper bound,
  multi-day windows are supported).
- The scheduler converts the global schedule once per pass instead of reading it per watch.

update_34 rewrites watch.json/tag.json compact and converts the global schedule's
"24"/"00" strings to ints.

Measured: 7.6KiB -> 3.9KiB per watch (296MiB -> 150MiB at 40k watches).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 12:00:22 +02:00
..