A comprehensive view of levy utilisation, fund expiry risk, and training spend across Legal and Business Services pathways - built to support data-driven workforce decisions.
| FY | Levy In | Adj | Spend | Legal | Bus Svcs | Expired | Transfers | Utilisation % | Cum Levy | Cum Spend | Status |
|---|
Technical specification and step-by-step build instructions for replicating this dashboard in Microsoft Power BI - including the data model, every DAX measure, and full calculation logic.
What the report delivers and the questions it answers.
The dashboard provides a comprehensive view of UK Apprenticeship Levy activity to support data-driven decision making. Specifically, it answers:
The rules that govern how levy transactions behave - these drive every calculation.
| Transaction Type | Sign | Business meaning |
|---|---|---|
| Levy | Positive | Monthly levy declaration paid in by the employer. |
| Levy adjustment | + or - | HMRC correction to a previous levy declaration. |
| Monthly payment | Negative | Routine monthly payment to a training provider. |
| Completion payment | Negative | Final 20% payment on successful end-point assessment. |
| Balance payment | Negative | True-up of outstanding cost on funding band. |
| Transfers | Negative | Funds transferred out (up to 50% annual levy). |
| Expired levy | Negative | Funds removed by HMRC after the policy window. |
UK FY: 1 April to 31 March. Format FY2025/26.
Star schema: one fact, three dimensions.
| Table | Type | Purpose |
|---|---|---|
| FactTransactions | Fact | One row per levy account event. |
| DimDate | Dim | Date intelligence and FY grouping. |
| DimTransactionType | Dim | Group types into Income / Outflow / Adjustment. |
| DimCourse | Dim | Legal vs Business Services split. |
Get the raw CSV into a clean, typed fact table.
Apprenticeship_Levy_Sample_Dataset.csv, click Transform Data.FactTransactions.Transaction Date → Date, Total → Decimal Number, all others → Text.TransactionDate, TransactionType, TrainingProvider, Apprentice, Course, Total.CourseCategory:if [Course] = null then "Unallocated"
else if Text.Contains([Course],"Paralegal") or Text.Contains([Course],"Solicitor") or Text.Contains([Course],"Legal")
then "Legal"
else "Business Services"
Modeling → New Table.
-- DimDate: continuous date dimension with UK FY DimDate = ADDCOLUMNS ( CALENDAR ( DATE ( 2017, 1, 1 ), DATE ( 2030, 3, 31 ) ), "Year", YEAR ( [Date] ), "MonthNum", MONTH ( [Date] ), "MonthName", FORMAT ( [Date], "MMM" ), "YearMonth", FORMAT ( [Date], "yyyy-MM" ), "FYStartYear", IF ( MONTH ( [Date] ) >= 4, YEAR ( [Date] ), YEAR ( [Date] ) - 1 ), "FY", "FY" & IF ( MONTH ( [Date] ) >= 4, YEAR ( [Date] ), YEAR ( [Date] ) - 1 ) & "/" & RIGHT ( IF ( MONTH ( [Date] ) >= 4, YEAR ( [Date] ) + 1, YEAR ( [Date] ) ), 2 ), "FYMonthIndex", MOD ( MONTH ( [Date] ) - 4 + 12, 12 ) + 1, "FYQuarter", "Q" & ( QUOTIENT ( MOD ( MONTH ( [Date] ) - 4 + 12, 12 ), 3 ) + 1 ) )
DimDate as a date table.MonthName by MonthNum.DimCourseCategory =
DATATABLE (
"CourseCategory", STRING, "CategoryOrder", INTEGER,
{
{ "Legal", 1 },
{ "Business Services", 2 },
{ "Unallocated", 3 }
}
)
DimTransactionType =
DATATABLE (
"TransactionType", STRING, "Bucket", STRING, "SignConvention", STRING,
{
{ "Levy", "Income", "Positive" },
{ "Levy adjustment", "Adjustment", "Mixed" },
{ "Monthly payment", "Spend", "Negative" },
{ "Completion payment", "Spend", "Negative" },
{ "Balance payment", "Spend", "Negative" },
{ "Transfers", "Transfer", "Negative" },
{ "Expired levy", "Expired", "Negative" }
}
)
| From | To | Cardinality |
|---|---|---|
| FactTransactions[TransactionDate] | DimDate[Date] | Many-to-one, single direction |
| FactTransactions[TransactionType] | DimTransactionType[TransactionType] | Many-to-one, single direction |
| FactTransactions[CourseCategory] | DimCourseCategory[CourseCategory] | Many-to-one, single direction |
The seven headline KPIs. Drop all measures into a hidden _Measures table.
-- 1. Total Levy In Total Levy In := CALCULATE ( SUM ( FactTransactions[Total] ), FactTransactions[TransactionType] = "Levy" ) -- 2. Total Levy Adjustments Total Levy Adjustments := CALCULATE ( SUM ( FactTransactions[Total] ), FactTransactions[TransactionType] = "Levy adjustment" ) -- 3. Total Levy Spend Total Levy Spend := CALCULATE ( -1 * SUM ( FactTransactions[Total] ), FactTransactions[TransactionType] IN { "Monthly payment", "Completion payment", "Balance payment" } ) -- 4-5. Category splits Legal Spend := CALCULATE ( [Total Levy Spend], FactTransactions[CourseCategory] = "Legal" ) Business Services Spend := CALCULATE ( [Total Levy Spend], FactTransactions[CourseCategory] = "Business Services" ) -- 6. Transfers Transfer Funds := CALCULATE ( -1 * SUM ( FactTransactions[Total] ), FactTransactions[TransactionType] = "Transfers" ) -- 7. Expired Expired Funds := CALCULATE ( -1 * SUM ( FactTransactions[Total] ), FactTransactions[TransactionType] = "Expired levy" ) -- 8. Remaining Balance Remaining Balance := VAR LevyIn = [Total Levy In] + [Total Levy Adjustments] VAR Outflow = [Total Levy Spend] + [Transfer Funds] + [Expired Funds] RETURN LevyIn - Outflow -- 9. % Utilisation % Utilisation := DIVIDE ( [Total Levy Spend], [Total Levy In] + [Total Levy Adjustments], 0 ) -- 10. % Leakage % Leakage := DIVIDE ( [Expired Funds] + [Transfer Funds], [Total Levy In] + [Total Levy Adjustments], 0 )
-- Cumulative measures Cumulative Levy In := CALCULATE ( [Total Levy In], FILTER ( ALL ( DimDate ), DimDate[Date] <= MAX ( DimDate[Date] ) ) ) Cumulative Spend := CALCULATE ( [Total Levy Spend], FILTER ( ALL ( DimDate ), DimDate[Date] <= MAX ( DimDate[Date] ) ) ) Cumulative Expired := CALCULATE ( [Expired Funds], FILTER ( ALL ( DimDate ), DimDate[Date] <= MAX ( DimDate[Date] ) ) ) Running Balance := CALCULATE ( [Remaining Balance], FILTER ( ALL ( DimDate ), DimDate[Date] <= MAX ( DimDate[Date] ) ) ) -- Completion Spike Flag (cohort throughput signal) Completion Spike Flag := VAR ThisMonth = CALCULATE ( -1 * SUM ( FactTransactions[Total] ), FactTransactions[TransactionType] = "Completion payment" ) VAR Avg6 = CALCULATE ( AVERAGEX ( VALUES ( DimDate[YearMonth] ), CALCULATE ( -1 * SUM ( FactTransactions[Total] ), FactTransactions[TransactionType] = "Completion payment" ) ), DATESINPERIOD ( DimDate[Date], MAX ( DimDate[Date] ), -6, MONTH ) ) RETURN IF ( ThisMonth > 1.5 * Avg6 && ThisMonth > 0, 1, 0 ) -- Year on year Spend YoY % := VAR Cur = [Total Levy Spend] VAR Prv = CALCULATE ( [Total Levy Spend], DATEADD ( DimDate[Date], -1, YEAR ) ) RETURN DIVIDE ( Cur - Prv, Prv, BLANK () ) -- FY status FY Utilisation Status := VAR U = [% Utilisation] RETURN SWITCH ( TRUE (), U >= 0.40, "Healthy", U >= 0.25, "Watch", "Low" )
LevyPots =
VAR Levies =
SELECTCOLUMNS (
FILTER ( FactTransactions, FactTransactions[TransactionType] = "Levy" ),
"EntryDate", FactTransactions[TransactionDate],
"PotAmount", FactTransactions[Total]
)
RETURN
ADDCOLUMNS (
Levies,
"ExpiryMonths", IF ( [EntryDate] >= DATE ( 2026, 8, 1 ), 12, 24 ),
"ExpiryDate", EDATE ( [EntryDate], IF ( [EntryDate] >= DATE ( 2026, 8, 1 ), 12, 24 ) )
)
Pot Remaining :=
VAR Today = TODAY ()
VAR PotEntry = SELECTEDVALUE ( LevyPots[EntryDate] )
VAR PotAmount = SELECTEDVALUE ( LevyPots[PotAmount] )
VAR PotExpiry = SELECTEDVALUE ( LevyPots[ExpiryDate] )
VAR CumCapacityToThisPot =
CALCULATE ( SUM ( LevyPots[PotAmount] ),
FILTER ( ALL ( LevyPots ), LevyPots[EntryDate] <= PotEntry ) )
VAR TotalDrain =
CALCULATE ( -1 * SUM ( FactTransactions[Total] ),
FactTransactions[TransactionType] IN
{ "Monthly payment", "Completion payment", "Balance payment", "Transfers" },
FactTransactions[TransactionDate] <= Today )
VAR PriorCapacity = CumCapacityToThisPot - PotAmount
VAR DrainedFromThisPot =
MIN ( PotAmount, MAX ( 0, TotalDrain - PriorCapacity ) )
VAR Remaining = PotAmount - DrainedFromThisPot
RETURN IF ( PotExpiry >= Today, Remaining, 0 )
Funds Expiring Next 12 Months :=
SUMX (
FILTER ( LevyPots,
LevyPots[ExpiryDate] >= TODAY ()
&& LevyPots[ExpiryDate] <= EDATE ( TODAY (), 12 ) ),
[Pot Remaining] )
Funds Expiring Next 24 Months :=
SUMX (
FILTER ( LevyPots,
LevyPots[ExpiryDate] >= TODAY ()
&& LevyPots[ExpiryDate] <= EDATE ( TODAY (), 24 ) ),
[Pot Remaining] )
Forecast Expiring This Period :=
SUMX (
FILTER ( LevyPots,
LevyPots[ExpiryDate] >= MIN ( DimDate[Date] )
&& LevyPots[ExpiryDate] <= MAX ( DimDate[Date] )
&& LevyPots[ExpiryDate] >= TODAY () ),
[Pot Remaining] )
| Visual | Type | Field |
|---|---|---|
| KPI cards | Card | Total Levy In, Spend, Legal, Business Services, Transfers, Expired, Remaining Balance |
| % Utilisation | KPI gauge | Value=[% Utilisation], Target=0.95 |
| Funds at risk (12m) | Card | [Funds Expiring Next 12 Months] |
| FY summary | Matrix | Rows=FY, Values=all core measures + Status |
| Slicers | Slicer | FY, TrainingProvider, CourseCategory |
| Visual | Type | Field |
|---|---|---|
| Monthly Levy vs Spend vs Expired | Stacked column + line | Columns=Levy In/Spend/Expired, Line=Completion |
| Cumulative Position | Area | Cumulative Levy In / Spend / Expired |
| % Utilisation by FY | Column + target | [% Utilisation], target 95% |
| YoY Spend Change | Waterfall | [Spend YoY %] |
| Completion Spike | Conditional column | Background by [Completion Spike Flag] |
| Visual | Type | Field |
|---|---|---|
| Funds at risk (12m / 24m) | Cards | The two horizon measures |
| Expiry schedule | Column | Axis=ExpiryDate (Month), Value=[Pot Remaining] |
| Policy switch marker | Constant line | X = 1 Aug 2026 |
| Pot detail | Table | EntryDate, PotAmount, ExpiryMonths, ExpiryDate, [Pot Remaining] |
| Visual | Type | Field |
|---|---|---|
| Lifetime split | Donut | Legend=CourseCategory, Value=Spend |
| Monthly trend | Line | Legend=CourseCategory, Value=Spend |
| Provider breakdown | Treemap | Group=Provider, Detail=Category |
| Top 10 courses | Bar | Top N=10 |
| Apprentice count | Card | DISTINCTCOUNT(Apprentice) |
Reference for every metric. Use as the canonical specification when migrating to production data.