mirror of
https://github.com/ktds-dg0501/kt-event-marketing.git
synced 2025-12-06 11:26:26 +00:00
336 lines
12 KiB
Plaintext
336 lines
12 KiB
Plaintext
@startuml ai-트렌드분석및추천
|
|
!theme mono
|
|
|
|
title AI Service - 트렌드 분석 및 이벤트 추천 (내부 시퀀스)
|
|
|
|
actor Client
|
|
participant "Kafka Consumer" as Consumer <<Component>>
|
|
participant "JobMessageHandler" as Handler <<Controller>>
|
|
participant "AIRecommendationService" as Service <<Service>>
|
|
participant "TrendAnalysisEngine" as TrendEngine <<Component>>
|
|
participant "RecommendationEngine" as RecommendEngine <<Component>>
|
|
participant "CacheManager" as Cache <<Component>>
|
|
participant "CircuitBreakerManager" as CB <<Component>>
|
|
participant "ExternalAIClient" as AIClient <<Component>>
|
|
participant "JobStateManager" as JobState <<Component>>
|
|
participant "Redis" as Redis <<Infrastructure>>
|
|
participant "Event DB" as EventDB <<Infrastructure>>
|
|
participant "External AI API" as ExternalAPI <<External>>
|
|
participant "Kafka Producer" as Producer <<Component>>
|
|
|
|
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 캐시 미스
|
|
TrendEngine -> EventDB: 과거 이벤트 데이터 조회\nSELECT * FROM events\nWHERE industry = ?\nAND region LIKE ?\nAND created_at > NOW() - INTERVAL 3 MONTH\nORDER BY roi DESC
|
|
EventDB --> TrendEngine: 이벤트 통계 데이터\n{성공 이벤트 리스트, ROI 정보}
|
|
|
|
TrendEngine -> TrendEngine: 트렌드 패턴 분석
|
|
note right
|
|
분석 항목:
|
|
1. 업종 트렌드
|
|
- 최근 3개월 성공 이벤트 유형
|
|
- 고객 선호 경품 Top 5
|
|
- 효과적인 참여 방법
|
|
|
|
2. 지역 특성
|
|
- 해당 지역 이벤트 성공률
|
|
- 지역 고객 연령대/성별 분포
|
|
|
|
3. 시즌 특성
|
|
- 계절별 추천 이벤트
|
|
- 특별 시즌 (명절, 기념일)
|
|
end note
|
|
|
|
TrendEngine -> CB: executeWithCircuitBreaker(\nAI API 트렌드 분석 호출)
|
|
activate CB
|
|
|
|
CB -> CB: Circuit Breaker 상태 확인
|
|
note right
|
|
Circuit Breaker 설정:
|
|
- Failure Rate Threshold: 50%
|
|
- Timeout: 30초
|
|
- Half-Open Wait Duration: 30초
|
|
- Permitted Calls in Half-Open: 3
|
|
end note
|
|
|
|
alt Circuit CLOSED (정상)
|
|
CB -> AIClient: callAIAPI(\nmethod: "trendAnalysis",\nprompt: 트렌드 분석 프롬프트,\ntimeout: 30초)
|
|
activate AIClient
|
|
|
|
AIClient -> AIClient: 프롬프트 구성
|
|
note right
|
|
프롬프트 예시:
|
|
"당신은 마케팅 트렌드 분석 전문가입니다.
|
|
업종: {업종}
|
|
지역: {지역}
|
|
과거 데이터: {이벤트 통계}
|
|
|
|
다음을 분석하세요:
|
|
1. 업종 트렌드 (성공 이벤트 유형)
|
|
2. 지역 특성 (고객 특성)
|
|
3. 시즌 특성 (현재 시기 추천)"
|
|
end note
|
|
|
|
AIClient -> ExternalAPI: POST /api/v1/analyze\nAuthorization: Bearer {API_KEY}\nTimeout: 30초
|
|
activate ExternalAPI
|
|
|
|
ExternalAPI --> AIClient: 200 OK\n{트렌드 분석 결과}
|
|
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 (30초 초과)
|
|
CB --> TrendEngine: TimeoutException
|
|
TrendEngine -> TrendEngine: Fallback 실행
|
|
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: 10초)
|
|
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: 10초)
|
|
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: 10초)
|
|
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
|
|
|
|
== 5. Kafka Event 발행 (선택적) ==
|
|
Handler -> Producer: publishEventRecommended(\neventId: eventDraftId,\nrecommendations: 3가지추천안)
|
|
Producer -> Producer: Kafka 메시지 구성
|
|
note right
|
|
Event Topic: event-topic
|
|
Message: {
|
|
eventType: "EventRecommended",
|
|
eventId: eventDraftId,
|
|
recommendations: [...]
|
|
timestamp: ISO8601
|
|
}
|
|
end note
|
|
|
|
Producer --> Handler: 이벤트 발행 완료
|
|
Handler --> Consumer: ACK (메시지 처리 완료)
|
|
deactivate Handler
|
|
|
|
note over Consumer: Job 처리 완료\n클라이언트는 폴링으로 결과 조회
|
|
end
|
|
|
|
== 예외 처리 ==
|
|
note over Handler, Producer
|
|
1. AI API 장애 시:
|
|
- Circuit Breaker Open
|
|
- Fallback: 기본 트렌드 데이터 사용
|
|
- Job 상태: COMPLETED (안내 메시지 포함)
|
|
|
|
2. Timeout (30초 초과):
|
|
- Circuit Breaker로 즉시 실패
|
|
- Retry 없음 (비동기 Job)
|
|
- Job 상태: FAILED
|
|
|
|
3. Kafka 메시지 처리 실패:
|
|
- DLQ로 이동
|
|
- 수동 검토 및 재처리
|
|
|
|
4. Redis 장애:
|
|
- 캐싱 스킵, DB만 사용
|
|
- Job 상태는 메모리에 임시 저장
|
|
end note
|
|
|
|
@enduml
|