fix(pdd): 长 SKU 精确名优先于短模板 contains,消除父子模板冲突

out_sku_sn「安卓血宴5钻带2璀璨」会被短名「安卓血宴5钻」contains 同分命中;精确相等提分,contains 按名称长度区分优先级。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-09-15 11:35:24 +08:00
co-authored by Cursor
parent 03dffcfbb8
commit 868e8a740e
2 changed files with 52 additions and 12 deletions
@@ -317,20 +317,30 @@ function scoreWorkProductRule(order: OrderRow, item: OrderItemRow, rule: WorkPro
const ruleSkuCode = normalizeMatchId(rule.sku_code)
const ruleProductName = normalizeMatchText(rule.product_name)
const skuCodeMatched = Boolean(ruleSkuCode && skuCodes.includes(ruleSkuCode))
const productNameMatched = ruleProductName
? rule.match_type === 'exact'
? skuNames.includes(ruleProductName) || skuCodes.includes(ruleProductName)
: skuNames.some((value) => value.includes(ruleProductName)) ||
skuCodes.some((value) => value.includes(ruleProductName))
: false
const exactProductName = Boolean(
ruleProductName && (skuNames.includes(ruleProductName) || skuCodes.includes(ruleProductName)),
)
const containsProductName = Boolean(
ruleProductName &&
!exactProductName &&
rule.match_type !== 'exact' &&
(skuNames.some((value) => value.includes(ruleProductName)) ||
skuCodes.some((value) => value.includes(ruleProductName))),
)
// sku_code 与 product_name 任一命中即可(不再要求两者同时成立,避免误填的 sku_code 挡住 out_sku_sn)
if (!skuCodeMatched && !productNameMatched) return null
if (skuCodeMatched && productNameMatched) {
return { rule, score: rule.match_type === 'exact' ? 300 : 220 }
// sku_code 与 product_name 任一命中即可
if (!skuCodeMatched && !exactProductName && !containsProductName) return null
// 同分冲突常见场景:短名「安卓血宴5钻」contains 命中长编码「安卓血宴5钻带2璀璨」
// 精确相等优先;contains 则更长的模板名更具体、分更高
let score = 100
if (exactProductName) {
score = 250
} else if (containsProductName) {
score = 100 + Math.min(80, ruleProductName.length)
}
if (skuCodeMatched) return { rule, score: 120 }
return { rule, score: rule.match_type === 'exact' ? 150 : 100 }
if (skuCodeMatched) score += exactProductName || containsProductName ? 40 : 120
return { rule, score }
}
/** 拼多多常复用原快手模板;来源/平台残留 91kaquan、kuaishou 时不拦截 out_sku_sn 模板名匹配。 */
@@ -713,6 +713,36 @@ test('拼多多订单在 sku_code 误填时仍可按 product_name/out_sku_sn 命
assert.equal(decision.rule?.id, rule.id)
})
test('长 out_sku_sn 在短模板名 contains 时可优先命中精确相等的长模板,避免冲突', () => {
const longRule = buildProductRule({
id: 101,
rule_key: 'rule-79bf8b3006ea',
product_name: '安卓血宴5钻带2璀璨',
match_type: 'contains',
})
const shortRule = buildProductRule({
id: 102,
rule_key: 'rule-f93c368547e1',
product_name: '安卓血宴5钻',
match_type: 'contains',
})
const order = { ...buildOrderRow(), provider: 'pdd', platform: 'pdd' }
const item = {
...buildOrderItemRow(),
sku_code: '安卓血宴5钻带2璀璨',
sku_name: '安卓血宴5钻带2璀璨',
item_snapshot_json: {
out_sku_sn: '安卓血宴5钻带2璀璨',
spec: '安卓区(qv互通),苏尔南-血宴5钻带2璀璨',
},
}
const decision = resolveMatchingProductRuleDecision(order, item, [longRule, shortRule], [])
assert.equal(decision.reason, 'matched')
assert.equal(decision.rule?.rule_key, 'rule-79bf8b3006ea')
assert.ok((decision.score || 0) > 100)
})
test('拼多多订单缺少 out_sku_sn 且 spec 不含规则名时不命中', () => {
const rule = buildProductRule({
provider: 'pdd',