diff --git a/apps/importer/src/shopee/__tests__/entity-matcher.test.ts b/apps/importer/src/shopee/__tests__/entity-matcher.test.ts index 55785b15..e6cdb7c5 100644 --- a/apps/importer/src/shopee/__tests__/entity-matcher.test.ts +++ b/apps/importer/src/shopee/__tests__/entity-matcher.test.ts @@ -280,6 +280,39 @@ async function runEntityMatcherSuite() { detectProductCondition('Điện thoại Samsung Galaxy S24 Ultra 256GB', 'Máy cũ, pin 90%'), 'used' ); + // A new device's title offering a trade-in in any order stays new (DOS/Rate#1430). + for (const title of [ + 'Samsung Galaxy S24 Ultra 256GB - Trợ giá lên đời máy cũ', + 'Samsung Galaxy S24 Ultra 256GB Lên đời máy cũ trợ giá 2 triệu', + 'Samsung Galaxy S24 Ultra 256GB Hỗ trợ đổi máy cũ', + 'iPhone 16 Pro Max 256GB Chính hãng VN/A - Đổi máy cũ', + 'iPhone 16 Pro Max 256GB Trợ giá máy cũ 1 triệu', + 'iPhone 16 Pro Max 256GB Thu cũ lên đời', + 'iPhone 16 Pro Max 256GB Máy cũ lên đời trợ giá', + 'iPhone 16 Pro Max 256GB Lên đời từ điện thoại cũ', + 'iPhone 16 Pro Max 256GB Thu đt cũ đổi mới', + 'iPhone 16 Pro Max 256GB Thu mua giá cao máy cũ', + 'iPhone 16 Pro Max 256GB Thu mua lại máy cũ', + 'iPhone 16 Pro Max 256GB Trợ giá 2 triệu cho máy cũ', + 'iPhone 16 Pro Max 256GB Lên đời - máy cũ trợ giá', + 'iPhone 16 Pro Max 256GB Lên-đời máy cũ', + ]) { + assert.equal(detectProductCondition(title), 'new', `${title} is a new device`); + } + // A real used listing stays used. + for (const title of [ + 'Điện thoại iPhone 12 Pro 128GB máy cũ đẹp', + 'iPhone 11 64GB Máy cũ giá rẻ bảo hành 3 tháng', + 'Samsung Galaxy S21 điện thoại cũ đã qua sử dụng', + 'Thanh lý máy cũ iPhone XS Max', + 'iPhone 13 cũ - máy cũ zin, trợ giá ship', + // A warranty swap is not a trade-in, and a bare "hỗ trợ" is not a trade-in verb. + 'iPhone 11 64GB máy cũ đổi mới trong 30 ngày', + 'iPhone 12 Pro hỗ trợ máy cũ', + ]) { + assert.equal(detectProductCondition(title), 'used', `${title} is a used device`); + } + assert.equal(detectProductCondition('Điện thoại cũ đổi mới 99%'), 'like_new_99'); for (const title of [ 'Tai nghe Apple AirPods Pro 2nd Gen USB-C Chính hãng VN/A', 'Apple iPhone SE 2nd generation 64GB', diff --git a/apps/importer/src/shopee/product-normalizer.ts b/apps/importer/src/shopee/product-normalizer.ts index 166d3b2a..5321efaf 100644 --- a/apps/importer/src/shopee/product-normalizer.ts +++ b/apps/importer/src/shopee/product-normalizer.ts @@ -155,9 +155,30 @@ export class NonNewListingError extends Error { } } -/** A trade-in offer for the buyer's old device, which says nothing about the listing's own. */ -const TRADE_IN_PHRASE = - /(?:thu|đổi)\s*(?:mua\s*)?(?:(?:máy|điện\s*thoại)\s*)?cũ\s*(?:lên\s*đời|(?:lấy|đổi)\s*(?:(?:máy|điện\s*thoại)\s*)?mới)|thu\s*(?:mua\s*)?(?:máy|điện\s*thoại)\s*cũ/gi; +/** + * A trade-in offer for the buyer's old device, which says nothing about the listing's own: + * a trade-in verb right before "(máy|điện thoại) cũ" ("thu mua máy cũ", "lên đời máy cũ", + * "trợ giá máy cũ", "hỗ trợ đổi máy cũ"), or "cũ" right before "lên đời" or "đổi/lấy mới" + * ("thu cũ đổi mới", "máy cũ lên đời"). A few filler words or an amount ("2 triệu") may sit + * between verb and device, and a hyphen, colon or comma may separate the words. "Mới" is not + * a trade-in when a number or a warranty term follows it ("đổi mới 99%", "đổi mới trong 30 + * ngày"), and a bare "hỗ trợ" is not a trade-in verb ("hỗ trợ máy cũ"). + */ +const SEP = String.raw`[\s\-:,]*`; +const OLD_DEVICE = String.raw`(?:(?:máy|điện\s*thoại|đt)${SEP})?cũ`; +const TRADE_IN_FILLER = String.raw`(?:(?:từ|cho|mọi|các|bằng|khi|lại|giá\s*cao|\d+(?:[.,]\d+)?\s*(?:triệu|tr|k|đ))${SEP})*`; +const TRADE_IN_TO_NEW = String.raw`(?:lên${SEP}đời|(?:lấy|đổi|sang)${SEP}(?:(?:máy|điện\s*thoại)${SEP})?mới(?!\s*(?:\d|trong|sau|nếu|khi)))`; +const TRADE_IN_PHRASE = new RegExp( + String.raw`(?:(?:hỗ\s*trợ${SEP})?(?:thu(?:\s*mua)?|đổi|lên${SEP}đời)|trợ\s*giá)${SEP}` + + TRADE_IN_FILLER + + OLD_DEVICE + + String.raw`(?:${SEP}${TRADE_IN_TO_NEW})?` + + '|' + + OLD_DEVICE + + SEP + + TRADE_IN_TO_NEW, + 'gi' +); /** * Detect listing physical condition from product title and optional description (Audit C6).