# AI Service 전체 메서드 워크플로우

## 1. AI 추천 생성 워크플로우 (Kafka 기반 비동기)

```mermaid
sequenceDiagram
    participant ES as Event Service
    participant Kafka as Kafka Topic
    participant Consumer as AIJobConsumer
    participant ARS as AIRecommendationService
    participant JSS as JobStatusService
    participant TAS as TrendAnalysisService
    participant CS as CacheService
    participant CAC as ClaudeApiClient
    participant Claude as Claude API
    participant Redis as Redis

    %% 1. Event Service가 Kafka 메시지 발행
    ES->>Kafka: Publish AIJobMessage<br/>(ai-event-generation-job topic)

    %% 2. Kafka Consumer가 메시지 수신
    Kafka->>Consumer: consume(AIJobMessage)
    Note over Consumer: @KafkaListener<br/>groupId: ai-service-consumers

    %% 3. AI 추천 생성 시작
    Consumer->>ARS: generateRecommendations(message)
    activate ARS

    %% 4. Job 상태: PROCESSING (10%)
    ARS->>JSS: updateJobStatus(jobId, PROCESSING, "트렌드 분석 중")
    JSS->>CS: saveJobStatus(jobId, status)
    CS->>Redis: SET ai:job:status:{jobId}

    %% 5. 트렌드 분석
    ARS->>ARS: analyzeTrend(message)
    ARS->>CS: getTrend(industry, region)
    CS->>Redis: GET ai:trend:{industry}:{region}

    alt 캐시 HIT
        Redis-->>CS: TrendAnalysis (cached)
        CS-->>ARS: TrendAnalysis
    else 캐시 MISS
        ARS->>TAS: analyzeTrend(industry, region)
        activate TAS

        %% Circuit Breaker 적용
        TAS->>TAS: circuitBreakerManager.executeWithCircuitBreaker()
        TAS->>CAC: sendMessage(apiKey, version, request)
        CAC->>Claude: POST /v1/messages
        Note over Claude: Model: claude-sonnet-4-5<br/>System: "트렌드 분석 전문가"<br/>Prompt: 업종/지역/계절 트렌드
        Claude-->>CAC: ClaudeResponse
        CAC-->>TAS: ClaudeResponse

        TAS->>TAS: parseResponse(responseText)
        TAS-->>ARS: TrendAnalysis
        deactivate TAS

        %% 트렌드 캐싱
        ARS->>CS: saveTrend(industry, region, analysis)
        CS->>Redis: SET ai:trend:{industry}:{region} (TTL: 1시간)
    end

    %% 6. Job 상태: PROCESSING (50%)
    ARS->>JSS: updateJobStatus(jobId, PROCESSING, "이벤트 추천안 생성 중")
    JSS->>CS: saveJobStatus(jobId, status)
    CS->>Redis: SET ai:job:status:{jobId}

    %% 7. 이벤트 추천안 생성
    ARS->>ARS: createRecommendations(message, trendAnalysis)
    ARS->>ARS: circuitBreakerManager.executeWithCircuitBreaker()
    ARS->>CAC: sendMessage(apiKey, version, request)
    CAC->>Claude: POST /v1/messages
    Note over Claude: Model: claude-sonnet-4-5<br/>System: "이벤트 기획 전문가"<br/>Prompt: 3가지 추천안 생성
    Claude-->>CAC: ClaudeResponse
    CAC-->>ARS: ClaudeResponse

    ARS->>ARS: parseRecommendationResponse(responseText)

    %% 8. Job 상태: PROCESSING (90%)
    ARS->>JSS: updateJobStatus(jobId, PROCESSING, "결과 저장 중")
    JSS->>CS: saveJobStatus(jobId, status)
    CS->>Redis: SET ai:job:status:{jobId}

    %% 9. 결과 저장
    ARS->>CS: saveRecommendation(eventId, result)
    CS->>Redis: SET ai:recommendation:{eventId} (TTL: 24시간)

    %% 10. Job 상태: COMPLETED (100%)
    ARS->>JSS: updateJobStatus(jobId, COMPLETED, "AI 추천 완료")
    JSS->>CS: saveJobStatus(jobId, status)
    CS->>Redis: SET ai:job:status:{jobId}

    deactivate ARS

    %% 11. Kafka ACK
    Consumer->>Kafka: acknowledgment.acknowledge()
```

---

## 2. Job 상태 조회 워크플로우 (동기)

```mermaid
sequenceDiagram
    participant ES as Event Service
    participant Controller as InternalJobController
    participant JSS as JobStatusService
    participant CS as CacheService
    participant Redis as Redis

    %% 1. Event Service가 Job 상태 조회
    ES->>Controller: GET /api/v1/ai-service/internal/jobs/{jobId}/status

    %% 2. Job 상태 조회
    Controller->>JSS: getJobStatus(jobId)
    activate JSS

    JSS->>CS: getJobStatus(jobId)
    CS->>Redis: GET ai:job:status:{jobId}

    alt 상태 존재
        Redis-->>CS: JobStatusResponse
        CS-->>JSS: Object (JobStatusResponse)
        JSS->>JSS: objectMapper.convertValue()
        JSS-->>Controller: JobStatusResponse
        Controller-->>ES: 200 OK + JobStatusResponse
    else 상태 없음
        Redis-->>CS: null
        CS-->>JSS: null
        JSS-->>Controller: JobNotFoundException
        Controller-->>ES: 404 Not Found
    end

    deactivate JSS
```

---

## 3. AI 추천 결과 조회 워크플로우 (동기)

```mermaid
sequenceDiagram
    participant ES as Event Service
    participant Controller as InternalRecommendationController
    participant ARS as AIRecommendationService
    participant CS as CacheService
    participant Redis as Redis

    %% 1. Event Service가 AI 추천 결과 조회
    ES->>Controller: GET /api/v1/ai-service/internal/recommendations/{eventId}

    %% 2. 추천 결과 조회
    Controller->>ARS: getRecommendation(eventId)
    activate ARS

    ARS->>CS: getRecommendation(eventId)
    CS->>Redis: GET ai:recommendation:{eventId}

    alt 결과 존재
        Redis-->>CS: AIRecommendationResult
        CS-->>ARS: Object (AIRecommendationResult)
        ARS->>ARS: objectMapper.convertValue()
        ARS-->>Controller: AIRecommendationResult
        Controller-->>ES: 200 OK + AIRecommendationResult
    else 결과 없음
        Redis-->>CS: null
        CS-->>ARS: null
        ARS-->>Controller: RecommendationNotFoundException
        Controller-->>ES: 404 Not Found
    end

    deactivate ARS
```

---

## 4. 헬스체크 워크플로우 (동기)

```mermaid
sequenceDiagram
    participant Client as Client/Actuator
    participant Controller as HealthController
    participant Redis as Redis

    %% 1. 헬스체크 요청
    Client->>Controller: GET /api/v1/ai-service/health

    %% 2. Redis 상태 확인
    Controller->>Controller: checkRedis()

    alt RedisTemplate 존재
        Controller->>Redis: PING
        alt Redis 정상
            Redis-->>Controller: PONG
            Controller->>Controller: redisStatus = UP
        else Redis 오류
            Redis-->>Controller: Exception
            Controller->>Controller: redisStatus = DOWN
        end
    else RedisTemplate 없음
        Controller->>Controller: redisStatus = UNKNOWN
    end

    %% 3. 전체 상태 판단
    alt Redis DOWN
        Controller->>Controller: overallStatus = DEGRADED
    else Redis UP/UNKNOWN
        Controller->>Controller: overallStatus = UP
    end

    %% 4. 응답
    Controller-->>Client: 200 OK + HealthCheckResponse
```

---

## 5. 주요 컴포넌트 메서드 목록

### 5.1 Controller Layer

#### InternalJobController
| 메서드 | HTTP | 엔드포인트 | 설명 |
|--------|------|-----------|------|
| `getJobStatus(jobId)` | GET | `/api/v1/ai-service/internal/jobs/{jobId}/status` | Job 상태 조회 |
| `createTestJob(jobId)` | GET | `/api/v1/ai-service/internal/jobs/debug/create-test-job/{jobId}` | 테스트 Job 생성 (디버그) |

#### InternalRecommendationController
| 메서드 | HTTP | 엔드포인트 | 설명 |
|--------|------|-----------|------|
| `getRecommendation(eventId)` | GET | `/api/v1/ai-service/internal/recommendations/{eventId}` | AI 추천 결과 조회 |
| `debugRedisKeys()` | GET | `/api/v1/ai-service/internal/recommendations/debug/redis-keys` | Redis 모든 키 조회 |
| `debugRedisKey(key)` | GET | `/api/v1/ai-service/internal/recommendations/debug/redis-key/{key}` | Redis 특정 키 조회 |
| `searchAllDatabases()` | GET | `/api/v1/ai-service/internal/recommendations/debug/search-all-databases` | 전체 DB 검색 |
| `createTestData(eventId)` | GET | `/api/v1/ai-service/internal/recommendations/debug/create-test-data/{eventId}` | 테스트 데이터 생성 |

#### HealthController
| 메서드 | HTTP | 엔드포인트 | 설명 |
|--------|------|-----------|------|
| `healthCheck()` | GET | `/api/v1/ai-service/health` | 서비스 헬스체크 |
| `checkRedis()` | - | (내부) | Redis 연결 확인 |

---

### 5.2 Service Layer

#### AIRecommendationService
| 메서드 | 호출자 | 설명 |
|--------|-------|------|
| `getRecommendation(eventId)` | Controller | Redis에서 추천 결과 조회 |
| `generateRecommendations(message)` | AIJobConsumer | AI 추천 생성 (전체 프로세스) |
| `analyzeTrend(message)` | 내부 | 트렌드 분석 (캐시 확인 포함) |
| `createRecommendations(message, trendAnalysis)` | 내부 | 이벤트 추천안 생성 |
| `callClaudeApiForRecommendations(message, trendAnalysis)` | 내부 | Claude API 호출 (추천안) |
| `buildRecommendationPrompt(message, trendAnalysis)` | 내부 | 추천안 프롬프트 생성 |
| `parseRecommendationResponse(responseText)` | 내부 | 추천안 응답 파싱 |
| `parseEventRecommendation(node)` | 내부 | EventRecommendation 파싱 |
| `parseRange(node)` | 내부 | Range 객체 파싱 |
| `extractJsonFromMarkdown(text)` | 내부 | Markdown에서 JSON 추출 |

#### TrendAnalysisService
| 메서드 | 호출자 | 설명 |
|--------|-------|------|
| `analyzeTrend(industry, region)` | AIRecommendationService | 트렌드 분석 수행 |
| `callClaudeApi(industry, region)` | 내부 | Claude API 호출 (트렌드) |
| `buildPrompt(industry, region)` | 내부 | 트렌드 분석 프롬프트 생성 |
| `parseResponse(responseText)` | 내부 | 트렌드 응답 파싱 |
| `extractJsonFromMarkdown(text)` | 내부 | Markdown에서 JSON 추출 |
| `parseTrendKeywords(arrayNode)` | 내부 | TrendKeyword 리스트 파싱 |

#### JobStatusService
| 메서드 | 호출자 | 설명 |
|--------|-------|------|
| `getJobStatus(jobId)` | Controller | Job 상태 조회 |
| `updateJobStatus(jobId, status, message)` | AIRecommendationService | Job 상태 업데이트 |
| `calculateProgress(status)` | 내부 | 상태별 진행률 계산 |

#### CacheService
| 메서드 | 호출자 | 설명 |
|--------|-------|------|
| `set(key, value, ttlSeconds)` | 내부 | 범용 캐시 저장 |
| `get(key)` | 내부 | 범용 캐시 조회 |
| `delete(key)` | 외부 | 캐시 삭제 |
| `saveJobStatus(jobId, status)` | JobStatusService | Job 상태 저장 |
| `getJobStatus(jobId)` | JobStatusService | Job 상태 조회 |
| `saveRecommendation(eventId, recommendation)` | AIRecommendationService | AI 추천 결과 저장 |
| `getRecommendation(eventId)` | AIRecommendationService | AI 추천 결과 조회 |
| `saveTrend(industry, region, trend)` | AIRecommendationService | 트렌드 분석 결과 저장 |
| `getTrend(industry, region)` | AIRecommendationService | 트렌드 분석 결과 조회 |

---

### 5.3 Consumer Layer

#### AIJobConsumer
| 메서드 | 트리거 | 설명 |
|--------|-------|------|
| `consume(message, topic, offset, ack)` | Kafka Message | Kafka 메시지 수신 및 처리 |

---

### 5.4 Client Layer

#### ClaudeApiClient (Feign)
| 메서드 | 호출자 | 설명 |
|--------|-------|------|
| `sendMessage(apiKey, anthropicVersion, request)` | TrendAnalysisService, AIRecommendationService | Claude API 호출 |

---

## 6. Redis 캐시 키 구조

| 키 패턴 | 설명 | TTL |
|--------|------|-----|
| `ai:job:status:{jobId}` | Job 상태 정보 | 24시간 (86400초) |
| `ai:recommendation:{eventId}` | AI 추천 결과 | 24시간 (86400초) |
| `ai:trend:{industry}:{region}` | 트렌드 분석 결과 | 1시간 (3600초) |

---

## 7. Claude API 호출 정보

### 7.1 트렌드 분석
- **URL**: `https://api.anthropic.com/v1/messages`
- **Model**: `claude-sonnet-4-5-20250929`
- **Max Tokens**: 4096
- **Temperature**: 0.7
- **System Prompt**: "당신은 마케팅 트렌드 분석 전문가입니다. 업종별, 지역별 트렌드를 분석하고 인사이트를 제공합니다."
- **응답 형식**: JSON (industryTrends, regionalTrends, seasonalTrends)

### 7.2 이벤트 추천안 생성
- **URL**: `https://api.anthropic.com/v1/messages`
- **Model**: `claude-sonnet-4-5-20250929`
- **Max Tokens**: 4096
- **Temperature**: 0.7
- **System Prompt**: "당신은 소상공인을 위한 마케팅 이벤트 기획 전문가입니다. 트렌드 분석을 바탕으로 실행 가능한 이벤트 추천안을 제공합니다."
- **응답 형식**: JSON (recommendations: 3가지 옵션)

---

## 8. Circuit Breaker 설정

### 적용 대상
- `claudeApi`: 모든 Claude API 호출

### 설정값
```yaml
failure-rate-threshold: 50%
slow-call-duration-threshold: 60초
sliding-window-size: 10
minimum-number-of-calls: 5
wait-duration-in-open-state: 60초
timeout-duration: 300초 (5분)
```

### Fallback 메서드
- `AIServiceFallback.getDefaultTrendAnalysis()`: 기본 트렌드 분석
- `AIServiceFallback.getDefaultRecommendations()`: 기본 추천안

---

## 9. 에러 처리

### Exception 종류
| Exception | HTTP Code | 발생 조건 |
|-----------|-----------|---------|
| `RecommendationNotFoundException` | 404 | Redis에 추천 결과 없음 |
| `JobNotFoundException` | 404 | Redis에 Job 상태 없음 |
| `AIServiceException` | 500 | AI 서비스 내부 오류 |

### 에러 응답 예시
```json
{
  "timestamp": "2025-10-30T15:30:00",
  "status": 404,
  "error": "Not Found",
  "message": "추천 결과를 찾을 수 없습니다: eventId=evt-123",
  "path": "/api/v1/ai-service/internal/recommendations/evt-123"
}
```

---

## 10. 로깅 레벨

```yaml
com.kt.ai: DEBUG
org.springframework.kafka: INFO
org.springframework.data.redis: INFO
io.github.resilience4j: DEBUG
```
