Commit Graph

290 Commits

Author SHA1 Message Date
5d0fbbcf6a [feat] 공급사 포털·backend: 협상완료 부가정보 입력(#5)·포털 브랜딩/로고·구분삭제(#19)·카드메타 nego 저장 2026-07-22 15:20:24 +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
e7a51b1b75 [feat] negosium/front: 협상방 마감 정비 — 우측 패널 납작 섹션·모바일 협상절차 스텝바·이용가이드 모달 토스화·중복 이용가이드 제거·폰트 정리·마감시간 tabular-nums 2026-07-22 08:29:58 +09:00
e9427543c8 [feat] landing: 히어로/데모 리뉴얼 — 콘솔형 히어로·price-sparkline 추가, 데모 미디어(mp4/jpg) 세트 추가, ROI 시뮬레이터·negotiation-console·버튼·홈 라우트 수정 2026-07-22 08:29:54 +09:00
6ffdb9a389 [feat] landing: 비교 토글 자동재개·헤더 nav 4개 정리·모바일 가운데 정렬·Phone Demo 히어로 디폴트, FAQ 연동답변·중개대행·역경매·와일드카드 등 논란 문구 정리 2026-07-21 13:49:27 +09:00
9ccf42cf6b [feat] landing: 카피 정직화(가이드 3탭·강화학습·이점)·실단가 반영, 비교 토글·FAQ 섹션 신설, 협상 콘솔 목표가 스파크라인·절감 델타 추가, 헤더 플리커·히어로 줄바꿈 수정 2026-07-21 10:47:45 +09:00
e93820a51b Merge branch 'design/frontend' into feature/landing 2026-07-21 08:48:07 +09:00
dd3609173e [feat] landing: 협상 데모 GIF 모바일 UI로 교체 (채팅 밀당→타결, 상단40/하단28 세이프에어리어, 9:18) 2026-07-21 08:45:29 +09:00
2386cb8867 [feat] negosium/front: 아이마켓 보고서 기준 토스풍 UI 리뉴얼 — 로그인·포털 목록(KPI·모바일 카드)·협상방 3단(말풍선·타임라인·결과/성공률 카드)·모바일 드로어, 목록 서버 페이징 유지·검색 비움 2026-07-20 17:27:11 +09:00
b6852ec6aa [feat] landing: 협상 데모 GIF 모바일 UI로 교체 (채팅 밀당→타결, 상단40/하단28 세이프에어리어, 9:18) 2026-07-20 17:26:50 +09:00
03c22f9474 [feat] landing: 솔루션 랜딩페이지 신규 — 히어로·데모·ROI 시뮬레이터 섹션, docker 서비스(:3100) 추가 2026-07-20 09:09:09 +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
21f90ed84a [fix] negodata/front: 윈도우 브라우저 폰트 미적용 수정 — format woff2-variations 를 표준 woff2 로 교체 2026-07-13 08:47:09 +09:00
ac487268d8 postgres-init lps db 추가. 2026-07-10 17:19:24 +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
22500eae4c [chore] compose: agent 에 OPENAI_API_KEY passthrough — LLM 표현/이해층 키 주입
빈 값이면 무시(server_configs env override 가 값 있을 때만 적용)라 무해.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:37:50 +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
1682481b01 [feat] agent: 협상 고도화 — LLM 표현/이해층 + 카드 전술 실행계층 + ktcommerce 정리
카드 재설계("멘트 카드 → 전술 카드"):
- tactics.py 신설: 카드번호→전술(카운터 산식) 레지스트리, min(counter,target) 클램프
- 카운터 수락=즉시 타결(pending_counter_price 일반화, 구 offer_1pct 흡수)
- 목표가 초과 타결 금지(성공스텝 진입 가드) + "카드 소진=실패" 폐지→종결 국면
- 선택형 와일드카드(WC-*) 발동 + card.wild_cards 멘트 DB 어댑터

LLM 계층:
- Phase 2 표현층 ScriptNaturalizer(카드 멘트 자연화, 마커·치환자·숫자 보존 검증)
- Phase 3 이해층 InputInterpreter(자유발화 NLU→기대입력, 한국어 가격 파서)
- OPENAI_API_KEY env override(server_configs) + 전역 자격증명 게이트

결정 스택(Phase 1):
- 협상 규칙 데이터화(negotiation.wildcard_*_ratio/max_counter_rounds)
- 선택카드 우선순위 prior(UCB 방문수 감쇠, Q-table 오염 없음)

버그픽스:
- 인하율 음수 표기 제거 + 인상/동일/인하 구분(discount_phrase)
- 자연화 강조마커 보존(볼드/색 소실 시 원본 폴백)
- 카드 시드 가격변수(prev_partner_price·target_mid_price·middle_price 등) 치환

정리:
- ktcommerce 테넌트 삭제 + 테스트 21파일 imarketkorea/_base 로 마이그레이션
- 실 LLM 호출 차단 conftest 가드

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:37:36 +09:00
민헌
18f6466a8b Merge branch 'feature/lps' — 인터넷 최저가 검색(LPS) 시스템 2026-07-10 13:30:48 +09:00
민헌
d4cee1caf7 feat(lps): API 스케일 파라미터를 .env 로 노출 — PROCESS_COUNT·커넥션 예산
주석 상태였던 PROCESS_COUNT/DB_CONNECTION_BUDGET 를 활성화하되 서버별
값은 .env(LPS_API_PROCESS_COUNT, LPS_DB_CONNECTION_BUDGET)로 조정.
기본 1/40 — API 는 단일 프로세스로도 ~1,100 RPS(실측)라 평시 충분,
부하 대비 시에만 상향. 풀 자동 산정이 예산 초과를 방지.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:02:17 +09:00
민헌
a267326bf7 fix(lps): Docker 이미지 무시크릿화 + 배포 견고화 — 리뷰 발견사항 일괄 적용
시크릿 유출 차단(핵심):
- config.local.toml(OpenAI·DECODO·네이버 키)이 COPY . . 로 이미지에
  구워지던 문제 — .dockerignore 제외 + 빌드 시 example(플레이스홀더)
  복사로 대체. 실값은 compose env 주입(리포 루트 .env, 템플릿 .env.example)
- DECODO_PORT_START/END/SESSION_MINUTES env override 추가 — 포트가
  toml(플레이스홀더 0)에만 있으면 자격증명을 넣어도 프록시가 조용히
  꺼지는 구멍 봉합

배포 견고화:
- API Dockerfile 에 HEALTHCHECK(/healthz) 추가
- autoheal 컨테이너 추가 — compose restart 는 unhealthy 를 재시작하지
  않으므로 라벨(autoheal=true) 기반 자동 재시작 담당
- 이미지 python 3.12→3.14 정렬(로컬 개발·테스트 환경과 일치)
- API 이미지 경량화: requirements-api.txt 분리(크롤 의존성 제거, 330MB)

검증: 양 이미지 빌드 성공, 이미지 내 시크릿·.profiles 부재 확인,
무시크릿 API 이미지 스모크(healthz/readyz/HEALTHCHECK healthy) 통과

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:53:47 +09:00
민헌
c075dfbcfb fix(lps): .dockerignore 에 .profiles/ 추가 — Chrome 세션 이미지 유출 차단
COPY . . 가 로컬 Chrome 프로필(쿠키·cf_clearance, 149MB)을 API·워커
이미지에 굽고 있었다. 컨테이너는 빈 프로필에서 웜업으로 쿠키를 만들고
volume(/profiles)에 영속하는 설계라 이미지에 있을 이유가 없다.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:45:19 +09:00
민헌
4381649658 docs(lps): 워커·브라우저 1:1 근거와 워커 수 상한 산정법 문서화
- 6-2: 브라우저가 아니라 신원(프로필+sticky IP)이 희소 자원인 이유,
  비율이 어긋날 때의 손해(유휴 낭비 vs 락 직렬화), 신원당 처리량 캡
- 6-3: 워커 상한 = min(RAM캡, 포트캡÷3, 실측 정체점) — 하드캡 계산식과
  계단식 실측 시 멈춤 신호 5가지(처리량 정체·blocks_1h·메모리·
  단일코어 포화·p95)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:32:12 +09:00
민헌
463416c3a2 fix(lps): 워커 행(hang) 3중 방어 — 잡 데드라인·웜업 타임아웃·정리 루프 격리
부하테스트(N=10) 실측: 쿠팡 브라우저 재기동 경로 행으로 워커 2/3가
12~15분 정지(쿠팡 검색 1콜 880s), heartbeat 가 lease 를 계속 갱신해
reaper 회수 불가 + stuck_running=0 으로 관측 사각. 이에 대한 방어:

- P1 잡 데드라인: runner._process 에 wait_for(기본 300s, env
  LPS_JOB_DEADLINE_SEC) — 초과 시 취소·fail → 백오프 재큐/DEAD
- 웜업 시도당 60s 타임아웃 — 웜업이 어댑터 락을 쥔 채 행하면
  그 워커의 실 검색이 전부 락 대기로 동반 정지(실측 케이스)
- P2 browser reaper: close_if_idle 에 60s 타임아웃 — 한 어댑터
  close 행이 정리 루프 전체를 멈추는 것 방지(실측 케이스)
- P3 stuck_running: lease 만료 OR 실행 10분 초과(run_started_at)
  — 행 상태에서도 지표·알림에 잡히게

테스트 5건 추가(데드라인 DEAD/재큐, stuck 지표, reaper 격리)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:27:03 +09:00
민헌
fbd2cec1da test(lps): 부하테스트 상품셋 100종 카탈로그 분리 — 실제 입력 분포 재현
- loadtest/catalog.json 신설(실존 상품 100종): 규격만 54 · 모델포함 26 ·
  이름만 20 · 기준가 포함 7 (price→가격밴드 필터 경로 포함)
- loadtest.py 는 인라인 6종 대신 카탈로그 로드, 실행 시 구성 요약 출력

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:50:28 +09:00
민헌
5e485e9b6e feat(lps): 오픈마켓 폴백 기본 비활성(LPS_FALLBACKS 토글) — 협의 결정 B안 적용
2026-07-10 협의: 검색은 네이버+쿠팡만. G마켓·옥션·11번가 폴백은 최종 최저가
기여 0회에 검색당 최대 15s·프록시 비용 ~87%를 차지해 로직에서 제외.
주석처리 대신 env 토글로 코드·테스트는 살려둔다(부패 방지·env 한 줄 재가동).

- worker_main: LPS_FALLBACKS(기본 빈값=OFF)로만 폴백 어댑터 생성, 잘못된 값 경고,
  기동 로그에 폴백 상태 표기. 핸들러는 빈 폴백을 원래 정상 처리라 로직 변경 없음
- run_local_worker.sh: 폴백 여부 대화형 질문 추가(기본 비활성)
- compose: LPS_FALLBACKS 주석 env(재가동용)
- decision-openmarket-crawler.md: 결정(B)·근거·재가동 절차(라이브 스모크 선행) 확정 기록
- README·architecture·operations: 기본 비활성 반영

검증: 전체 테스트 106 passed(폴백 로직 테스트는 fake 주입이라 계속 유효),
워커 실기동 로그 '오픈마켓 폴백: OFF' 확인

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 10:14:39 +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
민헌
89fe2c9805 feat(lps): 실시간 관측 대시보드 추가 — 큐 추이·코어별 CPU·프로세스 그룹 사용률
loadtest/monitor.py (:9700, 단일 파일·외부 인프라 없음) + run_monitor.sh(대화형).
e2e 부하(N=100 loadtest.py) 중 "프로세스가 잘 진행되는지, 코어가 전부 도는지,
병목이 어느 층인지"를 브라우저에서 2초 간격으로 본다:

- 큐 추이: /v1/lps/ops 폴링 — PENDING/RUNNING/DONE/DEAD 라인 + 처리량(개/분) 타일
- 프로세스 그룹 CPU: worker/api/chrome/postgres — 병목 층 판독
  (chrome 은 워커 자손만 집계해 사용자 브라우저와 분리, uvicorn spawn 자식은 부모로 api 귀속)
- 코어별 사용률 막대: 멀티코어 활용 확인
- 현재 스냅샷 표 + 호버 툴팁 + 라인 끝 직접 라벨(dataviz 팔레트 검증 통과, 다크 서피스)
- psutil 의존성 추가(로컬 관측 전용)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 09:50:14 +09:00
민헌
64d5063041 feat(lps): 서버 실행 스크립트에 멀티코어 대화형 선택 추가
일반 실행(1) 선택 시 PROCESS_COUNT 를 물어보고(기본 1, CPU 코어 수 표기),
2 이상이면 DB_CONNECTION_BUDGET 도 물어본다(기본 96 — 전용 PG 벤치값).
워커 스크립트(run_local_worker.sh)의 동시성 질문과 같은 대화형 패턴.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 09:09:55 +09:00
민헌
47c64eaf8b fix(lps): 워커 graceful shutdown — 신호 핸들러·잡 마무리 유예·정리 격리
- SIGINT/SIGTERM 핸들러 등록: cancel 대신 stop 이벤트 set → 새 잡 클레임 중단,
  하던 잡은 마무리 후 자연 종료(트레이스백 없이 exit 0). 신호 재수신 시 강제 종료
- 종료 유예 LPS_SHUTDOWN_GRACE_SEC(기본 60s) 초과 시 강제 취소(잡은 lease 만료 후 재큐)
- 워커/리퍼가 예외로 죽으면 기존처럼 전파하되, finally에서 남은 태스크 취소·완주 대기 후 정리
- 리스너·어댑터 정리를 항목별 try/except로 격리 — 하나 실패해도 나머지 Chrome 정리
- 웜업(bg) 태스크는 종료 신호 즉시 취소해 어댑터 락 해제
- compose lps-worker에 stop_grace_period: 75s (기본 10s면 드레인 전 SIGKILL)
- 운영 가이드에 워커 종료 절차 문서화

검증: SIGTERM/SIGINT 실기동 테스트 — graceful 로그 후 exit 0, 기존 테스트 26개 통과

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 09:03:57 +09:00
민헌
ddb6f6bd2f fix(lps): 워커 로그 노이즈 2건 제거 — 리퍼 네이버 스킵 + 폴백 데드라인 cancel 제거
- browser-reaper가 close_if_idle 없는 어댑터(네이버 httpx)를 건너뛰도록 getattr 가드
  → 30초마다 반복되던 ERROR 로그 제거
- 폴백 데드라인을 wait_for(cancel) → asyncio.wait(버림)으로 변경
  → in-flight page.goto 취소 시 patchright 내부 future가 남기는
    'Future exception was never retrieved' 노이즈 제거, 페이지 어중간 상태 방지
  → 버려진 태스크는 강한 참조 집합(_abandoned_fallbacks)에 보관(GC 중도 파괴 방지),
    종료 시 콜백이 예외 회수 후 집합에서 제거
- 데드라인 테스트에 잔여 태스크 배수(drain) 검증 추가

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 08:50:12 +09:00
민헌
08a9c52088 docs(lps): 아키텍처에 API 멀티코어 스케일·커넥션 풀 자동산정(6-1) 추가
process_count 를 늘릴 때 풀 오버서브스크립션→커넥션 고갈 함정과
connection_budget 기반 자동 역산 해법을 문서화.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 08:35:48 +09:00
민헌
0e30354ba5 feat(lps): 로컬 워커·부하테스트(GUI) 실행 스크립트 추가 (대화형)
기존 run_local_server.sh 스타일에 맞춘 대화형 .sh 두 개.

- run_local_worker.sh: 동시성(WORKER_CONCURRENCY)·Chrome 프로필(LPS_PROFILE_DIR)
  선택 후 워커 기동. 실행 중 워커 감지 시 교체 여부 확인. venv/config 자동 보장.
- run_loadtest_gui.sh: Locust 웹 UI(:8089) 기동. 워커 실행 중이면 경고(크롤 비용),
  API 미기동 시 PROCESS_COUNT 받아 백그라운드 기동(종료 시 함께 정리). 부하생성기 --processes.
- .gitignore(lps): .profiles/(Chrome 쿠키·cf_clearance 세션) 커밋 방지
- README·operations: 새 스크립트 안내로 갱신

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 08:33:11 +09:00
민헌
9c2432c818 feat(lps): process_count 기반 커넥션 풀 자동 산정 — 멀티코어 풀 오버서브스크립션 방지
connection_budget(기본 40)을 두고, 기동 시 process_count 에 맞춰
pool_size/max_overflow 를 역산: (pool+overflow)×2엔진×process_count ≤ budget.
워커를 늘려도 config 가 스스로 예산을 지켜 커넥션 고갈→요청 실패를 예방.

- config_models: MainDBConfig.connection_budget 추가
- server_configs: _autosize_pool(process_count 확정 후 산정) + _apply_pool_env_override
  (우선순위 = 명시 DB_POOL_SIZE > 자동 산정 > toml pool)
- web_main: 기동 로그에 실효 풀/총커넥션/예산 출력
- env: DB_CONNECTION_BUDGET override, docker-compose 에 knob 노출
- tests: test_pool_autosize 10케이스(예산 준수·분할비·비활성·infeasible 바닥)
- docs: operations 2-1 멀티코어/풀 섹션 + loadtest README 자동산정 표

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:59:05 +09:00
민헌
a63793daf0 test(lps): Locust API 부하 테스트 + 멀티코어 벤치 (풀 병목 발견·증명)
API(enqueue/조회, asyncpg I/O 바운드) 부하 테스트로 멀티코어 활용·한계 측정.

- loadtest/locustfile.py: enqueue(고유코드 write)+조회 가중 부하.
- loadtest/bench_multicore.sh: PROCESS_COUNT 1→N 자동 비교(--processes 로 부하생성기도 멀티프로세스).
- server_configs: PROCESS_COUNT / DB_POOL_SIZE / DB_MAX_OVERFLOW env override(코드·toml 수정 없이 튜닝).
- loadtest/README.md: 발견 문서화.

발견(11코어·1500users): 기본 풀(10/20)로 워커 늘리면 실패 폭증(1w=0 → 4w=10336) —
(pool+overflow)×2엔진×workers=240 > PG max_connections=100 커넥션 고갈(SQLAlchemy pool checkout 실패).
증명: 풀 8/4(96<100)로 PC=4 재실행 → RPS 1336→2705(2배), 실패 0. 코드는 멀티코어 활용 가능,
막는 건 풀 오버서브스크립션. 규칙: (pool+overflow)×2×process_count ≤ max_connections.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 23:45:25 +09:00
민헌
eba78dbbb9 docs(lps): README 최신화 — Dockerfile.worker·문서목록(배포/관측/크롤러논의)
폴더구조에 Dockerfile.worker, 문서 표에 Docker배포·관측·decision-openmarket-crawler 반영.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 23:32:52 +09:00
민헌
97e566f844 docs(lps): 운영 가이드 — 관측(readyz/ops/알림)·라이브 스모크·Docker 배포
P4/P5 반영: /readyz·/v1/lps/ops·워커 하트비트·임계 알림 env, LPS_LIVE 라이브 스모크,
docker compose 배포(헤드풀 Chromium+Xvfb·시크릿 env·프로필 볼륨·HEALTHCHECK).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 23:27:02 +09:00
민헌
eb1ff8fee1 test(lps): P5 브라우저 경로 스모크 — search 오케스트레이션 + 라이브 드리프트 감지
가장 취약·복잡한 경로(회전·재시도·차단감지)의 자동 테스트 공백을 메운다.

- mock 하니스(_MockPage/_MockCtx/_MockProxy)로 search() 를 결정론 테스트(브라우저 없이):
  정상 반환 / 차단→IP회전→복구 / 프록시전송오류→회전→복구 / 비프록시오류→실패(무회전) /
  차단 소진→blocked 실패. 5종.
- 라이브 스모크(각 어댑터 실제 사이트 검색→파싱): 셀렉터·안티봇 드리프트 감지. IP 의존·느려서
  기본 skip, LPS_LIVE=1 로 명시 실행(수동/야간). 네이버 스모크 통과 확인.

전체 96 passed, 5 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 23:25:42 +09:00
민헌
d6dd47e652 feat(lps): P4 관측·알림·워커 헬스 — readyz/ops + 하트비트/HEALTHCHECK + 임계 알림
프로덕션 운영 가시성. 행/좀비 워커 감지 + 큐/차단 지표 노출 + 임계 알림.

- API: /readyz(DB 도달성=readiness, 실패 503; /healthz=liveness와 구분).
  /v1/lps/ops(플랫 JSON): 큐 카운트 + oldest_pending_sec(큐지연) + dead_1h + stuck_running + blocks_1h.
- crud: JobQueue.ops()/ping(), BotDetectionLog.recent_count().
- worker: run_ops_monitor — 하트비트 파일 주기 갱신(Docker HEALTHCHECK 가 신선도로 행 워커 감지)
  + 임계(DEAD/차단/큐지연/stuck) 초과 시 WARN 로그 + (LPS_ALERT_WEBHOOK 있으면) Slack 호환 웹훅.
- Dockerfile.worker: HEALTHCHECK(하트비트 <120s). 임계·웹훅은 env(LPS_ALERT_*).
- 테스트: readyz/ops 2종.

검증: 컨테이너 healthy 판정, 하트비트 갱신, ops 스냅샷 정상. 91 tests.

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