From 7972ecb948eaea9bd30211e330810327a2576243 Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Thu, 7 May 2026 06:46:39 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20APScheduler=20entry=20point=20=E2=80=94?= =?UTF-8?q?=205-job=20weekly=20cadence=20(Pacific)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 (1M context) --- backend/app/scheduler/__init__.py | 0 backend/app/scheduler/__main__.py | 68 +++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 backend/app/scheduler/__init__.py create mode 100644 backend/app/scheduler/__main__.py diff --git a/backend/app/scheduler/__init__.py b/backend/app/scheduler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/scheduler/__main__.py b/backend/app/scheduler/__main__.py new file mode 100644 index 0000000..6fd6bd0 --- /dev/null +++ b/backend/app/scheduler/__main__.py @@ -0,0 +1,68 @@ +""" +APScheduler entry point for the scheduler container. + +Run: python -m app.scheduler + +Weekly cadence (America/Los_Angeles): + Fri 02:00 — scrape (Lucky weekend prices live by ~midnight) + Fri 05:00 — generate (fresh grocery data) + Fri 06:00 — email (per-voter approval links) + Fri 17:00 — deadline (pending items resolved per family policy) + Fri 18:00 — finalize (shopping-list email) +""" +import logging + +from apscheduler.schedulers.blocking import BlockingScheduler +from apscheduler.triggers.cron import CronTrigger + +from app.services.orchestrator.runner import run_step + +logging.basicConfig( + level="INFO", + format="%(asctime)s %(levelname)s %(name)s %(message)s", +) +logger = logging.getLogger(__name__) + +TZ = "America/Los_Angeles" + +scheduler = BlockingScheduler(timezone=TZ) + +scheduler.add_job( + lambda: run_step("scrape"), + CronTrigger(day_of_week="fri", hour=2, minute=0, timezone=TZ), + id="weekly_scrape", + name="Weekly scrape (Fri 02:00 PT)", +) +scheduler.add_job( + lambda: run_step("generate"), + CronTrigger(day_of_week="fri", hour=5, minute=0, timezone=TZ), + id="weekly_generate", + name="Weekly generate (Fri 05:00 PT)", +) +scheduler.add_job( + lambda: run_step("email"), + CronTrigger(day_of_week="fri", hour=6, minute=0, timezone=TZ), + id="weekly_email", + name="Weekly email (Fri 06:00 PT)", +) +scheduler.add_job( + lambda: run_step("deadline"), + CronTrigger(day_of_week="fri", hour=17, minute=0, timezone=TZ), + id="weekly_deadline", + name="Weekly deadline (Fri 17:00 PT)", +) +scheduler.add_job( + lambda: run_step("finalize"), + CronTrigger(day_of_week="fri", hour=18, minute=0, timezone=TZ), + id="weekly_finalize", + name="Weekly finalize (Fri 18:00 PT)", +) + +if __name__ == "__main__": + logger.info("Starting MealPlanner scheduler (tz=%s)", TZ) + for job in scheduler.get_jobs(): + logger.info(" Registered: %s", job.name) + try: + scheduler.start() + except (KeyboardInterrupt, SystemExit): + logger.info("Scheduler stopped")