-- ============================================================ -- anchoring 모듈 DDL (모듈 소유 — postgres-init 에 두지 않는다) -- 적용: psql -h -U -d negosium_db -f schema.sql -- 신규 DB 구축 순서: postgres-init/01~04 → 이 파일 -- 규범: docs/개발용.md §6. IF NOT EXISTS 라 재적용 안전. -- 컨벤션: FK/CHECK/PG ENUM 없음, SMALLINT 코드, uuid 키, TIMESTAMPTZ(UTC). -- ============================================================ \connect negosium_db CREATE SCHEMA IF NOT EXISTS anchoring; -- 앵커링 값 조정 이력. append-only — UPDATE/DELETE 금지(§5), updated_at/deleted 의도적 생략. CREATE TABLE IF NOT EXISTS anchoring.rate_adjustments ( id BIGSERIAL PRIMARY KEY, company_id uuid NOT NULL, -- 테넌트(partner.items.company_id 유래) supplier_type SMALLINT NOT NULL, -- 1=유통(δ20) 2=제조(δ10) 3=총판(δ15) price_bracket_index INTEGER NOT NULL, -- 가격구간 0..45 자릿수 사다리 (앱 보장) nego_count INTEGER NOT NULL, -- 유효 표본 수 n (>=10, 앱 보장) success_count INTEGER NOT NULL, -- n 중 성공(BID_SUCCESS) 건수 anchor_rate_before SMALLINT NOT NULL, -- 직전 값(‰) (이력 없었으면 정적 테이블 시작값) anchor_rate_after SMALLINT NOT NULL, -- 조정 후 값(‰), clamp [10,200] 앱 보장 consumed_session_ids JSONB NOT NULL, -- 소비한 세션 uuid 배열(창 박제 — 재현성·감사) created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- 현재 값 조회 최적화: 칸별 최신 조정 CREATE INDEX IF NOT EXISTS idx_rate_adjustments_cell ON anchoring.rate_adjustments (company_id, supplier_type, price_bracket_index, id DESC); -- 선행요건 §13 + 소비 마킹. target_anchoring_price 는 기존 컬럼(negodata 가 생성 시 박제). ALTER TABLE negotiation.sessions ADD COLUMN IF NOT EXISTS anchor_rate_permille SMALLINT NULL, -- 제안 당시 rate(‰) 박제 ADD COLUMN IF NOT EXISTS last_offered_price BIGINT NULL, -- 협력사 마지막 제시가(원) — 가격 입력마다 backend 가 갱신, 종료 후 불변. NULL=가격 흔적 없음(표본 제외) ADD COLUMN IF NOT EXISTS anchoring_adjustment_id BIGINT NULL; -- NULL=미처리 0=제외확정 >0=소비한 조정 id -- 배치 스캔 최적화: 미처리 "재협상" 세션만 (부분 인덱스). -- qt_type=1 을 술어에 포함해야 함 — 빼면 배치가 마킹하지 않는 비재협상 세션이 -- 영구 잔류해 인덱스가 전체 세션 수에 비례해 성장한다(의도는 이월 풀만 담는 소형 인덱스). CREATE INDEX IF NOT EXISTS idx_sessions_anchoring_pending ON negotiation.sessions (status) WHERE anchoring_adjustment_id IS NULL AND deleted = false AND qt_type = 1; -- ============================================================ -- 조회용 뷰 (파생 — 상태 없음, 진실 원천은 rate_adjustments) -- ============================================================ -- 회사별 앵커링 값 변경 이력 리스트업: "언제, 어떤 칸이, 몇 건 중 몇 건 성공으로, 몇 ‰에서 몇 ‰로" CREATE OR REPLACE VIEW anchoring.rate_history AS SELECT id AS adjustment_id, company_id, supplier_type, -- 1유통/2제조/3총판 price_bracket_index, -- 0..45 자릿수 사다리 anchor_rate_before, -- 이전 값(‰) anchor_rate_after, -- 새 값(‰) anchor_rate_after - anchor_rate_before AS delta_permille, nego_count, success_count, round(success_count::numeric / nego_count, 3) AS success_rate, created_at FROM anchoring.rate_adjustments; -- 칸별 현재값: 칸의 최신 조정 행. 여기 없는 칸의 현재값 = 정적 테이블 시작값(10‰) CREATE OR REPLACE VIEW anchoring.current_rates AS SELECT DISTINCT ON (company_id, supplier_type, price_bracket_index) company_id, supplier_type, price_bracket_index, anchor_rate_after AS anchor_rate_permille, id AS last_adjustment_id, created_at AS last_adjusted_at FROM anchoring.rate_adjustments ORDER BY company_id, supplier_type, price_bracket_index, id DESC;