Custom visuals are great — until they break, get deprecated, or you need to know which reports actually depend on them. I built a small Fabric notebook that gives you a tenant-wide answer in seconds.
Big thanks to Steinick Sibarani for the nudge that pushed me to put this together quickly — the conversation around custom-visual governance in his community thread was exactly the spark this needed.
What it does
Custom Visuals Inventory uses semantic-link-labs to list every custom visual used across one workspace, a list of workspaces, or your entire tenant. You get:
A flat table: workspace, report, visual name, visual GUID
Top visuals by report count (where is TRUECHART actually used?)
Heaviest reports by custom-visual count
A PBIR-format inventory (PBIR vs. PBIRLegacy) — because only PBIR reports can be scanned
One MODE switch flips between current, list, and all workspaces. That’s it.
FUAM is the go-to solution for tenant-wide Fabric monitoring — workspaces, capacities, activities, refreshables, git connections, tenant settings. But there is one thing it does not ship today: a custom visuals inventory per report.
This notebook fills the gap as a standalone companion — same scope (tenant-wide), same stack (Fabric notebook + Direct Lake friendly output).
If you run a Fabric tenant with more than a handful of reports, this is one notebook you want in your back pocket the next time a custom visual gets deprecated.
Power BI’s built-in usage metrics report is great — until you need history beyond 30 days. The underlying dataset is a rolling window: yesterday’s view counts are there, last quarter’s are gone. If you want to track adoption trends, prove ROI, or spot reports that have quietly died, you need to capture that data yourself.
The fix is surprisingly simple: a single Fabric notebook that queries the hidden Report Usage Metrics Model semantic model over XMLA and appends each daily snapshot to Lakehouse Delta tables. Unlimited history, append-only, ~30 lines of Python.
Honest Disclaimer — Prefer FUAM
Before you build this: if you have any meaningful scale of Power BI / Fabric tenant, use FUAM (Fabric Unified Admin Monitoring) instead. FUAM is the Microsoft-published, open-source accelerator that snapshots not just usage metrics but capacity events, activity logs, refresh history, inventories — the whole tenant story — into a Lakehouse with a ready-made semantic model and report. It is far more complete than what’s in this post.
So when do I reach for the little notebook below instead?
A single workspace where you just need report-level view history and don’t want to deploy a full monitoring solution.
You need a quick fix today and FUAM rollout is weeks away.
You want to understand how the underlying mechanism works — the notebook is small enough to read end-to-end in five minutes.
For everything else: start with FUAM.
The Problem: 30 Days Is Not Enough
Rolling window. Each day, the oldest day falls off. Anything older than 30 days is permanently gone — you cannot recover it.
No YoY, no trends. Did adoption grow over the last year? Which reports are losing users? You literally cannot answer these questions with the built-in model.
No ownership. Microsoft controls the model. Schema changes happen without notice. You cannot extend it, share it, or join it to your own dimensions cleanly.
The Solution: Daily Snapshot to Lakehouse
The hidden Report Usage Metrics Model is a normal semantic model — it just happens to be auto-managed by Microsoft. That means you can hit it with XMLA queries like any other model. Using sempy.fabric.evaluate_dax, we pull each source table, stamp a SnapshotUtc column, and append to Delta tables in a Lakehouse:
import sempy.fabric as fabric
from datetime import datetime, timezone
WORKSPACE_ID = "<your-workspace-guid>"
DATASET_NAME = "Report Usage Metrics Model"
TABLES = ["Views", "Reports", "Users", "Dates", "DistributionMethods", "Platforms"]
snap_ts = datetime.now(timezone.utc)
for t in TABLES:
df = fabric.evaluate_dax(
dataset=DATASET_NAME,
workspace=WORKSPACE_ID,
dax_string=f"EVALUATE '{t}'",
)
df.columns = [c.split("[")[-1].rstrip("]") if "[" in c else c for c in df.columns]
df["SnapshotUtc"] = snap_ts.isoformat()
(spark.createDataFrame(df).write
.mode("append")
.option("mergeSchema", "true")
.saveAsTable(f"usage_metrics_{t.lower()}"))
That’s the whole engine. Schedule it daily after 04:00 UTC (the source refreshes around 03:00 UTC) and you have a permanent historical record.
What Lands in the Lakehouse
One Delta table per source table, prefixed usage_metrics_*, with an extra SnapshotUtc column. Append-only — every run adds today’s full snapshot.
Source table
Lakehouse table
Role
Views
usage_metrics_views
Fact
Reports
usage_metrics_reports
Dimension
Users
usage_metrics_users
Dimension
Dates
usage_metrics_dates
Dimension
DistributionMethods
usage_metrics_distributionmethods
Dimension
Platforms
usage_metrics_platforms
Dimension
Three Design Decisions Worth Calling Out
Append, not merge. Every snapshot is the full 30-day window. Yes, days overlap across snapshots — but storage is cheap and append is bulletproof. Deduplication happens at query time (see below), not at ingest time.
mergeSchema=true. Microsoft occasionally adds columns to the usage metrics model. With schema merging on, the next snapshot just absorbs the new columns instead of crashing.
Retention with DELETE. Default 400 days (≈13 months, enough for YoY) — but you can set RETENTION_DAYS = None to keep forever. The delete is a normal Delta operation, so time travel still works if you mess up.
Querying Without Double-Counting
Because every snapshot contains the last 30 days, the same activity row appears in up to 30 snapshots. To get clean numbers, take the latest snapshot per natural key:
WITH ranked AS (
SELECT v.*,
ROW_NUMBER() OVER (
PARTITION BY Date, ReportGuid, UserGuid
ORDER BY SnapshotUtc DESC
) AS rn
FROM usage_metrics_views v
)
SELECT Date,
ReportGuid,
COUNT(*) AS views,
COUNT(DISTINCT UserGuid) AS distinct_users
FROM ranked
WHERE rn = 1
GROUP BY Date, ReportGuid
ORDER BY Date DESC, views DESC
For older history (where a date appears in only one snapshot), the deduplication is a no-op — the row simply passes through. For the most recent 30 days, you always pick the freshest version of each fact.
Prerequisites
Workspace on Premium / PPU / Fabric capacity (XMLA endpoint enabled — read is enough).
The Report Usage Metrics Model dataset exists in the workspace. It is auto-created the first time anyone clicks More options → View usage metrics report on any report in that workspace.
A Lakehouse attached to the notebook (any Lakehouse in the same workspace works).
Tenant setting “Usage metrics for content creators” enabled (admin portal).
Try It — Fabric Notebook Gallery
The notebook is also submitted to the Fabric Notebook Gallery. Grab the .ipynb directly from GitHub and import it into any Fabric workspace:
📓 Usage Metrics Snapshot.ipynb — one notebook, six Delta tables, daily schedule. Set WORKSPACE_ID, attach a Lakehouse, schedule after 04:00 UTC. Done.
Take It Further — A Power BI Report on Top
The Lakehouse tables are already the hard part. From here, building a proper Adoption & Usage Power BI report is a short hop:
Point a Direct Lake semantic model at the six usage_metrics_* Delta tables — no import, no refresh schedule, fresh on the next snapshot.
Wrap the "latest snapshot per natural key" dedup logic in a SQL view in the Lakehouse SQL endpoint so the model stays simple. One view per fact, deduped, ready to consume.
Build the obvious pages: Adoption over time (views & distinct users by month, YoY), Top & bottom reports, Dying reports (had >X views last year, <Y this quarter), Platform & distribution mix.
Join to your own Reports dim (owner, business domain, criticality) for actionable cuts — e.g. "critical reports with zero views in 30 days".
That’s the whole point of owning the history: once the data lives in your Lakehouse, you can model and visualise it however your stakeholders need — not however Microsoft’s built-in report happens to ship it.
Wrap-Up
One notebook, one daily schedule, six Delta tables — and the 30-day blind spot is gone. You own the history, you can join it to your own data, and you can build proper adoption dashboards on top.
Why moving your semantic layer to a separate data platform might not be the obvious choice it seems.
Disclaimer: The opinions expressed in this post are my own and do not represent the views of my employer.
The Debate in 30 Seconds
The analytics industry is in a heated argument about where your business metrics should live:
Camp A (BI-Tool-Centric): Define your semantic model in your BI tool. It’s where your users already are.
Camp B (Data-Platform-Centric): Define your semantic layer in the data platform. Let every downstream tool consume it.
Camp B has been gaining momentum across the industry. This post walks through seven dimensions to help you critically evaluate both approaches before making a strategic decision.
A Note on Terminology
These two terms are often used interchangeably, but they mean different things:
Semantic model — A concrete implementation: tables, relationships, measures, hierarchies, and business logic bundled into a queryable model. In Power BI, this is the semantic model (formerly “dataset”). It’s where your KPIs, time intelligence, and row-level security actually live.
Semantic layer — The broader architectural concept: an abstraction that maps business terms to underlying data so consumers don’t need to know table structures or write raw SQL. In Power BI, most often the semantic layer sits on top of one or more semantic models, but it can also incorporate other data sources — lakehouses, warehouses, APIs, or real-time streams.
Throughout this post, “semantic model” refers to the BI tool’s implementation, while “semantic layer” refers to the platform-level abstraction.
1. Maturity & Capability
BI-Tool-Centric
Data-Platform-Centric
Calculation Power
Mature expression languages with decades of investment. Time intelligence, many-to-many, calculation groups, dynamic formatting.
Early-stage. Most platform semantic layers support basic aggregations and simple expressions.
Modeling Richness
Star schemas, role-playing dimensions, display folders, synonyms, perspectives, hierarchies.
Flat or lightly relational. Still catching up on enterprise modeling patterns.
Verdict
✅ Mature, proven at scale
⚠️ Promising but nascent
Key question to ask:“Can your semantic layer handle the 20 most complex measures in our current BI model? Show me.”
2. Performance
BI-Tool-Centric
Data-Platform-Centric
Query Speed
In-memory engines, aggregation tables, query folding — built for sub-second interactive analysis.
Depends on warehouse engine. Adds network hops. Optimized for large-scale transforms, not slice-and-dice exploration.
Latency varies. Risk of degraded interactivity that frustrates business users.
Verdict
✅ Purpose-built for interactivity
⚠️ Acceptable for dashboards, risky for exploration
Key question:“When a business user applies three slicer filters on a 50M row dataset through your semantic layer — how fast does it respond in the slowest realistic scenarios, not just on average?”
3. The “Last Mile” Problem
Even if you externalize metric definitions, the BI tool still needs:
Conditional formatting rules
Visual-level filters and default aggregations
Display folders and field organization
Synonyms and linguistic metadata (for Q&A / NLP)
Report-level measures and layout-specific logic
The platform semantic layer doesn’t eliminate BI-tool-specific work. It just splits your logic across two places.
You still maintain the BI tool. Now you also maintain the platform layer. That’s not simplification — that’s added complexity.
4. Governance
BI-Tool-Centric
Data-Platform-Centric
Single Source of Truth
Already established in many orgs. Endorsed datasets, data lineage, deployment pipelines.
Promises centralization but adds a new governance surface. Now you govern the platform layer AND the BI layer.
Row-Level Security
Mature, integrated, tested.
Must be re-implemented or passed through — another seam where things break.
Business Ownership
Business analysts can see, test, and validate metrics in the tool they use daily.
Metrics live in a platform business users can’t access. Ownership shifts to engineering.
Verdict
✅ Governance where users are
⚠️ Governance where engineers are
Key question:“When a business user disputes a number, can they trace the metric definition themselves — or do they need to file a ticket with the data platform team?”
5. The AI Agent Angle
This is the emerging frontier. AI agents need semantic context to reason about data. The question is: which semantic layer feeds them?
BI-Tool-Centric
Data-Platform-Centric
Richness for AI
BI models already contain relationships, hierarchies, descriptions, business logic — exactly what an agent needs to generate accurate answers.
Metric definitions exist, but often lack the relational richness and business context an agent needs.
Proximity to Users
Agents that sit on top of the BI model serve the same users who already trust that model.
Agents on the platform layer may produce answers that don’t match what users see in their BI reports — eroding trust.
Maintenance
One model feeds both reports and agents.
Two models: one for agents (platform), one for visuals (BI). Drift risk is real.
Key question:“If my AI agent and my BI report give different answers for the same KPI, which one is wrong — and whose job is it to fix it?”
6. The Lock-In Reality
A common argument against BI-tool semantic models is lock-in. Let’s be honest about what’s really happening:
Claim
Reality
“Your BI tool is locking you in!”
Moving your semantic layer to any data platform is… also lock-in. You’re choosing which dependency to accept.
“We’re open and interoperable!”
Every platform’s semantic layer has proprietary syntax, proprietary APIs, and proprietary tooling. “Open” is a spectrum, not a binary.
“Define once, consume everywhere!”
In practice, every consuming tool interprets the layer differently, supports different subsets, and requires tool-specific workarounds.
“This is customer-driven!”
The push for platform-centric semantic layers shifts the center of gravity — and revenue — to the data platform. Follow the incentives.
Key question:“If I adopt your semantic layer and want to switch data platforms in 3 years, how portable are my metric definitions?”
7. Who Actually Has This Problem?
The platform-centric approach assumes you need one semantic layer serving many BI tools. But ask yourself:
How many BI tools does your organization actually use at scale?
Of those, how much genuine metric overlap exists between them?
Is the overlap large enough to justify an entirely new architectural layer?
For most enterprises, the honest answer is: we’ve standardized on one BI tool, and the multi-tool problem is theoretical. The platform semantic layer is an elegant solution to a problem you probably don’t have.
The Decision Framework
To make this actionable, here’s a decision tree that walks you through the key questions. Follow the green path — if you keep answering “Yes,” you’ll end up where most enterprises belong.
✅ Stay BI-Tool-Centric When:
You’ve standardized on one primary BI tool
You have deep investment in existing semantic models
Business users own and validate metric definitions
Interactive query performance is critical
Your AI/agent strategy can sit on top of the BI model
You don’t have a genuine multi-tool consumption problem
⚠️ Consider Data-Platform-Centric When:
You genuinely operate 3+ BI tools with significant metric overlap
Your primary consumers are programmatic (APIs, agents, apps) rather than visual
You’re early in your analytics journey with no entrenched BI semantic model
You’re willing to accept the added complexity and governance overhead
Bottom Line
The data-platform semantic layer movement solves elegantly for a scenario that affects a minority of organizations, while introducing architectural complexity, governance fragmentation, and performance trade-offs for everyone else.
Before you rearchitect your semantic layer because you were told it’s “strategic,” ask the hard questions above. The answer for most enterprises today is: your BI tool’s semantic model is already the foundation of your semantic layer — and that’s not a weakness. It’s a huge strength.
For a deeper technical perspective on why stacking semantic models on top of each other doesn’t work, I highly recommend Chris Webb’s post Power BI And Support For Third Party Semantic Models. He explains the architectural reasons why — from SQL generation assumptions to metric aggregation conflicts — putting one semantic model on top of another creates fundamental problems that aren’t specific to any single tool.
Don’t let industry narratives make your architecture decisions. Let your users, your governance model, and your actual consumption patterns guide you.
A few weeks ago, I published the first PBI Fixer article. It had 11 fixers — 5 for reports, 6 for semantic models — and a single promise: pbi_fixer(). One line of Python in a Fabric Notebook, and your reports get better.
That promise still holds. But the tool behind it has become something I did not anticipate. Built on top of Semantic Link Labs by Michael Kovalsky, which provides the core APIs that make all of this possible, the PBI Fixer has grown from a simple fixer into a full development environment.
45+
12
19
0
1
Automated Fixers
Interactive Tabs
BPA Auto-Fix Rules
License Cost
Line of Python
What started as a fixer grew into a full Power BI development environment — a Semantic Model Explorer, a Report Explorer, a Memory Analyzer, a Perspective Editor, a Translations Editor, a Model Diagram generator, a Prototyping tool, BPA analyzers on both the model and report layer, a Delta Analyzer, and genuine IBCS implementation. All inside a Fabric Notebook. All driven by the same pbi_fixer() function call.
This article is the deep dive. I will walk you through every major capability, explain the reasoning behind the design decisions, and — because a picture is worth a thousand words — show you the screenshots.
The PBI Fixer is the fifth generation of my attempt to codify Power BI best practices into reusable automation. Each generation taught me something that the next one built on:
2023 — Power BI Pimp Script (C# script): A single C# script for an external tool that automated calendar tables, measure tables, and calculation groups. The idea that best practices should be one-click actions, not documentation you read and then forget.
2024 — 80+ C# Macros: The Pimp Script expanded into a full library — 80+ macros organized by category, integrated with Stream Deck shortcuts. But still local, still C#, still semantic-model-only.
2025 — PBI Fixer PowerShell Edition (9 fixers): The first tool that touched both the report layer and the semantic model. PowerShell scripts that downloaded all reports from a workspace, replaced pie charts, applied themes, fixed page sizes, and re-uploaded them. Powerful, but fragile: Windows file dialogs, local execution, the report format was still opaque binary.
February 2026 — pbi_fixer() v1 (11 fixers): The move to the cloud. The introduction of the PBIR format and Fabric’s connect_report() API made it possible to read and modify report definitions programmatically — in a notebook, without downloading anything. 11 fixers, an ipywidgets UI, scan/fix/scan+fix modes. No local tools, no downloads, just pbi_fixer().
Now — PBI Fixer v2 (45+ fixers, 12 tabs): The current version. Not just fixers anymore — a complete development environment. The moment I realized that the same infrastructure (connect_report for PBIR, connect_semantic_model for TOM, ipywidgets for the UI) could power interactive exploration, editing, and analysis, the scope expanded naturally.
2. Getting Started — Still One Line
Before I dive into the tabs and features, let me make sure you can actually run this. It is still two cells in a Fabric Notebook:
# Cell 1: Install (run once per session, takes ~1 minute)
%pip install git+https://github.com/KornAlexander/semantic-link-labs.git@feature/pbi-fixer-ui
# Cell 2: Launch
from sempy_labs import pbi_fixer
pbi_fixer()
That is all. The interactive UI renders in the cell output. Type your workspace name, report name, and you are in.
You can also pre-fill the workspace and report:
# Load specific reports (comma-separated)
pbi_fixer(workspace="My Workspace", report="Sales Dashboard, Finance Report")
# Load ALL items in a workspace
pbi_fixer(workspace="My Workspace")
⚠️ Note: The PBI Fixer currently lives in a fork of Semantic Link Labs. It is not part of the official package yet — we’re evaluating if it makes sense. Once (if) it gets merged, the install becomes a true one-liner: just %pip install semantic-link-labs. Until then, use the fork URL above.
Prerequisites
A Microsoft Fabric capacity (F2 or higher, or a trial capacity)
A Fabric Notebook in your workspace
Reports in PBIR format for report fixers (the tool detects PBIRLegacy and can convert)
XMLA read/write enabled at the tenant level (required for semantic model operations)
Large semantic model storage format enabled in workspace settings (required for XMLA write)
3. Report Explorer — See Your Report, Fix Your Report
The Report Explorer: tree view of pages and visuals with live embedded preview and fix actions
The Report Explorer is where most report-focused work happens. It loads your report’s PBIR definition and renders a navigable tree: Report → Pages → Visuals. Select any visual to see its properties — chart type, data fields, formatting. There is a live embedded preview panel so you can see what the visual looks like while editing its properties.
The key feature is the action dropdown at the top. This is where all 15 report fixers live:
#
Fixer
What It Does
1
Fix Pie Charts
Replaces pie charts with clustered bar charts
2
Fix Bar Charts
Removes axis clutter, adds data labels, removes gridlines
3
Fix Column Charts
Same clean-up applied to column charts
4
Fix Line Charts
Standardized line chart formatting
5
Fix All Charts
Unified formatting pass across all chart types
6
Column → Line
Converts column charts to line charts
7
Column → Bar (IBCS)
Converts column charts to horizontal bar charts
8
Fix IBCS Variance
End-to-end IBCS variance implementation
9
Fix Page Size
Upgrades default pages to Full HD (1080×1920)
10
Hide Visual Filters
Hides visual-level filters from consumers
11
Fix Visual Alignment
Snaps misaligned visuals to consistent positions
12
Remove Unused Custom Visuals
Cleans up unused custom visual registrations
13
Disable Show Items No Data
Turns off “Show Items with No Data”
14
Migrate Report-Level Measures
Moves report-level measures to the semantic model
15
Convert to PBIR
Upgrades PBIRLegacy to PBIR format
Every fixer supports Scan mode — a read-only pass that tells you what would change without touching anything. This is essential for auditing: scan ten reports, get a findings list, then decide which ones to fix.
4. Semantic Model Explorer — Your Model Workbench
The Model Explorer: object tree, DAX editing, data preview, with undo/redo tracking
The Model Explorer is a full workbench for your semantic model. It connects via XMLA and gives you a tree of everything: tables, columns, measures, hierarchies, relationships, calculation groups. Select any object to see its properties. Select a measure to edit its DAX expression and format string.
What sets it apart:
DAX editing — edit measure expressions directly, with a Save/Discard workflow
Create measures, calculated columns, and calculated tables — directly from the tree
Data preview — see Top 10 / Top 100 / All rows for any table with a single click
Auto-create measures from columns — the tool looks at SummarizeBy settings and generates explicit measures automatically
Add PY measures — select measures and get PY + ΔPY + ΔPY% generated for each
Undo / Redo — full change tracking, so you can roll back
This is not Power BI Desktop. It is not a replacement for external modeling tools. It is a focused tool that does the 80% of model editing work you actually need, running in the same notebook where you do your data engineering. No context switch. No download. No separate application.
5. IBCS — Where It Gets Interesting for Me
IBCS variance charts with proper green/red coloring, generated automatically from any existing bar or column chart
If you have read my other articles, you know that IBCS — the International Business Communication Standards — is a topic close to my heart. Clean, standardized business charts. No pie charts. No 3D effects. Horizontal bar charts for comparisons. Proper variance notation. Consistent scaling.
The PBI Fixer now has genuine IBCS implementation built in. And I don’t mean “it removes gridlines” — although it does that too. I mean end-to-end variance chart creation.
Here is what Fix IBCS Variance does, automatically, when you run it on a chart:
Checks if a calendar table exists — if not, creates one (the same CalcCalendar from the semantic model fixers)
Detects and builds the date relationship — finds the matching date column in your fact table and creates the relationship
Generates PY measures — PY (prior year), ΔPY (absolute variance), ΔPY% (relative variance) for the measures used in the selected chart
Adds error bar measures — creates the measures that drive the variance display
A workflow that takes 30+ minutes by hand — done in seconds. The important thing: the resulting chart uses native Power BI visuals. No custom visuals, no marketplace dependencies.
The additional IBCS-aligned fixers complement this:
Column → Bar — flips vertical column charts to horizontal bar charts (IBCS prefers horizontal for categorical comparisons). Power BI Desktop does not have a “change orientation” button — the Fixer rewrites the visual JSON.
Fix All Charts — removes axis clutter, gridlines, adds data labels. This is what IBCS calls “decluttering” — making the data speak without chart junk.
Fix Pie Charts — replaces pie charts with bar charts. IBCS explicitly discourages pie charts.
These are not cosmetic tweaks. If you are serious about business reporting standards, this is the first time you can apply them programmatically across an entire report — not just read about what you should do, but actually do it with a function call.
6. Model BPA — 19 Rules, One-Click Auto-Fix
Model BPA: 19 best practice rules across 6 categories with Fix All / Fix Rule / Fix Row granularity
Best Practice Analyzers exist. External tools have them. Semantic Link Labs has run_model_bpa(). But here is the gap: most BPA tools tell you what is wrong. Some offer fix expressions for a handful of rules, but applying fixes at scale — across many rules, many objects, with granular control — is still cumbersome. And the majority of rules simply have no auto-fix at all.
The PBI Fixer’s Model BPA takes a different approach: every single one of its 19 rules comes with a one-click auto-fix button. And you can choose the granularity: fix everything with “Fix All”, fix a specific rule for all affected objects, or fix a single row. The underlying BPA engine in Semantic Link Labs was built by Michael Kovalsky — the PBI Fixer adds the interactive auto-fix layer on top.
Category
Rules
🔢 Data Types
Fix Floating Point Types (Double → Decimal)
📡 MDX
Fix IsAvailableInMDX (False on non-attribute, True on key/hierarchy)
📝 Documentation
Fix Measure Descriptions (auto-fill with DAX expression)
📊 Formatting
Date format, Month format, Measure format, Percentage format, Whole number format, Flag column format, Sort Month Column, Data Category, Use DIVIDE(), Remove +0 prefix
🔗 Schema
Hide Foreign Keys, Do Not Summarize, Mark Primary Keys
🏷 Naming
Trim whitespace, Capitalize first letter
I cross-checked the PBI Fixer’s 19 auto-fixers against the community standard BPA rules: out of ~28 standard rules, only 7 have a FixExpression. Of those, just 4 overlap with the PBI Fixer’s rules (floating point types, IsAvailableInMDX, hide foreign keys, do not summarize). That means 15 of the PBI Fixer’s 19 auto-fixers — including all formatting rules, DIVIDE() correction, data categories, primary key detection, and naming fixes — have no equivalent auto-fix in the standard BPA ruleset.
7. Memory Analyzer — Vertipaq at Your Fingertips
Column-level memory breakdown with color-coded percentage bars
The Memory Analyzer — built on top of SLL’s vertipaq_analyzer() by Michael Kovalsky — shows you where your model’s memory goes, broken down by tables, columns, partitions, relationships, and hierarchies. This is Vertipaq analysis built into the notebook, with color-coded size bars (red for columns taking >30% of total memory, orange for >10%, green for ≤10%).
For Direct Lake models, it also shows column residency and temperature — whether the data is hot (in memory), warm (cached), or cold (on disk). This is critical information for Direct Lake optimization that Desktop simply cannot show you.
Grid view of all model objects × languages — add, edit, and manage translations in one place
If you work in a multi-language environment, this one is for you. The Translations Editor shows all model objects (tables, columns, measures) in a grid with a column for each language. You add a language from 20 built-in language codes, then edit translations directly in the grid — cell by cell. It is a manual process, but having all objects and all languages visible side by side makes it far more manageable than editing translations through the XMLA endpoint or third-party tools.
You can create new languages, delete existing ones, and see at a glance which objects are missing translations. No native Power BI tool gives you this kind of overview.
9. Perspective Editor — Visual CRUD for Perspectives
Expandable table tree with tri-state indicators: ● Full / ◐ Partial / ○ None
Perspectives are one of those features that everyone agrees are useful but nobody creates because the tooling is awkward. The Perspective Editor — inspired by the perspective management capabilities in SLL — makes it visual: an expandable tree with tri-state checkboxes. Select tables, columns, or measures for each perspective. Create new perspectives. Delete old ones.
If you are using Direct Lake and cache pre-warming (another fixer in the toolkit), perspectives are essential — the warm-up perspective defines exactly which columns to pre-load into memory.
10. More Tabs — Model Diagram, Prototyping, Delta Analyzer, Report BPA
Model Diagram
Auto-generated SVG diagram with table boxes, columns, measures, and relationship lines
An auto-generated SVG diagram of your data model. Tables as boxes, columns and measures listed inside, relationships drawn as connecting lines with nearest-edge routing. This is not Power BI Desktop’s model view — it is exportable, embeddable, and generated in a notebook cell. Useful for documentation, architecture reviews, or simply getting a quick overview of an unfamiliar model.
Prototype & Excalidraw Export
Report page screenshots with auto-detected navigation and drillthrough paths
The Prototype tab captures every page of your report as a screenshot and detects page navigation links, buttons, and drillthrough connections between pages. You can export the whole thing as an Excalidraw file or SVG — giving you, for the first time, a bird’s eye view of your entire report on a single canvas.
Parquet file and column chunk level inspection for Direct Lake optimization
If you are using Direct Lake, the Delta Analyzer is essential. Built on SLL’s delta table inspection capabilities by Michael Kovalsky, it inspects your lakehouse delta tables at the parquet file level — number of files, row groups, column chunks, column statistics, and cardinality. This is the data you need to decide whether your tables need V-Order optimization, repartitioning, or column type changes.
Report BPA
Report-level best practice analysis with auto-fix capabilities
While Model BPA focuses on the semantic model layer, Report BPA analyzes the report definition itself. It catches issues like unused custom visual registrations, “Show Items with No Data”, report-level measures (which should be in the model), and auto-converts PBIRLegacy before scanning.
11. Semantic Model Fixers — The Foundation
The 11 semantic model fixers use the XMLA endpoint and TOM (Tabular Object Model) to modify the data model directly. These are the original “one-click best practices” from the very first generation:
#
Fixer
What It Does
1
Discourage Implicit Measures
Prevents implicit measure usage (required for calc groups)
2
Add Calendar Table
20-column calculated calendar with 3 hierarchies + auto-relationship
Auto-create explicit measures from SummarizeBy settings
8
Add PY Measures
PY + ΔPY + ΔPY% for selected measures
9
Setup Incremental Refresh
Auto date column detection + policy configuration
10
Cache Pre-warm
Direct Lake warm-up perspective + notebook generation
11
Prep for AI
Descriptions, synonyms, linguistic metadata for Copilot readiness
Each of these runs through TOM and modifies the model’s metadata. Because XMLA write operations are irreversible (you cannot download the model as .pbix afterward), the UI requires an explicit confirmation before running semantic model fixers. Scan mode is always safe.
Cache Pre-Warm — Why This Matters for Direct Lake
Direct Lake is powerful because it reads directly from delta tables without importing data. But there is a cold-start problem: the first query against a model after a refresh (or after columns get evicted from memory) can be significantly slower because the data needs to be loaded from OneLake into the Vertipaq engine on demand. This is especially noticeable on models with many columns or wide fact tables.
The Cache Pre-warm fixer solves this by generating two things: a perspective containing only the columns that are actually referenced by DAX measures (so you are not warming up unused columns), and a notebook cell that runs a simple DAX query against that perspective — forcing the engine to pre-load those columns into memory. You can schedule this notebook to run right after your data refresh pipeline, so by the time users open the report, the data is already hot. Zero-effort, production-grade cache management.
Setup Incremental Refresh — Auto-Detection Done Right
Configuring incremental refresh manually is tedious: you need to identify the right date column, create RangeStart and RangeEnd parameters, modify the partition expression, and set the refresh window. The Setup Incremental Refresh fixer automates this entire process. It scans your model for date columns, lets you pick the right one, and configures the incremental refresh policy — including the historical window and the rolling refresh window. What used to be a multi-step, error-prone manual process becomes a single click with sensible defaults.
Prep for AI — Getting Copilot-Ready
Microsoft Copilot in Power BI works better when your semantic model has rich metadata: measure descriptions, column descriptions, synonyms, and linguistic schema info. But most models have none of this — descriptions are empty, synonyms are missing, and the model is essentially a black box to the AI.
The Prep for AI fixer generates all of this metadata automatically. It creates descriptions for measures (based on their DAX expressions), adds synonyms for columns and tables (common abbreviations and alternative names), and populates the linguistic schema that Copilot uses for natural language understanding. This is not just cosmetic — it directly improves the quality of Copilot-generated DAX queries and natural language answers. Going from zero to Copilot-ready in one click. Credit for the original idea and implementation goes to Lukasz Obst.
12. The Standalone API — Every Fixer is a Function
Everything you see in the UI is a Python function underneath. You do not need the interactive widget to use the fixers — you can call them directly in your own notebooks, scripts, or pipelines:
# Replace all pie charts in a report
from sempy_labs.report._Fix_PieChart import fix_piecharts
fix_piecharts("Sales Report", workspace="Production")
# Scan first (read-only)
fix_piecharts("Sales Report", workspace="Production", scan_only=True)
# IBCS variance — end to end
from sempy_labs.report._Fix_IBCSVariance import fix_ibcs_variance
fix_ibcs_variance("Sales Report", workspace="Production")
# Batch process multiple reports
reports = ["Sales", "Finance", "HR"]
for report in reports:
fix_piecharts(report, workspace="Production")
fix_page_size(report, workspace="Production")
This is where the AI and CI/CD story comes in. Because every fixer is a stateless function with a predictable signature (report, workspace, scan_only), an AI agent could generate the right call from a natural language prompt. A deployment pipeline could run scan-only passes as quality gates. A Fabric scheduled notebook could batch-fix formatting across all reports in a workspace overnight.
The functions are the same. The caller changes.
13. Built on Semantic Link Labs
I want to be clear about the foundation: the PBI Fixer is built on top of Semantic Link Labs, created by Michael Kovalsky. SLL provides the core abstractions — connect_report() for PBIR access, connect_semantic_model() for TOM/XMLA, and the notebook infrastructure that makes all of this possible in a Fabric environment.
Without SLL, the PBI Fixer would not exist. The fixer adds the interactive UI layer, the specific fix logic, the BPA rules, and the explorer/analyzer capabilities — but the engine underneath is Michael’s work. Credit where it is due.
The PBI Fixer is currently in a fork of SLL. We are evaluating whether it makes sense to merge it into the main repository. If that happens, %pip install semantic-link-labs would be all you need — the fork URL goes away, and it truly becomes one line.
14. What This Is Not
Let me be honest about the limitations:
Not a Desktop replacement — the PBI Fixer does not have a drag-and-drop report canvas. You cannot create new visuals from scratch (yet). It extends Desktop; it does not replace it.
Still beta — this is an early-stage tool with rough edges. Test on a non-production workspace first.
Requires Fabric — this runs in Fabric Notebooks. If you are on Power BI Pro without Fabric, this tool is not available to you.
XMLA write is irreversible — once you modify a model via the XMLA endpoint, you can no longer download it as a .pbix with embedded data. This is a platform limitation, not a PBI Fixer limitation.
That said — if you have Fabric, and you care about report quality, standardized formatting, and Best Practice adherence, I believe this is the most comprehensive tool available right now. And it is free.
15. “But Can’t AI Just Do This?”
Fair question. With Copilot in Fabric, ChatGPT writing Python, and agentic AI on the horizon — why build a UI tool at all?
Because AI is great at generating code when you know what to ask for. But most Power BI developers do not know that their model has floating-point columns, that 12 columns have SummarizeBy set to Sum, or that their report still uses pie charts. Discovery comes first — and that requires a structured UI that surfaces issues you did not know existed.
The PBI Fixer handles both sides. The interactive UI is for discovery: browsing a model tree, scanning for BPA violations, spotting memory hogs in the Vertipaq analysis, seeing which columns lack translations. You do not need to know what to look for — the tool shows you.
And once you know what to fix, the standalone API is where AI shines. Every fixer is a stateless Python function with a predictable signature. An AI agent can call fix_piecharts("Sales Report") just as easily as a human can click “Fix All”. A deployment pipeline can run scan_only=True passes as quality gates. A scheduled notebook can batch-fix formatting across an entire workspace overnight.
Today, you use the UI. Tomorrow, an AI agent runs the same functions. The PBI Fixer is not competing with AI — it is the toolkit AI will use.
16. What is Next?
The framework is open. Every fixer is an independent Python function with a consistent interface. Adding a new fixer means writing one function and wiring it into the UI. If you have an idea for a fixer — or you want to contribute — the GitHub repository is open.
This is mostly a free-time project — I work on it when I can, driven by curiosity and the feedback I get. If time allows, there is plenty more to build: more IBCS implementation, more BPA rules, video walkthroughs, and the ongoing evaluation of whether merging into Semantic Link Labs makes sense.
The first PBI Fixer article ended with:
The PBI Fixer is not just a tool — it is a shift in how we think about report quality. Instead of manually checking and fixing every visual property, every model setting, every best practice after the fact, we can now codify our standards and apply them at scale.
A few weeks later, that statement holds — but the scale has changed. From 11 fixers checking a few properties to a full development environment covering reports, models, BPA, memory, translations, diagrams, and prototyping. Still one line.
pbi_fixer()
If you try it, let me know on LinkedIn. Feedback — positive and negative — is what drives this project forward.
Every Power BI semantic model starts clean. But over time — as measures pile up, relationships multiply, and formatting inconsistencies creep in — models silently degrade. By the time someone notices a slow report or a questionable DAX pattern, the damage is already done.
What if your models could check themselves — every week, automatically — and write the results to a place where you can query, compare, and trend them over time?
That’s exactly what this Fabric Notebook does. It combines three analyses into one script, saves everything to a Lakehouse, and runs on a schedule. No manual effort after the initial setup.
Why Run BPA and Memory Analyzer Together?
Microsoft Fabric comes with two built-in analysis notebooks: the Best Practice Analyzer (BPA) and the Memory Analyzer (also known as Vertipaq Analyzer). Most people run them separately, if they run them at all. But running them together gives you the full picture of your model’s health:
BPA catches structural problems — missing format strings, duplicate measures, hidden foreign keys not set, unused objects, DAX anti-patterns. It tells you what’s wrong with how the model is built.
Memory Analyzer shows you where the weight is — which tables, columns, and segments consume the most memory. A model can pass every BPA rule and still be bloated because of high-cardinality columns or unnecessary detail data.
Together, they answer both questions: “Is this model built correctly?” and “Is this model built efficiently?”
On top of that, the script also captures seven INFO.* DAX system views — tables, columns, measures, relationships, storage tables, storage columns, and storage segments. These are the raw metadata that power tools like DAX Studio’s Vertipaq Analyzer, but now they live in your Lakehouse as queryable Delta tables.
Direct Lake Models: What You Need to Know
This notebook works with both Import and Direct Lake semantic models — but the Memory Analyzer results mean different things depending on which mode you’re using.
Import Models: Stable, Predictable Memory
For Import models, memory analysis is straightforward. The entire dataset is loaded into memory at refresh time and stays there. When you run Memory Analyzer, you see the full footprint — every table, every column, every segment. The results are stable and reproducible.
Direct Lake Models: Dynamic, Query-Driven Memory
Direct Lake models load data on demand — only the columns and rows that are actually queried get paged into memory. This means the Memory Analyzer shows you a snapshot of what’s currently cached, not the theoretical maximum size of the model.
If you run the analysis right after opening the semantic model, you’ll see almost nothing in memory — the cache is cold. To get realistic memory metrics for a Direct Lake model:
Warm up the cache first. Run your most common reports or execute representative DAX queries to load the frequently accessed columns.
Run the notebook after typical usage. Schedule it for a time when the model has been actively queried during business hours — not at 3 AM when the cache is empty.
Interpret the results as “working set” size. The memory footprint you see reflects what’s needed to serve recent queries, not the full lakehouse table size. This is actually more useful for capacity planning — it tells you the realistic memory demand.
BPA rules, on the other hand, work identically for both Import and Direct Lake — they analyze the model’s structure (measures, relationships, formatting), not the data in memory.
The Three-Step Pipeline
The notebook runs sequentially through three steps:
Step 1: Best Practice Analyzer
Uses fabric.run_model_bpa() to evaluate the semantic model against the standard rule set. Results include the rule category (Formatting, Maintenance, Performance, DAX Expressions), severity, affected object, and a description of the issue.
BPA output — 116 rules checked across DAX Expressions (12), Formatting (27), Maintenance (45), and Performance (15) categories with severity breakdown
Step 2: Memory Analyzer
Uses fabric.model_memory_analyzer() to produce a tabbed summary: Model Summary, Tables, Partitions, Columns (Total Size), Columns (Temperature), Relationships, and Hierarchies. This is the same analysis DAX Studio provides — but running directly inside Fabric.
Memory Analyzer — Model Summary showing dataset size (381.91 KB), 7 tables, 27 columns, and INFO table results
Step 3: DAX INFO Views
Seven EVALUATE INFO.*() queries capture the full internal metadata of the model and save each as a Delta table:
Each result set gets an analysis_timestamp, model_name, and workspace_name column appended before saving — so you can track multiple models over time in the same Lakehouse.
Why Save to a Lakehouse?
Running an analysis notebook and looking at the output once is useful. But the real value comes from persistence:
Trend analysis. When BPA results are appended weekly, you can track whether your model is getting better or worse. Did someone introduce 12 new unformatted measures last sprint? You’ll see it.
Cross-model comparison. Run the same notebook against multiple semantic models (just change the configuration variables). Query all results in one place to find which models need the most attention.
SQL Analytics Endpoint. Every Delta table in a Fabric Lakehouse is automatically exposed via a SQL endpoint. That means you can query BPA results from SSMS, Azure Data Studio, or even build a Power BI report on top of your model health data.
Audit trail. When stakeholders ask “how has model quality improved this quarter?”, you have the data to answer.
Querying bpa_analysis_results via SQL in SSMS — DemoLakehouse with all 8 Delta tables, showing Category, Rule_Name, Severity, and other columns
Why Schedule It?
A one-time health check is a snapshot. A scheduled health check is a monitoring system.
Fabric Notebooks support native scheduling — no Data Factory pipeline needed. From the top ribbon, click Schedule, set it to Weekly, pick a day and time, and save. The notebook runs unattended, appends results to your Lakehouse, and you’re done.
Schedule setup — Weekly on Monday at 12:00 PM, Brussels timezone, running from April 2026 to April 2027
This means:
No manual intervention. Models are checked every week without anyone clicking “Run”.
Early warning system. New BPA violations show up within a week of being introduced.
Memory regression detection. If a model’s memory footprint suddenly doubles after a data source change, the INFO tables will show it in the next scheduled run.
Compliance documentation. Some organizations require periodic checks on data model quality. A scheduled Lakehouse write creates an immutable record.
Setup: Five Minutes to Automated Model Monitoring
Here’s the complete setup process:
Create a Lakehouse in your Fabric workspace (or use an existing one).
Create a new Notebook and attach the Lakehouse as the default (left panel → Lakehouses → Add).
Why moving to Microsoft Fabric is like upgrading from a small airstrip to a world-class airport
Preamble: This article is not a comprehensive migration guide. It is a narrative — a story told through an aviation metaphor — that illustrates why Microsoft Fabric transforms Power BI from a capable tool into a professional, future-proof analytics platform. The views expressed here are my own and do not represent official Microsoft guidance.
The full picture: Seven areas where Fabric elevates your Power BI environment
Imagine your Power BI environment is an airline. Reports are your fleet, data models are your engines, and your team — the analysts, developers, admins — are the pilots and ground crew keeping everything in the air.
For years, the operation has worked. Flights depart, passengers arrive, the business gets its data. But behind the scenes, things are starting to strain. Deployments feel like taxiing without instruments. Troubleshooting means searching through scattered flight logs. And when someone asks “can we do AI?”, you realize your runway is too short for the next generation of aircraft.
Microsoft Fabric is the airport upgrade. Not a different airline — the same planes, the same crew — but with a modern terminal, a control tower, proper hangars, and a runway long enough for whatever comes next.
Let me walk you through seven areas where this upgrade changes everything.
1. Training in the Flight Simulator — A Professional BI Environment
Before your reports go live, they train in the simulator
No airline would let a pilot fly passengers after just one practice run. Yet in many Power BI environments, that’s exactly what happens: changes go straight to production. A modified DAX measure, an updated data model, a new page — deployed directly to the live report that hundreds of users rely on.
Fabric introduces the flight simulator — a proper DEV → QA → PRD lifecycle.
Development (DEV): Build and experiment freely in an isolated workspace. Break things. Try new approaches. No one sees it until you’re ready.
Quality Assurance (QA): Test with real data, validate with stakeholders, catch issues before they matter.
Production (PRD): Deploy with confidence through Deployment Pipelines — automated, controlled, reversible.
On top of this, Fabric enables full Git integration. Every change to a report, semantic model, or notebook is version-controlled. You can see who changed what, when, and why. And if something goes wrong? Rollback in seconds — not hours of panicked troubleshooting.
Add CI/CD pipelines with code review, and you have a deployment process that matches what software engineering teams have enjoyed for decades. No more manual risk. No more “who published that change?”
In aviation terms: Your pilots now train in a simulator before every flight, and every procedure is documented in a flight manual that the entire crew can reference.
2. Upgrades in the Hangar — Better Power BI Reports
The hangar is where your reports get tuned for peak performance
Every aircraft needs regular maintenance. Engines are inspected, instruments recalibrated, performance data reviewed. Without a proper hangar? Maintenance happens on the tarmac, in the rain, with improvised tools.
That’s been the reality for many Power BI environments. Analyzing model performance meant installing third-party tools like Tabular Editor and DAX Studio, configuring XMLA endpoints, and relying on experts who knew the process by heart. Powerful — but fragile and inaccessible to most team members.
Fabric changes this by integrating Vertipaq Analyzer and Best Practice Analyzer (BPA) natively into the platform. What used to require multiple tools and expert configuration now takes just a few clicks:
Open your semantic model in Fabric
Click on BPA or Memory Analyzer
Run the notebook
That’s it. The same deep analysis — column-level memory consumption, rule violations, data type issues — now accessible to the entire team.
But the hangar offers more than diagnostics. With Fabric Notebooks, you can automate report optimization — fix formatting issues, apply best practices, even replace poor visualization choices programmatically. And with pre-built templates, new reports start from a solid foundation instead of a blank canvas.
In aviation terms: Your hangar now has state-of-the-art diagnostic equipment, automated maintenance routines, and a parts catalog that ensures every new aircraft starts with the right configuration.
OneLake: One airfield, all cargo — no matter where it originates
Ever been at the airport with a suitcase that’s 24 kg? Sorry, 23 kg max — you’re not getting on that plane. Repack, leave something behind, or pay extra.
That’s Power BI Pro with its 1 GB model limit. Your data doesn’t fit? Too bad — repack, split, or leave data behind. And on top of that: max 8 refreshes per day, data duplicated across models.
OneLake — Fabric’s unified data layer — changes this fundamentally:
Direct Lake instead of Import: Reports can query data directly from the lakehouse without importing it. Real-time freshness, no duplication, no artificial model size limits.
Shortcuts & Mirroring: Connect external data sources — Snowflake, SQL Server, Azure SQL — without copying data. The data stays where it lives; Fabric provides a unified access layer.
No more 8-refreshes-per-day limit: With Direct Lake, your reports always reflect the latest data. No scheduling gymnastics, no “the data is from this morning” disclaimers.
Break the 1 GB barrier: Large datasets that previously required Premium capacity or complex workarounds now have room to breathe.
Cross-team collaboration: Different departments share data through Lakehouses and Shortcuts instead of building isolated silos. Finance can access the same data foundation as Operations — governed, consistent, up-to-date.
In aviation terms: Instead of every plane carrying its own cargo hold, you now have a central logistics hub. Every flight can access what it needs, when it needs it — whether the cargo originated locally or was shipped in from an external partner.
4. The Control Tower — Monitoring & Governance
The control tower sees everything: performance, lineage, impact, security
A small regional airport has one person with binoculars, a radio, and a weather app. It works — until traffic grows. Then you need a proper control tower with radar, flight tracking, and a full operations team.
That’s the difference between Power BI without Fabric and with it.
Without Fabric, a user reports: “My report shows an error.” Your options? Basically none. No logs, no query history, no details. You can’t reproduce it, you can’t trace it, you can’t help — unless the error happens to occur again right in front of you.
With Fabric, you get the full control tower:
Workspace Monitoring: Every query, every error, every performance spike — logged and traceable. A user reports a problem from yesterday? You look it up, see the exact DAX query, the filter context, and what went wrong. Retrospective analysis becomes possible.
Automated quality checks: Scan all reports and semantic models for rule violations, performance issues, and configuration problems — continuously, not just when someone remembers to check.
Lineage & Impact Analysis: Before changing a data source, a column, or a measure — see exactly which reports and dashboards are affected. No more “we didn’t know that report depended on this table.”
OneLake Security: One unified security model across all data. Define access rules once; they apply everywhere — across Lakehouses, Warehouses, and Semantic Models.
In aviation terms: Your airport now has radar, flight tracking, runway management, and air traffic control — all in one tower. No more flying blind.
AI is no longer a future project — but without the right data foundation, every initiative remains patchwork
What does it mean when an airplane is “ready”?
It depends on who you ask:
The pilot says: preflight checks are done
Maintenance says: the plane is repaired and certified
Operations says: the plane is assigned to a flight
The CEO says: the plane is in our fleet
Same plane — four different meanings of “ready.” Without a shared understanding, communication breaks down.
That’s exactly the challenge with data. What does “revenue” mean? Ask Sales, Finance, and Operations — you’ll get three different answers.
This is where Ontology comes in: a shared understanding of your business — for humans AND for AI. Your semantic models become that ontology. Everyone speaks the same data language.
Fabric makes your data AI-ready from day one:
Central Data Lake for ML/AI: All data in OneLake, immediately available for Machine Learning and AI models. No complex data preparation, no separate pipelines. Data that powers your reports today can train models tomorrow.
Semantic Layer as Ontology: Your semantic models define what business terms mean. That shared understanding is what makes AI responses accurate and trustworthy.
Open Standards: Data in OneLake is stored as Delta-Parquet. Any ML framework — Python, Spark, R — can access it directly. No vendor lock-in, no format conversions.
This is perhaps the most future-critical argument: organizations that don’t centralize their data now will struggle to leverage AI later. Fabric doesn’t force you into AI workloads — but it ensures the foundation is there when you’re ready.
In aviation terms: You’re not just building runways for today’s aircraft. You’re laying the foundation — fuel systems, navigation infrastructure, clearance protocols — for the next generation of autonomous, intelligent flight.
6. AI-Ready — Copilot & Data Agents
The co-pilot is ready: AI-assisted analytics for everyone on board
Every pilot has a co-pilot. So should you.
The data foundation is in place. Now comes the part your business teams will love.
Copilot Built Right In: In Power BI and Fabric Notebooks, analyze data and create reports using natural language. No DAX or SQL required. Available starting from F2 capacity.
Data Agents: One agent that speaks all the languages: DAX, SQL, GraphQL — you don’t need to know which one. Ask a question, the agent figures out the right query language behind the scenes.
Reuse Everywhere: Build a Data Agent once, reuse it across Fabric, Foundry IQ, Microsoft 365 Copilot, and any MCP-compatible tool. An MCP endpoint is created automatically with each agent. Zero extra setup.
Custom Instructions at Every Level: Want the agent to respond in Chinese? Set it in the agent instructions. But it gets even better: each connected data source can have its own instructions — business context, naming conventions, query hints. Global behavior + source-level precision.
From Question to Insight in Seconds: Instead of waiting weeks for a new report, users interact with data directly. The semantic model becomes the central knowledge layer — not just for reports, but for AI-powered analytics.
In aviation terms: Your airport now has a co-pilot in every cockpit. Not replacing the pilot — but augmenting their capabilities, handling routine tasks, and making the entire operation smarter.
7. Fabric Grows with You
Start small, scale intentionally — Fabric grows with your ambitions
The last point might be the most important one: Fabric doesn’t ask you to go big on day one.
Here’s what surprises many people: even the smallest Fabric capacity — an F2 — already unlocks the full feature set. Deployment Pipelines, Git Integration, Workspace Monitoring, Direct Lake, Copilot, Data Agents — all available. No Premium Per User licenses needed. No feature gating. Everything from the previous six sections works on the smallest SKU.
That changes the adoption conversation entirely. You don’t need executive sponsorship for a large capacity commitment. Start with an F2 or F4. Pick one workspace, one high-value use case. Let your team experience the difference. Then, as value becomes visible, scale intentionally:
Add more workspaces to the capacity
Enable Direct Lake for high-traffic reports
Introduce Notebooks for automation
Onboard Data Engineers, Data Scientists, Real-Time Engineers onto the same platform
Cover all use cases — ETL, Notebooks, Planning, Warehouses, Lakehouses — and reduce shadow IT
In many cases, a small F-series capacity is even cheaper per user than adding PPU licenses once you have more than a small group of users. Fabric is not a niche solution — it’s a single platform for all data consumers. All use cases covered — ETL pipelines, Notebooks, Warehouses, Lakehouses, Eventstreams, Planning — all in one ecosystem. That means less shadow IT, more control, and a future-proof architecture that evolves with every Fabric update.
The recommendation is simple:
Equip all your workspaces with Fabric capacity.
The sooner you switch, the sooner you benefit — from better performance, less operational overhead, and a platform that’s ready for whatever comes next.
This article complements my earlier deep-dive: Power BI Without Fabric Is Like Owning a Sports Car but Leaving It in Eco Mode. That article goes into technical detail on BPA, Vertipaq Analyzer, Workspace Monitoring, and Premium feature access. This article tells the story from 10,000 feet — the altitude where the bigger picture becomes clear.
If you manage a Microsoft Fabric capacity, there’s a good chance your users have already experienced throttling — 20-second delays, rejected queries, or broken dashboards — without you even knowing. The root cause is usually a temporary CU spike that pushes the capacity past its limits. The fix? Capacity Overage — a zero-cost insurance policy that automatically absorbs those spikes before they impact users.
In this article, I’ll explain what happens without overage, how to enable it, and how to combine it with Surge Protection for comprehensive capacity resilience.
The Problem: What Happens Without Overage
Fabric uses a smoothing mechanism to spread compute (CU) consumption over time. When usage exceeds what the capacity can handle, throttling kicks in — and it gets progressively worse:
20-second delays on every interactive request. When smoothed usage exceeds 10 minutes of future capacity, Fabric adds a 20-second wait to every dashboard query, report load, and ad-hoc analysis. Users notice immediately.
Full rejection of interactive workloads. Once the 60-minute threshold is breached, all interactive requests — report views, DAX queries, dataflow previews — are rejected outright. Users see error messages instead of data.
Background jobs blocked for up to 36+ hours. At the 24-hour threshold, even scheduled refreshes, pipelines, and Spark jobs are rejected. Recovery can take 1.5× the overload window — meaning a capacity at 250% background rejection needs ~36 hours to recover, during which nothing runs.
Cascading user frustration & lost trust. End users don’t see “CU exhaustion” — they see broken dashboards, failed refreshes, and unresponsive reports. Repeated incidents erode confidence in the platform and drive shadow IT.
The Solution: How Capacity Overage Helps
Automatic throttling prevention. Overage kicks in the moment the interactive delay threshold (>100%) is crossed. It pays off excess CU debt so delays and rejections never reach the user.
Zero standing cost. There is no charge for having overage enabled. You only pay when it actually activates — and only for the CU-hours that would have caused throttling.
Admin-controlled spending limit. Set a rolling 24-hour CU-hour cap (in multiples of 48 CU). Once the limit is reached, standard throttling resumes — so costs are always predictable and bounded.
Instant activation, no restart required. Overage becomes active within 5 minutes of enabling. No capacity pause/resume, no workspace migration. Toggle it on and it works.
Works alongside Surge Protection. Overage handles interactive spikes; Surge Protection limits background job accumulation. Together they provide comprehensive capacity resilience.
Cost Perspective: Overage vs. Scaling Up
Overage charges at 3× the pay-as-you-go rate, but only for excess CU-hours actually consumed. For occasional spikes this is significantly cheaper than permanently over-provisioning by upgrading to the next SKU.
Rule of thumb: Keep the overage limit at ⅓ of your daily CU-hours — that is the break-even point with scaling up to the next SKU size.
Scenario
Without Overage
With Overage
Occasional spike (1–2× /week)
Users hit 20s delays or rejection; admins firefight manually
Auto-resolved; users unaffected; small pay-per-use charge
Major spike (e.g., month-end)
Hours of rejection; 36h+ recovery; escalations to IT
Overage absorbs burst up to limit; workloads continue; clear cost trail
Sustained overload
Throttled until optimized or scaled up
Buys time to react; doesn’t replace proper sizing — scale up if recurring
When to Use — and When Not
Ideal for: Rare unexpected spikes, small routine overloads, business-critical dashboards that cannot tolerate delays, capacities shared across multiple teams.
Not a substitute for: Persistent under-sizing (scale up instead), workload optimization, or dedicated capacity isolation for mission-critical workloads.
Surge Protection: Prevent Runaway Background Jobs
Surge Protection is a complementary capacity setting with two controls:
Workspace Consumption (interactive + background): Set a per-workspace CU limit as a percentage of total capacity over a rolling 24-hour window. When a workspace exceeds the threshold, it is blocked and all operations are rejected. Each workspace can be tagged as:
Available (default): follows surge protection rules
Mission Critical: exempt from workspace-level blocking, keeps running during spikes
Blocked: all operations rejected (manual or automatic)
Background Operations (background only): Set a background rejection threshold (e.g., 60%) so background jobs are rejected before the capacity enters deep throttling (normally at 100%). This frees up CU for interactive use and prevents long recovery times. Does not directly protect interactive requests.
Combined Strategy: Surge Protection + Overage
You can combine both features on a single capacity to protect specific workspaces from each other — for example, a development workspace from a production one, both on the same capacity:
Enable workspace-level surge protection with a CU % threshold per workspace.
Mark your critical workspaces as Mission Critical — they are exempt from workspace-level blocking.
Leave other workspaces as Available — they get auto-blocked before causing capacity-wide throttling.
Enable Capacity Overage — catches remaining interactive spikes for your mission-critical workspaces.
Result: Non-critical workspaces get blocked by surge protection before they can cause overage billing. Overage effectively only fires for mission-critical workspaces. Surge protection reduces overage activation frequency, keeping costs low.
Note: Capacity-level throttling still applies to all workspaces once CU limits are fully exhausted. Mission Critical status only overrides workspace-level surge protection, not capacity-level limits. Overage handles that gap.
Recommendation & How to Enable
Enable Capacity Overage on every production F16+ capacity with a conservative spending limit (⅓ of daily CU-hours). This provides a zero-cost insurance policy when idle and an automatic safety net when spikes occur — protecting user experience without requiring admin intervention.
Admin Portal → Capacity Settings → select your capacity
Expand “Capacity Overage” → toggle On
Set a 24h CU-hour spending limit (start conservative, e.g. 48–96 CU-hours)
Click Apply — active within 5 minutes
Monitor via Capacity Metrics app + Azure Cost Management
My kids and I vibe coded a jump & run game over the weekend. What started as a Python script turned into a full Fabric analytics pipeline — with a playable game running inside a notebook, stats saved to a Delta table, and a Power BI report on top.
Here is the game. Click the canvas below and start playing.
🎮 Play the Game
Controls: Arrow Keys / WASD to move · Space / W to jump · F to attack · Stomp enemies from above
My three boys wanted to build a game. We started with Python and Pygame — a classic jump & run with platforms, enemies, coins, water hazards, and a dragon boss at the end. The character collects power-up items (fox, egg, bat) that change its form and unlock different attacks: a lightning strike or a sonic wave.
It worked, but Pygame means installing Python locally. I had a different idea: what if we could play it inside a Microsoft Fabric notebook?
One Line to Play
I rewrote the entire game in HTML5 Canvas and JavaScript. The game logic — physics, collision detection, level generation, enemy AI, particle effects, audio — is a single self-contained HTML file. No external dependencies, no CDN links, no server.
A second notebook cell uses ipywidgets to create a paste-and-save UI. You copy the JSON from the game output, paste it into a text area, click Save, and it appends the row to a game_stats Delta table in the Lakehouse.
A third cell runs a SQL summary:
SELECT
COUNT(*) AS total_games,
SUM(CASE WHEN won THEN 1 ELSE 0 END) AS wins,
ROUND(100.0 * SUM(CASE WHEN won THEN 1 ELSE 0 END) / COUNT(*), 1) AS win_rate,
MAX(score) AS high_score,
ROUND(SUM(duration_seconds) / 60, 1) AS total_playtime_min,
SUM(coins_collected) AS total_coins,
SUM(enemies_stomped + enemies_zapped) AS total_kills,
SUM(deaths_total) AS total_deaths,
SUM(jumps) AS total_jumps
FROM game_stats
Now my kids argue about who is the best player — with data.
Taking It Further: Power BI Report & Automated Stats Collection
The Delta table is nice, but I imagine my boys want a leaderboard they can check anytime — not a SQL query. So the next step is a Power BI report on top of the game_stats table. Direct Lake, obviously. No import, no scheduled refresh. The report reads straight from the Delta table in the Lakehouse. Every new game round shows up the moment you refresh the page.
But the manual paste step still bothers me. Right now, after each game-over you have to copy the JSON, switch to the save cell, paste it, and click Save. That works, but it’s manual. The goal is to fully automate it — the game writes stats directly to the Lakehouse without any manual step in between. The challenge: Fabric’s displayHTML() runs inside a sandboxed iframe with no access to the parent notebook, no postMessage, no clipboard API, and no way to call PySpark from JavaScript. Every automatic approach I tried — OneLake REST API from the iframe, postMessage to the notebook, navigator.clipboard — was blocked by the sandbox.
The workaround I am exploring: a lightweight Fabric API endpoint (or a Data Pipeline trigger) that the game’s JavaScript can POST the JSON to directly. If Fabric eventually opens up notebook cell-to-cell communication or relaxes the iframe sandbox, the whole thing becomes a one-click experience. For now, the ipywidgets paste-and-save UI is the pragmatic solution.
Why This Matters (Beyond the Fun)
This project is a real demonstration of what Fabric can do:
displayHTML() renders any self-contained HTML inside a notebook cell — games, dashboards, custom UIs, interactive visualizations
ipywidgets enables interactive controls (text areas, buttons, dropdowns) natively in notebook output — no external web framework needed
Delta tables are first-class citizens — any structured data, from any source, can land in the Lakehouse
SQL analytics run directly against Delta tables — no separate query engine, no ETL
The full pipeline — from data generation to storage to analysis — lives in a single notebook
If a kids’ game can become a full analytics pipeline in a weekend, imagine what you can do with your business data.
How I used GitHub Copilot Agent Mode, Playwright MCP, and Excalidraw to prototype a Power BI report redesign — automatically.
1. The Problem
You have a Power BI report with 35 pages and 5 levels of drill-through navigation. Users get lost. Pages are hard to maintain. You want to redesign the navigation — but how do you plan it visually before touching a single Power BI file?
Traditional approaches involve:
Opening the report and clicking through pages manually
Sketching boxes on a whiteboard
Creating mockups in PowerPoint
None of these give you a connected view of what exists (screenshots of every page) alongside what should change (proposed page hierarchy).
2. The Solution: AI-Powered PBI Prototyping
I built a workflow that combines three tools:
Playwright MCP — automatically screenshots every page of a Power BI report
Excalidraw — organizes screenshots with labels, levels, and before/after comparison diagrams
PBIR files — extracts the actual page navigation graph from the report definition
All orchestrated by GitHub Copilot in Agent mode inside VS Code.
The complete prototyping workflow — from opening the report to the final Excalidraw file.
3. Screenshot Every Page with Playwright
Power BI renders visuals on <canvas> elements — there’s no way to extract the visual content as DOM elements. Screenshots are the only viable approach.
Using the Playwright MCP server in VS Code, Copilot navigates to the report, opens the page dropdown at the bottom, and loops through every page — clicking each one, waiting for visuals to render, and taking a screenshot. The entire process is hands-free.
Key learnings:
Open the report in full-screen mode before running the script — avoids capturing sidebars and filter panes
Wait 4 seconds per page for visuals to render (8 seconds for complex pages)
For non-default tenants, launch Edge with --remote-debugging-port=9222 and connect Playwright via CDP
Result: 35 clean, full-screen screenshots in under 3 minutes.
4. Extract Page Navigation from PBIR Files
Instead of manually mapping which pages link to which, I export the report’s PBIR definition via the Fabric REST API.
Every Power BI project (PBIP) consists of two parts: the PBIR (report definition) and the TMDL (semantic model definition). The PBIR contains a JSON file for every page and every visual — including the exact drill-through targets, button navigation actions, and page references. This means the complete navigation graph of the report is encoded in these files.
# Authenticate and export the report definition via Fabric REST API
Login-PowerBIServiceAccount
$uri = "https://api.fabric.microsoft.com/v1/workspaces/$workspaceId/reports/$reportId/getDefinition"
$response = Invoke-WebRequest -Uri $uri -Method Post -Headers $headers
# The API returns a long-running operation — poll until complete,
# then save all files locally (pages, visuals, assets, themes, ...)
By searching for cross-page references in the visual.json files, I can extract the complete navigation graph:
From: Page 1 (Overview) → To: Page 2a, Page 2b, Page 2c, ...
From: Page 2a → To: Page 3a, Page 3b, ...
From: Page 3a → To: Page 4a
From: Page 4a → To: Page 5a
This gives me the actual drill-through depth for every page — no guessing required.
Result: A complete navigation graph with hundreds of cross-page links, automatically determining that some sections have 5 levels of drill-through.
5. Build the Excalidraw Prototype
Excalidraw files are pure JSON — no special tools needed. I generate the entire prototype programmatically with Python. Screenshots are embedded as base64-encoded images directly into the Excalidraw JSON file, so the result is a single portable file with everything included.
Color-Coded Level Indicators
Each page gets a label with color coding based on its drill-through depth:
L1 (Teal) — Landing page
L2 (Blue) — First entry from overview
L3 (Purple) — Analysis pages
L4 (Orange) — History pages
L5 (Red) — Detail drill-through
TT (Gray) — Tooltip pages
Layout: Categories Horizontal, Pages Vertical
Categories are arranged as columns. Within each column, pages are stacked vertically in drill-through order. This gives an instant visual sense of which areas are deep and which are flat.
Before/After Comparison Diagrams
Below each category’s screenshots, I add box diagrams showing the current vs. proposed hierarchy:
I also tried the Figma MCP server for this workflow. Figma’s capture can convert HTML pages into editable design layers — great for web UIs. I successfully captured the before/after box diagrams as editable Figma frames.
However, for Power BI prototyping specifically, Figma has limitations: Power BI renders visuals on <canvas> elements which are invisible to the capture script, the MCP is write-only (no editing existing layers), and each capture ID is single-use. If you need to share prototypes with designers already in Figma, it works as an additional output — but Excalidraw is the better primary tool because:
Full programmatic control — the JSON file format allows unlimited manipulation
No API limits or authentication required
Works offline in VS Code with the Excalidraw extension
Images, text, shapes, and arrows in a single portable file
Total time: ~10 minutes for a 35-page report, fully automated.
8. Try It Yourself
Prerequisites
VS Code with GitHub Copilot (Agent mode)
Playwright MCP server (npx @playwright/mcp@latest)
Excalidraw VS Code extension (pomdtr.excalidraw-editor)
MicrosoftPowerBIMgmt.Profile PowerShell module
Quick start
Open VS Code with GitHub Copilot (Agent mode)
Install the Playwright MCP server (npx @playwright/mcp@latest)
Open your Power BI report in the browser and copy the URL
Ask Copilot: “Screenshot all pages of this Power BI report, download the PBIR definition, analyze the page navigation hierarchy, and create an Excalidraw file organized by category with level indicators and before/after comparison diagrams”
The AI handles the automation. You focus on the design decisions.
This workflow isn’t just for redesigning reports. It’s equally valuable as a documentation tool for important production reports:
Onboarding new team members — Instead of clicking through a 30-page report live, hand them an Excalidraw file with every page screenshot, labeled with descriptions and navigation hierarchy. They see the full picture in one canvas.
Change tracking — Run the screenshot workflow periodically and compare versions side by side. Visual diffs catch layout changes that text-based diffs miss.
Stakeholder communication — Share the Excalidraw file (or a Figma capture) with business stakeholders to discuss which pages they actually use, which are redundant, and where gaps exist.
Audit and compliance — For regulated environments, having a visual snapshot of every report page with timestamps serves as documentation of what was deployed and when.
Cross-team alignment — When multiple teams build reports in the same workspace, the navigation graph reveals overlaps, orphaned pages, and opportunities to consolidate.
The same 10-minute automated process that enables prototyping also produces comprehensive visual documentation — with zero manual effort.
What’s Next
Automated PBIR modification — Once the prototype is approved, programmatically update the report’s page structure to match the proposed design
Semantic model analysis — Apply the same prototyping approach to data model optimization
Template generation — Create reusable prototyping templates for common report patterns
Every Power BI developer knows the pain. You inherit a report — or revisit one you built six months ago — and find pie charts everywhere, axis labels on bar charts that serve no purpose, the default 720×1280 page size from 2018, visual filters that confuse end users, no calendar table, no “Last Refresh” indicator, and implicit measures galore.
Fixing all of that manually means clicking through dozens of property panes, writing Tabular Editor scripts, exporting TMDL, or opening the JSON files in Power BI Desktop developer mode. And that is just for one report.
What if you could do it all — for any report in any workspace — in a single line?
pbi_fixer()
That is not a concept. That is a working function, built on top of Semantic Link Labs, running natively inside a Microsoft Fabric Notebook. No external tools. No downloads. No separate C# runtime. Just Python, the Fabric APIs, and one function call.
A big thank you to Michael Kovalsky, the creator and maintainer of Semantic Link Labs. His work laid the groundwork that makes the PBI Fixer possible. He also pointed me in the right direction when I was exploring whether an interactive UI was even feasible inside a Fabric Notebook. Without Semantic Link Labs, none of this would exist.
The Game Changer: A Native Fabric Solution
One Line. (Nothing Else.)
The PBI Fixer is a notebook-native interactive UI built with ipywidgets. When you call pbi_fixer(), it renders a complete control panel directly in your notebook cell output:
Workspace / Report / Page input fields to target exactly what you need
Mode selector — Fix, Scan, or Scan + Fix
11 fixers split across two categories — Report Visuals and Semantic Model
Checkbox control — pick exactly the fixers you want
XMLA write confirmation — a safety gate for semantic model modifications
Progress log — live output showing every action, every finding, every fix
You get the same result whether you point it at a report in your personal workspace or at a production workspace with a service principal. It is the same API, the same tool, the same one line.
PBI Fixer UI in a Fabric Notebook
If You Are Not on Fabric, You Are Missing Out
Let me be direct: if you are still exclusively on Power BI Pro or Premium Per User without a Fabric capacity, you are missing out on an entire generation of tooling. The PBI Fixer leverages:
Fabric Notebooks — serverless Python compute with ipywidgets for interactive UIs
PBIR format — the enhanced report format that exposes every visual property as JSON, readable and writable through the Fabric REST API
XMLA endpoints — read/write access to the Tabular Object Model (TOM) for semantic model modifications
Semantic Link Labs — a community-driven Python library (pip install semantic-link-labs) that wraps all of this into clean, documented functions
None of this requires downloading anything to your machine. You open a notebook, run one cell, and you have an interactive report-fixing tool at your fingertips. This is what a modern BI platform looks like — and Fabric delivers with being able to run this community‑driven tool directly integrated. But also let’s be clear again: the Power BI Fixer is obviously not a Microsoft product.
2. What the PBI Fixer Does — In Detail
The tool is split into two categories, each addressing a different layer of your Power BI solution.
Report Fixers (5 Fixers at the time of writing)
These fixers operate on the report definition — the PBIR JSON files that describe every visual, page, and filter in your report. They use the connect_report() context manager to read (and optionally write) the report definition through the Fabric REST API. Some of the following fixes could more easily be applied by adding on single json design theme to the report, but this would not fix any chart where the default has been touched, therefore the following fixes are directly modifying the property of each visual / page.
1. Fix Pie Charts
Problem: Pie charts are one of the worst chart types for comparing values. Human perception of angles and areas is far less accurate than perception of length (bars). Yet pie charts remain one of the most used visuals in Power BI.
What it does: Replaces every pieChart visual in the report with a clusteredBarChart (configurable in the function itself). The data bindings, filters, and layout are preserved — only the visual type changes.
Scan mode: Reports exactly how many pie charts exist and on which pages, without modifying anything.
🔍 [1/5] Scanning Fix Pie Charts… 🟡 definition/pages/abc123/visuals/def456/visual.json — pie chart found (would be replaced with clusteredBarChart)
2. Fix Bar Charts
Problem: Bar charts are great — but Power BI’s default formatting includes axis titles nobody reads, axis value labels that duplicate the data labels, and gridlines that add visual clutter.
What it does: Applies five best-practice formatting rules to every barChart and clusteredBarChart:
Setting
Target
Why
X axis title
Off
The axis title (“Count of Sales”) adds no value — the data labels speak for themselves
X axis values
Off
Redundant when data labels are shown directly on the bars
Y axis title
Off
Category names are self-explanatory
Data labels
On
The single most important formatting choice — show the actual numbers
Vertical gridlines
Off
Reduces visual noise
Scan mode: Reports which specific properties deviate from the target for each visual, so you know exactly what would change.
🟡 definition/pages/…/visual.json — needs fixing: X axis title, X axis values, Data labels
3. Fix Column Charts
Problem: Same issues as bar charts, but with axes flipped. Power BI’s defaults for column charts include unnecessary axis titles, missing data labels, and gridlines.
What it does: Applies the equivalent five formatting rules to every columnChart and clusteredColumnChart, with the axis semantics appropriately swapped.
4. Fix Page Size
Problem: The default Power BI page size is 720×1280 pixels — a legacy from when Full HD was not universal. Reports created with this size appear small and waste screen real estate on modern displays.
What it does: Upgrades pages from the default 720×1280 to 1080×1920 (Full HD). Pages with custom sizes are left untouched — only the exact default match triggers a fix.
Scan mode: Reports which pages use the default size and which have custom dimensions.
🟡 “Sales Overview” (definition/pages/abc123/page.json) — default page size (720×1280), would be upgraded to 1080×1920 🟢 “Executive Summary” (definition/pages/def456/page.json) — custom size (800×1200), no change needed
5. Hide Visual Filters
Problem: Power BI automatically creates visual-level filters for every field used in a visual. These filters appear in the Filters pane and confuse end users who do not understand the distinction between visual, page, and report filters. Most report developers would rather hide them entirely.
What it does: Sets isHiddenInViewMode = True on every visual-level filter. For visuals that have query fields but no filterConfig, one is constructed automatically — the tool walks the visual’s queryState, classifies each field as Categorical or Advanced (for measures), and creates the hidden filter entries from scratch.
Scan mode: Reports how many visuals have visible filters and how many filters would be hidden.
Semantic Model Fixers (6 Fixers at the time of writing)
These fixers operate on the semantic model (the dataset) behind the report. They use the XMLA endpoint and the Tabular Object Model (TOM) through connect_semantic_model(). Because XMLA write operations are irreversible (the model can no longer be downloaded as a .pbix with embedded data after modification), the UI requires an explicit confirmation checkbox before running these fixers.
1. Discourage Implicit Measures
Problem: By default, Power BI allows end users to drag columns into the Values bucket and get automatic SUM/COUNT/etc. aggregations. This creates “implicit measures” that cannot be formatted, documented, or governed. It is also a prerequisite for calculation groups — they simply do not work correctly with implicit measures enabled. Actually if a calculation group is added this setting is automatically set to TRUE
What it does: Sets DiscourageImplicitMeasures = True on the model. One property, one toggle, impact the whole model.
2. Add Calendar Table (CalcCalendar)
Problem: Almost every analytical model needs a calendar (date) table. Yet many models either lack one entirely or use a poorly structured one. Without a proper calendar table marked with DataCategory = Time, time intelligence functions do not work and auto date/time creates hidden tables for every date column.
What it does: Checks if any table in the model has DataCategory = “Time”. If none exists, creates a comprehensive CalcCalendar calculated table with:
20 columns — Date, Year, Quarter, Month (number and abbreviation), Day, Fiscal Year (October start), End of Month, Week of Year, Weekday, and 9 boolean/flag columns (Is Current Month, Is Previous Month, Is Current/Previous Calendar Year, Is Current/Previous Fiscal Year, Is Before This Month, Is Current or Past Month, Month Key, Relative Month)
3 hierarchies — Date Hierarchy (Year → Quarter → Month → Day), Fiscal Date Hierarchy (Fiscal Year → Quarter → Month → Day), Calendar Hierarchy (Year → Month MMM → Week of Year → Weekday)
Display folders — Organized into Favorites, Calendar Date, Fiscal Date, and Flags
Sort-by-column — Month abbreviation sorted by month number
Marked as date table with the Date column as key
The DAX expression uses CALENDARAUTO() and derives all columns dynamically. The fiscal year start month is configurable (default: October).
3. Add Last Refresh Table
Problem: Users and administrators need to know when a dataset was last refreshed. This is especially critical for business-critical reports where stale data leads to wrong decisions.
What it does: Creates a hidden “Last Refresh” table with:
M partition (Power Query) that captures DateTime.LocalNow() on every refresh
Data column storing the refresh timestamp
Measure “Last Refresh Measure” displaying the formatted timestamp
Smart placement — If a “Measure” table exists, the measure is placed there instead of in the Last Refresh table, keeping things organized
4. Add Measure Table
Problem: Without a dedicated measure table, measures are scattered across fact and dimension tables. This makes them hard to find, hard to maintain, and easy to duplicate.
What it does: Creates an empty calculated table named “Measure” using the minimal expression {0} (a single-row, single-column placeholder). The auto-generated column is hidden, so only measures placed in this table are visible to the end user.
5. Add Units Calculation Group (Thousand / Million)
Problem: Displaying large numbers like “1,234,567” clutters visuals and makes comparison harder. Users often want to see “1,235K” or “1.2M”. Creating individual measures for every combination is not scalable.
What it does: Creates a “Units” calculation group with two items:
Thousand — divides by 1,000
Million — divides by 1,000,000
Both items include smart skip logic: measures whose name contains % or ratio are passed through unchanged — dividing a percentage by 1,000 would produce nonsense. The DAX uses DIVIDE() for safe division and checks ISNUMBER(SELECTEDMEASURE()) before applying.
Note: Calculation groups affect all visuals that use the slicer. The fixer includes a performance warning in its output.
6. Add Time Intelligence Calculation Group (21 Items)
Problem: Time intelligence is the most common analytical requirement — comparing this year to last year, computing year-to-date, calculating variances. Building these for every measure individually is tedious and error-prone. A calculation group solves this once for all measures.
What it does: Creates a “Time Intelligence” calculation group with 21 calculation items:
Category
Items
Base periods
AC (Actual), Y-1 (Prior Year), Y-2 (Two Years Ago), Y-3 (Three Years Ago)
Year-to-date
YTD, YTD-1 (Prior Year YTD), YTD-2 (Two Years Ago YTD)
Absolute variances
abs. AC vs Y-1, abs. AC vs Y-2, abs. AC vs YTD-1, abs. AC vs YTD-2
Relative variances
AC vs Y-1 (%), AC vs Y-2 (%), AC vs YTD-1 (%), AC vs YTD-2 (%)
Achievement
achiev. AC vs Y-1, achiev. AC vs Y-2, achiev. AC vs YTD-1, achiev. AC vs YTD-2
The DAX is generated dynamically based on the actual calendar table and date column names in the model. It uses SAMEPERIODLASTYEAR(), DATEADD(), DATESYTD(), and TOTALYTD() — all parameterized so they work regardless of how your calendar table is named.
Prerequisite: Requires a calendar table with DataCategory = “Time”. The fixer checks for this and prints a helpful message suggesting to run the Calendar fixer first if none exists.
3. Scan vs. Fix — Assess Before You Act
Every fixer in the PBI Fixer supports two modes:
Scan Mode
Scan mode is read-only. It opens the report or semantic model with readonly=True, checks every condition, and reports what would change — without touching anything. This is your assessment tool.
Use it to:
Audit a report before handing it to a client
Baseline the current state before applying fixes
Review across multiple reports to prioritize which ones need the most work
The output is a detailed log with green dots (✅ already correct), yellow dots (⚠️ would be fixed), and clear descriptions of each finding.
Fix Mode
Fix mode opens the report or semantic model with readonly=False and applies all selected fixes. The report definition changes are saved back to the Fabric service through the REST API. Semantic model changes are saved through the XMLA endpoint via TOM.
Scan + Fix
The third mode runs both phases sequentially: first a complete scan to document the current state, then a full fix pass. This gives you a before-and-after view in a single run.
4. How It Compares to Other Solutions
Tabular Editor C# Scripts
Let me be clear: I am a huge fan of Tabular Editor. For example TE’s C# scripting engine is powerful, mature, and battle-tested. Being able to save reusable C# scripts as one-click actions in your toolbar makes repetitive model tasks fast and consistent. If you work with semantic models professionally, Tabular Editor should absolutely be in your toolkit.
That said, there are scenarios where the PBI Fixer complements what Tabular Editor offers:
Local install required — Tabular Editor must be downloaded and installed on a Windows machine (or run as a portable version). The PBI Fixer runs entirely in a Fabric Notebook — no installs, no local machine dependency
C# vs. Python — Tabular Editor’s scripting language is C#. If your team lives in notebooks and Python, switching to C# for model automation is a context switch. The PBI Fixer keeps everything in one language and one environment
Semantic model only — Tabular Editor (understandably) focuses on the semantic model layer. It does not touch the report definition — it cannot fix pie charts, page sizes, or visual filters. The PBI Fixer covers both layers in a single tool
Interactive notebook UI — Tabular Editor scripts are run from a script editor, command line, or via Macros in the TE UI. The PBI Fixer provides an interactive widget directly inside a notebook, which fits naturally into Fabric-native workflows
License cost — TE3 (with the full scripting engine and Macros) requires a paid license, mho worth it for professional use, but it is a consideration for teams already invested in Fabric
Use Tabular Editor and its Macros for deep semantic model work at your desk. Use the PBI Fixer when you need report-layer fixes, notebook-native automation, or a single entry point that spans both layers without leaving Fabric.
TMDL Scripts / Git-Based Workflows
TMDL (Tabular Model Definition Language) is Microsoft’s text-based serialization format for semantic models. You can version-control your models in Git and apply changes through text manipulation.
Great for version control — TMDL excels at tracking changes over time
Manual text editing — adding a 20-column calendar table means writing 200+ lines of TMDL by hand
No report layer — TMDL is semantic-model-only, same as Tabular Editor
No assessment — you cannot “scan” a TMDL folder to find what is missing; you need to read and interpret the files yourself
Deployment pipeline required — changes in TMDL need to be deployed back to the service through Git integration or a deployment tool
The PBI Fixer’s scan mode provides the assessment that TMDL workflows lack, and its fix mode applies changes directly without requiring a full deployment pipeline.
Manual Modifications in Power BI Desktop
The most common approach: open the report, click through property panes, change settings, save, publish.
Time-consuming — fixing five formatting properties on ten bar charts means 50+ individual clicks
Error-prone — easy to miss a visual or forget a setting
Not scalable — doing this for 20 reports across five workspaces is a full day of work
No audit trail — no record of what was changed or what the previous state was
Not repeatable — the next report you create has the same default problems
The PBI Fixer is repeatable, consistent, and logs everything all by design.
Power BI REST API / Fabric SDK (Custom Python)
You could build this yourself using the raw Fabric REST API and the semantic-link SDK.
Maximum flexibility — you can do anything the API supports
Significant development effort — understanding the PBIR JSON schema, handling authentication, managing error cases, building a UI
No community — your custom scripts are yours to maintain
The PBI Fixer is built on top of Semantic Link Labs, which handles the heavy lifting. It uses connect_report() for the PBIR layer and connect_semantic_model() for TOM — both are well-tested, community-maintained abstractions over the raw APIs.
5. Getting Started
Prerequisites
A Microsoft Fabric capacity (F2 or higher, or a trial capacity)
A Fabric Notebook in your workspace
Semantic Link Labs installed – see script below
Reports in PBIR format (required for report fixers — check your Preview Features in Power BI Desktop, enable PBIR, save, and re-publish if your report is still in PBIRLegacy). The Fixer will also tell you in case it is not.
XMLA read/write enabled at the tenant level (Admin Portal → Tenant settings → Integration settings → “Allow XMLA endpoints and Analyze in Excel with on-premises datasets”). By default this is enabled. Required for semantic model fixers.
Large semantic model storage format enabled (Workspace Settings → Power BI → Large dataset storage format → ON). This applies to all datasets in the workspace and sets DefaultPowerBIDataSourceVersion to V3, which is required for any XMLA write operation. Without it, semantic model fixers will fail.
Usage
Two cells — that is all it takes. Install and import takes approx 1 min, the Fixer itself will run within seconds.
# Cell 1: Install and import
%pip install git+https://github.com/KornAlexander/semantic-link-labs.git
from sempy_labs.report import pbi_fixer
# Cell 2: Run
pbi_fixer()
Splitting this into two cells is intentional. The install only needs to run once per session — after that you can skip Cell 1 entirely and just re-run Cell 2. It also keeps things clean: use “Clear Output” on Cell 1 to remove the verbose pip installation log, so only the interactive PBI Fixer UI remains visible in your notebook.
The interactive UI renders in the cell output. Enter your workspace and report name, select the fixers you want, choose your mode, and click Run.
Note: If your report was uploaded as a .pbix file, it may be in PBIRLegacy format — the visual fixers won’t find any charts until you convert it. Open the report in Power BI Desktop, save it, and re-publish to convert to PBIR. Also make sure Large semantic model storage format is enabled in the workspace settings (see prerequisites above) — without it, all semantic model fixers will fail with an XMLA write error.
> Note: The PBI Fixer currently lives in a fork. Once it is part of the official Semantic Link Labs package, this becomes a true one-liner — just %pip install semantic-link-labs followed by pbi_fixer(). I am working toward getting it merged into the main repository, but given the scope of the fixers and the UI layer, this may take some time and may not happen at all.
For automation or scripting without the UI, call any fixer function directly:
from sempy_labs.report import fix_piecharts, fix_barcharts
# Scan only — see what would change
fix_piecharts(report="Sales Dashboard", workspace="Production", scan_only=True)
# Fix — apply the changes
fix_barcharts(report="Sales Dashboard", workspace="Production")
6. The Road Ahead
The PBI Fixer is designed as a framework, not a fixed set of rules. Each fixer is an independent Python function with a consistent interface: report, workspace, scan only. Adding a new fixer is as simple as writing one function and wiring it into the UI.
Currently available fixers:
Report Fixers
fix_piecharts — replaces all pie charts with Clustered Bar Charts
fix_barcharts — removes axis titles/values, adds data labels, removes gridlines
fix_columncharts — removes axis titles/values, adds data labels, removes gridlines
fix_page_size — changes default 720×1280 pages to 1080×1920 (Full HD)
fix_hide_visual_filters — sets isHiddenInViewMode on all visual-level filters
Semantic Model Fixers
fix_discourage_implicit_measures — sets DiscourageImplicitMeasures to True (recommended & required for calc groups)
add_calculated_calendar — adds a calculated calendar table if no table has been marked as a date table
add_measure_table — adds an empty “Measure” calculated table to centralise measures
add_last_refresh_table — adds a “Last Refresh” table with M partition & measure showing refresh timestamp
add_calc_group_units — Thousand & Million items, skips % / ratio measures
What’s especially exciting is that this is hopefully just the beginning—because the whole fixer is built for easy extension, anyone can contribute new fixers or improvements. As more people get involved, the checklists and best practices incorporated by the PBI Fixer will continue to grow and evolve. This open and extensible approach means the tool can adapt to new challenges and community needs over time.
Here is what I find most exciting about the extensibility:
IBCS Implementation
The International Business Communication Standards (IBCS) define a comprehensive set of rules for business charts: uniform scaling, standardized notation, consistent color coding, and strict chart type selection. Some of the PBI Fixer’s concepts already align with IBCS principles:
Replacing pie charts — IBCS explicitly discourages pie charts in favor of bar charts
Removing axis clutter — IBCS emphasizes clean, uncluttered visuals with data labels instead of gridlines
Standardizing page sizes — consistent canvas dimensions are a prerequisite for standardized layouts
These are early steps, but the architecture makes it straightforward to explore further IBCS-aligned fixers over time — and to potentially integrate with IBCS-focused custom visuals like TRUECHART for scenarios where native chart types reach their limits.
Simplifying the Defaults
Power BI’s default settings are optimized for getting started quickly — not always for production-quality reports. The idea is that the Fixer will potentially be extended by further best practice checks and fixes:
VertipaqAnalyzer integration — Semantic Link Labs already includes vertipaq_analyzer(). A future fixer could scan the model for high-cardinality columns, unused columns, and oversized string columns, then recommend or apply optimizations automatically
Report BPA integration — Semantic Link Labs already ships run_report_bpa() with 9 built-in rules that analyze the report definition for issues like oversized visuals, missing alt text, and excessive filters. Integrating this directly into the PBI Fixer as a scan-capable fixer would give you a full report-level health check alongside the visual fixes — and for rules with deterministic resolutions, auto-fix them in the same pass
Model BPA integration — The library’s comprehensive Model BPA covers semantic model best practices. Future fixers could take BPA findings and apply fixes for every rule that has a deterministic resolution — turning “assessment” into “assessment + auto-fix” in one step
What You Can Build Today
Even without waiting for future releases, the framework is open. You can write your own fixer function today:
def fix_my_custom_rule(report, page_name=None, workspace=None, scan_only=False): with connect_report(report=report, workspace=workspace, readonly=scan_only) as rw: # Your logic here — iterate visuals, check properties, apply fixes pass
Wire it into the report_fixers list in the UI, and it appears as a checkbox alongside the built-in fixers. Same for semantic model fixers — write a function that uses connect_semantic_model() and add it to sm_fixers.
The barrier to entry is intentionally low. If you can write a Python function that reads and modifies a dictionary, you can write a fixer.
Final Thoughts
The PBI Fixer is not just a tool — it is a shift in how we think about report quality. Instead of manually checking and fixing every visual property, every model setting, every best practice after the fact, we can now codify our standards and apply them at scale with a single function call.
Fabric’s notebook environment, combined with the PBIR format and the XMLA endpoint, has created something that was simply not possible two years ago: a unified, programmatic, interactive interface to both the report layer and the semantic model layer of Power BI — running entirely in the browser, with zero local tooling.
If you are building Power BI solutions professionally, this is the direction things are moving. One line. Everything assessed. Everything fixed.