# backend/app/automation/suggester.py # 반복 패턴 탐지 → automation_suggestion. notification.triaged 구독 + 수동 scan 둘 다 지원. from __future__ import annotations import uuid from sqlmodel import Session, select from ..models import AutomationRunLog, AutomationSuggestion from .event_bus import Event, EventBus from .events import AUTOMATION_SUGGESTED, NOTIFICATION_TRIAGED # 데모용 결정적 패턴 규칙: (감지 텍스트 부분일치, 제안) PATTERNS = [ { "match": "다이제스트", "pattern": "저녁 다이제스트를 자주 쓰시네요.", "offer": { "name": "아침에도 다이제스트", "cat": "mail", "trigger": "08:00", "cond": None, "action": "밤사이 메일 묶음 브리핑", }, }, ] def scan(s: Session, bus: EventBus | None = None) -> list[AutomationSuggestion]: """run_log 를 훑어 반복 패턴이 임계 이상이면 제안 생성(중복 방지). 생성된 제안마다 automation.suggested 발행(bus 가 주어지면).""" logs = s.exec(select(AutomationRunLog)).all() existing = {x.offer_name for x in s.exec(select(AutomationSuggestion)).all()} created: list[AutomationSuggestion] = [] for pat in PATTERNS: hits = [log for log in logs if pat["match"] in (log.text or "")] if len(hits) >= 3 and pat["offer"]["name"] not in existing: # 임계=3 o = pat["offer"] sug = AutomationSuggestion( id="asug-" + uuid.uuid4().hex[:8], pattern=pat["pattern"], offer_name=o["name"], offer_cat=o["cat"], offer_trigger=o["trigger"], offer_cond=o["cond"], offer_action=o["action"], status="open", ) s.add(sug) created.append(sug) s.commit() if bus: for sug in created: bus.publish( AUTOMATION_SUGGESTED, {"suggestion_id": sug.id, "offer_name": sug.offer_name, "pattern": sug.pattern}, ) return created def register(bus: EventBus, session_factory) -> None: """버스에 suggester 핸들러 등록 — notification.triaged 구독. phase-9 알림 트리아지의 '나중에' 반복 패턴을 입력으로 scan → automation.suggested 체인.""" def on_triaged(ev: Event) -> None: with session_factory() as s: scan(s, bus) bus.subscribe(NOTIFICATION_TRIAGED, on_triaged)