- Add backend routines management with add, get, update, delete, and list functionalities. - Create models for Routine, RoutineItem, RoutineSchedule, and RoutineExtension. - Develop event types for routine confirmation and modification. - Implement frontend components for routine assignment, confirmation dialog, and routine management views. - Add unit tests for routine API and integration tests for routine CRUD flow. - Create end-to-end test plan for routines feature covering parent and child interactions.
9.7 KiB
Feature: Push notification for chores that are going to expire in the next hour.
Overview
When a scheduled chore is going to expire within the next hour and it has not be marked completed or pending, a push notification should go out to tell the user that the scheduled chore needs to be finished soon.
Goal: When a chore is about to expire (within the hour) send a push notification to the user and allow the user to acknowlege it.
User Story: As a parent, I should receive a notification when a scheduled chore is going to expire soon. When I click on the notification, I should be brought to the page with the chore.
Rules:
- If there are multiple chores that are going to expire, present them in the push notification as well.
- The notification should show which child the chore belongs to.
- If a chore is scheduled to expire between 9:00pm to 9:15pm, the notification for it should go out at 8:00pm. That should give the earlier hour expiration plenty of notice.
Questions:
- If there are multiple chores in the push, should we say there are multiple chores ending next hour or should we list each by name?
- Should we section the notification if there are multiple children? [ex. Child1 chores: ... Child2 chores: ...]
- Should we reuse one of the other hourly schedulers or create a new one? should we refactor and have one hourly scheduler that does multiple operations?
Data Model Changes
Backend Model
No new models required. The feature reuses existing models:
ChoreSchedule— provides schedule mode, day configs, deadline times, and enabled statePendingConfirmation— queried to determine if a chore is already pending or approved (either status means the child has acted on it; no notification is needed)
Frontend Model
No changes to TypeScript interfaces.
Backend Implementation
backend/utils/schedule_utils.py (new)
A Python port of the three core functions from frontend/vue-app/src/common/scheduleUtils.ts. The frontend is the authoritative source for schedule math; this file mirrors that logic server-side so the scheduler can determine which chores are active and when they expire without making HTTP calls or duplicating logic ad-hoc.
interval_hits_today(anchor_date, interval_days, local_date)— returns True if an interval-mode schedule fires on the given local dateis_scheduled_today(schedule_dict, local_date)— returns True for days-mode (weekday match) or interval-mode (anchor + interval math); paused schedules always return Trueget_due_time_today(schedule_dict, local_date)— returns(hour, minute)orNone(None means Anytime or paused — no expiry)
Note on weekday convention: the app uses JS/Python convention where Sunday = 0 and Saturday = 6 (same as DayConfig.day). Python's date.weekday() returns Monday = 0, so a conversion of (weekday + 1) % 7 is applied when comparing.
backend/utils/chore_expiry_notification_scheduler.py (new)
get_expiring_chores_for_user(user_id, tz_str, now_dt)— loads all children for the user, iterates their chore tasks, checks for an active schedule with a deadline falling in(now, now + 75 min], and excludes any chore that already has aPendingConfirmationrecord (any status). Returns a list of dicts with child and task metadata._build_push_payload(expiring)— constructs the push payload. Single chore: title names the chore and deep-link fields are set for direct navigation. Multiple chores: generic title,child_idandentity_idare null, and the body lists chores grouped by child name (e.g.Alex: Clean Room, Make Bed\nSam: Trash).send_chore_expiry_notifications_for_user(user_id, tz_str)— callsget_expiring_chores_for_user, builds the payload, and callssend_push_to_user.run_chore_expiry_check(app)— skips the e2e environment, loops all verified users, skips users withpush_notifications_enabled=False, and callssend_chore_expiry_notifications_for_userfor each eligible user.start_chore_expiry_notification_scheduler(app)— registers an APSchedulercronjob atminute=0(top of every hour), consistent with the existing schedulers.
Push payload type: chore_expiring_soon. The absence of approve_token and deny_token fields is intentional and is what triggers the service worker to suppress action buttons.
backend/main.py (modified)
Import added and start_chore_expiry_notification_scheduler(app) called after the existing schedulers.
Backend Tests
get_expiring_chores_for_user: chore in 75-min window → includedget_expiring_chores_for_user: deadline in the past → excludedget_expiring_chores_for_user: deadline beyond 75-min window → excludedget_expiring_chores_for_user: Anytime schedule (no deadline) → excludedget_expiring_chores_for_user: chore not scheduled today (wrong weekday) → excludedget_expiring_chores_for_user: status=pending confirmation exists → excludedget_expiring_chores_for_user: status=approved confirmation exists → excludedget_expiring_chores_for_user: no schedule → excludedget_expiring_chores_for_user: paused schedule (enabled=False) → excludedget_expiring_chores_for_user: interval mode hits today → includedget_expiring_chores_for_user: interval mode misses today → excludedget_expiring_chores_for_user: multiple children returns entries for both_build_push_payload: single chore → named title, deep-link fields set_build_push_payload: multiple chores → generic title, child_id/entity_id null_build_push_payload: body groups chore names by child_build_push_payload: no approve_token or reject_token in payloadsend_chore_expiry_notifications_for_user: calls send_push_to_user with correct payloadsend_chore_expiry_notifications_for_user: no push when no chores expiringrun_chore_expiry_check: skips when DB_ENV=e2erun_chore_expiry_check: skips user with push_notifications_enabled=Falserun_chore_expiry_check: skips unverified userrun_chore_expiry_check: sends push for eligible verified user with push enabledschedule_utils: interval_hits_today — same day as anchor hitsschedule_utils: interval_hits_today — correct interval day hitsschedule_utils: interval_hits_today — non-interval day missesschedule_utils: interval_hits_today — date before anchor missesschedule_utils: interval_hits_today — empty anchor hits todayschedule_utils: is_scheduled_today — days mode matching weekdayschedule_utils: is_scheduled_today — days mode non-matching weekdayschedule_utils: is_scheduled_today — paused always trueschedule_utils: is_scheduled_today — interval mode hitschedule_utils: is_scheduled_today — interval mode missschedule_utils: get_due_time_today — days mode returns (hour, minute)schedule_utils: get_due_time_today — Anytime returns Noneschedule_utils: get_due_time_today — wrong day returns Noneschedule_utils: get_due_time_today — paused returns Noneschedule_utils: get_due_time_today — interval mode returns (hour, minute)schedule_utils: get_due_time_today — interval Anytime returns None
Frontend Implementation
frontend/vue-app/public/sw.js (modified)
In the push event handler, the actions array passed to showNotification is now conditionally set: when payload.type === 'chore_expiring_soon' the array is empty (no buttons); otherwise Approve and Reject buttons are shown as before. The notificationclick handler requires no changes — tapping the notification body already navigates to /parent/{child_id}?scrollTo={entity_id} when those fields are present, and falls back to /parent when they are null (multi-chore case).
Frontend Tests
No automated tests added. The sw.js change is a simple conditional and is covered by manual verification: triggering a chore_expiring_soon push and confirming no action buttons appear in the browser notification, and that tapping opens the correct URL.
Future Considerations
- 75-minute window double-notification: A chore deadline that falls in the overlap of two consecutive 75-minute windows (e.g. 9:10 pm appears in both the 8:00 pm run's window and the 9:00 pm run's window) will produce two notifications. This is a known and accepted trade-off; no deduplication is applied. A future improvement could track notified chores per day in a lightweight DB table to prevent repeats.
- Task extensions:
TaskExtensionsets anextension_datethat allows a chore to carry over to another day. Extended chores are treated identically to regular scheduled chores — the schedule deadline time is used as-is. A future enhancement could optionally adjust the deadline time for extended chores.
Acceptance Criteria (Definition of Done)
Backend
backend/utils/schedule_utils.pycreated — Python port of schedule math fromscheduleUtils.tsbackend/utils/chore_expiry_notification_scheduler.pycreated — hourly scheduler with 75-min look-ahead window- Chores with
Anytimeschedule (no deadline) are not notified - Chores with a
pendingorapprovedconfirmation are not notified - Chores on paused schedules are not notified
- Users with
push_notifications_enabled=Falseare skipped - Unverified users are skipped
- e2e environment is skipped
- Scheduler registered in
backend/main.py - All backend tests pass
Frontend
sw.jsupdated —chore_expiring_soonnotifications show no Approve/Reject action buttons- Tapping a single-chore notification navigates to the child's chore page
- Tapping a multi-chore notification navigates to
/parent