From 14bcf1481b212bc3c66f21e3785e8d7f0a982624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Fri, 7 Aug 2026 09:38:11 +0900 Subject: [PATCH] =?UTF-8?q?fix(negodata):=20=EA=B2=80=EC=83=89=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=EB=A5=BC=20'=EB=B3=80=EB=8F=99=20=EC=97=86=EC=9D=8C'?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=ED=91=9C=EC=8B=9C=ED=95=98=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EA=B5=AC=20=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LPS 가 outcome='error'(모든 소스 차단)로 내려주는 경우가 화면에서 '변동 없음'으로 보였다. 사용자는 '검색은 잘 됐는데 더 싼 게 없구나'로 읽지만, 실제로는 **가격을 확인조차 못 한** 것이다. 뜻이 정반대라 오해가 크다. 원인: success_yn=false 를 전부 notfound 로 묶었고, notfound 와 done 이 같은 문구('변동 없음')를 쓴다. fail_reason 은 이미 API·프론트 타입까지 내려오는데 쓰이지 않고 있었다. → fail_reason='error' 면 failed 로 분류한다. 화면은 '탐색 실패'가 되고, 실패 사유가 함께 뜨며, 재검색 대상(failedIds)에 포함돼 사용자가 다시 시도할 수 있다 — 확인 못 한 건 재시도가 맞다. 'not_found'(진짜 못 찾음)는 기존대로 '변동 없음'을 유지한다. LPS 변경 없음(이미 내려오는 값을 쓴 것). ItemState 주석도 실제 의미에 맞게 정정. tsc: 이 파일 오류 없음(기존 xlsx 미설치 오류는 무관). > 폴더 관례상 negodata 는 인수인계 대상이나, 이번 건은 사용자 요청으로 예외 적용. Co-Authored-By: Claude Opus 5 --- .../features/products/components/PriceUpdateModal.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/negodata/front/src/features/products/components/PriceUpdateModal.tsx b/negodata/front/src/features/products/components/PriceUpdateModal.tsx index 4939418..21a18e1 100644 --- a/negodata/front/src/features/products/components/PriceUpdateModal.tsx +++ b/negodata/front/src/features/products/components/PriceUpdateModal.tsx @@ -53,8 +53,8 @@ const toMallPrices = (byMall: LowestPriceEntryByMall): MallPrices => { type ItemState = | { kind: 'pending' } // 접수됨 — 결과 대기 | { kind: 'done'; price: number; malls: MallPrices } - | { kind: 'notfound'; malls: MallPrices } // 동일 상품 판정 실패(기존 값 유지) - | { kind: 'failed'; reason?: string } // 요청 자체가 접수되지 않음 — 재시도 대상 + | { kind: 'notfound'; malls: MallPrices } // 검색은 됐으나 같은 상품이 없었음(기존 값 유지) + | { kind: 'failed'; reason?: string } // 접수 실패 또는 검색 실패(사이트 차단) — 재시도 대상 | { kind: 'timeout' } // 폴링 상한 초과 — 서버는 계속 검색 중 | { kind: 'stopped' }; // 사용자가 지켜보기를 중단 — 서버는 계속 검색 중 @@ -256,6 +256,13 @@ export function PriceUpdateModal({ open, products, selectedIds, onDone, onClose // by_mall 이 비어 오는 옛 이력 대비 — 최소한 대표 최저가는 보이도록 폴백을 채운다. if (Object.keys(malls).length === 0) malls.etc = fresh.lp_price; setItem(id, { kind: 'done', price: fresh.lp_price, malls }); + } else if (fresh.fail_reason === 'error') { + // 검색 자체가 실패한 경우(모든 소스 차단). '못 찾음'과 뜻이 완전히 다르다 — + // 상품이 없다는 게 아니라 **확인을 못 했다**는 뜻이라, 재시도 대상으로 분류한다. + // (LPS 가 outcome='error' 로 내려준다. '변동 없음'으로 뭉뚱그리면 사용자는 + // '검색은 됐는데 더 싼 게 없구나'로 오해한다) + // 문구는 '탐색 실패 사유: {reason}' 형태로 붙는다 — 사유만 간결하게 담는다. + setItem(id, { kind: 'failed', reason: '사이트 차단으로 가격을 확인하지 못했습니다 (잠시 후 다시 시도해 주세요)' }); } else { setItem(id, { kind: 'notfound', malls }); }