diff --git a/.env.example b/.env.example index 37f5120..2de4eb9 100644 --- a/.env.example +++ b/.env.example @@ -146,12 +146,13 @@ WEBEX_TOKENS_PATH=./config/webex-service-tokens.json # WAN_STANDARD_ENABLED=true # --- WAN look-back window (minutes) used when the operator doesn't -# pass `--window` on /voicediag. Applies to healthscore + LQM. -# Alarms window is max(60, WAN_STANDARD_WINDOW_MINUTES) since -# sub-hour alarm queries are usually too noisy to be actionable. -# Prisma's finest LQM bucket is 5m; anything <= 5 is snapped up. -# Common values: 15 (default, real-time), 60, 360 (6h), 1440 (24h). -# WAN_STANDARD_WINDOW_MINUTES=15 +# pass `--window` on /voicediag (and always used by /phonestatus). +# Applies to healthscore + LQM. Alarms window is +# max(60, WAN_STANDARD_WINDOW_MINUTES) since sub-hour alarm queries +# are usually too noisy to be actionable. Prisma's finest LQM +# bucket is 5m; anything <= 5 is snapped up. +# Common values: 15 (real-time), 60, 360 (6h), 1440 (24h — default). +# WAN_STANDARD_WINDOW_MINUTES=1440 # ----------------------------------------------------------------------------- # /voicediag — Store voice-line standards diff --git a/commands/help.js b/commands/help.js index f02a49d..4482d2a 100644 --- a/commands/help.js +++ b/commands/help.js @@ -18,7 +18,8 @@ const SHORT_HELP = { // AV / phones avstatus: 'AV / device status for a store (alias: /wostatus)', - phonestatus: 'DECT + IP phone status for a store', + phonestatus: 'DECT + IP phone status for a store (plus 24h WAN follow-up)', + voicediag: 'Deep voice diagnostic: Webex Calling features + SD-WAN quality with fix cards', // Jira jirahistory: 'Recent Jira tickets for a store (optionally filtered by component)', @@ -56,9 +57,39 @@ const LONG_HELP = { notes: [ 'Shows DECT basestations + IP phones with Meraki links.', 'Detailed mode adds firmware, serial, SIP details and errors.', + 'When the store is Prisma SD-WAN managed (site name `CG` padded to 5 digits), a follow-up **WAN Diagnostics** message arrives with per-path latency/jitter/loss/MOS, site healthscore, and any alarms — averaged over the last 24 hours (override via `WAN_STANDARD_WINDOW_MINUTES`).', + 'For an in-depth voice diagnostic with per-user Webex Calling checks + fix cards, use `/voicediag `.', 'Web dashboard: `/phone-store-dashboard.html`.', ], }, + voicediag: { + title: '/voicediag', + usage: [ + '/voicediag ', + '/voicediag detail', + '/voicediag --only ', + '/voicediag --window ', + '/voicediag list-checks', + ], + examples: [ + '/voicediag 782', + '/voicediag 782 detail', + '/voicediag 782 --window 15m', + '/voicediag 782 --window 6h --only wanLatency,wanJitter,wanLoss,wanMos', + '/voicediag 782 --only dnd,callForwarding,voicemail', + '/voicediag list-checks', + ], + notes: [ + 'Runs a full battery of per-user Webex Calling checks (DND, call forwarding, voicemail, call intercept, call waiting, outgoing permission, etc.) plus eight SD-WAN checks (site, healthscore, link state, latency, jitter, loss, MOS, alarms) against the store\'s Prisma tenant.', + 'Default view hides OK checks and highlights errors/warnings/skipped. Pass `detail` (or `detailed`) to also see OK checks with expanded per-link tables + thresholds + roll-ups.', + 'Fixable issues (e.g. DND on, forwarding to wrong number) post a per-issue confirmation card. A single "apply all" card lets you fix everything at once after reviewing.', + '`--only` restricts the run to specific check ids (comma-separated). Use `/voicediag list-checks` to see every registered id + its scope.', + '`--window` overrides the WAN look-back window (accepts `15m`, `1h`, `6h`, `24h`, `1d`, or a bare minute count). Applies to healthscore + LQM (latency/jitter/loss/MOS) fetches. Alarms are floored at 60m. Default is 24h — set `WAN_STANDARD_WINDOW_MINUTES` for a different global default.', + 'Voice-quality thresholds default to ITU-T G.114 / RFC 3550. Override any of them via `WAN_STANDARD_*` env vars (see `.env.example`). Kill-switch: `WAN_STANDARD_ENABLED=false` silences the whole WAN bucket.', + 'HTTP equivalent: `?storeNum=[&detailed=true][&only=id1,id2][&window=15m]`. HTTP callers see the markdown snapshot only — cards are chat-only.', + 'Audit log: every remediation apply/cancel is logged under `voicediag:audit` with the requester identity.', + ], + }, 'provision-dect': { title: '/provision-dect', usage: ['/provision-dect '], @@ -196,7 +227,7 @@ const LONG_HELP = { const GROUPS = [ { title: 'Work orders', keys: ['wohistory', 'wosummary', 'woattachments'] }, - { title: 'AV & phones', keys: ['avstatus', 'phonestatus'] }, + { title: 'AV & phones', keys: ['avstatus', 'phonestatus', 'voicediag'] }, { title: 'Jira', keys: ['jirahistory', 'jiraticket', 'jirapoll'] }, { title: 'Provisioning', keys: ['provision-dect', 'provision-vc', 'vcmonitor'] }, { title: 'Admin & bulk', keys: ['offboarduser', 'webexhost', 'bulkavstatuscsv', 'bulkavswitchcsv', 'devicesbymodel'] }, diff --git a/services/enrichment/sdwanEnrichment.js b/services/enrichment/sdwanEnrichment.js index a8bfefc..952b09b 100644 --- a/services/enrichment/sdwanEnrichment.js +++ b/services/enrichment/sdwanEnrichment.js @@ -243,17 +243,25 @@ export async function collectSdwanForStore(storeNum, opts = {}) { }; } +// Default WAN look-back window (24h in minutes). Chosen because the +// primary users of /phonestatus + /voicediag are triaging trouble +// tickets after the fact, where "was voice quality bad today?" is +// more useful than "how does it look right this second?". Operators +// wanting a live snapshot can pass `--window 15m` on /voicediag or +// set WAN_STANDARD_WINDOW_MINUTES=15 in the environment. +const DEFAULT_WINDOW_MINUTES = 1440; + /** * Snap an arbitrary window request to a reasonable, sane value. - * Falls back to the env default (WAN_STANDARD_WINDOW_MINUTES) or 15. - * Prisma's own upper bound seems to sit around 30 days for aiops - * queries, but voice-quality signal degrades past a day of averaging - * so we cap at 24h for now. + * Falls back to the env default (WAN_STANDARD_WINDOW_MINUTES) or 1440 + * (24h). Prisma's own upper bound seems to sit around 30 days for + * aiops queries, but voice-quality signal degrades past a day of + * averaging so we cap at 24h for now. */ function normalizeWindowMinutes(m) { const raw = Number.isFinite(Number(m)) && Number(m) > 0 ? Number(m) - : Number(process.env.WAN_STANDARD_WINDOW_MINUTES) || 15; + : Number(process.env.WAN_STANDARD_WINDOW_MINUTES) || DEFAULT_WINDOW_MINUTES; // Clamp to [1, 1440] (1 min .. 24 h). return Math.max(1, Math.min(1440, Math.floor(raw))); } diff --git a/services/voiceDiag/README.md b/services/voiceDiag/README.md index 2b47a74..2e7693b 100644 --- a/services/voiceDiag/README.md +++ b/services/voiceDiag/README.md @@ -12,18 +12,21 @@ objects, and hands them to the renderer + adaptive-card layer in /voicediag default: hides OK checks, posts fixable cards /voicediag detail include OK checks + expand every details block /voicediag --only dnd,callForwarding -/voicediag --window 24h override the WAN look-back window (default 15m) +/voicediag --window 15m narrow the WAN look-back (default 24h) /voicediag list-checks enumerate every registered check + its scope ``` -HTTP path: `GET /voicediag?storeNum=[&detailed=true][&only=dnd,callWaiting][&window=24h]`. +HTTP path: `GET /voicediag?storeNum=[&detailed=true][&only=dnd,callWaiting][&window=15m]`. `--window` accepts `Nm` / `Nh` / `Nd` shorthand (e.g. `15m`, `1h`, `6h`, `24h`, `1d`) or a bare integer of minutes. Applies to the WAN healthscore + LQM (per-path latency/jitter/loss/MOS) fetches. Alarms are floored at 60m regardless — sub-hour alarm queries are usually too noisy to be actionable. Global default is -`WAN_STANDARD_WINDOW_MINUTES` (env, defaults to 15). +`WAN_STANDARD_WINDOW_MINUTES` (env, defaults to 1440 / 24h). The +same default is used by the `/phonestatus` WAN follow-up (no CLI +override on that surface — set the env if you want a different value +globally). HTTP callers get the markdown snapshot only — remediation cards are chat-only. diff --git a/tests/sdwanEnrichment.test.js b/tests/sdwanEnrichment.test.js index 065edc1..0a5e6db 100644 --- a/tests/sdwanEnrichment.test.js +++ b/tests/sdwanEnrichment.test.js @@ -768,23 +768,26 @@ test('collectSdwanForStore: reports the effective window on the returned payload }); setupSaseEnv(fake.baseUrl); try { - // Default (no opts): 15 min (also the env default) + // Default (no opts): 24h — the shipping default optimised for + // "was the site healthy today" over "is it healthy right now". + // Override via WAN_STANDARD_WINDOW_MINUTES or --window on /voicediag. let data = await collectSdwanForStore(782); + assert.equal(data.window?.minutes, 1440); + assert.equal(data.window?.alarmMinutes, 1440, + 'alarms match the requested window when it is >= 60m'); + + // Explicit 15m override → both windows go to 15/60 (alarm floor) + data = await collectSdwanForStore(782, { windowMinutes: 15 }); assert.equal(data.window?.minutes, 15); assert.equal(data.window?.alarmMinutes, 60, 'alarms floor at 60m even when the requested window is smaller'); - // Explicit 24h override → both windows go to 1440 - data = await collectSdwanForStore(782, { windowMinutes: 1440 }); - assert.equal(data.window?.minutes, 1440); - assert.equal(data.window?.alarmMinutes, 1440); - // Out-of-range values are clamped to [1, 1440] data = await collectSdwanForStore(782, { windowMinutes: 99999 }); assert.equal(data.window?.minutes, 1440, 'capped at 24h'); data = await collectSdwanForStore(782, { windowMinutes: -5 }); - assert.equal(data.window?.minutes, 15, 'invalid → falls to env default'); + assert.equal(data.window?.minutes, 1440, 'invalid → falls to env default (24h)'); } finally { await fake.close(); _resetSitesCache(); _resetPrismaAuthCache(); clearEnv(); }