diff --git a/AGENT_HANDOFF_CHAT_FIXES.md b/AGENT_HANDOFF_CHAT_FIXES.md deleted file mode 100644 index 759f79f..0000000 --- a/AGENT_HANDOFF_CHAT_FIXES.md +++ /dev/null @@ -1,186 +0,0 @@ -# Chat 오류 수정 — agent 측 핸드오프 - -> 작성: backend/frontend 담당. 대상: **agent(9500) 담당자**. -> 협상 진행 중 발생하던 오류(대화 막힘·오진행·desync)를 분석해 ①~⑤로 정리했다. -> backend/frontend 에서 고칠 수 있는 부분은 이미 반영했고(아래 "backend/front 완료"), -> **agent 코드 수정이 필요한 항목만 이 문서로 넘긴다.** 관련 계약은 [AGENT_INTEGRATION.md](AGENT_INTEGRATION.md) 도 함께 참고. - -## 배경 — 무엇이 문제였나 - -backend 와 agent 가 **세션 상태를 각자 독립적으로** 관리한다(backend=`negotiation.chats`, agent=`learning.chat_sessions`). -그런데 backend 는 매 턴 `session_id` 만 보내고 `step` 은 보내지 않아, 한쪽 상태가 어긋나면(리셋/타임아웃/재시도) -**영구 desync** 가 발생한다(예: backend 는 오프닝인 줄 아는데 agent 는 가격협상 단계부터 재개). -선행 시스템(KT be_v2 → chat_server)은 매 턴 `step`+`index` 를 보내 클라이언트가 상태를 쥐는 방식이라 이 문제가 없었다. - -엔진 자체는 정상이다 — 양쪽 상태가 맞고 입력 모드가 맞으면 흐름(서비스안내→담당자확인→협상품목안내→기존가격제시→가격협상_확인→협상완료→협상종료)도 합의가 기록도 정상. - ---- - -## ① 세션 step desync — agent 가 `client_step` 을 검증/동기화해야 함 - -**backend/front 완료** -- backend 가 매 턴 요청 body 에 `client_step`(backend 가 보는 직전 봇 step)을 함께 보낸다. - (`backend/services/agent_client.py` HttpAgentClient body, `chat_service._agent_context`) -- frontend 는 desync 의심 에러(1404/1405) 시 `messages` 를 다시 불러와 화면을 서버 기준으로 리싱크한다. - -**agent 가 해줘야 할 일** ⚠️ -1. `POST /v1/chat` 요청에 새로 추가된 `client_step`(Optional[str]) 을 받는다. -2. agent 세션(`learning.chat_sessions.step`)과 `client_step` 이 다르면 **desync** 다. 다음 중 하나로 대응: - - (권장) agent 가 자기 step 을 정답으로 보고 응답하되, 응답에 현재 `step`/`client_step` 을 정확히 실어 보내 backend/front 가 따라오게 한다. (이미 `Res_Chat.step` 있음 — 항상 신뢰 가능한 값으로 채울 것) - - 또는 desync 가 크면 명시적 에러/리셋 신호를 주어 backend 가 해당 세션을 재동기화하게 한다. -3. (대안) **세션 상태 조회 API** 를 제공하면 backend 가 타임아웃/재진입 시 정합을 맞출 수 있다: - `GET /v1/chat/sessions/{session_id}` → `{ step, ended, ... }`. - ---- - -## ② agent 타임아웃 시 재시도 desync — `/chat` 멱등성 필요 - -**backend/front 완료** -- 타임아웃을 일반 실패와 구분한다(에러코드 `CHAT_AGENT_TIMEOUT=1405`). backend 는 타임아웃 시 선점 유저 메시지를 롤백하고 경고 로깅한다(`chat_service.send`). -- frontend 는 1405 시 메시지를 리싱크한다. - -**문제의 본질**: 타임아웃은 "agent 가 이미 턴을 처리(step 전진)했는데 응답만 늦은" 경우일 수 있다. -이때 backend 가 롤백하면 backend 는 "안 일어난 일", agent 는 "전진" → desync. 단순 재시도하면 **중복 전진**. - -**agent 가 해줘야 할 일** ⚠️ -1. `POST /v1/chat` 을 **멱등(idempotent)** 하게 만든다. 같은 `session_id` + 같은 턴(예: backend 가 보낼 `client_step` 또는 멱등키)에 대한 - 재요청은 **이미 처리한 결과를 그대로 반환**하고 step 을 다시 전진시키지 않는다. - - 멱등키 후보: `(session_id, client_step, user_input)` 또는 backend 가 헤더로 보낼 `Idempotency-Key`(요청 시 합의 필요). -2. 멱등이 어렵다면 ①-3 의 **세션 상태 조회 API** 만이라도 제공. backend 가 타임아웃 후 현재 step 을 읽어 재시도 여부를 판단한다. - -> backend 의 agent 타임아웃 한계는 `config.local.toml [AgentConfig] timeout_sec`(현재 10초). 필요 시 조정 가능. - ---- - -## ③ 입력-모드 검증 — ✅ backend/front 에서 완결 (agent 작업 없음) - -- backend 가 직전 봇 메시지의 `input_mode` 와 이번 유저 입력을 대조해, 어긋나면 agent 로 넘기지 않고 `CHAT_INPUT_MODE_MISMATCH=1404` 반환. - (`chat_service._input_matches_mode`) — price 단계에 버튼텍스트, yes_no 단계에 가격 같은 케이스를 막아 "제자리걸음/오진행"을 차단. -- frontend 는 1404 시 리싱크. -- **단, agent 응답의 `input_mode`/`input_options` 가 정확해야** 이 검증이 옳게 동작한다. agent 는 각 step 의 `input_mode`(confirm·yes_no·percent·price·delivery_type)와 `input_options`(버튼 라벨)를 **정확히** 채워 보낼 것. - ---- - -## ④ agent 로 가는 협상 컨텍스트 부족 + RL 미작동 ⚠️ (가장 중요) - -**확인된 현상**: 정상 완료된 협상에서도 `learning.experience_logs=0`, `chat_sessions.used_action_ids=[]`. -즉 **Q-learning 카드선택(RL)이 한 번도 작동하지 않았다.** 흐름이 `기존가격제시 → 가격협상_확인` 으로 바로 가며 RL `가격협상` 단계를 건너뛴다. - -**backend/front 진행 상황** — agent state 입력 5개 차원의 데이터 소스 현황: - -| 필드 | agent 차원 | DB 소스 | 상태 | -|---|---|---|---| -| `target_price` | price_zone | `negotiation.sessions.target_price` | ✅ 실데이터 | -| `anchor_price` | price_zone | `quotation_settings.anchoring_value` → `round(target*(1-value))` | ✅ 실데이터 (배선 완료) | -| `partner_count` | partner | 견적당 `negotiation.sessions` 개수 | ✅ 실데이터 (배선 완료) | -| `revenue_amount` | revenue | ❌ 스키마에 컬럼 없음 | ⚠️ **기본값 20,000,000 — 논의 필요** | -| `distribution_code` | distribution | ❌ 컬럼 없음 (code_map 키여야 함) | ⚠️ **기본값 "A" — 논의 필요** | -| `acceptance_ratio` | acceptance | ❌ 컬럼 없음 | ⚠️ **기본값 0.05 — 논의 필요** | - -→ `anchor_price`/`partner_count` 는 실 DB 값으로 배선했다(`chat_service._agent_context`). 조회 실패 시 각각 `target*0.99` / `1` 폴백(항상 양수 보장 → agent state `ValueError` 방지). - -**🔴 논의가 필요한 부분 — `revenue_amount` / `distribution_code` / `acceptance_ratio`** - -이 셋은 현재 우리 스키마(`quotation`, `quotation_settings`, `partner.items`, `partner.suppliers` 등)에 **대응 컬럼이 전혀 없다.** -**일단 기본값으로 고정해 둔다**(20,000,000 / "A" / 0.05). 단, 이 상태에서는 agent 가 모든 협상을 같은 state 로 보아 -RL 이 상황을 구분하지 못하므로, 아래를 **agent 담당자와 합의한 뒤** backend 스키마/시드/배선을 확정해야 한다: - -1. **각 필드의 정확한 의미·단위·출처 정의** - - `revenue_amount`(매출액): *누구의* 매출인가(거래처/공급사/품목 단위?), 단위(원), 어느 시점 값인가. - - `distribution_code`(유통 코드): 우리가 어떤 분류로 채울지 + **agent `config code_map` 의 유효 키 목록**(없는 코드면 agent 가 `ValueError` → RL skip). 코드맵 공유 필수. - - `acceptance_ratio`(가격 수용률 0~1): 산출 정의(과거 협상 이력 집계? 어느 기간/단위?) — 집계 로직 주체(backend/agent) 합의. -2. **합의 후 backend 작업**: 위 정의에 맞춰 스키마 컬럼 추가(예: `quotation_settings` / 신규 테이블) + 시드 + `_agent_context` 배선. -3. **(별개) RL 단계가 왜 안 타는지** 점검: 재협상(1:1) 단일라운드 흐름에서 `가격협상`(카드선택) 단계가 실행되도록 step 라우팅 확인. 의도적으로 안 타는 거라면 그 조건(예: 재견적/멀티라운드에서만)을 backend 에 알려줄 것. - ---- - -## ⑤ 합의가 기록 — ✅ backend/front 에서 완결 (agent 작업 없음) - -- frontend 는 가격 입력 턴에 `user_input_type:"price"` 를 정확히 보낸다(확인됨). -- backend 는 종료(success) 시 입찰가를 `이번 턴 가격 → 마지막 제시가 → 목표가` 순으로 확정하며, - **협상 중 제시가가 하나도 기록되지 않아 목표가로 폴백하면 경고 로깅**한다(`chat_service.send`). 운영 로그에서 이 경고가 보이면 가격 캡처 누락을 의심할 것. - ---- - -## ⑥ be_v2↔chat_server 경우의 수 대조 결과 (선행 시스템 전수 비교) - -선행 시스템(be_v2↔chat_server)의 채팅 전체 경우의 수를 우리 backend↔agent 가 소화하는지 대조했다. -**핵심 종료 폼(summaryRSP/CM, rejectRSP/CM, 정보변경)은 정합하게 소화**되며(우리 backend `_resolve_bot_chat_type` -= chat_server step별 `type` 매핑과 일치, be_v2 의 reject success=True 버그도 미승계), 아래만 후속 처리한다. - -| 항목 | 처리 방향 | -|---|---| -| 재견적 1:N 교차집계(입찰종료/동가입찰/선호공급사/견적마감) | **negodata 에서 추후 처리** (현 chat 흐름엔 세션 단위 확정만 있음) | -| `delivery_type` 미영속 | ✅ **backend 수정 완료** — 재견적 `배송형태선택` 값을 summaryCM 요약(`delivery_type`)에 담는다(`chat_service._delivery_choice`). | -| `indicator` 협상 지표 | ⚠️ **agent 작업 요청** (⑦) | -| 폼 타입(`bot_chat_type`) 직접 전달 | ⚠️ **agent 작업 요청** (⑦) — backend passthrough 준비 완료 | -| `card_data`(경쟁사 가격차 등 카드 표시 데이터) | 🔵 **추후 공동 논의** | -| `mbti` / `is_new_quote` | ✅ 우리 프로젝트에서 불필요 — 제외 확정 | - ---- - -## ⑦ agent 가 표현 계약을 직접 책임지도록 (chat 단순화 P1) - -**배경/문제**: 선행 chat_server 는 step JSON 의 `type`(summaryRSP/CM·rejectRSP/CM·indicator)을 엔진이 직접 응답에 실었다. -우리는 이 책임을 backend 로 옮겨, backend 가 **agent 의 내부 step 문자열**(`협상완료`/`협상실패`/`결과안내`/`결과제출`)을 -하드코딩 집합과 대조해 폼을 역유도한다(`chat_service._resolve_bot_chat_type`). agent 가 step 이름을 하나만 바꿔도 -backend 가 **조용히 폼을 None 으로** 떨구는 취약 결합이다. indicator 게이지도 agent 가 값을 안 보내 통째 비활성. - -**backend/front 완료** ✅ -- backend 는 이제 agent 응답의 `bot_chat_type` / `indicator_value` 를 **있으면 그대로 신뢰**하고, - 없으면 기존 step 기반(`_resolve_bot_chat_type`)으로 **폴백**한다(`agent_client.AgentTurn`, `chat_service.send`). - → agent 가 보내기 시작하면 backend 코드 변경 없이 자동 전환되고, step-이름 결합은 폴백으로만 남는다. -- backend 는 `indicator_value` 를 `negotiation.chats.indicator_value` 컬럼에 영속 + `ChatMessage.indicator_value` 로 전달한다. -- frontend 는 이미 `bot_chat_type` 분기(요약/거부/지표)와 **indicator 게이지 컴포넌트**가 구현·연결돼 있어, **agent 가 값만 채우면 즉시 표시**된다. - -**agent 가 해줘야 할 일** ⚠️ -1. `Res_Chat` 에 **`bot_chat_type: Optional[str]`** 추가하고 각 step 에서 정확히 채울 것 - (`summaryRSP`/`summaryCM`/`rejectRSP`/`rejectCM`/`indicator`, 일반 텍스트는 None/`text`). - - 이러면 backend 가 step 이름을 추측하지 않으므로, agent 가 step 명을 바꿔도 폼이 안 깨진다. -2. `Res_Chat` 에 **`indicator_value: Optional[float]`(1~99)** 추가하고 `가격협상`(카드선택) 턴에 채울 것. - ⚠️ backend 컬럼이 `NUMERIC(8,6)`(절대값 <100)이라 **반드시 1~99 범위**(100 금지). (가능하면 `indicator_range` PZ1/2/3 도) -3. (선택) 종료 폼 단계(`협상완료` 등)는 `chat_end=False` + `bot_chat_type=summaryXXX` 로, 실제 종료는 다음 `협상종료` 턴 `chat_end=True` 로 — 현 흐름 유지면 OK. - ---- - -## ⑧ backend 수정으로 생긴 agent 계약 의존성 (agent 가 깨지 말아야 할 것) - -이번 backend 정리(use_mock 제거, anchor/배송 배선, 응답 passthrough)로 agent 응답에 대한 **묵시적 의존성**이 생겼다. -agent 가 아래를 바꾸면 backend 기능이 조용히 깨진다 — **업데이트라기보단 "유지 필요" 항목**이다. - -1. **`배송형태선택` 단계는 응답 `input_mode` 를 정확히 `"delivery_type"` 으로 보낼 것.** - - backend 는 재견적 요약(summaryCM)의 배송형태(`delivery_type`)를, **`input_mode=="delivery_type"` 인 봇 메시지 직후의 유저 선택 라벨**로 캡처한다(`chat_service._delivery_choice`). - - 이 step 의 `input_mode` 를 다른 값으로 바꾸면 배송형태가 요약에 안 담긴다. `input_options` 라벨(협력사배송/지정택배배송/픽업배송)도 유지 권장(프론트 표시·매핑 기준). - -2. **`anchor_price` 는 backend 가 계산해 보내므로 agent 는 받은 값을 그대로 쓸 것(재계산/덮어쓰기 금지).** - - backend 가 견적설정 `quotation_settings.anchoring_value` 로 `anchor = round(target*(1-value))` 를 계산해 요청 body 에 넣는다. - - agent 가 자체 `anchor_for(target)` 로 다시 계산하면 backend 와 어긋난다. 요청의 `anchor_price` 를 신뢰할 것. - -3. **backend 에 더 이상 mock 이 없다 → agent 가 반드시 떠 있어야 한다.** - - `use_mock`/`MockAgentClient` 제거됨. agent 미기동/오류 시 chat 은 `CHAT_AGENT_UNAVAILABLE(1402)` 로 degrade(프론트 toast). 로컬/CI 에서도 실제 agent 연동 전제. - -4. **(재확인) `partner_count` 는 backend 가 '견적당 세션 수'로 산출해 보낸다** — agent 는 받은 값으로 partner 차원(single/multiple/none)만 판정. - ---- - -## 변경된 `POST /v1/chat` 요청 body (backend → agent) - -```jsonc -{ - "session_id": "", // 그대로 세션 키로 사용 (핸드오프 #1, 기존) - "rq_type": "재협상 | 재견적", - "user_input": "<버튼텍스트 | 가격문자열 | null(오프닝)>", - "target_price": 100000, - "anchor_price": 99000, // ④ quotation_settings.anchoring_value 기반 (실데이터) - // ▼ RL state 입력 (④) - "revenue_amount": 20000000, // ④ 🔴 기본값 고정 — DB 소스 없음, 논의 필요 - "distribution_code": "A", // ④ 🔴 기본값 고정 — DB 소스 없음, code_map 키여야 함, 논의 필요 - "partner_count": 1, // ④ 견적당 세션 수 (실데이터) - "acceptance_ratio": 0.05, // ④ 🔴 기본값 고정 — DB 소스 없음, 논의 필요 - "client_step": "기존가격제시" // ① backend 가 보는 직전 봇 step (desync 감지용) -} -``` -헤더: `X-Tenant-ID: <견적(갑) company_id>` (기존) - -요약: **agent 작업 필요 = ①(step 동기화/응답 step 신뢰), ②(/chat 멱등 또는 세션상태 조회 API), ④(RL 단계 라우팅 + state 데이터 소스 합의), ⑦(`bot_chat_type`+`indicator_value` 응답 추가).** -③⑤ 는 backend/front 에서 완결. ⑥ 의 `delivery_type`·⑦ 의 passthrough/게이지는 backend·front 준비 완료(agent 가 값만 채우면 동작), 재견적 교차집계는 negodata 에서 별도 처리. diff --git a/AGENT_INTEGRATION.md b/AGENT_INTEGRATION.md deleted file mode 100644 index 62de5fb..0000000 --- a/AGENT_INTEGRATION.md +++ /dev/null @@ -1,81 +0,0 @@ -# Chat 연동 규약 (backend ↔ agent) - -> 대상: **agent(9500) 담당자**. backend(9300)가 채팅 한 턴을 agent `POST /v1/chat` 으로 위임한다. -> backend/frontend 개발은 이 규약을 가정하고 완료했고, 현재는 `AgentConfig.use_mock=true` 로 내장 mock 을 쓴다. -> agent 가 준비되면 **아래 항목을 맞춘 뒤** backend `config.local.toml` 의 `[AgentConfig] use_mock=false` 로 전환하면 된다. - -## 1. 호출 흐름 - -``` -프론트(5173) → backend(9300) /v1/negotiation/sessions/{id}/chat/send → agent(9500) POST /v1/chat -``` - -- 인증·소유권·견적마감·가격범위 검증, 말풍선 영속화(negotiation.chats), 종료 시 세션 입찰확정은 **backend 책임**. -- 협상 로직(스텝 전이·카드선택·학습)은 **agent 책임**. backend 는 agent 응답을 그대로 말풍선으로 저장/전달한다. - -## 2. backend → agent 요청 (`POST /v1/chat`) - -```json -{ - "session_id": "", - "rq_type": "재협상 | 재견적", - "user_input": "<버튼 텍스트 또는 가격문자열, 첫 턴(오프닝)은 null>", - "target_price": 100000, - "anchor_price": 99000 -} -``` -헤더: `X-Tenant-ID: <견적(갑) 회사 company_id>` - -## 3. agent → backend 응답 (`Res_Chat`) ↔ 프론트 ChatMessage 매핑 - -| agent 필드 | backend/프론트 | -|---|---| -| `session_id` | 세션 키 | -| `step` | `step` | -| `client_step` | `display_step` | -| `script` | `script` (말풍선 텍스트) | -| `input_mode` | `next_input_mode` (confirm·yes_no·percent·price·delivery_type) | -| `input_options` | `next_input_type` (버튼 라벨 배열) | -| `chat_end` | `chat_end` | -| `outcome` | "success"=협상완료(DONE)+입찰가 확정 / 그 외=협상거부(REJECTED) | -| `card_id` | (저장만, 표시 범위 외) | - -## 4. agent 쪽에서 맞춰줘야 하는 항목 ⚠️ - -1. **session_id honoring** — 첫 턴에 backend 가 보낸 `session_id`(우리 `negotiation.sessions.session_id`)를 - **새 uuid 발급 없이 그대로 세션 키로 사용**해야 한다. - - 현재 `agent/services/chat_service.py` 는 새 세션 생성 시 `session_id=str(uuid.uuid4())` 로 무시한다 → `req.session_id` 우선 사용하도록 수정 필요. - - 이미 `learning.experience_logs.session_id` 가 `negotiation.sessions` 를 가리키도록 설계돼 있어 agent 입장에서도 올바른 방향. -2. **tenant 헤더** — backend 가 `X-Tenant-ID = company_id` 로 보낸다. agent 의 TenantMiddleware 가 이 키로 엔진 해석. -3. **신규 세션 컨텍스트** — `target_price`/`anchor_price`/`rq_type` 를 backend 가 견적 데이터로 채워 보낸다(기본값 의존 X). -4. **tenant_id 정밀 해석(backend 측 TODO와 짝)** — 현재 backend 는 `X-Tenant-ID` 를 빈 값으로 보낸다. - 정확히는 **견적 작성자(갑) 회사 company_id** 여야 하며, `quotation.user_id → company.users.company_id` 조회로 채울 예정. - agent 가 기대하는 tenant 키 형식(company_id uuid 문자열 / `_base`)을 확정해주면 backend 가 맞춘다. -5. **(범위 외) indicator / summary / reject** — 이번 범위 미포함. agent 응답에 협상지표·최종요약·거부폼이 생기면 - backend ChatMessage 의 예약 필드(`indicator_value`/`bot_chat_type`/summary)로 확장 협의. - -## 4-바. agent 측 확정 회신 (4-4 tenant 키 형식) ✅ - -- **tenant 키 = 견적 작성자(갑) 회사 `company_id`(uuid 문자열)**. backend 가 `X-Tenant-ID` 에 그대로 넣으면 된다. -- **`_base`** 는 공유 베이스 정책 예약어(직접 보내지 말 것). -- **미등록 company_id 자동 온보딩**: 전용 `tenants//tenant.yaml` 이 없어도 agent 가 `_base` 설정으로 - 엔진을 만들고(기본 9카드·162 state), 첫 협상에서 **base warm-start(cold-start)** 로 학습 시작한다. - → backend 는 회사를 agent 에 사전 등록할 필요 없이 company_id 만 보내면 된다. -- 헤더 부재 시 400, 빈 값도 미등록(400). (그 외 present company_id 는 모두 수용) - -## 5. mock → 실제 전환 체크리스트 - -- [x] backend `config.local.toml` → `[AgentConfig] use_mock=false` (전환 완료 — agent 미기동 시 1402 로 graceful degrade 확인) -- [x] backend `httpx` 의존 설치(`requirements.txt` 반영됨) -- [x] 위 4-1 ~ 4-3 반영 (agent 측) — session_id honoring(req.session_id 그대로 사용)·tenant 헤더·신규 컨텍스트(target/anchor/rq_type) 완료 -- [x] tenant_id 형식 확정(4-4) — **company_id(uuid)**, backend 는 `chat_service._agent_context` 의 `tenant_id` 를 `quotation.user_id → company.users.company_id` 로 채우면 됨 -- [ ] agent(9500) 기동 후 양 서버 라이브 E2E (agent 단독 /chat 은 검증 완료) - -> 로컬에서 agent 없이 mock 으로 개발하려면 환경변수로 덮는다: `AGENT_USE_MOCK=true` - -## 6. 참고 (backend 구현 위치) - -- agent 어댑터: `backend/services/agent_client.py` (IAgentClient / Http / Mock) -- 오케스트레이션: `backend/services/chat_service.py` -- 계약(프로토콜): `backend/router/v1/negotiation/chat_protocol.py` -- 엔드포인트: `backend/router/v1/negotiation/chat.py` diff --git a/SHARED_ENUMS.md b/SHARED_ENUMS.md deleted file mode 100644 index 23f1515..0000000 --- a/SHARED_ENUMS.md +++ /dev/null @@ -1,123 +0,0 @@ -# 공유 ENUM(코드값) 계약 - -> 하나의 `negosium_db` 를 여러 서비스(backend·negodata·agent·바이어측)가 공유한다. -> 공유 테이블의 `SMALLINT` 코드값은 **모든 서비스가 동일하게** 매핑해야 한다(스키마 주석: *"세션/견적을 생성·갱신하는 쪽과 코드값이 일치해야 한다"*). -> 코드값에 DB CHECK 가 없으므로(애플리케이션 enum 매핑), **이 문서가 단일 출처(SSOT)** 다. -> -> **기준(canonical): `backend/common/enums.py`** + `postgres-init/01-schema.sql`. -> 코드를 추가·변경하려면: 이 문서 갱신 → 관련 서비스 enum 동기화 → 타 서비스 담당자 공지(아래 "변경 절차"). - -## 컨벤션 -- 코드값은 `1` 부터의 정수(`SMALLINT`). 의미 매핑은 각 서비스 `common/enums.py`. -- 시각은 `TIMESTAMPTZ`(UTC), 금액 `BIGINT`, 비율 `NUMERIC`. -- 라벨(한글) 표시는 프론트 책임. 와이어/DB 에는 코드(정수)만. - ---- - -## 1. 계정 / 회사 / 권한 - -| Enum | 컬럼 | 코드 | 의미 | -|---|---|---|---| -| AccountStatus | `company.users.status`, `supplier.supplier_users.status` | 1 / 2 | active / inactive | -| CompanyStatus | `company.companies.status` | 1 / 2 | active / inactive | -| UserRole | `company.users.role`, `supplier.supplier_users.role` | 1 / 2 | user / manager | -| TokenType | `*.user_tokens.type` | 1 / 2 | access / refresh | - -> negodata 일치(이름만 `AccountStatus`↔`UserStatus`). agent 미사용. - -## 2. 견적 / 협상 유형 - -| Enum | 컬럼 | 코드 | 의미 | -|---|---|---|---| -| QtType | `quotation.quotations.type`, `negotiation.sessions.qt_type` | 1 / 2 | 재협상(renego, 1:1) / 재견적(requote, 1:N) | - -> negodata 일치(`QuotationType`). agent 는 HTTP 로 문자열 `"재협상"/"재견적"` 사용(DB 미기록) → 충돌 없음. - -## 3. 견적 상태 — `QuotationStatus` - -`quotation.quotations.status` - -| 코드 | 의미 | -|---|---| -| 1 | 견적생성 (CREATED) | -| 2 | 견적진행중 (IN_PROGRESS) | -| 3 | 견적마감 (CLOSED) | - -> ⚠️ **미합의 항목**: negodata 는 `4 = 협상보류(ON_HOLD)` 를 추가로 정의함. 채택 여부 합의 필요(아래 §8). - -## 4. 협상 세션 상태 — `SessionStatus` ★기준 - -`negotiation.sessions.status` - -| 코드 | 의미 | -|---|---| -| 1 | 협상생성 (CREATED) | -| 2 | 협상중 (IN_PROGRESS) | -| 3 | 협상완료 (DONE) | -| 4 | 미참여 (NOT_PARTICIPATED) | -| 5 | 협상거부 (REJECTED) | - -전이: `CREATED→(participate)→IN_PROGRESS→(chat 종료)→DONE | REJECTED`, 마감 초과 시 `CREATED→NOT_PARTICIPATED`. - -> 🔴 **불일치(반드시 정렬)**: negodata 는 `1=협상중, 2=협상종료, 3=협상거부` 로 **숫자→의미가 완전히 다름**. -> 같은 컬럼이라 한쪽 기준으로 통일하지 않으면 데이터 오염. **이 5-state 정의를 기준으로 통일한다**(participate/미참여/거부/chat 종료 흐름 + 스키마 주석에 부합). 상세 §8. - -## 5. 채팅 — `ChatSender` / `card_type` - -`negotiation.chats.sender` - -| 코드 | 의미 | -|---|---| -| 1 | BOT — 갑(바이어/구매대행 봇/agent) | -| 2 | USER — 을(공급사/협력사) | - -`negotiation.chats.card_type` - -| 코드 | 의미 | -|---|---| -| 1 | nego_card | -| 2 | wild_card | - -> negodata 는 코드 동일, 이름만 `2 = PARTNER`(=공급사). **데이터 호환**(같은 코드·같은 주체). 명칭은 `USER` 로 통일 권장. -> `negotiation.chats` 쓰기 주체는 **backend** 단독. agent 는 `learning.*` 만 사용하며 chats 미기록. - -## 6. 상품 / 배송 / 카드 - -| Enum | 컬럼 | 코드 | 의미 | 비고 | -|---|---|---|---|---| -| DeliveryType | `partner.items.delivery_type`, `negotiation.sessions.reject_delivery_type` | 1 / 2 / 3 | 협력사배송 / 지정택배배송 / 픽업배송 | **negodata 정의 채택**(우리도 동일 매핑 사용) | -| CardStatus | `card.nego_cards`·`card.wild_cards` (사용여부) | 1 / 2 | active / inactive | negodata 정의 | - -## 7. 미확정(TBD) 코드 - -아래는 아직 매핑이 확정되지 않음 — 사용 전 이 문서에 먼저 코드 픽스. - -| 컬럼 | 메모 | -|---|---| -| `partner.items.quantity_unit` | EA/BOX/SET 등 단위 코드 | -| `partner.items.category_type` | 자동 증가 정수(코드 아님) | -| `partner.item_internet_lowest_prices.website` / `ai_model` | 크롤링 대상·AI 모델 코드 | -| `company.companies.industry` | 업종 코드 | - ---- - -## 8. 현재 불일치 & 정렬 계획 (negodata) - -| 항목 | 우리(기준) | negodata | 위험 | 조치 | -|---|---|---|---|---| -| **SessionStatus** | 1~5 (생성/중/완료/미참여/거부) | 1~3 (중/종료/거부) | 🔴 같은 컬럼 의미 충돌 → 오염 | negodata 가 **우리 5-state 로 정렬**. 세션 생성/갱신 와이어업 전 필수 | -| QuotationStatus `ON_HOLD` | 없음 | `4=협상보류` 추가 | 🟡 우리 chat 이 `4` 미처리(마감으로 안 봄) | 채택 여부 합의 → 채택 시 우리 enum/마감 분기에 반영 | -| ChatSender 명칭 | `USER`(2) | `PARTNER`(2) | 🟢 코드 동일, 명칭만 | `USER` 로 통일 | -| DeliveryType | (미정의) | 1/2/3 | 🟢 | negodata 정의를 우리도 채택(본 문서 §6) | - -**현재 상태:** negodata 는 위 enum 을 *정의만* 했고 `sessions`/`chats` 에 실제 기록하는 코드는 없음(지뢰 상태). 협상 세션 생성/갱신을 와이어업하기 **전에** SessionStatus 를 정렬해야 한다. - -**agent:** `learning.*` 스키마 격리. 공유 테이블 미기록, backend 와 HTTP(문자열 outcome/step)로만 통신 → 코드 충돌 없음. - ---- - -## 변경 절차 -1. 본 문서(`SHARED_ENUMS.md`)에서 코드값 추가/변경을 먼저 합의·반영. -2. 각 서비스 `common/enums.py` 동기화(backend → 기준). -3. 스키마 주석(`postgres-init/01-schema.sql`)과 일치 확인. -4. 타 서비스(negodata/agent/바이어측) 담당자에게 공지 — 특히 **이미 적재된 데이터가 있으면 마이그레이션 동반**. diff --git a/agent/CLEANROOM.md b/agent/CLEANROOM.md deleted file mode 100644 index aba1694..0000000 --- a/agent/CLEANROOM.md +++ /dev/null @@ -1,29 +0,0 @@ -# 클린룸 / 저작권 분리 정책 - -이 플랫폼(`agent`)은 특정 고객사(KT커머스 등)의 **독점 자산과 분리**되어 독립적으로 설계된다. -참고용 엔진(`Chat_server`, 단일 테넌트)은 **기능 구조(아이디어·방법론)** 를 이해하기 위해서만 열람했고, -그쪽의 **표현물(코드 문구·스크립트·고유 식별자·튜닝값)을 그대로 가져오지 않는다.** - -## 보호 대상 vs 자유 이용 대상 - -| 구분 | 예시 | 우리 정책 | -|---|---|---| -| **독점 표현물 (반입 금지)** | 협상 스크립트(`scripts_*.json`의 한글 영업 카피), 고객 고유 카드 코드(`NC26-xxx`), verbatim 라벨/문구, 이식한 LLM 프롬프트 원문 | 레포에 **번들하지 않음**. 테넌트 비공개 소스(YAML/DB)에서 **런타임 주입**. | -| **고유 식별자** | 카드 코드, 배포 ID, 회사 내부 코드 | 우리 **중립 스킴**(`NGC-*`)으로 대체. 실제 값은 테넌트가 자기 카탈로그(`card.nego_cards`)로 공급. | -| **기능적 방법·아이디어 (자유)** | Q-Learning/UCB 알고리즘, state 차원 구성 방식, reward 공식 형태, 람다DB 패턴 | 자유 이용(저작권 비보호). 단, **값**은 우리 자체 기본값을 선택. | - -## 적용 규칙 - -1. **카드 코드**: 플랫폼은 중립 데모 코드(`NGC-A001` 등)만 보유. 운영 시 각 테넌트가 자사 `card.nego_cards`로 매핑. -2. **스크립트 콘텐츠**: KT 스크립트(`scripts_renegotiation/requote/wildcard.json`)는 **반입 금지**. 우리 자체 placeholder만 사용(P7). -3. **임계값·가중치·하이퍼파라미터**: 우리가 선택한 **중립 플랫폼 기본값**. 특정 고객의 튜닝값을 복제하지 않음. 실제 튜닝은 테넌트 YAML/DB에서 주입. -4. **라벨/설명 문구**: 우리 자체 중립 표기(영문 키 또는 일반 표현). -5. **LLM 프롬프트**: 우리가 작성한 문구로 재작성. -6. **테넌트 식별자**(`ktcommerce`, `imarketkorea`): 단순 라우팅 키(회사 라벨)로만 사용. 데모 프로파일의 **값은 합성/중립**이며 해당 회사의 실제 운영값이 아니다. - -## 검증 기준 변경 (P1) - -- (이전) "ktcommerce config == Chat_server 하드코딩 1:1" → **폐기** (독점값 복제를 의미하므로). -- (변경) "우리 플랫폼 중립 기본값이 정확히 로드되고 deep-merge·차원 산출이 동작한다." - -> 기능 동등성(behavior parity)은 알고리즘/구조 수준에서 유지하되, **값**은 우리 자체 설정으로 간다.