The wall dashboard's Ombi tab now shows a lunch panel with pull time and
next scheduled run. It's served from Home Assistant (:8123), a different
origin, so allow that one origin to read the one read-only endpoint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The monthly day-25 pull pushed whatever month the district's permalink
served. September's PDF was still live on 9/29, so the 9/25 pull
re-pushed September and October would not have been pulled until 10/25.
Home Assistant now dedups lunch events per date, so pull every day and
pick up a new month the evening it's posted.
- push only today onward; skip weekend dates that have items (a misread
date: Fri 9/25's menu once landed on Sat 9/26) and log a warning
- settings schema is just calendar_run_time; load_settings keeps only
known keys and startup rewrites the file, dropping stale SMS-era data
- UI: remove day-of-month field and the "no dedup" warning, fix buttons
that reverted to "This Week" labels
- docs: daily scheduler, HA queued mode + dedup, calendar is iCloud
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The district discontinued Nutrislice in favor of a monthly PDF calendar
per grade band on kyrene.org. That PDF is a graphic calendar grid, not
reliably text-extractable, so scraper.py now sends it to Claude (vision +
structured JSON output) instead of hitting a JSON API. Fetches through a
stable Finalsite resource-manager permalink that always redirects to
whichever month is currently published, so no code change is needed each
month.
Also moves the pull schedule from weekly (day-of-week list) to monthly
(day-of-month), since the source itself is now monthly — defaults to the
25th, since the district tends to post next month's PDF a few days early.
Requires a new ANTHROPIC_API_KEY in the server's .env.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013RbBoYm23muVyMZ18Ldqa6
Deletes the entire SMS path at the user's request — Gmail SMTP, phone
number management, carrier gateway settings, and the daily SMS+calendar
job are all gone. Home Assistant calendar events are now the only output.
- scraper.py: get_week_menu() returns the whole school week in one call
(the Nutrislice API is week-granular regardless of the date requested);
drops the now-unused single-day get_lunch_menu() and the send_sms()/
CLI entrypoint that only the SMS path used.
- app.py: push_week_to_calendar() fetches the week and pushes every day
with a posted menu; runs on its own schedule (calendar_run_time/
calendar_run_days, tagged "calendar" jobs) fully decoupled from
anything else, since neither calendar target supports delete-based
dedup and double-firing would create duplicate events. Manual actions
become "Push This Week to Calendar" and "Preview This Week's Menu"
(now a real dry-run over the whole week, not just today).
- Deleted the vacation/no-push date range feature: a day with no posted
menu already produces empty items, and pushing already skips empty
items — the extra vacation-range layer for suppressing days the school
*did* post a menu for was judged unnecessary and removed along with
its UI, settings field, and API endpoints.
- Deleted scheduler.py, a dead alternate entrypoint (unused by Dockerfile/
docker-compose) that only worked via the now-deleted scraper.main().
- load_settings() now merges the file over _DEFAULTS instead of returning
it verbatim — the server's existing settings.json predates this schema
and has neither calendar_run_time nor calendar_run_days, which would
otherwise silently schedule zero jobs after this deploys. Verified with
a migration test using the server's actual old-schema settings shape.
All of this was exercised for real: Docker builds, live containers against
a mock webhook (single-day and full-week pushes, per-entity dedup
behavior), a migration test with the pre-existing settings.json shape,
and a Chrome pass over the rebuilt UI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsJqAZ82Veyf5oihBr71S5
Surfaces the settings/actions that only existed at the API or env-var
level: carrier_gateway was already an accepted setting but had no form
field; HA webhook and Gmail config were invisible from the UI entirely.
Adds a calendar-only push and a side-effect-free preview so the HA sync
and scraper can be tested without texting everyone. Secrets stay env-only
since the UI has no auth — only configured/not-configured status is
exposed.
Also fixes calendar_result reporting "ok" when HA_WEBHOOK_URL wasn't set
at all, and stops the calendar-only push from clobbering the SMS job's
last_run/last_result status fields.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsJqAZ82Veyf5oihBr71S5
Every successful menu fetch now POSTs the day's items to HA_WEBHOOK_URL
(when set), so an HA automation can turn it into a calendar event. Surface
the push result as a "Calendar Sync" badge on the status card, hidden
unless HA_WEBHOOK_URL is configured.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KsJqAZ82Veyf5oihBr71S5