First task of Feature 05 (Presence and Custody Schedules): the pure model the engine builds on.
Delivered
presence.py (pure: no HA imports, no DB, no timezone logic): PresenceSchedule (child_id, cycle_length_weeks 1..4 matching the schema CHECK, strict anchor date, pattern mapping week index → present weekdays) and PresenceOverride (inclusive date range, real-bool flag, optional note) with total construction validation: cycle length bounds, exact week-index coverage (gaps/extras/bool keys rejected), weekday ints 0..6 (bools and strings rejected — True and '02' would silently misread), strict YYYY-MM-DD dates, end>=start, non-bool child ids.
Serialization round-trips losslessly via the database's pipe-separated CSV encoding; decode derives cycle length from the segment count like the schema's CHECK.
First task of Feature 05 (Presence and Custody Schedules): the pure model the engine builds on.
## Delivered
- **presence.py** (pure: no HA imports, no DB, no timezone logic): PresenceSchedule (child_id, cycle_length_weeks 1..4 matching the schema CHECK, strict anchor date, pattern mapping week index → present weekdays) and PresenceOverride (inclusive date range, real-bool flag, optional note) with total construction validation: cycle length bounds, exact week-index coverage (gaps/extras/bool keys rejected), weekday ints 0..6 (bools and strings rejected — True and '02' would silently misread), strict YYYY-MM-DD dates, end>=start, non-bool child ids.
- Serialization round-trips losslessly via the database's pipe-separated CSV encoding; decode derives cycle length from the segment count like the schema's CHECK.
- **tests/test_presence.py**: 59 tests (construction matrix, encode/decode round-trips across shapes incl. empty segments, override coverage).
## Verification
uv run pytest: 972 passed.
Closes task ede1dc76 (Feature 05).
presence.py — a PURE module (no HA imports, no DB access, no timezone
logic): PresenceSchedule (child_id, cycle_length_weeks, anchor_date,
pattern mapping week index -> frozenset of present weekdays) and
PresenceOverride (child_id, inclusive start/end range, real-bool
is_present, optional note) with construction that rejects every
invalid shape up front:
- cycle length: integer 1..4 (the 4 cap mirrors schema.py's documented
CHECK); bools/floats/strings rejected.
- pattern: dict required; week indices validated as non-bool ints
covering 0..n-1 exactly (gaps and extras rejected); weekday sets
validated as ints 0..6 (bools rejected — True would silently mean
Monday; strings rejected — '02' would silently mean {0,2}); an
explicitly empty week is valid (absent all week — distinct from no
schedule, the engine's default).
- anchor/override dates: datetime.date or strict YYYY-MM-DD (strptime
round-trip; '2026-1-5' rejected).
- override range: end >= start; is_present must be a real bool;
note optional string.
- child ids: non-bool ints (SQLite binds True as 1).
Serialization: encode() emits the database's pipe-separated CSV
(sorted weekdays per week, empty segment for an absent week);
decode() derives the cycle length from the segment count (mirroring
the schema's segment-count CHECK) and validates strictly, so
decode(encode(x)) round-trips losslessly and equal patterns encode
identically. Tests: 59 (construction matrix, round-trips, encode
shapes, override coverage). Suite: 972 passed.
Review round 1 (Codex gpt-5.6-terra, 2 MEDIUM): datetime.datetime
subclasses datetime.date and slipped past the anchor check, storing a
time component that would TypeError every later calendar comparison —
rejected explicitly now (both model classes). The frozen schedule's
pattern dict let callers inject unvalidated weekdays or drop required
weeks after construction; the validated mapping is now exposed as a
read-only MappingProxyType over frozenset weeks. Tests: datetime
rejection matrix and immutability assertions. Suite: 974 passed.
Round 1 — CHANGES_REQUIRED (2 MEDIUM): datetime.datetime slipped past the date check (it subclasses date) and would TypeError later comparisons — rejected explicitly; the frozen schedule leaked a mutable pattern dict bypassing total validation — now a read-only MappingProxyType over frozenset weeks. Round 2 — APPROVED (zero findings). Suite 975 passed, reviewer-verified.
## Independent review — Codex gpt-5.6-terra
**Round 1 — CHANGES_REQUIRED (2 MEDIUM):** datetime.datetime slipped past the date check (it subclasses date) and would TypeError later comparisons — rejected explicitly; the frozen schedule leaked a mutable pattern dict bypassing total validation — now a read-only MappingProxyType over frozenset weeks.
**Round 2 — APPROVED (zero findings).** Suite 975 passed, reviewer-verified.
dev_agent
merged commit 7ae4018e59 into feature/167e3565-704d-41c7-adf6-3b88ed266f78-presence-custody-schedules2026-09-15 17:48:35 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
First task of Feature 05 (Presence and Custody Schedules): the pure model the engine builds on.
Delivered
Verification
uv run pytest: 972 passed.
Closes task ede1dc76 (Feature 05).
presence.py — a PURE module (no HA imports, no DB access, no timezone logic): PresenceSchedule (child_id, cycle_length_weeks, anchor_date, pattern mapping week index -> frozenset of present weekdays) and PresenceOverride (child_id, inclusive start/end range, real-bool is_present, optional note) with construction that rejects every invalid shape up front: - cycle length: integer 1..4 (the 4 cap mirrors schema.py's documented CHECK); bools/floats/strings rejected. - pattern: dict required; week indices validated as non-bool ints covering 0..n-1 exactly (gaps and extras rejected); weekday sets validated as ints 0..6 (bools rejected — True would silently mean Monday; strings rejected — '02' would silently mean {0,2}); an explicitly empty week is valid (absent all week — distinct from no schedule, the engine's default). - anchor/override dates: datetime.date or strict YYYY-MM-DD (strptime round-trip; '2026-1-5' rejected). - override range: end >= start; is_present must be a real bool; note optional string. - child ids: non-bool ints (SQLite binds True as 1). Serialization: encode() emits the database's pipe-separated CSV (sorted weekdays per week, empty segment for an absent week); decode() derives the cycle length from the segment count (mirroring the schema's segment-count CHECK) and validates strictly, so decode(encode(x)) round-trips losslessly and equal patterns encode identically. Tests: 59 (construction matrix, round-trips, encode shapes, override coverage). Suite: 972 passed.Independent review — Codex gpt-5.6-terra
Round 1 — CHANGES_REQUIRED (2 MEDIUM): datetime.datetime slipped past the date check (it subclasses date) and would TypeError later comparisons — rejected explicitly; the frozen schedule leaked a mutable pattern dict bypassing total validation — now a read-only MappingProxyType over frozenset weeks.
Round 2 — APPROVED (zero findings). Suite 975 passed, reviewer-verified.