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:
thug0bin 2026-02-26 21:21:48 +09:00
parent a2829436d1
commit 4c3e1d08b2
2 changed files with 83 additions and 4 deletions

View File

@ -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 응답 없음)")

View File

@ -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