1단계에서 만든 SourceState 가 job.result 에만 있어 화면까지 못 갔다. by_mall 은 **가격이 있는
몰만** 담으므로, 빠진 몰이 '거기엔 없더라'인지 '거기를 못 봤다'인지 구분할 자리가 없었다.
- price_history.sources (JSONB): 몰별 상태를 그대로 담는다.
{"naver": {"state": "matched", "count": 40}, "coupang": {"state": "blocked", "error": "..."}}
열린 스키마라 몰이 늘거나 근거를 덧붙여도 마이그레이션이 필요 없다(by_mall 과 같은 방침).
- price_history.partial (bool): 결과가 완전한가. sources 에서 유도 가능하지만 컬럼으로 둔다 —
소비자가 '어떤 상태가 확인된 것인가'라는 판단 규칙까지 알아야 하면 **상태 정의가 두 곳으로
흩어진다**. 판단은 LPS 가 끝내고 소비자(negodata·lps-admin)는 사실 하나만 읽는다.
- 부분 인덱스 ix_price_history_partial — partial=true 행만 담아 작게 유지(운영 점검·알림용).
- _record_history 가 per_source 를 받아 partial 을 계산해 기록한다. 네거티브 캐시 히트 경로는
sources 없이 남긴다(부분 결과는 애초에 캐시하지 않으므로 항상 확정).
- 마이그레이션: postgres-init/dbeaver/7_lps_source_state_dbeaver.sql (재실행 안전, **운영 적용 필요**)
검증(로컬 실 DB): 쿠팡 차단 vs 쿠팡 0건은 by_mall 이 둘 다 ['naver'] 로 같지만
partial(true/false)·sources.coupang.state(blocked/empty)가 두 경우를 갈라낸다.
JSONB 는 ensure_ascii=False 로 한글 사유가 깨지지 않는 것도 테스트로 고정.
테스트 3건 추가, 전체 289 passed. 진행 상황은 docs/result-states.md 4절.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
33 lines
2.2 KiB
SQL
33 lines
2.2 KiB
SQL
-- LPS 몰별 확인 상태 — **lps_db 에 연결해서 실행**. 재실행 안전(IF NOT EXISTS).
|
||
--
|
||
-- 왜 필요한가: '그 몰에 더 싼 게 없었다'와 '그 몰이 막혀서 못 봤다'가 지금 화면에서 똑같이
|
||
-- '–' 로 보인다. 사용자는 앞쪽으로 읽지만 실제로는 뒤쪽일 수 있다 — 안 본 걸 없다고 말하는 셈이다.
|
||
-- 크롤러는 그 차이를 이미 알고 있는데(SourceState), 담을 자리가 없어 화면까지 못 갔다.
|
||
--
|
||
-- 상태 정의와 표기 규칙은 lps/docs/result-states.md 가 소스다.
|
||
|
||
-- 몰별 확인 상태 — {"naver": {"state": "matched", "count": 40},
|
||
-- "coupang": {"state": "blocked", "error": "AdapterError: ..."}}
|
||
-- 열린 스키마(JSONB)로 둔다: 몰이 늘거나 상태에 근거를 덧붙여도 마이그레이션이 필요 없다.
|
||
-- state 값: matched / no_match / empty / blocked / env_blocked / unavailable / skipped
|
||
ALTER TABLE price_history ADD COLUMN IF NOT EXISTS sources JSONB;
|
||
|
||
-- 결과가 **완전한가**. true = 못 본 몰이 있어 이 값이 최종이 아니다.
|
||
-- sources 에서 유도할 수 있지만 굳이 컬럼으로 둔다 — 소비자(negodata)가 '어떤 상태가 확인된
|
||
-- 것인가'라는 판단 규칙까지 알아야 하면 상태 정의가 두 곳으로 흩어진다. 판단은 LPS 가 하고,
|
||
-- 소비자는 사실 하나만 읽게 한다.
|
||
ALTER TABLE price_history ADD COLUMN IF NOT EXISTS partial BOOLEAN NOT NULL DEFAULT FALSE;
|
||
|
||
-- '확인 못한 결과'만 빠르게 뽑기 위한 부분 인덱스(운영 점검·알림용).
|
||
-- 전체가 아니라 partial=true 행만 담아 인덱스가 작게 유지된다.
|
||
CREATE INDEX IF NOT EXISTS ix_price_history_partial
|
||
ON price_history (triggered_at) WHERE partial;
|
||
|
||
-- ── 검증 ──────────────────────────────────────────────────────────────────────
|
||
SELECT column_name, data_type,
|
||
CASE WHEN column_name IN ('sources','partial') THEN '이번 추가' ELSE '' END AS note
|
||
FROM information_schema.columns
|
||
WHERE table_name = 'price_history'
|
||
AND column_name IN ('outcome','by_mall','sources','partial')
|
||
ORDER BY column_name;
|