fix: 브리핑 중복발송 쓰로틀 + 람다 async 스케줄러 버그
- 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) <noreply@anthropic.com>
This commit is contained in:
+15
-2
@@ -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):
|
||||
|
||||
+8
-2
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user