Commit Graph

21 Commits

Author SHA1 Message Date
37fa65707f [fix] postgres-init: 카드 시드를 운영 현행에 맞춤 — 중반 카드 앵커가 인용 + 목표가 카드 종결 전용
협상 중반에 목표가를 부르면 이후 올려줄 금액이 없어져 남은 카드가 같은 값만 반복한다.
제안은 앵커가 → 중간가 → 목표가로 수렴해야 하므로 시드를 그 순서에 맞춘다.

- NGC-010 향후 거래 연계 · NGC-011 양보 가치 강조: 제시 금액 {target_price} → {anchor_price}
- tactic: WC-01 종결 전용 추가, WC-04 min_round 2 → 종결 전용, WC-05 종결 전용 + min_round 2,
  WC-02 min_round 2 추가 (WC-03 종전 유지)

init-data.sql · dbeaver/4_cards_reset_dbeaver.sql 동일 반영.
검증: 임시 DB 에 1_init + 4_cards_reset 적용 후 tactic·변수 확인.
2026-08-10 11:41:25 +09:00
6f5d69b5cb [feat] agent·negodata: 협상카드 전술 데이터화(tactic JSONB·스크립트 파싱) + 발동규칙 정비
IMK QA 2건(BB9A 카드 중복·8AB0 중간값 오계산)의 근본 원인이 전술 하드코딩(_TACTICS 번호 매칭)이라
전술을 데이터로 옮기고, 발동을 유효성 검사로 바꿨다. 전술 정본 = 카드 스크립트의 마지막 가격 변수(파싱),
문장으로 알 수 없는 운영 규칙(closing·min_round)만 card.*.tactic JSONB. 세션 시작 시 card_specs 스냅샷 박제.

발동 유효성(하나라도 걸리면 그 라운드 미발동 — 클램프 폐지):
목표가 초과 / 협력사 제시가 이상 / 당사 직전 제안 미만(역행 금지, IMK 논의) / 재료 결측 /
필수 변수 결측(시장가 카드 requires — 토큰 노출 방지) / 이미 쓴 카드(played_card_numbers 공용 이력)

- agent: 와일드=비종결·종결=전용 풀 분리(같은 카드 2회 구조적 차단), 발동 시 자기 제안가 기록(절충가 수렴),
  진입 존 프로브(빈 덱 재사용 교착 방지), 낼 카드 전무 시 소진→종결, 무효 금액 카드는 설득 폴백도 금지(playable),
  에디터 anchor_price 별칭 등록, 에러 재렌더 변수 치환
- backend: 카드 사용 기록을 step 휴리스틱→번호 prefix 판정(종결 발동 card:null 누락 해소)
- negodata: 카드 상세 "협상 전술" 섹션(제시 가격 파싱 표시·종결 전용·최소 라운드) + tactic API 배선
- postgres-init: tactic 컬럼·시드(WC-03/05 closing, WC-04 min_round 2), 멱등 alter 로 dev 정본화
  (번호 WC-0x 정규화, WC-01·03·NGC-010 구멘트 교체, WC-05 변수 middle_price 교정)

검증: agent 178 통과 · 시나리오 하네스 14케이스(BB9A·8AB0·역행 실수치 재현) · 랜덤 퍼즈 50협상 불변식 위반 0
(불변식: 카드 중복 금지·종결 카드 자리·타결가≤목표가·표시가=타결가·토큰 잔존 금지·종료 보장)
2026-08-05 11:48:14 +09:00
91b8dc77a0 [feat] negodata: 타결 상한(done_ceiling) — 세팅 기본율%·견적 override·세션 박제, 견적생성 3단계 타결/낙찰 섹션 분리·재생성 상속 2026-08-04 08:56:03 +09:00
민헌
0cefe315c4 [feat] negodata/backend: 몰별 최저가(by_mall) 노출 + 강제 재검색(force) 연동
몰별 성공/실패 표시
  lps_db.price_history 에는 몰별 정보가 다 있는데(naver_lowest·coupang_lowest·by_mall),
  동기화가 final_source(이긴 몰) 하나만 남기고 나머지를 버려서 API 로는
  "네이버는 어땠는지"를 알 수 없었다. 실제로 네이버는 빈손이고 쿠팡만 성공하는
  케이스가 기본값처럼 나오는 중이라 화면에 드러낼 필요가 있다.
  → price_history.by_mall(JSONB)을 partner.item_internet_lowest_prices 로 그대로
    미러링한다(열린 스키마 — 오픈마켓 폴백이 늘어도 스키마 변경 불필요).
  - crud: 읽기 계약에 by_mall 추가 + SELECT 포함
  - model: item_internet_lowest_prices.by_mall
  - service: 언팩·저장
  - protocol: LowestPriceEntry.by_mall
  - init.sql: 테이블 정의 + 하단 보정 ALTER(기존 DB 반영용)

강제 재검색
  POST /v1/item/{id}/lowest-price?force=true → LPS 네거티브 캐시 우회.
  사용자가 '다시 검색'을 누른 경우에만 true.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:55:38 +09:00
b539e073cb Merge commit 'dc8c288f' — lps-admin 신설·LPS 운영기능(ip_session/alerts/admin API)·최저가 출처링크 반영 2026-07-24 15:30:36 +09:00
a55975e62a [fix] negosium 시드: 협상카드 스크립트 개정 + 미사용 카드 정리
출처: '260716_AIO2O 수정 및 요청사항_v2_memo check.xlsx' > '협상카드 수정' 시트(D열).
- 스크립트 개정: NGC-003(장기 관계 강조)·NGC-004(명분 제공)·NGC-007(예산 상한 안내)·NGC-008(시장가 근거 제시)
- 미사용('IMK 거래 특성과 관련성 부족') 카드 삭제: NGC-006(총비용 가치 설명)·NGC-009(조건부 가격 조정)
- init-data.sql(신규/초기화 설치 정본)에서 006·009 INSERT 제거 + 4개 멘트 갱신
- 동일 최종본을 기존 DB 보정용 alter(postgres-init/alters/2026-07-24-nego-card-scripts.sql)로도 제공(멱등)

검증: 시드 nego_cards VALUES 를 temp 테이블에 강제 평가 → 9행·전부 유효 jsonb. seed=alter=로컬 DB 일치.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 15:17:13 +09:00
3eec80d989 [fix] agent: 협상 투찰가 표시≠타결가 버그 + 카드 사용 횟수 미강제 수정
1) 표시가≠투찰가 (배포 서버 협상 결과 오류)
- 카운터 제시 중 멘트의 절충/중간 변수(middle_price·target_mid_price)가 vars_for 재계산으로
  compute_counter 의 target 클램프·prev_customer 갱신과 어긋나, 화면엔 1,740,000 인데 실제로는
  1,700,000 으로 타결되던 문제 → vars_for 에서 세 변수(counter/middle/target_mid)를 pending 으로 고정
- WC-03 최후통첩 멘트가 제시 금액을 안 보여줘 수락 시 화면에 없던 target 으로 타결되던 문제 →
  멘트에 {target_price} 명시(seed init-data.sql). 운영 DB 는 별도 UPDATE 필요

2) 카드 사용 횟수 3회 초과
- quotation_settings.card_count(협상카드 사용 횟수 상한, 기본 3)가 어디서도 강제되지 않던 죽은 설정 →
  에이전트가 sessions.qt_setting_id 로 card_count 를 조회해 협상카드 재생 수를 min(선택수, card_count)로 캡
  (session.action_space_size 는 소진 판정 전용 — Q-table 은 카탈로그 전체로 별도 고정)

- 회귀 테스트: 표시가==타결가(test_card_tactics·test_p7_chat), card_count 로드(test_context_loader). 전체 153 pass

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 11:41:33 +09:00
ce551090b3 [fix] agent·negodata: 협상카드 변수 치환 누락 수정 + 상품 업로드 협력사 목록 422 수정
- agent: 인터넷 최저가(items.internet_lowest_price)를 협상 파이프라인에 연결
  (nego_context_crud 조회 → context loader → session.context → chat_engine.vars_for).
  값 있을 때만 {internet_lowest_price}/internet_min_price 치환, 미수집(0)이면 원형 유지(허위 시장가 방지)
- negodata: 카드 에디터 변수 카탈로그에 '인터넷 최저가' 추가
- negodata: 견적 생성 카드 선택 게이팅 — 조건전략 미작성({customer_condition} 잔존)·
  최저가 미수집 상품의 {internet_lowest_price} 인용 카드는 선택 불가(상품 변경 시 자동 해제 포함)
- negodata: 상품 엑셀 업로드의 협력사 목록 size 1000→100
  (PageParams le=100 초과 시 422 → 목록 비어 전 행 '미등록 협력사' 오탐)
- seed: WC-01 의 데이터 소스 없는 {customer_reference} 제거 → 고정 문구로 대체

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 10:36:24 +09:00
1e4726f4cc [feat] 스키마: company.settings·items/suppliers/sessions.custom·nego_cards 컬럼 추가(모델·init.sql) 2026-07-22 15:19:41 +09:00
민헌
3c5286aeec feat(lps): IP 선제 로테이션 — 요청 예산·포트 쿨다운·ip_session 관측
쿠팡 크롤 IP 를 '막힐 때까지' 쓰던 방식을 '막히기 전에 교체'로 전환한다.

- 요청 예산(LPS_IP_REQUEST_BUDGET, 기본 3): IP당 요청 수가 예산에 닿으면
  차단 전에 선제 회전. 실측상 5회 부근 차단 이력이 있어 보수적으로 3회.
  선제 교체된 포트는 평판이 깨끗해 로테이션 복귀 시 재사용된다.
- 포트 쿨다운(LPS_PORT_COOLDOWN_SEC, 기본 max(sticky,30분)): 차단 감지·
  전송오류 포트는 격리하고 _port() 가 건너뛴다. 전 포트 쿨다운이면 만료
  임박 포트 사용(가용성 우선). 포트 수는 config 범위에서 동적 산출.
- 차단 재시도 소진 시에도 회전 예약 — 불탄 포트로 다음 검색을 하지 않음.
- ip_session 테이블 신설: 세션마다 요청 수·성공/차단·종료 사유(budget/
  block/proxy_error/window/idle/shutdown)를 기록. bot_detection 과 달리
  무사 종료도 남아 예산 상한 튜닝의 원천 데이터가 된다(쿼리 database.md).
  models.py·migrations·init.sql(lps_db 섹션) 동행 갱신, dev DB 적용 완료.
- 테스트 17건 추가(쿨다운·예산 판정·세션 기록·CRUD), 전체 126 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:35:44 +09:00
민헌
f97e407f7c Merge branch 'main' into feature/negodata-lps 2026-07-13 13:55:36 +09:00
ac487268d8 postgres-init lps db 추가. 2026-07-10 17:19:24 +09:00
민헌
4a20194dc8 feat(negodata): 인터넷 최저가 출처·링크·가격 추이 UI — 검증 가능한 협상 근거
인터넷 최저가는 견적 목표가·협상 카드(NGC-008 시장가 근거)의 근거값
— "어디서 찾았는지"를 클릭 한 번으로 검증할 수 있어야 한다.

- 스키마: iilp 에 lp_name(찾은 상품명)·lp_url(판매 페이지 링크) 추가
  (alters/2026-07-10-iilp-source-link.sql, init.sql 멱등 반영·dev 적용)
- 동기화: price_history 의 final_source 에 맞는 naver/coupang name·url
  을 함께 반영(기존 행은 NULL 공존)
- API: LowestPriceEntry 에 lp_name/lp_url + orval 재생성
- UI: 상품 테이블 인터넷 최저가 셀 클릭 → 상세 시트
  · 대표가(rose) + 상품 단가 대비 차액(절감=emerald/역전=amber)
  · 출처 카드: 사이트 배지·찾은 상품명·판매 페이지 새탭 링크
  · 가격 추이 라인 그래프(성공 수집 2건부터, recharts+ChartContainer
    라이트/다크 쌍 — 통계 차트와 동일 문법)
  · 최근 수집 이력 리스트(미발견 amber, 행별 외부링크)

검증: 실데이터 e2e(A4 재수집 → lp_name/lp_url 반영 확인) +
헤드리스 브라우저 렌더 확인(시트·그래프·툴팁·링크), tsc·build 통과

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:10:00 +09:00
민헌
28b4a9e797 feat(negodata): lowest-price 스텁 API 실구현 — 즉시 트리거 + 온디맨드 동기화 조회
- POST /v1/item/{id}/lowest-price: 상품 1건 LPS 즉시 검색요청(manual
  우선순위). queued/duplicated/unavailable 상태 반환, 실패는
  ErrorType.LPS_UNAVAILABLE(2000 블록 신설)
- GET /v1/item/{id}/lowest-price: 대표 최저가 + 최근 수집 이력(최신순,
  LowestPriceEntry 타입화). 조회 전 lps_db 증분 동기화 1회 수행 —
  5분 크론을 기다리지 않는 실시간 폴링 UX(멱등·저비용)
- 동시성 방어 2겹: 프로세스 내 asyncio.Lock(크론·온디맨드 직렬화) +
  uq_iilp_item_crawl_time 유니크 인덱스(alters/2026-07-10, init.sql
  멱등 반영·dev DB 적용) — 경합 진 쪽 tx 실패 후 다음 tick 흡수
- LpsSyncService 무인자 생성자(FastAPI Depends 호환)
- compose: negodata-backend 에 LPS_DB_HOST·LPS_BASE_URL env(미설정 시
  연동 비활성으로 조용히 동작)

검증(도커 컨테이너 e2e): 인증→POST queued→LPS 크롤→GET 온디맨드
동기화로 이력 즉시 노출(not_found 정책: 대표값 미변경 확인),
company 스코프 차단(타사 상품 ITEM_NOT_FOUND) 확인

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:17:05 +09:00
a80dac20ed [fix] postgres-init: init.sql 병합 충돌 해소 — last_accessed_at DEFAULT now()
stash 충돌 마커 제거. company.users·supplier.supplier_users 에 DEFAULT now() 추가(신규 DB)
+ 말미 보정 ALTER(기존 DB). ORM server_default 의존이라 없으면 유저 생성 시 NOT NULL 위반.

Little-Helped: Stupid Claude
2026-07-09 17:04:40 +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
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
민헌
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
d21e7c10b0 [refactor] 공급유형 기준을 상품-협력사 매핑으로 전환 2026-07-07 17:05:07 +09:00
민헌
e7be7d5b5f Merge remote-tracking branch 'origin/main' into feature/backend-frontend 2026-07-07 16:11:40 +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