fix: классификатор не ловил кириллицу и склонения
- regex `\b` в JS не работает с кириллицей (русские буквы не word-char), поэтому ни одно русское ключевое слово не давало хитов; - exact-match не учитывал склонения: «приложение» vs «приложении». Заменил на ручной матч с допуском в 2 символа суффикса (shared prefix ≥ max(keyword.length - 2, 4)). Сохранён exact-match для коротких ключей (sms/ios/crm) чтобы избежать ложных срабатываний. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d8f3cfea24
commit
0455123d8e
30
index.html
30
index.html
@ -448,19 +448,37 @@ function applyLang(){
|
|||||||
function normalize(s){
|
function normalize(s){
|
||||||
return s.toLowerCase().replace(/ё/g,"е").replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(Boolean);
|
return s.toLowerCase().replace(/ё/g,"е").replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(Boolean);
|
||||||
}
|
}
|
||||||
function escapeReg(s){ return s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"); }
|
function matchTerm(tokens, term){
|
||||||
|
const norm = normalize(term);
|
||||||
|
if (norm.length === 0) return 0;
|
||||||
|
if (norm.length > 1) {
|
||||||
|
const joined = " " + tokens.join(" ") + " ";
|
||||||
|
const needle = " " + norm.join(" ") + " ";
|
||||||
|
let cnt = 0, idx = 0;
|
||||||
|
while ((idx = joined.indexOf(needle, idx)) !== -1) { cnt++; idx += needle.length - 1; }
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
const n = norm[0];
|
||||||
|
let cnt = 0;
|
||||||
|
for (const tok of tokens) {
|
||||||
|
if (tok === n) { cnt++; continue; }
|
||||||
|
if (n.length < 4) continue;
|
||||||
|
const k = Math.max(n.length - 2, 4);
|
||||||
|
if (tok.length < k || n.length < k) continue;
|
||||||
|
if (tok.slice(0, k) === n.slice(0, k)) cnt++;
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
function scoreZones(text){
|
function scoreZones(text){
|
||||||
const tokenStr = normalize(text).join(" ");
|
const tokens = normalize(text);
|
||||||
return state.directory.zones.map(z => {
|
return state.directory.zones.map(z => {
|
||||||
let score = 0; const hits = [];
|
let score = 0; const hits = [];
|
||||||
for (const k of z.keywords) {
|
for (const k of z.keywords) {
|
||||||
const n = normalize(k).join(" ");
|
const c = matchTerm(tokens, k);
|
||||||
const c = (tokenStr.match(new RegExp("\\b"+escapeReg(n)+"\\b","g"))||[]).length;
|
|
||||||
if (c > 0) { score += c * 1.0; hits.push(k); }
|
if (c > 0) { score += c * 1.0; hits.push(k); }
|
||||||
}
|
}
|
||||||
for (const sys of z.systems) {
|
for (const sys of z.systems) {
|
||||||
const n = normalize(sys).join(" ");
|
const c = matchTerm(tokens, sys);
|
||||||
const c = (tokenStr.match(new RegExp("\\b"+escapeReg(n)+"\\b","g"))||[]).length;
|
|
||||||
if (c > 0) { score += c * 1.5; hits.push(sys); }
|
if (c > 0) { score += c * 1.5; hits.push(sys); }
|
||||||
}
|
}
|
||||||
return { zoneId:z.id, score, hits };
|
return { zoneId:z.id, score, hits };
|
||||||
|
|||||||
30
mvp.html
30
mvp.html
@ -252,19 +252,37 @@ function applyLang() {
|
|||||||
function normalize(s){
|
function normalize(s){
|
||||||
return s.toLowerCase().replace(/ё/g,"е").replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(Boolean);
|
return s.toLowerCase().replace(/ё/g,"е").replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(Boolean);
|
||||||
}
|
}
|
||||||
function escapeReg(s){ return s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"); }
|
function matchTerm(tokens, term){
|
||||||
|
const norm = normalize(term);
|
||||||
|
if (norm.length === 0) return 0;
|
||||||
|
if (norm.length > 1) {
|
||||||
|
const joined = " " + tokens.join(" ") + " ";
|
||||||
|
const needle = " " + norm.join(" ") + " ";
|
||||||
|
let cnt = 0, idx = 0;
|
||||||
|
while ((idx = joined.indexOf(needle, idx)) !== -1) { cnt++; idx += needle.length - 1; }
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
|
const n = norm[0];
|
||||||
|
let cnt = 0;
|
||||||
|
for (const tok of tokens) {
|
||||||
|
if (tok === n) { cnt++; continue; }
|
||||||
|
if (n.length < 4) continue;
|
||||||
|
const k = Math.max(n.length - 2, 4);
|
||||||
|
if (tok.length < k || n.length < k) continue;
|
||||||
|
if (tok.slice(0, k) === n.slice(0, k)) cnt++;
|
||||||
|
}
|
||||||
|
return cnt;
|
||||||
|
}
|
||||||
function scoreZones(text){
|
function scoreZones(text){
|
||||||
const tokenStr = normalize(text).join(" ");
|
const tokens = normalize(text);
|
||||||
return state.directory.zones.map(z => {
|
return state.directory.zones.map(z => {
|
||||||
let score = 0; const hits = [];
|
let score = 0; const hits = [];
|
||||||
for (const k of z.keywords) {
|
for (const k of z.keywords) {
|
||||||
const n = normalize(k).join(" ");
|
const c = matchTerm(tokens, k);
|
||||||
const c = (tokenStr.match(new RegExp("\\b"+escapeReg(n)+"\\b","g"))||[]).length;
|
|
||||||
if (c > 0) { score += c * 1.0; hits.push(k); }
|
if (c > 0) { score += c * 1.0; hits.push(k); }
|
||||||
}
|
}
|
||||||
for (const sys of z.systems) {
|
for (const sys of z.systems) {
|
||||||
const n = normalize(sys).join(" ");
|
const c = matchTerm(tokens, sys);
|
||||||
const c = (tokenStr.match(new RegExp("\\b"+escapeReg(n)+"\\b","g"))||[]).length;
|
|
||||||
if (c > 0) { score += c * 1.5; hits.push(sys); }
|
if (c > 0) { score += c * 1.5; hits.push(sys); }
|
||||||
}
|
}
|
||||||
return { zoneId:z.id, score, hits };
|
return { zoneId:z.id, score, hits };
|
||||||
|
|||||||
Reference in New Issue
Block a user