@startuml ai-트렌드분석및추천 !theme mono title AI Service - 트렌드 분석 및 이벤트 추천 (내부 시퀀스) actor Client participant "Kafka Consumer" as Consumer <> participant "JobMessageHandler" as Handler <> participant "AIRecommendationService" as Service <> participant "TrendAnalysisEngine" as TrendEngine <> participant "RecommendationEngine" as RecommendEngine <> participant "CacheManager" as Cache <> participant "CircuitBreakerManager" as CB <> participant "ExternalAIClient" as AIClient <> participant "JobStateManager" as JobState <> participant "Redis" as Redis <> participant "External AI API" as ExternalAPI <> participant "Kafka Producer" as Producer <> note over Consumer: Kafka ai-job Topic 구독\nConsumer Group: ai-service-group == 1. Job 메시지 수신 == Consumer -> Handler: onMessage(jobMessage)\n{jobId, eventDraftId, 목적, 업종, 지역, 매장정보} activate Handler Handler -> Handler: 메시지 유효성 검증 note right 검증 항목: - jobId 존재 여부 - eventDraftId 유효성 - 필수 파라미터 (목적, 업종, 지역) end note alt 유효하지 않은 메시지 Handler -> Producer: DLQ 발행 (Dead Letter Queue)\n{jobId, error: INVALID_MESSAGE} Handler --> Consumer: ACK (메시지 처리 완료) note over Handler: 잘못된 메시지는 DLQ로 이동\n수동 검토 필요 else 유효한 메시지 Handler -> JobState: updateJobStatus(jobId, PROCESSING) JobState -> Redis: SET job:{jobId}:status = PROCESSING Redis --> JobState: OK JobState --> Handler: 상태 업데이트 완료 Handler -> Service: generateRecommendations(\neventDraftId, 목적, 업종, 지역, 매장정보) activate Service == 2. 트렌드 분석 == Service -> TrendEngine: analyzeTrends(업종, 지역, 목적) activate TrendEngine TrendEngine -> Cache: getCachedTrend(업종, 지역) Cache -> Redis: GET trend:{업종}:{지역} Redis --> Cache: 캐시 결과 alt 캐시 히트 Cache --> TrendEngine: 캐시된 트렌드 데이터 note right 캐시 키: trend:{업종}:{지역} TTL: 1시간 데이터: { industry_trends, regional_characteristics, seasonal_patterns } end note else 캐시 미스 note right of TrendEngine **트렌드 분석 입력 데이터** - 업종 정보 - 지역 정보 - 현재 시즌 (계절, 월) - 이벤트 목적 **외부 AI API 호출** - 과거 이벤트 데이터 사용 안 함 - 실시간 시장 트렌드 분석 - 업종별/지역별 일반적 특성 end note TrendEngine -> CB: executeWithCircuitBreaker(\nAI API 트렌드 분석 호출) activate CB CB -> CB: Circuit Breaker 상태 확인 note right **Circuit Breaker 설정** - Failure Rate Threshold: 50% - Timeout: 5분 (300초) - Half-Open Wait Duration: 1분 (60초) - Permitted Calls in Half-Open: 3 - Sliding Window Size: 10 end note alt Circuit CLOSED (정상) CB -> AIClient: callAIAPI(\nmethod: "trendAnalysis",\nprompt: 트렌드 분석 프롬프트,\ntimeout: 5분) activate AIClient AIClient -> AIClient: 프롬프트 구성 note right of AIClient **AI 프롬프트 구성** "당신은 마케팅 트렌드 분석 전문가입니다. **입력 정보** - 업종: {업종} - 지역: {지역} - 현재 시즌: {계절/월} - 이벤트 목적: {목적} **분석 요청사항** 1. 업종별 일반적 트렌드 (업종 특성 기반 효과적인 이벤트 유형) 2. 지역별 특성 (지역 고객 특성, 선호도) 3. 시즌별 추천 (현재 시기에 적합한 이벤트)" end note AIClient -> ExternalAPI: AI API 호출\nPOST /api/v1/analyze\nAuthorization: Bearer {API_KEY}\nTimeout: 5분\nPayload: {업종, 지역, 시즌, 목적} activate ExternalAPI ExternalAPI --> AIClient: 200 OK\n{"industryTrend": "...",\n"regionalCharacteristics": "...",\n"seasonalRecommendation": "..."} deactivate ExternalAPI AIClient -> AIClient: 응답 검증 및 파싱 AIClient --> CB: 분석 결과 deactivate AIClient CB -> CB: 성공 기록 (Circuit Breaker) CB --> TrendEngine: 트렌드 분석 결과 deactivate CB TrendEngine -> Cache: cacheTrend(\nkey: trend:{업종}:{지역},\ndata: 분석결과,\nTTL: 1시간) Cache -> Redis: SETEX trend:{업종}:{지역} 3600 {분석결과} Redis --> Cache: OK Cache --> TrendEngine: 캐싱 완료 else Circuit OPEN (장애) CB --> TrendEngine: CircuitBreakerOpenException TrendEngine -> TrendEngine: Fallback 실행\n(기본 트렌드 데이터 사용) note right Fallback 전략: - 이전 캐시 데이터 반환 - 또는 기본 트렌드 템플릿 사용 - 클라이언트에 안내 메시지 포함 end note else Circuit HALF-OPEN (복구 시도) CB -> AIClient: 제한된 요청 허용 (3개) AIClient -> ExternalAPI: POST /api/v1/analyze ExternalAPI --> AIClient: 200 OK AIClient --> CB: 성공 CB -> CB: 연속 성공 시 CLOSED로 전환 CB --> TrendEngine: 트렌드 분석 결과 else Timeout (5분 초과) CB --> TrendEngine: TimeoutException note right of TrendEngine **Timeout 처리** - 5분 초과 시 즉시 실패 - Fallback: 기본 트렌드 사용 - 사용자에게 안내 메시지 제공 end note TrendEngine -> TrendEngine: Fallback 실행\n(기본 트렌드 템플릿 사용) end end TrendEngine --> Service: 트렌드 분석 완료\n{업종트렌드, 지역특성, 시즌특성} deactivate TrendEngine == 3. 이벤트 추천 생성 (3가지 옵션) == Service -> RecommendEngine: generateRecommendations(\n목적, 트렌드, 매장정보) activate RecommendEngine RecommendEngine -> RecommendEngine: 추천 컨텍스트 구성 note right 추천 입력: - 이벤트 목적 (신규 고객 유치 등) - 트렌드 분석 결과 - 매장 정보 (업종, 위치, 크기) - 예산 범위 (저/중/고) end note group parallel RecommendEngine -> CB: executeWithCircuitBreaker(\nAI API 추천 생성 - 옵션 1: 저비용) activate CB CB -> AIClient: callAIAPI(\nmethod: "generateRecommendation",\nprompt: 저비용 추천 프롬프트,\ntimeout: 5분) activate AIClient AIClient -> AIClient: 프롬프트 구성 note right 옵션 1 프롬프트: "저비용, 높은 참여율 중심 이벤트 기획 목적: {목적} 트렌드: {트렌드} 매장: {매장정보} 출력 형식: - 이벤트 제목 - 추천 경품 (예산: 저) - 참여 방법 (난이도: 낮음) - 예상 참여자 수 - 예상 비용 - 예상 ROI" end note AIClient -> ExternalAPI: POST /api/v1/recommend\n(저비용 옵션) ExternalAPI --> AIClient: 200 OK\n{추천안 1} AIClient --> CB: 추천안 1 deactivate AIClient CB --> RecommendEngine: 옵션 1 완료 deactivate CB RecommendEngine -> CB: executeWithCircuitBreaker(\nAI API 추천 생성 - 옵션 2: 중비용) activate CB CB -> AIClient: callAIAPI(\nmethod: "generateRecommendation",\nprompt: 중비용 추천 프롬프트,\ntimeout: 5분) activate AIClient AIClient -> AIClient: 프롬프트 구성 note right 옵션 2 프롬프트: "중비용, 균형잡힌 ROI 이벤트 기획 목적: {목적} 트렌드: {트렌드} 매장: {매장정보} 출력 형식: - 이벤트 제목 - 추천 경품 (예산: 중) - 참여 방법 (난이도: 중간) - 예상 참여자 수 - 예상 비용 - 예상 ROI" end note AIClient -> ExternalAPI: POST /api/v1/recommend\n(중비용 옵션) ExternalAPI --> AIClient: 200 OK\n{추천안 2} AIClient --> CB: 추천안 2 deactivate AIClient CB --> RecommendEngine: 옵션 2 완료 deactivate CB RecommendEngine -> CB: executeWithCircuitBreaker(\nAI API 추천 생성 - 옵션 3: 고비용) activate CB CB -> AIClient: callAIAPI(\nmethod: "generateRecommendation",\nprompt: 고비용 추천 프롬프트,\ntimeout: 5분) activate AIClient AIClient -> AIClient: 프롬프트 구성 note right 옵션 3 프롬프트: "고비용, 높은 매출 증대 이벤트 기획 목적: {목적} 트렌드: {트렌드} 매장: {매장정보} 출력 형식: - 이벤트 제목 - 추천 경품 (예산: 고) - 참여 방법 (난이도: 높음) - 예상 참여자 수 - 예상 비용 - 예상 ROI" end note AIClient -> ExternalAPI: POST /api/v1/recommend\n(고비용 옵션) ExternalAPI --> AIClient: 200 OK\n{추천안 3} AIClient --> CB: 추천안 3 deactivate AIClient CB --> RecommendEngine: 옵션 3 완료 deactivate CB end RecommendEngine -> RecommendEngine: 3가지 추천안 통합 및 검증 note right 검증 항목: - 필수 필드 존재 여부 - 예상 성과 계산 (ROI) - 추천안 차별화 확인 - 홍보 문구 생성 (각 5개) - SNS 해시태그 자동 생성 end note RecommendEngine --> Service: 3가지 추천안 생성 완료 deactivate RecommendEngine == 4. 결과 저장 및 Job 상태 업데이트 == Service -> Cache: cacheRecommendations(\nkey: ai:recommendation:{eventDraftId},\ndata: {트렌드+추천안},\nTTL: 24시간) Cache -> Redis: SETEX ai:recommendation:{eventDraftId} 86400 {결과} Redis --> Cache: OK Cache --> Service: 캐싱 완료 Service -> JobState: updateJobStatus(\njobId,\nstatus: COMPLETED,\nresult: {트렌드, 추천안}) JobState -> Redis: HSET job:{jobId} status COMPLETED result {JSON} Redis --> JobState: OK JobState --> Service: 상태 업데이트 완료 Service --> Handler: 추천 생성 완료\n{트렌드분석, 3가지추천안} deactivate Service Handler --> Consumer: ACK (메시지 처리 완료) deactivate Handler note over Consumer: Job 처리 완료\nRedis에 저장된 결과를\n클라이언트는 폴링으로 조회 end == 예외 처리 == note over Handler, Producer **AI API 장애 시** - Circuit Breaker Open - Fallback: 기본 트렌드 템플릿 사용 - Job 상태: COMPLETED (안내 메시지 포함) - 사용자에게 "AI 분석이 제한적으로 제공됩니다" 안내 **Timeout (5분 초과)** - Circuit Breaker로 즉시 실패 - Retry 없음 (비동기 Job) - Job 상태: FAILED - 사용자에게 재시도 요청 안내 **Kafka 메시지 처리 실패** - DLQ(Dead Letter Queue)로 이동 - 수동 검토 및 재처리 - 에러 로그 기록 **Redis 장애** - 캐싱 스킵 - Job 상태는 메모리에 임시 저장 - 성능 저하 가능 (매 요청마다 AI API 호출) **성능 목표** - 평균 응답 시간: 2분 이내 - P95 응답 시간: 4분 이내 - Circuit Breaker Timeout: 5분 - Redis 캐시 TTL: 24시간 **데이터 처리 원칙** - 과거 이벤트 데이터 사용 안 함 - 외부 AI API로 실시간 트렌드 분석 - 업종/지역 기반 일반적 마케팅 트렌드 활용 end note @enduml