Task: e4a9d31b-08a7-4465-b954-180b9e3c8991 (Feature 16 — NestQuest API Service)
Done-condition: admin routes for presence — set a child's repeating schedule (cycle_length_weeks, anchor_date, pattern) and create/delete a date-range presence override. Each validates input and calls the core presence layer with no business logic in the handler. Tests as an admin confirm the schedule upserts, an override is created and then deleted, and the child's resolved presence for an affected date changes accordingly.
What this does:
New core presence write layer custom_components/nestquest/core/presence_management.py (HA-free): set_presence_schedule (model-validated PresenceSchedule, upsert by child, then regenerate_for_child, returns the stored schedule decoded), create_presence_override (model-validated PresenceOverride, DAO create with the same-child no-overlap policy, then regenerate, returns the record with its id), delete_presence_override (lookup first — unknown id raises ValueError prefixed override_id:, delete, then regenerate that override's child). This is the ONE implementation the API and the integration share.
The integration's three presence service handlers (services.py) are now thin wrappers over that layer (same HA service signatures, _generation_kwargs threading and _with_errors wrapping; the unknown-override HomeAssistantError message is unchanged).
Admin routes on the existing admin router (the ONE require_admin dependency — no new auth check): PUT /api/v1/admin/children/{child_id}/presence-schedule, POST /api/v1/admin/presence-overrides (201, returns the stored override with its id), DELETE /api/v1/admin/presence-overrides/{override_id} (answers {"status": "ok"}). Handlers are thin adapters; presence validation stays in the model.
Error mapping, documented in api/routes_admin.py: unknown child (schedule/override create) → 404 via the shared _raise_child_error; unknown override (delete) → 404 via the new _raise_presence_override_error keyed on the core's override_id: prefix; every other core ValueError (rejected schedule/override: cycle length, pattern coverage, anchor date, malformed/inverted dates, overlapping range) → 422. Unexpected errors are not mapped, so they surface as 500 rather than being swallowed as 4xx.
api/nestquest_core.py re-exports core_presence, core_presence_management and core_dao_presence following the existing pattern; a presence_management.py shim re-exports the core module for the integration package (same pattern as the other Feature 15 shims).
Tests (tests/test_api_admin_presence.py, shared admin JWT harness, as an admin): the schedule upserts and setting again replaces it (same row id, new values — one schedule per child); an override is created (201 with id, row present) and then deleted (row gone); the child's resolved presence (PresenceEngine.is_present built from the stored rows) flips with each write; an all-absent schedule set through the route deletes the child's future quest instances (regeneration side effect); non-admin JWT → 403, absent credential → 401 with nothing written; 404/422 mapping covered for unknown child/override, rejected schedules and overrides.
Checks (TZ=UTC, the bare shell TZ has 19 pre-existing failures on the pristine base too):
TZ=UTC uv run pytest -q → 1718 passed in 10.43s (baseline 1699 + 19 new)
TZ=UTC uv run pytest -q tests/test_api_admin_presence.py → 19 passed in 0.30s
Task: e4a9d31b-08a7-4465-b954-180b9e3c8991 (Feature 16 — NestQuest API Service)
Done-condition: admin routes for presence — set a child's repeating schedule (cycle_length_weeks, anchor_date, pattern) and create/delete a date-range presence override. Each validates input and calls the core presence layer with no business logic in the handler. Tests as an admin confirm the schedule upserts, an override is created and then deleted, and the child's resolved presence for an affected date changes accordingly.
What this does:
- New core presence write layer `custom_components/nestquest/core/presence_management.py` (HA-free): `set_presence_schedule` (model-validated PresenceSchedule, upsert by child, then `regenerate_for_child`, returns the stored schedule decoded), `create_presence_override` (model-validated PresenceOverride, DAO create with the same-child no-overlap policy, then regenerate, returns the record with its id), `delete_presence_override` (lookup first — unknown id raises ValueError prefixed `override_id:`, delete, then regenerate that override's child). This is the ONE implementation the API and the integration share.
- The integration's three presence service handlers (`services.py`) are now thin wrappers over that layer (same HA service signatures, `_generation_kwargs` threading and `_with_errors` wrapping; the unknown-override HomeAssistantError message is unchanged).
- Admin routes on the existing admin router (the ONE `require_admin` dependency — no new auth check): `PUT /api/v1/admin/children/{child_id}/presence-schedule`, `POST /api/v1/admin/presence-overrides` (201, returns the stored override with its id), `DELETE /api/v1/admin/presence-overrides/{override_id}` (answers `{"status": "ok"}`). Handlers are thin adapters; presence validation stays in the model.
- Error mapping, documented in `api/routes_admin.py`: unknown child (schedule/override create) → 404 via the shared `_raise_child_error`; unknown override (delete) → 404 via the new `_raise_presence_override_error` keyed on the core's `override_id:` prefix; every other core ValueError (rejected schedule/override: cycle length, pattern coverage, anchor date, malformed/inverted dates, overlapping range) → 422. Unexpected errors are not mapped, so they surface as 500 rather than being swallowed as 4xx.
- `api/nestquest_core.py` re-exports `core_presence`, `core_presence_management` and `core_dao_presence` following the existing pattern; a `presence_management.py` shim re-exports the core module for the integration package (same pattern as the other Feature 15 shims).
Tests (`tests/test_api_admin_presence.py`, shared admin JWT harness, as an admin): the schedule upserts and setting again replaces it (same row id, new values — one schedule per child); an override is created (201 with id, row present) and then deleted (row gone); the child's resolved presence (`PresenceEngine.is_present` built from the stored rows) flips with each write; an all-absent schedule set through the route deletes the child's future quest instances (regeneration side effect); non-admin JWT → 403, absent credential → 401 with nothing written; 404/422 mapping covered for unknown child/override, rejected schedules and overrides.
Checks (TZ=UTC, the bare shell TZ has 19 pre-existing failures on the pristine base too):
- `TZ=UTC uv run pytest -q` → 1718 passed in 10.43s (baseline 1699 + 19 new)
- `TZ=UTC uv run pytest -q tests/test_api_admin_presence.py` → 19 passed in 0.30s
The presence business operations (schedule upsert, override
create/delete, each followed by regenerate_for_child) lived only in the
HA-coupled service handlers, so the API admin plane could not reuse
them. Extract them into core/presence_management.py — model-validated
PresenceSchedule/PresenceOverride construction, the typed DAO calls
and the regeneration side effect in ONE Home-Assistant-free
implementation — and reduce the three HA service handlers to thin
wrappers over it.
Expose the admin plane's presence routes on the existing admin router
(PUT /children/{child_id}/presence-schedule, POST
/presence-overrides, DELETE /presence-overrides/{override_id}): the
body models carry only the shape, validation stays in the presence
model, and the error mapping stays narrow (unknown child/override 404,
rejected schedule/override 422). Tests drive all three routes as an
admin, prove the schedule upserts in place, an override is created and
then deleted, and the child's resolved presence (PresenceEngine)
changes accordingly.
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.
Task: e4a9d31b-08a7-4465-b954-180b9e3c8991 (Feature 16 — NestQuest API Service)
Done-condition: admin routes for presence — set a child's repeating schedule (cycle_length_weeks, anchor_date, pattern) and create/delete a date-range presence override. Each validates input and calls the core presence layer with no business logic in the handler. Tests as an admin confirm the schedule upserts, an override is created and then deleted, and the child's resolved presence for an affected date changes accordingly.
What this does:
custom_components/nestquest/core/presence_management.py(HA-free):set_presence_schedule(model-validated PresenceSchedule, upsert by child, thenregenerate_for_child, returns the stored schedule decoded),create_presence_override(model-validated PresenceOverride, DAO create with the same-child no-overlap policy, then regenerate, returns the record with its id),delete_presence_override(lookup first — unknown id raises ValueError prefixedoverride_id:, delete, then regenerate that override's child). This is the ONE implementation the API and the integration share.services.py) are now thin wrappers over that layer (same HA service signatures,_generation_kwargsthreading and_with_errorswrapping; the unknown-override HomeAssistantError message is unchanged).require_admindependency — no new auth check):PUT /api/v1/admin/children/{child_id}/presence-schedule,POST /api/v1/admin/presence-overrides(201, returns the stored override with its id),DELETE /api/v1/admin/presence-overrides/{override_id}(answers{"status": "ok"}). Handlers are thin adapters; presence validation stays in the model.api/routes_admin.py: unknown child (schedule/override create) → 404 via the shared_raise_child_error; unknown override (delete) → 404 via the new_raise_presence_override_errorkeyed on the core'soverride_id:prefix; every other core ValueError (rejected schedule/override: cycle length, pattern coverage, anchor date, malformed/inverted dates, overlapping range) → 422. Unexpected errors are not mapped, so they surface as 500 rather than being swallowed as 4xx.api/nestquest_core.pyre-exportscore_presence,core_presence_managementandcore_dao_presencefollowing the existing pattern; apresence_management.pyshim re-exports the core module for the integration package (same pattern as the other Feature 15 shims).Tests (
tests/test_api_admin_presence.py, shared admin JWT harness, as an admin): the schedule upserts and setting again replaces it (same row id, new values — one schedule per child); an override is created (201 with id, row present) and then deleted (row gone); the child's resolved presence (PresenceEngine.is_presentbuilt from the stored rows) flips with each write; an all-absent schedule set through the route deletes the child's future quest instances (regeneration side effect); non-admin JWT → 403, absent credential → 401 with nothing written; 404/422 mapping covered for unknown child/override, rejected schedules and overrides.Checks (TZ=UTC, the bare shell TZ has 19 pre-existing failures on the pristine base too):
TZ=UTC uv run pytest -q→ 1718 passed in 10.43s (baseline 1699 + 19 new)TZ=UTC uv run pytest -q tests/test_api_admin_presence.py→ 19 passed in 0.30sThe presence business operations (schedule upsert, override create/delete, each followed by regenerate_for_child) lived only in the HA-coupled service handlers, so the API admin plane could not reuse them. Extract them into core/presence_management.py — model-validated PresenceSchedule/PresenceOverride construction, the typed DAO calls and the regeneration side effect in ONE Home-Assistant-free implementation — and reduce the three HA service handlers to thin wrappers over it. Expose the admin plane's presence routes on the existing admin router (PUT /children/{child_id}/presence-schedule, POST /presence-overrides, DELETE /presence-overrides/{override_id}): the body models carry only the shape, validation stays in the presence model, and the error mapping stays narrow (unknown child/override 404, rejected schedule/override 422). Tests drive all three routes as an admin, prove the schedule upserts in place, an override is created and then deleted, and the child's resolved presence (PresenceEngine) changes accordingly.Independent Claude Sonnet 5 review completed for this exact commit.