Install
$ agentstack add skill-kpbray-power-bi-agent-skills-best-practices ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Best Practices Skill
This skill helps apply Power BI best practices, Best Practice Analyzer (BPA) rules, and enterprise standards to semantic models and reports.
When to Use This Skill
- Validating model quality against BPA rules
- Applying naming conventions
- Optimizing model performance
- Adding documentation and descriptions
- Enforcing enterprise governance patterns
- Reviewing DAX measures for quality
Best Practice Analyzer (BPA) Overview
BPA rules are organized into categories:
- Performance - Optimization and efficiency rules
- DAX Expressions - Measure and calculation quality
- Formatting - Naming and organization standards
- Maintenance - Housekeeping and cleanup rules
- Error Prevention - Validation and safety rules
Quick Validation Checklist
Model Structure
- [ ] All tables have descriptions
- [ ] Relationships are one-to-many (no bi-directional unless required)
- [ ] Key columns are marked as hidden
- [ ] Date table is marked as date table
- [ ] No unused columns or tables
DAX Measures
- [ ] All measures have descriptions (/// comments)
- [ ] DIVIDE() used instead of
/operator - [ ] Variables used for repeated expressions
- [ ] Measures organized in display folders
- [ ] No implicit measures (summarizations)
Naming Conventions
- [ ] Tables: PascalCase, singular nouns
- [ ] Columns: PascalCase with spaces allowed
- [ ] Measures: Business-friendly names
- [ ] No special characters or reserved words
Performance
- [ ] No calculated columns where measures work
- [ ] Integer keys instead of string keys
- [ ] Narrow tables preferred (fewer columns)
- [ ] Appropriate data types used
Performance Rules
HIGH IMPACT
Avoid bi-directional relationships
Bi-directional relationships cause performance overhead and can create ambiguity.
Instead of:
relationship {{guid}}
fromColumn: TableA.'Key'
toColumn: TableB.'Key'
crossFilteringBehavior: bothDirections
Do this:
relationship {{guid}}
fromColumn: TableA.'Key'
toColumn: TableB.'Key'
// Use DAX CROSSFILTER() in measures when needed
measure 'Filtered Value' =
CALCULATE(
[Base Measure],
CROSSFILTER(TableA[Key], TableB[Key], Both)
)
Use integer keys instead of strings
Integer comparisons are faster than string comparisons.
Instead of:
column 'Product Key'
dataType: string
Do this:
column 'Product Key'
dataType: int64
Minimize calculated columns
Calculated columns consume memory and slow refresh. Use measures when possible.
Instead of calculated column:
column 'Profit Margin' =
DIVIDE(Sales[Profit], Sales[Revenue], 0)
Use a measure:
measure 'Profit Margin' =
DIVIDE(SUM(Sales[Profit]), SUM(Sales[Revenue]), 0)
MEDIUM IMPACT
Remove unused columns
Unused columns waste memory. Delete columns not used in:
- Relationships
- Measures
- Visuals
- Filters
Avoid high cardinality columns
Columns with many unique values increase model size. Consider:
- Grouping/binning high cardinality data
- Moving detail columns to a separate table
- Using calculated columns only when necessary
Use appropriate data types
| Data | Recommended Type | |------|------------------| | IDs, Keys | int64 | | Flags | boolean | | Currency | decimal | | Percentages | double or calculated in DAX | | Dates | dateTime |
DAX Rules
REQUIRED
Use DIVIDE instead of /
Bad:
Margin = Sales[Profit] / Sales[Revenue]
Good:
Margin = DIVIDE(Sales[Profit], Sales[Revenue], 0)
Use VAR for repeated expressions
Bad:
Growth % =
(SUM(Sales[Amount]) - CALCULATE(SUM(Sales[Amount]), SAMEPERIODLASTYEAR(Date[Date])))
/ CALCULATE(SUM(Sales[Amount]), SAMEPERIODLASTYEAR(Date[Date]))
Good:
Growth % =
VAR CurrentSales = SUM(Sales[Amount])
VAR PriorSales = CALCULATE(SUM(Sales[Amount]), SAMEPERIODLASTYEAR(Date[Date]))
RETURN
DIVIDE(CurrentSales - PriorSales, PriorSales)
Add descriptions to measures
/// Calculates total sales revenue
/// Excludes returns and cancellations
measure 'Total Sales' =
SUM(Sales[Amount])
RECOMMENDED
Avoid IFERROR with aggregations
IFERROR masks data quality issues.
Bad:
Total = IFERROR(SUM(Sales[Amount]), 0)
Good:
Total = SUM(Sales[Amount])
// Handle blanks explicitly if needed
Total = COALESCE(SUM(Sales[Amount]), 0)
Use REMOVEFILTERS instead of ALL
REMOVEFILTERS is more explicit about intent.
Prefer:
All Sales = CALCULATE([Total Sales], REMOVEFILTERS(Products))
Avoid nested CALCULATE
Deeply nested CALCULATE is hard to maintain.
Bad:
Measure =
CALCULATE(
CALCULATE(
CALCULATE([Base], Filter1),
Filter2
),
Filter3
)
Good:
Measure =
CALCULATE(
[Base],
Filter1,
Filter2,
Filter3
)
Formatting Rules
Table Naming
| Pattern | Example | Use | |---------|---------|-----| | PascalCase | SalesOrders | Standard tables | | Singular | Customer not Customers | Dimension tables | | Prefix dim/fact | dimCustomer, factSales | Optional, for clarity |
Column Naming
| Pattern | Example | |---------|---------| | PascalCase with spaces | Customer Name | | Keys end with Key | Product Key | | IDs end with ID | Transaction ID | | Dates end with Date | Order Date |
Measure Naming
| Category | Pattern | Example | |----------|---------|---------| | Aggregations | Noun phrase | Total Sales | | Percentages | End with % | Gross Margin % | | Ratios | Include units | Sales per Customer | | Time Intelligence | Include period | Sales YTD, Sales PY | | Counts | End with Count | Customer Count |
Display Folders
Organize measures into logical folders:
├── Core Metrics
│ ├── Total Sales
│ ├── Total Cost
│ └── Total Profit
├── Time Intelligence
│ ├── Sales YTD
│ ├── Sales PY
│ └── Sales YoY %
├── Percentages
│ ├── Gross Margin %
│ └── % of Total
└── KPIs
├── Target
└── Achievement
Maintenance Rules
Remove unused objects
Delete these if not used:
- Tables with no relationships or references
- Columns not in visuals, measures, or relationships
- Measures not in visuals or other measures
- Hidden columns not used in calculations
Document everything
Every object should have a description:
/// Customer dimension table
/// Contains customer demographics and segmentation
table Customers
lineageTag: ...
/// Unique identifier for each customer
column 'Customer ID'
...
/// Customer's full name (First + Last)
column 'Customer Name'
...
Use consistent formatting
- TMDL: Use tabs for indentation
- DAX: Use 4 spaces for indentation
- Follow SQLBI DAX formatting conventions
Error Prevention Rules
Validate relationships
Before creating relationships, verify:
- "To" column contains unique values
- Data types match exactly
- Orphan handling is considered
- Filter direction is appropriate
Validate measure references
Before using measures in visuals:
- Test with simple filters
- Verify time intelligence with date table
- Check for circular references
- Validate with EVALUATE in DAX Studio
Test edge cases
Consider these scenarios:
- Empty filter context (grand total)
- Single value context (card visual)
- Multiple selections
- No data (blanks)
Enterprise Governance Patterns
Row-Level Security (RLS)
Define RLS roles in TMDL:
role SalesRep
modelPermission: read
tablePermission Sales = 'Sales'[Sales Rep] = USERPRINCIPALNAME()
Data Sensitivity
Mark sensitive columns:
column 'SSN'
dataType: string
isHidden
// Consider not including in model
Audit Trail
Document model changes:
- Use Git commit messages
- Add change log in model description
- Track measure versions with comments
Data Source Governance
Credential Security
NEVER store credentials in source files:
/// BAD: Hardcoded connection string
expression ConnectionString = "Server=prod.database.com;User=admin;Password=secret123"
/// GOOD: Use parameters without credentials
expression ServerName = "prod.database.com" meta [IsParameterQuery=true, Type="Text"]
Credentials should be:
- Configured in Power BI Service after publish
- Managed via gateway for on-premises sources
- Stored in Azure Key Vault for automated scenarios
Connection Parameters
Use parameters for environment-specific values:
/// Server name parameter (update per environment)
expression ServerName = "dev-server.database.windows.net" meta [IsParameterQuery=true, Type="Text", IsParameterQueryRequired=true]
/// Database name parameter
expression DatabaseName = "SalesDB_Dev" meta [IsParameterQuery=true, Type="Text", IsParameterQueryRequired=true]
Gateway Configuration
For on-premises or VNet data sources:
| Source Location | Gateway Required | |-----------------|------------------| | Cloud (Azure SQL, SharePoint Online) | No | | On-premises (SQL Server, file share) | Yes | | VNet (private endpoints) | Yes (VNet gateway) | | Local files (CSV, Excel) | Yes or use cloud storage |
Refresh Considerations
Import Mode:
- Data copied into model at refresh
- Schedule refreshes in Power BI Service
- Consider incremental refresh for large tables
DirectQuery:
- Live queries to source
- No refresh needed
- Source must be always available
- Performance dependent on source
Hybrid (Composite):
- Mix of Import and DirectQuery
- Use Import for dimensions, DirectQuery for facts
- Requires careful relationship configuration
Incremental Refresh Setup
- Create required parameters in Power Query:
RangeStart = #datetime(2020, 1, 1, 0, 0, 0) meta [IsParameterQuery=true, Type="DateTime"]
RangeEnd = #datetime(2025, 12, 31, 23, 59, 59) meta [IsParameterQuery=true, Type="DateTime"]
- Filter query with these parameters:
Table.SelectRows(Source, each [Date] >= RangeStart and [Date] "View Native Query"
## Validation Workflow
1. **After creating tables**
- Verify naming conventions
- Check data types
- Add descriptions
2. **After adding relationships**
- Confirm cardinality
- Verify no bi-directional unless needed
- Check filter flow
3. **After writing measures**
- Run BPA checks
- Verify DIVIDE usage
- Add descriptions
- Test calculations
4. **Before deployment**
- Remove unused objects
- Verify all descriptions
- Check performance
- Test with sample data
## Integration with Other Skills
After using other skills, apply best practices:
| Skill | Validation Focus |
|-------|------------------|
| `pbip-project` | File structure, encoding |
| `semantic-model` | Relationships, data types |
| `dax` | Measure quality, formatting |
| `report-visuals` | Visual references, performance |
| `power-query` | Query folding, data source parameters |
| `themes` | Accessibility, color contrast |
| `calculation-groups` | Appropriate use, format strings |
| `security` | RLS filter efficiency, complete coverage |
| `deployment` | BPA in CI/CD, secret management |
## BPA Rule Reference
See [bpa-rules.md](references/bpa-rules.md) for complete rule list.
## Naming Convention Reference
See [naming-conventions.md](references/naming-conventions.md) for detailed naming guidelines.
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [kpbray](https://github.com/kpbray)
- **Source:** [kpbray/power-bi-agent-skills](https://github.com/kpbray/power-bi-agent-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.