"""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"]