o2o-negosium-original/agent/negotiation/chat/service/indicator.py
민헌 1d4e5816b2 [fix] 재협상 가격협상 흐름 정비 — 무한루프·카드 연출·요약카드
agent 가격협상 무한 루프 수정 + 선행 chat_server 의 가격협상 연출(카드
스크립트 + 협상지표 게이지) 복원, 버짓 확인 입력가 표시 버그, 요약카드
공급사 담당자/날짜 표시 정비.

- agent: 가격협상 루프를 라운드 상한(MAX_ROUNDS)으로 종료(카드선택 실패해도
  종료 보장 — 기존엔 카드소진에만 의존해 무한 입력). chat_server iteration>=3 동작 복원.
- agent: 가격협상 턴에 선택 카드 스크립트(scripts_cards.json)를 script 로 렌더 +
  협상지표(indicator.py, 1~99/PZ) 산출해 indicator_value/bot_chat_type 전달.
  Res_Chat 에 indicator/bot_chat_type, Req_Chat 에 item_price 추가.
- agent: 가격협상_확인 멘트에 할인율(discount_rate) 표시,
  가격협상_확인_버짓이 target 이 아니라 입력가(input_price)를 표시하도록 수정.
- backend: item_price(기존 공급가) 를 agent 로 전달. 요약카드 공급사 담당자
  정보를 supplier_users(빈 이메일) 대신 partner.suppliers.manager_* 에서 조회,
  ChatSummary.supplier_manager_phone 추가.
- frontend: 요약카드 협상 개시/종료 시각을 "YYYY년 MM월 dd일 HH시 MM분 SS초"(KST)
  로, 공급 계약 만료일(+1년) ISO 파싱, 우선협상 대상자에 공급사 연락처/이메일 표시.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 11:52:34 +09:00

43 lines
1.8 KiB
Python

"""협상지표(협상 성공률) 산출 — 3구간 모델.
선행 chat_server 의 ThreeSegIndicator 를 우리 구현으로 재작성(브랜드/특정사 비포함, CLEANROOM.md).
가격협상(카드선택) 턴에 화면 게이지로 표시할 값을 만든다. anchor < target 전제
(anchor=우선협상 기준가, target=목표 매입가). 구매자 관점이라 제시가가 낮을수록 지표가 높다.
- PZ1: 제시가 ≤ anchor → 99 (우선협상 가능 구간)
- PZ2: anchor < 제시가 < target → 99→80 선형 보간(19 스텝)
- PZ3: 제시가 ≥ target → 79 에서 지수감쇠, 최소 1
반환: (indicator_value 1~99, indicator_range "PZ1"|"PZ2"|"PZ3") 또는 입력이 부적합하면 None.
backend 컬럼이 NUMERIC(8,6)(절대값 <100)이라 값은 항상 1~99 범위로 보장한다.
"""
import math
from typing import Optional, Tuple
def compute_indicator(anchor_price, current_price, target_price) -> Optional[Tuple[int, str]]:
try:
anchor = float(anchor_price)
current = float(current_price)
target = float(target_price)
except (TypeError, ValueError):
return None
if target <= 0 or current <= 0:
return None
# PZ1 — 우선협상 구간(제시가가 앵커가 이하)
if current <= anchor:
return 99, "PZ1"
# PZ2 — 협상 가능 구간(앵커가 초과 ~ 목표가 미만): 99→80 선형 보간
if anchor < current < target:
value = int(math.floor(99 - ((current - anchor) / (target - anchor) * 19)))
return max(80, min(99, value)), "PZ2"
# PZ3 — 목표가 이상: 79 에서 목표가 정규화 지수감쇠, 최소 1
scale_exponent = int(math.floor(math.log10(target)))
k = math.pow(10, -scale_exponent)
raw_score = 79 * math.exp(-k * (current - target))
return max(1, int(math.floor(raw_score))), "PZ3"