feat(worker-platform): 增加寄卖匹配品类隔离门禁,杜绝手枪跨品类误匹配密钥礼盒

1. 新增 detectItemCategory 与 formatItemCategoryLabel 品类主体识别器(覆盖苏尔南手枪/枪皮、特种兵密钥、爆破型肩炮、专属礼盒、改名卡、点券等类目);
2. 寄卖匹配比对中引入强品类隔离门禁,在宽松模式下亦严禁跨品类误判为非精确匹配;
3. describeConsignmentSpecDifferences 补充品类级差异冲突说明,非同品类订单自动沉底;
4. 补充专项单测并保证全量测试与构建通过。
This commit is contained in:
yml2213
2026-09-19 10:40:02 +08:00
parent a5727554a4
commit c62c8c2cad
2 changed files with 165 additions and 0 deletions
@@ -600,3 +600,46 @@ test('发布寄卖建议价:号主钻级比后台配置高时,按已配置
null,
)
})
test('品类隔离门禁:苏尔南手枪现货绝不匹配特种兵密钥、爆破型肩炮、专属礼盒等无关商品', () => {
const invPistol = buildInventory({
item_name: '苹果QQ区怒嚎3钻2璀璨 苏尔南',
system: '苹果',
server_zone: 'QQ区',
series: '怒嚎',
diamond_level: 3,
brilliance_count: 2,
})
// 1. 特种兵密钥
const keyOrder = extractPeacekeeperSpecs('特种兵密钥1个')
assert.equal(matchPeacekeeperOrderWithInventory(keyOrder, invPistol), null)
assert.equal(
matchPeacekeeperOrderWithInventory(keyOrder, invPistol, { allowUnspecifiedSpecs: true }),
null,
'宽松模式下也必须拒绝跨品类',
)
// 2. 爆破型肩炮
const cannonOrder = extractPeacekeeperSpecs('全新版【金色带特效】爆破型肩炮')
assert.equal(matchPeacekeeperOrderWithInventory(cannonOrder, invPistol), null)
assert.equal(
matchPeacekeeperOrderWithInventory(cannonOrder, invPistol, { allowUnspecifiedSpecs: true }),
null,
)
// 3. 专属礼盒
const boxOrder = extractPeacekeeperSpecs('1个精致荣耀专属礼盒')
assert.equal(matchPeacekeeperOrderWithInventory(boxOrder, invPistol), null)
assert.equal(
matchPeacekeeperOrderWithInventory(boxOrder, invPistol, { allowUnspecifiedSpecs: true }),
null,
)
// 4. 正确的苏尔南手枪单子正常命中
const pistolOrder = extractPeacekeeperSpecs('苹果QQ区怒嚎3钻 苏尔南手枪')
const matched = matchPeacekeeperOrderWithInventory(pistolOrder, invPistol)
assert.ok(matched)
assert.equal(matched.matchType, 'partial') // 2璀璨充0璀璨
})
@@ -64,6 +64,85 @@ export function normalizePeacekeeperSeries(value: string): string {
return extractPeacekeeperSeries(value) || String(value || '').trim()
}
/** 常见商品品类主体定义 */
export type ItemCategoryType =
| 'pistol_skin' // 手枪/苏尔南/枪皮
| 'key_secret' // 特种兵密钥/钥匙
| 'cannon' // 爆破型肩炮/肩甲
| 'gift_box' // 专属礼盒/宝箱/福袋
| 'rename_card' // 改名卡/更名卡
| 'diamond_topup' // 钻石/点券/充值
| 'other'
export function detectItemCategory(text: string): ItemCategoryType {
const normalized = String(text || '').toLowerCase()
if (!normalized) return 'other'
// 1. 手枪/枪皮/苏尔南系列
if (
normalized.includes('苏尔南') ||
normalized.includes('手枪') ||
normalized.includes('暗潮') ||
normalized.includes('怒嚎') ||
normalized.includes('怒号') ||
normalized.includes('血宴') ||
normalized.includes('枪皮')
) {
return 'pistol_skin'
}
// 2. 密钥/特种兵密钥
if (
normalized.includes('密钥') ||
normalized.includes('密匙') ||
normalized.includes('特种兵密钥') ||
normalized.includes('钥匙')
) {
return 'key_secret'
}
// 3. 肩炮
if (normalized.includes('肩炮') || normalized.includes('爆破型肩炮')) {
return 'cannon'
}
// 4. 专属礼盒/宝箱
if (normalized.includes('礼盒') || normalized.includes('宝箱') || normalized.includes('福袋')) {
return 'gift_box'
}
// 5. 改名卡
if (normalized.includes('改名') || normalized.includes('更名')) {
return 'rename_card'
}
// 6. 钻石/点券充值
if (normalized.includes('点券') || normalized.includes('代充') || normalized.includes('充值')) {
return 'diamond_topup'
}
return 'other'
}
export function formatItemCategoryLabel(type: ItemCategoryType): string {
switch (type) {
case 'pistol_skin':
return '苏尔南手枪/枪皮'
case 'key_secret':
return '特种兵密钥'
case 'cannon':
return '爆破型肩炮'
case 'gift_box':
return '专属礼盒'
case 'rename_card':
return '改名卡'
case 'diamond_topup':
return '点券/充值'
default:
return '其它类目'
}
}
function parseSpecsFromText(rawText: string): Omit<ParsedOrderSpecs, 'rawText'> {
const text = String(rawText || '').trim()
@@ -167,6 +246,29 @@ export function matchPeacekeeperOrderWithInventory(
return null
}
// 1. 品类与物品主体强隔离门禁(防跨品类误判为非精确匹配)
const orderCat = detectItemCategory(order.rawText)
const invCat = detectItemCategory(
`${inventory.item_name} ${inventory.series} ${inventory.category_name || ''}`,
)
// 若两端均能识别出品类且不一致(如订单是密钥/肩炮/礼盒,现货是手枪/枪皮),直接拒绝
if (orderCat !== 'other' && invCat !== 'other' && orderCat !== invCat) {
return null
}
// 若现货明确为手枪/枪皮类目,订单不能是无任何手枪特征的其他商品
if (invCat === 'pistol_skin') {
const orderHasPistolSignal =
Boolean(order.series) ||
order.rawText.includes('苏尔南') ||
order.rawText.includes('手枪') ||
order.rawText.includes('枪皮')
if (!orderHasPistolSignal) {
return null
}
}
const invSystem = normalizePeacekeeperSystem(inventory.system)
const invZone = normalizePeacekeeperServerZone(inventory.server_zone)
const invSeries = normalizePeacekeeperSeries(inventory.series)
@@ -281,6 +383,26 @@ export function describeConsignmentSpecDifferences(
inventory: ConsignmentInventoryRow,
): string[] {
const diffs: string[] = []
const orderCat = detectItemCategory(order.rawText)
const invCat = detectItemCategory(
`${inventory.item_name} ${inventory.series} ${inventory.category_name || ''}`,
)
if (orderCat !== 'other' && invCat !== 'other' && orderCat !== invCat) {
diffs.push(
`品类:订单「${formatItemCategoryLabel(orderCat)}」/ 现货「${formatItemCategoryLabel(invCat)}」`,
)
} else if (invCat === 'pistol_skin') {
const orderHasPistolSignal =
Boolean(order.series) ||
order.rawText.includes('苏尔南') ||
order.rawText.includes('手枪') ||
order.rawText.includes('枪皮')
if (!orderHasPistolSignal) {
diffs.push(`品类:订单商品非手枪/枪皮 / 现货「苏尔南手枪/枪皮」`)
}
}
const compare = (
label: string,
orderValue: string,