o2o-negosium-original/agent/tests/test_p1_tenant_config.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

108 lines
4.8 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.

"""P1 검증 (계획서 P1, CLEANROOM.md 반영).
검증 기준 변경: "Chat_server 하드코딩과 1:1 일치"(독점값 복제)를 폐기하고,
"우리 플랫폼 중립 기본값이 정확히 로드 + deep-merge + 차원 산출이 동작"으로 대체한다.
1. 데모 테넌트 config 가 플랫폼 중립 기본값으로 로드된다(합성 카드 코드/중립 라벨).
2. _base deep-merge 단위테스트 (상속 + 부분 오버라이드).
3. state_space_size 자동 산출 (3×3×3×3×2 = 162), action_space_size = 9.
4. 두 번째 테넌트 오버라이드가 base 위에 정확히 병합 + 차원 동일(warm-start 호환).
5. 독점 카드 코드(NC26-*)·verbatim 라벨이 레포 config 에 없다(클린룸 가드).
"""
import os
from tenancy.config_loader import TenantConfigLoader, _deep_merge
_TENANTS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "tenants")
def _loader() -> TenantConfigLoader:
return TenantConfigLoader(tenants_dir=_TENANTS_DIR, cache_ttl_seconds=0)
def test_platform_neutral_defaults_load():
cfg = _loader().load("_base")
# 우리 플랫폼 중립 기본값 (CLEANROOM.md)
assert cfg.state.revenue.thresholds == [10_000_000, 50_000_000]
assert cfg.state.revenue.weights == [0.3, 0.6, 1.0]
assert cfg.state.revenue.descriptions == ["low", "mid", "high"]
assert cfg.state.distribution.code_map == {"A": 0, "B": 1, "C": 2}
assert cfg.state.partner.weights == [0.5, 1.0, 0.3]
assert cfg.state.acceptance.thresholds == [0.03, 0.09]
assert cfg.state.price_zone.weights == [1.0, 0.5]
# reward: 균등 가중치 중립 기본값
assert cfg.reward.beta == 0.2
assert cfg.reward.success_reward == 1.0
assert cfg.reward.failure_penalty == -0.5
assert (cfg.reward.w1, cfg.reward.w2, cfg.reward.w3, cfg.reward.w4, cfg.reward.w5) == (0.2, 0.2, 0.2, 0.2, 0.2)
# policy: 표준 UCB 기본값
assert cfg.policy.type == "ucb"
assert cfg.policy.learning_rate == 0.1
assert cfg.policy.gamma == 0.95
assert abs(cfg.policy.params["exploration_constant"] - 2 ** 0.5) < 1e-12
def test_state_space_and_action_space_size():
cfg = _loader().load("_base")
assert cfg.state.state_space_size == 162 # 3×3×3×3×2 (차원 구성은 기능적 설계)
assert cfg.action_mapping.action_space_size == 11
# 공용 카드 코드 (파일 폴백 스냅샷 — 정본은 DB 카탈로그)
assert cfg.action_mapping.action_to_card["0"] == "NGC-001"
assert cfg.action_mapping.action_to_card["8"] == "NGC-009"
def test_base_deep_merge_unit():
base = {"a": 1, "nested": {"x": 1, "y": 2}, "list": [1, 2]}
override = {"b": 2, "nested": {"y": 20, "z": 30}, "list": [9]}
merged = _deep_merge(base, override)
assert merged["a"] == 1
assert merged["b"] == 2
assert merged["nested"] == {"x": 1, "y": 20, "z": 30} # dict 키 단위 병합
assert merged["list"] == [9] # 리스트는 통째 교체
def test_second_tenant_overrides_merged_on_base():
cfg = _loader().load("imarketkorea")
# 오버라이드된 값
assert cfg.state.revenue.thresholds == [30_000_000, 100_000_000]
assert cfg.reward.failure_penalty == -0.7
assert cfg.reward.beta == 0.25
# 오버라이드 안 한 값은 base 상속
assert cfg.state.distribution.code_map == {"A": 0, "B": 1, "C": 2}
assert cfg.reward.success_reward == 1.0
assert cfg.policy.type == "ucb"
# 차원은 데모 테넌트 A 와 동일(162) → base warm-start 호환(P5)
assert cfg.state.state_space_size == 162
assert cfg.action_mapping.action_space_size == 11
assert cfg.action_mapping.action_to_card["0"] == "NGC-B001"
def test_base_self_does_not_inherit():
cfg = _loader().load("_base")
assert cfg.tenant_id == "_base"
# _base 는 기본 11카드(자동 온보딩 테넌트가 물려받음, base 정책 162×11 정합)
assert cfg.action_mapping.action_space_size == 11
def test_is_registered():
loader = _loader()
assert loader.is_registered("imarketkorea") is True
# 미등록 company_id(uuid 등)는 _base 자동 온보딩 대상이라 '등록됨'으로 본다. 빈 키만 미등록.
assert loader.is_registered("00000000-0000-0000-0000-000000000001") is True
assert loader.is_registered("") is False
def test_no_proprietary_card_codes_or_labels_in_repo():
"""클린룸 가드: 독점 카드 코드/ verbatim 라벨이 로드된 config 에 존재하지 않는다."""
for tid in ("_base", "imarketkorea"):
cfg = _loader().load(tid)
cards = " ".join(cfg.action_mapping.action_to_card.values())
assert "NC26" not in cards # 참고 엔진의 고유 카드 코드
# verbatim 한글 라벨이 아닌 중립 라벨 사용
for d in cfg.state.revenue.descriptions:
assert "원" not in d