협의 결정(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>
- 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>
협상카드
- 목록/조회를 등록자 개인 → 회사(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
카드 카탈로그(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>
- 소유권 게이팅(common/authz): 변경 액션 본인∪OWNER, 협력사 삭제 OWNER 전용
- 견적 수동 낙찰(award) + 작성자명(creatorName) 표시 + 전화번호 입력 컴포넌트 + 카드 엑셀 업로드
- supplier_type 은 이번 커밋 미변경(다음 커밋에서 코드부터 정리 예정)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- common/anchoring 이식 패키지 신설: 원본(schedules/anchoring)에서 읽기 경로 발췌
(constants·base_table·service) + reader 이식판(Redis 미사용 — current_rates 뷰
단일 쿼리, 실패·무이력 시 정적 테이블 폴백으로 견적 생성 무중단)
- _build_quotation: 목표가 산정과 앵커 산출 분리 — 칸(items.company_id ×
quotations.supplier_type × 가격구간) rate 로 tp*(1000-rate)//1000 정수 박제,
anchor_rate_permille 동시 기록. quotation_settings.anchoring_value 계산 사용 중단
- 재생성(regenerate_next_round): target_price 만 상속, 앵커는 생성 시점 rate 재계산
(인수인계 규칙 1 — 상속 폐지)
- sessions 모델 anchor_rate_permille 매핑, crud get_item_companies 신설
- 테스트 6종 신설(스키마 부재 폴백·칸별 조정 반영·유형 미지정·재생성 재계산·
bracket 경계 골든 벡터) — 전체 스위트 50 통과
- negodata 담당자 승인 하 직접 적용. 6개월 압축 시뮬레이션(격주 배치 13회,
세션 522건)으로 박제→소비→재박제 루프·클램프·이월·이중소비 방지 검증 완료
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- test DB 세션마다 자동 create/drop (팀원은 Postgres만 있으면 pytest 한 방)
- auth_headers 시드 픽스처(무인증 /auth/create 제거 대응) + other_company_id
- 커버: 회사 스코프(견적·상품·협력사·대시보드·세팅), 견적 마감 재견적 O/X + 알림,
견적 생성·목표가, 알림함 읽기, 회사유저 OWNER 게이팅, 기존 파일 검증/기대결과 주석 정비
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
quotations 에 company_id 컬럼이 없어 견적 조회가 회사 무관하게 전부 노출되던 멀티테넌트 누수를 차단. 작성자(user_id)→users.company_id 서브쿼리로 회사 스코프를 걸어 목록(/list)과 단건·세션·협력사 엔드포인트(_fetch 가드)에서 남의 회사 견적을 NOT_FOUND 처리. 스케줄러/내부 호출은 company_id 없이 우회. 라우터/서비스의 '회사 스코핑 안 함' 거짓 주석 정정.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 생성(_calc_target_price)과 표시(get_target_breakdown)가 _candidates 헬퍼를 공유
→ 프론트 재계산 제거, 저장된 목표가와 항상 일치
- TargetPriceModal: useGetTargetBreakdown 으로 후보·채택·앵커링가 '표시만'
- QuotationType.is_new() 한 곳에서 신규유형 판단(생성·산정 공통)
- enum 라벨 단일출처(lib/enumLabels.ts): 협력사유형/카드 사용구분/유저상태
members·CardFormSheet 는 거기서 re-export
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[견적·목표가]
- 목표가 산정 KTC 기준 정비(MD 우선, 없으면 인터넷최저가·매입가·판매가 중 최저 / 신규는 인터넷최저가만)
- 인터넷 평균 수수료 0.078 상수화(견적설정 컬럼 제거)
- 앵커링가 세션 저장 + 목표가 클릭 시 산정내역 모달
- 재생성 시 목표가·앵커링가 재계산 없이 직전 값 상속(KTC)
- MD 제시가·협력사 유형(유통·제조·총판·기타) 입력
[협상카드]
- 사용 구분(공통/신규견적전용/재견적전용)
[협상 진행]
- 협력사 초청 메일 발송(일괄/개별·재발송, 발송상태 표시)
[기타]
- 견적상세 창 닫기 버그 수정, 불필요한 컬럼 주석 정리
- DB: 기존 DB는 postgres-init/04-alter.sql 적용 필요(자동 마이그레이션 없음)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- OWNER 권한 신설, 최고관리자가 자기 회사 소속 직원(USER) 계정 생성·수정·삭제 관리
- 회사 사용자 API(/v1/company/user/*), 회원관리 페이지·폼
- 로그인/프로필 흐름 정비, 무인증 계정생성(/auth/create) 제거
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- models.py: created_at/updated_at/last_accessed_at/start_time/end_time/bid_at
DateTime → DateTime(timezone=True)
- quotation_service: tz-aware 입력을 naive 로 깎던 _naive_utc 제거(컬럼이 tz-aware라 불필요),
start_time/end_time 직접 사용. negosium_db 는 UTC 고정.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
백엔드 close_and_decide 경로:
- 동시 이중 마감 가드: 마감 판정 전 원자적 CLOSED 선점(claim)으로 두 크론 잡·수동마감 경합 직렬화
- 재생성 실패 표면화: regenerate_next_round 결과 검사 → 실패 시 REGEN_FAILED 반환(체인 끊김 은폐 방지)
- 차수 충돌 방지: 다음 라운드 = 체인 최신 round+1(chain_max_round 기준)
- 재생성 사유 집계 정밀화: 미참여/동가를 양성 표식으로 구분(단독낙찰·거부 오집계 제거)
- 재생성 라운드 최소 협상기간 하한(즉시 재마감 캐스케이드 방지)
- 잡 루프 per-item 예외 격리(한 건 실패가 배치 전체를 멈추지 않음)
프론트:
- useChatController: 무권한 가드를 sessionId 별로 추적해 세션 변경 시 자연 해제
- useScrollLock: 마지막 해제를 rAF 로 지연해 재마운트 사이 일시적 잠금 해제 방지
- quotation 상세 쿼리 placeholderData 로 라운드 전환 중 시트 유지
테스트:
- 신규 test_close_and_decide_fixes.py(동시성·차수·집계·기간 하한 검증)
- conftest 결함 수정(존재하지 않는 tbl_account TRUNCATE 제거, companies.status 명시)
- stale 테스트 갱신(test_quotation_create 를 타입드 Req/신규 응답 형식에 맞게 재작성)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
라우터가 req.model_dump(exclude_unset=True)로 dict를 만들어 넘기던 것을
req(Req_*) 객체 그대로 전달하도록 변경. 서비스 시그니처를 전부 타입드로 통일.
- create 5개(quotation/card/item/supplier/quotation_setting): ORM은 명시 kwargs
조립(item만 컬럼 16개라 model_dump 펼침). 경계 검증·타입 유지, **data 결합 제거.
- update 4개: 서비스가 req 받아 내부에서 model_dump(exclude_unset=True) 생성 후
CRUD(dict)로 전달 — 공식 PATCH 메커니즘 유지.
- 서비스 계층 data: dict 시그니처 0개. CRUD는 dict 유지(의도).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 견적 목록에 대표 상품(세션 item) 조인 노출 → '대상 상품 확인 불가' 해소
(backend protocol/crud/service + orval 재생성 + front mapQuotation 매핑)
- 견적·협상카드 목록 클라이언트 페이지네이션 추가 (useClientPagination)
- 견적중단을 서버 stop_quotation 호출로 영속 처리(상태=견적마감)
- 신규 협상견적 라벨 발의→등록 통일
- 발의 위저드 카드선택에 협상/와일드 구분 뱃지 추가
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
QuotationCardData 에 number/edit_script/condition/memo 추가, card 카탈로그(nego/wild)
라이브 조인으로 채움. DB 컬럼 변경 없음(기존 카드 테이블 조인만 확장).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
POST /v1/item/image — SAS 토큰을 URL에 붙여 httpx PUT 으로 Azure Blob 업로드.
이미지 타입/용량 검증 + IMAGE_* 에러코드. config 에 azure_blob_base_url/sas/root 추가.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
item/supplier 의 POST upload-excel 스텁 라우트와 Res_ExcelUpload 프로토콜 제거.
프론트 엑셀 모달은 클라이언트 CSV 파싱+check-codes/create 로 도는 독립 기능이라 영향 없음.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- item/supplier/quotation/quotation_setting CRUD·service·router 추가
- item protocol delivery_type str→int (ERD/스키마 SMALLINT 일치)
- DeliveryType enum + 한글 라벨, 공용 GET /v1/enums (도메인 코드 메타데이터)
- CompanyBrief → CompanyData 로 *Data 네이밍 통일
- CORS: WebServerConfig.client_url(단일) 도입 (config_models/router)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>