Replace /phonestatus follow-ups with /voicestatus, /wanstatus, /phonediag, and /dectdiag; extend /voicediag with relay probes and section-based MPP diagnostics. Co-authored-by: Cursor <cursoragent@cursor.com>
154 lines
3.8 KiB
JavaScript
154 lines
3.8 KiB
JavaScript
// services/jiraPoller/enrichmentRules.js
|
|
// Rule-based enrichment planner for the hourly Jira poller.
|
|
|
|
export const COMM_SERVICES_COMPONENT = 'Communication Services';
|
|
|
|
export const CHECK_IDS = {
|
|
voicestatus: 'voicestatus',
|
|
callreport: 'callreport',
|
|
avstatus: 'avstatus',
|
|
};
|
|
|
|
const CALL_QUALITY_PATTERNS = [
|
|
/\bgarbled\b/i,
|
|
/\bstatic(?:y|s)?\b/i,
|
|
/\bchoppy\b/i,
|
|
/\bmuffled\b/i,
|
|
/\bcan(?:'t| ?not) connect\b/i,
|
|
/\bnot connecting\b/i,
|
|
/\bcan(?:'t| ?not) call\b/i,
|
|
/\bno dial tone\b/i,
|
|
/\bone[- ]way\b/i,
|
|
/\bdropped calls?\b/i,
|
|
/\bcall quality\b/i,
|
|
/\bbad audio\b/i,
|
|
/\bpoor audio\b/i,
|
|
/\bvoice quality\b/i,
|
|
/\bno audio\b/i,
|
|
/\bdead air\b/i,
|
|
/\bintermittent\b/i,
|
|
];
|
|
|
|
const SPAM_PATTERNS = [
|
|
/\bspam\b/i,
|
|
/\brobocall/i,
|
|
/\brobo[- ]?call/i,
|
|
/\bnuisance calls?\b/i,
|
|
/\bunwanted calls?\b/i,
|
|
/\bauto[- ]?dial/i,
|
|
/\btelemarketer/i,
|
|
/\bprank calls?\b/i,
|
|
/\bharassing calls?\b/i,
|
|
];
|
|
|
|
function hasComponent(components, name) {
|
|
return (components || []).some((c) => c?.name === name);
|
|
}
|
|
|
|
function ticketText(ticket) {
|
|
const summary = ticket?.summary || '';
|
|
const description = ticket?.description || '';
|
|
return `${summary}\n${description}`.toLowerCase();
|
|
}
|
|
|
|
function matchesAny(text, patterns) {
|
|
return patterns.some((re) => re.test(text));
|
|
}
|
|
|
|
/**
|
|
* Extract store number from summary text (e.g. "Store 2477 - ...").
|
|
* @param {string} summary
|
|
* @returns {string|null}
|
|
*/
|
|
export function extractStoreFromSummary(summary) {
|
|
if (!summary) return null;
|
|
const m = String(summary).match(/\bstore\s+(\d{2,6})\b/i);
|
|
return m ? m[1] : null;
|
|
}
|
|
|
|
/**
|
|
* @param {object} ticket
|
|
* @param {string} ticket.summary
|
|
* @param {string} [ticket.description]
|
|
* @param {Array<{name?: string}>} [ticket.components]
|
|
* @param {object} classification from classifyTicket()
|
|
* @param {string|null} classification.storeNum
|
|
* @param {'phone'|'av'|'skip'} classification.kind
|
|
* @returns {{
|
|
* checks: string[],
|
|
* matchedRules: string[],
|
|
* storeNum: string|null,
|
|
* skip: boolean,
|
|
* skipReason?: string,
|
|
* }}
|
|
*/
|
|
export function resolveEnrichmentPlan(ticket, classification) {
|
|
const text = ticketText(ticket);
|
|
const isCommServices = hasComponent(ticket?.components, COMM_SERVICES_COMPONENT);
|
|
const callQuality = isCommServices && matchesAny(text, CALL_QUALITY_PATTERNS);
|
|
const spam = isCommServices && matchesAny(text, SPAM_PATTERNS);
|
|
|
|
let storeNum = classification?.storeNum || null;
|
|
if (!storeNum && (callQuality || spam)) {
|
|
storeNum = extractStoreFromSummary(ticket?.summary);
|
|
}
|
|
|
|
const matchedRules = [];
|
|
if (callQuality) matchedRules.push('comm-call-quality');
|
|
if (spam) matchedRules.push('comm-spam');
|
|
|
|
if (callQuality || spam) {
|
|
if (!storeNum) {
|
|
return {
|
|
checks: [],
|
|
matchedRules,
|
|
storeNum: null,
|
|
skip: true,
|
|
skipReason: 'Communication Services symptom matched but no store number found',
|
|
};
|
|
}
|
|
const checks = [];
|
|
if (callQuality) {
|
|
checks.push(CHECK_IDS.voicestatus, CHECK_IDS.callreport);
|
|
} else if (spam) {
|
|
checks.push(CHECK_IDS.callreport);
|
|
}
|
|
return { checks: [...new Set(checks)], matchedRules, storeNum, skip: false };
|
|
}
|
|
|
|
if (classification?.kind === 'skip' || !storeNum) {
|
|
return {
|
|
checks: [],
|
|
matchedRules: [],
|
|
storeNum: null,
|
|
skip: true,
|
|
skipReason: classification?.reason || 'no store number',
|
|
};
|
|
}
|
|
|
|
if (classification.kind === 'phone') {
|
|
return {
|
|
checks: [CHECK_IDS.voicestatus],
|
|
matchedRules: ['default-phone'],
|
|
storeNum,
|
|
skip: false,
|
|
};
|
|
}
|
|
|
|
if (classification.kind === 'av') {
|
|
return {
|
|
checks: [CHECK_IDS.avstatus],
|
|
matchedRules: ['default-av'],
|
|
storeNum,
|
|
skip: false,
|
|
};
|
|
}
|
|
|
|
return {
|
|
checks: [],
|
|
matchedRules: [],
|
|
storeNum,
|
|
skip: true,
|
|
skipReason: `unsupported kind: ${classification?.kind}`,
|
|
};
|
|
}
|