Commit Graph

119 Commits

Author SHA1 Message Date
91b8dc77a0 [feat] negodata: 타결 상한(done_ceiling) — 세팅 기본율%·견적 override·세션 박제, 견적생성 3단계 타결/낙찰 섹션 분리·재생성 상속 2026-08-04 08:56:03 +09:00
2bf91d64f5 [refactor] negodata/front: 견적등록 모달 훅 분리 + 시장가 모순 카드 차단(10번), 엑셀 업로드 xlsx 파싱·CP949 폴백(7번) 2026-08-03 09:33:06 +09:00
bd0f39f131 [feat] negodata·agent: 목표가 네고 적용/미적용 산정근거·와일드카드 1% 10원 반올림·표 xl 전환·QA 체크리스트 2026-07-31 10:22:51 +09:00
5520e3c2b6 [feat] negodata: 견적 삭제(개발자 전용)·연결 데이터 캐스케이드 soft-delete
- 견적관리 체크박스 선택 + 툴바 '선택 삭제'(상품·협력사 패턴), 상세 시트 단건 '삭제'
- 백엔드 soft_delete 를 연결 협상 세션·대화까지 한 트랜잭션 캐스케이드
- 체크박스·선택삭제·상세삭제 모두 개발자(role 3)에게만 노출
2026-07-30 13:20:42 +09:00
2a004734d8 [feat] negodata·negosium: 엑셀 데이터 내보내기·견적상세 협상카드 연동·드로어 뒤로가기
- 엑셀 데이터 내보내기(상품·협력사·협상카드): 현재 검색/페이지 필터를 무시하고 전체를 페이지 순회로 받아(fetchAllForExport, 무언 절삭 방지) 양식과 동일 헤더로 CSV 내보내기(downloadXxxData) + '데이터 다운로드' 메뉴·todayStamp 파일명 → 내려받아 수정 후 재업로드(라운드트립)
- 견적상세 ChatTab: 사용 협상카드 매칭을 chat_id→card_id(카드 PK)로 교정, 카드 사용 메시지 본문 script 중복 제거(카드 박스에만 노출), 카드칩→/cards?detail= 상세 링크, 카드 멘트 실가격 마스킹(target_price 변수 미주입+maskPrices)
- 견적상세 협상카드 탭: 번호·스크립트 미리보기 컬럼 추가(cardScriptPreview 평문 추출)·카드명 상세 링크
- 상세 드로어: 백드롭 좌상단 '뒤로'(navigate(-1)) 버튼 추가(데스크톱), 공용 Sheet·견적상세 동일 적용
- 협상요약(negosium): 배송 리드타임 라벨을 회사설정(labels.lead_time) 연동 → '표준납기' 표기
2026-07-30 11:03:51 +09:00
82cca894fc [fix] negosium: 'MD → 구매담당자' 표기 통일
- 챗 봇/안내 라벨(ChatMessage·MDInformation·index.css 메뉴 제목)
- negodata 목표가 산정 문구(pricing) · 온보딩 FAQ/용어(OnboardingGuideModal)
- 견적생성 메모 라벨을 '협력사 안내 메모'로 명확화(협상방에서 협력사에게 보여줄 내용)
2026-07-30 08:31:16 +09:00
9dca78dc72 [feat] negosium: 협상완료 부가정보 수취·표시·잠금 + VAT 표기 통일
- 완료 요약 카드(투찰/협상 결과)에 부가정보·의견을 항상 구조로 표시, 미입력 필드는 '-'
- 협상 리스트 부가정보를 완전 읽기전용으로 잠금 — 입력은 완료 챗 마무리에서만
- 어드민 협상상세(현황·챗 탭)에 협력사 의견(custom.opinion) 노출
- 협상 화면 VAT 표기를 회사설정(vat_yn) 기준으로 통일 — hidden이면 라벨 생략
2026-07-30 08:31:16 +09:00
671904c762 Merge commit '30f134836b59843d84b60fa8c0923bd81afefb41' into feature/negodata 2026-07-29 09:57:05 +09:00
b33ae05cd6 [feat] negodata: 견적 목표가·앵커 반올림 + 통계 협상카드 효과 — 목표가=매입가×(1−네고율) 자동입력·산정후보를 낙찰기준탭으로 이동·목표가상한 상시표시 제거, 후보가/앵커 생성가 10원 단위 반올림(신규 세션, schedules 앵커와 동일 공식), 산정모달은 구매담당자 제시가 있어도 후보 전부 노출·MD입력가→구매담당자 제시가, 신규상품 등록에 공급사칸 추가, 상품상세/견적생성 라벨 회사설정 연동, 통계 협상카드 효과(유형별 사용빈도+사용직후 평균 제시가 하락)·와일드카드 1%인하 집계, mono 폰트 폴백 2026-07-29 09:51:03 +09:00
민헌
89652fac1f [refactor] negodata/front: 인터넷 최저가 상세 시트 재설계 — 실구매가 중심 + 근거 검증
협상 근거로 쓰는 화면이라 "얼마에 살 수 있고, 그게 어디서 나온 값인가"를
한 번에 검증할 수 있게 정보 구조를 다시 짰다.

구조: 결론 → 판단 → 근거
  - 히어로: 합계(상품가+배송비) 32px + 단가 대비 판정을 한 블록에.
    면은 브랜드보다 채도를 낮춘 --accent 표면, 값만 브랜드 컬러(화면에 단 하나).
  - 판매처 비교표: 상품가/배송비/합계를 몰별로. 상품가 최저와 실구매가 최저가
    다를 수 있어 둘 다 표시. 결과 없는 몰도 '미발견' 행으로 남겨 빈손임을 드러낸다.
  - 검색어(찾은 상품명) + 복사 버튼 → 판매처에서 다시 찾아볼 때 붙여넣기용.
  - 가격 추이: 상품가 단일 기준. 합계 기준은 배송비를 아는 회차에만 값이 생겨
    (네이버는 배송비 미제공 — 실측 39/39) 기준을 바꾸면 값의 의미와 표본 수가
    동시에 변한다. 기준선(상품 단가) 추가 — 이 선 위면 사는 게 손해.
  - 수집 이력: 미발견도 노출(전체의 30%가 not_found 인데 그동안 숨겨져 있었다).

데이터 정직성
  - 배송비 미상(전체 69%)을 0원으로 뭉개지 않는다 — 무료배송으로 읽히면 안 된다.
    모르면 히어로 라벨이 '합계' → '상품가'로 바뀌고 그 사실을 문구로 밝힌다.
  - 화면 주인공(합계)과 목표가 산정에 쓰이는 값(상품가)이 다르므로 각주로 명시.
  - 금액 옆 배송 주석은 '+3,500'(연산으로 오해) → '배송비 포함(3,500원)'.

접근성·일관성
  - 대비 전수 계산: --accent 면 위에서 muted-fg(4.16)·amber-700(4.28)이 AA 미달이라
    accent-foreground(9.85)·800 계열(6.0~6.5)로 교체. 히어로는 큰 글씨 기준 통과.
  - rose(파괴적 뉘앙스) 제거 → 브랜드 인디고 한 톤. 장식 아이콘 전부 제거.
  - 타이포 7단계를 상수 T 로 고정(임의 크기 금지).
  - 차트 점 3종 모양을 DOT 상수로 추출해 차트와 범례가 공유 — 범례는 차트 밖이라
    --color-price 가 해석되지 않아 색이 어긋났다. 전역 토큰으로 통일.
  - Y축 눈금 1·2·5×10ⁿ, 기준 변경 시 애니메이션 끔, 커스텀 X축 tick 제거
    (recharts 의 가장자리 앵커 보정을 건너뛰어 점과 라벨이 어긋났다).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 16:06:27 +09:00
민헌
5e24741395 [refactor] negodata/front: 최저가 탐색 모달 B2B 재설계 + 완료 판정 버그 수정
완료 판정 버그(로딩이 안 멈추던 원인 중 하나)
  폴링이 crawl_end_time 을 문자열로 비교했다. 서버는 마이크로초 6자리(.755705Z),
  JS toISOString() 은 밀리초 3자리(.755Z)라 자릿수가 달라 사전순이 시간순과 어긋난다
  ('.' < 'Z'). 게다가 클라이언트 시각을 서버 타임스탬프의 기준점으로 써서
  브라우저 시계가 조금만 앞서도 어떤 결과도 기준을 넘지 못했다.
  → 요청 직전 서버가 준 최신 수집 시각을 상품별 기준선으로 읽고 epoch ms 로 비교.

UI 재설계 (소비자 앱풍 → B2B 실무 툴)
  - 상태 3분리: confirm | searching | done. 검색 중에 완료 결론을 섞지 않는다
  - 표가 주인공: [선택 | 상품 | 기존 최저가 | 네이버 | 쿠팡 | 결과], 컴팩트 행, tabular-nums
  - 결과 열은 상태어(탐색 성공 / 변동 없음 / 탐색 실패 / 탐색 중)
  - 진행 카드: 표시등 + mm:ss 경과 + 진행바 + N/M, 셀 단위 스켈레톤으로 진행 위치 표시
  - radius 8/6/4, shadow 최소화, CTA 우측 정렬(풀와이드 금지), 강조는 indigo-800 한 톤
  - 완료 후 자동으로 닫지 않는다(결과 확인). 선택 해제는 닫을 때로 미룸

선택 재검색
  체크박스로 대상을 고르고, 검색이 끝나면 갱신되지 않은 행만 자동 선택된다
  (이미 갱신된 상품에 크롤 비용을 다시 쓰지 않도록). 재검색은 force=true 로 나가
  네거티브 캐시를 우회한다.

접근성 (스킬 기준 + 대비 실측)
  - 보조 텍스트 muted-foreground 4.88:1, indigo-800 9.93:1 (기존 1.92:1 FAIL 해소)
  - 상태는 색 단독이 아니라 텍스트/굵기/sr-only 병행
  - role=status aria-live, 탈출구 3중(X·ESC·오버레이), 버튼 높이 통일

api/generated 는 orval 재생성분. resTargetBreakdown 변경은 이전 백엔드 수정과의
스펙 동기화(이번 작업과 무관하지만 재생성으로 함께 정리됨).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 11:56:00 +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
a5fa44ee25 [refactor] negodata/backend: QuotationService 파사드 분할 — services/quotation/(pricing·build·closing·queries·invites), 시그니처·임포트 경로 외 동작 무변경 2026-07-27 16:15:19 +09:00
7b612824be [refactor] negodata: 목표가 산정 정리 — purchase_only 죽은코드 제거(백+프론트)·산정 재료 로더 공용화·자동재생성 잔재 주석 정정 2026-07-27 16:07:21 +09:00
bacf6271df [chore] 작업분 일괄: logs.sh 컨테이너 메뉴화 · dbeaver 초기화 SQL · landing 운영 compose · negodata/front eslint 도입 · 업로드 샘플 CSV 2026-07-24 18:45:46 +09:00
b539e073cb Merge commit 'dc8c288f' — lps-admin 신설·LPS 운영기능(ip_session/alerts/admin API)·최저가 출처링크 반영 2026-07-24 15:30:36 +09:00
d580e4d5ea Merge branch 'feature/negodata'
# Conflicts:
#	landing/app/routes/home.tsx
2026-07-24 14:22:31 +09:00
51d07bb779 [feat] negodata: 재협상 소유권 게이팅·처리담당자·상세 링크·재생성 타이틀·알림 토스트(읽음API)·재협상요청 표기·커스텀필드 select 2026-07-24 14:18:52 +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
cfee6e89d0 [feat] negodata: 권한 3단계(개발자 신설)·개발자 메뉴 그룹·디자인 시스템 페이지
- UserRole 1=일반 2=최고관리자 3=개발자(내부 운영). 개발자 계정은 회원 목록·총계에서 제외해 고객사에 노출하지 않음
- 계정 생성 시 권한 선택(일반/최고관리자) 추가, 개발자는 앱에서 부여 불가(DB 시드 전용)
- 사이드바 '개발자' 그룹 신설 — 회사 설정·디자인 시스템을 개발자에게만 노출
- /dev/design 디자인 시스템 페이지: 색 토큰·타이포·버튼·배지·입력·반경, 목록 화면 구조, 반응형 기준, URL 상태 규칙, 회사 커스터마이징 훅
- 공급사 재협상 요청(#15) 플로우 문서 — sessions.custom 기반(DDL 0)
2026-07-23 15:41:36 +09:00
db07a60cfa [fix] negosium·negodata·landing: 채팅 진입·부가정보 저장 오류 수정, 모바일 여백·스크롤·노치 대응
- 채팅 링크로 바로 진입하면 세션이 협상생성(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
2026-07-23 12:50:40 +09:00
0f10b6a70f [feat] negodata·공급사포털: 상품 필드 숨김 설정·용어 카탈로그 확장·로그인 전 브랜딩·모바일 대응
- 회사 설정 hidden_fields 신설: 상품 목록·등록폼·엑셀 양식에서 감추고 목표가 후보에서도 제외
- 용어 카탈로그 9→37개(상품·협력사·견적 그룹), 목록·폼·엑셀 헤더 배선
- 엑셀 '최저한도' → '인터넷 최저가' 표기 정정(구양식 헤더는 alias 유지), 공급사 컬럼 검증 유지
- 목표가 산정내역에 적용 모드·숨김 필드 반영, 목표가 상한 라벨에 산식(상품단가×2) 표기
- 공급사 포털: 초청 링크 session_id 로 로그인 전 회사 브랜딩 조회(무인증 엔드포인트), 기본 브랜드 negotium 워드마크, negotium·AI O2O 크레딧 표기
- 목록 테이블 뷰포트 기준 전환·툴바 줄바꿈·협상현황 카드뷰로 모바일/태블릿 대응
- 이용안내 FAQ 탭 추가, 이용안내·회사설정 탭 URL 쿼리 동기화 및 빠른이동 등록
2026-07-23 11:29:35 +09:00
03322d6ca3 [feat] negodata: 카드 성공률 순위·상품 엑셀 TO-BE 싱크·회사설정 JSON 입출력·목록 포털형 UI
- 협상카드 성공률(사용 세션 대비 타결) 집계 추가, 견적 생성 카드선택 순위 정렬·통계 TOP5
- 상품 엑셀: 품목코드·모델명 라벨화, 리드타임 suffix 제거, 공급사 컬럼 신설(등록 후 supplier_items 매핑)
- 설정 화면 JSON 불러오기/내보내기(빈 값은 미갱신), IMK 설정 JSON 보관
- 설정·회원 페이지 새로고침 forbidden 수정(자식 loader 가 initAuth 대기)
- 협력사·견적 목록도 포털형 카드(툴바+테이블 결합)로 통일
2026-07-22 17:27:23 +09:00
f8f8f70f54 [style] negodata/front: 상품·카드 목록 포털형 카드(툴바+테이블 한 카드로 결합) 2026-07-22 17:23:48 +09:00
0242ecff09 [feat] negodata: 초청 메일 헤더에 회사 브랜딩(settings.branding.email_header) 배선 2026-07-22 15:54:05 +09:00
bb576af346 [feat] negodata/front: 협상완료 부가정보 협상현황 모달·아이콘 툴팁(base-ui)·헤더 회사명 모바일 전용 2026-07-22 15:54:05 +09:00
88edcfed63 [feat] negodata: 회사 커스터마이징(설정·라벨·커스텀필드·엑셀·상품공급사#20·분류카테고리#17)·일괄삭제·로그export·구분·카드 전체선택·부가정보 표시 2026-07-22 15:20:24 +09:00
1e4726f4cc [feat] 스키마: company.settings·items/suppliers/sessions.custom·nego_cards 컬럼 추가(모델·init.sql) 2026-07-22 15:19:41 +09:00
e8d2acaade [feat] negodata: 통계 인상억제율(재협상)·월별 차트 — 직전 라운드 투찰가 대비 인하율 파생 집계 2026-07-22 15:19:14 +09:00
25e13d7b01 [feat] negodata/front: 디자인 리뉴얼 1차 — 인디고 콘솔 토큰·⌘K 크롬·대시보드/로그인/알림함 개편, 툴바 통일, 시트 스크롤 수정 2026-07-16 16:24:40 +09:00
3d515055fb [feat] negodata/front: 온보딩 이모지→아이콘 교체, step0 레이아웃·애니메이션 등 개편 2026-07-15 16:39:58 +09:00
민헌
c81df5bd88 refactor(lps): 설정을 TOML 단일 소스로 통합 — env/.env 이중 관리 제거
설정이 .env(compose 주입)·config.toml·코드 곳곳의 os.environ 직독 3계층에
흩어져 관리가 어려웠다. TOML 하나로 통합한다(협의 결정).

- 신설 [WorkerConfig](동시성·폴백·프로필·데드라인·유예·Chrome·하트비트),
  [AlertConfig](웹훅·쿨다운·임계 10종). [WebServerConfig].api_keys(guard),
  [DecodoConfig].ip_request_budget/port_cooldown_sec 추가 — 흩어져 있던
  LPS_* env 20여 개를 섹션으로 흡수.
- server_configs 의 env override 계층(DB_*·시크릿·NAVER_KEYS 등) 삭제.
  남는 env 는 APP_ENV(부트스트랩)·PROCESS_COUNT/WORKER_CONCURRENCY(실행
  스크립트 대화형 입력 전용)·LPS_LIVE(테스트 옵트인)뿐.
- Docker: env 주입 → config.docker.toml 마운트 + APP_ENV=docker.
  이미지 무시크릿 유지, 마운트 누락 시 FileNotFoundError 즉시 실패.
  .env.example 삭제, config.docker.toml.example 신설.
- negodata 호출부: guard 키를 env 직독에서 [WebServerConfig].lps_api_key
  (+기존 관례대로 env override)로 이동.
- 실행 스크립트: 프로필·폴백·예산 프롬프트 제거(toml 소스 안내),
  동시성/프로세스 수만 임시 override 로 유지.
- docs 7종·example toml 의 env 표기를 toml 키로 일괄 갱신.
- 전체 145 passed + APP_ENV=docker 로딩·API 기동 스모크 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:11:31 +09:00
민헌
e7d2c87fbe feat(lps): API guard — LPS_API_KEY 설정 시에만 /v1 에 X-API-Key 검증
외부에서 API 를 함부로 호출(비용 발생 enqueue 등)하지 못하도록 정적 키
guard 를 추가한다. '키의 존재'가 토글 — 개발(local/dev)은 env 를 비워
개방 모드(기동 시 WARN), prod 만 키를 주입한다(협의 결정).

- router/v1/validator/auth.py: X-API-Key 의존성 — secrets.compare_digest
  상수시간 비교, 콤마 구분 복수 키(무중단 키 교체), 매 요청 env 조회
  (재기동 없이 테스트 가능). /v1 라우터 전체에 적용.
- /healthz·/readyz 는 라우터 밖이라 항상 개방(LB 프로브).
- negodata lps_sync_service: LPS_API_KEY env 있으면 헤더 자동 첨부(한 곳).
- compose(lps-api·negodata-backend) LPS_API_KEY 패스스루 + .env.example.
- prod 체크리스트(operations.md): 키 주입 + lps-api 포트 비공개 + 기동
  로그 'API guard ON' 확인. api.md 인증 섹션 추가.
- 라이브 스모크: 무헤더/오키 401 · 정키 2종 200 · healthz 200 확인.
- 테스트 6건 추가, 전체 141 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:45:31 +09:00
민헌
f97e407f7c Merge branch 'main' into feature/negodata-lps 2026-07-13 13:55:36 +09:00
21f90ed84a [fix] negodata/front: 윈도우 브라우저 폰트 미적용 수정 — format woff2-variations 를 표준 woff2 로 교체 2026-07-13 08:47:09 +09:00
민헌
9ef9bfbca3 feat(negodata/front): 최저가 상세보기 버튼 노출 + 상세 시트 가독성 정리
- 상품 테이블 인터넷 최저가 셀: 값 옆에 차트 아이콘 버튼 상시 노출
  (기존 hover 밑줄만으론 클릭 가능성이 안 보임) — 값 없는 상품도
  버튼으로 진입해 빈 상태 안내를 받는다
- 상세 시트 재구성: 견적 상세 드로어와 같은 SectionCard 문법으로 통일
  · 헤드라인 카드: 상품명 → 대표가(26px rose) + 단가 대비 칩
    (절감=emerald·역전=amber, Trending 아이콘) + 단가·마지막 수집 캡션
  · 최저가 출처: 섹션 타이틀 우측 사이트 배지, 링크는 outline 버튼화
  · 가격 추이: 수집 횟수 캡션, 2회 미만이면 안내 문구(빈 영역 방지)
  · 수집 이력: 배지 폭 고정(w-14)으로 컬럼 정렬, 링크 없는 행 자리 유지

검증: 헤드리스 렌더 확인(테이블 셀·시트 v2 스크린샷), tsc 통과

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:18:09 +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
민헌
1cce752b47 Merge branch 'feature/negodata-lps' — negodata↔LPS 인터넷 최저가 연동(수동 트리거 전용) 2026-07-10 16:49:49 +09:00
민헌
5ddf5f6249 refactor(negodata): LPS 자동 요청 배치 제거 — 검색은 수동 트리거 전용
협의 결정(2026-07-10): 크롤 비용이 사용자 행동에만 비례하도록,
갱신 오래된 상품을 자동으로 검색 요청하던 잡(매일 04:00, 상한 500건,
일 ~$2)을 제거한다. 검색 진입점은 상품 화면의 수동 트리거
(POST /v1/item/{id}/lowest-price) 하나만 남는다.

- request_lps_searches 잡·request_stale_searches 서비스·stale_items
  CRUD·배치 상수(REFRESH_HOURS 등) 제거
- 수집 잡(sync_lps_results, 5분)은 유지 — 크롤을 일으키지 않는
  반영 백스톱(모달 조기 종료·폴링 초과분 자동 반영, 비용 0)

검증: 스케줄러 등록 잡 3개(견적마감 2 + LPS 수집 1) 확인, 수집 잡 실행 정상

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:47:39 +09:00
민헌
3171311746 feat(negodata/front): PriceUpdateModal 데모 → LPS 실연동
- 가짜 setTimeout 애니메이션 제거 — 상품별 POST lowest-price(큐 접수,
  duplicated 는 결과 대기 합류) 후 GET 5초 폴링(서버가 조회 시 lps_db
  증분 동기화를 겸하므로 폴링이 곧 수집)
- 요청 시각 이후 crawl_end_time 이력만 이번 결과로 인정, 완료/미발견
  로그 구분(emerald/amber), 5분 대기초과분은 주기 동기화 자동반영 안내
- 종료 시 /v1/item/list invalidate → 테이블 인터넷 최저가 컬럼 갱신
- 닫기 = 폴링만 중단(서버 검색은 계속됨을 UI 에 명시)
- orval 재생성: lowest-price 응답 타입화(LowestPriceEntry·lowest_price)

검증: tsc 통과, vite build 통과 (API 흐름 자체는 ③ e2e 로 검증됨)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:32:38 +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
c0e6987c3c [fix] negodata/front: 통계 카테고리별 절감 차트 -> 손해(음수)는 안보이게, 대시보드에서 일반관리자도 회사 통계 보이게 수정 2026-07-10 15:58:13 +09:00
민헌
523661e3d2 feat(negodata): LPS 최저가 동기화 배치 — 요청(API)·수집(lps_db)·반영(items)
설계(2026-07-10 결정): 요청은 LPS API(dedupe·워커알림 보존), 결과는
lps_db.price_history 직접 읽기(읽기전용 엔진). product_code=item_id
로 결과가 자동 매핑된다.

- 잡③ request_lps_searches(매일 04:00 KST): 24h 이상 미수집 상품을
  최대 500건 enqueue(job_type=batch, 100건/콜 청크) — 비용 발생 잡
- 잡④ sync_lps_results(5분): 워터마크(max crawl_end_time) 증분 수집
  → item_internet_lowest_prices append(성공/실패 모두) + 성공분 최신값
  items.internet_lowest_price 박제(+yn). 한 트랜잭션(부분반영 방지)
- 스캔 바닥 3일 — 비uuid·미존재 상품 행이 워터마크를 못 올려도
  재스캔 범위 유한
- ⚠️ 시각은 aware UTC 통일 — naive 를 timestamptz 파라미터로 넘기면
  PG 세션 타임존(KST) 해석으로 9시간 어긋남(중복 수집 실측 버그 수정)
- LowestPriceWebsite 코드 enum(naver=1 coupang=2 …), iilp ORM 모델,
  lps_base_url config(+LPS_BASE_URL env)

검증: 실상품 1건 e2e(요청→크롤 found 15,000원→이력+박제 반영),
멱등성(재실행 빈 카운터), 비uuid 12건 스킵, 대상선정 쿼리 500건 상한

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:38:57 +09:00
b41f7c67f1 [feat] negodata: 카드 에디터 조건 전략 변수 + 미리보기 변수 라벨 치환
① 카드 목록 스크립트 미리보기에서 {변수} 토큰을 한글 라벨 칩으로 렌더
   (renderCardScriptPreview) — 원시 토큰({prev_partner_price} 등) 노출 제거.
② "조건 전략"(customer_condition) 특수 변수 추가:
   - 툴바 '+ 조건 전략' → 본문에 위치 칩 삽입, 본문 아래 별도 Slate 입력창 노출
   - 조건 내용은 변수 노드에 중첩 보관(edit_script 배열 유지·하위호환) + 저장 시
     script 의 해당 위치에 주입 → agent 는 완성된 문구를 그대로 읽음(무변경)
   - 서브 에디터는 조건 전략 버튼 숨김(중첩 방지), 빈 조건 저장 차단

DB·백엔드·agent 변경 없음(edit_script 타입 unknown, 조건 노드 내 중첩).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:18:11 +09:00
민헌
9e9de52200 feat(negodata): lps_db 읽기전용 연결 기반 — DBType.LPS + 조건부 엔진 등록
LPS(인터넷 최저가 검색) 연동 1단계. price_history 동기화 배치가 쓸
읽기전용 접속을 기존 확장점(DBType enum + 세션매니저 맵)에 등록한다.

- DBType.LPS=2, LpsDBConfig(read_* 만 — write 엔진 미등록으로 앱 차원
  읽기전용 강제, write 요청 시 KeyError)
- config name 비면 미등록(구버전 toml·LPS 없는 환경에서도 부팅 가능),
  배치는 is_registered() 로 사전 확인
- env override: LPS_DB_HOST/PORT/USER/PASSWORD/NAME (도커용)
- 검증: 엔진 등록·읽기 세션·write 가드 KeyError·lps_db.price_history
  실조회 확인

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:11:18 +09:00
26d9fa3c4b Merge branch 'feat/chat-update' — 협상 고도화(LLM 표현/이해층·카드 전술·변수 카탈로그) 2026-07-10 13:39:19 +09:00
afacd74382 [feat] negodata: 카드 변수 카탈로그에 전술 변수 6종 추가
agent 전술 재설계로 늘어난 치환 변수를 편집기 툴바·칩에 노출:
제시가(input_price)·협력사 직전가(prev_partner_price)·제안가(counter_price)·
중간가(target_mid_price)·절충가(middle_price)·인하율(discount_rate).
name 은 agent chat_engine.vars_for 치환 키와 일치. 라벨은 렌더 시점 조회라
기존 저장 카드도 마이그레이션 없이 한글 라벨로 표기된다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:37:50 +09:00
7f1c0be2b3 [feat] negodata: 협력사 채팅 계정(supplier_users) 관리 — 발급·비번 재설정·활성상태
- 백엔드: supplier_users/supplier_user_tokens ORM 매핑, /v1/supplier/{id}/account 4종(조회·발급·재설정·상태), 회사 스코프 게이팅, 재설정·비활성 시 토큰 삭제로 기존 로그인 즉시 무효화
- 비밀번호: 발급=서버 자동생성(1회 반환), 재설정=커스텀 지정 가능(미지정 시 자동생성)
- 프론트: 협력사 상세 SupplierAccountManager 섹션 + 목록 '채팅 계정' 컬럼, orval 재생성(SupplierType 은 enumLabels 로컬 상수로 이동)
- 테스트: test_supplier_account.py 9건, 전체 스위트 green
2026-07-10 10:14:00 +09:00
1b9ca062f3 [feat] negodata: 협상카드 회사 스코프·공용 읽기전용 + 견적 목표가 산정내역 수정
협상카드
- 목록/조회를 등록자 개인 → 회사(company.users 조인) 단위로 확장: 같은 회사 유저 카드 + 공용(user_id NULL)
- 공용 카드는 기본 제공 자산 → 수정·삭제 불가(OWNER 포함), is_shared=True API 등록 거부
- 개인 카드 변경은 본인∪최고관리자만, 프론트 버튼 disable+툴팁
- 공개범위 셀렉터·엑셀 공개범위 컬럼·CardTable 전체 배지 제거(항상 개인 등록)
- test_card.py 5케이스 재작성

견적 목표가 산정내역
- get_setting_rates: 단일 컬럼 select 결과를 이중 인덱싱해 항상 예외 → 마진율이 늘 0으로 적용되던 버그 수정
- get_target_breakdown: 채택 후보 판정을 '현재 최소값' → '저장 목표가와 값 일치'로 변경(산정 후 상품가 변동에도 초록 체크 유지)
- 견적상세 협상카드 탭 세션ID 컬럼 제거

견적생성 모달
- 목표가 미리보기·마감기한 기본값(+1h)·미래 검증, combobox 팝오버 scrollIntoView, maskPrices 비율(%) 마스킹

Little-Helped: Stupid Claude
2026-07-09 17:04:40 +09:00
0d62440ee1 [feat] negodata/front: 온보딩 페이지 추가 + 이용안내 모달 재작성 2026-07-09 16:08:54 +09:00