Enterprise Power BI · Sustainment & Enhancement
Enhancing an Enterprise Contact-Centre Reporting Suite
Ticket-driven Power BI enhancements inside a 27-report operational suite — investigated, built, tested, and deployed from non-production to production.
- Power BI
- DAX
- Microsoft Fabric
- Deployment Pipelines
- QA Support
- Jira
Overview
I spent much of my co-op working inside an established enterprise reporting environment built on Microsoft Fabric and Power BI. The suite contained 27 operational and performance reports used to run a contact-centre operation; I worked directly in eight or nine of them.
The work was sustainment and enhancement rather than greenfield development. Business stakeholders raised requests as tickets; I investigated what each one required, implemented it in the non-production environment, tested the result, and — once the change had been reviewed and confirmed — deployed it to production through the team's deployment pipeline.
Context
These reports were the operating picture for the contact centre: queue volumes and service levels, abandon rates, handle-time and occupancy measures, IVR containment, and monthly performance scorecards used by team managers and coaches to review individual and team results. When someone makes a staffing decision or has a coaching conversation based on a number in a report, a metric that aggregates incorrectly doesn't get caught — it gets trusted.
The platform itself was established before I joined and maintained by a dedicated team. Data was ingested and transformed through layered lakehouse stages by data engineers, and the reporting layer consumed a curated gold layer through a shared semantic model powering most reports in the suite. Non-production and production environments were separated, changes moved between them through deployment pipelines, and end users accessed reports through published applications with role-based access.
I worked in the reporting layer — report files, pages, visuals, filters, and report-level measures — using both Power BI Desktop and the Power BI Service.
How the work reached me
Many requests were formally owned by a senior analyst, who assigned me the investigation and implementation. I worked through the report behaviour independently, built and tested the change, and reported results and release status back for stakeholder follow-up.
Scope of my role
I owned
- Investigating assigned requests and working out how they should behave in the report
- Writing new DAX measures, modifying existing ones, and configuring visuals against measures already in the model
- Building and publishing changes in the non-production workspace
- Testing output against expected results, and deploying reviewed changes to production
I collaborated on
- QA cycles with testers, revising implementations across multiple rounds of feedback
- Release readiness and coordination with the senior analyst who owned the tickets
- Validation. I tested changes against expected results and source data; formal validation documentation and final stakeholder sign-off were owned by the senior analyst
Outside my scope
- The shared semantic model. My measures lived at report level; a model change propagates to every report built on it, and ownership sat elsewhere. Knowing which side of that line a change falls on was part of doing the work correctly
- Data engineering pipelines and lakehouse architecture
- Platform architecture, the access model, and the deployment runbook
- Formal requirements documentation for this work, which sat with another business analyst
Representative enhancements
Four of the six enhancements I delivered. Details are generalized and report names replaced.
Forecast and variance measures on a queue report
Released- The request
- Add forecast and variance measures alongside existing volume and handle-time columns, across two pages of a queue report.
- What I had to work out
- Where columns belong in a table already wide enough to scroll, and how variance should read at a glance — volume as a percentage against forecast, handle time as a signed difference — so someone scanning a row could tell direction without doing arithmetic. Each column had to sit next to the measure it modified rather than being appended at the end.
- Outcome
- Reviewed in non-production against the changes I'd made, approved, and deployed to production. Managers gained forecast-to-actual comparison in the report itself.
Distinct agent counts at every aggregation level
In testing- The request
- Add a distinct count of agents to two queue reporting pages, at whichever aggregation level the user is viewing — queue or overall, by interval, day, week, or month.
- What I had to work out
- Distinct counts don't roll up. The distinct agents in a week aren't the sum of the distinct agents across its days, and agents work across multiple queues, so the same person can't be double-counted at the overall level. The measure had to recompute inside whatever filter context the user landed in rather than aggregating a stored value. I also needed to be sure the count meant what was intended: how many distinct people were attached to that queue in that period, not how many interactions they handled.
- What I checked
- Compared counts against production data, because the non-production environment's data wasn't current enough to prove them on its own.
- Outcome
- A reviewer confirmed the layout was correct but couldn't complete validation in the QA environment, because the data there wasn't current — an environment constraint rather than a defect in the change. Saying so plainly was part of the work.
Agent-level filtering on a team scorecard
Released- The request
- Add a filter so managers could exclude specific agents from a team scorecard. One sentence, and the answer wasn't.
- Multi-select behaviour
- The default required holding a modifier key to select more than one agent, which most users would never discover. I changed it so multiple selections work with a plain click, starting from select-all with specific people deselected from there.
- People move between teams
- When an agent changes teams, their historical months no longer match their current manager and coach combination — so filtering by manager could silently drop part of a person's history. I recommended clearing those filters and filtering on agent name directly when looking across months, and flagged it as behaviour users needed to be told about rather than discover.
- Invisible filters
- An exclusion filter in the filter pane would have worked and been faster to build. But a user who applies it and collapses the pane sees a scorecard that looks complete and isn't, with nothing on the page to indicate otherwise. I chose the visible on-canvas approach instead.
- Outcome
- Passed QA. The tester asked that defaults be set to include all values, so the report opens in a complete state and anyone narrowing it does so deliberately. Applied, and deployed to production.
Percentages on a daily summary report
Released- The request
- Show percentages alongside raw counts for containment and outage figures across three pages, matching how an existing report presented the same idea. The constraint was consistency: two reports showing the same concept differently is how people stop trusting both.
- The loop
- Implemented and published; the reviewer asked for one category to move to the bottom of the chart on every page and thousands separators added for readability; both applied, re-published, approved, deployed. It's the most ordinary of the four, and it's what most of the work looked like.
Why the agent count was the hard part
The distinct-count request looked small. The reason it wasn't: distinct counts don't roll up the way totals do.
Distinct agents per day
- Mon84
- Tue91
- Wed87
Days added up
262
double-counts people
True weekly distinct
143
recomputed in context
Many of the same people work across days and across queues, so a distinct count can't be summed or stored — the measure has to recompute inside whatever period and filter the user is viewing.
Where I put the scorecard filter, and why
The exclusion filter could live in the filter pane or on the report canvas. Both work; only one keeps the filter state visible to the person reading the scorecard.
Scorecard
looks complete
Pane collapsed → the exclusion is invisible, and the reader trusts a partial view.
Scorecard
state always shown
The current filter is on the canvas, so nobody reads a filtered scorecard by accident. I chose B.
My working process
Read the ticket and the report together. The request is one sentence; the report is the real specification.
Find where the change belongs — which page, which visual, and whether it needs a new measure, a modified one, or a visual configured against a measure that already exists.
Build it in non-production. Never in production, per the team's standard.
Test the output against what the result is supposed to be, using production data as the reference where non-production data isn't current.
Publish for QA and let testers use it. Reviewers consistently found things the ticket couldn't have predicted.
Revise on feedback — usually formatting, ordering, or default behaviour.
Deploy to production through the deployment pipeline once the change is confirmed, and report back.
Technical environment
Worked in directly
- Power BI Desktop
- Power BI Service
- DAX (authored and modified report-level measures)
- Report visuals and pages
- Report and visual-level filters
- Deployment pipelines
- Jira
Worked within
- Microsoft Fabric workspaces
- Shared semantic model
- Curated gold-layer tables
- Published Power BI applications
- Role-based access model
Maintained by others
- Ingestion and transformation pipelines
- Lakehouse architecture
- The semantic model itself
- Access administration
- The runbook
Outcomes
- Managers can now compare forecast to actual inside the queue report, where the comparison previously meant leaving the report to line the two figures up by hand
- The distinct-agent measure answers a staffing question the report couldn't answer before — how many people actually covered a queue in a period — correct at every aggregation level
- Team-scorecard filtering lets a manager exclude specific agents for a fair comparison, and opens showing everyone so nobody reads a pre-filtered view by accident
- Two reports now present the same percentages the same way, removing a source of the “which number is right” confusion that erodes trust in both
- Because these figures feed staffing and coaching decisions, a wrong aggregation gets trusted rather than caught — so the validation behind each change mattered as much as the change itself
What I learned
- Small requests aren't small. “Add a column” carried aggregation behaviour, filter interaction, and a definitional question about what was being counted. Writing the measure was the quick part.
- Know which layer you're changing. A report-level measure affects one report; a semantic model change affects everything built on it. Understanding that distinction was as important as the DAX itself.
- Reviewers see what you can't. Almost all the feedback I received was about usability rather than correctness — ordering, formatting, defaults. Getting the number right is necessary and not sufficient.