/** * 拼音产品名模糊匹配器 * 系统化方案:自动覆盖所有中文产品名的同音字ASR误识别 * * 原理: * 1. 启动时为每个产品名生成"拼音签名"(声母+韵母序列) * 2. 运行时将ASR文本的滑动窗口转为拼音签名 * 3. 签名匹配则用正确产品名替换 * * 优势:新增产品只需加入 PRODUCTS 列表即可自动覆盖,无需手写regex */ // ============ 字符→拼音映射表 ============ // 仅覆盖产品名用字 + 常见ASR同音替代字(~350字) const P = { // --- a --- '阿': 'a', '啊': 'a', '爱': 'ai', '艾': 'ai', '哎': 'ai', '安': 'an', '暗': 'an', '按': 'an', '氨': 'an', '胺': 'an', '昂': 'ang', // --- b --- '八': 'ba', '巴': 'ba', '白': 'bai', '百': 'bai', '柏': 'bai', '包': 'bao', '宝': 'bao', '保': 'bao', '报': 'bao', '煲': 'bao', '苞': 'bao', '胞': 'bao', '北': 'bei', '被': 'bei', '背': 'bei', '贝': 'bei', '备': 'bei', '辈': 'bei', '杯': 'bei', '倍': 'bei', '本': 'ben', '苯': 'ben', '奔': 'ben', '笨': 'ben', // --- c --- '采': 'cai', '彩': 'cai', '菜': 'cai', '蔡': 'cai', '猜': 'cai', '财': 'cai', '材': 'cai', '才': 'cai', '草': 'cao', '操': 'cao', '曹': 'cao', '槽': 'cao', '茶': 'cha', '查': 'cha', '差': 'cha', '插': 'cha', '察': 'cha', '纯': 'chun', '唇': 'chun', '春': 'chun', '醇': 'chun', '蠢': 'chun', '萃': 'cui', '翠': 'cui', '脆': 'cui', '粹': 'cui', '催': 'cui', // --- d --- '大': 'da', '蛋': 'dan', '旦': 'dan', '但': 'dan', '淡': 'dan', '弹': 'dan', '担': 'dan', '毒': 'du', '独': 'du', '度': 'du', '读': 'du', '督': 'du', // --- e --- // --- f --- '发': 'fa', '法': 'fa', '罚': 'fa', '反': 'fan', '返': 'fan', '犯': 'fan', '翻': 'fan', '范': 'fan', '饭': 'fan', '非': 'fei', '飞': 'fei', '费': 'fei', '肺': 'fei', '废': 'fei', '吠': 'fei', '肤': 'fu', '夫': 'fu', '服': 'fu', '福': 'fu', '付': 'fu', '副': 'fu', '附': 'fu', '府': 'fu', '腐': 'fu', '辅': 'fu', '浮': 'fu', '扶': 'fu', '复': 'fu', // --- g --- '格': 'ge', '隔': 'ge', '革': 'ge', '各': 'ge', '阁': 'ge', '葛': 'ge', '骼': 'ge', '骨': 'gu', '谷': 'gu', '古': 'gu', '鼓': 'gu', '估': 'gu', '故': 'gu', '顾': 'gu', '关': 'guan', '管': 'guan', '官': 'guan', '馆': 'guan', // --- h --- '好': 'hao', '号': 'hao', '浩': 'hao', '耗': 'hao', '豪': 'hao', '衡': 'heng', '横': 'heng', '恒': 'heng', '亨': 'heng', '红': 'hong', '洪': 'hong', '宏': 'hong', '鸿': 'hong', '黑': 'hei', '嘿': 'hei', '活': 'huo', '火': 'huo', '获': 'huo', '霍': 'huo', '货': 'huo', '祸': 'huo', '黄': 'huang', '皇': 'huang', '荒': 'huang', '慌': 'huang', '煌': 'huang', '惶': 'huang', // --- j --- '基': 'ji', '机': 'ji', '鸡': 'ji', '积': 'ji', '极': 'ji', '几': 'ji', '计': 'ji', '记': 'ji', '级': 'ji', '见': 'jian', '健': 'jian', '剑': 'jian', '键': 'jian', '建': 'jian', '件': 'jian', '检': 'jian', '简': 'jian', '减': 'jian', '渐': 'jian', '坚': 'jian', '尖': 'jian', '肩': 'jian', '交': 'jiao', '教': 'jiao', '角': 'jiao', '焦': 'jiao', '较': 'jiao', '觉': 'jiao', '胶': 'jiao', '叫': 'jiao', '酵': 'jiao', '节': 'jie', '结': 'jie', '洁': 'jie', '杰': 'jie', '接': 'jie', '揭': 'jie', '截': 'jie', '菌': 'jun', '军': 'jun', '均': 'jun', '君': 'jun', '俊': 'jun', // --- k --- '抗': 'kang', '康': 'kang', '慷': 'kang', '口': 'kou', // --- l --- '乐': 'le', '勒': 'le', '力': 'li', '利': 'li', '立': 'li', '厉': 'li', '励': 'li', '历': 'li', '丽': 'li', '离': 'li', '莉': 'li', '礼': 'li', '理': 'li', '李': 'li', '里': 'li', '藜': 'li', '梨': 'li', '黎': 'li', '绿': 'lv', '芦': 'lu', '炉': 'lu', '路': 'lu', '鹿': 'lu', '鲁': 'lu', '卢': 'lu', '露': 'lu', '陆': 'lu', '庐': 'lu', '酪': 'lao', '烙': 'lao', '落': 'luo', '络': 'luo', // --- m --- '面': 'mian', '免': 'mian', '棉': 'mian', '眠': 'mian', '绵': 'mian', '勉': 'mian', '免': 'mian', // --- n --- // --- p --- '排': 'pai', '牌': 'pai', '拍': 'pai', '派': 'pai', '葡': 'pu', '铺': 'pu', '浦': 'pu', '蒲': 'pu', // --- q --- '腔': 'qiang', // --- r --- '乳': 'ru', '如': 'ru', '入': 'ru', '儒': 'ru', // --- s --- '霜': 'shuang', '双': 'shuang', '爽': 'shuang', '水': 'shui', '睡': 'shui', '谁': 'shui', '舒': 'shu', '书': 'shu', '叔': 'shu', '输': 'shu', '树': 'shu', '竖': 'shu', '生': 'sheng', '声': 'sheng', '胜': 'sheng', '升': 'sheng', '省': 'sheng', '圣': 'sheng', '素': 'su', '速': 'su', '诉': 'su', '苏': 'su', '塑': 'su', '酸': 'suan', '算': 'suan', '蒜': 'suan', // --- t --- '萄': 'tao', '逃': 'tao', '淘': 'tao', '桃': 'tao', '陶': 'tao', '套': 'tao', '讨': 'tao', '陀': 'tuo', '驼': 'tuo', '拖': 'tuo', '脱': 'tuo', '托': 'tuo', '酮': 'tong', '铜': 'tong', '同': 'tong', '桐': 'tong', '童': 'tong', '痛': 'tong', '通': 'tong', '统': 'tong', // --- w --- // --- x --- '细': 'xi', '希': 'xi', '西': 'xi', '系': 'xi', '息': 'xi', '稀': 'xi', '席': 'xi', '吸': 'xi', '纤': 'xian', '先': 'xian', '鲜': 'xian', '仙': 'xian', '险': 'xian', '显': 'xian', '线': 'xian', '限': 'xian', '县': 'xian', '现': 'xian', '献': 'xian', '小': 'xiao', // --- y --- '眼': 'yan', '演': 'yan', '验': 'yan', '烟': 'yan', '严': 'yan', '颜': 'yan', '盐': 'yan', '言': 'yan', '岩': 'yan', '延': 'yan', '氧': 'yang', '养': 'yang', '仰': 'yang', '样': 'yang', '洋': 'yang', '央': 'yang', '阳': 'yang', '益': 'yi', '意': 'yi', '易': 'yi', '亿': 'yi', '以': 'yi', '艺': 'yi', '忆': 'yi', '异': 'yi', '议': 'yi', '翼': 'yi', '衣': 'yi', '依': 'yi', '一': 'yi', '饮': 'yin', '引': 'yin', '印': 'yin', '隐': 'yin', '银': 'yin', '音': 'yin', '应': 'ying', '映': 'ying', '影': 'ying', '英': 'ying', '营': 'ying', '迎': 'ying', '育': 'yu', '玉': 'yu', '域': 'yu', '遇': 'yu', '雨': 'yu', '宇': 'yu', '御': 'yu', '语': 'yu', '鱼': 'yu', '原': 'yuan', '圆': 'yuan', '远': 'yuan', '园': 'yuan', '元': 'yuan', '源': 'yuan', '缘': 'yuan', // --- z --- '籽': 'zi', '子': 'zi', '紫': 'zi', '自': 'zi', '字': 'zi', '转': 'zhuan', '赚': 'zhuan', '砖': 'zhuan', '专': 'zhuan', }; // 需要拼音匹配的中文产品名(3字及以上,避免2字误匹配) const PRODUCTS = [ // 5字 '细胞抗氧素', // 4字 '胶原蛋白', '白藜芦醇', '好转反应', '阿育吠陀', // 3字 '活力健', '倍力健', '氨基酸', '益生菌', '辅酵素', '葡萄籽', '排毒饮', '乳酪煲', '草本茶', '异黄酮', '骨骼健', '舒采健', '衡醇饮', '洁面乳', '爽肤水', ]; // ============ 构建拼音索引 ============ function toPinyinKey(text) { const parts = []; for (const ch of text) { const py = P[ch]; if (!py) return null; // 未知字符,跳过此窗口 parts.push(py); } return parts.join('-'); } const pinyinIndex = new Map(); // pinyin-key → correct product name for (const name of PRODUCTS) { const key = toPinyinKey(name); if (key) { pinyinIndex.set(key, name); } } // 按长度分组,方便按长度优先匹配 const productLengths = [...new Set(PRODUCTS.map(p => p.length))].sort((a, b) => b - a); // ============ 运行时匹配 ============ function pinyinMatchProducts(text) { if (!text || typeof text !== 'string') return text || ''; let result = text; // 从长窗口到短窗口扫描,避免短匹配覆盖长匹配 for (const len of productLengths) { if (result.length < len) continue; for (let i = 0; i <= result.length - len; i++) { const window = result.substring(i, i + len); // 快速跳过:窗口含非中文字符 if (!/^[\u4e00-\u9fff]+$/.test(window)) continue; const key = toPinyinKey(window); if (!key) continue; const product = pinyinIndex.get(key); if (product && window !== product) { console.log(`[PinyinMatcher] "${window}" → "${product}" (pinyin: ${key})`); result = result.substring(0, i) + product + result.substring(i + len); // 跳过已替换部分 i += product.length - 1; } } } return result; } module.exports = { pinyinMatchProducts, toPinyinKey, pinyinIndex, P, PRODUCTS, };