@luquimbo/bi-superpowers 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.claude-plugin/marketplace.json +46 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.mcp.json +4 -0
  4. package/AGENTS.md +34 -7
  5. package/README.md +139 -118
  6. package/bin/cli.js +45 -31
  7. package/bin/commands/install.js +321 -0
  8. package/bin/lib/microsoft-mcp.js +8 -0
  9. package/bin/lib/microsoft-mcp.test.js +5 -0
  10. package/package.json +1 -1
  11. package/skills/contributions/SKILL.md +1 -1
  12. package/skills/data-model-design/SKILL.md +1 -1
  13. package/skills/data-modeling/SKILL.md +82 -56
  14. package/skills/data-quality/SKILL.md +1 -1
  15. package/skills/dax/SKILL.md +74 -36
  16. package/skills/dax-doctor/SKILL.md +1 -1
  17. package/skills/dax-udf/SKILL.md +1 -1
  18. package/skills/deployment/SKILL.md +1 -1
  19. package/skills/excel-formulas/SKILL.md +1 -1
  20. package/skills/fabric-scripts/SKILL.md +1 -1
  21. package/skills/fast-standard/SKILL.md +1 -1
  22. package/skills/governance/SKILL.md +103 -50
  23. package/skills/migration-assistant/SKILL.md +1 -1
  24. package/skills/model-documenter/SKILL.md +1 -1
  25. package/skills/pbi-connect/SKILL.md +1 -1
  26. package/skills/power-query/SKILL.md +1 -1
  27. package/skills/project-kickoff/SKILL.md +1 -1
  28. package/skills/query-performance/SKILL.md +1 -1
  29. package/skills/report-design/SKILL.md +1 -1
  30. package/skills/report-layout/SKILL.md +1 -1
  31. package/skills/rls-design/SKILL.md +1 -1
  32. package/skills/semantic-model/SKILL.md +1 -1
  33. package/skills/testing-validation/SKILL.md +1 -1
  34. package/skills/theme-tweaker/SKILL.md +1 -1
  35. package/src/content/mcp-requirements.json +13 -0
  36. package/src/content/skills/data-modeling.md +81 -55
  37. package/src/content/skills/dax.md +73 -35
  38. package/src/content/skills/governance.md +102 -49
@@ -22,56 +22,60 @@ Best practices for designing semantic models in Power BI.
22
22
 
23
23
  ### Fact Tables
24
24
  - Contain measurable events/transactions
25
+ - **Plural names** (multiple events): `Sales`, `Orders`, `Transactions`
26
+ - **No `Fact_` prefix** — that's developer jargon, users see this in the model
25
27
  - Granularity: one row per event/transaction
26
- - Include foreign keys to dimension tables
27
- - Include numeric measures (Amount, Quantity, etc.)
28
+ - Include foreign keys to dimension tables (hidden from report view)
29
+ - Hide raw numeric columns; expose them through explicit measures
28
30
 
29
31
  ```
30
- Fact_Sales
31
- ├── SalesID (PK)
32
- ├── DateKey (FK)
33
- ├── ProductKey (FK)
34
- ├── CustomerKey (FK)
35
- ├── StoreKey (FK)
36
- ├── Quantity
37
- ├── UnitPrice
38
- └── TotalAmount
32
+ Sales ← plural, business name, no prefix
33
+ ├── SalesKey (PK, hidden)
34
+ ├── DateKey (FK, hidden)
35
+ ├── ProductKey (FK, hidden)
36
+ ├── CustomerKey (FK, hidden)
37
+ ├── StoreKey (FK, hidden)
38
+ ├── Quantity (hidden, exposed via [Quantity Sold] measure)
39
+ ├── Unit Price (hidden, used in measures)
40
+ └── Sales Amount (hidden, used in [Sales] measure)
39
41
  ```
40
42
 
41
43
  ### Dimension Tables
42
44
  - Contain descriptive attributes
43
- - Include surrogate key (integer PK)
45
+ - **Singular names** (describes one entity): `Customer`, `Product`, `Date`
46
+ - **No `Dim_` prefix** — users see this name
47
+ - Include surrogate key (integer PK, hidden)
44
48
  - Denormalized for simplicity
45
49
  - Include business key for lookups
46
50
 
47
51
  ```
48
- Dim_Product
49
- ├── ProductKey (PK, surrogate)
50
- ├── ProductID (business key)
51
- ├── ProductName
52
+ Product ← singular, business name, no prefix
53
+ ├── ProductKey (PK, surrogate, hidden)
54
+ ├── Product ID (business key, visible)
55
+ ├── Product Name
52
56
  ├── Category
53
57
  ├── Subcategory
54
58
  ├── Brand
55
- └── UnitCost
59
+ └── Unit Cost
56
60
  ```
57
61
 
58
62
  ### Date Dimension
59
- Essential for time intelligence. Mark as Date Table in Power BI.
63
+ Essential for time intelligence. Mark as Date Table in Power BI. Singular: `Date`, never `Dates` or `Dim_Date`.
60
64
 
61
65
  ```
62
- Dim_Date
63
- ├── DateKey (PK, YYYYMMDD integer)
66
+ Date ← singular, no prefix
67
+ ├── DateKey (PK, YYYYMMDD integer, hidden)
64
68
  ├── Date (actual date)
65
69
  ├── Year
66
70
  ├── Quarter
67
71
  ├── Month
68
- ├── MonthName
72
+ ├── Month Name
69
73
  ├── Week
70
- ├── DayOfWeek
71
- ├── DayName
72
- ├── IsWeekend
73
- ├── IsHoliday
74
- └── FiscalYear (if different from calendar)
74
+ ├── Day of Week
75
+ ├── Day Name
76
+ ├── Is Weekend
77
+ ├── Is Holiday
78
+ └── Fiscal Year (if different from calendar)
75
79
  ```
76
80
 
77
81
  ## Relationship Best Practices
@@ -117,55 +121,62 @@ Option 2: Duplicate dimension tables (clearer but redundant)
117
121
 
118
122
  ## Naming Conventions
119
123
 
120
- | Element | Convention | Example |
121
- |---------|------------|---------|
122
- | Fact tables | `Fact_` prefix | `Fact_Sales`, `Fact_Inventory` |
123
- | Dimension tables | `Dim_` prefix | `Dim_Product`, `Dim_Customer` |
124
- | Bridge tables | `Bridge_` prefix | `Bridge_CustomerProduct` |
125
- | Foreign keys | Match dimension key name | `ProductKey`, `CustomerKey` |
126
- | Measures | Business-friendly names | `Total Sales`, `Avg Order Value` |
124
+ **Core principle:** user-visible names speak business language, technical elements stay hidden. No `dim`, `fact`, `tbl` prefixes — those leak SQL jargon into the user experience.
125
+
126
+ | Element | Convention | Example | Anti-Pattern |
127
+ |---------|------------|---------|--------------|
128
+ | Fact tables | Plural, business name | `Sales`, `Orders`, `Transactions` | `Fact_Sales`, `fct_sales` |
129
+ | Dimension tables | Singular, business name | `Customer`, `Product`, `Date` | `Dim_Customer`, `dim_customers` |
130
+ | Bridge tables | `Bridge` prefix | `Bridge Customer Region` | `BridgeCustomerRegion` |
131
+ | Foreign keys | `[Entity]Key`, hidden | `ProductKey`, `CustomerKey` | `prod_id`, `FK_Product` |
132
+ | Surrogate PK | `[Entity]Key`, hidden | `CustomerKey` | `ID`, `pk_customer` |
133
+ | Business key | `[Entity] ID` or `Code` | `Customer ID`, `Product Code` | `BusinessKey` |
134
+ | Attribute columns | Natural language | `First Name`, `Order Date` | `FrstNm`, `OrdDt` |
135
+ | Measures | Auto-explicative, no prefix | `Sales`, `Margin %`, `Sales YTD` | `Total Sales`, `Sum of Sales` |
136
+
137
+ See `/governance` for the complete naming guide.
127
138
 
128
139
  ## Common Patterns
129
140
 
130
141
  ### Many-to-Many with Bridge Table
131
142
  ```
132
- Dim_Customer ──(1:*)── Bridge_CustomerProduct ──(*:1)── Dim_Product
143
+ Customer ──(1:*)── Bridge Customer Product ──(*:1)── Product
133
144
  ```
134
145
 
135
146
  ### Slowly Changing Dimensions (SCD)
136
147
  ```
137
148
  Type 1: Overwrite (no history)
138
149
  Type 2: Add new row with version tracking
139
- - StartDate, EndDate, IsCurrent flag
150
+ - Start Date, End Date, Is Current flag
140
151
 
141
- Dim_Customer_SCD2
142
- ├── CustomerKey (surrogate, unique per version)
143
- ├── CustomerID (business key)
144
- ├── CustomerName
152
+ Customer (SCD2)
153
+ ├── CustomerKey (surrogate, unique per version, hidden)
154
+ ├── Customer ID (business key)
155
+ ├── Customer Name
145
156
  ├── Address
146
- ├── StartDate
147
- ├── EndDate
148
- └── IsCurrent
157
+ ├── Start Date
158
+ ├── End Date
159
+ └── Is Current
149
160
  ```
150
161
 
151
162
  ### Junk Dimension (Low-Cardinality Flags)
152
163
  Combine multiple low-cardinality attributes:
153
164
  ```
154
- Dim_OrderFlags
155
- ├── OrderFlagKey
156
- ├── IsRush
157
- ├── IsGift
158
- ├── IsOnline
159
- └── PaymentType
165
+ Order Flags
166
+ ├── OrderFlagKey (hidden)
167
+ ├── Is Rush
168
+ ├── Is Gift
169
+ ├── Is Online
170
+ └── Payment Type
160
171
  ```
161
172
 
162
173
  ### Degenerate Dimension
163
174
  Attributes stored in fact table (no separate dimension):
164
175
  ```
165
- Fact_Sales
176
+ Sales
166
177
  ├── ...
167
- ├── InvoiceNumber (degenerate dimension)
168
- ├── OrderNumber (degenerate dimension)
178
+ ├── Invoice Number (degenerate dimension)
179
+ ├── Order Number (degenerate dimension)
169
180
  └── ...
170
181
  ```
171
182
 
@@ -191,9 +202,12 @@ Fact_Sales
191
202
 
192
203
  ## Model Validation Checklist
193
204
 
205
+ - [ ] No technical prefixes (`Dim_`, `Fact_`, `tbl_`)
206
+ - [ ] Dimensions are singular (`Customer`), facts are plural (`Sales`)
207
+ - [ ] Foreign keys hidden from report view
208
+ - [ ] Sumable source columns hidden (only explicit measures visible)
194
209
  - [ ] All relationships are single-direction (except where required)
195
210
  - [ ] Date table marked as Date Table
196
- - [ ] Foreign keys hidden from report view
197
211
  - [ ] No circular dependencies
198
212
  - [ ] Measures in dedicated folder or table
199
213
  - [ ] Appropriate data types assigned
@@ -210,14 +224,26 @@ Fact_Sales
210
224
 
211
225
  ### Snowflake Complexity
212
226
  ```
213
- Dim_ProductDim_CategoryDim_Department
214
- ✓ Denormalize: Dim_Product (includes Category, Department)
227
+ ProductCategoryDepartment
228
+ ✓ Denormalize: Product (includes Category, Department columns)
215
229
  ```
216
230
 
217
231
  ### Calculated Columns for Measures
218
232
  ```
219
233
  ❌ Calculated column: [Profit] = [Revenue] - [Cost]
220
- ✓ Measure: Profit = SUM([Revenue]) - SUM([Cost])
234
+ ✓ Measure: Profit = SUM(Sales[Revenue]) - SUM(Sales[Cost])
235
+ ```
236
+
237
+ ### Technical Prefixes on User-Visible Tables
238
+ ```
239
+ ❌ Fact_Sales, Dim_Customer (developer jargon leaks to users)
240
+ ✓ Sales, Customer (business names)
241
+ ```
242
+
243
+ ### Visible Sumable Columns
244
+ ```
245
+ ❌ Quantity column visible — users drag it into visuals as Sum/Average
246
+ ✓ Hide Quantity, expose explicit measure: Quantity Sold = SUM(Sales[Quantity])
221
247
  ```
222
248
 
223
249
  ### Missing Date Dimension
@@ -15,10 +15,13 @@ You are a **DAX Expert** specializing in efficient measure design, context manip
15
15
 
16
16
  ## MANDATORY RULES
17
17
  1. **USE VARIABLES.** Always use VAR/RETURN for any calculation used more than once.
18
- 2. **FILTER ON DIMENSIONS.** Never filter directly on fact tables when a dimension exists.
19
- 3. **SAFE DIVISION.** Always use DIVIDE() instead of / operator.
20
- 4. **EXPLAIN CONTEXT.** Help users understand filter vs row context.
21
- 5. **PERFORMANCE FIRST.** Suggest optimizations proactively.
18
+ 2. **FINAL VARIABLE IS `Result`.** Every measure with VAR/RETURN ends with `VAR Result = ... RETURN Result` (SQLBI convention).
19
+ 3. **PASCALCASE FOR VARIABLES.** No underscore prefix. `TotalSales`, not `_TotalSales`.
20
+ 4. **`@` PREFIX FOR TEMPORARY COLUMNS.** When adding columns inside `ADDCOLUMNS` or `SUMMARIZECOLUMNS`, prefix with `@` (e.g., `@Rank`, `@Sales`).
21
+ 5. **FILTER ON DIMENSIONS.** Never filter directly on fact tables when a dimension exists.
22
+ 6. **SAFE DIVISION.** Always use DIVIDE() instead of / operator.
23
+ 7. **EXPLAIN CONTEXT.** Help users understand filter vs row context.
24
+ 8. **PERFORMANCE FIRST.** Suggest optimizations proactively.
22
25
 
23
26
  ---
24
27
 
@@ -27,14 +30,26 @@ Best practices for writing and optimizing DAX in Power BI.
27
30
 
28
31
  ## Naming Conventions
29
32
 
33
+ Conventions follow the SQLBI Style Guide. The principle: **measure names speak business language, code reads top-to-bottom**.
34
+
30
35
  | Element | Convention | Example |
31
36
  |---------|------------|---------|
32
- | Measures | PascalCase, descriptive | `TotalSalesAmount`, `AvgOrderValue` |
33
- | Calculated Columns | PascalCase, table prefix if ambiguous | `Sales[FullDate]`, `Product Category` |
34
- | Variables | Underscore prefix + PascalCase | `_Result`, `_FilteredTable`, `_CurrentDate` |
35
- | Measure Tables | `_Measures` or `Metrics` | `_Measures[TotalSales]` |
36
- | KPI Measures | Prefix with category | `KPI_Sales_Target`, `KPI_Margin_Actual` |
37
- | Debug/Test Measures | Underscore prefix | `_Debug_RowCount`, `_Test_SalesTotal` |
37
+ | Measures | Auto-explicative, no prefix | `Sales`, `Margin %`, `Sales YTD` |
38
+ | Time intelligence suffix | Space + period | `Sales YTD`, `Sales PY`, `Sales MTD` |
39
+ | Variations | Space + variation + `%` | `Sales YoY %`, `Sales MoM %` |
40
+ | Ratios | Space + `%` | `Margin %`, `Conversion %` |
41
+ | Calculated Columns | Natural name | `Full Date`, `Product Category` |
42
+ | Variables | PascalCase, no prefix | `TotalSales`, `FilteredTable`, `CurrentDate` |
43
+ | Final variable | Always `Result` | `VAR Result = ...` `RETURN Result` |
44
+ | Temporary columns | Prefix `@` | `@Rank`, `@Sales`, `@Delta` |
45
+ | Measure Tables | Prefix `_`, hidden | `_Measures`, `_KPIs` |
46
+ | Debug/Test Measures | `_` prefix, hidden | `_Debug Row Count` |
47
+
48
+ **Anti-patterns:**
49
+ - `Total Sales`, `Sum of Sales` (redundant, the aggregation is implied)
50
+ - `_Result`, `_FilteredTable` (no underscore prefix on variables)
51
+ - `YTD_Sales`, `Sales_YTD` (use space-separated suffix)
52
+ - `MarginPct`, `MarginRatio` (use `Margin %` instead)
38
53
 
39
54
  ---
40
55
 
@@ -100,25 +115,47 @@ SumViaMeasure = SUMX(Products, [TotalSales]) // [TotalSales] filters to each pr
100
115
  - Intermediate calculations
101
116
  - Debugging complex measures
102
117
 
118
+ **Conventions (SQLBI Style Guide):**
119
+ - **PascalCase** without underscore prefix
120
+ - **Final variable always `Result`** — clearly marks the measure's output
121
+ - **Temporary columns prefixed with `@`** to distinguish from model columns
122
+
103
123
  ```dax
104
- -- Good: Use variables
105
- SalesYoY =
106
- VAR _CurrentSales = [TotalSales]
107
- VAR _PriorYearSales =
124
+ -- Good: PascalCase vars, Result final var
125
+ Sales YoY % =
126
+ VAR CurrentSales = [Sales]
127
+ VAR PriorYearSales =
108
128
  CALCULATE(
109
- [TotalSales],
129
+ [Sales],
110
130
  SAMEPERIODLASTYEAR('Date'[Date])
111
131
  )
112
- VAR _Change = _CurrentSales - _PriorYearSales
113
- RETURN
114
- DIVIDE(_Change, _PriorYearSales)
132
+ VAR Delta = CurrentSales - PriorYearSales
133
+ VAR Result = DIVIDE(Delta, PriorYearSales)
134
+ RETURN Result
135
+
136
+ -- Good: Temporary column with @ prefix
137
+ Top 10 Products Sales =
138
+ VAR RankedProducts =
139
+ ADDCOLUMNS(
140
+ VALUES(Product[Product Name]),
141
+ "@Rank", RANKX(VALUES(Product[Product Name]), [Sales], , DESC)
142
+ )
143
+ VAR Top10 = FILTER(RankedProducts, [@Rank] <= 10)
144
+ VAR Result = SUMX(Top10, [Sales])
145
+ RETURN Result
115
146
 
116
147
  -- Bad: Repeated calculations (slower, harder to read)
117
- SalesYoY_Bad =
148
+ Sales YoY Bad =
118
149
  DIVIDE(
119
- [TotalSales] - CALCULATE([TotalSales], SAMEPERIODLASTYEAR('Date'[Date])),
120
- CALCULATE([TotalSales], SAMEPERIODLASTYEAR('Date'[Date]))
150
+ [Sales] - CALCULATE([Sales], SAMEPERIODLASTYEAR('Date'[Date])),
151
+ CALCULATE([Sales], SAMEPERIODLASTYEAR('Date'[Date]))
121
152
  )
153
+
154
+ -- Bad: Underscore prefix on variables (old convention)
155
+ Sales YoY Old =
156
+ VAR _CurrentSales = [Sales]
157
+ VAR _PriorYearSales = CALCULATE([Sales], SAMEPERIODLASTYEAR('Date'[Date]))
158
+ RETURN DIVIDE(_CurrentSales - _PriorYearSales, _PriorYearSales)
122
159
  ```
123
160
 
124
161
  ### Safe Division
@@ -627,29 +664,30 @@ Running_Total = CALCULATE(...) -- Use measures
627
664
  ## Formatting Standards
628
665
 
629
666
  ```dax
630
- -- Simple measure (one line)
631
- TotalSales = SUM(Sales[Amount])
667
+ -- Simple measure (one line, no VAR/RETURN needed)
668
+ Sales = SUM(Sales[Amount])
632
669
 
633
670
  -- Medium complexity (few lines)
634
- Margin = DIVIDE([Profit], [Revenue], 0)
671
+ Margin % = DIVIDE([Profit], [Revenue], 0)
635
672
 
636
- -- Complex measure (structured with comments)
637
- ComplexMeasure =
638
- // Purpose: Calculate year-over-year change with handling for missing prior year
639
- VAR _CurrentSales = [TotalSales]
640
- VAR _PriorYearSales =
673
+ -- Complex measure (structured with comments, Result as final var)
674
+ Sales YoY % =
675
+ // Purpose: Year-over-year change with handling for missing prior year
676
+ VAR CurrentSales = [Sales]
677
+ VAR PriorYearSales =
641
678
  CALCULATE(
642
- [TotalSales],
679
+ [Sales],
643
680
  SAMEPERIODLASTYEAR('Date'[Date])
644
681
  )
645
- VAR _Change = _CurrentSales - _PriorYearSales
646
- VAR _HasPriorYear = NOT(ISBLANK(_PriorYearSales))
647
- RETURN
682
+ VAR Delta = CurrentSales - PriorYearSales
683
+ VAR HasPriorYear = NOT(ISBLANK(PriorYearSales))
684
+ VAR Result =
648
685
  IF(
649
- _HasPriorYear,
650
- DIVIDE(_Change, _PriorYearSales),
686
+ HasPriorYear,
687
+ DIVIDE(Delta, PriorYearSales),
651
688
  BLANK()
652
689
  )
690
+ RETURN Result
653
691
  ```
654
692
 
655
693
  ---
@@ -22,57 +22,100 @@ You are a **BI Governance Specialist** who establishes and enforces naming conve
22
22
 
23
23
  ## Naming Conventions
24
24
 
25
+ Conventions follow SQLBI Style Guide, Chris Webb, and Microsoft's semantic model best practices. The guiding principle: **user-visible names speak business language, technical elements stay hidden**.
26
+
25
27
  ### Tables
26
28
 
29
+ **Rules:**
30
+ 1. **No technical prefixes.** Never use `dim`, `fact`, `tbl`, `t_`. These are for developers; users see tables in visuals and shouldn't read SQL jargon.
31
+ 2. **Dimensions singular, facts plural.** A dimension describes one entity (`Customer`, `Product`, `Date`). A fact contains many events (`Sales`, `Orders`, `Transactions`).
32
+ 3. **Spaces allowed for user-visible tables.** Use natural language: `Purchase Orders`, not `PurchaseOrders` or `PO_Header`.
33
+ 4. **Business names, not source names.** Match the language users speak, not the database schema.
34
+
27
35
  | Type | Convention | Example | Anti-Pattern |
28
36
  |------|-----------|---------|-------------|
29
- | Dimension | Singular noun | `Customer`, `Product`, `Date` | `Customers`, `DimCustomer`, `tbl_Customer` |
30
- | Fact | Singular noun or verb | `Sale`, `Order`, `Inventory` | `FactSales`, `fct_Sales` |
31
- | Bridge | Prefix with `Bridge` | `BridgeCustomerRegion` | `CustomerRegion_Bridge` |
32
- | Measure table | Prefix `_` | `_Measures`, `_KPIs` | `Measures Table`, `CalcMeasures` |
33
- | Config/Utility | Prefix `_` | `_Parameters`, `_DateConfig` | `Parameters` |
34
- | Calculated table | Document clearly | `CalendarTable` (with comment) | Unmarked calculated tables |
37
+ | Dimension | Singular, business name | `Customer`, `Product`, `Date` | `Customers`, `DimCustomer`, `tbl_Customer` |
38
+ | Fact | Plural, business name | `Sales`, `Orders`, `Transactions` | `FactSales`, `fct_sales`, `Sale` |
39
+ | Bridge | Prefix with `Bridge` | `Bridge Customer Region` | `CustomerRegion_Bridge` |
40
+ | Measure table | Prefix `_` (hidden) | `_Measures`, `_KPIs` | `Measures Table` |
41
+ | Config/Utility | Prefix `_` (hidden) | `_Parameters`, `_DateConfig` | `Parameters` |
35
42
 
36
43
  ### Columns
37
44
 
45
+ **Rules:**
46
+ 1. **Descriptive and readable.** `Order Date`, not `OrdDt`. Spaces allowed.
47
+ 2. **Foreign keys hidden with special marker.** Use `CustomerKey` suffix or `_Customer` prefix to sort them together. **Always hide from report view.**
48
+ 3. **No abbreviations** except universal ones (`YTD`, `PY`, `MTD`, `QTD`, `YoY`). Avoid `Qty`, `Amt`, `Vta`, `Cant`.
49
+ 4. **Consistency.** If you use spaces in one table, use them everywhere.
50
+
38
51
  | Type | Convention | Example |
39
52
  |------|-----------|---------|
40
- | Key (primary) | `[TableName]Key` | `CustomerKey`, `ProductKey` |
41
- | Key (alternate) | `[TableName]ID` or `[TableName]Code` | `CustomerID`, `ProductCode` |
42
- | Attribute | PascalCase, descriptive | `FirstName`, `OrderDate`, `ProductCategory` |
43
- | Flag/Boolean | `Is[Condition]` | `IsActive`, `IsDeleted`, `HasDiscount` |
44
- | Amount/Currency | Suffix with `Amount` | `SalesAmount`, `DiscountAmount` |
45
- | Count | Suffix with `Count` | `OrderCount`, `ItemCount` |
46
- | Date | Suffix with `Date` | `OrderDate`, `ShipDate`, `HireDate` |
53
+ | Primary key (surrogate) | `[Table]Key`, hidden | `CustomerKey`, `ProductKey` |
54
+ | Primary key (business) | `[Entity] ID` or `[Entity] Code` | `Customer ID`, `Product Code` |
55
+ | Attribute | Natural language | `First Name`, `Order Date`, `Product Category` |
56
+ | Flag/Boolean | `Is [Condition]` or `Has [Condition]` | `Is Active`, `Has Discount` |
57
+ | Amount/Currency | Include unit in name | `Sales Amount`, `Discount Amount` |
58
+ | Count | Include `Count` suffix | `Order Count`, `Item Count` |
59
+ | Date | Include `Date` suffix | `Order Date`, `Ship Date` |
47
60
 
48
61
  ### Measures
49
62
 
50
- | Type | Convention | Example |
51
- |------|-----------|---------|
52
- | Base aggregation | PascalCase, descriptive | `TotalSales`, `OrderCount` |
53
- | Time intelligence | Suffix with period | `TotalSales_YTD`, `Revenue_PY` |
54
- | Ratio/Percentage | Suffix with `Pct` or `Ratio` | `MarginPct`, `ConversionRatio` |
55
- | KPI | Prefix with `KPI_` | `KPI_Revenue_Actual`, `KPI_Revenue_Target` |
56
- | Ranking | Prefix with `Rank_` | `Rank_TopProducts` |
57
- | Debug/Test | Prefix with `_` | `_Debug_RowCount`, `_Test_Total` |
58
- | Format string | Include in measure | Use format strings, not calculated strings |
63
+ **Rules:**
64
+ 1. **Auto-explicative names.** The name alone should tell you what it calculates. No documentation required.
65
+ 2. **No redundant prefixes.** Use `Sales`, not `Total Sales`, `Sum of Sales`, or `Amount of Sales`. The aggregation is implied.
66
+ 3. **Space-separated suffixes for time intelligence.** `Sales YTD`, `Sales PY`, `Sales YoY %`, `Sales MTD`.
67
+ 4. **Space-separated suffixes for ratios and percentages.** `Margin %`, `Growth %`, `Conversion Rate`.
68
+ 5. **Never prefix with the table name.** Just `Sales`, not `Transactions[Sales]` in the visible name.
69
+ 6. **Explicit measures, hidden source columns.** Create DAX measures for every numeric calculation. Hide sumable columns (or set summarization to `Don't summarize`) so users can't drag raw columns into visuals.
70
+ 7. **Organize in display folders** when the measure count grows: `Sales`, `Sales\Time Intelligence`, `Sales\Comparatives`.
71
+
72
+ | Type | Convention | Example | Anti-Pattern |
73
+ |------|-----------|---------|-------------|
74
+ | Base aggregation | Noun, no prefix | `Sales`, `Customers`, `Units Sold` | `Total Sales`, `Sum of Sales`, `Sales Amount Total` |
75
+ | Time intelligence | Noun + period suffix | `Sales YTD`, `Sales PY`, `Sales MTD` | `YTD_Sales`, `Sales_PriorYear` |
76
+ | Variation | Noun + variation + `%` | `Sales YoY %`, `Sales MoM %` | `YoY_Pct_Sales` |
77
+ | Ratio/Percentage | Noun + ` %` | `Margin %`, `Conversion %` | `MarginPct`, `ConversionRatio` |
78
+ | KPI vs target | Noun + vs Target | `Sales vs Target`, `Sales vs Budget %` | `KPI_Sales` |
79
+ | Ranking | `Rank: ` prefix or `Rank` suffix | `Rank: Top Products`, `Product Rank` | `RANK_Products` |
80
+ | Debug/Test | `_` prefix (hidden) | `_Debug Row Count` | `Debug: Row Count` (visible) |
59
81
 
60
82
  ### Variables (DAX)
61
83
 
62
- | Convention | Example |
63
- |-----------|---------|
64
- | Prefix with `_` + PascalCase | `_CurrentSales`, `_PriorYear` |
65
- | Descriptive names | `_FilteredTable`, `_MaxDate` |
66
- | Avoid single letters | `_Result` not `_R` |
84
+ **Rules:**
85
+ 1. **PascalCase.** No underscore prefix. `TotalSales`, `AverageMargin`, `FilteredTable`.
86
+ 2. **Final variable always named `Result`.** SQLBI convention for readability the reader knows immediately where the measure ends.
87
+ 3. **Temporary columns prefixed with `@`.** When you add a column inside `ADDCOLUMNS` or `SUMMARIZECOLUMNS`, use `@Rank`, `@Sales`, `@Delta`. Distinguishes them from model columns.
88
+
89
+ ```dax
90
+ // Good: PascalCase vars, Result final var, @ for temp columns
91
+ Sales YoY % =
92
+ VAR CurrentSales = [Sales]
93
+ VAR PriorYearSales =
94
+ CALCULATE([Sales], SAMEPERIODLASTYEAR('Date'[Date]))
95
+ VAR Delta = CurrentSales - PriorYearSales
96
+ VAR Result = DIVIDE(Delta, PriorYearSales)
97
+ RETURN Result
98
+
99
+ // Temporary column example
100
+ Top Products =
101
+ VAR RankedProducts =
102
+ ADDCOLUMNS(
103
+ VALUES(Product[Product Name]),
104
+ "@Rank", RANKX(VALUES(Product[Product Name]), [Sales], , DESC)
105
+ )
106
+ VAR Result =
107
+ FILTER(RankedProducts, [@Rank] <= 10)
108
+ RETURN Result
109
+ ```
67
110
 
68
111
  ### Power Query
69
112
 
70
113
  | Element | Convention | Example |
71
114
  |---------|-----------|---------|
72
- | Query name | PascalCase matching table | `Customer`, `Sale` |
73
- | Staging query | Prefix with `stg_` | `stg_Customer_Raw` |
74
- | Helper function | Prefix with `fn` | `fnCleanText`, `fnGetFiscalYear` |
75
- | Parameter | Prefix with `prm` | `prmServerName`, `prmStartDate` |
115
+ | Query name | Matches destination table | `Customer`, `Sales` |
116
+ | Staging query | Prefix `stg `, hidden | `stg Customer Raw` |
117
+ | Helper function | Prefix `fn` | `fnCleanText`, `fnGetFiscalYear` |
118
+ | Parameter | Prefix `prm` | `prmServerName`, `prmStartDate` |
76
119
  | Step names | Descriptive, PascalCase | `FilteredActiveRows`, `RenamedColumns` |
77
120
 
78
121
  ---
@@ -81,26 +124,28 @@ You are a **BI Governance Specialist** who establishes and enforces naming conve
81
124
 
82
125
  ### Recommended Folder Structure
83
126
 
127
+ Display folders separate measures with `\` (backslash). Use them as soon as you have more than ~10 measures.
128
+
84
129
  ```
85
130
  _Measures/
86
- ├── 📁 Core Metrics
87
- │ ├── TotalSales
88
- │ ├── TotalCost
89
- │ └── GrossMargin
131
+ ├── 📁 Core
132
+ │ ├── Sales
133
+ │ ├── Cost
134
+ │ └── Gross Margin
90
135
  ├── 📁 Time Intelligence
91
- │ ├── TotalSales_YTD
92
- │ ├── TotalSales_PY
93
- │ └── TotalSales_YoY
94
- ├── 📁 KPIs
95
- │ ├── KPI_Revenue_Actual
96
- │ ├── KPI_Revenue_Target
97
- │ └── KPI_Revenue_Status
136
+ │ ├── Sales YTD
137
+ │ ├── Sales PY
138
+ │ └── Sales YoY %
139
+ ├── 📁 Targets
140
+ │ ├── Sales Target
141
+ │ ├── Sales vs Target
142
+ │ └── Sales vs Target %
98
143
  ├── 📁 Rankings
99
- │ ├── Rank_TopProducts
100
- │ └── Rank_TopCustomers
144
+ │ ├── Rank: Top Products
145
+ │ └── Rank: Top Customers
101
146
  └── 📁 _Debug (hidden in production)
102
- ├── _Debug_RowCount
103
- └── _Test_Total
147
+ ├── _Debug Row Count
148
+ └── _Test Sales
104
149
  ```
105
150
 
106
151
  ---
@@ -139,11 +184,13 @@ Every model should have:
139
184
  ### Measure Documentation Template
140
185
 
141
186
  ```dax
142
- // Description: Calculates total sales for the current year to date
187
+ // Description: Calculates sales for the current year to date
143
188
  // Business rule: Includes all confirmed and shipped orders, excludes cancelled
144
189
  // Owner: [team/person]
145
190
  // Last modified: [date]
146
- TotalSales_YTD = TOTALYTD([TotalSales], 'Date'[Date])
191
+ Sales YTD =
192
+ VAR Result = TOTALYTD([Sales], 'Date'[Date])
193
+ RETURN Result
147
194
  ```
148
195
 
149
196
  ---
@@ -165,11 +212,17 @@ TotalSales_YTD = TOTALYTD([TotalSales], 'Date'[Date])
165
212
  ### Code Review Checklist
166
213
 
167
214
  - [ ] Measures use VAR/RETURN for repeated calculations
215
+ - [ ] Final variable in every measure is named `Result`
216
+ - [ ] Variables use PascalCase (no underscore prefix)
217
+ - [ ] Temporary columns use `@` prefix (e.g., `@Rank`)
168
218
  - [ ] DIVIDE() used instead of / operator
169
219
  - [ ] No FILTER on fact tables (use dimension filters)
170
- - [ ] Variables follow naming conventions
171
220
  - [ ] Complex measures have comments
172
221
  - [ ] No hardcoded values (use parameters)
222
+ - [ ] No technical prefixes on tables (`Dim_`, `Fact_`)
223
+ - [ ] Foreign keys hidden from report view
224
+ - [ ] Sumable source columns hidden (only explicit measures visible)
225
+ - [ ] Measure names are auto-explicative (no `Total`, `Sum of`)
173
226
 
174
227
  ---
175
228