o2o-negosium-original/schedules/anchoring/schema.sql
민헌 a132dac57a feat(anchoring): 조회용 뷰 2종 신설 — records 테이블은 검토 후 기각 (TODO 2)
요구(회사별 앵커링 값 업데이트 리스트업 + 이전 값 판별)는 rate_adjustments
한 행에 anchor_rate_before→after 가 박제되어 이미 충족 — 신규 테이블은 동일
정보의 사본만 만들므로 기각하고, 조회를 제품화하는 파생 뷰로 해결:

- anchoring.rate_history: 값 변경 이력 리스트업(이전→새 값, delta_permille,
  success_rate, created_at)
- anchoring.current_rates: 칸별 현재값(최신 조정 행 — 없는 칸 = 시작값 10‰)

뷰는 상태가 없어 오염·재구축 이슈 자체가 없고 append-only 보호 대상 아님.
통합 테스트에 뷰 검증 추가(이력 before/after·성공률, 현재값). 운영 문서 §8
쿼리를 뷰 기반으로 단순화, TODO 과제 2 종결(대시보드 페이징 요구 시 스냅샷
테이블 승격 재검토 명시).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:22:25 +09:00

71 lines
4.2 KiB
SQL

-- ============================================================
-- anchoring 모듈 DDL (모듈 소유 — postgres-init 에 두지 않는다)
-- 적용: psql -h <host> -U <user> -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
-- 배치 스캔 최적화: 미처리 세션만 (부분 인덱스)
CREATE INDEX IF NOT EXISTS idx_sessions_anchoring_pending
ON negotiation.sessions (qt_type, status)
WHERE anchoring_adjustment_id IS NULL AND deleted = false;
-- ============================================================
-- 조회용 뷰 (파생 — 상태 없음, 진실 원천은 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;