// services/renderers/voiceReportRenderer.js function envInt(name, fallback) { const v = Number(process.env[name]); return Number.isFinite(v) ? v : fallback; } function fmtTime(iso) { if (!iso) return '—'; try { return new Date(iso).toISOString().replace('T', ' ').replace(/\.\d{3}Z$/, ' UTC'); } catch { return String(iso); } } function countPoorQuality(calls) { const mosWarn = Number(process.env.WAN_STANDARD_APP_MOS_WARN || 4.0); const lossWarn = Number(process.env.WAN_STANDARD_APP_LOSS_WARN_PCT || 5); const jitterWarn = Number(process.env.WAN_STANDARD_APP_JITTER_WARN_MS || 30); let poor = 0; for (const call of calls) { const q = call.media || call.wan || {}; if (q.mos != null && q.mos < mosWarn) poor++; else if (q.loss != null && q.loss > lossWarn) poor++; else if (q.jitter != null && q.jitter > jitterWarn) poor++; } return poor; } function renderOutcomeBreakdown(outcomes) { const entries = Object.entries(outcomes || {}); if (!entries.length) return '_No outcome data._'; return entries .sort((a, b) => b[1] - a[1]) .map(([k, v]) => `- **${k}:** ${v}`) .join('\n'); } function renderReachSummary(summary) { const lines = []; if (summary.inbound > 0) { lines.push( `- **Inbound reached a phone:** ${summary.inboundReachedPhone} of ${summary.inbound}`, ); if (summary.inboundAaOnly > 0) { lines.push(`- **Inbound stopped at auto-attendant:** ${summary.inboundAaOnly}`); } } if (summary.outbound > 0) { lines.push( `- **Outbound connected:** ${summary.outboundConnected} of ${summary.outbound}`, ); } if (summary.abnormal > 0) { lines.push(`- **Abnormal outcomes:** ${summary.abnormal}`); } return lines.join('\n'); } function renderCallReachLine(call) { if (call.direction === 'inbound') { if (call.reachedPhone) { const who = [call.endpointUser, call.endpointModel].filter(Boolean).join(' / '); return `reached ${who || 'phone'}`; } if (call.reachedAttendant) { const key = call.aaKeyPress ? ` (AA key ${call.aaKeyPress})` : ''; return `auto-attendant only${key}`; } return 'not answered'; } if (call.direction === 'outbound') { return call.connected || call.reachedPhone ? 'connected' : 'not connected'; } return call.outcome || '—'; } /** * @param {object} report from collectVoiceReport * @param {{ detail?: boolean }} opts */ export function renderVoiceReportMarkdown(report, opts = {}) { if (!report?.ok) { let md = `**Voice report** — Store ${report?.store?.storeNum || '?'}\n\n`; md += `_${report?.reason || report?.window?.reason || 'Unable to build report.'}_\n`; return md.trim(); } const { store, window, cdr, mediaQuality, prisma, joined } = report; const calls = joined.calls || joined.legs || []; const summary = joined.summary || {}; const maxRows = envInt('VOICEREPORT_MAX_DETAIL_ROWS', 25); const detail = Boolean(opts.detail); let md = `**Voice report — Store ${store.storeNum}**\n\n`; md += `- **Date:** ${window.label} (${window.mode})\n`; md += `- **Timezone:** ${window.timeZone}\n`; md += `- **Window:** ${fmtTime(window.startTime)} → ${fmtTime(window.endTime)}\n`; md += `- **Location:** ${store.locationName}\n`; if (store.dialNumber) md += `- **Main:** ${store.dialNumber}\n`; md += '\n**Call volume (CDR)**\n'; if (!cdr.available) { md += `_CDR unavailable:_ ${cdr.reason || 'unknown'}\n`; } else { md += `- **Total calls:** ${summary.total ?? calls.length}\n`; md += `- **Inbound:** ${summary.inbound ?? 0} | **Outbound:** ${summary.outbound ?? 0}\n`; md += `- **CDR legs fetched:** ${cdr.rawCount ?? '—'} → **${summary.total ?? calls.length}** correlated call(s)\n`; const reach = renderReachSummary(summary); if (reach) md += `${reach}\n`; md += '\n**Call outcomes**\n'; md += `${renderOutcomeBreakdown(summary.outcomes)}\n`; } md += '\n**Media quality (Webex report)**\n'; if (!mediaQuality.available) { md += `_Unavailable:_ ${mediaQuality.reason || 'not generated'}\n`; } else { md += `- **Rows in window:** ${mediaQuality.rows?.length ?? 0} (from ${mediaQuality.rawCount ?? '?'} downloaded)\n`; const poor = countPoorQuality(calls.filter((c) => c.media)); md += `- **Calls with quality flags:** ${poor}\n`; } md += '\n**WAN voice (Prisma DPI)**\n'; if (!prisma.available) { md += `_Unavailable:_ ${prisma.reason || 'not configured'}\n`; } else { const m = prisma.appAudio?.mos; md += `- **App:** ${prisma.appAudio.appName}\n`; if (m?.min != null) md += `- **Worst MOS in window:** ${m.min}\n`; if (m?.max != null) md += `- **Best MOS in window:** ${m.max}\n`; if (prisma.appAudio?.detailsUrl) { md += `- [View in Prisma SCM](${prisma.appAudio.detailsUrl})\n`; } } if (detail && calls.length) { md += '\n**Call detail**\n'; const show = calls.slice(0, maxRows); for (const call of show) { const dir = String(call.direction || 'unknown').toUpperCase(); const outcome = [call.outcome, call.outcomeReason].filter(Boolean).join(' / '); const flag = call.abnormal || !call.normalOutcome ? ' ⚠️' : ''; md += `\n- **${fmtTime(call.start)}** ${dir} ${call.duration || 0}s — ${outcome || '—'}${flag}\n`; md += ` ${call.callingNumber || '?'} → ${call.calledNumber || '?'} (${call.legCount} leg(s), ${renderCallReachLine(call)})\n`; if (call.media) { md += ` Media: MOS ${call.media.mos ?? '—'}, jitter ${call.media.jitter ?? '—'}, loss ${call.media.loss ?? '—'}\n`; } if (call.wan) { md += ` WAN≈: MOS ${call.wan.mos ?? '—'}, jitter ${call.wan.jitter ?? '—'}, loss ${call.wan.loss ?? '—'}\n`; } } if (calls.length > maxRows) { md += `\n_${calls.length - maxRows} more calls — use Control Hub Detailed Call History for full export._\n`; } } md += `\n*Generated in ${report.elapsedMs}ms*`; return md.trim(); }