openapi: 3.0.3 info: title: AI Service API description: | AI 기반 트렌드 분석 및 이벤트 추천 서비스 API ## 주요 기능 - 업종/지역/시즌별 트렌드 분석 - AI 기반 이벤트 기획안 추천 (3가지 옵션) - 비동기 Job 처리 및 폴링 기반 결과 조회 ## 기술 스택 - AI Engine: Claude API / GPT-4 API - 캐싱: Redis (트렌드 1시간, 추천안 24시간) - 메시지 큐: Kafka (비동기 Job 처리) - 안정성: Circuit Breaker 패턴 version: 1.0.0 contact: name: AI Service Team email: ai-team@kt.com servers: - url: https://api.kt-event.com/ai/v1 description: 프로덕션 서버 - url: https://dev-api.kt-event.com/ai/v1 description: 개발 서버 - url: http://localhost:8083/ai/v1 description: 로컬 개발 서버 tags: - name: AI Analysis description: AI 기반 트렌드 분석 및 추천 엔드포인트 - name: Job Status description: 비동기 Job 상태 조회 엔드포인트 paths: /analyze-trends: post: tags: - AI Analysis summary: 트렌드 분석 요청 description: | 업종, 지역, 시즌을 기반으로 트렌드 분석을 수행합니다. ## 처리 방식 - **비동기 처리**: Kafka를 통한 비동기 Job 생성 - **응답 시간**: 즉시 Job ID 반환 (< 100ms) - **실제 처리 시간**: 5~30초 이내 (AI API 응답 시간 포함) ## 캐싱 전략 - 캐시 키: `trend:{업종}:{지역}` - TTL: 1시간 - 캐시 히트 시 즉시 응답 ## Circuit Breaker - Failure Rate Threshold: 50% - Timeout: 30초 - Half-Open Wait Duration: 30초 operationId: analyzeTrends security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/TrendAnalysisRequest" examples: restaurant: summary: 음식점 트렌드 분석 value: eventDraftId: "evt_draft_001" industry: "음식점" region: "서울 강남구" purpose: "신규 고객 유치" storeInfo: storeName: "맛있는 고깃집" storeSize: "중형" monthlyRevenue: 30000000 cafe: summary: 카페 트렌드 분석 value: eventDraftId: "evt_draft_002" industry: "카페" region: "서울 홍대" purpose: "재방문 유도" storeInfo: storeName: "커피스토리" storeSize: "소형" monthlyRevenue: 15000000 responses: "202": description: | 트렌드 분석 Job이 생성되었습니다. - Job ID를 사용하여 `/jobs/{jobId}` 엔드포인트로 결과 폴링 - 예상 처리 시간: 5~30초 content: application/json: schema: $ref: "#/components/schemas/JobCreatedResponse" example: jobId: "job_ai_20250122_001" status: "PROCESSING" message: "트렌드 분석이 진행 중입니다" estimatedCompletionTime: "2025-01-22T10:05:30Z" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "429": $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" /recommend-events: post: tags: - AI Analysis summary: 이벤트 추천 요청 description: | 트렌드 분석 결과를 기반으로 3가지 차별화된 이벤트 기획안을 생성합니다. ## 처리 방식 - **비동기 처리**: Kafka를 통한 비동기 Job 생성 - **응답 시간**: 즉시 Job ID 반환 (< 100ms) - **실제 처리 시간**: 5~30초 이내 ## 3가지 추천 옵션 1. **저비용 옵션**: 높은 참여율 중심 2. **중비용 옵션**: 균형잡힌 ROI 3. **고비용 옵션**: 높은 매출 증대 효과 ## 병렬 처리 - 3가지 옵션 동시 생성 (병렬) - 전체 처리 시간: 단일 요청 시간과 동일 ## 캐싱 전략 - 캐시 키: `ai:recommendation:{eventDraftId}` - TTL: 24시간 operationId: recommendEvents security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/EventRecommendationRequest" examples: newCustomer: summary: 신규 고객 유치 이벤트 value: eventDraftId: "evt_draft_001" purpose: "신규 고객 유치" industry: "음식점" region: "서울 강남구" storeInfo: storeName: "맛있는 고깃집" storeSize: "중형" monthlyRevenue: 30000000 trendAnalysisJobId: "job_ai_20250122_001" responses: "202": description: | 이벤트 추천 Job이 생성되었습니다. - Job ID를 사용하여 `/jobs/{jobId}` 엔드포인트로 결과 폴링 - 예상 처리 시간: 5~30초 content: application/json: schema: $ref: "#/components/schemas/JobCreatedResponse" example: jobId: "job_ai_20250122_002" status: "PROCESSING" message: "이벤트 추천이 진행 중입니다" estimatedCompletionTime: "2025-01-22T10:05:30Z" "400": $ref: "#/components/responses/BadRequest" "401": $ref: "#/components/responses/Unauthorized" "429": $ref: "#/components/responses/TooManyRequests" "500": $ref: "#/components/responses/InternalServerError" /jobs/{jobId}: get: tags: - Job Status summary: Job 상태 조회 description: | 비동기 Job의 처리 상태 및 결과를 조회합니다. ## 폴링 전략 - **초기 폴링**: 1초 간격 (처음 5회) - **장기 폴링**: 3초 간격 (이후) - **최대 대기 시간**: 60초 ## Job 상태 - `PENDING`: 대기 중 - `PROCESSING`: 처리 중 - `COMPLETED`: 완료 - `FAILED`: 실패 ## 캐싱 - Redis를 통한 Job 상태 저장 - 키: `job:{jobId}` operationId: getJobStatus security: - bearerAuth: [] parameters: - name: jobId in: path required: true description: Job ID schema: type: string example: "job_ai_20250122_001" responses: "200": description: Job 상태 조회 성공 content: application/json: schema: oneOf: - $ref: "#/components/schemas/TrendAnalysisJobResponse" - $ref: "#/components/schemas/EventRecommendationJobResponse" examples: processing: summary: 처리 중 value: jobId: "job_ai_20250122_001" status: "PROCESSING" message: "트렌드 분석 중입니다" progress: 50 createdAt: "2025-01-22T10:05:00Z" estimatedCompletionTime: "2025-01-22T10:05:30Z" trendCompleted: summary: 트렌드 분석 완료 value: jobId: "job_ai_20250122_001" status: "COMPLETED" message: "트렌드 분석이 완료되었습니다" result: industryTrends: successfulEventTypes: - type: "할인 이벤트" successRate: 85 - type: "경품 추첨" successRate: 78 popularPrizes: - prize: "커피 쿠폰" preferenceScore: 92 - prize: "현금 할인" preferenceScore: 88 effectiveParticipationMethods: - method: "간단한 설문조사" engagementRate: 75 regionalCharacteristics: successRate: 82 demographicProfile: ageGroups: - range: "20-29" percentage: 35 - range: "30-39" percentage: 40 genderDistribution: male: 45 female: 55 seasonalPatterns: currentSeason: "겨울" recommendedEventTypes: - "따뜻한 음료 할인" - "연말 감사 이벤트" specialOccasions: - occasion: "설날" daysUntil: 15 completedAt: "2025-01-22T10:05:25Z" recommendationCompleted: summary: 이벤트 추천 완료 value: jobId: "job_ai_20250122_002" status: "COMPLETED" message: "이벤트 추천이 완료되었습니다" result: recommendations: - option: 1 title: "신규 고객 환영 커피 쿠폰 증정" budget: "low" prize: name: "아메리카노 쿠폰" quantity: 100 estimatedCost: 300000 participationMethod: type: "간단한 설문조사" difficulty: "low" description: "매장 방문 후 QR 코드 스캔 및 간단한 설문" estimatedParticipants: 150 estimatedROI: 250 promotionalTexts: - "따뜻한 커피 한 잔으로 시작하는 하루!" - "신규 방문 고객님께 특별한 선물" hashtags: - "#강남맛집" - "#커피쿠폰" - "#신규고객환영" - option: 2 title: "런치 세트 20% 할인 이벤트" budget: "medium" prize: name: "런치 세트 할인권" quantity: 50 estimatedCost: 500000 participationMethod: type: "재방문 미션" difficulty: "medium" description: "첫 방문 후 리뷰 작성 시 할인권 제공" estimatedParticipants: 80 estimatedROI: 320 promotionalTexts: - "점심 시간, 특별한 할인 기회!" - "리뷰 남기고 할인받자" hashtags: - "#강남맛집" - "#런치할인" - "#점심특가" - option: 3 title: "디너 코스 무료 업그레이드 추첨" budget: "high" prize: name: "디너 코스 업그레이드권" quantity: 10 estimatedCost: 1000000 participationMethod: type: "바이럴 확산" difficulty: "high" description: "SNS 공유 및 친구 태그 3명 이상" estimatedParticipants: 200 estimatedROI: 450 promotionalTexts: - "프리미엄 디너 코스로 업그레이드!" - "친구와 함께 즐기는 특별한 저녁" hashtags: - "#강남맛집" - "#디너코스" - "#프리미엄디너" completedAt: "2025-01-22T10:05:35Z" failed: summary: 처리 실패 value: jobId: "job_ai_20250122_003" status: "FAILED" message: "AI API 오류로 인해 처리에 실패했습니다" error: code: "AI_API_ERROR" detail: "External AI API timeout after 30 seconds" failedAt: "2025-01-22T10:05:45Z" "404": $ref: "#/components/responses/NotFound" "401": $ref: "#/components/responses/Unauthorized" "500": $ref: "#/components/responses/InternalServerError" components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: | JWT 토큰 기반 인증 - User Service에서 발급한 JWT 토큰 사용 - 헤더: `Authorization: Bearer {token}` schemas: TrendAnalysisRequest: type: object required: - eventDraftId - industry - region - purpose properties: eventDraftId: type: string description: 이벤트 초안 ID (Event Service에서 생성) example: "evt_draft_001" industry: type: string description: 업종 enum: - 음식점 - 카페 - 소매점 - 뷰티/미용 - 의료/헬스케어 - 기타 example: "음식점" region: type: string description: 지역 (시/구/동) example: "서울 강남구" purpose: type: string description: 이벤트 목적 enum: - 신규 고객 유치 - 재방문 유도 - 매출 증대 - 인지도 향상 example: "신규 고객 유치" storeInfo: $ref: "#/components/schemas/StoreInfo" EventRecommendationRequest: type: object required: - eventDraftId - purpose - industry - region - storeInfo properties: eventDraftId: type: string description: 이벤트 초안 ID example: "evt_draft_001" purpose: type: string description: 이벤트 목적 enum: - 신규 고객 유치 - 재방문 유도 - 매출 증대 - 인지도 향상 example: "신규 고객 유치" industry: type: string description: 업종 example: "음식점" region: type: string description: 지역 example: "서울 강남구" storeInfo: $ref: "#/components/schemas/StoreInfo" trendAnalysisJobId: type: string description: | 트렌드 분석 Job ID (선택) - 제공 시 해당 트렌드 분석 결과 재사용 - 미제공 시 새로운 트렌드 분석 수행 example: "job_ai_20250122_001" StoreInfo: type: object required: - storeName properties: storeName: type: string description: 매장명 example: "맛있는 고깃집" storeSize: type: string description: 매장 크기 enum: - 소형 - 중형 - 대형 example: "중형" monthlyRevenue: type: integer format: int64 description: 월 평균 매출 (원) minimum: 0 example: 30000000 JobCreatedResponse: type: object required: - jobId - status - message properties: jobId: type: string description: Job ID (폴링 조회용) example: "job_ai_20250122_001" status: type: string enum: - PENDING - PROCESSING description: Job 상태 example: "PROCESSING" message: type: string description: 상태 메시지 example: "트렌드 분석이 진행 중입니다" estimatedCompletionTime: type: string format: date-time description: 예상 완료 시간 (ISO 8601) example: "2025-01-22T10:05:30Z" TrendAnalysisJobResponse: type: object required: - jobId - status - message - createdAt properties: jobId: type: string description: Job ID example: "job_ai_20250122_001" status: type: string enum: - PENDING - PROCESSING - COMPLETED - FAILED description: Job 상태 example: "COMPLETED" message: type: string description: 상태 메시지 example: "트렌드 분석이 완료되었습니다" progress: type: integer minimum: 0 maximum: 100 description: 진행률 (%) example: 100 result: $ref: "#/components/schemas/TrendAnalysisResult" error: $ref: "#/components/schemas/JobError" createdAt: type: string format: date-time description: Job 생성 시간 example: "2025-01-22T10:05:00Z" estimatedCompletionTime: type: string format: date-time description: 예상 완료 시간 (PROCESSING 상태일 때만) example: "2025-01-22T10:05:30Z" completedAt: type: string format: date-time description: Job 완료 시간 (COMPLETED 상태일 때만) example: "2025-01-22T10:05:25Z" failedAt: type: string format: date-time description: Job 실패 시간 (FAILED 상태일 때만) example: "2025-01-22T10:05:45Z" EventRecommendationJobResponse: type: object required: - jobId - status - message - createdAt properties: jobId: type: string description: Job ID example: "job_ai_20250122_002" status: type: string enum: - PENDING - PROCESSING - COMPLETED - FAILED description: Job 상태 example: "COMPLETED" message: type: string description: 상태 메시지 example: "이벤트 추천이 완료되었습니다" progress: type: integer minimum: 0 maximum: 100 description: 진행률 (%) example: 100 result: $ref: "#/components/schemas/EventRecommendationResult" error: $ref: "#/components/schemas/JobError" createdAt: type: string format: date-time description: Job 생성 시간 example: "2025-01-22T10:05:00Z" estimatedCompletionTime: type: string format: date-time description: 예상 완료 시간 (PROCESSING 상태일 때만) example: "2025-01-22T10:05:30Z" completedAt: type: string format: date-time description: Job 완료 시간 (COMPLETED 상태일 때만) example: "2025-01-22T10:05:35Z" failedAt: type: string format: date-time description: Job 실패 시간 (FAILED 상태일 때만) example: "2025-01-22T10:05:45Z" TrendAnalysisResult: type: object required: - industryTrends - regionalCharacteristics - seasonalPatterns properties: industryTrends: $ref: "#/components/schemas/IndustryTrends" regionalCharacteristics: $ref: "#/components/schemas/RegionalCharacteristics" seasonalPatterns: $ref: "#/components/schemas/SeasonalPatterns" IndustryTrends: type: object required: - successfulEventTypes - popularPrizes - effectiveParticipationMethods properties: successfulEventTypes: type: array description: 최근 성공한 이벤트 유형 (최대 5개) items: type: object required: - type - successRate properties: type: type: string description: 이벤트 유형 example: "할인 이벤트" successRate: type: integer minimum: 0 maximum: 100 description: 성공률 (%) example: 85 popularPrizes: type: array description: 고객 선호 경품 Top 5 items: type: object required: - prize - preferenceScore properties: prize: type: string description: 경품명 example: "커피 쿠폰" preferenceScore: type: integer minimum: 0 maximum: 100 description: 선호도 점수 example: 92 effectiveParticipationMethods: type: array description: 효과적인 참여 방법 items: type: object required: - method - engagementRate properties: method: type: string description: 참여 방법 example: "간단한 설문조사" engagementRate: type: integer minimum: 0 maximum: 100 description: 참여율 (%) example: 75 RegionalCharacteristics: type: object required: - successRate - demographicProfile properties: successRate: type: integer minimum: 0 maximum: 100 description: 해당 지역 이벤트 성공률 (%) example: 82 demographicProfile: type: object required: - ageGroups - genderDistribution properties: ageGroups: type: array description: 연령대별 분포 items: type: object required: - range - percentage properties: range: type: string description: 연령대 example: "20-29" percentage: type: integer minimum: 0 maximum: 100 description: 비율 (%) example: 35 genderDistribution: type: object required: - male - female properties: male: type: integer minimum: 0 maximum: 100 description: 남성 비율 (%) example: 45 female: type: integer minimum: 0 maximum: 100 description: 여성 비율 (%) example: 55 SeasonalPatterns: type: object required: - currentSeason - recommendedEventTypes properties: currentSeason: type: string description: 현재 계절 enum: - 봄 - 여름 - 가을 - 겨울 example: "겨울" recommendedEventTypes: type: array description: 계절별 추천 이벤트 유형 items: type: string example: - "따뜻한 음료 할인" - "연말 감사 이벤트" specialOccasions: type: array description: 다가오는 특별 이벤트 (명절, 기념일 등) items: type: object required: - occasion - daysUntil properties: occasion: type: string description: 특별 이벤트명 example: "설날" daysUntil: type: integer description: 남은 일수 example: 15 EventRecommendationResult: type: object required: - recommendations properties: recommendations: type: array description: 3가지 이벤트 추천안 minItems: 3 maxItems: 3 items: $ref: "#/components/schemas/EventRecommendation" EventRecommendation: type: object required: - option - title - budget - prize - participationMethod - estimatedParticipants - estimatedROI - promotionalTexts - hashtags properties: option: type: integer description: 옵션 번호 (1, 2, 3) enum: [1, 2, 3] example: 1 title: type: string description: 이벤트 제목 (수정 가능) maxLength: 50 example: "신규 고객 환영 커피 쿠폰 증정" budget: type: string description: 예산 수준 enum: - low - medium - high example: "low" prize: type: object required: - name - quantity - estimatedCost properties: name: type: string description: 경품명 (수정 가능) example: "아메리카노 쿠폰" quantity: type: integer minimum: 1 description: 경품 수량 example: 100 estimatedCost: type: integer minimum: 0 description: 예상 비용 (원) example: 300000 participationMethod: type: object required: - type - difficulty - description properties: type: type: string description: 참여 방법 유형 example: "간단한 설문조사" difficulty: type: string description: 난이도 enum: - low - medium - high example: "low" description: type: string description: 참여 방법 상세 설명 example: "매장 방문 후 QR 코드 스캔 및 간단한 설문" estimatedParticipants: type: integer minimum: 0 description: 예상 참여자 수 example: 150 estimatedROI: type: integer minimum: 0 description: 예상 투자 대비 수익률 (%) example: 250 promotionalTexts: type: array description: 홍보 문구 (5개) minItems: 5 maxItems: 5 items: type: string example: - "따뜻한 커피 한 잔으로 시작하는 하루!" - "신규 방문 고객님께 특별한 선물" - "강남 최고의 고깃집에서 만나요" - "지금 바로 참여하세요!" - "한정 수량! 서두르세요!" hashtags: type: array description: SNS 해시태그 (자동 생성) items: type: string example: - "#강남맛집" - "#커피쿠폰" - "#신규고객환영" JobError: type: object required: - code - detail properties: code: type: string description: 에러 코드 enum: - AI_API_ERROR - TIMEOUT - CIRCUIT_BREAKER_OPEN - INVALID_PARAMETERS - INTERNAL_ERROR example: "AI_API_ERROR" detail: type: string description: 에러 상세 메시지 example: "External AI API timeout after 30 seconds" ErrorResponse: type: object required: - error - message - timestamp properties: error: type: string description: 에러 타입 example: "BAD_REQUEST" message: type: string description: 에러 메시지 example: "필수 파라미터가 누락되었습니다" details: type: array description: 상세 에러 정보 items: type: object properties: field: type: string description: 에러 필드 example: "industry" message: type: string description: 필드별 에러 메시지 example: "업종은 필수 입력 항목입니다" timestamp: type: string format: date-time description: 에러 발생 시간 example: "2025-01-22T10:05:00Z" responses: BadRequest: description: 잘못된 요청 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: "BAD_REQUEST" message: "필수 파라미터가 누락되었습니다" details: - field: "industry" message: "업종은 필수 입력 항목입니다" timestamp: "2025-01-22T10:05:00Z" Unauthorized: description: 인증 실패 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: "UNAUTHORIZED" message: "유효하지 않은 인증 토큰입니다" timestamp: "2025-01-22T10:05:00Z" NotFound: description: 리소스를 찾을 수 없음 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: "NOT_FOUND" message: "Job을 찾을 수 없습니다" timestamp: "2025-01-22T10:05:00Z" TooManyRequests: description: 요청 제한 초과 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: "TOO_MANY_REQUESTS" message: "요청 제한을 초과했습니다. 잠시 후 다시 시도해주세요" timestamp: "2025-01-22T10:05:00Z" headers: Retry-After: description: 재시도 가능 시간 (초) schema: type: integer example: 60 InternalServerError: description: 서버 내부 오류 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: error: "INTERNAL_SERVER_ERROR" message: "서버 내부 오류가 발생했습니다" timestamp: "2025-01-22T10:05:00Z"