"""이산 협상 State 모델 (우리 자체 구현, config 주입형). Chat_server 의 state.py 는 임계값/가중치를 IntEnum 클래스변수로 박아 config 주입이 불가능했다. 여기서는 임계값/가중치/코드맵을 모두 TenantConfig.state(StateConfig)에서 주입받고, State 는 차원 인덱스 튜플만 보유하는 순수 값객체로 둔다. state_index 는 mixed-radix(혼합 진법) 인코딩으로 차원 곱 공간에 매핑한다 — 차원 개수가 회사마다 달라도 일반적으로 동작한다(기능적 방법). """ from dataclasses import dataclass from typing import List @dataclass(frozen=True) class State: """5개 이산 차원 인덱스. 각 인덱스는 해당 차원의 [0, dim) 범위.""" revenue_idx: int distribution_idx: int partner_idx: int acceptance_idx: int price_zone_idx: int def to_tuple(self) -> tuple[int, int, int, int, int]: return (self.revenue_idx, self.distribution_idx, self.partner_idx, self.acceptance_idx, self.price_zone_idx) def encode_index(indices: List[int], dims: List[int]) -> int: """mixed-radix 인코딩: indices 를 dims 진법으로 단일 정수에 매핑. idx = ((...(i0)*d1 + i1)*d2 + i2)...)*d_{n-1} + i_{n-1} 범위/차원 검증 포함 — 잘못된 입력은 즉시 실패(학습 차원 오염 방지). """ if len(indices) != len(dims): raise ValueError(f"indices/dims length mismatch: {len(indices)} vs {len(dims)}") idx = 0 for i, (val, dim) in enumerate(zip(indices, dims)): if dim <= 0: raise ValueError(f"dim[{i}] must be positive, got {dim}") if not (0 <= val < dim): raise ValueError(f"index[{i}]={val} out of range [0,{dim})") idx = idx * dim + val return idx