Commit Graph

7 Commits

Author SHA1 Message Date
민헌
d1d1ed6ec5 feat(lps-admin): 3단계 — 몰별 확인 상태를 운영 화면에 노출
2단계에서 저장한 sources/partial 을 운영자가 볼 수 있게 한다. 운영자 목적은 **진단**이라
상태를 접지 않는다 — blocked(IP 회전으로 자동 회복)와 env_blocked(사람이 환경·설정을 고쳐야 함)를
뭉뚱그리면 회복될 일에 매달리거나 손봐야 할 설정을 방치하게 된다.

API
- /v1/lps/products, /v1/lps/products/{code}/history 둘 다 sources·partial 을 싣는다.
- 이력은 **시점마다** 싣는다. 최신 상태를 과거 시점의 몰별 표 옆에 붙이면 '그때도 막혔던 것처럼'
  보여 오해를 부른다 — 그래서 ProductItem 이 아니라 PricePoint 에 담았다.

화면
- lib/sourceState.ts: 상태별 라벨·색·설명·confirmed 를 한곳에. 미지의 상태가 와도 화면이 깨지지
  않는다(값 그대로 표시 + '모름' 취급). 색은 전부 @theme 토큰 참조(raw hex 금지).
- 상품 목록: partial 이면 '일부 확인 못함' 배지 + 툴팁에 어느 몰인지.
- 몰별 비교 카드 위: 몰별 상태·수집 건수·실패 사유 원문(툴팁). 가격표에 없는 몰이 **왜** 없는지를
  여기서 답한다 — by_mall 은 가격이 있는 몰만 담으므로 그 답이 여기밖에 없다.

검증: ASGI 직접 호출로 두 엔드포인트 응답 확인(한글 사유 포함), tsc 오류 없음.
테스트 3건 추가(목록 노출 / 시점별 상태가 각각 다르게 / 컬럼 추가 이전 옛 행 호환).
전체 292 passed. 진행 상황은 docs/result-states.md 4절.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 11:05:31 +09:00
민헌
8b9e81777c feat(lps): 알림 확장 — AlertManager 쿨다운·회복, DB풀 포화·소스별 장기실패 룰
기존 ops-monitor 는 임계 초과가 지속되면 30초마다 같은 웹훅을 반복 발송했고
(쿨다운 없음), 해소 여부도 알 수 없었다. 감시 항목도 큐 지표 4종뿐이었다.

- common/alerts.py AlertManager 신설: 룰 키별 상태 관리 — 발화 1회 +
  쿨다운(LPS_ALERT_COOLDOWN_MIN, 기본 30분)마다 리마인드, 해소 시 회복
  알림 1회. sender/clock 주입으로 네트워크·대기 없이 단위 테스트.
- 워커 ops-monitor 를 AlertManager 로 이관(기존 4룰 유지) + 신규 2룰:
  db_pool(풀 포화율 ≥ LPS_ALERT_POOL_PCT 90%) ·
  source_fail:<src>(최근 30분 시도 ≥ LPS_ALERT_SOURCE_FAIL_30M(5) & 성공 0
  — 쿼터 소진·셀렉터 드리프트·전면 차단 신호).
- DBSessionManager.pool_status(): 전 엔진 합산 checked_out/capacity/pct.
- SearchAdapter 에 시간 윈도우 성공/실패 카운터(recent_stats) — 누적
  카운터로는 '최근 30분 성공 0건'을 볼 수 없어 추가. 쿠팡(브라우저)·
  네이버(API) 성공/실패 지점에 배선.
- API 자체 풀 모니터: lifespan 백그라운드 태스크(run_pool_monitor) —
  대량 폴링으로 풀을 고갈시키는 주범이 API 자신일 수 있다.
  /v1/lps/ops 에 pool_checked_out/pool_capacity/pool_pct 노출(스모크 확인).
- 테스트 9건 추가(발화·쿨다운·회복·룰 독립·윈도우 카운터·풀 현황), 전체 135 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 16:42:12 +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
민헌
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
민헌
4863425ca5 refactor(lps): job_type 명칭 정리 — 출처 기반(레거시 negowiz 제거)
레거시 용어(negowiz)와 모호한 이름(new/single)을 출처 기반으로 변경.
우선순위 1~4는 유지, 기본값 single→manual.

- new_product(1) / manual(2, 기본) / partner(3) / batch(4)
- lps_service 우선순위 맵 + protocol 기본값/설명 + docs/api.md 표 + 테스트 갱신
- 전체 54/54

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 11:33:46 +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
민헌
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