어느 가격이 "공급사에 지불하는 단가"인지는 회사마다 달라, 컬럼을 합치는 대신 회사가 고르게 했다.
판정은 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
112 lines
6.4 KiB
Markdown
112 lines
6.4 KiB
Markdown
# IMK 0803 가격협상 요청 정리
|
||
|
||
원본: `0803_가격협상 우선 적용 및 논의 정리.xlsx` (12건). 시트의 `우선/논의` 구분 대신
|
||
**스펙이 확정돼 바로 착수 가능한 것 / 결정이 있어야 착수 가능한 것**으로 다시 갈랐다.
|
||
시트와 다른 3건 — ⑪은 수정으로, ③은 논의로, ④는 코드가 아니라 회사설정으로.
|
||
|
||
---
|
||
|
||
## 수정 (스펙 확정)
|
||
|
||
### ④ 부가세 입력칸 — 코드 문제 아님
|
||
`vat_yn` 은 상품폼(`ProductFormSheet.tsx`)·업로드 양식(`ExcelUploadModal.tsx`) 둘 다 이미 있다.
|
||
IMK 회사설정이 `hidden_fields = [made_in, delivery_fee_yn, selling_price, vat_yn]` 라 화면·양식에서
|
||
같이 빠진 것. 설정에서 체크 해제하면 끝.
|
||
|
||
### ⑥ 유의사항 문구 / 헬프데스크
|
||
- 유의사항의 VAT·배송비 문구는 `GuideContent.tsx` 에서 상품별 동적 표기로 이미 바뀐 상태 → 요청대로 삭제.
|
||
- 헬프데스크 연락처 실사용처 3곳, 전부 placeholder였음:
|
||
`frontend/src/features/chat/components/menu/Contact.tsx`,
|
||
`frontend/src/features/chat/components/popup/GuideContent.tsx`,
|
||
`frontend/src/pages/LoginPage.tsx`. 초청 메일엔 없음.
|
||
- **반영**: `companies.settings.branding.helpdesk: string[]` 로 회사별 설정화. 아래 "헬프데스크 배선" 참조.
|
||
|
||
### ⑦ 공급가–매입가 일원화
|
||
목표가 후보엔 이미 공급가가 없다(internet·purchase·selling — `pricing.py`). 잔여 2건:
|
||
1. 화면의 공급가(`item.price`, IMK 라벨 "공급가") 노출 제거
|
||
2. 신규 견적이 아직 인터넷최저가만 씀 → 매입가 후보 추가
|
||
|
||
⚠️ **`items.price` 를 hidden 으로 감추면 안 된다.** `hidden_fields` 는 목록·등록폼·엑셀양식 3곳을
|
||
동시에 감추므로 신규 상품의 `price` 가 NULL 로 쌓이고, 협상 멘트의 인하율이 통째로 빠진다.
|
||
`items.price` 소비처 → `items-price-is-nego-baseline` 메모 참조.
|
||
|
||
### ⑫ 시장가 산식 `(상품가+배송비)/1.1`
|
||
인터넷 최저가는 몰 판매가(VAT 포함·배송비 별도)로 수집되는데 우리 매입가·공급사 견적가는 VAT 별도라
|
||
축이 어긋난 채 비교 중. 배송비를 더해 실구매 총액을 만들고 1.1로 나눠 VAT를 벗긴다(약 9% 낮아짐).
|
||
|
||
미결 2개:
|
||
- 배송비를 대부분 모른다(네이버 쇼핑 API 미제공, 실측 39/39 null). 0으로 칠지 / 미상이면 환산 스킵할지
|
||
- 적용 범위 — 카드 멘트 인용값만인지, 목표가 후보(`internet × (1−수수료율)`)에도 거는지.
|
||
후자면 인터넷 기준 목표가가 9% 내려간다.
|
||
|
||
### ⑪ 인터넷최저가 ≥ 목표가 → 시장가 카드 차단 (절반 완료)
|
||
견적생성 시 선택 게이팅은 반영됨(`useCardGating.tsx`). 잔여 = 협상 진행 중 런타임 차단.
|
||
|
||
### ⑤ 상품 일괄등록 유효성 오류
|
||
재현 케이스(어느 필드가 오탐인지) 확보 후 수정. 엑셀 원본을 받는 게 빠름.
|
||
|
||
### ② 목표가 초과 낙찰 허용
|
||
상한 그릇은 들어감(`sessions.done_ceiling_price`, 커밋 91b8dc77).
|
||
**협상 엔진이 아직 안 읽는다** — `agent/`·루트 `backend/` 어디에도 `done_ceiling` 참조 없음.
|
||
타결 판정에 배선하면 EST-202607-973E 케이스 해소.
|
||
조건: 기존 단가 > 견적가 > 목표가 > 앵커링가면 타결. 여기서 "기존 단가" = `items.price`.
|
||
|
||
---
|
||
|
||
## 논의 (결정 필요)
|
||
|
||
### ③ 중간값 로직
|
||
②의 짝. "목표가 초과 제시 안 함" 상한을 푸는 건 맞는데 **대신 무엇을 상한으로 쓸지** —
|
||
`done_ceiling_price`(목표가×(1+율))인지 기존 단가인지.
|
||
IMK 예시(목표 39,800 / 필요 40,200)는 +1.0% 수준이고 현 기본율은 +5%.
|
||
|
||
### ⑧ 세팅 횟수만큼 카드 소진
|
||
목표가에 이미 근접했는데도 남은 카드를 다 태울지. 라운드가 늘면 결렬 위험·시간도 는다.
|
||
"최소 사용 횟수 보장" vs "조기 타결 우선" 중 택.
|
||
|
||
### ⑨ 앵커 제안가 단조성
|
||
후속 카드 제안가가 앞 카드보다 낮아지는 건(16,980 → 16,810) 카드마다 고정 인하율을 쓰기 때문.
|
||
제안가를 카드가 아니라 **라운드에 종속**시켜 단조 상향으로 바꾸는 구조 변경 → 이 중 유일하게 범위가 큼.
|
||
|
||
### ⑩ 와일드카드 + 최종제안 중복
|
||
종결 국면은 현재 **필수 관문**이다(`chat_engine.py` `check_iteration_limit`):
|
||
1. `closing_played` false → `force_closing` → 종결 전용 와일드카드 또는 폴백 최후통첩(목표가 제시)
|
||
2. `closing_played` true → 제시가 ≤ 목표가면 타결, 초과면 결렬
|
||
|
||
스킵하면 마지막 우리 카운터가 안 나가고 공급사 마지막 제시가로 즉시 판정 → **결렬률 상승**.
|
||
구현 시 `closing_played = True` 는 반드시 세워야 무한루프를 피한다.
|
||
|
||
단 중복 방지는 이미 양쪽에 걸려 있다 — 진입 단계(`chat_engine.py`)도 종결 단계(`chat_service.py`)도
|
||
`is_played` 로 쓴 카드를 건너뛴다. 코드상 같은 카드가 2회 나올 수 없으므로 IMK가 본 것이
|
||
진짜 동일 카드인지 확인이 먼저(dev DB엔 EST-202607-5947 없음 — 운영 데이터).
|
||
|
||
### ⑬ SG별 앵커링
|
||
**SG = `items.category`** (IMK 라벨 "SG명"). 현재 SG는 앵커링에도 목표가 산정에도 안 들어간다.
|
||
|
||
앵커링 축은 3개 — `(company_id, supplier_type, price_range_index)`:
|
||
|
||
| 축 | 값 |
|
||
|---|---|
|
||
| `company_id` | 회사 |
|
||
| `supplier_type` | 1 유통 / 2 제조 / 3 총판 — 개별 협력사가 아니라 **유형** |
|
||
| `price_range_index` | 목표가 기준 46개 자릿수 구간 |
|
||
|
||
SG 축을 추가하면 셀이 46×3×SG수로 쪼개져 셀당 표본 10건(`SAMPLE_THRESHOLD`)을 못 채우고
|
||
평가가 스킵·이월되어 **학습이 사실상 멈춘다**. 이게 실제 결정 포인트.
|
||
|
||
---
|
||
|
||
## 헬프데스크 배선 (⑥ 반영분)
|
||
|
||
`companies.settings.branding.helpdesk: string[]` — 한 줄 = 담당자 한 명, 자유 문자열.
|
||
|
||
| 경로 | 파일 |
|
||
|---|---|
|
||
| 편집 UI | `negodata/front/src/features/settings/SettingsView.tsx` (브랜딩 탭) |
|
||
| 타입 | `negodata/front/src/features/settings/catalog.ts` |
|
||
| 로그인 후 전달 | `backend/services/auth_service.py` `me()` — `branding` dict 통째로 내려 자동 포함 |
|
||
| 로그인 전 전달 | `backend/services/auth_service.py` `session_branding()` + `protocol.py` `Res_SessionBranding` |
|
||
| 표시 | `frontend/` 의 `Contact.tsx` · `GuideContent.tsx` · `LoginPage.tsx` |
|
||
|
||
값이 비면 각 표시부는 연락처 줄을 렌더하지 않는다(placeholder 노출 금지).
|