From cacdeb7bdae39fb5b6c7ad6a46806d053a1cb98a Mon Sep 17 00:00:00 2001 From: kyu Date: Wed, 3 Jun 2026 16:41:26 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=B8=8C=EB=A6=AC=ED=95=91=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=EB=B0=9C=EC=86=A1=20=EC=93=B0=EB=A1=9C=ED=8B=80=20+?= =?UTF-8?q?=20=EB=9E=8C=EB=8B=A4=20async=20=EC=8A=A4=EC=BC=80=EC=A4=84?= =?UTF-8?q?=EB=9F=AC=20=EB=B2=84=EA=B7=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - send_briefing에 30분 쓰로틀(_last_briefing_sent) — n8n 중복/재시도로 /briefing/send가 분당 수십 회 호출돼 같은 브리핑이 3~4건+씩 발송되던 폭주를 원천 차단. - 스케줄러 lambda:coro() → 명명 코루틴 함수(_daily_score_notify/_ta_intraday/_ta_close). APScheduler가 lambda 반환 코루틴을 await 안 해 16:30 일간리포트·TA 장마감분석이 'coroutine never awaited'로 실행 안 되던 버그 수정. Co-Authored-By: Claude Opus 4.8 (1M context) --- score-engine/main.py | 17 +++++++++++++++-- ta-engine/main.py | 10 ++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/score-engine/main.py b/score-engine/main.py index ff09f7d..737bf00 100644 --- a/score-engine/main.py +++ b/score-engine/main.py @@ -2878,9 +2878,17 @@ async def calculate_daily_scores(as_of: date | None = None, notify: bool = False # ── 정기 브리핑 ─────────────────────────────────────────── +_last_briefing_sent: datetime | None = None # 중복/폭주 차단 쓰로틀 + async def send_briefing(): - """정기 시황 브리핑 — 종목당 1카드(매매가/포지션/재무/근거 통합)""" + """정기 시황 브리핑 — 종목당 1카드(매매가/포지션/재무/근거 통합). + 30분 내 재호출은 무시 — n8n 중복/재시도 폭주로 같은 브리핑이 여러 건 발송되는 것 방지.""" + global _last_briefing_sent now = datetime.now() + if _last_briefing_sent and (now - _last_briefing_sent).total_seconds() < 1800: + logger.info("briefing.throttled", since=(now - _last_briefing_sent).total_seconds()) + return + _last_briefing_sent = now ta_redis = aioredis.Redis( host=REDIS_HOST, port=6379, password=REDIS_PASSWORD, db=5, decode_responses=True) @@ -3020,6 +3028,11 @@ async def send_briefing(): app = FastAPI(title="종목 점수 엔진") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) +async def _daily_score_notify(): + """16:30 정기 일간리포트 — 코루틴 함수로 등록해야 APScheduler가 await함 + (lambda로 감싸면 coroutine never awaited 버그).""" + await calculate_daily_scores(notify=True) + @app.on_event("startup") async def startup(): global pg_pool, redis_cl @@ -3029,7 +3042,7 @@ async def startup(): redis_cl = aioredis.Redis( host=REDIS_HOST, port=6379, password=REDIS_PASSWORD, db=3, decode_responses=True) await init_db() - scheduler.add_job(lambda: calculate_daily_scores(notify=True), "cron", + scheduler.add_job(_daily_score_notify, "cron", day_of_week="mon-fri", hour=16, minute=30, id="daily_score", replace_existing=True) # 텔레그램 정기 알림 하루 2회로 축소 (사용자 요청 2026-06-02): diff --git a/ta-engine/main.py b/ta-engine/main.py index e91ac90..a45a4c2 100644 --- a/ta-engine/main.py +++ b/ta-engine/main.py @@ -783,9 +783,9 @@ async def startup(): redis_cl = aioredis.Redis( host=REDIS_HOST, port=6379, password=REDIS_PASSWORD, db=5, decode_responses=True) await init_db() - scheduler.add_job(lambda: job_analyze(limit=500), "cron", day_of_week="mon-fri", + scheduler.add_job(_ta_intraday, "cron", day_of_week="mon-fri", hour="9-16", minute="*/30", id="ta_30m", replace_existing=True) - scheduler.add_job(lambda: job_analyze(limit=0), "cron", day_of_week="mon-fri", + scheduler.add_job(_ta_close, "cron", day_of_week="mon-fri", hour=16, minute=15, id="ta_close", replace_existing=True) scheduler.start() logger.info("ta-engine.started") @@ -870,6 +870,12 @@ async def buy_candidates(limit: int = Query(default=20)): result.append(d) return result +async def _ta_intraday(): # 코루틴 함수로 등록 (lambda 감싸면 APScheduler가 await 못함) + await job_analyze(limit=500) + +async def _ta_close(): + await job_analyze(limit=0) + @app.post("/analyze/all") async def analyze_all(limit: int = 0): asyncio.create_task(job_analyze(limit=limit))