mirror of
https://github.com/MinaSaad1/pbi-cli
synced 2026-04-21 13:37:19 +00:00
feat: v3.10.0 -- audit fixes, 12 skills, README rewrite
- Fix 4 code bugs: TopN filter cross-table, theme_set corruption, visual_bind type annotation, tmdl_diff hierarchy pluralization - Add missing VisualTypeError and ReportNotFoundError to errors.py - Register 5 command groups in CLI (report, visual, filters, format, bookmarks) - Split monolithic report skill into 5 focused skills (12 total): power-bi-report, power-bi-visuals, power-bi-pages, power-bi-themes, power-bi-filters - Update CLAUDE.md snippet for 12 skills organised by layer - Add diff-tmdl section to deployment skill - Write CHANGELOG entries for v3.0.0 through v3.10.0 - Rewrite README.md and README.pypi.md for both model and report layers - Add skill triggering test suite (19/19 passing) - 488 tests passing, ruff clean
This commit is contained in:
parent
88cb644f64
commit
f0504bcf51
20 changed files with 1732 additions and 225 deletions
106
CHANGELOG.md
106
CHANGELOG.md
|
|
@ -5,6 +5,112 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [3.10.0] - 2026-04-02
|
||||
|
||||
### Added
|
||||
- Split `power-bi-report` skill into 5 focused skills: `power-bi-report` (overview), `power-bi-visuals`, `power-bi-pages`, `power-bi-themes`, `power-bi-filters` (12 skills total)
|
||||
- CLAUDE.md snippet now organises skills by layer (Semantic Model vs Report Layer)
|
||||
- Skill triggering test suite (19 prompts, 12 skills)
|
||||
|
||||
### Fixed
|
||||
- `filter_add_topn` inner subquery now correctly references category table when it differs from order-by table
|
||||
- `theme_set` resourcePackages structure now matches Desktop format (flat `items` array)
|
||||
- `visual_bind` type annotation corrected to `list[dict[str, Any]]`
|
||||
- `tmdl_diff` hierarchy changes reported as `hierarchies_*` instead of falling to `other_*`
|
||||
- Missing `VisualTypeError` and `ReportNotFoundError` classes added to `errors.py`
|
||||
- `report`, `visual`, `filters`, `format`, `bookmarks` command groups registered in CLI
|
||||
|
||||
### Changed
|
||||
- README rewritten to cover both semantic model and report layers, 12 skills, 27 command groups, 32 visual types
|
||||
|
||||
## [3.9.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
- `pbi database diff-tmdl` command: compare two TMDL export folders offline, summarise changes (tables, measures, columns, relationships, model properties); lineageTag-only changes are stripped to avoid false positives
|
||||
|
||||
### Fixed
|
||||
- `filter_add_topn` inner subquery now correctly references the category table when it differs from the order-by table (cross-table TopN filters)
|
||||
- `theme_set` resourcePackages structure now matches Desktop format (flat `items`, not nested `resourcePackage`)
|
||||
- `visual_bind` type annotation corrected from `list[dict[str, str]]` to `list[dict[str, Any]]`
|
||||
- `tmdl_diff` hierarchy changes now reported as `hierarchies_*` instead of falling through to `other_*`
|
||||
- Missing `VisualTypeError` and `ReportNotFoundError` error classes added to `errors.py`
|
||||
- `report`, `visual`, `filters`, `format`, `bookmarks` command groups registered in CLI (were implemented but inaccessible)
|
||||
|
||||
## [3.8.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
- `azureMap` visual type (Azure Maps) with Category and Size roles
|
||||
- `pageBinding` field surfaced in `page_get()` for drillthrough pages
|
||||
|
||||
### Fixed
|
||||
- `card` and `multiRowCard` queryState role corrected from `Fields` to `Values` (matches Desktop)
|
||||
- `kpi` template: added `TrendLine` queryState key (date/axis column for sparkline)
|
||||
- `gauge` template: added `MaxValue` queryState key (target/max measure)
|
||||
- `MaxValue` added to `MEASURE_ROLES`
|
||||
- kpi role aliases: `--trend`, `--trend_line`
|
||||
- gauge role aliases: `--max`, `--max_value`, `--target`
|
||||
|
||||
## [3.7.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
- `page_type`, `filter_config`, and `visual_interactions` fields in page read operations (`page_get`, `page_list`)
|
||||
|
||||
## [3.6.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
- `image` visual type (static images, no data binding)
|
||||
- `shape` visual type (decorative shapes)
|
||||
- `textbox` visual type (rich text)
|
||||
- `pageNavigator` visual type (page navigation buttons)
|
||||
- `advancedSlicerVisual` visual type (tile/image slicer)
|
||||
|
||||
## [3.5.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
- `clusteredColumnChart` visual type with aliases `clustered_column`
|
||||
- `clusteredBarChart` visual type with aliases `clustered_bar`
|
||||
- `textSlicer` visual type with alias `text_slicer`
|
||||
- `listSlicer` visual type with alias `list_slicer`
|
||||
|
||||
## [3.4.0] - 2026-03-31
|
||||
|
||||
### Added
|
||||
- `cardVisual` (modern card) visual type with `Data` role and aliases `card_visual`, `modern_card`
|
||||
- `actionButton` visual type with alias `action_button`, `button`
|
||||
- `pbi report set-background` command to set page background colour
|
||||
- `pbi report set-visibility` command to hide/show pages
|
||||
- `pbi visual set-container` command for border, background, and title on visual containers
|
||||
|
||||
### Fixed
|
||||
- Visual container schema URL updated from 1.5.0 to 2.7.0
|
||||
- `visualGroup` containers tagged as type `group` in `visual_list`
|
||||
- Colour validation, KeyError guards, visibility surfacing, no-op detection
|
||||
|
||||
## [3.0.0] - 2026-03-31
|
||||
|
||||
### Added
|
||||
- **PBIR report layer**: `pbi report` command group (create, info, validate, list-pages, add-page, delete-page, get-page, set-theme, get-theme, diff-theme, preview, reload, convert)
|
||||
- **Visual CRUD**: `pbi visual` command group (add, get, list, update, delete, bind, where, bulk-bind, bulk-update, bulk-delete, calc-add, calc-list, calc-delete, set-container)
|
||||
- **Filters**: `pbi filters` command group (list, add-categorical, add-topn, add-relative-date, remove, clear)
|
||||
- **Formatting**: `pbi format` command group (get, clear, background-gradient, background-conditional, background-measure)
|
||||
- **Bookmarks**: `pbi bookmarks` command group (list, get, add, delete, set-visibility)
|
||||
- 20 visual type templates (barChart, lineChart, card, tableEx, pivotTable, slicer, kpi, gauge, donutChart, columnChart, areaChart, ribbonChart, waterfallChart, scatterChart, funnelChart, multiRowCard, treemap, cardNew, stackedBarChart, lineStackedColumnComboChart)
|
||||
- HTML preview server (`pbi report preview`) with live reload
|
||||
- Power BI Desktop reload trigger (`pbi report reload`)
|
||||
- PBIR path auto-detection (walk-up from CWD, `.pbip` sibling detection)
|
||||
- `power-bi-report` Claude Code skill (8th skill)
|
||||
- Visual data binding with `Table[Column]` notation and role aliases
|
||||
- Visual calculations (calc-add, calc-list, calc-delete)
|
||||
- Bulk operations for mass visual updates across pages
|
||||
|
||||
### Changed
|
||||
- Architecture: pbi-cli now covers both semantic model layer (via .NET TOM) and report layer (via PBIR JSON files)
|
||||
|
||||
## [2.2.0] - 2026-03-27
|
||||
|
||||
### Added
|
||||
- Promotional SVG assets and redesigned README
|
||||
|
||||
## [2.0.0] - 2026-03-27
|
||||
|
||||
### Breaking
|
||||
|
|
|
|||
69
README.md
69
README.md
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<p align="center">
|
||||
<b>Give Claude Code the Power BI skills it needs.</b><br/>
|
||||
Install once, then just ask Claude to work with your semantic models.
|
||||
Install once, then just ask Claude to work with your semantic models <i>and</i> reports.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -117,22 +117,34 @@ Add the printed path to your system PATH, then restart your terminal. We recomme
|
|||
|
||||
## Skills
|
||||
|
||||
After running `pbi connect`, Claude Code discovers **7 Power BI skills** automatically. Each skill teaches Claude a different area. You don't need to memorize commands.
|
||||
After running `pbi connect`, Claude Code discovers **12 Power BI skills** automatically. Each skill teaches Claude a different area. You don't need to memorize commands.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/skills-hub.svg" alt="7 Skills" width="850"/>
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/skills-hub.svg" alt="12 Skills" width="850"/>
|
||||
</p>
|
||||
|
||||
### Semantic Model Skills (require `pbi connect`)
|
||||
|
||||
| Skill | What you say | What Claude does |
|
||||
|-------|-------------|-----------------|
|
||||
| **DAX** | *"What are the top 10 products by revenue?"* | Writes and executes DAX queries, validates syntax |
|
||||
| **Modeling** | *"Create a star schema with Sales and Calendar"* | Creates tables, relationships, measures, hierarchies |
|
||||
| **Deployment** | *"Save a snapshot before I make changes"* | Exports/imports TMDL, manages transactions |
|
||||
| **Deployment** | *"Save a snapshot before I make changes"* | Exports/imports TMDL, manages transactions, diffs snapshots |
|
||||
| **Security** | *"Set up RLS for regional managers"* | Creates roles, filters, perspectives |
|
||||
| **Docs** | *"Document everything in this model"* | Generates data dictionaries, measure inventories |
|
||||
| **Partitions** | *"Show me the M query for the Sales table"* | Manages partitions, expressions, calendar config |
|
||||
| **Diagnostics** | *"Why is this query so slow?"* | Traces queries, checks model health, benchmarks |
|
||||
|
||||
### Report Layer Skills (no connection needed)
|
||||
|
||||
| Skill | What you say | What Claude does |
|
||||
|-------|-------------|-----------------|
|
||||
| **Report** | *"Create a new report project for Sales"* | Scaffolds PBIR reports, validates structure, previews layout |
|
||||
| **Visuals** | *"Add a bar chart showing revenue by region"* | Adds, binds, updates, bulk-manages 32 visual types |
|
||||
| **Pages** | *"Add an Executive Overview page"* | Manages pages, bookmarks, visibility, drillthrough |
|
||||
| **Themes** | *"Apply our corporate brand colours"* | Applies themes, conditional formatting, colour scales |
|
||||
| **Filters** | *"Show only the top 10 products"* | Adds page/visual filters (TopN, date, categorical) |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
|
@ -141,7 +153,10 @@ After running `pbi connect`, Claude Code discovers **7 Power BI skills** automat
|
|||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/architecture-flow.svg" alt="Architecture" width="850"/>
|
||||
</p>
|
||||
|
||||
Direct in-process .NET interop from Python to Power BI Desktop. No MCP server, no external binaries, sub-second execution.
|
||||
**Two layers, one CLI:**
|
||||
|
||||
- **Semantic Model layer** -- Direct in-process .NET interop from Python to Power BI Desktop via TOM/ADOMD. No MCP server, no external binaries, sub-second execution.
|
||||
- **Report layer** -- Reads and writes PBIR (Enhanced Report Format) JSON files directly. No connection needed. Works with `.pbip` projects.
|
||||
|
||||
<details>
|
||||
<summary><b>Configuration details</b></summary>
|
||||
|
|
@ -163,21 +178,57 @@ Bundled DLLs ship inside the Python package (`pbi_cli/dlls/`).
|
|||
|
||||
## All Commands
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/feature-grid.svg" alt="22 Command Groups" width="850"/>
|
||||
</p>
|
||||
27 command groups covering both the semantic model and the report layer.
|
||||
|
||||
| Category | Commands |
|
||||
|----------|----------|
|
||||
| **Queries** | `dax execute`, `dax validate`, `dax clear-cache` |
|
||||
| **Model** | `table`, `column`, `measure`, `relationship`, `hierarchy`, `calc-group` |
|
||||
| **Deploy** | `database export-tmdl`, `database import-tmdl`, `database export-tmsl`, `database diff-tmdl`, `transaction` |
|
||||
| **Security** | `security-role`, `perspective` |
|
||||
| **Connect** | `connect`, `disconnect`, `connections list`, `connections last` |
|
||||
| **Data** | `partition`, `expression`, `calendar`, `advanced culture` |
|
||||
| **Diagnostics** | `trace start/stop/fetch/export`, `model stats` |
|
||||
| **Report** | `report create`, `report info`, `report validate`, `report preview`, `report reload` |
|
||||
| **Pages** | `report add-page`, `report delete-page`, `report get-page`, `report set-background`, `report set-visibility` |
|
||||
| **Visuals** | `visual add/get/list/update/delete`, `visual bind`, `visual bulk-bind/bulk-update/bulk-delete`, `visual where` |
|
||||
| **Filters** | `filters list`, `filters add-categorical/add-topn/add-relative-date`, `filters remove/clear` |
|
||||
| **Formatting** | `format get/clear`, `format background-gradient/background-conditional/background-measure` |
|
||||
| **Bookmarks** | `bookmarks list/get/add/delete/set-visibility` |
|
||||
| **Tools** | `setup`, `repl`, `skills install/list/uninstall` |
|
||||
|
||||
Use `--json` for machine-readable output (for scripts and AI agents):
|
||||
|
||||
```bash
|
||||
pbi --json measure list
|
||||
pbi --json dax execute "EVALUATE Sales"
|
||||
pbi --json visual list --page overview
|
||||
```
|
||||
|
||||
Run `pbi <command> --help` for full options.
|
||||
|
||||
---
|
||||
|
||||
## Supported Visual Types (32)
|
||||
|
||||
pbi-cli supports creating and binding data to 32 Power BI visual types:
|
||||
|
||||
**Charts:** bar, line, column, area, ribbon, waterfall, stacked bar, clustered bar, clustered column, scatter, funnel, combo, donut/pie, treemap
|
||||
|
||||
**Cards/KPIs:** card (legacy), cardVisual (modern), cardNew, multi-row card, KPI, gauge
|
||||
|
||||
**Tables:** table, matrix (pivot table)
|
||||
|
||||
**Slicers:** slicer, text slicer, list slicer, advanced slicer (tile/image)
|
||||
|
||||
**Maps:** Azure Map
|
||||
|
||||
**Decorative:** action button, image, shape, textbox, page navigator
|
||||
|
||||
Use friendly aliases: `pbi visual add --page p1 --type bar` instead of `--type barChart`.
|
||||
|
||||
---
|
||||
|
||||
## REPL Mode
|
||||
|
||||
For interactive work, the REPL keeps a persistent connection:
|
||||
|
|
@ -208,7 +259,7 @@ pip install -e ".[dev]"
|
|||
```bash
|
||||
ruff check src/ tests/ # Lint
|
||||
mypy src/ # Type check
|
||||
pytest -m "not e2e" # Run tests
|
||||
pytest -m "not e2e" # Run tests (488 tests)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
252
README.pypi.md
252
README.pypi.md
|
|
@ -1,7 +1,7 @@
|
|||
<img src="https://raw.githubusercontent.com/MinaSaad1/pbi-cli/master/assets/header.svg" alt="pbi-cli" width="800"/>
|
||||
|
||||
**Give Claude Code the Power BI skills it needs.**
|
||||
Install once, then just ask Claude to work with your semantic models.
|
||||
Install once, then just ask Claude to work with your semantic models *and* reports.
|
||||
|
||||
<a href="https://pypi.org/project/pbi-cli-tool/"><img src="https://img.shields.io/pypi/pyversions/pbi-cli-tool?style=flat-square&color=3776ab&label=Python" alt="Python"></a>
|
||||
<a href="https://github.com/MinaSaad1/pbi-cli/actions"><img src="https://img.shields.io/github/actions/workflow/status/MinaSaad1/pbi-cli/ci.yml?branch=master&style=flat-square&label=CI" alt="CI"></a>
|
||||
|
|
@ -13,14 +13,18 @@ Install once, then just ask Claude to work with your semantic models.
|
|||
|
||||
## What is this?
|
||||
|
||||
pbi-cli gives **Claude Code** (and other AI agents) the ability to manage Power BI semantic models. It ships with 7 skills that Claude discovers automatically. You ask in plain English, Claude uses the right `pbi` commands.
|
||||
pbi-cli gives **Claude Code** (and other AI agents) the ability to manage Power BI semantic models **and reports**. It ships with 12 skills that Claude discovers automatically. You ask in plain English, Claude uses the right `pbi` commands.
|
||||
|
||||
```
|
||||
You Claude Code pbi-cli Power BI
|
||||
"Add a YTD measure ---> Uses Power BI ---> CLI commands ---> Desktop
|
||||
to the Sales table" skills
|
||||
to the Sales table" skills (12)
|
||||
```
|
||||
|
||||
**Two layers, one CLI:**
|
||||
- **Semantic Model** -- Direct .NET interop to Power BI Desktop (measures, tables, DAX, security)
|
||||
- **Report Layer** -- Reads/writes PBIR JSON files directly (visuals, pages, themes, filters)
|
||||
|
||||
---
|
||||
|
||||
## Get Started
|
||||
|
|
@ -40,8 +44,6 @@ pbi connect # 2. Auto-detects Power BI Desktop and installs ski
|
|||
|
||||
That's it. Open Power BI Desktop with a `.pbix` file, run `pbi connect`, and everything is set up automatically. Open Claude Code and start asking.
|
||||
|
||||
You can also specify the port manually: `pbi connect -d localhost:54321`
|
||||
|
||||
> **Requires:** Windows with Python 3.10+ and Power BI Desktop running.
|
||||
|
||||
<details>
|
||||
|
|
@ -60,12 +62,7 @@ Find the directory:
|
|||
python -c "import site; print(site.getusersitepackages().replace('site-packages','Scripts'))"
|
||||
```
|
||||
|
||||
Add the printed path to your system PATH:
|
||||
```cmd
|
||||
setx PATH "%PATH%;C:\Users\YourName\AppData\Roaming\Python\PythonXXX\Scripts"
|
||||
```
|
||||
|
||||
Then **restart your terminal**. We recommend `pipx` instead to avoid this entirely.
|
||||
Add the printed path to your system PATH, then restart your terminal. We recommend `pipx` to avoid this entirely.
|
||||
|
||||
</details>
|
||||
|
||||
|
|
@ -73,182 +70,76 @@ Then **restart your terminal**. We recommend `pipx` instead to avoid this entire
|
|||
|
||||
## Skills
|
||||
|
||||
After running `pbi connect`, Claude Code discovers **7 Power BI skills**. Each skill teaches Claude a different area of Power BI development. You don't need to memorize commands. Just describe what you want.
|
||||
After running `pbi connect`, Claude Code discovers **12 Power BI skills**. Each skill teaches Claude a different area. You don't need to memorize commands.
|
||||
|
||||
```
|
||||
You: "Set up RLS for regional managers"
|
||||
|
|
||||
v
|
||||
Claude Code --> Picks the right skill
|
||||
|
|
||||
+-- Modeling
|
||||
+-- DAX
|
||||
+-- Deployment
|
||||
+-- Security
|
||||
+-- Documentation
|
||||
+-- Diagnostics
|
||||
+-- Partitions
|
||||
```
|
||||
### Semantic Model (require `pbi connect`)
|
||||
|
||||
### Modeling
|
||||
| Skill | What you say | What Claude does |
|
||||
|-------|-------------|-----------------|
|
||||
| **DAX** | *"Top 10 products by revenue?"* | Writes and executes DAX queries |
|
||||
| **Modeling** | *"Create a star schema"* | Creates tables, relationships, measures |
|
||||
| **Deployment** | *"Save a snapshot"* | Exports/imports TMDL, diffs snapshots |
|
||||
| **Security** | *"Set up RLS"* | Creates roles, filters, perspectives |
|
||||
| **Docs** | *"Document this model"* | Generates data dictionaries |
|
||||
| **Partitions** | *"Show the M query"* | Manages partitions, expressions |
|
||||
| **Diagnostics** | *"Why is this slow?"* | Traces queries, benchmarks |
|
||||
|
||||
> *"Create a star schema with Sales, Products, and Calendar tables"*
|
||||
### Report Layer (no connection needed)
|
||||
|
||||
Claude creates the tables, sets up relationships, marks the date table, and adds formatted measures. Covers tables, columns, measures, relationships, hierarchies, and calculation groups.
|
||||
|
||||
<details>
|
||||
<summary>Example: what Claude runs behind the scenes</summary>
|
||||
|
||||
```bash
|
||||
pbi table create Sales --mode Import
|
||||
pbi table create Products --mode Import
|
||||
pbi table create Calendar --mode Import
|
||||
pbi relationship create --from-table Sales --from-column ProductKey --to-table Products --to-column ProductKey
|
||||
pbi relationship create --from-table Sales --from-column DateKey --to-table Calendar --to-column DateKey
|
||||
pbi table mark-date Calendar --date-column Date
|
||||
pbi measure create "Total Revenue" -e "SUM(Sales[Revenue])" -t Sales --format-string "$#,##0"
|
||||
```
|
||||
</details>
|
||||
|
||||
### DAX
|
||||
|
||||
> *"What are the top 10 products by revenue this year?"*
|
||||
|
||||
Claude writes and executes DAX queries, validates syntax, and creates measures with time intelligence patterns like YTD, previous year, and rolling averages.
|
||||
|
||||
<details>
|
||||
<summary>Example: what Claude runs behind the scenes</summary>
|
||||
|
||||
```bash
|
||||
pbi dax execute "
|
||||
EVALUATE
|
||||
TOPN(
|
||||
10,
|
||||
ADDCOLUMNS(VALUES(Products[Name]), \"Revenue\", CALCULATE(SUM(Sales[Amount]))),
|
||||
[Revenue], DESC
|
||||
)
|
||||
"
|
||||
```
|
||||
</details>
|
||||
|
||||
### Deployment
|
||||
|
||||
> *"Export the model to Git for version control"*
|
||||
|
||||
Claude exports your model as TMDL files for version control and imports them back. Handles transactions for safe multi-step changes.
|
||||
|
||||
<details>
|
||||
<summary>Example: what Claude runs behind the scenes</summary>
|
||||
|
||||
```bash
|
||||
pbi database export-tmdl ./model/
|
||||
# ... you commit to git ...
|
||||
pbi database import-tmdl ./model/
|
||||
```
|
||||
</details>
|
||||
|
||||
### Security
|
||||
|
||||
> *"Set up row-level security so regional managers only see their region"*
|
||||
|
||||
Claude creates RLS roles with descriptions, sets up perspectives for different user groups, and exports the model for version control.
|
||||
|
||||
<details>
|
||||
<summary>Example: what Claude runs behind the scenes</summary>
|
||||
|
||||
```bash
|
||||
pbi security-role create "Regional Manager" --description "Users see only their region's data"
|
||||
pbi perspective create "Executive Dashboard"
|
||||
pbi perspective create "Regional Detail"
|
||||
pbi database export-tmdl ./model-backup/
|
||||
```
|
||||
</details>
|
||||
|
||||
### Documentation
|
||||
|
||||
> *"Document everything in this model"*
|
||||
|
||||
Claude catalogs every table, measure, column, and relationship. Generates data dictionaries, measure inventories, and can export the full model as TMDL for human-readable reference.
|
||||
|
||||
<details>
|
||||
<summary>Example: what Claude runs behind the scenes</summary>
|
||||
|
||||
```bash
|
||||
pbi --json model get
|
||||
pbi --json model stats
|
||||
pbi --json table list
|
||||
pbi --json measure list
|
||||
pbi --json relationship list
|
||||
pbi database export-tmdl ./model-docs/
|
||||
```
|
||||
</details>
|
||||
|
||||
### Diagnostics
|
||||
|
||||
> *"Why is this DAX query so slow?"*
|
||||
|
||||
Claude traces query execution, clears caches for clean benchmarks, checks model health, and verifies the environment.
|
||||
|
||||
<details>
|
||||
<summary>Example: what Claude runs behind the scenes</summary>
|
||||
|
||||
```bash
|
||||
pbi dax clear-cache
|
||||
pbi trace start
|
||||
pbi dax execute "EVALUATE SUMMARIZECOLUMNS(...)" --timeout 300
|
||||
pbi trace stop
|
||||
pbi trace export ./trace.json
|
||||
```
|
||||
</details>
|
||||
|
||||
### Partitions & Expressions
|
||||
|
||||
> *"Set up partitions for incremental refresh on the Sales table"*
|
||||
|
||||
Claude manages table partitions, shared M/Power Query expressions, and calendar table configuration.
|
||||
|
||||
<details>
|
||||
<summary>Example: what Claude runs behind the scenes</summary>
|
||||
|
||||
```bash
|
||||
pbi partition list --table Sales
|
||||
pbi partition create "Sales_2024" --table Sales --expression "..." --mode Import
|
||||
pbi expression create "ServerURL" --expression '"https://api.example.com"'
|
||||
pbi calendar mark Calendar --date-column Date
|
||||
```
|
||||
</details>
|
||||
| Skill | What you say | What Claude does |
|
||||
|-------|-------------|-----------------|
|
||||
| **Report** | *"Create a new report"* | Scaffolds PBIR reports, validates, previews |
|
||||
| **Visuals** | *"Add a bar chart"* | Adds, binds, bulk-manages 32 visual types |
|
||||
| **Pages** | *"Add a new page"* | Manages pages, bookmarks, drillthrough |
|
||||
| **Themes** | *"Apply brand colours"* | Themes, conditional formatting |
|
||||
| **Filters** | *"Show top 10 only"* | TopN, date, categorical filters |
|
||||
|
||||
---
|
||||
|
||||
## All Commands
|
||||
|
||||
22 command groups covering the full Power BI Tabular Object Model. You rarely need these directly when using Claude Code, but they're available for scripting, CI/CD, or manual use.
|
||||
27 command groups covering both the semantic model and the report layer.
|
||||
|
||||
| Category | Commands |
|
||||
|----------|----------|
|
||||
| **Queries** | `dax execute`, `dax validate`, `dax clear-cache` |
|
||||
| **Model** | `table`, `column`, `measure`, `relationship`, `hierarchy`, `calc-group` |
|
||||
| **Deploy** | `database export-tmdl`, `database import-tmdl`, `database export-tmsl`, `transaction` |
|
||||
| **Deploy** | `database export-tmdl/import-tmdl/export-tmsl/diff-tmdl`, `transaction` |
|
||||
| **Security** | `security-role`, `perspective` |
|
||||
| **Connect** | `connect`, `disconnect`, `connections list`, `connections last` |
|
||||
| **Connect** | `connect`, `disconnect`, `connections list/last` |
|
||||
| **Data** | `partition`, `expression`, `calendar`, `advanced culture` |
|
||||
| **Diagnostics** | `trace start`, `trace stop`, `trace fetch`, `trace export`, `model stats` |
|
||||
| **Tools** | `setup`, `repl`, `skills install`, `skills list` |
|
||||
| **Diagnostics** | `trace start/stop/fetch/export`, `model stats` |
|
||||
| **Report** | `report create/info/validate/preview/reload`, `report add-page/delete-page/get-page` |
|
||||
| **Visuals** | `visual add/get/list/update/delete/bind`, `visual bulk-bind/bulk-update/bulk-delete` |
|
||||
| **Filters** | `filters list/add-categorical/add-topn/add-relative-date/remove/clear` |
|
||||
| **Formatting** | `format get/clear/background-gradient/background-conditional/background-measure` |
|
||||
| **Bookmarks** | `bookmarks list/get/add/delete/set-visibility` |
|
||||
| **Tools** | `setup`, `repl`, `skills install/list/uninstall` |
|
||||
|
||||
Use `--json` for machine-readable output (for scripts and AI agents):
|
||||
Use `--json` for machine-readable output:
|
||||
|
||||
```bash
|
||||
pbi --json measure list
|
||||
pbi --json dax execute "EVALUATE Sales"
|
||||
pbi --json visual list --page overview
|
||||
```
|
||||
|
||||
Run `pbi <command> --help` for full options.
|
||||
---
|
||||
|
||||
## 32 Supported Visual Types
|
||||
|
||||
**Charts:** bar, line, column, area, ribbon, waterfall, stacked bar, clustered bar, clustered column, scatter, funnel, combo, donut/pie, treemap
|
||||
|
||||
**Cards/KPIs:** card, cardVisual (modern), cardNew, multi-row card, KPI, gauge
|
||||
|
||||
**Tables:** table, matrix • **Slicers:** slicer, text, list, advanced • **Maps:** Azure Map
|
||||
|
||||
**Decorative:** action button, image, shape, textbox, page navigator
|
||||
|
||||
---
|
||||
|
||||
## REPL Mode
|
||||
|
||||
For interactive work, the REPL keeps a persistent connection alive between commands:
|
||||
For interactive work, the REPL keeps a persistent connection:
|
||||
|
||||
```
|
||||
$ pbi repl
|
||||
|
|
@ -261,44 +152,7 @@ pbi(localhost-54321)> dax execute "EVALUATE TOPN(5, Sales)"
|
|||
pbi(localhost-54321)> exit
|
||||
```
|
||||
|
||||
Tab completion, command history, and a dynamic prompt showing your active connection.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
pbi-cli connects directly to Power BI Desktop's Analysis Services engine via pythonnet and the .NET Tabular Object Model (TOM). No external binaries or MCP servers needed. Everything runs in-process for sub-second command execution.
|
||||
|
||||
```
|
||||
+------------------+ +---------------------+ +------------------+
|
||||
| pbi-cli | | Bundled TOM DLLs | | Power BI |
|
||||
| (Python CLI) | pythnet | (.NET in-process) | XMLA | Desktop |
|
||||
| Click commands |-------->| TOM / ADOMD.NET |-------->| msmdsrv.exe |
|
||||
+------------------+ +---------------------+ +------------------+
|
||||
```
|
||||
|
||||
**Why a CLI?** When an AI agent uses an MCP server directly, the tool schemas consume ~4,000+ tokens per tool in the context window. A `pbi` command costs ~30 tokens. Same capabilities, 100x less context.
|
||||
|
||||
<details>
|
||||
<summary><b>Configuration details</b></summary>
|
||||
|
||||
All config lives in `~/.pbi-cli/`:
|
||||
|
||||
```
|
||||
~/.pbi-cli/
|
||||
config.json # Default connection preference
|
||||
connections.json # Named connections
|
||||
repl_history # REPL command history
|
||||
```
|
||||
|
||||
Bundled DLLs ship inside the Python package (`pbi_cli/dlls/`):
|
||||
- Microsoft.AnalysisServices.Tabular.dll
|
||||
- Microsoft.AnalysisServices.AdomdClient.dll
|
||||
- Microsoft.AnalysisServices.Core.dll
|
||||
- Microsoft.AnalysisServices.Tabular.Json.dll
|
||||
- Microsoft.AnalysisServices.dll
|
||||
|
||||
</details>
|
||||
Tab completion, command history, and a dynamic prompt.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -313,7 +167,7 @@ pip install -e ".[dev]"
|
|||
```bash
|
||||
ruff check src/ tests/ # Lint
|
||||
mypy src/ # Type check
|
||||
pytest -m "not e2e" # Run tests
|
||||
pytest -m "not e2e" # Run tests (488 tests)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||
|
||||
[project]
|
||||
name = "pbi-cli-tool"
|
||||
version = "3.9.0"
|
||||
version = "3.10.0"
|
||||
description = "CLI for Power BI semantic models and PBIR reports - direct .NET connection for token-efficient AI agent usage"
|
||||
readme = "README.pypi.md"
|
||||
license = {text = "MIT"}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
"""pbi-cli: CLI for Power BI semantic models via direct .NET interop."""
|
||||
|
||||
__version__ = "3.9.0"
|
||||
__version__ = "3.10.0"
|
||||
|
|
|
|||
|
|
@ -19,13 +19,21 @@ _PBI_CLI_CLAUDE_MD_SNIPPET = (
|
|||
"When working with Power BI, DAX, semantic models, or data modeling,\n"
|
||||
"invoke the relevant pbi-cli skill before responding:\n"
|
||||
"\n"
|
||||
"**Semantic Model (requires `pbi connect`):**\n"
|
||||
"- **power-bi-dax** -- DAX queries, measures, calculations\n"
|
||||
"- **power-bi-modeling** -- tables, columns, measures, relationships\n"
|
||||
"- **power-bi-diagnostics** -- troubleshooting, tracing, setup\n"
|
||||
"- **power-bi-deployment** -- TMDL export/import, transactions\n"
|
||||
"- **power-bi-deployment** -- TMDL export/import, transactions, diff\n"
|
||||
"- **power-bi-docs** -- model documentation, data dictionary\n"
|
||||
"- **power-bi-partitions** -- partitions, M expressions, data sources\n"
|
||||
"- **power-bi-security** -- RLS roles, perspectives, access control\n"
|
||||
"- **power-bi-diagnostics** -- troubleshooting, tracing, setup\n"
|
||||
"\n"
|
||||
"**Report Layer (no connection needed):**\n"
|
||||
"- **power-bi-report** -- scaffold, validate, preview PBIR reports\n"
|
||||
"- **power-bi-visuals** -- add, bind, update, bulk-manage visuals\n"
|
||||
"- **power-bi-pages** -- pages, bookmarks, visibility, drillthrough\n"
|
||||
"- **power-bi-themes** -- themes, conditional formatting, styling\n"
|
||||
"- **power-bi-filters** -- page and visual filters (TopN, date, categorical)\n"
|
||||
"\n"
|
||||
"Critical: Multi-line DAX (VAR/RETURN) cannot be passed via `-e`.\n"
|
||||
"Use `--file` or stdin piping instead. See power-bi-dax skill.\n"
|
||||
|
|
|
|||
|
|
@ -40,3 +40,27 @@ class TomError(PbiCliError):
|
|||
self.operation = operation
|
||||
self.detail = detail
|
||||
super().__init__(f"{operation}: {detail}")
|
||||
|
||||
|
||||
class VisualTypeError(PbiCliError):
|
||||
"""Raised when a visual type is not recognised."""
|
||||
|
||||
def __init__(self, visual_type: str) -> None:
|
||||
self.visual_type = visual_type
|
||||
super().__init__(
|
||||
f"Unknown visual type '{visual_type}'. "
|
||||
"Run 'pbi visual types' to see supported types."
|
||||
)
|
||||
|
||||
|
||||
class ReportNotFoundError(PbiCliError):
|
||||
"""Raised when no PBIR report definition folder can be found."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str = (
|
||||
"No PBIR report found. Run this command inside a .pbip project "
|
||||
"or pass --path to the .Report folder."
|
||||
),
|
||||
) -> None:
|
||||
super().__init__(message)
|
||||
|
|
|
|||
492
src/pbi_cli/core/filter_backend.py
Normal file
492
src/pbi_cli/core/filter_backend.py
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
"""Pure-function backend for PBIR filter operations.
|
||||
|
||||
Every function takes a ``Path`` to the definition folder and returns a plain
|
||||
Python dict suitable for ``format_result()``.
|
||||
|
||||
Filters are stored in ``filterConfig.filters[]`` inside either:
|
||||
- ``pages/<page_name>/page.json`` for page-level filters
|
||||
- ``pages/<page_name>/visuals/<visual_name>/visual.json`` for visual-level filters
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from pbi_cli.core.errors import PbiCliError
|
||||
from pbi_cli.core.pbir_path import get_page_dir, get_visual_dir
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# JSON helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
"""Read and parse a JSON file."""
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _write_json(path: Path, data: dict[str, Any]) -> None:
|
||||
"""Write JSON with consistent formatting."""
|
||||
path.write_text(
|
||||
json.dumps(data, indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _generate_name() -> str:
|
||||
"""Generate a 20-character hex identifier matching PBIR convention."""
|
||||
return secrets.token_hex(10)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Path resolution helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _resolve_target_path(
|
||||
definition_path: Path,
|
||||
page_name: str,
|
||||
visual_name: str | None,
|
||||
) -> Path:
|
||||
"""Return the JSON file path for the target (page or visual)."""
|
||||
if visual_name is None:
|
||||
return get_page_dir(definition_path, page_name) / "page.json"
|
||||
return get_visual_dir(definition_path, page_name, visual_name) / "visual.json"
|
||||
|
||||
|
||||
def _get_filters(data: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""Extract the filters list from a page or visual JSON dict."""
|
||||
filter_config = data.get("filterConfig")
|
||||
if not isinstance(filter_config, dict):
|
||||
return []
|
||||
filters = filter_config.get("filters")
|
||||
if not isinstance(filters, list):
|
||||
return []
|
||||
return filters
|
||||
|
||||
|
||||
def _set_filters(data: dict[str, Any], filters: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""Return a new dict with filterConfig.filters replaced (immutable update)."""
|
||||
filter_config = dict(data.get("filterConfig") or {})
|
||||
filter_config["filters"] = filters
|
||||
return {**data, "filterConfig": filter_config}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def filter_list(
|
||||
definition_path: Path,
|
||||
page_name: str,
|
||||
visual_name: str | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""List filters on a page or specific visual.
|
||||
|
||||
If visual_name is None, returns page-level filters from page.json.
|
||||
If visual_name is given, returns visual-level filters from visual.json.
|
||||
Returns the raw filter dicts from filterConfig.filters[].
|
||||
"""
|
||||
target = _resolve_target_path(definition_path, page_name, visual_name)
|
||||
if not target.exists():
|
||||
raise PbiCliError(f"File not found: {target}")
|
||||
data = _read_json(target)
|
||||
return _get_filters(data)
|
||||
|
||||
|
||||
def filter_add_categorical(
|
||||
definition_path: Path,
|
||||
page_name: str,
|
||||
table: str,
|
||||
column: str,
|
||||
values: list[str],
|
||||
visual_name: str | None = None,
|
||||
name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Add a categorical filter to a page or visual.
|
||||
|
||||
Builds the full filterConfig entry from table/column/values.
|
||||
The source alias is always the first letter of the table name (lowercase).
|
||||
Returns a status dict with name, type, and scope.
|
||||
"""
|
||||
target = _resolve_target_path(definition_path, page_name, visual_name)
|
||||
if not target.exists():
|
||||
raise PbiCliError(f"File not found: {target}")
|
||||
|
||||
filter_name = name if name is not None else _generate_name()
|
||||
alias = table[0].lower()
|
||||
scope = "visual" if visual_name is not None else "page"
|
||||
|
||||
where_values: list[list[dict[str, Any]]] = [
|
||||
[{"Literal": {"Value": f"'{v}'"}}] for v in values
|
||||
]
|
||||
|
||||
entry: dict[str, Any] = {
|
||||
"name": filter_name,
|
||||
"field": {
|
||||
"Column": {
|
||||
"Expression": {"SourceRef": {"Entity": table}},
|
||||
"Property": column,
|
||||
}
|
||||
},
|
||||
"type": "Categorical",
|
||||
"filter": {
|
||||
"Version": 2,
|
||||
"From": [{"Name": alias, "Entity": table, "Type": 0}],
|
||||
"Where": [
|
||||
{
|
||||
"Condition": {
|
||||
"In": {
|
||||
"Expressions": [
|
||||
{
|
||||
"Column": {
|
||||
"Expression": {"SourceRef": {"Source": alias}},
|
||||
"Property": column,
|
||||
}
|
||||
}
|
||||
],
|
||||
"Values": where_values,
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
if scope == "page":
|
||||
entry["howCreated"] = "User"
|
||||
|
||||
data = _read_json(target)
|
||||
filters = list(_get_filters(data))
|
||||
filters.append(entry)
|
||||
updated = _set_filters(data, filters)
|
||||
_write_json(target, updated)
|
||||
|
||||
return {"status": "added", "name": filter_name, "type": "Categorical", "scope": scope}
|
||||
|
||||
|
||||
def filter_remove(
|
||||
definition_path: Path,
|
||||
page_name: str,
|
||||
filter_name: str,
|
||||
visual_name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Remove a filter by name from a page or visual.
|
||||
|
||||
Raises PbiCliError if filter_name is not found.
|
||||
Returns a status dict with the removed filter name.
|
||||
"""
|
||||
target = _resolve_target_path(definition_path, page_name, visual_name)
|
||||
if not target.exists():
|
||||
raise PbiCliError(f"File not found: {target}")
|
||||
|
||||
data = _read_json(target)
|
||||
filters = _get_filters(data)
|
||||
remaining = [f for f in filters if f.get("name") != filter_name]
|
||||
|
||||
if len(remaining) == len(filters):
|
||||
raise PbiCliError(
|
||||
f"Filter '{filter_name}' not found on "
|
||||
f"{'visual ' + visual_name if visual_name else 'page'} '{page_name}'."
|
||||
)
|
||||
|
||||
updated = _set_filters(data, remaining)
|
||||
_write_json(target, updated)
|
||||
return {"status": "removed", "name": filter_name}
|
||||
|
||||
|
||||
def filter_add_topn(
|
||||
definition_path: Path,
|
||||
page_name: str,
|
||||
table: str,
|
||||
column: str,
|
||||
n: int,
|
||||
order_by_table: str,
|
||||
order_by_column: str,
|
||||
direction: str = "Top",
|
||||
visual_name: str | None = None,
|
||||
name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Add a TopN filter to a page or visual.
|
||||
|
||||
*direction* is ``"Top"`` (highest N by *order_by_column*) or
|
||||
``"Bottom"`` (lowest N). Direction maps to Power BI query Direction
|
||||
values: Top = 2 (Descending), Bottom = 1 (Ascending).
|
||||
|
||||
Returns a status dict with name, type, scope, n, and direction.
|
||||
"""
|
||||
direction_upper = direction.strip().capitalize()
|
||||
if direction_upper not in ("Top", "Bottom"):
|
||||
raise PbiCliError(f"direction must be 'Top' or 'Bottom', got '{direction}'.")
|
||||
|
||||
pbi_direction = 2 if direction_upper == "Top" else 1
|
||||
|
||||
target = _resolve_target_path(definition_path, page_name, visual_name)
|
||||
if not target.exists():
|
||||
raise PbiCliError(f"File not found: {target}")
|
||||
|
||||
filter_name = name if name is not None else _generate_name()
|
||||
cat_alias = table[0].lower()
|
||||
ord_alias = order_by_table[0].lower()
|
||||
# Avoid alias collision when both tables start with the same letter
|
||||
if ord_alias == cat_alias and order_by_table != table:
|
||||
ord_alias = ord_alias + "2"
|
||||
scope = "visual" if visual_name is not None else "page"
|
||||
|
||||
# Inner subquery From: include both tables when they differ
|
||||
inner_from: list[dict[str, Any]] = [
|
||||
{"Name": cat_alias, "Entity": table, "Type": 0},
|
||||
]
|
||||
if order_by_table != table:
|
||||
inner_from.append({"Name": ord_alias, "Entity": order_by_table, "Type": 0})
|
||||
|
||||
entry: dict[str, Any] = {
|
||||
"name": filter_name,
|
||||
"field": {
|
||||
"Column": {
|
||||
"Expression": {"SourceRef": {"Entity": table}},
|
||||
"Property": column,
|
||||
}
|
||||
},
|
||||
"type": "TopN",
|
||||
"filter": {
|
||||
"Version": 2,
|
||||
"From": [
|
||||
{
|
||||
"Name": "subquery",
|
||||
"Expression": {
|
||||
"Subquery": {
|
||||
"Query": {
|
||||
"Version": 2,
|
||||
"From": inner_from,
|
||||
"Select": [
|
||||
{
|
||||
"Column": {
|
||||
"Expression": {
|
||||
"SourceRef": {"Source": cat_alias}
|
||||
},
|
||||
"Property": column,
|
||||
},
|
||||
"Name": "field",
|
||||
}
|
||||
],
|
||||
"OrderBy": [
|
||||
{
|
||||
"Direction": pbi_direction,
|
||||
"Expression": {
|
||||
"Aggregation": {
|
||||
"Expression": {
|
||||
"Column": {
|
||||
"Expression": {
|
||||
"SourceRef": {
|
||||
"Source": ord_alias
|
||||
if order_by_table != table
|
||||
else cat_alias
|
||||
}
|
||||
},
|
||||
"Property": order_by_column,
|
||||
}
|
||||
},
|
||||
"Function": 0,
|
||||
}
|
||||
},
|
||||
}
|
||||
],
|
||||
"Top": n,
|
||||
}
|
||||
}
|
||||
},
|
||||
"Type": 2,
|
||||
},
|
||||
{"Name": cat_alias, "Entity": table, "Type": 0},
|
||||
],
|
||||
"Where": [
|
||||
{
|
||||
"Condition": {
|
||||
"In": {
|
||||
"Expressions": [
|
||||
{
|
||||
"Column": {
|
||||
"Expression": {
|
||||
"SourceRef": {"Source": cat_alias}
|
||||
},
|
||||
"Property": column,
|
||||
}
|
||||
}
|
||||
],
|
||||
"Table": {"SourceRef": {"Source": "subquery"}},
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
if scope == "page":
|
||||
entry["howCreated"] = "User"
|
||||
|
||||
data = _read_json(target)
|
||||
filters = list(_get_filters(data))
|
||||
filters.append(entry)
|
||||
updated = _set_filters(data, filters)
|
||||
_write_json(target, updated)
|
||||
|
||||
return {
|
||||
"status": "added",
|
||||
"name": filter_name,
|
||||
"type": "TopN",
|
||||
"scope": scope,
|
||||
"n": n,
|
||||
"direction": direction_upper,
|
||||
}
|
||||
|
||||
|
||||
# TimeUnit integer codes used by Power BI for RelativeDate filters.
|
||||
_RELATIVE_DATE_TIME_UNITS: dict[str, int] = {
|
||||
"days": 0,
|
||||
"weeks": 1,
|
||||
"months": 2,
|
||||
"years": 3,
|
||||
}
|
||||
|
||||
|
||||
def filter_add_relative_date(
|
||||
definition_path: Path,
|
||||
page_name: str,
|
||||
table: str,
|
||||
column: str,
|
||||
amount: int,
|
||||
time_unit: str,
|
||||
visual_name: str | None = None,
|
||||
name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Add a RelativeDate filter (e.g. "last 3 months") to a page or visual.
|
||||
|
||||
*amount* is a positive integer representing the period count.
|
||||
*time_unit* is one of ``"days"``, ``"weeks"``, ``"months"``, ``"years"``.
|
||||
|
||||
The filter matches rows where *column* falls in the last *amount* *time_unit*
|
||||
relative to today (inclusive of the current period boundary).
|
||||
|
||||
Returns a status dict with name, type, scope, amount, and time_unit.
|
||||
"""
|
||||
time_unit_lower = time_unit.strip().lower()
|
||||
if time_unit_lower not in _RELATIVE_DATE_TIME_UNITS:
|
||||
valid = ", ".join(_RELATIVE_DATE_TIME_UNITS)
|
||||
raise PbiCliError(
|
||||
f"time_unit must be one of {valid}, got '{time_unit}'."
|
||||
)
|
||||
time_unit_code = _RELATIVE_DATE_TIME_UNITS[time_unit_lower]
|
||||
days_code = _RELATIVE_DATE_TIME_UNITS["days"]
|
||||
|
||||
target = _resolve_target_path(definition_path, page_name, visual_name)
|
||||
if not target.exists():
|
||||
raise PbiCliError(f"File not found: {target}")
|
||||
|
||||
filter_name = name if name is not None else _generate_name()
|
||||
alias = table[0].lower()
|
||||
scope = "visual" if visual_name is not None else "page"
|
||||
|
||||
# LowerBound: DateSpan(DateAdd(DateAdd(Now(), +1, days), -amount, time_unit), days)
|
||||
lower_bound: dict[str, Any] = {
|
||||
"DateSpan": {
|
||||
"Expression": {
|
||||
"DateAdd": {
|
||||
"Expression": {
|
||||
"DateAdd": {
|
||||
"Expression": {"Now": {}},
|
||||
"Amount": 1,
|
||||
"TimeUnit": days_code,
|
||||
}
|
||||
},
|
||||
"Amount": -amount,
|
||||
"TimeUnit": time_unit_code,
|
||||
}
|
||||
},
|
||||
"TimeUnit": days_code,
|
||||
}
|
||||
}
|
||||
|
||||
# UpperBound: DateSpan(Now(), days)
|
||||
upper_bound: dict[str, Any] = {
|
||||
"DateSpan": {
|
||||
"Expression": {"Now": {}},
|
||||
"TimeUnit": days_code,
|
||||
}
|
||||
}
|
||||
|
||||
entry: dict[str, Any] = {
|
||||
"name": filter_name,
|
||||
"field": {
|
||||
"Column": {
|
||||
"Expression": {"SourceRef": {"Entity": table}},
|
||||
"Property": column,
|
||||
}
|
||||
},
|
||||
"type": "RelativeDate",
|
||||
"filter": {
|
||||
"Version": 2,
|
||||
"From": [{"Name": alias, "Entity": table, "Type": 0}],
|
||||
"Where": [
|
||||
{
|
||||
"Condition": {
|
||||
"Between": {
|
||||
"Expression": {
|
||||
"Column": {
|
||||
"Expression": {"SourceRef": {"Source": alias}},
|
||||
"Property": column,
|
||||
}
|
||||
},
|
||||
"LowerBound": lower_bound,
|
||||
"UpperBound": upper_bound,
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
if scope == "page":
|
||||
entry["howCreated"] = "User"
|
||||
|
||||
data = _read_json(target)
|
||||
filters = list(_get_filters(data))
|
||||
filters.append(entry)
|
||||
updated = _set_filters(data, filters)
|
||||
_write_json(target, updated)
|
||||
|
||||
return {
|
||||
"status": "added",
|
||||
"name": filter_name,
|
||||
"type": "RelativeDate",
|
||||
"scope": scope,
|
||||
"amount": amount,
|
||||
"time_unit": time_unit_lower,
|
||||
}
|
||||
|
||||
|
||||
def filter_clear(
|
||||
definition_path: Path,
|
||||
page_name: str,
|
||||
visual_name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Remove all filters from a page or visual.
|
||||
|
||||
Returns a status dict with the count of removed filters and scope.
|
||||
"""
|
||||
target = _resolve_target_path(definition_path, page_name, visual_name)
|
||||
if not target.exists():
|
||||
raise PbiCliError(f"File not found: {target}")
|
||||
|
||||
scope = "visual" if visual_name is not None else "page"
|
||||
data = _read_json(target)
|
||||
filters = _get_filters(data)
|
||||
removed = len(filters)
|
||||
|
||||
updated = _set_filters(data, [])
|
||||
_write_json(target, updated)
|
||||
return {"status": "cleared", "removed": removed, "scope": scope}
|
||||
|
|
@ -515,13 +515,12 @@ def theme_set(
|
|||
if not found:
|
||||
resource_packages.append({
|
||||
"name": "RegisteredResources",
|
||||
"resourcePackage": {
|
||||
"items": [{
|
||||
"name": theme_path.name,
|
||||
"type": 202,
|
||||
"path": f"BaseThemes/{theme_path.name}",
|
||||
}],
|
||||
},
|
||||
"type": "RegisteredResources",
|
||||
"items": [{
|
||||
"name": theme_path.name,
|
||||
"type": 202,
|
||||
"path": f"BaseThemes/{theme_path.name}",
|
||||
}],
|
||||
})
|
||||
report_data["resourcePackages"] = resource_packages
|
||||
|
||||
|
|
|
|||
|
|
@ -145,17 +145,29 @@ def _diff_table_entities(
|
|||
"partitions_added": [],
|
||||
"partitions_removed": [],
|
||||
"partitions_changed": [],
|
||||
"hierarchies_added": [],
|
||||
"hierarchies_removed": [],
|
||||
"hierarchies_changed": [],
|
||||
"other_added": [],
|
||||
"other_removed": [],
|
||||
"other_changed": [],
|
||||
}
|
||||
|
||||
# Map TMDL keywords to their plural result-dict prefix
|
||||
keyword_plurals: dict[str, str] = {
|
||||
"measure": "measures",
|
||||
"column": "columns",
|
||||
"partition": "partitions",
|
||||
"hierarchy": "hierarchies",
|
||||
}
|
||||
|
||||
all_keys = set(base_entities) | set(head_entities)
|
||||
for key in sorted(all_keys):
|
||||
keyword, _, name = key.partition("/")
|
||||
added_key = f"{keyword}s_added" if f"{keyword}s_added" in result else "other_added"
|
||||
removed_key = f"{keyword}s_removed" if f"{keyword}s_removed" in result else "other_removed"
|
||||
changed_key = f"{keyword}s_changed" if f"{keyword}s_changed" in result else "other_changed"
|
||||
plural = keyword_plurals.get(keyword, "other")
|
||||
added_key = f"{plural}_added"
|
||||
removed_key = f"{plural}_removed"
|
||||
changed_key = f"{plural}_changed"
|
||||
|
||||
if key not in base_entities:
|
||||
result[added_key].append(name)
|
||||
|
|
|
|||
|
|
@ -541,7 +541,7 @@ def visual_bind(
|
|||
definition_path: Path,
|
||||
page_name: str,
|
||||
visual_name: str,
|
||||
bindings: list[dict[str, str]],
|
||||
bindings: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
"""Bind semantic model fields to visual data roles.
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ def cli(ctx: click.Context, json_output: bool, connection: str | None) -> None:
|
|||
def _register_commands() -> None:
|
||||
"""Lazily import and register all command groups."""
|
||||
from pbi_cli.commands.advanced import advanced
|
||||
from pbi_cli.commands.bookmarks import bookmarks
|
||||
from pbi_cli.commands.calc_group import calc_group
|
||||
from pbi_cli.commands.calendar import calendar
|
||||
from pbi_cli.commands.column import column
|
||||
|
|
@ -59,6 +60,8 @@ def _register_commands() -> None:
|
|||
from pbi_cli.commands.database import database
|
||||
from pbi_cli.commands.dax import dax
|
||||
from pbi_cli.commands.expression import expression
|
||||
from pbi_cli.commands.filters import filters
|
||||
from pbi_cli.commands.format_cmd import format_cmd
|
||||
from pbi_cli.commands.hierarchy import hierarchy
|
||||
from pbi_cli.commands.measure import measure
|
||||
from pbi_cli.commands.model import model
|
||||
|
|
@ -66,12 +69,14 @@ def _register_commands() -> None:
|
|||
from pbi_cli.commands.perspective import perspective
|
||||
from pbi_cli.commands.relationship import relationship
|
||||
from pbi_cli.commands.repl_cmd import repl
|
||||
from pbi_cli.commands.report import report
|
||||
from pbi_cli.commands.security import security_role
|
||||
from pbi_cli.commands.setup_cmd import setup
|
||||
from pbi_cli.commands.skills_cmd import skills
|
||||
from pbi_cli.commands.table import table
|
||||
from pbi_cli.commands.trace import trace
|
||||
from pbi_cli.commands.transaction import transaction
|
||||
from pbi_cli.commands.visual import visual
|
||||
|
||||
cli.add_command(setup)
|
||||
cli.add_command(connect)
|
||||
|
|
@ -96,6 +101,11 @@ def _register_commands() -> None:
|
|||
cli.add_command(advanced)
|
||||
cli.add_command(repl)
|
||||
cli.add_command(skills)
|
||||
cli.add_command(report)
|
||||
cli.add_command(visual)
|
||||
cli.add_command(filters)
|
||||
cli.add_command(format_cmd)
|
||||
cli.add_command(bookmarks)
|
||||
|
||||
|
||||
_register_commands()
|
||||
|
|
|
|||
|
|
@ -50,6 +50,31 @@ pbi database import-tmdl ./model-tmdl/
|
|||
pbi database export-tmsl
|
||||
```
|
||||
|
||||
## TMDL Diff (Compare Snapshots)
|
||||
|
||||
Compare two TMDL export folders to see what changed between snapshots.
|
||||
Useful for CI/CD pipelines ("what did this PR change in the model?").
|
||||
|
||||
```bash
|
||||
# Compare two exports
|
||||
pbi database diff-tmdl ./model-before/ ./model-after/
|
||||
|
||||
# JSON output for CI/CD scripting
|
||||
pbi --json database diff-tmdl ./baseline/ ./current/
|
||||
```
|
||||
|
||||
Returns a structured summary:
|
||||
- **tables**: added, removed, and changed tables with per-table entity diffs
|
||||
(measures, columns, partitions, hierarchies added/removed/changed)
|
||||
- **relationships**: added, removed, and changed relationships
|
||||
- **model**: changed model-level properties (e.g. culture, default power bi dataset version)
|
||||
- **summary**: total counts of all changes
|
||||
|
||||
LineageTag-only changes (GUID regeneration without real edits) are automatically
|
||||
filtered out to avoid false positives.
|
||||
|
||||
No connection to Power BI Desktop is needed -- works on exported folders.
|
||||
|
||||
## Database Operations
|
||||
|
||||
```bash
|
||||
|
|
|
|||
137
src/pbi_cli/skills/power-bi-filters/SKILL.md
Normal file
137
src/pbi_cli/skills/power-bi-filters/SKILL.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
---
|
||||
name: Power BI Filters
|
||||
description: >
|
||||
Add, remove, and manage page-level and visual-level filters on Power BI PBIR
|
||||
reports using pbi-cli. Invoke this skill whenever the user mentions "filter",
|
||||
"TopN filter", "top 10", "bottom 5", "relative date filter", "last 30 days",
|
||||
"categorical filter", "include values", "exclude values", "clear filters",
|
||||
"slicer filter", "page filter", "visual filter", or wants to restrict which
|
||||
data appears on a page or in a specific visual.
|
||||
tools: pbi-cli
|
||||
---
|
||||
|
||||
# Power BI Filters Skill
|
||||
|
||||
Add and manage filters on PBIR report pages and visuals. Filters are stored
|
||||
in the `filterConfig` section of `page.json` (page-level) or `visual.json`
|
||||
(visual-level). No Power BI Desktop connection is needed.
|
||||
|
||||
## Listing Filters
|
||||
|
||||
```bash
|
||||
# List all filters on a page
|
||||
pbi filters list --page page_abc123
|
||||
|
||||
# List filters on a specific visual
|
||||
pbi filters list --page page_abc123 --visual visual_def456
|
||||
```
|
||||
|
||||
Returns each filter's name, type, field, and scope (page or visual).
|
||||
|
||||
## Categorical Filters
|
||||
|
||||
Include or exclude specific values from a column:
|
||||
|
||||
```bash
|
||||
# Include only East and West regions
|
||||
pbi filters add-categorical --page page1 \
|
||||
--table Sales --column Region \
|
||||
--values "East" "West"
|
||||
```
|
||||
|
||||
The filter appears in the page's `filterConfig.filters` array. Power BI
|
||||
evaluates it as an IN-list against the specified column.
|
||||
|
||||
## TopN Filters
|
||||
|
||||
Show only the top (or bottom) N items ranked by a measure:
|
||||
|
||||
```bash
|
||||
# Top 10 products by revenue
|
||||
pbi filters add-topn --page page1 \
|
||||
--table Product --column Name \
|
||||
--n 10 \
|
||||
--order-by-table Sales --order-by-column Revenue
|
||||
|
||||
# Bottom 5 by quantity (ascending)
|
||||
pbi filters add-topn --page page1 \
|
||||
--table Product --column Name \
|
||||
--n 5 \
|
||||
--order-by-table Sales --order-by-column Quantity \
|
||||
--direction Bottom
|
||||
```
|
||||
|
||||
The `--table` and `--column` define which dimension to filter (the rows you
|
||||
want to keep). The `--order-by-table` and `--order-by-column` define the
|
||||
measure used for ranking. These can be different tables -- for example,
|
||||
filtering Product names by Sales revenue.
|
||||
|
||||
Direction defaults to `Top` (descending -- highest N). Use `--direction Bottom`
|
||||
for ascending (lowest N).
|
||||
|
||||
## Relative Date Filters
|
||||
|
||||
Filter by a rolling window relative to today:
|
||||
|
||||
```bash
|
||||
# Last 30 days
|
||||
pbi filters add-relative-date --page page1 \
|
||||
--table Calendar --column Date \
|
||||
--period days --count 30 --direction last
|
||||
|
||||
# Next 7 days
|
||||
pbi filters add-relative-date --page page1 \
|
||||
--table Calendar --column Date \
|
||||
--period days --count 7 --direction next
|
||||
```
|
||||
|
||||
Period options: `days`, `weeks`, `months`, `quarters`, `years`.
|
||||
Direction: `last` (past) or `next` (future).
|
||||
|
||||
## Visual-Level Filters
|
||||
|
||||
Add a filter to a specific visual instead of the whole page by including
|
||||
`--visual`:
|
||||
|
||||
```bash
|
||||
pbi filters add-categorical --page page1 --visual vis_abc \
|
||||
--table Sales --column Channel \
|
||||
--values "Online"
|
||||
```
|
||||
|
||||
## Removing Filters
|
||||
|
||||
```bash
|
||||
# Remove a specific filter by name
|
||||
pbi filters remove --page page1 --name filter_abc123
|
||||
|
||||
# Clear ALL filters from a page
|
||||
pbi filters clear --page page1
|
||||
```
|
||||
|
||||
Filter names are auto-generated unique IDs. Use `pbi filters list` to find
|
||||
the name of the filter you want to remove.
|
||||
|
||||
## Workflow: Set Up Dashboard Filters
|
||||
|
||||
```bash
|
||||
# 1. Add a date filter to the overview page
|
||||
pbi filters add-relative-date --page overview \
|
||||
--table Calendar --column Date \
|
||||
--period months --count 12 --direction last
|
||||
|
||||
# 2. Add a TopN filter to show only top customers
|
||||
pbi filters add-topn --page overview \
|
||||
--table Customer --column Name \
|
||||
--n 20 \
|
||||
--order-by-table Sales --order-by-column Revenue
|
||||
|
||||
# 3. Verify
|
||||
pbi filters list --page overview
|
||||
```
|
||||
|
||||
## JSON Output
|
||||
|
||||
```bash
|
||||
pbi --json filters list --page page1
|
||||
```
|
||||
151
src/pbi_cli/skills/power-bi-pages/SKILL.md
Normal file
151
src/pbi_cli/skills/power-bi-pages/SKILL.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
name: Power BI Pages
|
||||
description: >
|
||||
Manage Power BI report pages and bookmarks -- add, remove, configure, and lay
|
||||
out pages in PBIR reports using pbi-cli. Invoke this skill whenever the user
|
||||
mentions "add page", "new page", "delete page", "page layout", "page size",
|
||||
"page background", "hide page", "show page", "drillthrough", "page order",
|
||||
"page visibility", "page settings", "page navigation", "bookmark", "create
|
||||
bookmark", "save bookmark", "delete bookmark", or wants to manage bookmarks
|
||||
that capture page-level state. Also invoke when the user asks about drillthrough
|
||||
configuration or pageBinding.
|
||||
tools: pbi-cli
|
||||
---
|
||||
|
||||
# Power BI Pages Skill
|
||||
|
||||
Manage pages in PBIR reports. Pages are folders inside `definition/pages/`
|
||||
containing a `page.json` file and a `visuals/` directory. No Power BI Desktop
|
||||
connection is needed.
|
||||
|
||||
## Listing and Inspecting Pages
|
||||
|
||||
```bash
|
||||
# List all pages with display names, order, and visibility
|
||||
pbi report list-pages
|
||||
|
||||
# Get full details of a specific page
|
||||
pbi report get-page page_abc123
|
||||
```
|
||||
|
||||
`get-page` returns:
|
||||
- `name`, `display_name`, `ordinal` (sort order)
|
||||
- `width`, `height` (canvas size in pixels)
|
||||
- `display_option` (e.g. `"FitToPage"`)
|
||||
- `visual_count` -- how many visuals on the page
|
||||
- `is_hidden` -- whether the page is hidden in the navigation pane
|
||||
- `page_type` -- `"Default"` or `"Drillthrough"`
|
||||
- `filter_config` -- page-level filter configuration (if any)
|
||||
- `visual_interactions` -- custom visual interaction rules (if any)
|
||||
- `page_binding` -- drillthrough parameter definition (if drillthrough page)
|
||||
|
||||
## Adding Pages
|
||||
|
||||
```bash
|
||||
# Add with display name (folder name auto-generated)
|
||||
pbi report add-page --display-name "Executive Overview"
|
||||
|
||||
# Custom folder name and canvas size
|
||||
pbi report add-page --display-name "Details" --name detail_page \
|
||||
--width 1920 --height 1080
|
||||
```
|
||||
|
||||
Default canvas size is 1280x720 (standard 16:9). Common alternatives:
|
||||
- 1920x1080 -- Full HD
|
||||
- 1280x960 -- 4:3
|
||||
- Custom dimensions for mobile or dashboard layouts
|
||||
|
||||
## Deleting Pages
|
||||
|
||||
```bash
|
||||
# Delete a page and all its visuals
|
||||
pbi report delete-page page_abc123
|
||||
```
|
||||
|
||||
This removes the entire page folder including all visual subdirectories.
|
||||
|
||||
## Page Background
|
||||
|
||||
```bash
|
||||
# Set a solid background colour
|
||||
pbi report set-background page_abc123 --color "#F5F5F5"
|
||||
```
|
||||
|
||||
## Page Visibility
|
||||
|
||||
Control whether a page appears in the report navigation pane:
|
||||
|
||||
```bash
|
||||
# Hide a page (useful for drillthrough or tooltip pages)
|
||||
pbi report set-visibility page_abc123 --hidden
|
||||
|
||||
# Show a hidden page
|
||||
pbi report set-visibility page_abc123 --visible
|
||||
```
|
||||
|
||||
## Bookmarks
|
||||
|
||||
Bookmarks capture page-level state (filters, visibility, scroll position).
|
||||
They live in `definition/bookmarks/`:
|
||||
|
||||
```bash
|
||||
# List all bookmarks in the report
|
||||
pbi bookmarks list
|
||||
|
||||
# Get details of a specific bookmark
|
||||
pbi bookmarks get "My Bookmark"
|
||||
|
||||
# Add a new bookmark
|
||||
pbi bookmarks add "Executive View"
|
||||
|
||||
# Delete a bookmark
|
||||
pbi bookmarks delete "Old Bookmark"
|
||||
|
||||
# Toggle bookmark visibility
|
||||
pbi bookmarks set-visibility "Draft View" --hidden
|
||||
```
|
||||
|
||||
## Drillthrough Pages
|
||||
|
||||
Drillthrough pages have a `pageBinding` field in `page.json` that defines the
|
||||
drillthrough parameter. When you call `get-page` on a drillthrough page, the
|
||||
`page_binding` field returns the full binding definition including parameter
|
||||
name, bound filter, and field expression. Regular pages return `null`.
|
||||
|
||||
To create a drillthrough page, add a page and then configure it as drillthrough
|
||||
in Power BI Desktop (PBIR drillthrough configuration is not yet supported via
|
||||
CLI -- the CLI can read and report on drillthrough configuration).
|
||||
|
||||
## Workflow: Set Up Report Pages
|
||||
|
||||
```bash
|
||||
# 1. Add pages in order
|
||||
pbi report add-page --display-name "Overview" --name overview
|
||||
pbi report add-page --display-name "Sales Detail" --name sales_detail
|
||||
pbi report add-page --display-name "Regional Drillthrough" --name region_drill
|
||||
|
||||
# 2. Hide the drillthrough page from navigation
|
||||
pbi report set-visibility region_drill --hidden
|
||||
|
||||
# 3. Set backgrounds
|
||||
pbi report set-background overview --color "#FAFAFA"
|
||||
|
||||
# 4. Verify the setup
|
||||
pbi report list-pages
|
||||
```
|
||||
|
||||
## Path Resolution
|
||||
|
||||
Page commands inherit the report path from the parent `pbi report` group:
|
||||
|
||||
1. Explicit: `pbi report --path ./MyReport.Report list-pages`
|
||||
2. Auto-detect: walks up from CWD looking for `*.Report/definition/`
|
||||
3. From `.pbip`: finds sibling `.Report` folder from `.pbip` file
|
||||
|
||||
## JSON Output
|
||||
|
||||
```bash
|
||||
pbi --json report list-pages
|
||||
pbi --json report get-page page_abc123
|
||||
pbi --json bookmarks list
|
||||
```
|
||||
169
src/pbi_cli/skills/power-bi-report/SKILL.md
Normal file
169
src/pbi_cli/skills/power-bi-report/SKILL.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
---
|
||||
name: Power BI Report
|
||||
description: >
|
||||
Scaffold, validate, preview, and manage Power BI PBIR report projects using
|
||||
pbi-cli. Invoke this skill whenever the user mentions "create report", "new
|
||||
report", "PBIR", "scaffold", "validate report", "report structure", "preview
|
||||
report", "report info", "reload Desktop", "convert report", ".pbip project",
|
||||
"report project", or wants to understand the PBIR folder format, set up a new
|
||||
report from scratch, or work with the report as a whole. For specific tasks,
|
||||
see also: power-bi-visuals (charts, binding), power-bi-pages (page management),
|
||||
power-bi-themes (themes, formatting), power-bi-filters (page/visual filters).
|
||||
tools: pbi-cli
|
||||
---
|
||||
|
||||
# Power BI Report Skill
|
||||
|
||||
Manage Power BI PBIR report projects at the top level -- scaffolding, validation,
|
||||
preview, and Desktop integration. No connection to Power BI Desktop is needed
|
||||
for most operations.
|
||||
|
||||
## PBIR Format
|
||||
|
||||
PBIR (Enhanced Report Format) stores reports as a folder of JSON files:
|
||||
|
||||
```
|
||||
MyReport.Report/
|
||||
definition.pbir # dataset reference
|
||||
definition/
|
||||
version.json # PBIR version
|
||||
report.json # report settings, theme
|
||||
pages/
|
||||
pages.json # page order
|
||||
page_abc123/
|
||||
page.json # page settings
|
||||
visuals/
|
||||
visual_def456/
|
||||
visual.json # visual type, position, bindings
|
||||
```
|
||||
|
||||
Each file has a public JSON schema from Microsoft for validation.
|
||||
PBIR is GA as of January 2026 and the default format in Desktop since March 2026.
|
||||
|
||||
## Creating a Report
|
||||
|
||||
```bash
|
||||
# Scaffold a new report project
|
||||
pbi report create ./MyProject --name "Sales Report"
|
||||
|
||||
# With dataset reference
|
||||
pbi report create ./MyProject --name "Sales" --dataset-path "../Sales.Dataset"
|
||||
```
|
||||
|
||||
This creates the full folder structure with `definition.pbir`, `report.json`,
|
||||
`version.json`, and an empty `pages/` directory.
|
||||
|
||||
## Report Info and Validation
|
||||
|
||||
```bash
|
||||
# Show report metadata summary (pages, theme, dataset)
|
||||
pbi report info
|
||||
pbi report info --path ./MyReport.Report
|
||||
|
||||
# Validate report structure and JSON files
|
||||
pbi report validate
|
||||
```
|
||||
|
||||
Validation checks:
|
||||
- Required files exist (`definition.pbir`, `report.json`, `version.json`)
|
||||
- All JSON files parse without errors
|
||||
- Schema URLs are present and consistent
|
||||
- Page references in `pages.json` match actual page folders
|
||||
|
||||
## Preview
|
||||
|
||||
Start a live HTML preview of the report layout:
|
||||
|
||||
```bash
|
||||
pbi report preview
|
||||
```
|
||||
|
||||
Opens a browser showing all pages with visual placeholders, types, positions,
|
||||
and data bindings. The preview auto-refreshes when files change.
|
||||
|
||||
Requires the `preview` optional dependency: `pip install pbi-cli-tool[preview]`
|
||||
|
||||
## Desktop Integration
|
||||
|
||||
```bash
|
||||
# Trigger Power BI Desktop to reload the current report
|
||||
pbi report reload
|
||||
```
|
||||
|
||||
Power BI Desktop's Developer Mode auto-detects TMDL changes but not PBIR
|
||||
changes. This command sends a keyboard shortcut to the Desktop window to
|
||||
trigger a reload. Requires the `reload` optional dependency: `pip install pbi-cli-tool[reload]`
|
||||
|
||||
## Convert
|
||||
|
||||
```bash
|
||||
# Convert a .Report folder into a distributable .pbip project
|
||||
pbi report convert ./MyReport.Report --output ./distributable/
|
||||
```
|
||||
|
||||
## Path Resolution
|
||||
|
||||
All report commands auto-detect the `.Report` folder:
|
||||
|
||||
1. Explicit: `pbi report --path ./MyReport.Report info`
|
||||
2. Auto-detect: walks up from CWD looking for `*.Report/definition/`
|
||||
3. From `.pbip`: finds sibling `.Report` folder from `.pbip` file
|
||||
|
||||
## Workflow: Build a Complete Report
|
||||
|
||||
This workflow uses commands from multiple skills:
|
||||
|
||||
```bash
|
||||
# 1. Scaffold report (this skill)
|
||||
pbi report create . --name "SalesDashboard" --dataset-path "../SalesModel.Dataset"
|
||||
|
||||
# 2. Add pages (power-bi-pages skill)
|
||||
pbi report add-page --display-name "Overview" --name overview
|
||||
pbi report add-page --display-name "Details" --name details
|
||||
|
||||
# 3. Add visuals (power-bi-visuals skill)
|
||||
pbi visual add --page overview --type card --name revenue_card
|
||||
pbi visual add --page overview --type bar --name sales_by_region
|
||||
|
||||
# 4. Bind data (power-bi-visuals skill)
|
||||
pbi visual bind revenue_card --page overview --field "Sales[Total Revenue]"
|
||||
pbi visual bind sales_by_region --page overview \
|
||||
--category "Geo[Region]" --value "Sales[Amount]"
|
||||
|
||||
# 5. Apply theme (power-bi-themes skill)
|
||||
pbi report set-theme --file brand-colors.json
|
||||
|
||||
# 6. Validate (this skill)
|
||||
pbi report validate
|
||||
```
|
||||
|
||||
## Combining Model and Report
|
||||
|
||||
pbi-cli covers both the semantic model layer and the report layer:
|
||||
|
||||
```bash
|
||||
# Model layer (requires pbi connect)
|
||||
pbi connect
|
||||
pbi measure create Sales "Total Revenue" "SUM(Sales[Amount])"
|
||||
|
||||
# Report layer (no connection needed)
|
||||
pbi report create . --name "Sales"
|
||||
pbi visual add --page overview --type card --name rev_card
|
||||
pbi visual bind rev_card --page overview --field "Sales[Total Revenue]"
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
|
||||
| Skill | When to use |
|
||||
|-------|-------------|
|
||||
| **power-bi-visuals** | Add, bind, update, delete visuals |
|
||||
| **power-bi-pages** | Add, remove, configure pages and bookmarks |
|
||||
| **power-bi-themes** | Themes, conditional formatting |
|
||||
| **power-bi-filters** | Page and visual filters |
|
||||
|
||||
## JSON Output
|
||||
|
||||
```bash
|
||||
pbi --json report info
|
||||
pbi --json report validate
|
||||
```
|
||||
137
src/pbi_cli/skills/power-bi-themes/SKILL.md
Normal file
137
src/pbi_cli/skills/power-bi-themes/SKILL.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
---
|
||||
name: Power BI Themes
|
||||
description: >
|
||||
Apply, inspect, and compare Power BI report themes and conditional formatting
|
||||
rules using pbi-cli. Invoke this skill whenever the user mentions "theme",
|
||||
"colours", "colors", "branding", "dark mode", "corporate theme", "styling",
|
||||
"conditional formatting", "colour scale", "gradient", "data bars",
|
||||
"background colour", "formatting rules", "visual formatting", or wants to
|
||||
change the overall look-and-feel of a report or apply data-driven formatting
|
||||
to specific visuals.
|
||||
tools: pbi-cli
|
||||
---
|
||||
|
||||
# Power BI Themes Skill
|
||||
|
||||
Manage report-wide themes and per-visual conditional formatting. No Power BI
|
||||
Desktop connection is needed.
|
||||
|
||||
## Applying a Theme
|
||||
|
||||
Power BI themes are JSON files that define colours, fonts, and visual defaults
|
||||
for the entire report. Apply one with:
|
||||
|
||||
```bash
|
||||
pbi report set-theme --file corporate-theme.json
|
||||
```
|
||||
|
||||
This copies the theme file into the report's `StaticResources/RegisteredResources/`
|
||||
folder and updates `report.json` to reference it. The theme takes effect when
|
||||
the report is opened in Power BI Desktop.
|
||||
|
||||
## Inspecting the Current Theme
|
||||
|
||||
```bash
|
||||
pbi report get-theme
|
||||
```
|
||||
|
||||
Returns:
|
||||
- `base_theme` -- the built-in theme name (e.g. `"CY24SU06"`)
|
||||
- `custom_theme` -- custom theme name if one is applied (or `null`)
|
||||
- `theme_data` -- full JSON of the custom theme file (if it exists)
|
||||
|
||||
## Comparing Themes
|
||||
|
||||
Before applying a new theme, preview what would change:
|
||||
|
||||
```bash
|
||||
pbi report diff-theme --file proposed-theme.json
|
||||
```
|
||||
|
||||
Returns:
|
||||
- `current` / `proposed` -- display names
|
||||
- `added` -- keys in proposed but not current
|
||||
- `removed` -- keys in current but not proposed
|
||||
- `changed` -- keys present in both but with different values
|
||||
|
||||
This helps catch unintended colour changes before committing.
|
||||
|
||||
## Theme JSON Structure
|
||||
|
||||
A Power BI theme JSON file typically contains:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Corporate Brand",
|
||||
"dataColors": ["#0078D4", "#00BCF2", "#FFB900", "#D83B01", "#8661C5", "#00B294"],
|
||||
"background": "#FFFFFF",
|
||||
"foreground": "#252423",
|
||||
"tableAccent": "#0078D4",
|
||||
"visualStyles": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Key sections:
|
||||
- `dataColors` -- palette for data series (6-12 colours recommended)
|
||||
- `background` / `foreground` -- page and text defaults
|
||||
- `tableAccent` -- header colour for tables and matrices
|
||||
- `visualStyles` -- per-visual-type overrides (font sizes, padding, etc.)
|
||||
|
||||
See [Microsoft theme documentation](https://learn.microsoft.com/power-bi/create-reports/desktop-report-themes) for the full schema.
|
||||
|
||||
## Conditional Formatting
|
||||
|
||||
Apply data-driven formatting to individual visuals:
|
||||
|
||||
```bash
|
||||
# Gradient background (colour scale from min to max)
|
||||
pbi format background-gradient visual_abc --page page1 \
|
||||
--table Sales --column Revenue \
|
||||
--min-color "#FFFFFF" --max-color "#0078D4"
|
||||
|
||||
# Rules-based background (specific value triggers a colour)
|
||||
pbi format background-conditional visual_abc --page page1 \
|
||||
--table Sales --column Status --value "Critical" --color "#FF0000"
|
||||
|
||||
# Measure-driven background (a DAX measure returns the colour)
|
||||
pbi format background-measure visual_abc --page page1 \
|
||||
--table Sales --measure "Status Color"
|
||||
|
||||
# Inspect current formatting rules
|
||||
pbi format get visual_abc --page page1
|
||||
|
||||
# Clear all formatting rules on a visual
|
||||
pbi format clear visual_abc --page page1
|
||||
```
|
||||
|
||||
## Workflow: Brand a Report
|
||||
|
||||
```bash
|
||||
# 1. Create the theme file
|
||||
cat > brand-theme.json << 'EOF'
|
||||
{
|
||||
"name": "Acme Corp",
|
||||
"dataColors": ["#1B365D", "#5B8DB8", "#E87722", "#00A3E0", "#6D2077", "#43B02A"],
|
||||
"background": "#F8F8F8",
|
||||
"foreground": "#1B365D",
|
||||
"tableAccent": "#1B365D"
|
||||
}
|
||||
EOF
|
||||
|
||||
# 2. Preview the diff against the current theme
|
||||
pbi report diff-theme --file brand-theme.json
|
||||
|
||||
# 3. Apply it
|
||||
pbi report set-theme --file brand-theme.json
|
||||
|
||||
# 4. Verify
|
||||
pbi report get-theme
|
||||
```
|
||||
|
||||
## JSON Output
|
||||
|
||||
```bash
|
||||
pbi --json report get-theme
|
||||
pbi --json report diff-theme --file proposed.json
|
||||
pbi --json format get vis1 --page p1
|
||||
```
|
||||
223
src/pbi_cli/skills/power-bi-visuals/SKILL.md
Normal file
223
src/pbi_cli/skills/power-bi-visuals/SKILL.md
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
---
|
||||
name: Power BI Visuals
|
||||
description: >
|
||||
Add, configure, bind data to, and bulk-manage visuals on Power BI PBIR report
|
||||
pages using pbi-cli. Invoke this skill whenever the user mentions "add a chart",
|
||||
"bar chart", "line chart", "card", "KPI", "gauge", "scatter", "table visual",
|
||||
"matrix", "slicer", "combo chart", "bind data", "visual type", "visual layout",
|
||||
"resize visuals", "bulk update visuals", "bulk delete", "visual calculations",
|
||||
or wants to place, move, bind, or remove any visual on a report page. Also invoke
|
||||
when the user asks what visual types are supported or how to connect a visual to
|
||||
their data model.
|
||||
tools: pbi-cli
|
||||
---
|
||||
|
||||
# Power BI Visuals Skill
|
||||
|
||||
Create and manage visuals on PBIR report pages. No Power BI Desktop connection
|
||||
is needed -- these commands operate directly on JSON files.
|
||||
|
||||
## Adding Visuals
|
||||
|
||||
```bash
|
||||
# Add by alias (pbi-cli resolves to the PBIR type)
|
||||
pbi visual add --page page_abc123 --type bar
|
||||
pbi visual add --page page_abc123 --type card --name "Revenue Card"
|
||||
|
||||
# Custom position and size (pixels)
|
||||
pbi visual add --page page_abc123 --type scatter \
|
||||
--x 50 --y 400 --width 600 --height 350
|
||||
|
||||
# Named visual for easy reference
|
||||
pbi visual add --page page_abc123 --type combo --name sales_combo
|
||||
```
|
||||
|
||||
Each visual is created as a folder with a `visual.json` file inside the page's
|
||||
`visuals/` directory. The template includes the correct schema URL and queryState
|
||||
roles for the chosen type.
|
||||
|
||||
## Binding Data
|
||||
|
||||
Visuals start empty. Use `visual bind` with `Table[Column]` notation to connect
|
||||
them to your semantic model. The bind options vary by visual type -- see the
|
||||
type table below.
|
||||
|
||||
```bash
|
||||
# Bar chart: category axis + value
|
||||
pbi visual bind mybar --page p1 \
|
||||
--category "Geography[Region]" --value "Sales[Revenue]"
|
||||
|
||||
# Card: single field
|
||||
pbi visual bind mycard --page p1 --field "Sales[Total Revenue]"
|
||||
|
||||
# Matrix: rows + values + optional column
|
||||
pbi visual bind mymatrix --page p1 \
|
||||
--row "Product[Category]" --value "Sales[Amount]" --value "Sales[Quantity]"
|
||||
|
||||
# Scatter: X, Y, detail, optional size and legend
|
||||
pbi visual bind myscatter --page p1 \
|
||||
--x "Sales[Quantity]" --y "Sales[Revenue]" --detail "Product[Name]"
|
||||
|
||||
# Combo chart: category + column series + line series
|
||||
pbi visual bind mycombo --page p1 \
|
||||
--category "Calendar[Month]" --column "Sales[Revenue]" --line "Sales[Margin]"
|
||||
|
||||
# KPI: indicator + goal + trend axis
|
||||
pbi visual bind mykpi --page p1 \
|
||||
--indicator "Sales[Revenue]" --goal "Sales[Target]" --trend "Calendar[Date]"
|
||||
|
||||
# Gauge: value + max/target
|
||||
pbi visual bind mygauge --page p1 \
|
||||
--value "Sales[Revenue]" --max "Sales[Target]"
|
||||
```
|
||||
|
||||
Binding uses ROLE_ALIASES to translate friendly names like `--value` into the PBIR
|
||||
role name (e.g. `Y`, `Values`, `Data`). Measure vs Column is inferred from the role:
|
||||
value/indicator/goal/max roles create Measure references, category/row/detail roles
|
||||
create Column references. Override with `--measure` flag if needed.
|
||||
|
||||
## Inspecting and Updating
|
||||
|
||||
```bash
|
||||
# List all visuals on a page
|
||||
pbi visual list --page page_abc123
|
||||
|
||||
# Get full details of one visual
|
||||
pbi visual get visual_def456 --page page_abc123
|
||||
|
||||
# Move, resize, or toggle visibility
|
||||
pbi visual update vis1 --page p1 --width 600 --height 400
|
||||
pbi visual update vis1 --page p1 --x 100 --y 200
|
||||
pbi visual update vis1 --page p1 --hidden
|
||||
pbi visual update vis1 --page p1 --visible
|
||||
|
||||
# Delete a visual
|
||||
pbi visual delete visual_def456 --page page_abc123
|
||||
```
|
||||
|
||||
## Container Properties
|
||||
|
||||
Set border, background, or title on the visual container itself:
|
||||
|
||||
```bash
|
||||
pbi visual set-container vis1 --page p1 --background "#F0F0F0"
|
||||
pbi visual set-container vis1 --page p1 --border-color "#CCCCCC" --border-width 2
|
||||
pbi visual set-container vis1 --page p1 --title "Sales by Region"
|
||||
```
|
||||
|
||||
## Visual Calculations
|
||||
|
||||
Add DAX calculations that run inside the visual scope:
|
||||
|
||||
```bash
|
||||
pbi visual calc-add vis1 --page p1 --role Values \
|
||||
--name "RunningTotal" --expression "RUNNINGSUM([Revenue])"
|
||||
|
||||
pbi visual calc-list vis1 --page p1
|
||||
pbi visual calc-delete vis1 --page p1 --name "RunningTotal"
|
||||
```
|
||||
|
||||
## Bulk Operations
|
||||
|
||||
Operate on many visuals at once by filtering with `--type` or `--name-pattern`:
|
||||
|
||||
```bash
|
||||
# Find visuals matching criteria
|
||||
pbi visual where --page overview --type barChart
|
||||
pbi visual where --page overview --type kpi --y-min 300
|
||||
|
||||
# Bind the same field to ALL bar charts on a page
|
||||
pbi visual bulk-bind --page overview --type barChart \
|
||||
--category "Date[Month]" --value "Sales[Revenue]"
|
||||
|
||||
# Resize all KPI cards
|
||||
pbi visual bulk-update --page overview --type kpi --width 250 --height 120
|
||||
|
||||
# Hide all visuals matching a pattern
|
||||
pbi visual bulk-update --page overview --name-pattern "Temp_*" --hidden
|
||||
|
||||
# Delete all placeholders
|
||||
pbi visual bulk-delete --page overview --name-pattern "Placeholder_*"
|
||||
```
|
||||
|
||||
Filter options for `where`, `bulk-bind`, `bulk-update`, `bulk-delete`:
|
||||
- `--type` -- PBIR visual type or alias (e.g. `barChart`, `bar`)
|
||||
- `--name-pattern` -- fnmatch glob on visual name (e.g. `Chart_*`)
|
||||
- `--x-min`, `--x-max`, `--y-min`, `--y-max` -- position bounds (pixels)
|
||||
|
||||
All bulk commands require at least `--type` or `--name-pattern` to prevent
|
||||
accidental mass operations.
|
||||
|
||||
## Supported Visual Types (32)
|
||||
|
||||
### Charts
|
||||
|
||||
| Alias | PBIR Type | Bind Options |
|
||||
|--------------------|------------------------------|-----------------------------------------------|
|
||||
| bar | barChart | --category, --value, --legend |
|
||||
| line | lineChart | --category, --value, --legend |
|
||||
| column | columnChart | --category, --value, --legend |
|
||||
| area | areaChart | --category, --value, --legend |
|
||||
| ribbon | ribbonChart | --category, --value, --legend |
|
||||
| waterfall | waterfallChart | --category, --value, --breakdown |
|
||||
| stacked_bar | stackedBarChart | --category, --value, --legend |
|
||||
| clustered_bar | clusteredBarChart | --category, --value, --legend |
|
||||
| clustered_column | clusteredColumnChart | --category, --value, --legend |
|
||||
| scatter | scatterChart | --x, --y, --detail, --size, --legend |
|
||||
| funnel | funnelChart | --category, --value |
|
||||
| combo | lineStackedColumnComboChart | --category, --column, --line, --legend |
|
||||
| donut / pie | donutChart | --category, --value, --legend |
|
||||
| treemap | treemap | --category, --value |
|
||||
|
||||
### Cards and KPIs
|
||||
|
||||
| Alias | PBIR Type | Bind Options |
|
||||
|--------------------|------------------------------|-----------------------------------------------|
|
||||
| card | card | --field |
|
||||
| card_visual | cardVisual | --field (modern card) |
|
||||
| card_new | cardNew | --field |
|
||||
| multi_row_card | multiRowCard | --field |
|
||||
| kpi | kpi | --indicator, --goal, --trend |
|
||||
| gauge | gauge | --value, --max / --target |
|
||||
|
||||
### Tables
|
||||
|
||||
| Alias | PBIR Type | Bind Options |
|
||||
|--------------------|------------------------------|-----------------------------------------------|
|
||||
| table | tableEx | --value |
|
||||
| matrix | pivotTable | --row, --value, --column |
|
||||
|
||||
### Slicers
|
||||
|
||||
| Alias | PBIR Type | Bind Options |
|
||||
|--------------------|------------------------------|-----------------------------------------------|
|
||||
| slicer | slicer | --field |
|
||||
| text_slicer | textSlicer | --field |
|
||||
| list_slicer | listSlicer | --field |
|
||||
| advanced_slicer | advancedSlicerVisual | --field (tile/image slicer) |
|
||||
|
||||
### Maps
|
||||
|
||||
| Alias | PBIR Type | Bind Options |
|
||||
|--------------------|------------------------------|-----------------------------------------------|
|
||||
| azure_map / map | azureMap | --category, --size |
|
||||
|
||||
### Decorative and Navigation
|
||||
|
||||
| Alias | PBIR Type | Bind Options |
|
||||
|--------------------|------------------------------|-----------------------------------------------|
|
||||
| action_button | actionButton | (no data binding) |
|
||||
| image | image | (no data binding) |
|
||||
| shape | shape | (no data binding) |
|
||||
| textbox | textbox | (no data binding) |
|
||||
| page_navigator | pageNavigator | (no data binding) |
|
||||
|
||||
## JSON Output
|
||||
|
||||
All commands support `--json` for agent consumption:
|
||||
|
||||
```bash
|
||||
pbi --json visual list --page overview
|
||||
pbi --json visual get vis1 --page overview
|
||||
pbi --json visual where --page overview --type barChart
|
||||
```
|
||||
|
|
@ -844,7 +844,7 @@ class TestThemeSet:
|
|||
None,
|
||||
)
|
||||
assert reg is not None
|
||||
items = reg.get("resourcePackage", {}).get("items", [])
|
||||
items = reg.get("items", [])
|
||||
assert any(i["name"] == "Ocean.json" for i in items)
|
||||
|
||||
def test_theme_set_idempotent_for_same_theme(
|
||||
|
|
@ -857,7 +857,7 @@ class TestThemeSet:
|
|||
report_data = _read(sample_report / "report.json")
|
||||
packages: list[dict[str, Any]] = report_data.get("resourcePackages", [])
|
||||
reg = next(p for p in packages if p.get("name") == "RegisteredResources")
|
||||
items = reg.get("resourcePackage", {}).get("items", [])
|
||||
items = reg.get("items", [])
|
||||
names = [i["name"] for i in items]
|
||||
# No duplicate entries for the same file
|
||||
assert names.count("Stable.json") == 1
|
||||
|
|
|
|||
109
tests/test_skill_triggering.py
Normal file
109
tests/test_skill_triggering.py
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
"""Skill triggering evaluation -- verify prompts match expected skills.
|
||||
|
||||
This is NOT a pytest test. Run directly:
|
||||
python tests/test_skill_triggering.py
|
||||
|
||||
Uses keyword-based scoring to simulate which skill description best matches
|
||||
each user prompt, without requiring an LLM call.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.resources
|
||||
import re
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def _load_skills() -> dict[str, str]:
|
||||
"""Load all skill names and descriptions from bundled skills."""
|
||||
skills_pkg = importlib.resources.files("pbi_cli.skills")
|
||||
skills: dict[str, str] = {}
|
||||
for item in skills_pkg.iterdir():
|
||||
if item.is_dir() and (item / "SKILL.md").is_file():
|
||||
content = (item / "SKILL.md").read_text(encoding="utf-8")
|
||||
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
||||
if match:
|
||||
fm = yaml.safe_load(match.group(1))
|
||||
skills[item.name] = fm.get("description", "").lower()
|
||||
return skills
|
||||
|
||||
|
||||
def _score_prompt(prompt: str, description: str) -> int:
|
||||
"""Score how well a prompt matches a skill description using word overlap."""
|
||||
prompt_words = set(re.findall(r"[a-z]+", prompt.lower()))
|
||||
desc_words = set(re.findall(r"[a-z]+", description))
|
||||
# Weight longer matching words higher (domain terms matter more)
|
||||
score = 0
|
||||
for word in prompt_words & desc_words:
|
||||
if len(word) >= 5:
|
||||
score += 3
|
||||
elif len(word) >= 3:
|
||||
score += 1
|
||||
return score
|
||||
|
||||
|
||||
def _find_best_skill(prompt: str, skills: dict[str, str]) -> str:
|
||||
"""Find the skill with the highest keyword overlap score."""
|
||||
scores = {name: _score_prompt(prompt, desc) for name, desc in skills.items()}
|
||||
return max(scores, key=lambda k: scores[k])
|
||||
|
||||
|
||||
# Test cases: (prompt, expected_skill)
|
||||
TEST_CASES: list[tuple[str, str]] = [
|
||||
# power-bi-visuals
|
||||
("Add a bar chart to the overview page showing sales by region", "power-bi-visuals"),
|
||||
("I need to bind Sales[Revenue] to the value field on my KPI visual", "power-bi-visuals"),
|
||||
("What visual types does pbi-cli support? I need a scatter plot", "power-bi-visuals"),
|
||||
("Resize all the card visuals on the dashboard page to 200x120", "power-bi-visuals"),
|
||||
# power-bi-pages
|
||||
("Add a new page called Regional Detail to my report", "power-bi-pages"),
|
||||
("Hide the drillthrough page from the navigation bar", "power-bi-pages"),
|
||||
("Create a bookmark for the current executive view", "power-bi-pages"),
|
||||
# power-bi-themes
|
||||
("Apply our corporate brand colours to the entire report", "power-bi-themes"),
|
||||
(
|
||||
"I want conditional formatting on the revenue column green for high red for low",
|
||||
"power-bi-themes",
|
||||
),
|
||||
("Compare this new theme JSON against what is currently applied", "power-bi-themes"),
|
||||
# power-bi-filters
|
||||
("Filter the overview page to show only the top 10 products by revenue", "power-bi-filters"),
|
||||
("Add a date filter for the last 30 days on the Sales page", "power-bi-filters"),
|
||||
("What filters are currently on my dashboard page", "power-bi-filters"),
|
||||
# power-bi-report
|
||||
("Create a new PBIR report project for our sales dashboard", "power-bi-report"),
|
||||
("Validate the report structure to make sure everything is correct", "power-bi-report"),
|
||||
("Start the preview server so I can see the layout", "power-bi-report"),
|
||||
# Should NOT trigger report skills
|
||||
("Create a measure called Total Revenue equals SUM of Sales Amount", "power-bi-modeling"),
|
||||
("Export the semantic model to TMDL for version control", "power-bi-deployment"),
|
||||
("Set up row-level security for regional managers", "power-bi-security"),
|
||||
]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
skills = _load_skills()
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
print(f"Testing {len(TEST_CASES)} prompts against {len(skills)} skills\n")
|
||||
print(f"{'#':<3} {'Result':<6} {'Expected':<22} {'Got':<22} Prompt")
|
||||
print("-" * 100)
|
||||
|
||||
for i, (prompt, expected) in enumerate(TEST_CASES, 1):
|
||||
got = _find_best_skill(prompt, skills)
|
||||
ok = got == expected
|
||||
status = "PASS" if ok else "FAIL"
|
||||
if ok:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
short_prompt = prompt[:45] + "..." if len(prompt) > 45 else prompt
|
||||
print(f"{i:<3} {status:<6} {expected:<22} {got:<22} {short_prompt}")
|
||||
|
||||
print(f"\n{passed}/{len(TEST_CASES)} passed, {failed} failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in a new issue