협상 불가 사유를 내면 500 이 나고, 거부 폼은 목록·채팅이 따로 놀았으며,
제출한 내용을 다시 볼 방법이 없었다. 결렬 건은 협력사가 낸 거부가를 계약가로
간주해 낙찰시켜 절감 통계가 음수로 뒤집힐 수 있었고, 견적 상세는 판정 가격이
세 곳에 흩어져 대화 탭에선 아예 보이지 않았다.
agent
- 결렬 종료 로깅 크래시 수정 — _log 를 action_id 기반으로 되돌리고 선택 근거
(Q·UCB·방문수)는 decision/policy 가 있을 때만 채운다. 종료 행은 카드 선택이
없고 policy.update 뒤라 값을 넣으면 학습 화면 집계가 오염된다
negosium
- 거부 폼을 목록·채팅 공용 컴포넌트 하나로 통일(사유 3종 + 공급 희망가·의견 선택)
- 거부 사유 열람 — 목록에 '거부 사유 보기'(부가정보 보기와 같은 규격), 채팅
재진입 시 대화 끝에 거부 내역 카드. 목록·채팅 init 응답에 reject_reason·reject_price 추가
- 자유 입력 거부("협상 포기합니다")가 사유 NULL 로 저장되던 문제 수정 — 폼 마커가
없으면 원문을 사유로 쓰고, 문장 속 숫자를 희망가로 오인하지 않는다
- koreanNumber 를 전역 lib 으로 이동(공용 폼이 쓴다)
negodata
- 직접 낙찰에 계약가 입력 — 결렬·미응찰 건을 오프라인으로 다시 협상한 결과를
담당자가 확정해 넣는다. 후보는 초청 협력사 전부(가격 미제출도 포함),
계약가는 sessions.custom.offline_award 에 근거·작성자·시각과 함께 남긴다
- 통계 계약가 = 담당자 확정가 우선, 없으면 투찰가. 거부가를 계약가로 치던 파생 제거.
KPI 에 오프라인 반영 건수 추가
- 견적 상세 리모델링 — 가격 레일(앵커링가/투찰현황 · 목표가 · 타결 상한가 · 결과가)을
시트에 고정해 접힘·탭 전환에도 남기고, 스펙트럼에 타결 판정선과 구간색 추가.
라벨은 폭을 실측해 두 레인으로 배치(겹침 불가). 상품·마감시각 등 전 행 동일 컬럼 제거,
협상현황에 부가정보 노출, 1:1 은 협력사·세션상태를 결과 밴드로 올림
테스트: negosium 58 · negodata 110 통과. 프론트 빌드/린트 통과.
협력사가 단종·품절을 대화 도중 알아채도 봇의 결렬 선언을 기다려야 했고,
거부로 끝난 협상은 가격을 남겨도 낙찰 후보에서 빠져 계약으로 이어지지 않았다.
negosium
- 채팅 액션바에 협상 거부 진입점 — 대화가 끝나지 않고 입력을 기다리는 동안만 노출,
주 CTA 와 붙지 않게 넓은 화면은 우측 끝 고정·좁은 화면은 wrap
- 거부 팝업은 목록 거부 팝업과 같은 어휘·규격, 대화 중이라 공급 희망 가격·의견을 더 받는다
- /reject 에 reject_price·opinion 추가 — sessions.reject_price 저장, 의견은 custom 병합
- 화면 문구 '거절' → '거부' 통일 (버튼·배지·탭·토스트·안내 팝업)
negodata
- 개찰 견적 직접 낙찰 후보 = 가격을 써낸 세션 — 투찰한 협상완료 + 공급 희망가를 남긴 협상거부
- 계약가 파생 _award_price/awardPrice — coalesce(투찰가, 거부 시 공급 희망가)
- 통계 낙찰 세션 조인도 같은 기준 — 안 고치면 거부가로 낙찰한 건이 절감 집계에서 빠진다
- 세션 상태 탭 라벨 '거절사유/거절가격/거절배송방식' → '거부…'
자동 마감 판정(close_and_decide)은 그대로 — 자동 낙찰은 투찰가만 본다.
- 목록 상태를 파생 계산(_effective_status): 견적 마감/마감시간 경과 + 협상생성(1) → 미참여(4).
표시·상태필터·기본정렬 그룹에 같이 적용해 탭·KPI 카운트가 배지와 어긋나지 않게 함(DB 쓰기 없음).
마감 일괄정리 이후 생긴 세션이 '협상 대기'에 남아 눌러도 마감 에러가 나던 헛클릭 제거.
- 미참여·협상거부 세션도 채팅 init 허용 → '결과 보기'로 지난 대화 열람. 대화 재개는 send 가
협상중만 허용해 읽기 전용. 열 게 없는 건은 목록 has_chat(대화 이력 유무)으로 버튼 자체를 감춤.
- 종료 건은 참여 API 를 타지 않고 채팅으로 직행, 채팅 액션바는 목록 복귀만 남겨 입력 잠금.
- chain_max_round 가 단일컬럼 select 결과를 행 튜플로 읽어 매 목록 호출마다 예외 → 체인 최대차수
0 → 옛 라운드에도 재협상 요청이 뜨고 통과되던 버그 수정(+ABC 에 중복된 죽은 구현 제거).
어느 가격이 "공급사에 지불하는 단가"인지는 회사마다 달라, 컬럼을 합치는 대신 회사가 고르게 했다.
판정은 settings.features.nego_baseline_field 1순위, 미설정 회사는 price 만 숨겼으면 purchase_price 폴백.
판정식 정본은 negodata/backend/common/nego_baseline.py (agent·negosium backend 가 같은 규칙 미러).
- negodata front: 회사 설정에 협상 기준가 라디오(선택지마다 실제 나갈 문장 미리보기 + 학습 경고),
'공급사 포털 안내' 탭 신설(협상 유의사항·헬프데스크 연락처), 용어 카탈로그에 target_price·supplier 추가.
인터넷 최저가는 숨김 대상에서 제외(신규 견적의 유일한 목표가 후보).
상품 등록 기본값에서 개발용 더미 제거(price 1,000,000·PROD-BAT-###·800,000·대한민국·10 EA·14).
- agent: get_item_price → get_item_baseline (기준가·호칭·회사 용어사전을 한 쿼리로),
"기존 공급가 대비" 하드코딩을 회사 용어로 치환 + 받침 기준 조사 자동 보정,
협상 스크립트 4종의 협력사·목표가·배송형태 용어를 {label_*} 토큰화, input_options 도 변수 치환 적용.
- negosium: 협상 화면 기준 단가·배송형태 라벨을 회사 설정 기준으로, 유의사항 본문과 헬프데스크 연락처를
하드코딩에서 회사 설정으로(미등록 시 영역 숨김). 유의사항의 VAT·배송비 문구 삭제(IMK 0803 ⑥).
- negodata backend: LPS 검색 가격 힌트를 items.price 고정에서 기준가 규칙으로.
검증: 기준가 설정 3 × 숨김 4 = 12조합에서 협상 멘트·포털 표시·LPS 힌트가 전부 일치.
agent 테스트 176건 통과. 보고서 negodata/docs/nego-baseline-verification.md
- 완료 요약 카드(투찰/협상 결과)에 부가정보·의견을 항상 구조로 표시, 미입력 필드는 '-'
- 협상 리스트 부가정보를 완전 읽기전용으로 잠금 — 입력은 완료 챗 마무리에서만
- 어드민 협상상세(현황·챗 탭)에 협력사 의견(custom.opinion) 노출
- 협상 화면 VAT 표기를 회사설정(vat_yn) 기준으로 통일 — hidden이면 라벨 생략
- 채팅 링크로 바로 진입하면 세션이 협상생성(1)에 머물러 오프닝 메시지가 seed 되지 않던 문제:
chat init/messages 에서 participate 와 동일하게 협상중(2)으로 전이(마감 견적·마감시간 초과는 제외)
- 부가정보 저장이 1301 로 실패하던 문제: '협상완료' 요약은 chat_end=false 라 세션이 아직 협상중이므로,
마지막 말풍선이 summaryRSP/CM 이면 협상중 세션도 저장 허용
- 채팅 언마운트 시 messages 캐시도 제거 — staleTime:Infinity 라 빈 목록이 남아 재진입해도 빈 화면이던 문제
- 채팅 스크롤을 컨테이너 기준 scrollTo 로 변경(rAF 후 실행) — 요약·폼이 뒤늦게 레이아웃되면 중간에 멈추던 문제
- 모바일 채팅 상단 여백 제거(pt-[64px] 는 우측 협상절차 카드가 있는 lg 이상만)
- 노치 대응: viewport-fit=cover + safe-t/b/x 유틸 신설, 헤더·하단 액션 덱·드로어·랜딩 헤더에 적용, h-screen→100dvh
- init 에서 미참여/거부(4·5) 세션 진입을 NEGO_NOT_PARTICIPABLE 로 차단
(participate/reject 와 동일 규칙). 프론트는 목록으로 복귀 처리.
- init 을 '진입 스냅샷'으로 격리: 화면 도중·포커스 재조회 제거, 언마운트 시
캐시 비움 → 정상 종료 후 튕김 방지 + 재진입 시 진입 게이트 재적용.
- 인디케이터를 session_status 직접 비교 → wasInProgress sticky 플래그로 전환:
진행 화면에서 실시간 완료돼도 유지, 완료/거부 결과 열람 재진입 시엔 숨김.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
표현 아키텍처: agent 스크립트는 의미(텍스트)만 소유, 표현(굵기·색)은 프론트 소유.
Slate 는 negodata 에디터 내부에만 두고, 전송/저장은 마커 문자열 한 벌(구버전의 리치텍스트 이중관리 폐기).
- 트랙 A (negodata): serializeToMarker 추가 — Slate 마크(bold/underline/color)를 **/__/{{토큰}} 로
인코딩해 nego_cards.script 저장. edit_script(Slate 원본)는 재편집 전용. 고정 3색 → 시맨틱 토큰(강조/안내).
- 트랙 B (agent): 카드 멘트 DB 소스 — ICardScriptRepository/CardScriptDbRepository(port+adapter),
ScriptRepository.resolve_card_script 가 cards.source_type=backoffice_db 면 card.nego_cards.script 우선,
없으면 파일 폴백. action_id→card_id→nego_cards.number 매칭.
- 트랙 C (양 프론트): renderEmphasis 재귀 파서 — **굵게**·__밑줄__·{{강조|빨강}}·{{안내|파랑}} 중첩 렌더.
색은 시맨틱 토큰→디자인 토큰 클래스(다크모드 안전). negodata tokens.css 에 --info 신설. CardTable 미리보기 적용.
- supplier_items 연동: 유통코드=supplier_items.supply_type(→quotations.supplier_type 폴백),
파트너유형=상품별 매핑 협력사 수(→세션 이력 폴백).
- 가격 수용률: 기존 공급가(item_price) 기준 양보율로 정정 — 첫 라운드부터 실값(첫 제시가 기준 0 아님).
테스트: agent 86/86, 공급사 frontend·negodata front tsc 통과.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
KT-NEGOWIZ fe_v2 의 서비스 안내 팝업을 우리 구조로 이식.
- frontend: GuideContent(공용 본문, VAT/배송비는 chat init 메타로 동적 표시)
+ ServiceInfoPopup(채팅 진입 시 자동 — 안내 보지 않기/오늘 하루 보지 않기)
+ ServiceGuidePopup(메뉴 유의사항·이용 가이드 버튼 수동 — X/배경/ESC 닫기)
+ useServiceInfoPopup 훅(서버 상태 + localStorage 오늘 하루)
+ apis/auth 에 popupStatus 쿼리·hidePopup 뮤테이션, 헬프데스크 연락처 반영,
한국어 단어 잘림 방지(break-keep)
- backend: GET /v1/auth/popup/status, POST /v1/auth/popup/hide
(authenticate 공통 인증 + supplier_users.hide_service_info 영구 저장), 테스트 4건
- db: supplier_users.hide_service_info 컬럼 — 00-init 테이블 정의 갱신,
기존 DB 보정은 alters/2026-07-07-supplier-users-hide-service-info.sql 별도 파일
(기준선 이후 보정 ALTER 는 alters/ 파일로 관리하도록 규칙 변경)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 반응형: 최소 폭 1350→1024px, max-[1350px]/max-[1180px] 2단계 축소
- 사이드바 350→310→280, 우측 메뉴 360→320→280, 채팅 거터 140/126→80/72→48/40 (헤더 패딩 동기)
- 가격·할인율 입력 min-w 480→420→320, 가운데 버튼 패딩·최소폭 축소, 상품목록 버튼 1180px 미만 아이콘만
- 상품 이미지 220→200, 사이드바 정보 행 값 영역 고정 150px → flex-1
- 리드타임: formatLeadTime 헬퍼 신설 — 좌측 상품정보·협상 결과 요약 카드에 "일" 접미 표시
- 인디케이터: init 의 session_status 를 스토어에 적재, 협상중(IN_PROGRESS) 세션에서만 표시
- 사이드바 정보 영역 의미 없는 가로 스크롤 제거 (shrink 금지 컬럼 + overflow-x 자동 계산이 원인)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 상품목록 버튼 래퍼에 pr-[16px] 추가 — 채팅 스크롤바 오른쪽 끝과 정렬
- 상단 pt-[64px] 를 스크롤 컨테이너 안쪽으로 이동 — 첫 메시지는 협상절차 카드와 높이 유지, 스크롤 시 여백 없이 맨 위까지 표시
- chat-scroll 스크롤바 트랙에 margin-top 64px — 스크롤바 시작 높이를 협상절차 카드 상단과 일치
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
- ErrorPage 컴포넌트 추가: init/messages 로드 실패(서버/네트워크) 시 ChatContainer 가 에러 화면+재시도(refetch) 렌더 (기존엔 빈 채팅처럼 보임)
- 대기 표시: 채팅 영역 '협상 중' 타이핑 버블 + 입력 버튼 자리 '...' 점 애니메이션
- desync 에러코드(CHAT_INPUT_MODE_MISMATCH 1404 / CHAT_AGENT_TIMEOUT 1405) 추가 및 메시지
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>