o2o-negosium-original/agent/router/v1/chat/protocol.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

66 lines
3.3 KiB
Python

"""chat 라우터 프로토콜 (대화형 /chat, P7 슬라이스)."""
from typing import List, Optional
from pydantic import Field
from common.models.gmodel import Req_WebPacketProtocol, Res_WebPacketProtocol
class Req_Chat(Req_WebPacketProtocol):
"""대화 한 턴. session_id 없으면 새 협상 시작(아래 컨텍스트로). tenant 는 헤더로만."""
session_id: Optional[str] = Field(None, description="없으면 새 세션 생성")
rq_type: str = Field("재협상", description="재협상 | 재견적")
user_input: Optional[str] = Field(None, description="버튼 선택 텍스트 또는 가격(price 모드)")
# ① desync 감지: backend 가 보는 직전 봇 step(내부 step 또는 표시 step). 없으면 검사 생략.
# agent 는 자기 세션 step 을 정답으로 보고 진행하되, 불일치 시 경고 로깅하고 응답에 desynced 를 실어
# backend/front 가 agent 응답의 step/client_step 으로 리싱크하게 한다.
client_step: Optional[str] = Field(None, description="backend 가 본 직전 봇 step (desync 감지용)")
# 새 세션 시작 시 협상 컨텍스트 (옵션, 기본값 제공)
revenue_amount: float = 20_000_000
distribution_code: str = "A"
partner_count: int = 1
acceptance_ratio: float = 0.05
# 갑(KT/iMK)이 직접 입력. anchor < target. anchor 기본 제안값 = target*(1-0.01).
target_price: int = 10000 # KT 목표 매입가
anchor_price: int = 9900 # KT 앵커링가(목표가보다 낮음). 제시가 ≤ anchor → 우선협상
# 기존 공급가(품목 기준가). 가격협상_확인 멘트의 인하율(discount_rate) 산출용. 0 이면 인하율 미표시.
item_price: int = 0
class Res_Chat(Res_WebPacketProtocol):
session_id: Optional[str] = None
step: Optional[str] = None
client_step: Optional[str] = None
script: Optional[str] = None
input_mode: Optional[str] = None
input_options: Optional[List[str]] = None
chat_end: bool = False
outcome: Optional[str] = None
# ① step desync 신호: 요청 client_step 이 agent 세션 step 과 달랐음을 알린다(agent step 이 정답).
desynced: bool = False
# 가격협상 턴에서 선택된 협상 카드 + 학습 메타
card_id: Optional[str] = None
# ⑦ 표현 계약: 가격협상 턴은 카드 스크립트를 script 로, 협상지표 게이지를 indicator_value 로 내려보낸다.
# backend/front 가 이미 indicator/bot_chat_type 패스스루·게이지 렌더 준비 완료.
bot_chat_type: Optional[str] = None # 가격협상="indicator", 종료폼=summaryRSP/CM 등. 일반 텍스트는 None.
indicator_value: Optional[float] = None # 협상 성공률 1~99 (가격협상 턴)
indicator_range: Optional[str] = None # PZ1|PZ2|PZ3 (가격 구간)
policy: Optional[str] = None
q_value: Optional[float] = None
updated_q: Optional[float] = None
visit_count: Optional[int] = None
reward_total: Optional[float] = None
class Res_ChatSession(Res_WebPacketProtocol):
"""① 세션 상태 조회 응답. backend 가 타임아웃/재진입 시 정합을 맞출 때 사용."""
session_id: Optional[str] = None
step: Optional[str] = None
client_step: Optional[str] = None
rq_type: Optional[str] = None
ended: bool = False
found: bool = False