Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apps/web/src/components/inspect/DeepInspectWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
implausiblyLowOfferIds,
plausibleOffers,
} from '@repo/shared-data/rules';
import { isOfferUnavailable } from '@/lib/product-market-stats';
import { productImageUrl } from '@/lib/rate-domain-core';

const INSPECT_CONTROL_CHARS_RE = /\p{Cc}+/gu;
Expand Down Expand Up @@ -184,8 +185,13 @@ export default function DeepInspectWorkspace({
};

const master = result?.masterProduct ?? null;
// A sold-out listing is never the fallback match (DOS/Rate#1177).
const matchedOffer =
result?.matchedOffer ?? plausibleOffers(result?.allOffers ?? [])[0] ?? null;
result?.matchedOffer ??
plausibleOffers(
(result?.allOffers ?? []).filter((o) => !isOfferUnavailable(o))
)[0] ??
null;
const discount = result?.discountAnalysis ?? null;
// Judged as the product page's offers table judges the row (#797).
const implausibleOfferIds = implausiblyLowOfferIds(result?.allOffers ?? []);
Expand Down
12 changes: 8 additions & 4 deletions apps/web/src/lib/rate-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3914,10 +3914,11 @@ export async function inspectProductOrShopUrl(
getMasterProductPriceHistory(masterProduct.id, offers),
getMasterProductReviewSummary(masterProduct.id, reviews),
]);
// No bait-price listing as the fallback match (DOS/Rate#797).
// No bait-price or sold-out listing as the fallback match
// (DOS/Rate#797, #1177).
const matchedOffer =
offers.find((o) => o.id === matchedProd.id) ||
plausibleOffers(offers)[0] ||
plausibleOffers(offers.filter((o) => !isOfferUnavailable(o)))[0] ||
null;
const discountAnalysis = matchedOffer
? priceHistory.byOffer[matchedOffer.id] || null
Expand Down Expand Up @@ -4023,8 +4024,11 @@ export async function inspectProductOrShopUrl(
);
if (byShopMerchant) return byShopMerchant;
}
// The cheapest plausible offer, never a bait-price listing (DOS/Rate#797).
return plausibleOffers(offers)[0] || null;
// The cheapest plausible in-stock offer, never a bait-price or sold-out
// listing (DOS/Rate#797, #1177).
return (
plausibleOffers(offers.filter((o) => !isOfferUnavailable(o)))[0] || null
);
};

// 3. Internal slug or exact master product slug match (including slugified URL keyword)
Expand Down
204 changes: 204 additions & 0 deletions apps/web/src/server/rate/__tests__/core-rate-architecture.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13674,6 +13674,210 @@ async function runCoreArchitectureSuite() {
);
}

// -------------------------------------------------------------------------
// 126l. Extension verdict, cheaper-elsewhere and fallback match skip
// sold-out listings (#1177)
// -------------------------------------------------------------------------
{
const { evaluateExtensionPriceVerdict, lookupExtensionProduct } =
await import('@/server/rate/extension');

const offer1177 = (
id: string,
price: number,
availability: string | null
) =>
({
id,
platform: 'shopee',
url: `https://shopee.vn/i.1177.${id}`,
price_current: price,
price_original: null,
availability,
merchant: {
id: `m-${id}`,
name: `Shop ${id}`,
risk_score: 5,
is_official: true,
},
}) as never;
const offers1177 = [
offer1177('fpt-sold', 2_694_000, 'out_of_stock'),
offer1177('fpt-gone', 3_032_000, 'discontinued'),
offer1177('tgdd', 3_140_000, 'in_stock'),
offer1177('cps', 3_690_000, null),
];
const verdict1177 = evaluateExtensionPriceVerdict({
isAccessory: false,
accessoryReason: null,
isAmbiguousVariant: false,
observedPrice: 3_140_000,
observedOriginalPrice: null,
offers: offers1177,
observedOfferId: 'tgdd',
priceHistory: null,
});
assert.equal(verdict1177.marketMinPrice, 3_140_000);
assert.equal(verdict1177.marketMaxPrice, 3_690_000);
assert.equal(verdict1177.cheaperOfferCount, 0);
assert.equal(verdict1177.maxSavingsAmount, 0);
// A sold-out listing's own page is judged against the in-stock market.
const ownSold1177 = evaluateExtensionPriceVerdict({
isAccessory: false,
accessoryReason: null,
isAmbiguousVariant: false,
observedPrice: 3_032_000,
observedOriginalPrice: null,
offers: offers1177,
observedOfferId: 'fpt-gone',
priceHistory: null,
});
assert.equal(ownSold1177.marketMinPrice, 3_140_000);
assert.equal(ownSold1177.cheaperOfferCount, 0);
assert.notEqual(ownSold1177.badgeLabel, 'GiΓ‘ thαΊ₯p bαΊ₯t thường');
// From the dearest in-stock page, only the in-stock store is cheaper.
const soldPage1177 = evaluateExtensionPriceVerdict({
isAccessory: false,
accessoryReason: null,
isAmbiguousVariant: false,
observedPrice: 3_690_000,
observedOriginalPrice: null,
offers: offers1177,
observedOfferId: 'cps',
priceHistory: null,
});
assert.equal(soldPage1177.cheaperOfferCount, 1);
assert.equal(soldPage1177.maxSavingsAmount, 550_000);

const origFetch1177 = globalThis.fetch;
const itemIds1177: Record<string, number> = {
'fpt-sold': 117711,
'fpt-gone': 117712,
tgdd: 117713,
cps: 117714,
};
const viewRow1177 = (id: string, price: number) => ({
id,
cluster_id: 'master-1177',
merchant_id: `m-${id}`,
platform: 'shopee',
title: 'Samsung Galaxy Buds3 Pro',
price_current: price,
stock: 0,
url: `https://shopee.vn/product-i.1177.${itemIds1177[id]}`,
threat_risk_score: 5,
threat_source: 'dosafe',
dosafe_risk_score: 5,
review_risk_score: 5,
review_risk_breakdown: { total_reviews: 3 },
});
const json1177 = (body: unknown) =>
new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
try {
globalThis.fetch = (async (input: RequestInfo | URL) => {
const url = new URL(
typeof input === 'string'
? input
: input instanceof URL
? input.toString()
: input.url
);
const firstPage = Number(url.searchParams.get('offset') ?? 0) === 0;
const select = url.searchParams.get('select') ?? '';
if (
url.pathname.endsWith('/products') &&
url.searchParams.has('platform_item_id')
) {
return json1177([
{ cluster_id: 'master-1177', platform_item_id: '1177_117714' },
]);
}
if (
url.pathname.endsWith('/master_products') &&
url.searchParams.get('id') === 'eq.master-1177'
) {
return json1177({
id: 'master-1177',
slug: 'samsung-galaxy-buds3-pro',
name: 'Samsung Galaxy Buds3 Pro',
brand: 'Samsung',
model: 'Galaxy Buds3 Pro',
images: [],
key_specs: {},
specifications: {},
min_price: 3_140_000,
max_price: 3_690_000,
category_id: 'cat-earbuds',
});
}
if (url.pathname.endsWith('/v_products_with_merchant_trust')) {
return json1177(
firstPage
? [
viewRow1177('fpt-sold', 2_694_000),
viewRow1177('fpt-gone', 3_032_000),
viewRow1177('tgdd', 3_140_000),
viewRow1177('cps', 3_690_000),
]
: []
);
}
if (
url.pathname.endsWith('/products') &&
select === 'id,availability'
) {
return json1177(
firstPage
? [
{ id: 'fpt-sold', availability: 'out_of_stock' },
{ id: 'fpt-gone', availability: 'discontinued' },
{ id: 'tgdd', availability: 'in_stock' },
{ id: 'cps', availability: 'in_stock' },
]
: []
);
}
return json1177([]);
}) as typeof fetch;

// On the CellphoneS listing: the sold-out FPT listings are not cheaper.
const onCps1177 = await lookupExtensionProduct({
platform: 'shopee',
externalId: '1177_117714',
mode: 'pill',
});
assert.equal(onCps1177.masterProduct?.id, 'master-1177');
assert.equal(onCps1177.matchedOffer?.id, 'cps');
assert.deepEqual(
onCps1177.topCheaperOffers.map((o) => o.id),
['tgdd']
);
assert.equal(onCps1177.priceAssessment.marketMinPrice, 3_140_000);

// No listing matches: the fallback is the cheapest in-stock offer.
const unmatched1177 = await lookupExtensionProduct({
platform: 'shopee',
externalId: '1177_117799',
mode: 'pill',
});
assert.equal(unmatched1177.matchedOffer?.id, 'tgdd');
assert.ok(
unmatched1177.topCheaperOffers.every(
(o) => o.id !== 'fpt-sold' && o.id !== 'fpt-gone'
)
);
} finally {
globalThis.fetch = origFetch1177;
}

console.log(
' βœ… [126l/135] Extension verdict, cheaper-elsewhere and fallback match skip sold-out listings (#1177) passed'
);
}

// -------------------------------------------------------------------------
// 127. Rate Server Index & Extension Entity, Slug, and Category Bounds (#1029)
// -------------------------------------------------------------------------
Expand Down
24 changes: 16 additions & 8 deletions apps/web/src/server/rate/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
type MerchantSummary,
type ReviewAnalysisSummary,
} from '../../lib/rate-api';
import { isOfferUnavailable } from '../../lib/product-market-stats';
import {
computeProductStandingInCategory,
type ProductLeaderboardBadgeInfo,
Expand Down Expand Up @@ -606,7 +607,11 @@ export function evaluateExtensionPriceVerdict(params: {
observedOfferId,
priceHistory,
} = safeParams;
const safeOffers = Array.isArray(offers) ? offers : [];
// Sold-out and discontinued listings keep their last price; they set no
// market reference and are no cheaper option (DOS/Rate#1177).
const safeOffers = (Array.isArray(offers) ? offers : []).filter(
(o) => !isOfferUnavailable(o)
);

if (isAccessory) {
return {
Expand Down Expand Up @@ -1223,15 +1228,17 @@ export async function lookupExtensionProduct(

// Fetch offers, price history, and reviews for matched master product
const offers = await getMasterProductOffers(matchedMaster.id);
// Without a URL match, fall back to the cheapest plausible offer, never a
// bait-price listing (DOS/Rate#797).
// Without a URL match, fall back to the cheapest plausible in-stock offer,
// never a bait-price or sold-out listing (DOS/Rate#797, #1177).
const inStockOffers = offers.filter((o) => !isOfferUnavailable(o));
const urlMatchedOffer =
offers.find(
(o) =>
(canonicalUrl && o.url.includes(canonicalUrl)) ||
doesOfferUrlMatchCandidateIds(o.url, candidateItemIds)
) || null;
const matchedOffer = urlMatchedOffer || plausibleOffers(offers)[0] || null;
const matchedOffer =
urlMatchedOffer || plausibleOffers(inStockOffers)[0] || null;

if (
matchedOffer &&
Expand Down Expand Up @@ -1305,10 +1312,11 @@ export async function lookupExtensionProduct(
: null;

const isAmbiguous = matchStatus === 'ambiguous_variant';
// The market reference and "cheaper elsewhere" leave bait-price listings
// out; the matched offer above still comes from every offer, so a shopper
// on a bait listing sees it judged against the real market (DOS/Rate#797).
const marketOffers = plausibleOffers(offers);
// The market reference and "cheaper elsewhere" leave bait-price and
// sold-out listings out; the URL-matched offer above still comes from every
// offer, so a shopper on such a listing sees it judged against the real
// market (DOS/Rate#797, #1177).
const marketOffers = plausibleOffers(inStockOffers);
const priceAssessment = evaluateExtensionPriceVerdict({
isAccessory: false,
accessoryReason: null,
Expand Down
Loading