178 lines
8.2 KiB
JavaScript
178 lines
8.2 KiB
JavaScript
/**
|
||
* 拼音产品名模糊匹配器
|
||
* 系统化方案:自动覆盖所有中文产品名的同音字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,
|
||
};
|