# AI 업셀링 시스템 아키텍처 > 청춘약국 AI 기반 맞춤 제품 추천 시스템의 전체 구조 및 데이터 흐름 ## 개요 고객이 마일리지를 적립할 때, 실시간으로 AI가 추가 구매 추천을 생성하는 시스템. **핵심 특징:** - POS(PIT3000) 판매 데이터 기반 추천 - 고객별 구매 이력 분석 - 약국 실제 재고(최근 판매 제품) 기반 - Clawdbot Gateway를 통한 Claude 연동 (추가 API 비용 없음) --- ## 아키텍처 다이어그램 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 전체 흐름 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ [POS 판매] │ │ │ │ │ ▼ │ │ [MSSQL: PM_PRES] ←─── PIT3000 POS 데이터 │ │ │ │ │ ▼ │ │ [키오스크 적립 요청] POST /api/kiosk/claim │ │ │ │ │ ├──────────────────────────────────────┐ │ │ │ │ │ │ ▼ ▼ │ │ [SQLite: mileage.db] [백그라운드 스레드] │ │ - claim_tokens _generate_upsell_recommendation() │ - users │ │ │ │ │ │ ┌────────────────────┼────────────────┐ │ │ │ ▼ │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ 데이터 수집 │ │ │ │ │ ├─────────────────────┤ │ │ │ │ │ 1. 현재 구매 품목 │ │ │ │ │ │ 2. 고객 구매 이력 │ │ │ │ │ │ 3. 약국 보유 제품 │ │ │ │ │ └──────────┬──────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ Clawdbot Gateway │ │ │ │ │ │ (WebSocket) │ │ │ │ │ │ │ │ │ │ │ │ Model: Sonnet │ │ │ │ │ │ (비용 최적화) │ │ │ │ │ └──────────┬──────────┘ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ Claude AI 응답 │ │ │ │ │ │ {product, reason, │ │ │ │ │ │ message} │ │ │ │ │ └──────────┬──────────┘ │ │ │ │ │ │ │ │ └───────────────────┼─────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────┐ │ │ │ SQLite: ai_recommendations │ │ │ - recommended_product │ │ │ │ - recommendation_message│ │ │ │ - trigger_products │ │ │ │ - expires_at │ │ │ └──────────┬──────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────┐ │ │ │ 마이페이지 / 키오스크 │ │ │ │ 추천 카드 노출 │ │ │ └─────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 데이터 흐름 상세 ### 1단계: 트리거 (키오스크 적립) ```python # POST /api/kiosk/claim # 고객이 전화번호로 마일리지 적립 요청 # 적립 완료 후 백그라운드에서 AI 추천 생성 threading.Thread(target=_bg_upsell, daemon=True).start() ``` **포인트:** 적립 응답은 즉시 반환, AI 추천은 백그라운드에서 처리 (non-blocking) --- ### 2단계: 데이터 수집 #### 2-1. 현재 구매 품목 ```python # 키오스크 트리거 시 전달받은 sale_items에서 추출 current_items = ', '.join(item['name'] for item in sale_items) # 예: "타이레놀, 판피린, 비타민C" ``` #### 2-2. 고객 구매 이력 (최근 5건) ```sql -- SQLite: 최근 적립한 거래 ID 조회 SELECT ct.transaction_id FROM claim_tokens ct WHERE ct.claimed_by_user_id = ? AND ct.transaction_id != ? ORDER BY ct.claimed_at DESC LIMIT 5 -- MSSQL: 각 거래의 품목 조회 SELECT ISNULL(G.GoodsName, '') AS goods_name FROM SALE_SUB S LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode WHERE S.SL_NO_order = :tid ``` #### 2-3. 약국 보유 제품 목록 (TOP 40) ```sql -- MSSQL: 최근 30일 판매 상위 40개 제품 SELECT TOP 40 ISNULL(G.GoodsName, '') AS name, COUNT(*) as sales, MAX(G.Saleprice) as price FROM SALE_SUB S LEFT JOIN PM_DRUG.dbo.CD_GOODS G ON S.DrugCode = G.DrugCode WHERE S.SL_DT_appl >= CONVERT(VARCHAR(8), DATEADD(DAY, -30, GETDATE()), 112) AND G.GoodsName IS NOT NULL AND G.GoodsName NOT LIKE N'%(판매불가)%' GROUP BY G.GoodsName ORDER BY COUNT(*) DESC ``` **왜 TOP 40?** - AI 컨텍스트 토큰 절약 - 실제로 많이 팔리는 제품만 추천 (재고 있음 보장) - 판매불가 제품 자동 제외 --- ### 3단계: AI 프롬프트 구성 ```python UPSELL_MODEL = 'anthropic/claude-sonnet-4-5' # Opus 대신 Sonnet (비용 최적화) SYSTEM_PROMPT = """당신은 동네 약국(청춘약국)의 친절한 약사입니다. 고객의 구매 이력을 보고, 약국에 실제로 있는 제품 중에서 하나를 추천합니다. 반드시 [약국 보유 제품 목록]에 있는 제품명을 그대로 사용하세요. 목록에 없는 제품은 절대 추천하지 마세요. 강압적이거나 광고 같은 느낌이 아닌, 진심으로 건강을 걱정하는 약사의 말투로 작성해주세요. 반드시 아래 JSON 형식으로만 응답하세요.""" USER_PROMPT = f"""고객 이름: {user_name} 오늘 구매한 약: {current_items} 최근 구매 이력: {recent_products} [약국 보유 제품 목록 — 이 중에서만 추천하세요] {product_list} 규칙: 1. 위 목록에 있는 제품 중 오늘 구매한 약과 함께 먹으면 좋거나, 구매 패턴상 필요해보이는 약 1가지만 추천 2. 오늘 이미 구매한 제품은 추천하지 마세요 3. 메시지는 2문장 이내, 따뜻하고 자연스러운 톤 4. product 필드에는 목록에 있는 제품명을 정확히 그대로 적어주세요 응답 JSON: {{"product": "목록에 있는 정확한 제품명", "reason": "추천 이유 (내부용)", "message": "고객용 메시지"}}""" ``` --- ### 4단계: AI 응답 및 저장 ```json // Claude 응답 예시 { "product": "종근당 비타민D 1000IU", "reason": "감기약과 함께 면역력 강화에 도움", "message": "홍길동님, 감기약 드시면서 비타민D도 같이 챙기시면 회복에 도움이 되실 거예요. 요즘 일조량 적을 때 특히 좋답니다." } ``` ```sql -- SQLite: ai_recommendations 테이블에 저장 INSERT INTO ai_recommendations (user_id, transaction_id, recommended_product, recommendation_message, recommendation_reason, trigger_products, ai_raw_response, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ``` --- ### 5단계: 추천 노출 ``` GET /api/recommendation/{user_id} ``` - 마이페이지에서 조회 - 키오스크에서 적립 직후 표시 - 7일 후 만료 (expires_at) --- ## 핵심 쿼리 정리 | 용도 | DB | 쿼리 | |------|-----|------| | 고객 최근 거래 | SQLite | `claim_tokens WHERE claimed_by_user_id = ?` | | 거래별 품목 | MSSQL | `SALE_SUB JOIN CD_GOODS WHERE SL_NO_order = ?` | | 보유 제품 TOP 40 | MSSQL | `SALE_SUB GROUP BY GoodsName ORDER BY COUNT DESC` | | 추천 저장 | SQLite | `INSERT INTO ai_recommendations` | | 추천 조회 | SQLite | `SELECT FROM ai_recommendations WHERE user_id = ?` | --- ## 비용 최적화 전략 ### 1. 모델 선택 ```python # 업셀링은 Sonnet (빠르고 저렴) UPSELL_MODEL = 'anthropic/claude-sonnet-4-5' # 복잡한 분석은 Opus (메인 세션) # sessions.patch로 세션별 모델 오버라이드 ``` ### 2. 토큰 절약 - 보유 제품 TOP 40개만 전달 (전체 재고 X) - 시스템 프롬프트 간결하게 - JSON 응답 강제 (불필요한 설명 제거) ### 3. 세션 분리 ```python # 고객별 세션 분리 → 컨텍스트 축적 방지 session_id = f'upsell-real-{user_name}' ``` --- ## Fallback 전략 ```python # 1차 시도: 실데이터 기반 (보유 제품 목록 제공) rec = generate_upsell_real(user_name, current_items, recent_products, available) # 2차 시도: 자유 생성 (보유 제품 목록 없이) if not rec: rec = generate_upsell(user_name, current_items, recent_products) ``` **왜 Fallback?** - MSSQL 연결 실패 시에도 추천 가능 - 보유 제품 쿼리 실패해도 서비스 지속 --- ## 관련 파일 ``` pharmacy-pos-qr-system/ ├── backend/ │ ├── app.py │ │ ├── _get_available_products() # 보유 제품 조회 │ │ ├── _generate_upsell_recommendation() # 메인 로직 │ │ └── /api/recommendation/{user_id} # 추천 조회 API │ │ │ ├── services/ │ │ └── clawdbot_client.py │ │ ├── generate_upsell() # 자유 생성 │ │ ├── generate_upsell_real() # 실데이터 기반 │ │ └── ask_clawdbot() # Gateway 호출 │ │ │ ├── templates/ │ │ └── admin_ai_crm.html # CRM 관리 페이지 │ │ │ └── db/ │ └── mileage.db # SQLite (ai_recommendations) │ └── docs/ ├── ai-upselling-architecture.md # 이 문서 └── clawdbot-gateway-api.md # Gateway 연동 가이드 ``` --- ## 향후 개선 방향 ### 1. 추천 정확도 향상 - 제품 카테고리 분류 추가 (감기약, 영양제, 외용제 등) - 계절/시간대별 추천 가중치 - 고객 연령대/성별 기반 필터 ### 2. 성과 측정 - 추천 → 실제 구매 전환율 추적 - A/B 테스트 (추천 vs 비추천) - 인기 추천 제품 통계 ### 3. 실시간 재고 연동 - 현재: 최근 30일 판매 기준 (간접 재고) - 개선: 실제 재고 수량 기반 추천 ### 4. 멀티 추천 - 현재: 1개 제품만 추천 - 개선: 상황별 2-3개 옵션 제시 --- *작성: 2026-02-27 | 용림 🐉*