HR reporting automation · case-study data

A Self-Service Performance Report for Salford & Co

One refreshable Power BI report that answers the recurring questions about performance, engagement and training, so HR stops rebuilding the same pack each quarter.

Organisation: Salford & Co (case study)Population: 753 employees, 2024Tools: Power BI · DAX · time intelligence

Executive summary

  • 753employees appraised
  • 74.8%average performance score
  • 1.0 ptdip from Q1 to Q2, since recovered
  • 12DAX measures behind the report

Performance averaged 74.8% across 2024. It dipped from 75.3% in Q1 to 74.3% in Q2, then held at 74.8% for the rest of the year. Results by department were almost identical, and more training hours did not line up with higher scores.

Recommendation: direct development at individuals and managers rather than whole departments, and review what training covers rather than how many hours it takes.

01

Business context

Every quarter HR was asked the same questions: how is performance trending, which teams need attention, is training paying off? Each answer meant a new spreadsheet cut. The aim was one report that answers them on demand and refreshes with new appraisal data.

02

Data model

Appraisal records for 753 employees across 2024, holding performance score, goals met, engagement score, tenure, training hours, department, job role, gender, marital status and a manager's remark.

  • HR Performance AppraisalThe fact table: one row per appraisal.
  • Calendar DimA date table related to the appraisal date. Time intelligence functions such as PREVIOUSQUARTER depend on it.
  • Key MeasuresAn empty table used as a folder, so all measures sit in one place.
  • QuarterOverQuarter MeasuresA second folder for the comparison and colour logic.
03

DAX measures explained

Headline measures

Total Employees

Total Employees = DISTINCTCOUNT('HR Performance Appraisal'[Employee ID])

Counts distinct IDs rather than rows, so an employee appraised more than once is still counted once.

Averages

Avg Performance Score = AVERAGE('HR Performance Appraisal'[Performance Score])
Avg Engagement Score  = AVERAGE('HR Performance Appraisal'[Engagement Score])
Avg Tenure            = AVERAGE('HR Performance Appraisal'[Tenure (Years)])
Avg Goals Met         = AVERAGE('HR Performance Appraisal'[Goals Met])
Avg Training Hours    = AVERAGE('HR Performance Appraisal'[Training Hours Completed])
Total Training Hours  = SUM('HR Performance Appraisal'[Training Hours Completed])

Simple aggregations, defined once so every visual and slicer uses the same logic.

Time intelligence

Previous Quarter Performance

Previous Quarter Performance =
CALCULATE(
    AVERAGE('HR Performance Appraisal'[Performance Score]),
    PREVIOUSQUARTER('Calendar Dim'[Date])
)

PREVIOUSQUARTER shifts the date filter back one quarter, so on the Q3 point of a chart this returns Q2's average.

Quarter-on-Quarter Change (rewritten)

QuarterOverQuarter Performance Change(%) =
VAR LatestDay = LASTNONBLANK('Calendar Dim'[Date], [Avg Performance Score])
VAR CurrentQ  = CALCULATE([Avg Performance Score], DATESQTD(LatestDay))
VAR PreviousQ = CALCULATE([Avg Performance Score], PREVIOUSQUARTER(LatestDay))
VAR PctChange = DIVIDE(CurrentQ - PreviousQ, PreviousQ)
VAR Icon = SWITCH(TRUE(), PctChange > 0, "▲ ", PctChange < 0, "▼ ", "↔ ")
RETURN
    IF(ISBLANK(PctChange), BLANK(), Icon & FORMAT(PctChange, "0.0%"))

The original measure divided by the previous quarter with /. On the headline card there is no single "current quarter", so the previous-quarter value was blank and the card showed #INF. The rewrite finds the latest date with data, compares that quarter with the one before it, and uses DIVIDE so a missing value returns blank instead of an error. The card now reads ▲ 0.1%.

Presentation logic

Performance Color Code

Performance Color Code =
VAR PerformanceDifference = [Avg Performance Score] - [Previous Quarter Performance]
RETURN
SWITCH(TRUE(),
    PerformanceDifference > 0, "#6AA84F",   -- green: improving
    PerformanceDifference < 0, "#E06666",   -- red: declining
    "#F4F3F3")                              -- grey: no change

Returns a colour code that drives conditional formatting, so the direction of change is visible without reading the number.

Dynamic Overview Title

Dynamic Overview Title =
VAR SelectedEmployee   = SELECTEDVALUE('HR Performance Appraisal'[Employee Name])
VAR SelectedDepartment = SELECTEDVALUE('HR Performance Appraisal'[Department])
RETURN
IF(NOT ISBLANK(SelectedEmployee), SelectedEmployee & "'s Performance Record",
IF(NOT ISBLANK(SelectedDepartment), "Performance Overview for " & SelectedDepartment,
"Overview Of Employee Performance"))

SELECTEDVALUE returns a value only when exactly one is selected in the slicers, so the page title changes to match the view: company, department or individual. An earlier version added "Mr" or "Ms" based on gender. It was removed because a report title has no need for it.

Removed: Training Time Breakdown

This measure converted total training hours into a "days, hours, minutes" string. It was shown on an unlabelled card reading "3152DD,4HH,0MM", which no reader could interpret. The card was deleted. Training is covered by Avg Training Hours on the scatter chart.

04

How the report is used

This report has no what-if slider. Its interactive controls are three slicers: quarter, department and employee. They filter every visual and the dynamic title, so a manager can move from company to department to one person without a new report being built.

  • QuarterCompares any quarter with the trend line and the quarter-on-quarter card.
  • DepartmentIsolates one team, and the title updates to name it.
  • EmployeeShows one person's record for appraisal conversations.
05

Dashboard and findings

Salford and Co performance dashboard in Power BI with KPI cards, a quarterly performance trend line, a training hours versus performance scatter plot, an appraisal table with names hidden, a marital status donut and performance bands by department.
The Power BI report exported directly from the file. Employee names are hidden for this portfolio.
Quarter 2024Avg performanceChange
Q175.3%–
Q274.3%▼ 1.0 pt
Q374.8%▲ 0.5 pt
Q474.8%▲ 0.1 pt

Performance bands look almost identical in HR, Marketing, Operations and Sales, so the differences that matter sit with individuals, not teams.

  • Within each department, the average scores of the above-average, average and exceptional bands differ by one point or less between teams.
  • The scatter of training hours against performance shows no visible pattern: employees with 10 hours and 40 hours of training score across the same range.
  • Marital status groups are close to even in size and add little to the performance story. That visual could be replaced with goals met by role.
06

Limitations and responsible use

  • No statistical test. The training finding is read from the scatter chart. A correlation coefficient would confirm it.
  • One year of data. Four quarters are too few to call a trend. The Q2 dip may be seasonal.
  • Personal data. The report shows named employees with manager remarks. In live use, row-level security should limit each manager to their own team, and demographic visuals such as marital status should be justified before they are shown.
  • Case-study data. Figures describe a fictional company and are not results from an employer.
07

Recommendations

  1. Target development at individuals and managers, using the employee slicer and manager remarks, since department averages are too similar to prioritise one team.
  2. Review training content, not volume. More hours are not linked to higher scores, so assess which programmes change performance.
  3. Investigate the Q2 dip with managers before assuming it is seasonal, and watch whether it recurs in 2025.
  4. Add row-level security before the report is shared beyond HR.
  5. Refresh quarterly from the appraisal system so the report replaces the manual pack for good.
08

How to reproduce it

Download the Power BI file (Salford_Performance_Appraisal.pbix). It opens in Power BI Desktop with the data model and every measure described here.

  1. Load the appraisal data and create a calendar table. Mark it as a date table and relate it to the appraisal date.
  2. Create measure folder tables and the measures in section 03.
  3. Apply the colour code measure through conditional formatting on the change card.
  4. Add the three slicers and bind the page title to the dynamic title measure.
Prepared by Olajumoke O. Medunoye