- 04-alter 에 quotations.close_reason · quotation_settings.mid_action/over_action/regen_limit ALTER 누락분 append. 기존 DB 마이그레이션이 close_reason 개편을 안 담고 있어 UndefinedColumn 나던 것 보정(멱등 IF NOT EXISTS). - postgres-init/01-schema.sql·04-alter.sql → *_20260702.sql 로 rename(스키마 최종 변경일 = 파일명). 사람들이 DB 최신 여부를 파일명으로 판단. - 주석·문서 참조(README·docker-compose·02-learning·agent models·anchoring doc)는 글롭 01-schema*.sql·04-alter*.sql 로 바꿔 다음 rename 때 수정 불필요. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
142 lines
8.4 KiB
SQL
142 lines
8.4 KiB
SQL
-- ============================================================
|
|
-- learning : 협상 에이전트(agent) RL 학습 자산 (Q-Table / 경험로그)
|
|
-- ============================================================
|
|
-- negosium_db 안의 7번째 schema. agent 서비스가 소유한다(backend 는 미사용).
|
|
-- 01-schema*.sql 과 동일 컨벤션: FK 미사용(앱 레이어 무결성), TIMESTAMPTZ(UTC), 코드값 SMALLINT(1부터).
|
|
--
|
|
-- 멀티테넌트 논리 격리: 모든 테이블에 company_id 컬럼. company.companies.company_id(uuid)를
|
|
-- 문자열로 보관하되, 공유 베이스 정책은 예약어 '_base' 를 쓴다(uuid/sentinel 혼용 → VARCHAR).
|
|
-- 모든 유니크/인덱스는 company_id 선두 복합으로 둔다(테넌트 간 충돌 방지 + 스코프 조회).
|
|
|
|
\connect negosium_db
|
|
|
|
CREATE SCHEMA IF NOT EXISTS learning;
|
|
|
|
-- ------------------------------------------------------------
|
|
-- Q-Table 버전 (학습 스냅샷의 헤더)
|
|
-- ------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS learning.q_table_versions (
|
|
version_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
company_id VARCHAR(64) NOT NULL, -- 테넌트 키(company uuid 문자열 또는 '_base')
|
|
version_name VARCHAR(50) NOT NULL, -- 버전명 (예: v000_warmstart_from_base)
|
|
scope SMALLINT NOT NULL DEFAULT 2, -- 1=base, 2=tenant
|
|
base_version_id uuid NULL, -- warm-start 출처 추적(베이스 버전)
|
|
state_space_size INTEGER NOT NULL, -- 차원 정합성 체크용
|
|
action_space_size INTEGER NOT NULL,
|
|
learning_rate NUMERIC(6,4) NOT NULL DEFAULT 0.1000,
|
|
discount_factor NUMERIC(6,4) NOT NULL DEFAULT 0.9500,
|
|
epochs INTEGER NOT NULL DEFAULT 0,
|
|
is_active BOOLEAN NOT NULL DEFAULT FALSE, -- 활성 버전 포인터(테넌트당 1개)
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
|
);
|
|
-- version_name 은 테넌트 스코프에서만 유니크 (계획서 C: UniqueConstraint(tenant, version_name))
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_qtv_company_version
|
|
ON learning.q_table_versions (company_id, version_name);
|
|
-- 테넌트별 활성 버전은 최대 1개 (부분 유니크)
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_qtv_company_active
|
|
ON learning.q_table_versions (company_id) WHERE is_active AND NOT deleted;
|
|
|
|
-- ------------------------------------------------------------
|
|
-- Q 값 (state_index, action_id) -> q_value
|
|
-- ------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS learning.q_values (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
company_id VARCHAR(64) NOT NULL,
|
|
version_id uuid NOT NULL,
|
|
state_index INTEGER NOT NULL,
|
|
action_id INTEGER NOT NULL,
|
|
q_value DOUBLE PRECISION NOT NULL DEFAULT 0.0
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_qval_company_version_sa
|
|
ON learning.q_values (company_id, version_id, state_index, action_id);
|
|
CREATE INDEX IF NOT EXISTS idx_qval_company_version_state
|
|
ON learning.q_values (company_id, version_id, state_index);
|
|
|
|
-- ------------------------------------------------------------
|
|
-- 방문 횟수 (UCB 탐색용)
|
|
-- ------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS learning.visit_counts (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
company_id VARCHAR(64) NOT NULL,
|
|
version_id uuid NOT NULL,
|
|
state_index INTEGER NOT NULL,
|
|
action_id INTEGER NOT NULL,
|
|
count BIGINT NOT NULL DEFAULT 0
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_visit_company_version_sa
|
|
ON learning.visit_counts (company_id, version_id, state_index, action_id);
|
|
CREATE INDEX IF NOT EXISTS idx_visit_company_version_state
|
|
ON learning.visit_counts (company_id, version_id, state_index);
|
|
|
|
-- ------------------------------------------------------------
|
|
-- 경험 로그 (transition). OPE/오프라인RL 의 데이터 소스.
|
|
-- propensity / turn / available_actions / settled_price 는 신규 로깅(소급 불가, 계획서 H0).
|
|
-- ------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS learning.experience_logs (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
company_id VARCHAR(64) NOT NULL,
|
|
transition_id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
session_id uuid NULL, -- negotiation.sessions.session_id 연결
|
|
state_index INTEGER NOT NULL,
|
|
action_id INTEGER NOT NULL,
|
|
card_id VARCHAR(40) NULL, -- 사용된 카드(테넌트 카탈로그)
|
|
q_value_at_selection DOUBLE PRECISION NULL,
|
|
reward DOUBLE PRECISION NULL, -- 보상 산출 후 update
|
|
next_state_index INTEGER NULL,
|
|
done BOOLEAN NOT NULL DEFAULT FALSE,
|
|
snapshot JSONB NULL, -- NegotiationSnapshot 전체(연속 feature)
|
|
propensity DOUBLE PRECISION NULL, -- 행동정책 선택확률 (OPE 필수)
|
|
turn INTEGER NULL, -- 협상 라운드(iteration)
|
|
available_actions JSONB NULL, -- 선택 시점 가용 액션(마스킹)
|
|
settled_price BIGINT NULL, -- 타결가(원)
|
|
visit_count_at_selection BIGINT NULL,
|
|
total_visits_at_selection BIGINT NULL,
|
|
ucb_score_at_selection DOUBLE PRECISION NULL,
|
|
is_new_quote BOOLEAN NOT NULL DEFAULT FALSE, -- 학습 격리(신규견적은 UCB 비활성)
|
|
is_invalidated BOOLEAN NOT NULL DEFAULT FALSE,
|
|
invalidated_reason VARCHAR(255) NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_exp_company_transition
|
|
ON learning.experience_logs (company_id, transition_id);
|
|
CREATE INDEX IF NOT EXISTS idx_exp_company_session
|
|
ON learning.experience_logs (company_id, session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_exp_company_state_action
|
|
ON learning.experience_logs (company_id, state_index, action_id);
|
|
|
|
-- ------------------------------------------------------------
|
|
-- 테넌트별 action_id -> card 매핑 (계획서 C: tenant_action_cards)
|
|
-- PoC 는 카드 매핑 고정. P6 에서 동기화 소스로 사용.
|
|
-- ------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS learning.tenant_action_cards (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
company_id VARCHAR(64) NOT NULL,
|
|
action_id INTEGER NOT NULL,
|
|
card_id VARCHAR(40) NOT NULL, -- card.nego_cards.number 등 테넌트 카탈로그 식별자
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
|
);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS uq_tac_company_action
|
|
ON learning.tenant_action_cards (company_id, action_id) WHERE NOT deleted;
|
|
|
|
-- ------------------------------------------------------------
|
|
-- 대화 세션 상태 (P8-A: /chat 진행 상태 영속화 — 재시작/멀티워커 안전)
|
|
-- 채팅 '로그'(메시지)가 아니라 진행 '상태'(현재 step·맥락·사용카드·라운드)다.
|
|
-- ------------------------------------------------------------
|
|
CREATE TABLE IF NOT EXISTS learning.chat_sessions (
|
|
session_id uuid PRIMARY KEY,
|
|
company_id VARCHAR(64) NOT NULL,
|
|
tenant_id VARCHAR(64) NOT NULL,
|
|
rq_type VARCHAR(10) NOT NULL DEFAULT '재협상',
|
|
step VARCHAR(40) NOT NULL DEFAULT '시작', -- 현재 대기 중인 step
|
|
context JSONB NOT NULL DEFAULT '{}'::jsonb, -- 앵커/목표가·라운드·last_state 등
|
|
used_action_ids JSONB NOT NULL DEFAULT '[]'::jsonb, -- 사용한 카드(중복방지/소진 판정)
|
|
action_space_size INTEGER NOT NULL DEFAULT 0,
|
|
ended BOOLEAN NOT NULL DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_chat_sessions_company ON learning.chat_sessions (company_id);
|