fix(negodata): '일부 확인 못함'을 결과 열에도 표시 — 몰 칸만 고쳐 놓친 부분

실화면 확인에서 드러났다. 쿠팡 칸은 '확인 못함'으로 잘 나오는데 결과 열이 '변동 없음'이었다.
못 본 몰이 있는데 '변동 없음'이라 하면 사용자는 여전히 "이 가격이 최종이구나"로 읽는다 —
그 몰에 더 싼 값이 있었을 수 있어 이 결과는 최종이 아니다.

docs/result-states.md 3-2절에 이미 정의해 둔 표기인데(| 일부 확인 못함 | partial |)
몰 칸만 구현하고 결과 열을 빠뜨렸다.

- 결과 열: unconfirmed 가 있으면 '일부 확인 못함'(툴팁에 어느 몰인지). '탐색 성공'(더 싸게 찾음)은
  그대로 우선한다 — 값이 실제로 갱신된 건 사실이므로.
- 재검색 대상: 못 본 몰이 있으면 **완료로 치지 않는다**. 가격이 갱신됐어도 다음 '다시 검색'의
  기본 대상으로 남긴다 — 그게 '다시 시도할 이유가 생긴다'의 실제 동작이다.

tsc 오류 없음. 실데이터로 확인(섬유유연제: 네이버 4,820 matched · 쿠팡 blocked/env_blocked).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
민헌 2026-08-07 14:29:15 +09:00
parent 6ddb2c4504
commit 6e401615eb

View File

@ -270,7 +270,9 @@ export function PriceUpdateModal({ open, products, selectedIds, onDone, onClose
if (fresh.success_yn && fresh.lp_price != null) { if (fresh.success_yn && fresh.lp_price != null) {
found += 1; found += 1;
const prev = prevPriceOf(id); 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 이 비어 오는 옛 이력 대비 — 최소한 대표 최저가는 보이도록 폴백을 채운다. // by_mall 이 비어 오는 옛 이력 대비 — 최소한 대표 최저가는 보이도록 폴백을 채운다.
if (Object.keys(malls).length === 0) malls.etc = fresh.lp_price; if (Object.keys(malls).length === 0) malls.etc = fresh.lp_price;
setItem(id, { kind: 'done', price: fresh.lp_price, malls, unconfirmed }); 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) { switch (s.kind) {
case 'done': case 'done':
case 'notfound': case 'notfound':
// 못 본 몰이 있으면 '변동 없음'이라 말하면 안 된다 — 그 몰에 더 싼 값이 있었을 수 있어
// 이 결과는 최종이 아니다. 사용자에게 필요한 건 어느 몰이 왜 막혔는지가 아니라
// '결과가 완전하지 않다'는 사실 하나다(재시도할 이유가 생긴다).
if (s.unconfirmed?.length) {
return (
<span className="text-amber-600" title={`${s.unconfirmed.join(', ')}을(를) 확인하지 못해 최종 결과가 아닙니다`}>
</span>
);
}
// 검색은 정상 수행됐으나 기존보다 낮은 가격이 없었음(미발견 포함) — 값이 안 바뀐 상태. // 검색은 정상 수행됐으나 기존보다 낮은 가격이 없었음(미발견 포함) — 값이 안 바뀐 상태.
return <span className="text-muted-foreground"> </span>; return <span className="text-muted-foreground"> </span>;
case 'failed': case 'failed':