o2o-negosium-original/agent/tests/test_negotiation_step_preview.py
hbyang 1682481b01 [feat] agent: 협상 고도화 — LLM 표현/이해층 + 카드 전술 실행계층 + ktcommerce 정리
카드 재설계("멘트 카드 → 전술 카드"):
- tactics.py 신설: 카드번호→전술(카운터 산식) 레지스트리, min(counter,target) 클램프
- 카운터 수락=즉시 타결(pending_counter_price 일반화, 구 offer_1pct 흡수)
- 목표가 초과 타결 금지(성공스텝 진입 가드) + "카드 소진=실패" 폐지→종결 국면
- 선택형 와일드카드(WC-*) 발동 + card.wild_cards 멘트 DB 어댑터

LLM 계층:
- Phase 2 표현층 ScriptNaturalizer(카드 멘트 자연화, 마커·치환자·숫자 보존 검증)
- Phase 3 이해층 InputInterpreter(자유발화 NLU→기대입력, 한국어 가격 파서)
- OPENAI_API_KEY env override(server_configs) + 전역 자격증명 게이트

결정 스택(Phase 1):
- 협상 규칙 데이터화(negotiation.wildcard_*_ratio/max_counter_rounds)
- 선택카드 우선순위 prior(UCB 방문수 감쇠, Q-table 오염 없음)

버그픽스:
- 인하율 음수 표기 제거 + 인상/동일/인하 구분(discount_phrase)
- 자연화 강조마커 보존(볼드/색 소실 시 원본 폴백)
- 카드 시드 가격변수(prev_partner_price·target_mid_price·middle_price 등) 치환

정리:
- ktcommerce 테넌트 삭제 + 테스트 21파일 imarketkorea/_base 로 마이그레이션
- 실 LLM 호출 차단 conftest 가드

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:37:36 +09:00

62 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""/v1/negotiation/step (P2 루프 HTTP 프리뷰) 회귀 스모크.
서버 기동 후 실제로 칠 수 있는 유일한 업무 엔드포인트 — 테넌트 라우팅 + config 주입 + 응답 형태를 검증.
DB 로깅은 db_engine 픽스처 유무와 무관하게 log=false 로 끄고 검증(순수 응답 형태).
"""
import pytest
_BODY = {
"revenue_amount": 20_000_000,
"distribution_code": "A",
"partner_count": 1,
"acceptance_ratio": 0.11,
"input_price": 9950,
"anchor_price": 9900, # KT 앵커링가 (anchor < target)
"target_price": 10000, # KT 목표 매입가
"round_number": 3,
"outcome": "success",
"log": False,
}
@pytest.mark.asyncio
async def test_step_requires_tenant_header(client):
r = await client.post("/v1/negotiation/step", json=_BODY)
assert r.status_code == 400
assert r.json()["result"]["desc"] == "TENANT_HEADER_MISSING"
@pytest.mark.asyncio
async def test_step_tenant_divergence(client):
rk = await client.post("/v1/negotiation/step", headers={"X-Tenant-ID": "_base"}, json=_BODY)
ri = await client.post("/v1/negotiation/step", headers={"X-Tenant-ID": "imarketkorea"}, json=_BODY)
assert rk.status_code == 200 and ri.status_code == 200
dk, di = rk.json(), ri.json()
# 같은 입력이 테넌트 config 에 따라 다른 상태/카드로 갈린다 (_base=공용 NGC-0xx, imk=NGC-Bxxx)
assert dk["card_id"].startswith("NGC-0")
assert di["card_id"].startswith("NGC-B")
assert dk["state_index"] != di["state_index"]
# 응답 형태
assert dk["result"]["success"] is True
# input 9950: price_reward=(10000-9950)/(10000-9900)=0.5,
# W=Σ0.2·Sᵢ=0.2×(0.6+0.3+0.5+1.0+0.5)=0.58 (revenue mid, dist A, single, accept high, zone1),
# penalty=0.02×3=0.06, end(success)=1.0 → 0.58×0.5 + 0.42×1.0 − 0.06 = 0.65
assert dk["reward"]["total"] == pytest.approx(0.65, abs=1e-6)
assert dk["logged"] is False # log=false
@pytest.mark.asyncio
async def test_step_invalid_distribution_code_is_domain_error(client):
body = dict(_BODY, distribution_code="Z")
r = await client.post("/v1/negotiation/step", headers={"X-Tenant-ID": "imarketkorea"}, json=body)
assert r.status_code == 200 # HTTP 는 200, 결과코드로 에러 전달(backend 규약)
assert r.json()["result"]["desc"] == "NEGO_INVALID_STEP"
@pytest.mark.asyncio
async def test_step_invalid_outcome(client):
body = dict(_BODY, outcome="maybe")
r = await client.post("/v1/negotiation/step", headers={"X-Tenant-ID": "imarketkorea"}, json=body)
assert r.json()["result"]["desc"] == "INVALID_REQUEST_DATA"