248 lines
6.4 KiB
TypeScript
248 lines
6.4 KiB
TypeScript
import {
|
||
PERSON_IMPORT_SOURCES,
|
||
type PersonImportSource,
|
||
} from '@mento/shared';
|
||
|
||
export type CountEntity =
|
||
| 'lead'
|
||
| 'person'
|
||
| 'company'
|
||
| 'deal'
|
||
| 'invoice'
|
||
| 'quote'
|
||
| 'product'
|
||
| 'activity'
|
||
| 'ticket'
|
||
| 'note'
|
||
| 'cheque'
|
||
| 'reservation'
|
||
| 'routine'
|
||
| 'project';
|
||
|
||
export type CountPeriod = 'today' | 'yesterday' | 'week' | 'last_week' | 'month' | 'last_month';
|
||
|
||
export type LeadBucket = 'summary' | 'open' | 'archived' | 'converted';
|
||
export type DealBucket = 'all' | 'open' | 'won' | 'lost' | 'stale';
|
||
|
||
export type CountIntent = {
|
||
entity: CountEntity;
|
||
personSource?: PersonImportSource;
|
||
mood?: 'newcomer' | 'happy' | 'unhappy' | 'special';
|
||
leadBucket: LeadBucket;
|
||
dealBucket: DealBucket;
|
||
period?: CountPeriod;
|
||
/** متن باقیمانده برای تطبیق مرحله یا منبع لید */
|
||
hint: string;
|
||
};
|
||
|
||
const SOURCE_PATTERNS: Array<{ re: RegExp; source: PersonImportSource }> = [
|
||
{ re: /دستی/, source: 'manual' },
|
||
{ re: /اکسل/, source: 'excel' },
|
||
{ re: /دیدار/, source: 'didar' },
|
||
{ re: /ته\s*حساب/, source: 'tahesab' },
|
||
{ re: /گوشی|مخاطب\s*گوشی|مخاطبین/, source: 'phone' },
|
||
{ re: /فرم/, source: 'lead_form' },
|
||
{ re: /مونتیفای/, source: 'montify' },
|
||
{ re: /سایر|بقیه/, source: 'other' },
|
||
];
|
||
|
||
export function normalizeAskText(raw: string) {
|
||
return raw
|
||
.replace(/ي/g, 'ی')
|
||
.replace(/ك/g, 'ک')
|
||
.replace(//g, ' ')
|
||
.replace(/[؟?!.,،؛:]+/g, ' ')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
.toLowerCase();
|
||
}
|
||
|
||
export function isCountQuestion(q: string) {
|
||
return /چند|تعداد|چقدر|چنتا|چند\s*تا/.test(q);
|
||
}
|
||
|
||
function detectEntity(q: string): CountEntity | null {
|
||
if (/لید|سرنخ/.test(q)) return 'lead';
|
||
if (/پیش\s*فاکتور/.test(q)) return 'quote';
|
||
if (/فاکتور/.test(q)) return 'invoice';
|
||
if (/معاملات|معامله/.test(q)) return 'deal';
|
||
if (/چک/.test(q)) return 'cheque';
|
||
if (/رزرو|نوبت/.test(q)) return 'reservation';
|
||
if (/روتین/.test(q)) return 'routine';
|
||
if (/پروژه/.test(q)) return 'project';
|
||
if (/تیکت/.test(q)) return 'ticket';
|
||
if (/یادداشت/.test(q)) return 'note';
|
||
if (/فعالیت/.test(q)) return 'activity';
|
||
if (/محصول|کالا/.test(q)) return 'product';
|
||
if (/شرکت/.test(q) && !/مشتری/.test(q)) return 'company';
|
||
if (/مشتری|اشخاص|شخص ها|افراد|مخاطب/.test(q)) return 'person';
|
||
return null;
|
||
}
|
||
|
||
function detectPeriod(q: string): CountPeriod | undefined {
|
||
if (/دیروز/.test(q)) return 'yesterday';
|
||
if (/امروز/.test(q)) return 'today';
|
||
if (/هفته\s*(?:ی\s+)?(گذشته|قبل|پیش)/.test(q)) return 'last_week';
|
||
if (/این\s*هفته|هفته\s*جاری/.test(q)) return 'week';
|
||
if (/ماه\s*(?:ی\s+)?(گذشته|قبل|پیش)/.test(q)) return 'last_month';
|
||
if (/این\s*ماه|ماه\s*جاری/.test(q)) return 'month';
|
||
return undefined;
|
||
}
|
||
|
||
function detectLeadBucket(q: string): LeadBucket {
|
||
if (/بایگانی/.test(q)) return 'archived';
|
||
if (/تبدیل/.test(q)) return 'converted';
|
||
if (/باز|فعال|جاری/.test(q)) return 'open';
|
||
return 'summary';
|
||
}
|
||
|
||
function detectDealBucket(q: string): DealBucket {
|
||
if (/در\s*خطر|راکد|گندیده/.test(q)) return 'stale';
|
||
if (/برنده|موفق|بسته\s*شده\s*موفق/.test(q)) return 'won';
|
||
if (/باخته|از\s*دست\s*رفته|ناموفق/.test(q)) return 'lost';
|
||
if (/باز|جاری|فعال/.test(q)) return 'open';
|
||
return 'all';
|
||
}
|
||
|
||
function detectMood(q: string): CountIntent['mood'] {
|
||
if (/ناراضی/.test(q)) return 'unhappy';
|
||
if (/خوشحال/.test(q)) return 'happy';
|
||
if (/خاص/.test(q)) return 'special';
|
||
if (/تازه/.test(q)) return 'newcomer';
|
||
return undefined;
|
||
}
|
||
|
||
const HINT_STOP = [
|
||
'چند تا',
|
||
'ته حساب',
|
||
'مخاطب گوشی',
|
||
'مخاطبین',
|
||
'پیش فاکتور',
|
||
'بایگانی',
|
||
'مونتیفای',
|
||
'معاملات',
|
||
'مشتریان',
|
||
'سرنخ',
|
||
'لیدها',
|
||
'فعالیت',
|
||
'یادداشت',
|
||
'بگویید',
|
||
'داریم',
|
||
'دارید',
|
||
'هستند',
|
||
'تعداد',
|
||
'چقدر',
|
||
'چنتا',
|
||
'امروز',
|
||
'دیروز',
|
||
'جاری',
|
||
'تبدیل',
|
||
'برنده',
|
||
'باخته',
|
||
'ناراضی',
|
||
'خوشحال',
|
||
'محصول',
|
||
'فاکتور',
|
||
'معامله',
|
||
'مشتری',
|
||
'شرکت',
|
||
'پروژه',
|
||
'روتین',
|
||
'رزرو',
|
||
'نوبت',
|
||
'تیکت',
|
||
'منبع',
|
||
'مرحله',
|
||
'وضعیت',
|
||
'هفته',
|
||
'گذشته',
|
||
'پیش',
|
||
'قبل',
|
||
'لطفا',
|
||
'الان',
|
||
'شده',
|
||
'وارد',
|
||
'تازه',
|
||
'خاص',
|
||
'بگو',
|
||
'دارم',
|
||
'هست',
|
||
'است',
|
||
'جاری',
|
||
'فعال',
|
||
'باز',
|
||
'لید',
|
||
'کالا',
|
||
'چک',
|
||
'ماه',
|
||
'پیش',
|
||
'های',
|
||
'چند',
|
||
'کل',
|
||
'همه',
|
||
'این',
|
||
'یک',
|
||
'یه',
|
||
'از',
|
||
'با',
|
||
'در',
|
||
'تا',
|
||
'ها',
|
||
].sort((a, b) => b.length - a.length);
|
||
|
||
const PERSON_HINT_STOP = [
|
||
'ته حساب',
|
||
'مخاطب گوشی',
|
||
'مخاطبین',
|
||
'مونتیفای',
|
||
'ناراضی',
|
||
'خوشحال',
|
||
'دستی',
|
||
'اکسل',
|
||
'دیدار',
|
||
'گوشی',
|
||
'مخاطب',
|
||
'فرم',
|
||
'سایر',
|
||
'تازه',
|
||
'خاص',
|
||
].sort((a, b) => b.length - a.length);
|
||
|
||
function leftoverHint(q: string, entity: CountEntity) {
|
||
let text = ` ${q} `;
|
||
const words = entity === 'person' || entity === 'company'
|
||
? [...HINT_STOP, ...PERSON_HINT_STOP]
|
||
: HINT_STOP;
|
||
for (const word of [...words].sort((a, b) => b.length - a.length)) {
|
||
text = text.replaceAll(word, ' ');
|
||
}
|
||
return text.replace(/\s+/g, ' ').trim();
|
||
}
|
||
|
||
export function classifyCountQuestion(raw: string): CountIntent | null {
|
||
const q = normalizeAskText(raw);
|
||
if (!isCountQuestion(q)) return null;
|
||
const entity = detectEntity(q);
|
||
if (!entity) return null;
|
||
const personSource = SOURCE_PATTERNS.find((item) => item.re.test(q))?.source;
|
||
const period = detectPeriod(q);
|
||
let hint = leftoverHint(q, entity);
|
||
if (period && entity === 'lead' && !/مرحله|وضعیت|ستون/.test(q)) {
|
||
hint = hint.replace(/(^|\s)جدید(?=\s|$)/g, ' ').replace(/\s+/g, ' ').trim();
|
||
}
|
||
return {
|
||
entity,
|
||
personSource:
|
||
entity === 'person' || entity === 'company' ? personSource : undefined,
|
||
mood: entity === 'person' ? detectMood(q) : undefined,
|
||
leadBucket: detectLeadBucket(q),
|
||
dealBucket: detectDealBucket(q),
|
||
period,
|
||
hint,
|
||
};
|
||
}
|
||
|
||
export function personSourceKeys(): readonly PersonImportSource[] {
|
||
return PERSON_IMPORT_SOURCES;
|
||
}
|