카드 재설계("멘트 카드 → 전술 카드"):
- 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>
60 lines
2.3 KiB
Python
60 lines
2.3 KiB
Python
"""H5 검증 — 학습 검증 하네스 (PoC 본체).
|
|
|
|
핵심 판정(계획서 G): 학습형(qtable_ucb)이 random/static 대비 성과 우상향.
|
|
- 평균보상 우위(95%CI 분리) + '좋은 카드' 적중 우위 → 학습 루프 유효.
|
|
- 시뮬레이터 결정론(동일 seed 재현).
|
|
- 멀티테넌트 분리 실행(--tenant).
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from eval_harness.buyer import HeuristicBuyer, Scenario, make_card_effectiveness
|
|
from eval_harness.runner import run
|
|
from eval_harness.simulator import run_episode
|
|
from negotiation.qtable.domain.model.q_table import QTable
|
|
from negotiation.policies.qtable_policy import UCBQTablePolicy
|
|
from tenancy.config import RewardConfig, StateConfig
|
|
|
|
|
|
def test_simulator_deterministic():
|
|
eff = make_card_effectiveness(9, seed=1)
|
|
scn = Scenario(anchor_price=800, target_price=1000)
|
|
sc, rc = StateConfig(), RewardConfig()
|
|
|
|
def one():
|
|
b = HeuristicBuyer(eff, seed=7)
|
|
p = UCBQTablePolicy(QTable(sc.state_space_size, 9))
|
|
return run_episode(p, b, scn, sc, rc, 9, learn=False).total_reward
|
|
|
|
assert one() == one() # 동일 seed → 동일 결과
|
|
|
|
|
|
def test_card_effectiveness_has_good_cards():
|
|
eff = make_card_effectiveness(9, seed=42, n_good=3)
|
|
good = [a for a, e in eff.items() if e >= 0.7]
|
|
assert len(good) >= 3 # 효과 좋은 카드 존재 → 학습 대상 신호
|
|
|
|
|
|
@pytest.mark.parametrize("tenant", ["_base", "imarketkorea"])
|
|
def test_learning_beats_baseline(tenant):
|
|
report = run("configs/exp_default.yaml", tenant)
|
|
pols = report["policies"]
|
|
q = pols["qtable_ucb"]
|
|
rnd = pols["random"]
|
|
|
|
# 평균보상 우위 + 95%CI 비중첩
|
|
assert q["mean_reward"] > rnd["mean_reward"]
|
|
assert q["mean_reward"] - q["reward_ci95"] > rnd["mean_reward"] + rnd["reward_ci95"]
|
|
# 좋은 카드 적중 우위 (학습으로 카드 우열 파악)
|
|
assert q["good_card_hit_rate"] >= rnd["good_card_hit_rate"] + 0.2
|
|
assert q["good_card_hit_rate"] >= 0.7
|
|
# 종합 판정
|
|
assert report["verdict"]["pass"] is True
|
|
|
|
|
|
def test_static_does_not_learn():
|
|
report = run("configs/exp_default.yaml", "imarketkorea")
|
|
static = report["policies"]["static"]
|
|
# 정적 정책은 항상 고정 카드 → 좋은카드 적중 학습 없음(우연 일치만)
|
|
assert static["good_card_hit_rate"] <= report["policies"]["qtable_ucb"]["good_card_hit_rate"]
|