@luquimbo/bi-superpowers 1.0.0

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 (193) hide show
  1. package/.claude-plugin/plugin.json +8 -0
  2. package/.mcp.json +25 -0
  3. package/AGENTS.md +244 -0
  4. package/CHANGELOG.md +265 -0
  5. package/LICENSE +21 -0
  6. package/README.md +211 -0
  7. package/bin/build-plugin.js +30 -0
  8. package/bin/cli.js +1064 -0
  9. package/bin/commands/add.js +533 -0
  10. package/bin/commands/add.test.js +77 -0
  11. package/bin/commands/build-desktop.js +166 -0
  12. package/bin/commands/changelog.js +443 -0
  13. package/bin/commands/diff.js +325 -0
  14. package/bin/commands/lint.js +419 -0
  15. package/bin/commands/lint.test.js +103 -0
  16. package/bin/commands/mcp-setup.js +246 -0
  17. package/bin/commands/pull.js +287 -0
  18. package/bin/commands/pull.test.js +36 -0
  19. package/bin/commands/push.js +231 -0
  20. package/bin/commands/push.test.js +14 -0
  21. package/bin/commands/search.js +344 -0
  22. package/bin/commands/search.test.js +115 -0
  23. package/bin/commands/setup.js +545 -0
  24. package/bin/commands/setup.test.js +46 -0
  25. package/bin/commands/sync-profile.js +405 -0
  26. package/bin/commands/sync-profile.test.js +14 -0
  27. package/bin/commands/sync-source.js +418 -0
  28. package/bin/commands/sync-source.test.js +14 -0
  29. package/bin/commands/watch.js +206 -0
  30. package/bin/lib/generators/claude-plugin.js +266 -0
  31. package/bin/lib/generators/claude-plugin.test.js +110 -0
  32. package/bin/lib/generators/index.js +116 -0
  33. package/bin/lib/generators/shared.js +282 -0
  34. package/bin/lib/licensing/index.js +35 -0
  35. package/bin/lib/licensing/storage.js +364 -0
  36. package/bin/lib/licensing/storage.test.js +55 -0
  37. package/bin/lib/licensing/validator.js +213 -0
  38. package/bin/lib/licensing/validator.test.js +137 -0
  39. package/bin/lib/microsoft-mcp.js +176 -0
  40. package/bin/lib/microsoft-mcp.test.js +106 -0
  41. package/bin/lib/skills.js +84 -0
  42. package/bin/mcp/powerbi-modeling-launcher.js +38 -0
  43. package/bin/postinstall.js +44 -0
  44. package/bin/utils/errors.js +159 -0
  45. package/bin/utils/git.js +298 -0
  46. package/bin/utils/logger.js +142 -0
  47. package/bin/utils/mcp-detect.js +274 -0
  48. package/bin/utils/mcp-detect.test.js +105 -0
  49. package/bin/utils/pbix.js +305 -0
  50. package/bin/utils/pbix.test.js +37 -0
  51. package/bin/utils/profiles.js +312 -0
  52. package/bin/utils/projects.js +168 -0
  53. package/bin/utils/readline.js +206 -0
  54. package/bin/utils/readline.test.js +47 -0
  55. package/bin/utils/tui.js +314 -0
  56. package/bin/utils/tui.test.js +127 -0
  57. package/commands/contributions.md +265 -0
  58. package/commands/data-model-design.md +468 -0
  59. package/commands/dax-doctor.md +248 -0
  60. package/commands/fabric-scripts.md +452 -0
  61. package/commands/migration-assistant.md +290 -0
  62. package/commands/model-documenter.md +242 -0
  63. package/commands/pbi-connect.md +239 -0
  64. package/commands/project-kickoff.md +905 -0
  65. package/commands/report-layout.md +296 -0
  66. package/commands/rls-design.md +533 -0
  67. package/commands/theme-tweaker.md +624 -0
  68. package/config.example.json +23 -0
  69. package/config.json +23 -0
  70. package/desktop-extension/manifest.json +37 -0
  71. package/desktop-extension/package.json +10 -0
  72. package/desktop-extension/server.js +95 -0
  73. package/docs/openrouter-free-models.md +92 -0
  74. package/library/examples/README.md +151 -0
  75. package/library/examples/finance-reporting/README.md +351 -0
  76. package/library/examples/finance-reporting/data-model.md +267 -0
  77. package/library/examples/finance-reporting/measures.dax +557 -0
  78. package/library/examples/hr-analytics/README.md +371 -0
  79. package/library/examples/hr-analytics/data-model.md +315 -0
  80. package/library/examples/hr-analytics/measures.dax +460 -0
  81. package/library/examples/marketing-analytics/README.md +37 -0
  82. package/library/examples/marketing-analytics/data-model.md +62 -0
  83. package/library/examples/marketing-analytics/measures.dax +110 -0
  84. package/library/examples/retail-analytics/README.md +439 -0
  85. package/library/examples/retail-analytics/data-model.md +288 -0
  86. package/library/examples/retail-analytics/measures.dax +481 -0
  87. package/library/examples/supply-chain/README.md +37 -0
  88. package/library/examples/supply-chain/data-model.md +69 -0
  89. package/library/examples/supply-chain/measures.dax +77 -0
  90. package/library/examples/udf-library/README.md +228 -0
  91. package/library/examples/udf-library/functions.dax +571 -0
  92. package/library/snippets/dax/README.md +292 -0
  93. package/library/snippets/dax/business-domains.md +576 -0
  94. package/library/snippets/dax/calculate-patterns.md +276 -0
  95. package/library/snippets/dax/calculation-groups.md +489 -0
  96. package/library/snippets/dax/error-handling.md +495 -0
  97. package/library/snippets/dax/iterators-and-aggregations.md +474 -0
  98. package/library/snippets/dax/kpis-and-metrics.md +293 -0
  99. package/library/snippets/dax/rankings-and-topn.md +235 -0
  100. package/library/snippets/dax/security-patterns.md +413 -0
  101. package/library/snippets/dax/text-and-formatting.md +316 -0
  102. package/library/snippets/dax/time-intelligence.md +196 -0
  103. package/library/snippets/dax/user-defined-functions.md +477 -0
  104. package/library/snippets/dax/virtual-tables.md +546 -0
  105. package/library/snippets/excel-formulas/README.md +84 -0
  106. package/library/snippets/excel-formulas/aggregations.md +330 -0
  107. package/library/snippets/excel-formulas/dates-and-times.md +361 -0
  108. package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
  109. package/library/snippets/excel-formulas/lookups.md +169 -0
  110. package/library/snippets/excel-formulas/text-functions.md +363 -0
  111. package/library/snippets/governance/naming-conventions.md +97 -0
  112. package/library/snippets/governance/review-checklists.md +107 -0
  113. package/library/snippets/power-query/README.md +389 -0
  114. package/library/snippets/power-query/api-integration.md +707 -0
  115. package/library/snippets/power-query/connections.md +434 -0
  116. package/library/snippets/power-query/data-cleaning.md +298 -0
  117. package/library/snippets/power-query/error-handling.md +526 -0
  118. package/library/snippets/power-query/parameters.md +350 -0
  119. package/library/snippets/power-query/performance.md +506 -0
  120. package/library/snippets/power-query/transformations.md +330 -0
  121. package/library/snippets/report-design/accessibility.md +78 -0
  122. package/library/snippets/report-design/chart-selection.md +54 -0
  123. package/library/snippets/report-design/layout-patterns.md +87 -0
  124. package/library/templates/data-models/README.md +93 -0
  125. package/library/templates/data-models/finance-model.md +627 -0
  126. package/library/templates/data-models/retail-star-schema.md +473 -0
  127. package/library/templates/excel/README.md +83 -0
  128. package/library/templates/excel/budget-tracker.md +432 -0
  129. package/library/templates/excel/data-entry-form.md +533 -0
  130. package/library/templates/power-bi/README.md +72 -0
  131. package/library/templates/power-bi/finance-report.md +449 -0
  132. package/library/templates/power-bi/kpi-scorecard.md +461 -0
  133. package/library/templates/power-bi/sales-dashboard.md +281 -0
  134. package/library/themes/excel/README.md +436 -0
  135. package/library/themes/power-bi/README.md +271 -0
  136. package/library/themes/power-bi/accessible.json +307 -0
  137. package/library/themes/power-bi/bi-superpowers-default.json +858 -0
  138. package/library/themes/power-bi/corporate-blue.json +291 -0
  139. package/library/themes/power-bi/dark-mode.json +291 -0
  140. package/library/themes/power-bi/minimal.json +292 -0
  141. package/library/themes/power-bi/print-friendly.json +309 -0
  142. package/package.json +93 -0
  143. package/skills/contributions/SKILL.md +267 -0
  144. package/skills/data-model-design/SKILL.md +470 -0
  145. package/skills/data-modeling/SKILL.md +254 -0
  146. package/skills/data-quality/SKILL.md +664 -0
  147. package/skills/dax/SKILL.md +708 -0
  148. package/skills/dax-doctor/SKILL.md +250 -0
  149. package/skills/dax-udf/SKILL.md +489 -0
  150. package/skills/deployment/SKILL.md +320 -0
  151. package/skills/excel-formulas/SKILL.md +463 -0
  152. package/skills/fabric-scripts/SKILL.md +454 -0
  153. package/skills/fast-standard/SKILL.md +509 -0
  154. package/skills/governance/SKILL.md +205 -0
  155. package/skills/migration-assistant/SKILL.md +292 -0
  156. package/skills/model-documenter/SKILL.md +244 -0
  157. package/skills/pbi-connect/SKILL.md +241 -0
  158. package/skills/power-query/SKILL.md +406 -0
  159. package/skills/project-kickoff/SKILL.md +907 -0
  160. package/skills/query-performance/SKILL.md +480 -0
  161. package/skills/report-design/SKILL.md +207 -0
  162. package/skills/report-layout/SKILL.md +298 -0
  163. package/skills/rls-design/SKILL.md +535 -0
  164. package/skills/semantic-model/SKILL.md +237 -0
  165. package/skills/testing-validation/SKILL.md +643 -0
  166. package/skills/theme-tweaker/SKILL.md +626 -0
  167. package/src/content/base.md +237 -0
  168. package/src/content/mcp-requirements.json +69 -0
  169. package/src/content/routing.md +203 -0
  170. package/src/content/skills/contributions.md +259 -0
  171. package/src/content/skills/data-model-design.md +462 -0
  172. package/src/content/skills/data-modeling.md +246 -0
  173. package/src/content/skills/data-quality.md +656 -0
  174. package/src/content/skills/dax-doctor.md +242 -0
  175. package/src/content/skills/dax-udf.md +481 -0
  176. package/src/content/skills/dax.md +700 -0
  177. package/src/content/skills/deployment.md +312 -0
  178. package/src/content/skills/excel-formulas.md +455 -0
  179. package/src/content/skills/fabric-scripts.md +446 -0
  180. package/src/content/skills/fast-standard.md +501 -0
  181. package/src/content/skills/governance.md +197 -0
  182. package/src/content/skills/migration-assistant.md +284 -0
  183. package/src/content/skills/model-documenter.md +236 -0
  184. package/src/content/skills/pbi-connect.md +233 -0
  185. package/src/content/skills/power-query.md +398 -0
  186. package/src/content/skills/project-kickoff.md +899 -0
  187. package/src/content/skills/query-performance.md +472 -0
  188. package/src/content/skills/report-design.md +199 -0
  189. package/src/content/skills/report-layout.md +290 -0
  190. package/src/content/skills/rls-design.md +527 -0
  191. package/src/content/skills/semantic-model.md +229 -0
  192. package/src/content/skills/testing-validation.md +635 -0
  193. package/src/content/skills/theme-tweaker.md +618 -0
@@ -0,0 +1,196 @@
1
+ # Time Intelligence DAX Patterns
2
+
3
+ Reusable DAX measures for time-based calculations.
4
+
5
+ ## Year-to-Date (YTD)
6
+
7
+ ### Basic YTD
8
+ ```dax
9
+ SalesYTD =
10
+ TOTALYTD(
11
+ SUM(Sales[Amount]),
12
+ 'Date'[Date]
13
+ )
14
+ ```
15
+
16
+ ### YTD with Fiscal Year (April Start)
17
+ ```dax
18
+ SalesYTD_Fiscal =
19
+ TOTALYTD(
20
+ SUM(Sales[Amount]),
21
+ 'Date'[Date],
22
+ "3/31" -- Fiscal year ends March 31
23
+ )
24
+ ```
25
+
26
+ ### YTD Same Period Last Year
27
+ ```dax
28
+ SalesYTD_PY =
29
+ CALCULATE(
30
+ [SalesYTD],
31
+ SAMEPERIODLASTYEAR('Date'[Date])
32
+ )
33
+ ```
34
+
35
+ ## Month-to-Date (MTD)
36
+
37
+ ### Basic MTD
38
+ ```dax
39
+ SalesMTD =
40
+ TOTALMTD(
41
+ SUM(Sales[Amount]),
42
+ 'Date'[Date]
43
+ )
44
+ ```
45
+
46
+ ### MTD with Comparison
47
+ ```dax
48
+ SalesMTD_vs_PY =
49
+ VAR _CurrentMTD = [SalesMTD]
50
+ VAR _PriorYearMTD =
51
+ CALCULATE(
52
+ [SalesMTD],
53
+ SAMEPERIODLASTYEAR('Date'[Date])
54
+ )
55
+ RETURN
56
+ DIVIDE(_CurrentMTD - _PriorYearMTD, _PriorYearMTD)
57
+ ```
58
+
59
+ ## Quarter-to-Date (QTD)
60
+
61
+ ### Basic QTD
62
+ ```dax
63
+ SalesQTD =
64
+ TOTALQTD(
65
+ SUM(Sales[Amount]),
66
+ 'Date'[Date]
67
+ )
68
+ ```
69
+
70
+ ## Rolling Periods
71
+
72
+ ### Rolling 3-Month Average
73
+ ```dax
74
+ Sales_Rolling3M_Avg =
75
+ AVERAGEX(
76
+ DATESINPERIOD(
77
+ 'Date'[Date],
78
+ MAX('Date'[Date]),
79
+ -3,
80
+ MONTH
81
+ ),
82
+ CALCULATE(SUM(Sales[Amount]))
83
+ )
84
+ ```
85
+
86
+ ### Rolling 12-Month Total
87
+ ```dax
88
+ Sales_Rolling12M =
89
+ CALCULATE(
90
+ SUM(Sales[Amount]),
91
+ DATESINPERIOD(
92
+ 'Date'[Date],
93
+ MAX('Date'[Date]),
94
+ -12,
95
+ MONTH
96
+ )
97
+ )
98
+ ```
99
+
100
+ ### Rolling 12-Month Average (Safe)
101
+ ```dax
102
+ Sales_Rolling12M_Avg =
103
+ VAR _Period =
104
+ DATESINPERIOD(
105
+ 'Date'[Date],
106
+ MAX('Date'[Date]),
107
+ -12,
108
+ MONTH
109
+ )
110
+ VAR _MonthsWithData =
111
+ COUNTROWS(
112
+ FILTER(
113
+ _Period,
114
+ CALCULATE(SUM(Sales[Amount])) > 0
115
+ )
116
+ )
117
+ VAR _Total =
118
+ CALCULATE(
119
+ SUM(Sales[Amount]),
120
+ _Period
121
+ )
122
+ RETURN
123
+ DIVIDE(_Total, _MonthsWithData)
124
+ ```
125
+
126
+ ## Period Comparisons
127
+
128
+ ### Prior Year
129
+ ```dax
130
+ Sales_PY =
131
+ CALCULATE(
132
+ SUM(Sales[Amount]),
133
+ SAMEPERIODLASTYEAR('Date'[Date])
134
+ )
135
+ ```
136
+
137
+ ### Prior Month
138
+ ```dax
139
+ Sales_PM =
140
+ CALCULATE(
141
+ SUM(Sales[Amount]),
142
+ PREVIOUSMONTH('Date'[Date])
143
+ )
144
+ ```
145
+
146
+ ### Year-over-Year Growth %
147
+ ```dax
148
+ Sales_YoY_Pct =
149
+ VAR _Current = SUM(Sales[Amount])
150
+ VAR _PY = [Sales_PY]
151
+ RETURN
152
+ DIVIDE(_Current - _PY, _PY)
153
+ ```
154
+
155
+ ### Month-over-Month Growth %
156
+ ```dax
157
+ Sales_MoM_Pct =
158
+ VAR _Current = SUM(Sales[Amount])
159
+ VAR _PM = [Sales_PM]
160
+ RETURN
161
+ DIVIDE(_Current - _PM, _PM)
162
+ ```
163
+
164
+ ## Running Totals
165
+
166
+ ### Cumulative Total (Calendar Year)
167
+ ```dax
168
+ Sales_Cumulative =
169
+ CALCULATE(
170
+ SUM(Sales[Amount]),
171
+ FILTER(
172
+ ALL('Date'),
173
+ 'Date'[Date] <= MAX('Date'[Date])
174
+ && 'Date'[Year] = MAX('Date'[Year])
175
+ )
176
+ )
177
+ ```
178
+
179
+ ### Cumulative Total (All Time)
180
+ ```dax
181
+ Sales_Cumulative_AllTime =
182
+ CALCULATE(
183
+ SUM(Sales[Amount]),
184
+ FILTER(
185
+ ALL('Date'),
186
+ 'Date'[Date] <= MAX('Date'[Date])
187
+ )
188
+ )
189
+ ```
190
+
191
+ ## Usage Notes
192
+
193
+ - All patterns assume a properly configured Date table marked as Date Table
194
+ - Replace `Sales[Amount]` with your measure or column
195
+ - Replace `'Date'[Date]` with your date column
196
+ - For fiscal years, adjust the year-end date in TOTALYTD
@@ -0,0 +1,477 @@
1
+ # DAX User-Defined Functions (UDFs) — Reference Wiki
2
+
3
+ > **Status**: Public Preview (since September 2025)
4
+ > **Compatibility**: Level 1702+ (Fabric / recent Power BI Desktop)
5
+ > **Last updated**: March 2026
6
+
7
+ This document is a ground-truth reference for DAX User-Defined Functions. Most LLMs lack training data on this feature because it was announced in April 2025 and entered public preview in September 2025.
8
+
9
+ ---
10
+
11
+ ## What Are DAX UDFs?
12
+
13
+ User-Defined Functions (UDFs) let you encapsulate reusable DAX logic as named, parameterized functions. Before UDFs, the only reuse mechanism was copy-pasting measure logic or using calculation groups (a different concept).
14
+
15
+ **Key benefits:**
16
+ - Write once, call from any measure or query
17
+ - Typed parameters with explicit evaluation modes
18
+ - Shareable via DAX Lib community repository
19
+ - Git-friendly storage in TMDL format
20
+
21
+ ---
22
+
23
+ ## Complete Syntax
24
+
25
+ ### Query-Scoped (DEFINE FUNCTION)
26
+
27
+ Used in DAX queries (DAX Studio, Fabric notebooks, XMLA):
28
+
29
+ ```dax
30
+ DEFINE
31
+ FUNCTION <FunctionName>(
32
+ <param1> : <Type> [<Mode>] [= <default>],
33
+ <param2> : <Type> [<Mode>] [= <default>]
34
+ ) = <SingleDAXExpression>
35
+
36
+ EVALUATE <query using FunctionName>
37
+ ```
38
+
39
+ ### Model-Scoped (TMDL)
40
+
41
+ Stored permanently in the semantic model:
42
+
43
+ ```tmdl
44
+ /// Optional description comment
45
+ createOrReplace function <FunctionName>(
46
+ <param1> : <Type> [<Mode>] [= <default>],
47
+ <param2> : <Type> [<Mode>] [= <default>]
48
+ ) = <SingleDAXExpression>
49
+ ```
50
+
51
+ Model UDFs are stored in `functions.tmdl` within PBIP projects.
52
+
53
+ ---
54
+
55
+ ## Parameter Type Reference
56
+
57
+ ### Scalar Types
58
+
59
+ | Type | DAX Equivalent | Example Values |
60
+ |------|---------------|----------------|
61
+ | `Variant` | Untyped | Any single value |
62
+ | `Int64` | Whole number | `42`, `-7`, `0` |
63
+ | `Decimal` | Fixed decimal | `3.14`, `99.99` |
64
+ | `Double` | Floating point | `1.23e10` |
65
+ | `String` | Text | `"Hello"`, `"North"` |
66
+ | `DateTime` | Date/time | `DATE(2025, 1, 1)` |
67
+ | `Boolean` | True/false | `TRUE`, `FALSE` |
68
+ | `Numeric` | Any number | Int64, Decimal, or Double |
69
+
70
+ ### Aggregate Types
71
+
72
+ | Type | Description | Use Case |
73
+ |------|-------------|----------|
74
+ | `Scalar` | Any single value | General-purpose params |
75
+ | `AnyVal` | Default if type omitted | Loosely typed params |
76
+ | `Table` | Table expression | Iterator-style functions |
77
+ | `AnyRef` | Column/table reference | Advanced reference passing |
78
+
79
+ ---
80
+
81
+ ## VAL vs EXPR — Deep Dive
82
+
83
+ ### VAL (Value Mode) — Default
84
+
85
+ ```
86
+ Evaluation: EAGER — computed once at call site
87
+ Analogy: Like assigning to a DAX variable (VAR x = ...)
88
+ ```
89
+
90
+ The caller evaluates the expression, and the function receives the **result**.
91
+
92
+ ```dax
93
+ DEFINE
94
+ FUNCTION Double(x : Int64 VAL) = x * 2
95
+
96
+ EVALUATE { Double(SUM(Sales[Amount])) }
97
+ -- SUM(Sales[Amount]) runs once, result is doubled
98
+ ```
99
+
100
+ ### EXPR (Expression Mode) — Lazy
101
+
102
+ ```
103
+ Evaluation: LAZY — re-computed in each row context inside the function
104
+ Analogy: Like a callback or lambda expression
105
+ ```
106
+
107
+ The function receives the **unevaluated expression** and evaluates it in its own context.
108
+
109
+ ```dax
110
+ DEFINE
111
+ FUNCTION SumPositive(data : Table, expr : Scalar EXPR) =
112
+ SUMX(FILTER(data, expr > 0), expr)
113
+
114
+ EVALUATE { SumPositive(Sales, Sales[Amount]) }
115
+ -- Sales[Amount] is evaluated per-row inside FILTER and SUMX
116
+ ```
117
+
118
+ ### Decision Matrix
119
+
120
+ | Scenario | Use | Why |
121
+ |----------|-----|-----|
122
+ | Param is a total/aggregate | VAL | Already computed, no row context needed |
123
+ | Param used inside SUMX/FILTER | EXPR | Needs per-row evaluation |
124
+ | Param is a fixed threshold | VAL | Constant value |
125
+ | Param is a column expression | EXPR | Must resolve per row |
126
+ | Performance is critical | VAL | Single evaluation |
127
+ | Building iterator abstractions | EXPR | Requires lazy evaluation |
128
+
129
+ ### The Classic Bug
130
+
131
+ ```dax
132
+ -- BUG: VAL mode inside an iterator
133
+ DEFINE
134
+ FUNCTION BrokenSum(t : Table, val : Scalar VAL) =
135
+ SUMX(t, val)
136
+ -- 'val' is a fixed number. SUMX just multiplies it by row count!
137
+
138
+ -- FIX: Use EXPR
139
+ DEFINE
140
+ FUNCTION CorrectSum(t : Table, expr : Scalar EXPR) =
141
+ SUMX(t, expr)
142
+ -- 'expr' re-evaluates per row as expected
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Model-Independent vs Model-Dependent UDFs
148
+
149
+ ### Model-Independent (Portable — Recommended)
150
+
151
+ All data the function needs is passed through parameters. The function body does NOT reference any model objects directly.
152
+
153
+ ```dax
154
+ -- Model-independent: all inputs via parameters
155
+ DEFINE
156
+ FUNCTION GrowthRate(current : Scalar, previous : Scalar) =
157
+ DIVIDE(current - previous, ABS(previous))
158
+
159
+ EVALUATE { GrowthRate([TotalSales], [PriorYearSales]) }
160
+ ```
161
+
162
+ **Benefits:**
163
+ - Portable across models
164
+ - Compatible with DAX Lib sharing
165
+ - Easy to test in isolation
166
+ - No hidden dependencies
167
+
168
+ ### Model-Dependent (Tied to Model)
169
+
170
+ The function body directly references model objects:
171
+
172
+ ```dax
173
+ -- Model-dependent: references Sales table directly
174
+ DEFINE
175
+ FUNCTION TotalSalesInRegion(region : String) =
176
+ CALCULATE(SUM(Sales[Amount]), Region[Name] = region)
177
+ ```
178
+
179
+ ### Refactoring Tip
180
+
181
+ Convert model-dependent to model-independent by extracting references to parameters:
182
+
183
+ ```dax
184
+ -- Before (model-dependent)
185
+ FUNCTION TopNSales(n : Int64) =
186
+ CALCULATE(SUM(Sales[Amount]), TOPN(n, ALL(Products), SUM(Sales[Amount])))
187
+
188
+ -- After (model-independent)
189
+ FUNCTION TopNByMeasure(measure : Scalar EXPR, n : Int64, data : Table) =
190
+ CALCULATE(measure, TOPN(n, data, measure))
191
+ ```
192
+
193
+ ---
194
+
195
+ ## NAMEOF and TABLEOF (February 2026)
196
+
197
+ These companion functions enable safe model-object references inside UDFs:
198
+
199
+ | Function | Returns | Example |
200
+ |----------|---------|---------|
201
+ | `NAMEOF(column)` | Full column path as string | `NAMEOF(Sales[Amount])` → `"Sales[Amount]"` |
202
+ | `TABLEOF(column)` | Table name as string | `TABLEOF(Sales[Amount])` → `"Sales"` |
203
+
204
+ **Why they matter:** If you reference `Sales[Amount]` as a string in a UDF and the column is renamed to `Sales[Revenue]`, the string breaks silently. NAMEOF/TABLEOF track renames automatically.
205
+
206
+ ```dax
207
+ -- Safe reference pattern
208
+ DEFINE
209
+ FUNCTION DescribeColumn(col : AnyRef) =
210
+ "Column " & NAMEOF(col) & " in table " & TABLEOF(col)
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Practical Patterns
216
+
217
+ ### Financial Calculations
218
+
219
+ ```dax
220
+ DEFINE
221
+ -- Compound Annual Growth Rate
222
+ FUNCTION CAGR(
223
+ beginValue : Decimal,
224
+ endValue : Decimal,
225
+ years : Decimal
226
+ ) = IF(
227
+ beginValue > 0 && years > 0,
228
+ POWER(endValue / beginValue, 1 / years) - 1,
229
+ BLANK()
230
+ )
231
+
232
+ -- Safe Margin
233
+ FUNCTION SafeMargin(revenue : Decimal, cost : Decimal) =
234
+ IF(revenue = 0, BLANK(), (revenue - cost) / revenue)
235
+
236
+ -- Safe Division with alternate result
237
+ FUNCTION SafeDivide(
238
+ numerator : Scalar,
239
+ denominator : Scalar,
240
+ alternateResult : Scalar VAL = BLANK()
241
+ ) = IF(denominator = 0 || ISBLANK(denominator), alternateResult, numerator / denominator)
242
+ ```
243
+
244
+ ### Statistical Functions
245
+
246
+ ```dax
247
+ DEFINE
248
+ -- Coefficient of Variation
249
+ FUNCTION CoeffOfVariation(
250
+ data : Table,
251
+ expr : Scalar EXPR
252
+ ) =
253
+ VAR _avg = AVERAGEX(data, expr)
254
+ VAR _stdev = STDEVX.P(data, expr)
255
+ RETURN DIVIDE(_stdev, _avg)
256
+
257
+ -- Z-Score
258
+ FUNCTION ZScore(value : Numeric, mean : Numeric, stdDev : Numeric) =
259
+ IF(stdDev <> 0, (value - mean) / stdDev, BLANK())
260
+ ```
261
+
262
+ ### Date Utilities
263
+
264
+ ```dax
265
+ DEFINE
266
+ -- Working days between two dates (simplified, no holidays)
267
+ FUNCTION WorkingDays(startDate : DateTime, endDate : DateTime) =
268
+ VAR _totalDays = INT(endDate - startDate)
269
+ VAR _fullWeeks = INT(_totalDays / 7)
270
+ VAR _remainingDays = MOD(_totalDays, 7)
271
+ RETURN _fullWeeks * 5 + MIN(_remainingDays, 5)
272
+
273
+ -- Fiscal quarter with configurable start month
274
+ FUNCTION FiscalQuarter(
275
+ dateValue : DateTime,
276
+ fiscalStartMonth : Int64 VAL = 7
277
+ ) =
278
+ VAR _adjustedMonth = MOD(MONTH(dateValue) - fiscalStartMonth + 12, 12)
279
+ RETURN INT(_adjustedMonth / 3) + 1
280
+
281
+ -- Fiscal year
282
+ FUNCTION FiscalYear(
283
+ dateValue : DateTime,
284
+ fiscalStartMonth : Int64 VAL = 7
285
+ ) = IF(
286
+ MONTH(dateValue) >= fiscalStartMonth,
287
+ YEAR(dateValue) + 1,
288
+ YEAR(dateValue)
289
+ )
290
+ ```
291
+
292
+ ### Data Quality
293
+
294
+ ```dax
295
+ DEFINE
296
+ -- Coalesce: return first non-blank value
297
+ FUNCTION Coalesce3(a : Scalar, b : Scalar, c : Scalar) =
298
+ IF(NOT(ISBLANK(a)), a, IF(NOT(ISBLANK(b)), b, c))
299
+
300
+ -- Clamp value to range
301
+ FUNCTION Clamp(
302
+ value : Decimal,
303
+ minVal : Decimal,
304
+ maxVal : Decimal
305
+ ) = MIN(MAX(value, minVal), maxVal)
306
+
307
+ -- Check if value is in range
308
+ FUNCTION IsInRange(value : Numeric, minVal : Numeric, maxVal : Numeric) =
309
+ value >= minVal && value <= maxVal
310
+ ```
311
+
312
+ ### Text Utilities
313
+
314
+ ```dax
315
+ DEFINE
316
+ -- Extract initials from full name
317
+ FUNCTION GetInitials(fullName : String) =
318
+ VAR _Cleaned = TRIM(fullName)
319
+ RETURN
320
+ CONCATENATEX(
321
+ GENERATESERIES(1, LEN(_Cleaned) - LEN(SUBSTITUTE(_Cleaned, " ", "")) + 1),
322
+ UPPER(MID(_Cleaned, IF([Value] = 1, 1, FIND("§", SUBSTITUTE(_Cleaned, " ", "§", [Value] - 1)) + 1), 1)),
323
+ ""
324
+ )
325
+ ```
326
+
327
+ ### Conditional Aggregation (Iterator-Style)
328
+
329
+ ```dax
330
+ DEFINE
331
+ -- Sum values matching a condition (uses EXPR mode)
332
+ FUNCTION SumWhere(
333
+ data : Table,
334
+ amount : Scalar EXPR,
335
+ condition : Boolean EXPR
336
+ ) = SUMX(FILTER(data, condition), amount)
337
+
338
+ EVALUATE
339
+ { SumWhere(Sales, Sales[Amount], Sales[Status] = "Completed") }
340
+ ```
341
+
342
+ ---
343
+
344
+ ## DAX Lib — Community UDF Repository
345
+
346
+ [DAX Lib](https://daxlib.org) is the community repository for model-independent UDFs.
347
+
348
+ ### Consuming Packages
349
+
350
+ 1. Open **Tabular Editor 3**
351
+ 2. Navigate to **Tools > DAX Package Manager**
352
+ 3. Browse or search packages
353
+ 4. Click **Install** — functions are added to your model's `functions.tmdl`
354
+
355
+ ### Popular Package Categories
356
+
357
+ | Category | Examples |
358
+ |----------|---------|
359
+ | Financial | CAGR, IRR, NPV, XIRR |
360
+ | Statistical | Median, Percentile, StdDev, ZScore |
361
+ | Date | FiscalYear, WorkingDays, DateDiff |
362
+ | Text | ProperCase, ExtractNumber, Slugify |
363
+ | Data Quality | Coalesce, Clamp, IsInRange |
364
+
365
+ ### Publishing Your Own
366
+
367
+ 1. Write model-independent functions
368
+ 2. Add documentation comments (`///`)
369
+ 3. Follow DAX Lib naming conventions
370
+ 4. Submit via the DAX Lib GitHub repository
371
+
372
+ ---
373
+
374
+ ## Workflow: Creating Your First UDF
375
+
376
+ ### Step 1: Prototype in DAX Studio
377
+
378
+ ```dax
379
+ -- Test the logic with DEFINE FUNCTION
380
+ DEFINE
381
+ FUNCTION GrowthRate(current : Decimal, previous : Decimal) =
382
+ DIVIDE(current - previous, ABS(previous))
383
+
384
+ MEASURE Sales[TestGrowth] =
385
+ GrowthRate([TotalSales], [PriorYearSales])
386
+
387
+ EVALUATE
388
+ SUMMARIZECOLUMNS(
389
+ 'Date'[Year],
390
+ "Sales", [TotalSales],
391
+ "PY", [PriorYearSales],
392
+ "Growth", [TestGrowth]
393
+ )
394
+ ```
395
+
396
+ ### Step 2: Promote to Model (TMDL)
397
+
398
+ ```tmdl
399
+ /// Growth rate between two values, handling zero and negative denominators
400
+ createOrReplace function GrowthRate(
401
+ current : Decimal,
402
+ previous : Decimal
403
+ ) = DIVIDE(current - previous, ABS(previous))
404
+ ```
405
+
406
+ ### Step 3: Use in Measures
407
+
408
+ ```dax
409
+ SalesGrowthYoY = GrowthRate([TotalSales], [PriorYearSales])
410
+ MarginGrowth = GrowthRate([Margin], [PriorYearMargin])
411
+ ```
412
+
413
+ ---
414
+
415
+ ## Tooling Support
416
+
417
+ | Tool | Create | Edit | Execute | Notes |
418
+ |------|--------|------|---------|-------|
419
+ | **Tabular Editor 3** | Yes | Yes | Yes | Best authoring experience |
420
+ | **DAX Studio** | Query-scoped | Query-scoped | Yes | DEFINE FUNCTION only |
421
+ | **PBIP / TMDL** | Yes | Yes | Yes | Edit `functions.tmdl` directly |
422
+ | **XMLA endpoint** | Yes | Yes | Yes | Script deployment |
423
+ | **Power BI Desktop** | Via TE3 | Via TE3 | Yes | No native UI yet |
424
+ | **Power BI Service** | No | No | Yes | Read-only consumption |
425
+ | **Fabric notebooks** | Query-scoped | Query-scoped | Yes | DAX cell magic |
426
+
427
+ ---
428
+
429
+ ## Limitations Summary
430
+
431
+ | Limitation | Status |
432
+ |-----------|--------|
433
+ | No recursion | By design |
434
+ | No function overloading | By design |
435
+ | No optional params in DEFINE FUNCTION | Query-scoped only; model UDFs support defaults |
436
+ | No explicit return type annotation | Inferred from body |
437
+ | Cannot create via Power BI Service UI | Use TMDL, TE3, or XMLA |
438
+ | Compatibility level 1702+ required | Fabric and recent Desktop |
439
+ | Public preview | Behavior may change before GA |
440
+
441
+ ---
442
+
443
+ ## Naming Conventions
444
+
445
+ | Convention | Example | When |
446
+ |-----------|---------|------|
447
+ | PascalCase | `GrowthRate`, `SafeDivide` | All UDFs |
448
+ | Verb-first | `CalculateGrowth`, `FormatCurrency` | Action functions |
449
+ | Is/Has prefix | `IsInRange`, `HasValue` | Boolean-returning functions |
450
+ | Underscore prefix | `_InternalHelper` | Private/helper functions |
451
+ | Category prefix | `Fin_CAGR`, `Stat_Median` | Library/package functions |
452
+
453
+ ---
454
+
455
+ ## UDFs vs Measures vs Calculation Groups
456
+
457
+ | Aspect | UDFs | Measures | Calculation Groups |
458
+ |--------|------|----------|--------------------|
459
+ | **Parameters** | Yes, typed | No | Implicit (selected item) |
460
+ | **Storage** | Model or query | Model | Model |
461
+ | **Reusability** | Call from anywhere | Reference in visuals | Apply to any measure |
462
+ | **Use case** | Shared logic | Business metrics | Format/time intelligence templates |
463
+ | **Composable** | Yes | Via other measures | Limited |
464
+
465
+ ---
466
+
467
+ ## External References
468
+
469
+ - [Microsoft Learn — DAX User-Defined Functions](https://learn.microsoft.com/dax/user-defined-functions)
470
+ - [SQLBI — Introduction to DAX UDFs](https://www.sqlbi.com/articles/introduction-to-dax-user-defined-functions/)
471
+ - [SQLBI — VAL vs EXPR Deep Dive](https://www.sqlbi.com/articles/val-and-expr-evaluation-modes-in-dax-user-defined-functions/)
472
+ - [DAX Lib — Community Repository](https://daxlib.org)
473
+ - [Tabular Editor — DAX Package Manager](https://docs.tabulareditor.com/dax-package-manager)
474
+
475
+ ---
476
+
477
+ *This wiki is maintained as part of BI Agent Superpowers. If you find outdated information, please update this file and run `bi-superpowers recharge`.*