Commit Graph

7 Commits

Author SHA1 Message Date
민헌
3a2c6e43d4 feat(lps): price_history 몰별 최저가 스냅샷(by_mall JSONB)
몰이 열린 집합이라 와이드 컬럼(gmarket_*, st11_* …) 대신 JSONB 한 컬럼으로 담는다
— 몰 추가 시 마이그레이션 0. naver/coupang/final 3선 컬럼은 그래프 하위호환 유지.

- models: price_history.by_mall JSONB 추가
- crud: record/list 에 by_mall 왕복(json.dumps + CAST jsonb)
- handler: _price_snapshot 에 summarize_by_mall 적재, final 은 소스무관 전체 최저로
- protocol/service: history API 응답에 by_mall 노출
- migrations/2026-07-09: 기존 dev DB 동기화용 ALTER(추적 파일)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:15:58 +09:00
민헌
5906acc48a feat(lps): 최저가 이력(price_history) — 트리거 기반 시계열 그래프
같은 상품 반복 검색 시 최저가를 스냅샷으로 적재 → 네이버/쿠팡/최종 3개 선 그래프.
배치 아님(조회된 상품만, 실제 검색 시각에 기록) — 트래픽/리소스 절약.

- price_history 테이블: product_code·triggered_at(X축)·naver/coupang/final 최저가+상세·outcome
- crud/price_history: record() + list_by_product(시각 오름차순)
- handler: AI 매칭 후 소스별 min + 전체 min 스냅샷 기록(_price_snapshot).
  found/not_found 기록, 네거티브 캐시 히트·기술실패는 미기록
- API: GET /v1/lps/products/{product_code}/history → 그래프 데이터(시각 오름차순)
- tests: 스냅샷 계산/기록·조회/핸들러 기록규칙/API → 전체 54/54

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:13:28 +09:00
민헌
bca8032b79 feat(lps): 봇 감지 시 IP 회전+재시도 + 감지 이력 기록
쿠팡이 봇으로 감지(Akamai 차단 페이지)하면, 같은 IP로 재시도하던 것을
'브라우저 끄고 새 IP로 켜서 재시도'로 전환. 감지 패턴도 축적한다.

- DecodoProxy.rotate(): 시간창 무관 즉시 다음 포트(=새 IP). current_port 노출
- coupang/adapter: 0건+차단마커 감지 시 → 감지기록 → proxy.rotate()+강제재기동 →
  인라인 재시도(max_block_retries=1). IP당 요청수(ip_request_no) 추적, 재기동 시 리셋
- bot_detection 테이블 + crud: source/query/ip_request_no/proxy_port/elapsed_sec/marker/
  headless/html_len 기록 → 'IP당 몇 요청 만에 감지되나' 분석 가능
- worker_main: on_detect=BotDetectionLog.record 주입
- 로그: [coupang][BOT-DETECTED] ip_req#N port=... elapsed=...s marker=...
- tests: proxy rotate 즉시회전 + 감지기록 CRUD → 전체 47/47

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:04:07 +09:00
민헌
d54dee4f24 feat(lps): 재시도/못찾음 완결 로직 — 재정제 루프 + not_found + 네거티브 캐시
'못 찾음'을 유한하게 종료. 두 재시도 축을 분리(기술=큐 attempts/백오프,
검색어=refine 라운드). not_found 는 정상 종료(DONE)지 dead-letter 아님.

- ai/keyword: LLM 검색어 생성(정밀/광역) — 원본 0매칭 시에만 지연 호출(비용 절약)
- handler: 한정 재정제 루프(원본→정밀→광역, max_rounds=3) + 명시적 outcome(found/not_found)
  · 0매칭+소스정상 → 다음 라운드, 0매칭+기술실패 → raise(큐 재시도)
  · 라운드 소진 → not_found + 네거티브 캐시 기록
- negative_cache: search_negative 테이블 + TTL(24h) upsert — 같은 상품 재요청 재검색 차단
- worker_main: OPENAI 있으면 judge+keyword_gen ON, neg_cache 상시
- tests: 재정제/not_found/캐시히트/기술실패/캐시CRUD 11건 → 전체 39/39
- 라이브: 없는상품 3라운드→not_found(30s), 재요청 캐시히트(0.00s)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 09:12:47 +09:00
민헌
bebb6a71e6 feat(lps): 검색 API 라우터 — 요청 적재(enqueue) + 상태/큐 통계 조회
커머스 검색요청 계약을 프레임워크(protocol+service+RemoveNoneResponse)로 재구성.
실제 검색은 워커가 큐에서 꺼내 수행하도록 API 는 적재까지만 담당(비동기 분리).

- POST /v1/lps/search: 상품 리스트 → 상품별 SEARCH 잡 적재, product_code 로 활성 중복 방지, job_type→우선순위 매핑
- GET /v1/lps/jobs/{job_id}: 잡 상태/시도/결과 조회
- GET /v1/lps/queue/stats: 상태별 카운트(모니터링)
- protocol/lps_service 추가, job_crud.get() 단건조회, enums LPS_JOB_NOT_FOUND
- tests: 적재/중복/상태/미존재/통계 5건 (ASGI 클라이언트 + 실 lps_db) → 전체 16/16

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 16:49:28 +09:00
민헌
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
민헌
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