feat: 실데이터 기반 AI 업셀링 추천 — 약국 보유 제품 목록에서 추천
- generate_upsell_real(): MSSQL 최근 30일 판매 TOP 40 제품 목록을 AI에 제공 - AI가 실제 약국 보유 제품 중에서만 선택하여 추천 - 실데이터 실패 시 기존 자유 생성(generate_upsell) fallback - 기존 generate_upsell은 그대로 보존 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a2829436d1
commit
4c3e1d08b2
@ -1897,9 +1897,32 @@ def admin():
|
||||
# AI 업셀링 추천
|
||||
# ============================================================================
|
||||
|
||||
def _get_available_products():
|
||||
"""약국 보유 제품 목록 (최근 30일 판매 실적 기준 TOP 40)"""
|
||||
try:
|
||||
mssql_session = db_manager.get_session('PM_PRES')
|
||||
rows = mssql_session.execute(text("""
|
||||
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
|
||||
""")).fetchall()
|
||||
return [{'name': r.name, 'price': float(r.price or 0), 'sales': r.sales} for r in rows]
|
||||
except Exception as e:
|
||||
logging.warning(f"[AI추천] 보유 제품 목록 조회 실패: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def _generate_upsell_recommendation(user_id, transaction_id, sale_items, user_name):
|
||||
"""키오스크 적립 후 AI 업셀링 추천 생성 (fire-and-forget)"""
|
||||
from services.clawdbot_client import generate_upsell
|
||||
from services.clawdbot_client import generate_upsell, generate_upsell_real
|
||||
|
||||
if not sale_items:
|
||||
return
|
||||
@ -1940,9 +1963,18 @@ def _generate_upsell_recommendation(user_id, transaction_id, sale_items, user_na
|
||||
except Exception as e:
|
||||
logging.warning(f"[AI추천] 구매 이력 수집 실패 (현재 품목만 사용): {e}")
|
||||
|
||||
# Claude로 추천 생성
|
||||
logging.info(f"[AI추천] 생성 시작: user={user_name}, items={current_items}")
|
||||
rec = generate_upsell(user_name, current_items, recent_products)
|
||||
# 실데이터 기반 추천 (보유 제품 목록 제공)
|
||||
available = _get_available_products()
|
||||
rec = None
|
||||
|
||||
if available:
|
||||
logging.info(f"[AI추천] 실데이터 생성 시작: user={user_name}, items={current_items}, 보유제품={len(available)}개")
|
||||
rec = generate_upsell_real(user_name, current_items, recent_products, available)
|
||||
|
||||
# 실데이터 실패 시 기존 방식 fallback
|
||||
if not rec:
|
||||
logging.info(f"[AI추천] 자유 생성 fallback: user={user_name}")
|
||||
rec = generate_upsell(user_name, current_items, recent_products)
|
||||
|
||||
if not rec:
|
||||
logging.warning("[AI추천] 생성 실패 (AI 응답 없음)")
|
||||
|
||||
@ -247,6 +247,53 @@ def generate_upsell(user_name, current_items, recent_products):
|
||||
return _parse_upsell_response(response_text)
|
||||
|
||||
|
||||
UPSELL_REAL_SYSTEM_PROMPT = """당신은 동네 약국(청춘약국)의 친절한 약사입니다.
|
||||
고객의 구매 이력을 보고, 약국에 실제로 있는 제품 중에서 하나를 추천합니다.
|
||||
반드시 [약국 보유 제품 목록]에 있는 제품명을 그대로 사용하세요.
|
||||
목록에 없는 제품은 절대 추천하지 마세요.
|
||||
강압적이거나 광고 같은 느낌이 아닌, 진심으로 건강을 걱정하는 약사의 말투로 작성해주세요.
|
||||
반드시 아래 JSON 형식으로만 응답하세요. 다른 텍스트 없이 JSON만 출력하세요."""
|
||||
|
||||
|
||||
def generate_upsell_real(user_name, current_items, recent_products, available_products):
|
||||
"""
|
||||
실데이터 기반 업셀링 추천 생성
|
||||
available_products: 약국 보유 제품 리스트 [{'name': ..., 'price': ..., 'sales': ...}, ...]
|
||||
"""
|
||||
product_list = '\n'.join(
|
||||
f"- {p['name']} ({int(p['price'])}원, 최근 {p['sales']}건 판매)"
|
||||
for p in available_products if p.get('name')
|
||||
)
|
||||
|
||||
prompt = f"""고객 이름: {user_name}
|
||||
오늘 구매한 약: {current_items}
|
||||
최근 구매 이력: {recent_products}
|
||||
|
||||
[약국 보유 제품 목록 — 이 중에서만 추천하세요]
|
||||
{product_list}
|
||||
|
||||
규칙:
|
||||
1. 위 목록에 있는 제품 중 오늘 구매한 약과 함께 먹으면 좋거나, 구매 패턴상 필요해보이는 약 1가지만 추천
|
||||
2. 오늘 이미 구매한 제품은 추천하지 마세요
|
||||
3. 메시지는 2문장 이내, 따뜻하고 자연스러운 톤
|
||||
4. product 필드에는 목록에 있는 제품명을 정확히 그대로 적어주세요
|
||||
|
||||
응답은 반드시 아래 JSON 형식으로만:
|
||||
{{"product": "목록에 있는 정확한 제품명", "reason": "추천 이유 (내부용, 1문장)", "message": "{user_name}님, [추천 메시지 2문장 이내]"}}"""
|
||||
|
||||
response_text = ask_clawdbot(
|
||||
prompt,
|
||||
session_id=f'upsell-real-{user_name}',
|
||||
system_prompt=UPSELL_REAL_SYSTEM_PROMPT,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if not response_text:
|
||||
return None
|
||||
|
||||
return _parse_upsell_response(response_text)
|
||||
|
||||
|
||||
def _parse_upsell_response(text):
|
||||
"""AI 응답에서 JSON 추출"""
|
||||
import re
|
||||
|
||||
Loading…
Reference in New Issue
Block a user