o2o-negosium-original/schedules/anchoring/docs/워크플로우.md
민헌 a2c299aa14 refactor(anchoring): 도메인 이름 전면 개편 — adjustments·anchoring_value/price·price_range·sample 용어 통일
용어 체계: 값=anchoring_value(정수‰)·가격=anchoring_price·조정=adjustment·구간=price_range·표본=sample

- DB: rate_adjustments→anchoring.adjustments (id→adjustment_id, price_bracket_index→price_range_index,
  nego_count→sample_count, anchor_rate_before/after→anchoring_value_before/after,
  consumed_session_ids→used_session_ids)
- sessions: target_anchoring_price→anchoring_price, anchor_rate_permille→anchoring_value,
  last_offered_price→last_offer_price, anchoring_adjustment_id→used_by_adjustment_id
- 뷰: rate_history/current_rates→value_history/current_values, delta_permille→value_change
- 코드: calc_price_range_index·calc_anchoring_price·evaluate_samples·get_current_value·
  get_latest_adjusted_value·get_current_anchoring_value·fetch_current_values·get_base_anchoring_value·
  Adjustment(ORM)·update_last_offer_price, 상수 ANCHORING_VALUE_MIN/MAX·ADJUSTMENT_STEP·
  PRICE_RANGE_COUNT/INDEX_MAX, 배치 로그 키 bracket=→price_range=
- API: negodata protocol 필드 target_anchoring_price→anchoring_price (front 생성 모델·컴포넌트 동반)
- 기존 DB 마이그레이션 신설: schedules/anchoring/migrations/20260706_rename_anchoring.sql
  (멱등 DO 블록 — 테이블·컬럼·뷰·인덱스·PK 제약. 코드 배포와 동시 적용 필요)
- postgres-init 01·04, 문서 6종 동기화
- 실배포 전 수정 포함: main.py argparse 화(--dry-run 단독·오타 플래그 기동 전 차단),
  박제 정합식 calc_anchoring_price 재사용, clamped 지표가 실제 포화만 집계(경계값 유지 제외)

주의: sessions.anchoring_value(정수‰)와 quotation_settings.anchoring_value(구 float 비율)는
같은 이름·다른 단위 — 구 컬럼은 미변경.

검증: 모듈 20·negodata 50·backend 57 테스트 통과, front tsc·vite build 통과,
로컬 DB 마이그레이션 적용 후 배치 dry-run·상주 기동·양 서버 부팅 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:17:01 +09:00

8.6 KiB
Raw Blame History

앵커링 시스템 워크플로우 설명 (비개발자용)

문서 성격: 개발용.md의 워크플로우를 비개발자도 이해할 수 있게 풀어 쓴 안내서. 버전: v1.2 기준 (2026-07-02) — 정책 배경은 기획용.md, 기술 상세는 개발용.md 참조.


한 줄 요약

협상마다 "이 가격 이하면 합의한다"는 기준선을 장부에서 찾아 정하고, 협상이 끝날 때마다 결과가 쌓이고, 2주에 한 번 시스템이 그 결과를 채점해서 장부의 숫자를 한 칸씩 조정한다 — 이 순환 구조입니다.


등장하는 것 4가지

이름 비유 역할
기준표 (정적 기본 테이블) 공장 출하 시 기본 설정값 모든 칸의 출발점(전부 1%). 절대 안 바뀌는 내장 표
협상 기록 (negotiation.sessions) 협상 한 건 한 건의 계약서 철 "그때 기준가가 얼마였고, 상대가 얼마를 써냈고, 얼마에 끝났는지"가 적힘
조정 장부 (anchoring.adjustments) 가격 정책 변경 대장 "언제, 어떤 근거로, 몇 %에서 몇 %로 바꿨다"가 한 줄씩만 추가됨
빠른 조회판 (Redis) 벽에 붙여둔 최신 가격표 협상 시작할 때 즉시 참조하는 사본. 원본은 항상 조정 장부

여기서 칸(cell) 이란 값을 관리하는 최소 단위로, 어느 회사 × 어떤 협력사 유형(유통/제조/총판) × 어떤 가격대 조합입니다. 가격대는 자릿수 단위 사다리(1천 원대·2천 원대 … 1만 원대·2만 원대 … 9천만 원대, 총 46칸)로 나뉘고, 1억을 넘는 금액은 전부 마지막 가격대 칸으로 들어갑니다.


워크플로우 — 시간 순서대로

① 서버가 켜질 때

기준표(46개 가격구간 × 시작값 1%)를 메모리에 올리고, 표가 손상됐으면 아예 서버를 켜지 않습니다. 잘못된 가격으로 협상하는 것보다 안 켜지는 게 낫다는 안전장치입니다.

② 견적(협상 건)이 만들어질 때 — "기준선을 정한다"

재협상 견적이 생성되는 시점에 견적 시스템(negodata)이 이 협상의 칸을 찾습니다. 그 칸의 현재 앵커링 값(깎는 비율)을 빠른 조회판에서 읽고 — 없으면 조정 장부, 그것도 없으면 기준표 순서로 —

기준가(앵커링가) = 목표가 × (1 − 앵커링 값), 1원 단위 내림

예) 목표가 30,000원, 앵커링 값 3% → 기준가 29,100원 — 협력사가 이 이하를 써내면 그 가격으로 합의

이 기준가는 협력사 화면에 표시되지 않고, 챗봇이 합의 가능 여부를 판단하는 내부 기준선으로만 동작합니다(정보 비대칭 유지 전략).

을 계산합니다. 그리고 이 협상에 쓴 비율과 기준가를 협상 기록에 도장 찍듯 고정(박제) 합니다. 이후 2주 정산이 지나가서 칸의 값이 바뀌어도, 이미 만들어진 협상의 기준가는 절대 흔들리지 않습니다. 협상이 다음 라운드로 재생성될 때도 옛 값을 물려받지 않고 그 시점의 칸 값으로 새로 계산합니다.

③ 협력사가 가격을 써낼 때 — "가격 흔적"

협력사가 협상 채팅에서 가격을 입력할 때마다, 그 마지막 제시가가 협상 기록에 남습니다(last_offer_price).

이게 중요한 이유: 가격을 써낸 협상과 한 번도 안 써낸 협상은 정책적으로 완전히 다르게 취급하기 때문입니다.

  • 가격을 써냈는데 기준가 아래로 합의가 안 됨(결렬·중간 이탈 포함) = "기준이 시장보다 세다"는 신호 = 실패로 카운트
  • 가격을 한 번도 안 써내고 끝남(미참여·무응답, 담당자 부재 등) = 기준가는 화면에 안 보이므로 우리 기준과 무관 = 판단 재료에서 제외

④ 협상이 끝나면

따로 하는 일이 없습니다. 계약서 철(협상 기록)에 결과가 이미 다 남아 있으니까요.

이게 이번 설계(v1.2)의 특징입니다 — 별도 표본 장부를 만들지 않고, 협상 기록 자체를 나중에 채점 근거로 씁니다. 기준가·마지막 제시가·투찰가·종료 상태가 전부 확정된 값이라, 언제 채점해도 같은 결과가 나옵니다.

⑤ 격주 토요일 자정 — "정산"

2주에 한 번 시스템이 깨어나 아직 채점 안 된 종료 협상들을 전부 꺼내 채점합니다:

상황 채점
기준가 이하로 합의 성사 성공
기준가보다 높게 합의(예외적) 실패
가격을 써냈지만 합의 못 함 — 결렬·중간 이탈 포함 실패
가격을 한 번도 안 써내고 끝남 제외 (셈에서 뺌)

칸별로 모아서 유효 결과가 10건 이상인 칸만 성공률을 내고, 3단계 규칙으로 딱 한 계단만 조정합니다:

성공률 판단 조치
60% 이상 잘 통하고 있다 올림 (유통 ±2%p / 총판 ±1.5%p / 제조 ±1%p)
30% ~ 60% 적당하다 유지
30% 미만 너무 셌다 내림 (같은 폭)

값은 어떤 경우에도 1% 아래로 내려가지 않고 20% 위로 올라가지 않습니다.

정산이 끝나면:

  1. 조정 장부에 한 줄 추가 — "몇 건 중 몇 건 성공, 1% → 3%, 어떤 협상들을 근거로"
  2. 채점에 쓴 협상들에 "이 조정에 사용됨" 스탬프를 찍음 (같은 협상이 두 번 채점되는 일 방지)
  3. 벽의 가격표(빠른 조회판)를 새 값으로 갱신

10건이 안 되는 칸은 스탬프를 안 찍고 그대로 둡니다 — 그게 이월입니다. 다음 정산 때 4주치가 함께 채점되고, 그래도 부족하면 6주, 8주… 로 자연스럽게 기간이 늘어납니다.

⑥ 그리고 다시 ②로

다음 재협상은 조정된 값으로 시작합니다. 이 순환이 반복되면서 각 칸의 값은 "시장이 받아주는 한계선" 근처에서 자연스럽게 안정됩니다.


이 구조가 주는 안전장치

  • 같은 협상이 두 번 채점될 수 없음 — 스탬프 찍힌 협상은 다음 정산에서 자동으로 빠집니다. 정산이 실수로 두 번 돌아도 결과가 같습니다.
  • 정산이 한 번 빠져도 문제 없음 — 다음 정산이 4주치를 한 번에 채점하고, 그래도 조정은 한 계단만 하므로 값이 튀지 않습니다.
  • 진행 중 협상은 절대 안 흔들림 — 기준가는 협상 생성 시점에 고정되므로, 정산이 값을 바꿔도 이미 시작한 협상에는 영향이 없습니다.
  • "왜 이 칸이 7%야?"에 항상 답할 수 있음 — 조정 장부에 모든 변경이 근거(어떤 협상들, 성공률)와 함께 영구 보존됩니다. 장부는 수정·삭제가 금지돼 있습니다.
  • 빠른 조회판이 날아가도 무사 — 어차피 사본이라 조정 장부에서 언제든 다시 만들 수 있습니다. 조회판(Redis)이 아예 꺼져 있어도 협상은 원본 장부를 직접 읽어 계속 동작하고, 조회판에 옛 값이 남아 있더라도 매주 정산 시각에 최신 값으로 전부 다시 붙입니다.
  • 회사 간 칸막이 — A사의 협상 결과는 A사의 칸에만 반영됩니다. 다른 회사의 값과 기록은 완전히 분리됩니다.
  • 예행 연습이 가능 — 실제로 아무것도 바꾸지 않고 "이번 정산에서 무엇이 어떻게 바뀔지"만 미리 보는 모드(dry-run)가 있어, 첫 가동처럼 조심스러운 순간에 결과를 눈으로 확인한 뒤 진행할 수 있습니다.
  • 잘못 찍힌 기준가를 자동 감지 — 정산 때마다 각 협상에 도장 찍힌 기준가가 규칙대로 계산된 값인지 대조해서, 견적 시스템 쪽 계산 실수를 경고로 잡아냅니다.

자주 나올 질문

Q. 값이 조정되면 기준표가 바뀌는 건가요? 아닙니다. 기준표는 출발선이고 절대 바뀌지 않습니다. 조정 장부에 이력이 한 줄 쌓여서 "현재 위치"가 바뀌는 것입니다.

Q. 결과가 많이 쌓이면 값이 크게 움직이나요? 아닙니다. 13건이 쌓였든 30건이 쌓였든 성공률만 계산하고, 조정은 정산일당 딱 한 계단입니다.

Q. 거래가 거의 없는 칸은요? 10건이 찰 때까지 정산일마다 기다립니다(2주 → 4주 → 6주…). 그동안은 시작값(1%) 근처에 머무는데, 거래가 없는 칸이니 사업 영향도 작습니다.

Q. 사람이 수동으로 값을 바꿀 수 있나요? 현재 정책에서는 없습니다. 값은 오직 협상 결과 데이터로만 움직이고, 모든 변경은 이력으로 추적 가능합니다.