Commit Graph

397 Commits

Author SHA1 Message Date
민헌
da9bd7750c feat(lps): 작업 큐 엔진 — PostgreSQL 원자적 claim + lease + dead-letter
레퍼런스의 DB-큐 반면교사를 전부 뒤집는 큐 엔진을 구축한다.
 - 원자적 claim: FOR UPDATE SKIP LOCKED 서브쿼리 + 같은 UPDATE + RETURNING
   → 워커 다수여도 이중 할당 원천 불가(fetch/claim 미분리)
 - 모든 전이는 조건부 CAS(status/worker_id 가드) + RETURNING
 - 복구는 timeout 추측이 아닌 lease 만료 소유권 기반(reaper 회수)
 - 재시도 지수백오프 + max_attempts + dead-letter 를 큐에 내장(스크립트 난립 제거)
 - dedupe_key 부분 유니크로 활성 중복 차단, counts()로 관측(수동 psql 대체)

- enums: JobStatus(PENDING/RUNNING/DONE/DEAD)·JobType(SEARCH/OUTBOX)
- models: job 테이블(무FK·SMALLINT코드·TIMESTAMPTZ, claim/lease/dedupe 인덱스)
- crud/job_crud: JobQueue(enqueue/claim/complete/fail/renew_lease/reap/counts)
- tests: 동시 8워커 이중할당0·lease회수·재시도→dead·소유권가드 등 8건(실 lps_db)

부수: 포트 9400→9600 (negodata 9400 충돌 회피)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:37:32 +09:00
민헌
e27b73c018 feat(lps): 쿠팡 검색 어댑터 — Akamai 우회(Patchright) + 정규화 파서
소스 어댑터 패턴으로 크롤링을 시작한다. 쿠팡은 Akamai Bot Manager 의
JS 행동 챌린지라 curl_cffi 단독 불가 → Patchright(스텔스 Playwright) +
실제 Chrome(channel=chrome)으로 챌린지를 통과하고 selectolax 로 파싱한다.

- contract: SearchAdapter(ABC)·NormalizedProduct·AdapterHealth·AdapterError
- coupang: adapter(브라우저 재사용, 챌린지 1회)·parser(순수)·selectors(외부화, webpack 해시 prefix 매칭)
- 공용: rate_limiter(2~8s 랜덤)·proxy(무프록시 off 인터페이스)·util.parse_price
- 가격 파싱: 단위가격 '(1개당 44,400원)'의 앞 '1' 오인 방지 — '원' 앞 숫자 앵커링
- deps: curl_cffi·selectolax·patchright(nodriver 는 py3.14 버그로 대체)
- tests: 파서 회귀(소형 fixture) — 라이브 2회 검색 + 파서 3/3 통과

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:59:48 +09:00
b368a19f79 [feat] agent: 카드번호 기반 Q-table 마이그레이션 + 학습 리셋 스크립트
카탈로그 카드 추가/삭제/재정렬 시 action_id 가 밀려도 학습이 카드를 따라가도록,
Q-table 버전에 카탈로그 스냅샷(카드번호)을 저장하고 카드번호로 리맵한다.

- q_table_versions.action_cards JSONB 신설(카드번호 목록, index=action_id).
  models.py + init.sql(DDL·ALTER) + 로컬 DB ALTER.
- 버전 생성(get_or_create/warm_start/migrate)이 action_cards 저장.
- migrate_active_version_dim: 옛 action_cards ↔ 새 카탈로그를 카드번호로 리맵
  (중간 삽입/삭제 보정, 사라진 카드 버림, 새 카드 fresh). 레거시(스냅샷 없음)는 위치 폴백.
  version_name 은 vid 접미로 유니크.
- model_store.load: card_list 계산 → 차원변경 OR 동일차원 내용변경 시 마이그레이션,
  레거시 버전 action_cards backfill(set_version_action_cards).
- tools/reset_learning.py: learning 스키마만 비우는 리셋(카드·협상 데이터 보존),
  --company/--yes 옵션. 카탈로그 바꾸고 학습 처음부터 할 때 사용.
- 테스트: 중간 카드 삭제 시 카드번호 리맵으로 학습 보존 검증. agent 100/100.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:19:55 +09:00
민헌
11a7be7154 feat(lps): 인터넷 최저가 검색 솔루션 프레임워크 골격 추가
backend 와 동일한 프레임워크로 lps/ 폴더를 신설한다. 구체적인 도메인
로직(크롤링/오픈API/최저가 산정)은 미정이라 골격만 구성한다.

- FastAPI 부트스트랩(web_main/router) + lifespan·CORS·gzip·로그 미들웨어 + /healthz
- TOML 설정 로더(APP_ENV) + pydantic config + DB env override
- DB 세션 매니저(논리 DB × R/W, service→람다 위임) — 엔진 lazy 라 DB 없이도 부팅
- 공통 응답 규약(gmodel) + ErrorType/DBType/DBWRType
- router→services→crud 계층 컨벤션(services/crud 는 빈 폴더로 자리만)
- 포트 9400(backend 9300·agent 9500 회피), Dockerfile/run_local_server.sh/README
- tests/test_health.py 스모크(healthz, DB 없이 통과)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 15:18:27 +09:00
7b7f606ace Merge branch 'feat/nego-card-update' 2026-07-08 15:09:31 +09:00
f4e59a2184 [feat] agent: per-company 카탈로그 — 회사 전용 카드도 action space 포함
카탈로그 스코프를 공용(user_id NULL) 전용에서 "공용 + 그 회사 유저가 만든 카드"로 확장.
공용 먼저(action_id 0..N-1 안정) → 회사 카드 뒤(append) 정렬로 공용 카드 학습을 보호.

- get_nego_catalog(cdb, company_id): company_id 주면 공용 + company.users 소속 카드 함께 조회.
- registry._apply_db_catalog: company_id(UUID)면 회사 카드 포함(데모/비-UUID는 공용만).
- 효과: 견적이 회사 전용 카드를 선택해도 카탈로그에 있어 _selection_mask 가 매핑됨
  (기존 스코프 불일치 갭 — 회사 카드 선택이 무시되던 문제 해소).

한계: 공용 카드 추가 시 회사 카드 action_id 밀림(공용은 안정) — 카드번호 기반 매핑 후속.
agent 99/99.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 15:03:30 +09:00
18020a9e07 [feat] 카드 카탈로그 DB 정본화 — config 결합 제거 + 학습 보존 자동반영
카드 카탈로그(negodata)가 Q-table action space 를 정의하는 정본이 되고, 카드 변경이
config 수정·학습 손실 없이 agent 에 자동 반영되는 고리를 완성.

- action space 정리: 카탈로그 전체(NGC-001~011, 11장) 고정, 견적별 선택은 축소가 아니라
  available_mask(_selection_mask) 로 처리 — action_id↔카드 대응을 견적마다 일정하게 유지해
  Q-table 학습 일관성 보장. 구 인덱스 방식(selected[action_id]) 폐기.
- ① 카탈로그 DB 정본화: action_mapping.type=db 면 registry 가 card.nego_cards(user_id NULL,
  number 순) 조회로 action_to_card 동적 구성(파일은 폴백). port/adapter(card_catalog_*).
  _base=type:db. → negodata 카드 추가/삭제 시 config 수정 불필요.
- ② 차원 변경 학습 보존 마이그레이션: migrate_active_version_dim — 겹치는 셀 복사
  (append/truncate 안전) + 새 카드 fresh. model_store.load 가 차원 불일치 시 호출.
- ③ reload 엔드포인트: /v1/catalog-refresh(테넌트) · /v1/catalog-refresh-all(전역, 화이트리스트).
- ④ 브랜드: company_profile_repo — 자동 온보딩 고객사(company_id UUID)는
  company.companies.name 으로 {company_name} 채움. 데모 테넌트는 파일 유지.
- 크로스서비스: negodata card_service 가 공용 nego 카드 변경 시 agent_notify 로 전역 리로드 알림
  (best-effort, is_test skip). config 에 agent_base_url.
- 하니스 episodes 400→600(action 11 수렴). 테스트 갱신·추가로 agent 98/98.

알려진 갭(후속): per-company 카탈로그 스코프(회사 카드도 action space 포함), 카탈로그 중간
삭제 시 카드번호 기반 마이그레이션.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 14:56:28 +09:00
민헌
235863aea9 fix(list): 견적 유형(qt_type)에 신규협상·신규견적(3·4) 추가
프론트 QtType 이 재협상·재견적(1·2)만 정의해, 신규협상(3)/신규견적(4)
세션은 목록 '구분'이 '-' 로 깨지고 필터로도 걸러지지 않았다.
backend·negodata enum 과 동일하게 4종으로 맞춘다.

- QtType/QT_TYPE_LABEL 에 NEW_NEGO(3)·NEW_QUOTE(4) 추가 → 목록 표시 정상화.
- filterOptions '구분' 에 신규견적·신규협상 노출 → 필터 선택 가능.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 14:15:46 +09:00
민헌
5519c6e73b feat(negotiation): 세션 목록 기본 그룹 정렬 + order 지정 시 전체 정렬
목록 진입 시 "지금 할 일"이 먼저 보이도록 기본 정렬을 개선하고,
사용자가 마감일로 직접 훑어볼 수 있는 전체 정렬 모드를 추가한다.

- 기본(order 미지정): 협상생성·협상중을 상단 그룹으로 마감 임박순,
  종료(완료·미참여·거부)는 하단 그룹으로 최근 마감순. 동일 마감은
  session_id 로 tie-break 하여 페이지네이션 안정화.
- order=asc/desc: 그룹 구분 없이 전체를 마감(qt_end_time) 기준으로 정렬.
- router: order 기본값을 "asc" → None(Optional) 로 변경해 "그룹 기본"과
  "명시적 전체 정렬"을 구분.
- frontend: SessionListParams.order 주석을 새 계약에 맞게 갱신(타입 무변경).
- tests: 기본=그룹 / 파라미터=전체 정렬 검증으로 분리(21 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 14:12:56 +09:00
민헌
c2760c804f fix(agent): 하드코딩 '재원부족' 와일드카드 제거
_pick_wildcard 가 실제 선택 와일드카드와 매핑되지 않은 채 '와일드카드를
하나라도 골랐으면' 조건만으로 wild_card_budget(재원부족)을 반환해, 선택하지
않은 재원부족 멘트가 노출되는 오작동이 있었다.

- 재원부족 분기 삭제 → 해당 구간은 일반 가격협상으로 폴백. 와일드카드는
  앵커<제시가≤앵커*1.02 구간의 1% 인하(wild_card_1pct)만 발동한다.
- wildcard_used 마킹을 1% 분기 안으로 이동 — 가격협상으로 돌아갈 때도
  마킹하면 이후 라운드의 정당한 1% 카드까지 억제되는 부작용 방지.
- test_p7_chat: 와일드카드 발동 단언을 wild_card_1pct 로 교체.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 18:10:45 +09:00
민헌
98df5e2608 Merge remote-tracking branch 'origin/main' into feature/backend-frontend
# Conflicts:
#	postgres-init/init-data/init.sql
2026-07-07 17:26:58 +09:00
민헌
fc71526d5a feat(chat): 종료 세션 진입 차단 + 협상중 인디케이터 sticky 유지
- 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>
2026-07-07 17:22:54 +09:00
a2731d97f5 conflict 수정. 2026-07-07 17:18:54 +09:00
15f3d10eb6 와일들카드 선택 수정 . 2026-07-07 17:16:19 +09:00
d21e7c10b0 [refactor] 공급유형 기준을 상품-협력사 매핑으로 전환 2026-07-07 17:05:07 +09:00
9ac8fe6363 Merge commit '8c700b1e080a29a02375664e856bc6e5b757c7c3' into feature/negodata 2026-07-07 16:19:52 +09:00
c62b66e35b [feat] negodata: 소유권 게이팅 + 견적 낙찰 + 작성자명 표시 + 전화입력 + 카드 엑셀
- 소유권 게이팅(common/authz): 변경 액션 본인∪OWNER, 협력사 삭제 OWNER 전용
- 견적 수동 낙찰(award) + 작성자명(creatorName) 표시 + 전화번호 입력 컴포넌트 + 카드 엑셀 업로드
- supplier_type 은 이번 커밋 미변경(다음 커밋에서 코드부터 정리 예정)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:18:33 +09:00
c32ee300e6 [feat] negodata: 통계 페이지(성과 분석) 추가 — 파생 집계 API + recharts 차트
- 백엔드 GET /v1/statistics/summary: 회사/내견적 스코프, 최근 6개월 파생 집계(저장 X, DDL 0)
- 지표: 총절감/절감률/낙찰률/앵커도달률/마감수/재견적라운드 + 월별추이·마감결과분해·참여율·유형별(협상/견적)·카테고리·카드빈도
- 프론트 recharts+shadcn Chart, 사이드바 통계 nav, /statistics

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 16:18:33 +09:00
민헌
e7be7d5b5f Merge remote-tracking branch 'origin/main' into feature/backend-frontend 2026-07-07 16:11:40 +09:00
8c700b1e08 [fix] agent: format_script 색 마커 {{강조|..}} 보존 — format_map → 정규식 치환
str.format_map 이 {{·}} 를 이스케이프로 해석해 색 마커 {{강조|텍스트}} 를 {강조|텍스트} 로
붕괴시키던 버그 수정. 정규식 \{(\w+)\} 으로 {변수} 단일 토큰만 치환 — 색 마커는 내부 |(비-\w)로
매칭되지 않아 그대로 보존된다. {변수} 치환·미등록 토큰 원형 유지는 동일.

엔드투엔드 검증(backoffice_db + 실 DB 카드)에서 발견. 회귀 테스트 추가, agent 87/87.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:53:48 +09:00
1917d76304 refactor(db): postgres-init 초기화 파일을 init-data/ 로 이동
00-init.sql → init-data/init.sql (스키마 DDL), temp-data.sql → init-data/init-data.sql (로컬 시드).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:23:23 +09:00
민헌
7bfd77f6c3 Merge remote-tracking branch 'origin/main' into feature/backend-frontend 2026-07-07 15:23:12 +09:00
1c8c4254a6 [feat] 협상 멘트 스타일 파이프라인(마커) + agent 카드 DB 소스 + supplier_items 연동
표현 아키텍처: 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>
2026-07-07 15:19:45 +09:00
민헌
be2865a4b2 docs(common): 공통 응답 봉투 + healthz 설명 보강
- gmodel: ErrorInfo(success/code/desc) + Res_WebPacketProtocol(result/msg)
  에 description 추가 → 모든 Res_* 응답의 공통 봉투가 Swagger 에 채워짐.
- router: /healthz 에 summary/description 추가(유일하게 설명 없던 오퍼레이션).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:15:07 +09:00
민헌
70104404eb docs(negotiation): protocol Field + 파라미터 설명 보강
- protocol: ListItem/Res_SessionList 의 빈 Field 에 description 추가.
- session.py: page/page_size 쿼리 + session_id 경로 파라미터에 설명 추가
  (Path import). req 인자는 문법상 = ... 로 두되 requestBody required 유지.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:15:00 +09:00
민헌
3c56c32c85 docs(chat): protocol Field + 경로 파라미터 설명 보강
- protocol: ChatSummary 전 필드 + ChatMessage/Res_ChatInit/Res_ChatSend 의
  빈 Field 에 description 추가. 혼동되던 두 배송 필드를 조립 코드 기준으로
  구분 명시(item_delivery_type=상품 기본 폴백 / delivery_type=배송형태선택 결과).
- chat.py: session_id 경로 파라미터에 Path(description=...) 추가.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:14:53 +09:00
민헌
3b08c26908 docs(auth): protocol Field 설명 보강
Res_CreateAccount.su_id 등 빈 Field 에 description 추가 → Swagger 노출.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:14:46 +09:00
민헌
26b8634c3a chore(db): 미사용 dev_seed.sql 제거
postgres-init 2파일 체계(00-init + temp-data)로 시드가 통합되면서
어디서도 참조하지 않는 잔재 스크립트. 삭제로 유일 파일이던 scripts/ 도 정리됨.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:14:41 +09:00
민헌
174bcc9f9f test(loadtest): 협상 도메인 부하 시나리오 + run_local_locust 개선
- negotiation_locustfile: 공급사 브라우징(list/chat init·messages) +
  참여/거부 부하. reject 는 1회성이라 소비 전용 REJECT 풀(큐),
  participate 는 IN_PROGRESS no-op 이라 재사용 READ 풀. 시드 세션마다
  chat 오프닝 행을 심어 agent 호출 회피. catch_response 로 result.success 검증.
- run_local_locust.sh:
  · 단일 파일 자동 선택(불필요한 프롬프트 생략) + 다중 시 입력 검증
  · .venv 존재 확인, glob 수집, psql 에러 노출(2>/dev/null 제거)
  · negotiation 선택 시 세션 풀(CTE+generate_series) 시딩 → 풀 파일 전달
  · LOAD_READ_POOL / LOAD_REJECT_POOL 로 풀 크기 조절

검증: 10 users·20s 헤드리스에서 전 엔드포인트 0% 실패, agent 무호출 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:36:48 +09:00
민헌
3b1719666f refactor(auth): popup API 리뷰 개선 + 무토큰 테스트 보강
원작성자 컨벤션 대조 리뷰 반영.

- README 엔드포인트 표에 logout·popup/status·popup/hide 추가(누락분 포함)
- user_crud: get_hide_service_info 의 불필요한 bool() 캐스트 제거
  (execute 가 단일 컬럼 select 에서 이미 스칼라 반환 — get_supplier_name 과 동일)
- account: popup 라우터를 me 뒤로 이동(로그인→계정→토큰→내정보→팝업 흐름)
- test_auth: test_popup_hide_without_token 추가(me 섹션과 무토큰 가드 대칭)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:36:37 +09:00
민헌
3c8923c075 feat(chat): 협상 유의사항 안내 팝업 2종 이식 + 팝업 숨김 저장 API
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>
2026-07-07 13:41:46 +09:00
민헌
dc0e78ec73 Merge branch 'design/frontend': 채팅 UI 개선 + postgres-init 2파일 체계 통합
- 프론트: 채팅 반응형 레이아웃(~1024px)·정렬 보정, 협상 종료 캐시 무효화, 완료세션 재진입 검증 생략, 목록 테이블 레이아웃 정리
- DB: postgres-init 를 00-init.sql(스키마 전체+기존 DB 보정 ALTER) + temp-data.sql(시드) 2파일로 통합, 구 01~05 폐기
- 카드: script TEXT + tone·strategy_type 컬럼, 협상 카드 시드(일반 11장·와일드 5장, 변수 9종 체계)
- 문서: ENUM_TYPE.md 코드값 조견표 신설

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:44:56 +09:00
민헌
65ea1c6ac8 docs(db): ENUM_TYPE.md 코드값 조견표 + 00-init 기존 DB 보정(ALTER) 섹션
- ENUM_TYPE.md: SQL 코드값 16종 정리(값/코드명/의미/사용 컬럼), 진실 원천=각 서비스 enums.py 명시
  - UserRole 값 2 서비스별 상이(OWNER/MANAGER)는 의도된 설계로 기재
  - CardTone·CardStrategyType 앱 enum 미정의 표시, CardStatus 는 API 전용(available 매핑) 각주
- 00-init.sql 말미에 기존 DB 보정 섹션 신설(구 04-alter 역할): 카드 script TEXT + tone·strategy_type
  멱등 ALTER — 기존 DB 재실행 시 최신 스키마로 보정, 신규 DB 는 no-op

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:43:44 +09:00
민헌
822179f99a chore(db): card-update.sql 제거 — 카드 개편분 temp-data.sql 반영 완료
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:26:37 +09:00
민헌
d2710250e0 Merge branch 'main' into design/frontend
# Conflicts:
#	postgres-init/00-init.sql
#	postgres-init/01-schema_202607061714.sql
#	postgres-init/01-schema_202607070958.sql
#	postgres-init/04-alter_202607070958.sql
2026-07-07 10:21:44 +09:00
민헌
26f518ed6e refactor(db): postgres-init 2파일 체계로 통합 — 00-init.sql(스키마 전체) + temp-data.sql(시드)
- 00-init.sql: 구 01(도메인)+02(learning)+05(anchoring) 통합, 구 04(누적 ALTER)는 01에 기반영되어 폐기
- temp-data.sql: 구 03 시드 + 협상 카드 시드(일반 11장·와일드 5장, 멱등 가드, available=TRUE)
- card 테이블: script TEXT 전환 + tone·strategy_type 컬럼 추가, 카드 변수 9종 체계 문서화
- 참조 갱신: 루트 README·docker-compose 주석, schedules/anchoring conftest(00-init 적용)·README·문서
- anchoring 테스트 20건 통과, 신규 DB 초기화·멱등성 검증 완료

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:20:44 +09:00
43dbcbcd96 [feat] negodata: 협력사↔상품 매핑(취급상품·공급유형) + 견적 픽리스트 서버검색 콤보박스
- partner.supplier_items 매핑테이블 신설: supply_type(0없음/1유통/2제조/3총판), 부분 유니크 인덱스(soft-delete 인지). quotations.supplier_type와 의미단위 달라 컬럼명 supply_type.
- 백엔드: supplier_item CRUD/service/router(/v1/supplier-item, by-supplier·by-item·create·bulk·update·delete). 소유권 company 스코프. ErrorType.SUPPLIER_ITEM_NOT_FOUND 추가.
- DDL: 01-schema/04-alter(날짜접미 리네임) + dev DB 반영.
- 프론트(feature): 협력사 엑셀 '취급상품' 컬럼(콤마 상품명→일괄매핑, 이름 미매칭 스킵+리포트, 유형 안받고 없음), 협력사 상세 취급상품 관리(추가/삭제/유형변경 즉시반영), 견적모달 협력사 리스트에 선택상품 공급유형 배지.
- 재사용 서버검색 Combobox 신설 → 취급상품·견적상품·협력사초청·협상카드 픽리스트에 적용(size 100 캡 해소, 검색은 서버 ILIKE). 견적 선택상품 파생값은 useGetItem 단건조회로 안정화.
- orval 재생성(supplier-item 클라이언트/모델).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 09:59:13 +09:00
ed175c5b65 [feat] agent: Req_Chat 슬림화 — 협상 컨텍스트를 DB 조회로 전환 + CRUD 계층 도입
- Req_Chat 을 session_id/user_input/client_step 3필드로 축소 — rq_type·목표가·앵커·품목가·
  매출액·유통코드·파트너 유형·수용률 필드 전부 제거
- NegotiationContextLoader 신설: 세션 시작 시 공유 DB 1회 조회로 컨텍스트 확정
  · rq_type = sessions.qt_type ({1,3}→재협상 / {2,4}→재견적)
  · anchor = sessions.anchoring_price(박제) — NULL 이면 무할인 폴백 anchor=target (v1.2 정책 승계)
  · 매출액 = suppliers.total_revenue(KTC 미러), 유통코드 = quotations.supplier_type 매핑
  · 파트너 유형 = 상품별 distinct supplier 수 → PartnerType enum(0=NONE/1=SINGLE/2=MULTIPLE)
- 가격 수용률은 세션 내 동적 계산: max(0, (첫 제시가−현재가)/첫 제시가)
- DB 쿼리를 backend crud 패턴으로 분리: INegoContextCRUD(ABC)+NegoContextCRUD,
  IChatSessionRepository 인터페이스 추가 (테스트 더블 주입 가능)
- 와일드카드 1% 수락 시 합의가=offer_1pct 반영 + Res_Chat.settled_price 신설 —
  backend 요약/입찰가가 이를 최우선 사용 (19,800원 수락이 20,000원으로 기록되던 버그 수정)
- backend: agent 전송 바디 3필드로 축소, 앵커/파트너 조회 메서드 제거,
  test_anchoring_chat 을 새 구조로 재작업(박제 소비/폴백 검증은 agent 테스트로 이관)
- 데모 페이지(/demo·negotiation_demo.html) 제거 — 컨텍스트 주입 경로 폐지로 무의미
- 테스트: agent 83/83, backend 57/57 (컨텍스트 로더 실데이터 왕복 4종 + CRUD 더블 검증 포함)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 09:44:21 +09:00
민헌
d9191420c1 Merge branch 'main' into design/frontend 2026-07-07 09:18:30 +09:00
민헌
6bf37dcf5a fix(list): 테이블 고정 레이아웃·셀 말줄임 제거
- table-fixed 를 내용 기반 자동 레이아웃으로 전환, 셀의 overflow-hidden·text-ellipsis 제거
- 셀 내용이 잘리는 대신 컬럼 폭이 내용에 맞게 조정되도록 (whitespace-nowrap 은 유지)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:29:18 +09:00
민헌
4b0455c237 fix(negotiation): 협상완료 세션 재진입 시 견적마감·마감시간 검증 생략
- DONE 세션의 participate 는 결과 열람용 재진입(무변경)이므로 견적마감(NEGO_QUOTATION_CLOSED)·마감시간 검증을 건너뛴다
- reject 는 blocked_statuses 로 DONE 을 이미 차단하므로 이 분기는 participate 에만 적용
- 테스트: 견적마감+마감시간 경과 상태에서도 완료 세션 참여 성공, 세션·견적 상태 무변경 검증

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:28:26 +09:00
1ac42ddc9c [feat] negodata: 협력사 총매출액(total_revenue) 추가 + 우선선정(priority) 제거
Helped-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 17:27:59 +09:00
민헌
1f16bec9ee fix(chat): 협상 종료 시 목록·init 쿼리 캐시 무효화
- send 응답에서 종료 전이(session_status ≠ 협상중 또는 chat_end) 감지 시 sessions·init 캐시 무효화
- 마감/종료/권한 에러로 목록 복귀할 때도 동일하게 무효화
- /list 복귀 시 staleTime(60s) 과 무관하게 항상 최신 상태 표시, 종료 세션 재진입 시 인디케이터 숨김 즉시 적용

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 17:25:38 +09:00
3176ec4262 Merge branch 'main' into feature/negodata
Helped-By: Claude Opus 4.8뀨 <noreply@anthropic.com>
2026-07-06 16:58:04 +09:00
민헌
482c8ed97c feat(chat): 반응형 레이아웃(~1024px) + 리드타임 "일" 표시 + 완료세션 인디케이터 숨김
- 반응형: 최소 폭 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>
2026-07-06 16:37:18 +09:00
f1a29b8bde [feat] negodata/front: 견적생성 UX 재개편(협상/견적 2축·낙찰기준 스펙트럼·카드 스텝) + 개찰 표기 + 세팅 정리
Co-Authored-By: Claude Opus 4.8뀨 <noreply@anthropic.com>
2026-07-06 16:36:33 +09:00
38709b3098 [feat] negodata: 결렬 폐지→개찰 마감 개편 + 낙찰기준 견적 단위 이관 + 세팅 컬럼 정리 (백엔드+DB)
Co-Authored-By: Claude Opus 4.8뀨 <noreply@anthropic.com>
2026-07-06 16:36:00 +09:00
민헌
630788d9eb fix(chat): 채팅 영역 오른쪽 끝·상단 여백 정렬 보정
- 상품목록 버튼 래퍼에 pr-[16px] 추가 — 채팅 스크롤바 오른쪽 끝과 정렬
- 상단 pt-[64px] 를 스크롤 컨테이너 안쪽으로 이동 — 첫 메시지는 협상절차 카드와 높이 유지, 스크롤 시 여백 없이 맨 위까지 표시
- chat-scroll 스크롤바 트랙에 margin-top 64px — 스크롤바 시작 높이를 협상절차 카드 상단과 일치

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 15:53:31 +09:00
a85192a793 Merge pull request '앵커링 v1.2 통합 + 도메인 이름 전면 개편 + 인프라 통합' (#7) from refactor/backend into main
Reviewed-on: Negosium/o2o-negosium#7
2026-07-06 04:45:25 +00:00
민헌
e408e34a0f chore(compose): anchoring-redis 호스트 포트 6380 으로 (호스트 redis 6379 충돌 회피)
컨테이너 내부 통신은 6379 그대로 — 호스트에서 직접 접속할 때만 6380.
로컬에 homebrew redis(6379) 가 떠 있어도 스택이 뜬다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:43:15 +09:00