Skip to content
All work

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.

Option AFilter in the pane
▸ Filters (collapsed)1 applied

Scorecard

looks complete

Pane collapsed → the exclusion is invisible, and the reader trusts a partial view.

Option BControl on the page
AgentsAll ▾ · 2 excluded

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

  1. Read the ticket and the report together. The request is one sentence; the report is the real specification.

  2. 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.

  3. Build it in non-production. Never in production, per the team's standard.

  4. Test the output against what the result is supposed to be, using production data as the reference where non-production data isn't current.

  5. Publish for QA and let testers use it. Reviewers consistently found things the ticket couldn't have predicted.

  6. Revise on feedback — usually formatting, ordering, or default behaviour.

  7. 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.

Interested in working together?

Get in touch