Review Power BI diff
Turn a large, unreadable PBIP diff (hash-named visual.json files, TMDL model
files) into a single review artifact: an executive summary of what was built,
page wireframes showing what changed where, spec-checked measures with full
DAX, and a ranked red-flags list. The reviewer is a Power BI expert, so show
raw DAX and exact bindings.
Input: an optional git ref range as the argument (A...B, A..B, or a
single base ref meaning "vs working tree"). Default: main...HEAD. The user
may also name pages to treat as scratch, beyond the automatic rule below.
Scope: review and report. Findings go in the artifact, not into edits to the PBIP files, the DAX, or an issue tracker.
Step 1: extract the change model (deterministic)
Run the bundled extractor, outputting to the scratchpad:
python3 <skill-dir>/scripts/extract_changes.py "<range>" \
--out <scratchpad>/pbi-review --repo <repo-root>
<skill-dir> is this skill's directory. It produces:
<out>/change_model.json: the structured change model. Everything below works from this file, not from the raw git diff.<out>/objects/<repo-path>.before|.after: raw before/after of every changed file, for drill-down when something in the model looks suspicious.
Change-model shape: reports.<name>.pages.<id> has display_name, status
(added / modified / visuals-changed / deleted), width/height,
visibility (present when not default-visible), scratch (see below), page
filters, and visuals.<id>. This covers every visual on each touched page
(including unchanged ones, so wireframes are complete), with visual_type,
title, abs_x/abs_y/width/height/z, status, fields (role to
bound fields), filters, and for modified visuals changed_sections plus
*_before values. models.<name> has per-table
measures_added/modified/deleted (with full DAX, dax_before/dax_after
for modified), same for columns and calculation_items, plus
relationships and functions diffs. commits and authors identify whose
work this is. Calculation-group internals and partition/refresh-policy
changes are not member-diffed. The change model flags the table with a note;
drill into objects/ for those.
If change_model.json is large, query it with python one-liners rather than
reading it whole; read pages/tables one at a time.
Scratch pages
Pages marked scratch: true (Power BI default names, Page N), plus any
pages the user named at invocation, are developer scratch work. Review the
model in full, but curated pages only: scratch pages get no wireframe, no
visual tables, and no red-flag checks except #11. References from their
visuals (checks 3-4) aren't findings; they go in the exclusion line. They
surface in the artifact exactly twice: the exclusion line (see
references/artifact.md) and, when visible, check #11.
Step 2: analyze
Spec check (every added/modified measure)
For each measure in measures_added / measures_modified, look for a
matching definition in the repo's metric-definition docs. Try
docs/metric-definitions/ first, else search the repo for definition-style
docs, else ask the user where definitions live (match by name, then by
concept). Verdicts, each rendered as a badge in the artifact:
- ✅ Matches definition. DAX implements the documented logic (check the numerator/denominator, grain, filter context, exclusions, and business-hours flags, not just the name).
- ⚠️ Deviates. State exactly how the DAX differs from the definition.
- ❓ No definition found. This is itself a useful finding; list these so docs can catch up. If the repo has no metric-definition docs at all, say so once and mark every measure ❓ without repeating it per measure.
Done when every added/modified measure carries a verdict. Spec-check inline: ~15 measures is a ceiling, not a trigger. Past it, use as few parallel subagents as cover the measures in batches (each gets its batch + the definitions docs) and merge. Never spawn an agent to review the review.
If the repo documents requirements (e.g. docs/requirements/), also map the
overall work against them: which requirements does this branch address, and
what claimed scope is missing.
Red-flag checks
Detection pass: surface everything. Run every check below and record every candidate it raises; don't drop one for being minor, uncertain, or probably fine. Each check either yields candidates or comes back confirmed clean.
Filter pass: evidence, then rank. Before building the artifact, revisit
the candidates. Pull each one's evidence from the change model, reading the
objects/…before|.after files whenever the model doesn't carry enough;
discard only what the evidence disproves, and rank: 🔴 wrong results /
broken, 🟡 should fix before merge, 🔵 minor/hygiene. Every finding in the
artifact cites what you checked; one you can neither confirm nor disprove
ships as 🔵 stating what you checked and what is still unknown.
- Bare
/division in new/changed DAX where the denominator can be zero/blank (DIVIDEis the safe idiom). - Missing
formatStringon added measures that return numbers. - Deleted or renamed measures still referenced. Grep the repo at head
for each deleted measure name (visuals reference them as
<Table>.<Name>inqueryRef, other measures as[<Name>]). A rename shows up as one added + one deleted measure; check the deleted name. Flag equally any visual binding to a measure that doesn't exist in the model at head (e.g. a missing name prefix). - Modified measure ripple. For measures whose DAX changed, grep which other measures/visuals reference them and note affected downstream logic.
USERELATIONSHIPtargets. Confirm the named relationship exists (checkrelationshipsin the change model anddefinition/relationships.tmdlat head) and is inactive as expected.- Visuals bound to unrelated tables. Fields from two fact tables with no relationship path on the same visual.
- Placeholder names. Visuals left untitled where siblings are titled.
- Hidden filters that change data. Visual/page filters with
hidden_in_view: true(field emitted only when true). - Overlapping data visuals on the same page: pairs covering >15% of the
smaller visual's area (use
abs_x/abs_y/width/height; ignore shapes/textboxes layered as backgrounds). - Hardcoded literals in DAX (magic dates, hardcoded brand/region
values) and, when the repo documents naming/DAX standards (e.g. under
docs/standards/), convention violations against them. - Visible scratch pages. A scratch page without
visibility: HiddenInViewModeships to end users: 🟡 hide (or delete) before publishing. Hidden scratch pages are tolerated.
Step 3: build the artifact
Follow references/artifact.md for the artifact
structure and wireframe rules. Length follows substance: within that
structure, prefer wireframes and tables over prose, skip filler padding, and
never restate a table in prose beneath it. Load the artifact-design skill
if available, write a single self-contained HTML file to the scratchpad, and
publish it with the Artifact tool (favicon 📊, stable title
PBI Review: <branch or range>). If the Artifact tool is unavailable, save
the HTML and give the user its path. Close out with the outcome first
(artifact URL or path, and 🔴/🟡/🔵 counts), then any detail.