From 6e401615eb83586e7cdaa1ab72de63a73e45af50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AF=BC=ED=97=8C?= Date: Fri, 7 Aug 2026 14:29:15 +0900 Subject: [PATCH] =?UTF-8?q?fix(negodata):=20'=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EB=AA=BB=ED=95=A8'=EC=9D=84=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=97=B4=EC=97=90=EB=8F=84=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?=E2=80=94=20=EB=AA=B0=20=EC=B9=B8=EB=A7=8C=20=EA=B3=A0=EC=B3=90?= =?UTF-8?q?=20=EB=86=93=EC=B9=9C=20=EB=B6=80=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 실화면 확인에서 드러났다. 쿠팡 칸은 '확인 못함'으로 잘 나오는데 결과 열이 '변동 없음'이었다. 못 본 몰이 있는데 '변동 없음'이라 하면 사용자는 여전히 "이 가격이 최종이구나"로 읽는다 — 그 몰에 더 싼 값이 있었을 수 있어 이 결과는 최종이 아니다. docs/result-states.md 3-2절에 이미 정의해 둔 표기인데(| 일부 확인 못함 | partial |) 몰 칸만 구현하고 결과 열을 빠뜨렸다. - 결과 열: unconfirmed 가 있으면 '일부 확인 못함'(툴팁에 어느 몰인지). '탐색 성공'(더 싸게 찾음)은 그대로 우선한다 — 값이 실제로 갱신된 건 사실이므로. - 재검색 대상: 못 본 몰이 있으면 **완료로 치지 않는다**. 가격이 갱신됐어도 다음 '다시 검색'의 기본 대상으로 남긴다 — 그게 '다시 시도할 이유가 생긴다'의 실제 동작이다. tsc 오류 없음. 실데이터로 확인(섬유유연제: 네이버 4,820 matched · 쿠팡 blocked/env_blocked). Co-Authored-By: Claude Opus 5 --- .../products/components/PriceUpdateModal.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/negodata/front/src/features/products/components/PriceUpdateModal.tsx b/negodata/front/src/features/products/components/PriceUpdateModal.tsx index aeb0293..79ee979 100644 --- a/negodata/front/src/features/products/components/PriceUpdateModal.tsx +++ b/negodata/front/src/features/products/components/PriceUpdateModal.tsx @@ -270,7 +270,9 @@ export function PriceUpdateModal({ open, products, selectedIds, onDone, onClose if (fresh.success_yn && fresh.lp_price != null) { found += 1; const prev = prevPriceOf(id); - if (prev <= 0 || fresh.lp_price < prev) succeeded.add(id); // 값이 실제로 갱신된 행 + // 값이 실제로 갱신된 행. 단 **못 본 몰이 있으면 완료로 치지 않는다** — + // 그 몰에 더 싼 값이 있었을 수 있어, 다음 '다시 검색'의 기본 대상으로 남겨둔다. + if ((prev <= 0 || fresh.lp_price < prev) && unconfirmed.length === 0) succeeded.add(id); // by_mall 이 비어 오는 옛 이력 대비 — 최소한 대표 최저가는 보이도록 폴백을 채운다. if (Object.keys(malls).length === 0) malls.etc = fresh.lp_price; setItem(id, { kind: 'done', price: fresh.lp_price, malls, unconfirmed }); @@ -354,6 +356,16 @@ export function PriceUpdateModal({ open, products, selectedIds, onDone, onClose switch (s.kind) { case 'done': case 'notfound': + // 못 본 몰이 있으면 '변동 없음'이라 말하면 안 된다 — 그 몰에 더 싼 값이 있었을 수 있어 + // 이 결과는 최종이 아니다. 사용자에게 필요한 건 어느 몰이 왜 막혔는지가 아니라 + // '결과가 완전하지 않다'는 사실 하나다(재시도할 이유가 생긴다). + if (s.unconfirmed?.length) { + return ( + + 일부 확인 못함 + + ); + } // 검색은 정상 수행됐으나 기존보다 낮은 가격이 없었음(미발견 포함) — 값이 안 바뀐 상태. return 변동 없음; case 'failed':