o2o-negosium-original/agent/tests/test_p1_tenant_config.py

106 lines
4.5 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("ktcommerce")
# 우리 플랫폼 중립 기본값 (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("ktcommerce")
assert cfg.state.state_space_size == 162 # 3×3×3×3×2 (차원 구성은 기능적 설계)
assert cfg.action_mapping.action_space_size == 9
# 합성 데모 카드 코드 (우리 스킴)
assert cfg.action_mapping.action_to_card["0"] == "NGC-A001"
assert cfg.action_mapping.action_to_card["8"] == "NGC-A009"
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 == 9
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"
assert cfg.action_mapping.action_space_size == 0
def test_is_registered():
loader = _loader()
assert loader.is_registered("ktcommerce") is True
assert loader.is_registered("imarketkorea") is True
assert loader.is_registered("nonexistent_tenant") is False
def test_no_proprietary_card_codes_or_labels_in_repo():
"""클린룸 가드: 독점 카드 코드/ verbatim 라벨이 로드된 config 에 존재하지 않는다."""
for tid in ("_base", "ktcommerce", "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