FKITDEV-8639: SL Discrepancy Between Report UI and Excel Export
Summary
The service level (SL) ratio shown in the video calls report UI differs significantly from the SL value in the exported Excel file — beyond what rounding could explain. Reported by Zsuzsa on the NUSZ deployment. She notes it wasn’t a problem before, only noticed today.
Related Ticket
A related report states that lost + served calls ≠ incoming calls count in the daily statistics export.
Not a Regression
RCA confirmed this is a latent design flaw since commit
1a8c1ef561(FKDEV-284 — “Add sum column for service level report download”). The user noticed it today likely because recent data had unusually uneven call distribution across periods, making the discrepancy visible.
Root Cause (5 Whys)
Why 1: SL “Sum” value disagrees with per-period values
CallsReportService.js line 98 builds the Excel SL row by concatenating per-period SL values (chartData.service_level) with the overall SL (report.report.service_level). These use different formulas:
- Per-period (line 682):
Math.round(((periodCalls - periodLateAnswers) / periodCalls) * 100)— independently rounded - Overall (line 570):
Math.round(((totalCalls - totalLateAnswers) / totalCalls) * 100)— volume-weighted
Why 2: These are mathematically different (Simpson’s Paradox)
Example: Period A = 2 calls, 1 late (SL=50%). Period B = 100 calls, 0 late (SL=100%). Naive average = 75%. Weighted overall = 99/102 = 97%. A 22-point gap.
Why 3: The Sum column was bolted on after the original design
Original design (commit f3abed8670, FKITDEV-10) only had per-period values. The Sum column was added later (commit 1a8c1ef561, FKDEV-284) using the weighted overall calculation, without reconciling with the per-period display.
Why 4: No reconciliation guard exists
No unit test asserts that the Sum column value and per-period values are mathematically consistent. No column-count assertion ensures all Excel rows have the same width.
All Bugs Found (4)
Bug A: SL Discrepancy (PRIMARY)
Per-period SL values are independently Math.round’d. The Sum column uses the volume-weighted overall. With uneven call volumes across periods, discrepancy can be 30+ percentage points.
Files: CallsReportService.js lines 570-572, 681-683, 98
Bug B: Excel Column Misalignment
Header row adds ‘Sum’ column (line 89-90), but only service_level row has a Sum value. All other rows (calls, calltimes, late_answers, etc.) are one cell shorter, causing visual misalignment in Excel.
Files: CallsReportService.js lines 88-98
Bug C: Locale Parameter Swallowed
ReportsService.js:63 passes true (intended as isDownload) to CallsReportService.prepareReportData:142 which receives it as locale. The actual locale from getDownloadableResult is never forwarded. Dictionary falls back to default, masking the bug in Hungarian-only deployments.
Files: ReportsService.js line 63, CallsReportService.js line 142
Bug D: Counting Gap (Related Ticket)
waiting_calls = history.length (all entries). exits requires leaveAt to be set. Customers who close browser without triggering leave event are counted in waiting_calls but neither calls nor exits. Hence: waiting_calls > calls + exits.
Files: CallsReportService.js lines 742-744, 843-844
NUSZ Customization Check
NUSZ branch has no overrides for report calculation. Only translation entries in customization/translations/reports.trans.js. The callOverride('reportCallsDownload:rows') hook at line 88 has no NUSZ handler registered.
Fixes
Bug A (SL discrepancy)
Keep the weighted overall Sum (mathematically correct). Add Sum values for all rows so users can verify the math themselves.
Bug B (column alignment)
Append Sum values to all rows in reportRows() (lines 92-110), not just service_level. Every row must have the same column count as the header.
Bug C (locale parameter)
Change ReportsService.js:63 from:
this.prepareReportData(reportParams, format, ..., filterUserId, true)to:
this.prepareReportData(reportParams, format, ..., filterUserId, locale)Bug D (counting gap)
Count history entries where !chatStarted && !leaveAt (older than a timeout threshold) as implicit exits. Or add abandoned as a new metric.
Execution Order
- Bug C (one-line fix, zero risk)
- Bug B (add missing Sum cells)
- Bug A (add Sum values + verify weighted SL is correct)
- Bug D (decide on ghost entry policy)
Blast Radius
- Bug A/B: No downstream impact — additive changes to Excel export
- Bug C: Could change translation output for non-Hungarian locales — test with
locale='en' - Bug D: Will increase
exitscount on dashboards. SL formula does NOT use exits (confirmed line 570), so SL unaffected
Prevention
- Unit test with uneven call volumes asserting Sum == weighted average
- Column count assertion in
reportRows()— all rows same.length - Migrate
CallsReportService.jsto TypeScript to catch parameter mismatches
Implementation Log
Task 1 (Bug C) — DONE — commit on feature/FKITDEV-8639
ReportsService.js:63 — changed true to locale. Test: prepareReportData locale forwarding passes.
Task 2 (Bug B + A partial) — DONE — commit 543f293c38
CallsReportService.js lines 92–115: appended .concat(report.report.X) to every data row so all rows are the same width as the header. Tests:
all rows should have the same number of columns as the header row— passesSum column should contain correct aggregate values— passes
Gotchas discovered during implementation:
node_moduleswas absent — had to install deps withyarn installyarnwas not on PATH — installed globally vianpm install -g yarn- Tests require
NODE_ENV=dev DEV_DOMAIN=test.localto loadgetconfigconfig file - Module exports as
{ CallsReportService, ActivitySessionInfo }— Task 1 test had wrong require; fixed toconst { CallsReportService } = require(...)
Links
- vuer_oss — report system
- rabbitmq-communication — RPC report endpoints