Client:
Government Agency
Workstream 02
Bulk Reporting Enhancements
Eliminated 58% of duplicate report submissions and reduced "where is my report?" support tickets by 41% by introducing a canonical 7-state lifecycle model, real-time WebSocket progress visibility, and a 4-step modal that reduced report configuration abandonment by 40%.
The Problem
The IWMS platform generated reports for thousands of assets, users, and compliance events. These bulk operations ran asynchronously—but the system gave users no visibility into what was happening. Reports were submitted into a black box. Users couldn't distinguish between "processing," "queued," "failed," or "still running from 20 minutes ago."
The consequence: users resubmitted the same report repeatedly out of uncertainty, creating system overload and queue congestion. Operations managers couldn't plan downstream work around report completion. "Where is my report?" was the #2 support ticket category—a user experience failure masquerading as a process problem.
My Role
Lead UX Designer
State model + UI design
Timeline
4 months
Discovery through handoff
Collaborators
Cross-functional
PM, 3 engineers, ops team, support
Volume
100K+ reports/day
Across all client orgs
Constraints
•
Technical: WebSocket support wasn't available for all report types; non-critical operations had to fall back to polling
•
Regulatory: Complete audit trails required for compliance (SOX, SEC)—report metadata could not be deleted
•
Performance: 100K+ daily reports couldn't all load at once — virtual scrolling and server-side filtering required
•
Terminology: Three internal teams used different words for the same statuses — alignment required facilitated workshops
Impact
58%
Fewer duplicate report submissions
41%
"Where is my report?" tickets eliminated
85%
Reduction in API polling load
40%
Fewer abandoned report configurations
Research & Insights
Three findings that directly shaped the system design
Research Finding
Session analysis showed users returned to the reports list an average of 4.3 times while waiting for a single long-running report. For reports taking 30+ minutes, resubmission rate was 67% — users concluded the first submission had failed or was lost.
Method: Session recording analysis (n=200 sessions) + server-side duplicate detection
Insight
Users weren't impatient—they were uncertain. Without progress visibility, they had no signal to differentiate "still working" from "silently failed." The 67% resubmission rate wasn't impatience; it was a rational response to an opaque system. The solution wasn't to make reports faster — it was to reduce uncertainty while they ran.
Design Decision
WebSocket real-time progress + ETA estimation. Even when we couldn't make reports faster, showing percentage complete with a time estimate dramatically reduced uncertainty. Collaborated with engineering on a WebSocket connection that broadcasts progress every 5 seconds. Users could close the page and trust the system would notify them.
Research Finding
Stakeholder interviews across 3 teams (engineering, operations, support) revealed they used 6 different terms for the same report states. Engineering used "Queued/Running/Done." Operations said "Pending/Processing/Completed." Support said "In Queue/Active/Finished." Users saw all three vocabularies in different parts of the UI.
Method: Terminology audit across 3 teams + UI copy inventory (14 status variants found)
Insight
Terminology inconsistency isn't cosmetic—it's a cross-team communication failure. When users called support saying "my report is still processing," support couldn't tell if the user meant "Queued," "Running," or "Finalizing" in the backend system. Mismatched vocabulary compounded every support interaction. Alignment on a single canonical model would reduce support handle time by removing interpretation overhead.
Design Decision
Facilitated a cross-team terminology workshop to define a single canonical 7-state model. I drove this as a UX exercise, not an engineering one—the language users see had to start from user mental models, not backend state names. The resulting glossary became a contract between teams. All UI copy, API response labels, and support documentation aligned to the same vocabulary.
Research Finding
Task analysis showed 31% of report configuration sessions were abandoned before submission. The abandonment pattern was clustered at the filter configuration step—a single-screen form with 14+ fields shown simultaneously. Users either submitted with incomplete filters (leading to incorrect reports) or gave up.
Method: Funnel analysis on report creation flow + exit survey (n=47 responses)
Insight
The 14-field form wasn't complex because reports are complex—it was complex because the form made no distinction between required decisions (report type, date range) and optional refinements (advanced filters). Users faced peak cognitive load at the step with the most optionality. Progressive disclosure would reduce perceived complexity without reducing capability.
Design Decision
4-step multi-step modal with API-backed partial saves. Step 1: report type (required, 4 choices). Step 2: data scope and date range (required). Step 3: optional filters (default to "all"). Step 4: scheduling (if recurring). Each step is independently completable. Partial saves meant users who abandoned could return to a draft in progress.
Key Decisions & Tradeoffs
The choices that shaped the system — including what I explicitly didn't build
1
7 states vs. a simpler 3-state model — granularity in service of compliance
Options Considered
A.
3-state: Pending / Processing / Complete (simple, easy to explain)
B.
7-state canonical model — full lifecycle visibility
Why Option B
Operations teams needed to distinguish Suspended (temporary, reversible) from Deactivated for compliance reporting. Support needed to tell users whether they were "Queued" (not started yet) vs. "Processing" (actively running) — same advice to a user differs based on which state they're in. 3 states collapsed distinctions that users and compliance auditors needed.
Tradeoffs
Higher engineering implementation cost (7 state transitions vs. 3)
More complex onboarding for new users — mitigated with status tooltips on hover
Adopted platform-wide for all async operations — status system became a shared standard
2
WebSocket push vs. polling — accepting technical complexity for UX quality
Options Considered
A.
Client polling every 30s (simpler, no infrastructure change)
B.
WebSocket push for real-time progress — higher quality, more infrastructure
C.
Email-only notification when complete (no in-app progress)
Why Option B
Research showed users polled the page themselves every 2–3 minutes. Polling every 30s wouldn't change behavior. WebSocket allowed progress every 5 seconds — enough granularity to feel live. Email-only (Option C) was insufficient for short-running reports (<5 min). The infrastructure cost was justified by the 58% duplicate reduction it enabled.
Tradeoffs
WebSocket not supported for all report types — non-critical reports fell back to 30s polling with a "checking for updates" indicator
85% reduction in API polling load — fewer requests than the old 2–3 min manual refresh pattern
3
Multi-step modal vs. inline editing — focus over efficiency
Options Considered
A.
Inline row editing in the grid (low friction, familiar pattern)
B.
4-step modal with progressive disclosure — higher-quality configuration
C.
Dedicated full-page configuration form
Why Option B
Inline editing (Option A) was rejected because configuration decisions cascade — report type determines which filters are available. That dependency chain can't be expressed in a flat row edit. Full-page (Option C) adds navigation overhead for what users experience as a quick setup task. Modal keeps context without navigation cost.
Tradeoffs
Modal adds 1 layer of interaction vs. inline — acceptable for the complexity being managed
40% fewer abandoned configurations — partial save API meant returning to a draft was seamless
Step-level validation catches errors earlier — fewer "submitted with wrong date range" corrections
What I explicitly chose not to build — and why
Real-time ETA estimation
Proposed for the progress bar. Rejected: ETA accuracy for variable-sized reports would have been poor — a 30-minute estimate that shows 45 minutes is worse than no estimate. We showed percentage complete only. ETA is on the roadmap once we have enough historical data for reliable ML-based estimation.
In-app report preview before download
Requested by product. Rejected for v1: rendering 10,000-row report previews in-browser would have required significant infrastructure. The use case (verify format before downloading) was better served by a standardized report format guide. Deferred to v2 with PDF preview endpoint.
Report scheduling via natural language
Suggested as a "delight" feature ("run every first Monday"). Rejected: natural language parsing adds ambiguity for compliance-critical scheduling. A structured scheduler (day/week/month with time picker) is less charming but unambiguous — appropriate for regulatory reporting contexts.
Report Lifecycle State Model
The canonical 7-state model that became the platform standard
Before this work, three internal teams used six different terms for the same states. This canonical model — defined through facilitated cross-team workshops — became the single source of truth for all async operations platform-wide. UI copy, API response labels, and support documentation all aligned to these definitions.
This model was adopted for all async platform operations: User ID imports, bulk updates, and export jobs.
Success Path
📨
Submitted
Immediately visible to user
⏳
Queued
Position in queue shown
⚙
Processing
Real-time % progress
📦
Finalizing
Brief final step
✓
Completed
Push notification sent
Failure Path
⚙
Processing
From any active state
⚠
Failed
Specific error shown
↩
Queued
Retry returns to queue
🗂
Expired
File deleted per retention policy
Why "Finalizing" as a Separate State?
Operations teams needed to distinguish "generation complete" from "download available." File assembly (packaging, encryption, storage) takes 10–30 seconds. Without Finalizing, users saw Processing → Completed with a blank progress bar and thought the system froze.
Failed State: Actionable Errors
Generic "Report failed" messages were the #1 complaint in exit surveys. Each failure mode now shows a specific reason + one-click remediation action: "Filter returned 0 records — adjust date range" with a direct link to reconfigure.
Expired: Compliance Requirement
SOX and SEC audit requirements meant report metadata must be retained even when files are deleted per retention policy. The Expired state shows metadata and generation date even after the file is gone — audit trail preserved, storage controlled.
Service Blueprint: Report Generation Lifecycle
What the user sees vs. what happens behind the scenes
Layer
1. Configure
2. Submit
3. Processing
4. Complete / Error
User Actions
Opens report creator
Selects type, sets scope and date range, adds optional filters, configures schedule
Clicks "Create Report"
Sees confirmation and is redirected to reports list with new entry visible
Monitors progress
Views live progress bar and % complete. May close page — will receive push notification on completion
Downloads or retries
Downloads file or reads specific error message with remediation action
Front-Stage (UI)
4-Step Modal
Progressive disclosure — only required fields per step. Partial save on each step. Step-level validation.
Confirmation Screen
Toast notification. New row appears in "Submitted" state in AG Grid immediately.
Real-Time Progress Bar
% complete updated via WebSocket every 5s. Status badge transitions in real-time. ETA withheld (accuracy too low).
Status Badge + Action
Completed: download button. Failed: "Retry" or "Edit Configuration" with specific error message.
— — — Line of Visibility (User sees above / System operates below) — — —
Back-Stage (API / Service)
Form Validation API
Validates filter combinations. Checks scope returns results. Returns errors per field for step-level guidance.
Queue Entry API
Duplicate detection: hash of parameters. If identical job already queued, links user to existing entry rather than creating new job.
Worker Processing + WebSocket
Query execution, data aggregation, file generation. Progress broadcast every 5s via WebSocket. Fallback: 30s polling.
File Assembly + Notification
Package output, apply encryption, upload to signed URL. Push notification via WebSocket + optional email for 10+ min reports.
Infrastructure
REST API + partial-save session store
Job Queue (Redis) + duplicate-detection hash store
Worker Pool (horizontal scaling) + WebSocket server
S3 / signed URL storage + notification service + audit log
Design-Engineering integration note: The duplicate-detection hash (Back-Stage, Submit column) was a collaborative solution to the 58% duplicate submission problem. Engineering proposed server-side deduplication. I designed the front-stage experience to match: instead of showing an error, the UI silently links the user to the existing job with a banner reading "A matching report is already in queue — we'll notify you when it's ready." No friction, no confusion.
User Journey: Operations Manager Generating a Monthly Compliance Report (Before)
Operations Manager — Monthly SOX Compliance Report Generation
Persona: Operations Manager — generates 10–15 compliance reports per month, each covering 500+ records
Phase 1
Configure & Submit
Actions
•
Opens report creation form — 14 fields on single screen
•
Works through fields without clear priority signal
•
Submits and receives no confirmation message
Pain Points
×
31% of configurations abandoned at the filter step
×
No clear indication of which fields are required vs. optional
×
Form submission gives no feedback that request was received
neutral
Phase 2
Wait & Wonder
Actions
•
Navigates away to continue other work
•
Returns to check status after ~15 minutes
•
Cannot find the report in the list
Pain Points
×
No progress indicator — no confirmation processing has started
×
Status of "Processing" is ambiguous — how long is normal?
×
"Processing" could mean queued, running, or hung
frustrated
Phase 3
Resubmit (Duplicate)
Actions
•
Concludes original submission was lost
•
Re-opens the configuration form
•
Submits identical report again
Pain Points
×
67% resubmission rate for reports running 30+ minutes
×
Duplicate submission creates two reports in queue
×
Queue congestion slows all reports for all users
frustrated
Phase 4
Eventually Downloads
Actions
•
Finds one or both versions in "Completed" state
•
Cannot tell which version is correct
•
Downloads one and checks manually
Pain Points
×
No timestamp on completion — cannot tell which ran when
×
No metadata about what configuration produced which report
×
Lost trust in system reliability — contacts support regardless
frustrated
Design Process
1
1. State Modeling & Terminology Workshop
Before any UI work, I facilitated a cross-team workshop to define a canonical state model. Engineering, operations, and support were in the room. My role was to translate between technical state machines and user-facing vocabulary — ensuring the final model served both compliance auditing and everyday user comprehension.
Methods
Cross-team workshop facilitation
State diagram exercises
Terminology audit (14 variants catalogued)
Deliverables
→
Canonical 7-state model
→
Status glossary with user-facing definitions
→
State transition rules and trigger conditions
2
2. Research & Behavioral Analysis
Session recording analysis quantified the duplicate submission problem. Exit surveys on abandoned configurations identified the filter-step as the primary drop-off point. Both findings were presented with specific data to secure engineering investment in WebSocket infrastructure and partial-save API.
Methods
Session recording analysis (n=200)
Funnel analysis on configuration flow
Exit survey (n=47 responses)
Deliverables
→
Behavioral analysis report
→
Priority matrix for impact vs. effort
→
Business case for WebSocket investment
3
3. Architecture & API Design Collaboration
Worked with backend engineers to design the duplicate-detection hash, WebSocket progress broadcast frequency, and partial-save API for the multi-step modal. Defined the UX requirements that drove infrastructure decisions — not just accepting technical constraints, but shaping them.
Methods
Architecture sessions with engineering
API contract design
WebSocket frequency optimization testing
Deliverables
→
Duplicate detection API spec
→
WebSocket event schema
→
Partial-save session API design
4
4. Design, Test, and Iterate
Three prototype iterations with operations team members. First round revealed that the Finalizing state needed clearer differentiation from Processing. Second round validated the multi-step modal flow. Third round tested the failure state error messages — specificity was the key variable.
Methods
Prototype testing (3 rounds, 6 participants)
A/B test on error message specificity
Accessibility audit before handoff
Deliverables
→
High-fidelity prototype
→
Usability test findings + iteration log
→
Accessibility audit report
High-Fidelity Design Solutions
AG Grid with Report Type Tabs + Real-Time Progress
Tab navigation organizes Recurring, One-Time, and System-Generated reports into focused views while reusing a single component. Real-time progress bars eliminate the primary driver of duplicate submissions.
after
After: Enhanced Bulk Reporting System
Tab organization + real-time status eliminates uncertainty and duplicate submissions
Bulk Reporting
Generate and manage compliance, operational, and audit reports
Recurring Reports
One-Time Reports
System-Generated
+ Create Report
Export All
⚙ Filters
Search reports...
Report Name
Schedule
Status
Progress
Last Completed
Actions
Monthly SOX Compliance
Monthly — 1st
Processing
65% · ~3 min remaining
Apr 1, 10:34 AM
Weekly Asset Inventory
Weekly — Mon
Completed
Ready to download
Today, 6:00 AM
Quarterly User Activity
Quarterly
Scheduled
—
Jan 1, 12:00 PM
Daily Transaction Log
Daily — 11 PM
Failed
Filter returned 0 records
Yesterday, 11:02 PM
Showing 4 of 12 recurring reports · 1 failed
← Prev
Page 1 of 3
Next →
Real-Time Progress (Finding #1)
WebSocket % progress eliminates the uncertainty that drove 67% resubmission rate. Users know the system is working.
Actionable Failure States
Specific error message in the table view: "Filter returned 0 records." Retry or reconfigure — no support ticket needed.
Tab Organization
Three report types separated into focused views. Single AG Grid component, different data contexts — no code duplication.
4-Step Configuration Modal
Breaking the 14-field form into 4 focused steps reduced configuration abandonment by 40%. Each step saves partially. Users can return to a draft. Step 3 (filters) is optional with sensible defaults — the primary cause of abandonment.
after
Create Report: Step 1 of 4 — Report Type
Progressive disclosure reduces cognitive load — required decisions first, optional refinements last
Create New Report
✕
1
Report Type
2
Data Scope
3
Filters (Optional)
4
Schedule
Select Report Type
Choose the type of report to generate. This determines available data and filters in the next steps.
Compliance Report
SOX, SEC, and regulatory compliance reporting across accounts and transactions
Asset Inventory
Track and audit asset locations, assignments, and status across facilities
User Activity
Platform engagement, login history, and permission usage by user or group
Maintenance Reports
Work orders, maintenance schedules, and completion status
Draft auto-saved
Cancel
Next: Data Scope
Progressive Disclosure (Finding #3)
Step 1 shows only 4 options. Step 3 (filters) is explicitly labeled "Optional" with defaults applied. The 14-field overwhelm is gone.
Partial Save (API requirement)
Draft auto-saved at each step. Users who exit can return to find their configuration in progress — 40% fewer abandoned configurations.
Enhanced Filter Builder: Before vs. After
Replacing a basic two-field filter panel with a query-builder pattern and saved presets. Power users can surface their most common filter combinations in one click.
before
Before: Basic Filters
Two fields, no presets, minimal usability
Filter Reports
Status
Date Range
mm/dd/yyyy - mm/dd/yyyy
Apply
✕ No presets
✕ No multi-select status filter
✕ No report type filter
✕ No saved filter states
after
After: Enhanced Filter Builder
Presets, multi-select status chips, and saved filters
Advanced Filters
Save as Preset
Quick Filters
Active Reports
Failed Last 7 Days
Scheduled This Week
Status
Processing
Completed
Scheduled
Failed
Queued
Date Range
Report Type
Apply Filters
Clear
Accessibility: Section 508 Compliance
Long-running asynchronous operations create specific accessibility challenges: progress indicators, live status updates, and complex modal flows all required deliberate accessibility work beyond standard compliance.
📡 Live Region for Progress Updates
Real-time progress updates via WebSocket needed to be surfaced to screen readers without overwhelming them.
aria-live="polite" region announces status changes on transition (not every 5s)
Progress percentage announced at 25%, 50%, 75%, and 100% only
"Report Completed: Monthly SOX Compliance report is ready to download" — full context in announcement
Failure: "Report Failed: Monthly SOX Compliance — filter returned 0 records. Retry or edit configuration."
⌥ Multi-Step Modal Keyboard Flow
The 4-step modal required a complete focus management strategy to maintain orientation through step transitions.
Focus moves to step heading on each step transition — users know where they are
Escape closes modal with confirmation if draft is unsaved
Tab order: step indicator → form fields → Cancel → Next (logical reading order)
Back button returns to previous step without losing forward progress
— Status Differentiation Without Color
7 status states risk over-reliance on color. Each state uses color + text label + icon shape for full WCAG 1.4.1 compliance.
Processing: spinner icon + "Processing" text + indigo badge
Completed: checkmark icon + "Completed" text + green badge
Failed: triangle warning icon + "Failed" text + red badge — icon distinguishes from other states without color
Progress bar: percentage text always shown alongside visual bar
📝 Filter Builder Accessibility
The multi-select status filter chips and date range inputs required custom ARIA work for screen reader usability.
Status chips: role="checkbox" with aria-checked — behave like a checkbox group
Date range: start and end inputs explicitly labeled "Date range start" / "Date range end"
Saved preset buttons: aria-pressed to communicate active state
Filter count announced when applied: "3 filters active, 47 reports shown"
Outcomes & Impact
58%
Reduction in duplicate report submissions
First month post-launch
41%
"Where is my report?" support tickets eliminated
Operations self-serve status lookups
85%
Reduction in API polling load
WebSocket push vs. manual page refresh
40%
Fewer abandoned report configurations
Multi-step modal + partial saves
7
Canonical status states adopted platform-wide
All async operations now use the same model
3→1
Teams aligned on shared status vocabulary
From 6 terms to one canonical glossary
Platform-Wide System Impact
Status model as platform standard
The 7-state canonical model was adopted for all async platform operations — User ID bulk imports, data exports, and scheduled jobs. The cross-team terminology alignment eliminated an entire category of support escalation: mismatched vocabulary between what users reported and what engineers saw in logs.
Infrastructure efficiency
The 85% reduction in API polling wasn't just a UX improvement — it materially reduced server load. The duplicate-detection hash (designed collaboratively with engineering) eliminated server-side waste from identical redundant jobs, improving queue throughput for all users.
Reflection
What I would do differently
The ETA estimation decision (showing % complete but not time remaining) was the right call for v1—but I should have designed the data collection infrastructure to enable better estimates in v2. We're now sitting on months of historical job data without a clear schema for training a simple time-estimate model. Thinking further ahead about what data the feature would need to improve would have been worth the extra sprint.
The unexpected lesson: terminology is design
The terminology workshop felt like process overhead when I proposed it. My PM pushed back. But the 41% reduction in "where is my report?" support tickets was substantially caused by vocabulary alignment, not just UI changes. When support and users finally shared the same words for the same states, support calls became shorter, user self-service became possible, and engineering could respond to bug reports without interpretation overhead. Language work is UX work.
How this influenced my approach to system design
This project validated a principle I now apply to all async system design: users in uncertainty act irrationally from the system's perspective but rationally from theirs. A user resubmitting a 30-minute report every 10 minutes isn't impatient — they're responding to an opaque system the only way they can. Before designing any long-running operation interface, I now start with the question: "What does the user need to see to trust the system is working?" The answer almost always shapes the back-end architecture as much as the UI.
Terminology is infrastructure. The 41% reduction in "where is my report?" tickets came as much from vocabulary alignment across 3 teams as from UI changes. When support, engineering, and users finally shared the same words for the same states, confusion collapsed. In platform UX, language standardization is a design deliverable — not a prerequisite someone else handles before your work begins.

