From 138f689c9870d5b3d23164137b01571d6d4a3a3f Mon Sep 17 00:00:00 2001 From: hyungi Date: Tue, 12 May 2026 21:30:34 +0000 Subject: [PATCH 1/2] fix(scheduler): pass KST timezone to all CronTriggers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AsyncIOScheduler(timezone="Asia/Seoul") 의 scheduler-level timezone 이 CronTrigger 에 자동 전파되지 않아 6 cron 모두 UTC 로 fire 되던 버그. 영향 (모두 9h 오차): - morning_briefing 의도 05:10 KST → 실제 14:10 KST - daily_digest 의도 20:00 KST → 실제 05:00 KST (다음날) - global_digest 의도 04:00 KST → 실제 13:00 KST - law_monitor 의도 07:00 KST → 실제 16:00 KST - mailplus_morning 의도 07:00 KST → 실제 16:00 KST - mailplus_evening 의도 18:00 KST → 실제 03:00 KST (다음날) Fix: 모든 CronTrigger 에 timezone=KST (= ZoneInfo("Asia/Seoul")) 명시. 검증 (재시작 후): law_monitor next: 2026-05-13 07:00 KST mailplus_morning next: 2026-05-13 07:00 KST mailplus_evening next: 2026-05-13 18:00 KST daily_digest next: 2026-05-13 20:00 KST global_digest next: 2026-05-14 04:00 KST morning_briefing next: 2026-05-14 05:10 KST --- app/main.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/main.py b/app/main.py index e21dfd7..c0cf4b3 100644 --- a/app/main.py +++ b/app/main.py @@ -38,6 +38,9 @@ async def lifespan(app: FastAPI): from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.triggers.cron import CronTrigger + from zoneinfo import ZoneInfo + + KST = ZoneInfo("Asia/Seoul") from services.search.query_analyzer import prewarm_analyzer from workers.briefing_worker import run as morning_briefing_run from workers.daily_digest import run as daily_digest_run @@ -90,12 +93,12 @@ async def lifespan(app: FastAPI): # safety > law > manual 우선순위로 25건씩. 6720 레거시 → 야간당 ~150건 → 약 45일 소화. scheduler.add_job(tier_backfill_run, "interval", minutes=30, id="tier_backfill") # 일일 스케줄 (KST) - scheduler.add_job(law_monitor_run, CronTrigger(hour=7), id="law_monitor") - scheduler.add_job(mailplus_run, CronTrigger(hour=7), id="mailplus_morning") - scheduler.add_job(mailplus_run, CronTrigger(hour=18), id="mailplus_evening") - scheduler.add_job(daily_digest_run, CronTrigger(hour=20), id="daily_digest") - scheduler.add_job(global_digest_run, CronTrigger(hour=4, minute=0), id="global_digest") - scheduler.add_job(morning_briefing_run, CronTrigger(hour=5, minute=10), id="morning_briefing") + scheduler.add_job(law_monitor_run, CronTrigger(hour=7, timezone=KST), id="law_monitor") + scheduler.add_job(mailplus_run, CronTrigger(hour=7, timezone=KST), id="mailplus_morning") + scheduler.add_job(mailplus_run, CronTrigger(hour=18, timezone=KST), id="mailplus_evening") + scheduler.add_job(daily_digest_run, CronTrigger(hour=20, timezone=KST), id="daily_digest") + scheduler.add_job(global_digest_run, CronTrigger(hour=4, minute=0, timezone=KST), id="global_digest") + scheduler.add_job(morning_briefing_run, CronTrigger(hour=5, minute=10, timezone=KST), id="morning_briefing") scheduler.add_job(news_collector_run, "interval", hours=6, id="news_collector") scheduler.start() From 2dbbeac1c7e6e75eef65d6c1f2231fbac695953c Mon Sep 17 00:00:00 2001 From: hyungi Date: Tue, 12 May 2026 21:30:41 +0000 Subject: [PATCH 2/2] fix(daily_digest): cast today to date object for KST comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 매일 20:00 KST cron fire 시 fail: UndefinedFunctionError: operator does not exist: date = character varying 원인: today 가 strftime("%Y-%m-%d") 로 string, func.date(created_at) 가 date 타입. PostgreSQL 가 date = string 비교 거부. Fix: today = datetime.now(ZoneInfo("Asia/Seoul")).date() — date 객체로. KST 기준은 scheduler cron 이 KST 20:00 에 fire 되므로 자연 일치. scope: app/workers/daily_digest.py:24 --- app/workers/daily_digest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/workers/daily_digest.py b/app/workers/daily_digest.py index 213a9be..edd31e0 100644 --- a/app/workers/daily_digest.py +++ b/app/workers/daily_digest.py @@ -6,6 +6,7 @@ DEVONthink/OmniFocus → PostgreSQL/CalDAV 쿼리로 전환. import os from datetime import datetime, timezone +from zoneinfo import ZoneInfo from pathlib import Path from sqlalchemy import func, select, text @@ -21,7 +22,8 @@ logger = setup_logger("daily_digest") async def run(): """일일 다이제스트 생성 + 저장 + 발송""" - today = datetime.now(timezone.utc).strftime("%Y-%m-%d") + # KST 기준 오늘 (cron 이 KST timezone fix 후 20:00 KST 에 fire). date 객체로 비교 — Document.created_at::date 와 직접 매칭. + today = datetime.now(ZoneInfo("Asia/Seoul")).date() sections = [] async with async_session() as session: