"""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 == 11 # 합성 데모 카드 코드 (우리 스킴) 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 == 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("ktcommerce") is True 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", "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