feat(worker-platform): 对齐生产环境分类树并增强寄卖匹配多品类硬隔离门禁
1. 接入生产分类树体系:将 ItemCategoryType 对齐生产 work_categories 真实分类(苏尔南手枪、苏尔南钻石、特种兵密钥、地铁逃生、大转盘车皮、蛋仔派对、王者荣耀、第五人格、人气礼物等); 2. 强化跨游戏与跨子类隔离:精准拆分地铁逃生下的苏尔南钻石与苏尔南手枪,杜绝钻石充值单误匹配手枪现货; 3. 严格品类硬隔离门禁:对齐 matchPeacekeeperOrderWithInventory 与 describeConsignmentSpecDifferences,品类冲突时彻底拒绝匹配并在非精确列表中置为 conflict 沉底; 4. 补充全品类匹配与隔离单元测试,覆盖手枪、密钥、车皮、钻石、跨游戏等测试用例。
This commit is contained in:
@@ -641,4 +641,24 @@ test('品类隔离门禁:苏尔南手枪现货绝不匹配特种兵密钥、
|
||||
const matched = matchPeacekeeperOrderWithInventory(pistolOrder, invPistol)
|
||||
assert.ok(matched)
|
||||
assert.equal(matched.matchType, 'partial') // 2璀璨充0璀璨
|
||||
|
||||
// 5. 跨游戏:蛋仔派对、王者荣耀、第五人格
|
||||
assert.equal(
|
||||
matchPeacekeeperOrderWithInventory(extractPeacekeeperSpecs('蛋仔派对 6480许愿币'), invPistol),
|
||||
null,
|
||||
)
|
||||
assert.equal(
|
||||
matchPeacekeeperOrderWithInventory(extractPeacekeeperSpecs('王者荣耀 荣耀水晶1颗'), invPistol),
|
||||
null,
|
||||
)
|
||||
assert.equal(
|
||||
matchPeacekeeperOrderWithInventory(extractPeacekeeperSpecs('第五人格 6480回声'), invPistol),
|
||||
null,
|
||||
)
|
||||
|
||||
// 6. 苏尔南钻石(地铁逃生分类) vs 苏尔南手枪(手枪分类)隔离
|
||||
assert.equal(
|
||||
matchPeacekeeperOrderWithInventory(extractPeacekeeperSpecs('苏尔南钻石100颗'), invPistol),
|
||||
null,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -64,61 +64,125 @@ 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' // 钻石/点券/充值
|
||||
| 'pistol_suernan' // 苏尔南手枪 (暗潮 / 怒嚎 / 血宴)
|
||||
| 'suernan_diamond' // 苏尔南钻石
|
||||
| 'peacekeeper_key' // 密钥 / 特种兵密钥
|
||||
| 'metro_escape' // 地铁逃生 (机甲/传世/护甲/背包/枪械类)
|
||||
| 'spin_wheel_car' // 大转盘 / 车皮 / 960 / 1800
|
||||
| 'danzai_party' // 蛋仔派对 (商城/许愿币/至臻/机甲)
|
||||
| 'honor_of_kings' // 王者荣耀
|
||||
| 'identity_v' // 第五人格
|
||||
| 'popularity_gift' // 人气礼物 / 专属礼盒
|
||||
| 'other'
|
||||
|
||||
export function detectItemCategory(text: string): ItemCategoryType {
|
||||
const normalized = String(text || '').toLowerCase()
|
||||
if (!normalized) return 'other'
|
||||
|
||||
// 1. 手枪/枪皮/苏尔南系列
|
||||
// 1. 跨游戏识别优先
|
||||
if (
|
||||
normalized.includes('蛋仔') ||
|
||||
normalized.includes('许愿币') ||
|
||||
normalized.includes('潮流纤维')
|
||||
) {
|
||||
return 'danzai_party'
|
||||
}
|
||||
if (
|
||||
normalized.includes('王者') ||
|
||||
normalized.includes('王者荣耀') ||
|
||||
normalized.includes('荣耀水晶') ||
|
||||
normalized.includes('皮肤赠送')
|
||||
) {
|
||||
return 'honor_of_kings'
|
||||
}
|
||||
if (
|
||||
normalized.includes('第五人格') ||
|
||||
normalized.includes('第五') ||
|
||||
normalized.includes('回声')
|
||||
) {
|
||||
return 'identity_v'
|
||||
}
|
||||
|
||||
// 2. 苏尔南钻石(地铁逃生下的独立子类:如苏尔南钻石100颗等,与手枪区分)
|
||||
if (
|
||||
(normalized.includes('苏尔南') || normalized.includes('地铁')) &&
|
||||
normalized.includes('钻石') &&
|
||||
(normalized.includes('颗') ||
|
||||
normalized.includes('万') ||
|
||||
(!normalized.includes('暗潮') &&
|
||||
!normalized.includes('怒嚎') &&
|
||||
!normalized.includes('血宴') &&
|
||||
!normalized.includes('手枪') &&
|
||||
!normalized.includes('枪皮')))
|
||||
) {
|
||||
return 'suernan_diamond'
|
||||
}
|
||||
|
||||
// 3. 苏尔南手枪 (暗潮 / 怒嚎 / 血宴 / 枪皮 / 璀璨钻级)
|
||||
if (
|
||||
normalized.includes('苏尔南') ||
|
||||
normalized.includes('手枪') ||
|
||||
normalized.includes('暗潮') ||
|
||||
normalized.includes('怒嚎') ||
|
||||
normalized.includes('怒号') ||
|
||||
normalized.includes('血宴') ||
|
||||
normalized.includes('枪皮')
|
||||
normalized.includes('手枪') ||
|
||||
normalized.includes('枪皮') ||
|
||||
(normalized.includes('苏尔南') && !normalized.includes('钻石'))
|
||||
) {
|
||||
return 'pistol_skin'
|
||||
return 'pistol_suernan'
|
||||
}
|
||||
|
||||
// 2. 密钥/特种兵密钥
|
||||
// 4. 密钥 (特种兵密钥 / 钥匙)
|
||||
if (
|
||||
normalized.includes('密钥') ||
|
||||
normalized.includes('密匙') ||
|
||||
normalized.includes('特种兵密钥') ||
|
||||
normalized.includes('钥匙')
|
||||
) {
|
||||
return 'key_secret'
|
||||
return 'peacekeeper_key'
|
||||
}
|
||||
|
||||
// 3. 肩炮
|
||||
if (normalized.includes('肩炮') || normalized.includes('爆破型肩炮')) {
|
||||
return 'cannon'
|
||||
// 5. 大转盘 / 车皮 / 960 / 1800
|
||||
if (
|
||||
normalized.includes('大转盘') ||
|
||||
normalized.includes('车皮') ||
|
||||
normalized.includes('960') ||
|
||||
normalized.includes('1800') ||
|
||||
normalized.includes('载具') ||
|
||||
normalized.includes('玛莎') ||
|
||||
normalized.includes('特斯拉') ||
|
||||
normalized.includes('兰博基尼')
|
||||
) {
|
||||
return 'spin_wheel_car'
|
||||
}
|
||||
|
||||
// 4. 专属礼盒/宝箱
|
||||
if (normalized.includes('礼盒') || normalized.includes('宝箱') || normalized.includes('福袋')) {
|
||||
return 'gift_box'
|
||||
// 6. 地铁逃生 (机甲 / 传世武器 / 护甲 / 背包 / 枪械类 / 肩炮)
|
||||
if (
|
||||
normalized.includes('地铁') ||
|
||||
normalized.includes('逃生') ||
|
||||
normalized.includes('机甲') ||
|
||||
normalized.includes('传世') ||
|
||||
normalized.includes('护甲') ||
|
||||
normalized.includes('防弹衣') ||
|
||||
normalized.includes('头盔') ||
|
||||
normalized.includes('背包') ||
|
||||
normalized.includes('肩炮') ||
|
||||
normalized.includes('装甲武器') ||
|
||||
normalized.includes('辐射区')
|
||||
) {
|
||||
return 'metro_escape'
|
||||
}
|
||||
|
||||
// 5. 改名卡
|
||||
if (normalized.includes('改名') || normalized.includes('更名')) {
|
||||
return 'rename_card'
|
||||
}
|
||||
|
||||
// 6. 钻石/点券充值
|
||||
if (normalized.includes('点券') || normalized.includes('代充') || normalized.includes('充值')) {
|
||||
return 'diamond_topup'
|
||||
// 7. 人气礼物 / 专属礼盒
|
||||
if (
|
||||
normalized.includes('人气礼物') ||
|
||||
normalized.includes('专属礼盒') ||
|
||||
normalized.includes('礼盒') ||
|
||||
normalized.includes('宝箱') ||
|
||||
normalized.includes('福袋')
|
||||
) {
|
||||
return 'popularity_gift'
|
||||
}
|
||||
|
||||
return 'other'
|
||||
@@ -126,18 +190,24 @@ export function detectItemCategory(text: string): ItemCategoryType {
|
||||
|
||||
export function formatItemCategoryLabel(type: ItemCategoryType): string {
|
||||
switch (type) {
|
||||
case 'pistol_skin':
|
||||
return '苏尔南手枪/枪皮'
|
||||
case 'key_secret':
|
||||
case 'pistol_suernan':
|
||||
return '苏尔南手枪 (暗潮/怒嚎/血宴)'
|
||||
case 'suernan_diamond':
|
||||
return '苏尔南钻石'
|
||||
case 'peacekeeper_key':
|
||||
return '特种兵密钥'
|
||||
case 'cannon':
|
||||
return '爆破型肩炮'
|
||||
case 'gift_box':
|
||||
return '专属礼盒'
|
||||
case 'rename_card':
|
||||
return '改名卡'
|
||||
case 'diamond_topup':
|
||||
return '点券/充值'
|
||||
case 'metro_escape':
|
||||
return '地铁逃生装备/机甲'
|
||||
case 'spin_wheel_car':
|
||||
return '大转盘/车皮'
|
||||
case 'danzai_party':
|
||||
return '蛋仔派对'
|
||||
case 'honor_of_kings':
|
||||
return '王者荣耀'
|
||||
case 'identity_v':
|
||||
return '第五人格'
|
||||
case 'popularity_gift':
|
||||
return '人气礼物/专属礼盒'
|
||||
default:
|
||||
return '其它类目'
|
||||
}
|
||||
@@ -246,19 +316,19 @@ export function matchPeacekeeperOrderWithInventory(
|
||||
return null
|
||||
}
|
||||
|
||||
// 1. 品类与物品主体强隔离门禁(防跨品类误判为非精确匹配)
|
||||
// 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') {
|
||||
// 若现货明确为苏尔南手枪类目,订单不能是无任何手枪特征的其他商品
|
||||
if (invCat === 'pistol_suernan') {
|
||||
const orderHasPistolSignal =
|
||||
Boolean(order.series) ||
|
||||
order.rawText.includes('苏尔南') ||
|
||||
@@ -392,14 +462,14 @@ export function describeConsignmentSpecDifferences(
|
||||
diffs.push(
|
||||
`品类:订单「${formatItemCategoryLabel(orderCat)}」/ 现货「${formatItemCategoryLabel(invCat)}」`,
|
||||
)
|
||||
} else if (invCat === 'pistol_skin') {
|
||||
} else if (invCat === 'pistol_suernan') {
|
||||
const orderHasPistolSignal =
|
||||
Boolean(order.series) ||
|
||||
order.rawText.includes('苏尔南') ||
|
||||
order.rawText.includes('手枪') ||
|
||||
order.rawText.includes('枪皮')
|
||||
if (!orderHasPistolSignal) {
|
||||
diffs.push(`品类:订单商品非手枪/枪皮 / 现货「苏尔南手枪/枪皮」`)
|
||||
diffs.push(`品类:订单商品非手枪/枪皮 / 现货「苏尔南手枪 (暗潮/怒嚎/血宴)」`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user