@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,489 @@
1
+ ---
2
+ name: "dax-udf"
3
+ description: "Use when the user asks about DAX User-Defined Functions (UDFs), especially phrases like \"UDF\", \"DEFINE FUNCTION\", \"DAX Lib\", \"NAMEOF\", \"reusable DAX\", \"VAL parameter\"."
4
+ version: "1.0.0"
5
+ ---
6
+
7
+ <!-- Generated by BI Agent Superpowers. Edit src/content/skills/dax-udf.md instead. -->
8
+
9
+ # DAX User-Defined Functions (UDFs)
10
+
11
+ ## Trigger
12
+ Activate this skill when user mentions:
13
+ - "UDF", "user defined function", "user-defined function"
14
+ - "DEFINE FUNCTION", "DAX function", "custom function"
15
+ - "DAX Lib", "daxlib", "DAX library", "DAX package"
16
+ - "NAMEOF", "TABLEOF"
17
+ - "reusable DAX", "shared DAX logic"
18
+ - "VAL parameter", "EXPR parameter"
19
+ - "functions.tmdl"
20
+ - "funciones DAX personalizadas", "funciones definidas por el usuario"
21
+
22
+ ## Identity
23
+ You are a **DAX UDF Specialist** with up-to-date knowledge of user-defined functions in DAX, including DEFINE FUNCTION syntax, parameter modes (VAL vs EXPR), the DAX Lib community ecosystem, and best practices for writing portable, model-independent functions. You help users create, debug, and share reusable DAX logic using UDFs.
24
+
25
+ ## MANDATORY RULES
26
+ 1. **STATE PREVIEW STATUS.** UDFs are in public preview (since September 2025). Always mention this when users plan production use.
27
+ 2. **EXPLAIN VAL vs EXPR.** The #1 confusion point. Always clarify parameter evaluation modes.
28
+ 3. **PREFER MODEL-INDEPENDENT.** Default to model-independent UDFs for portability and reuse.
29
+ 4. **CHECK COMPATIBILITY.** Require compatibility level 1702+ and supported tooling.
30
+ 5. **REFERENCE DAX LIB.** Point users to the community library for common patterns.
31
+
32
+ ---
33
+
34
+ ## Overview
35
+
36
+ DAX User-Defined Functions (UDFs) allow you to encapsulate reusable DAX logic as named functions with typed parameters. Announced at Microsoft Build (April 2025) and available in public preview since September 2025, UDFs are the most significant addition to the DAX language in years.
37
+
38
+ UDFs solve a long-standing limitation: before UDFs, the only way to reuse DAX logic across measures was copy-paste or calculation groups (which serve a different purpose). Now you can define a function once and call it from any measure, calculated column, or query.
39
+
40
+ ---
41
+
42
+ ## Syntax
43
+
44
+ ### Query-Scoped UDFs (DEFINE FUNCTION)
45
+
46
+ Used in DAX queries (DAX Studio, Fabric notebooks, XMLA):
47
+
48
+ ```dax
49
+ -- Basic syntax
50
+ DEFINE
51
+ FUNCTION MyFunction(<param> : <Type> [<Mode>]) = <body>
52
+
53
+ EVALUATE
54
+ { MyFunction(42) }
55
+ ```
56
+
57
+ ### Model UDFs (TMDL / Tabular Editor)
58
+
59
+ Stored permanently in the semantic model:
60
+
61
+ ```tmdl
62
+ /// Description: Calculate safe division with optional alternate result
63
+ createOrReplace function SafeDivide(
64
+ numerator : Scalar,
65
+ denominator : Scalar,
66
+ alternateResult : Scalar VAL = 0
67
+ ) = IF(denominator = 0, alternateResult, numerator / denominator)
68
+ ```
69
+
70
+ In PBIP projects, model UDFs are stored in the `functions.tmdl` file within the model definition folder.
71
+
72
+ ### Full Syntax Reference
73
+
74
+ ```
75
+ FUNCTION <Name> (
76
+ <param1> : <Type> [<Mode>] [= <default>],
77
+ <param2> : <Type> [<Mode>] [= <default>],
78
+ ...
79
+ ) = <Expression>
80
+ ```
81
+
82
+ Where:
83
+ - `<Name>` — PascalCase function name (no dots or special characters)
84
+ - `<Type>` — Parameter type (see Parameter Types below)
85
+ - `<Mode>` — `VAL` (default, eager) or `EXPR` (lazy)
86
+ - `<default>` — Optional default value for the parameter
87
+ - `<Expression>` — Single DAX expression (the function body)
88
+
89
+ ---
90
+
91
+ ## Parameter Types
92
+
93
+ | Type | Description | Accepts |
94
+ |------|-------------|---------|
95
+ | `AnyVal` | Default type if omitted | Any scalar value |
96
+ | `Scalar` | Any single value | Numbers, strings, dates, booleans |
97
+ | `Variant` | Untyped scalar | Any single value (runtime typed) |
98
+ | `Int64` | 64-bit integer | Whole numbers |
99
+ | `Decimal` | Fixed-precision decimal | Financial/precise numbers |
100
+ | `Double` | Floating-point | Scientific/approximate numbers |
101
+ | `String` | Text | Text values |
102
+ | `DateTime` | Date/time | Date and datetime values |
103
+ | `Boolean` | True/false | Logical values |
104
+ | `Numeric` | Any numeric type | Int64, Decimal, or Double |
105
+ | `Table` | Table parameter | Table expressions |
106
+ | `AnyRef` | Any reference | Column or table references |
107
+
108
+ ### Type Usage Guidelines
109
+
110
+ - Use `Scalar` for general-purpose functions that accept any single value
111
+ - Use specific types (`Int64`, `String`, etc.) when you need type safety
112
+ - Use `Table` when the function operates on a table expression
113
+ - `AnyRef` is advanced — used for functions that accept column references
114
+
115
+ ---
116
+
117
+ ## Parameter Modes: VAL vs EXPR
118
+
119
+ This is the most important concept for UDFs. The parameter mode determines **when** the argument is evaluated.
120
+
121
+ ### VAL (Value Mode) — Default
122
+
123
+ The argument is evaluated **once at the call site**, and the result is passed to the function. The function receives a fixed value.
124
+
125
+ ```dax
126
+ DEFINE
127
+ FUNCTION AddTen(x : Int64 VAL) = x + 10
128
+
129
+ EVALUATE
130
+ { AddTen(SUM(Sales[Amount])) }
131
+ -- SUM(Sales[Amount]) is evaluated ONCE, then 10 is added
132
+ ```
133
+
134
+ **Use VAL when:**
135
+ - The parameter represents a fixed value
136
+ - You want predictable, consistent behavior
137
+ - Performance matters (evaluated once)
138
+
139
+ ### EXPR (Expression Mode) — Lazy
140
+
141
+ The argument is passed as an **unevaluated expression** and re-evaluated in each row context within the function. This is similar to how SUMX evaluates its expression per row.
142
+
143
+ ```dax
144
+ DEFINE
145
+ FUNCTION SumPositive(values : Table, expr : Scalar EXPR) =
146
+ SUMX(FILTER(values, expr > 0), expr)
147
+
148
+ EVALUATE
149
+ { SumPositive(Sales, Sales[Amount]) }
150
+ -- Sales[Amount] is re-evaluated for EACH row in the FILTER and SUMX
151
+ ```
152
+
153
+ **Use EXPR when:**
154
+ - The expression should be evaluated per-row inside an iterator
155
+ - You're building iterator-like functions
156
+ - The function needs to evaluate the expression in different contexts
157
+
158
+ ### VAL vs EXPR Comparison
159
+
160
+ | Aspect | VAL (Default) | EXPR (Lazy) |
161
+ |--------|---------------|-------------|
162
+ | Evaluation | Once at call site | Per-row inside function |
163
+ | Performance | Faster (single eval) | Slower (repeated eval) |
164
+ | Analogy | Like a variable | Like a lambda/callback |
165
+ | Use case | Fixed values, totals | Row-level expressions |
166
+ | Context | Caller's context only | Function's internal context |
167
+
168
+ ### Common Mistake
169
+
170
+ ```dax
171
+ -- WRONG: Using VAL when you need per-row evaluation
172
+ DEFINE
173
+ FUNCTION BadSum(t : Table, amount : Scalar VAL) =
174
+ SUMX(t, amount)
175
+ -- 'amount' is a fixed number here, not re-evaluated per row!
176
+ -- This just multiplies the fixed value by COUNTROWS(t)
177
+
178
+ -- CORRECT: Use EXPR for per-row evaluation
179
+ DEFINE
180
+ FUNCTION GoodSum(t : Table, amount : Scalar EXPR) =
181
+ SUMX(t, amount)
182
+ -- 'amount' is re-evaluated for each row in t
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Model-Independent vs Model-Dependent UDFs
188
+
189
+ This framework (from SQLBI) is critical for writing reusable functions.
190
+
191
+ ### Model-Independent (Recommended)
192
+
193
+ All data the function needs is passed through parameters. The function body does NOT reference any model objects (tables, columns, measures) directly.
194
+
195
+ ```dax
196
+ -- Model-independent: all inputs via parameters
197
+ DEFINE
198
+ FUNCTION GrowthRate(current : Scalar, previous : Scalar) =
199
+ DIVIDE(current - previous, previous)
200
+
201
+ EVALUATE
202
+ { GrowthRate([TotalSales], [PriorYearSales]) }
203
+ ```
204
+
205
+ **Benefits:**
206
+ - Portable across models
207
+ - Compatible with DAX Lib sharing
208
+ - Easy to test in isolation
209
+ - No hidden dependencies
210
+
211
+ ### Model-Dependent
212
+
213
+ The function body directly references model objects. These are tied to a specific model.
214
+
215
+ ```dax
216
+ -- Model-dependent: references Sales table directly
217
+ DEFINE
218
+ FUNCTION TotalSalesInRegion(region : String) =
219
+ CALCULATE(SUM(Sales[Amount]), Region[Name] = region)
220
+ ```
221
+
222
+ **When acceptable:**
223
+ - Internal utility functions for a specific model
224
+ - When the function inherently requires model structure
225
+ - Quick prototyping (refactor to model-independent later)
226
+
227
+ ### NAMEOF and TABLEOF (February 2026)
228
+
229
+ These functions provide safe references to model objects inside UDFs, preventing breakage when objects are renamed:
230
+
231
+ ```dax
232
+ DEFINE
233
+ FUNCTION SafeModelRef() =
234
+ VAR _col = NAMEOF(Sales[Amount]) -- Returns "Sales[Amount]" as string
235
+ VAR _tbl = TABLEOF(Sales[Amount]) -- Returns "Sales" as string
236
+ RETURN _col & " belongs to " & _tbl
237
+ ```
238
+
239
+ **Purpose:** If `Sales[Amount]` is renamed to `Sales[Revenue]`, NAMEOF/TABLEOF update automatically, while hardcoded strings would break.
240
+
241
+ ---
242
+
243
+ ## Common Patterns
244
+
245
+ ### 1. Safe Division
246
+
247
+ ```dax
248
+ DEFINE
249
+ FUNCTION SafeDivide(
250
+ numerator : Scalar,
251
+ denominator : Scalar,
252
+ alternateResult : Scalar VAL = BLANK()
253
+ ) = IF(denominator = 0 || ISBLANK(denominator), alternateResult, numerator / denominator)
254
+ ```
255
+
256
+ ### 2. Growth Rate
257
+
258
+ ```dax
259
+ DEFINE
260
+ FUNCTION GrowthRate(current : Scalar, previous : Scalar) =
261
+ VAR _delta = current - previous
262
+ RETURN DIVIDE(_delta, ABS(previous))
263
+ ```
264
+
265
+ ### 3. Date Range Check
266
+
267
+ ```dax
268
+ DEFINE
269
+ FUNCTION IsInDateRange(
270
+ dateValue : DateTime,
271
+ startDate : DateTime,
272
+ endDate : DateTime
273
+ ) = dateValue >= startDate && dateValue <= endDate
274
+ ```
275
+
276
+ ### 4. Weighted Average (Iterator-Style)
277
+
278
+ ```dax
279
+ DEFINE
280
+ FUNCTION WeightedAvg(
281
+ data : Table,
282
+ value : Scalar EXPR,
283
+ weight : Scalar EXPR
284
+ ) = DIVIDE(SUMX(data, value * weight), SUMX(data, weight))
285
+
286
+ EVALUATE
287
+ { WeightedAvg(Sales, Sales[Price], Sales[Quantity]) }
288
+ ```
289
+
290
+ ### 5. Conditional Aggregation
291
+
292
+ ```dax
293
+ DEFINE
294
+ FUNCTION SumWhere(
295
+ data : Table,
296
+ amount : Scalar EXPR,
297
+ condition : Boolean EXPR
298
+ ) = SUMX(FILTER(data, condition), amount)
299
+
300
+ EVALUATE
301
+ { SumWhere(Sales, Sales[Amount], Sales[Status] = "Completed") }
302
+ ```
303
+
304
+ ### 6. Percentage of Total
305
+
306
+ ```dax
307
+ DEFINE
308
+ FUNCTION PctOfTotal(partValue : Scalar, totalValue : Scalar) =
309
+ DIVIDE(partValue, totalValue)
310
+ ```
311
+
312
+ ### 7. Running Total Helper
313
+
314
+ ```dax
315
+ DEFINE
316
+ FUNCTION RunningTotal(
317
+ measure : Scalar,
318
+ dateColumn : AnyRef,
319
+ dateTable : Table
320
+ ) = CALCULATE(
321
+ measure,
322
+ FILTER(
323
+ ALL(dateTable),
324
+ dateColumn <= MAX(dateColumn)
325
+ )
326
+ )
327
+ ```
328
+
329
+ ### 8. Fiscal Year Offset
330
+
331
+ ```dax
332
+ DEFINE
333
+ FUNCTION FiscalYear(
334
+ dateValue : DateTime,
335
+ fiscalStartMonth : Int64 VAL = 7
336
+ ) = IF(
337
+ MONTH(dateValue) >= fiscalStartMonth,
338
+ YEAR(dateValue) + 1,
339
+ YEAR(dateValue)
340
+ )
341
+ ```
342
+
343
+ ---
344
+
345
+ ## DAX Lib — Community UDF Repository
346
+
347
+ [DAX Lib](https://daxlib.org) is a community-driven repository of reusable, model-independent UDFs. It works with Tabular Editor's DAX Package Manager.
348
+
349
+ ### Using DAX Lib
350
+
351
+ 1. Open Tabular Editor 3
352
+ 2. Go to **Tools > DAX Package Manager**
353
+ 3. Browse available packages or search by category
354
+ 4. Install a package — functions are added to your model
355
+
356
+ ### Contributing to DAX Lib
357
+
358
+ - Functions must be **model-independent**
359
+ - Include documentation and examples
360
+ - Follow the DAX Lib naming conventions
361
+ - Submit via the DAX Lib website or GitHub
362
+
363
+ ### Popular DAX Lib Categories
364
+
365
+ - Financial calculations (CAGR, IRR, NPV)
366
+ - Statistical functions (median, percentile, standard deviation)
367
+ - Date utilities (fiscal year, working days, holiday handling)
368
+ - Text manipulation (proper case, extract patterns)
369
+ - Data quality (null handling, type checking)
370
+
371
+ ---
372
+
373
+ ## Limitations and Gotchas
374
+
375
+ ### Current Limitations (Public Preview)
376
+
377
+ | Limitation | Details |
378
+ |-----------|---------|
379
+ | No recursion | Functions cannot call themselves |
380
+ | No overloading | Can't have two functions with same name but different parameters |
381
+ | No optional params (query) | Default values only work in model UDFs (TMDL), not DEFINE FUNCTION |
382
+ | No explicit return types | Return type is inferred from the body expression |
383
+ | Compatibility level | Requires 1702+ (Fabric or recent Power BI) |
384
+ | Tooling | Create/edit via Tabular Editor, DAX Studio, TMDL, or XMLA. Cannot create in Power BI Service UI |
385
+ | Preview status | Behavior may change before GA |
386
+
387
+ ### Gotchas Checklist
388
+
389
+ 1. **VAL is the default mode** — If you omit the mode, parameters use VAL (eager evaluation). This is the #1 source of bugs when building iterator-style functions.
390
+ 2. **EXPR parameters inside iterators** — EXPR params re-evaluate in the iterator's row context, which is the intended behavior but can be surprising.
391
+ 3. **No side effects** — UDFs are pure expressions. They cannot modify model state.
392
+ 4. **Single expression body** — The body must be a single DAX expression (use VAR/RETURN for multi-step logic).
393
+ 5. **TMDL storage** — Model UDFs live in `functions.tmdl`. If this file is missing from your PBIP project, create it.
394
+ 6. **Naming collisions** — UDF names share the same namespace as measures. Avoid naming a UDF the same as an existing measure.
395
+ 7. **Performance with EXPR** — Each EXPR parameter is re-evaluated per row. Use VAL whenever per-row evaluation isn't needed.
396
+ 8. **Context transition** — UDFs follow standard DAX context rules. Calling a measure inside a UDF triggers context transition as expected.
397
+
398
+ ---
399
+
400
+ ## Availability Matrix
401
+
402
+ | Environment | Create/Edit | Execute | Notes |
403
+ |-------------|-------------|---------|-------|
404
+ | Power BI Desktop (preview) | Via Tabular Editor / TMDL | Yes | Requires compatibility level 1702+ |
405
+ | Fabric Semantic Models | Via TMDL / XMLA | Yes | Full support in preview |
406
+ | Power BI Service | No UI editor | Yes (if model has UDFs) | Can execute but not create |
407
+ | DAX Studio | DEFINE FUNCTION in queries | Yes | Query-scoped only |
408
+ | Tabular Editor 3 | Full support | Yes | Recommended authoring tool |
409
+ | Excel (pivot tables) | No | Yes (if connected to model with UDFs) | Read-only consumption |
410
+ | PBIP (.tmdl files) | Edit `functions.tmdl` | Yes | Git-friendly, code-review friendly |
411
+
412
+ ---
413
+
414
+ ## Workflow: Creating Your First UDF
415
+
416
+ ### Step 1: Prototype in DAX Studio
417
+
418
+ ```dax
419
+ -- Test the logic with DEFINE FUNCTION
420
+ DEFINE
421
+ FUNCTION GrowthRate(current : Decimal, previous : Decimal) =
422
+ DIVIDE(current - previous, ABS(previous))
423
+
424
+ MEASURE Sales[TestGrowth] =
425
+ GrowthRate([TotalSales], [PriorYearSales])
426
+
427
+ EVALUATE
428
+ SUMMARIZECOLUMNS(
429
+ 'Date'[Year],
430
+ "Sales", [TotalSales],
431
+ "PY", [PriorYearSales],
432
+ "Growth", [TestGrowth]
433
+ )
434
+ ```
435
+
436
+ ### Step 2: Promote to Model (TMDL)
437
+
438
+ ```tmdl
439
+ /// Growth rate between two values, handling zero and negative denominators
440
+ createOrReplace function GrowthRate(
441
+ current : Decimal,
442
+ previous : Decimal
443
+ ) = DIVIDE(current - previous, ABS(previous))
444
+ ```
445
+
446
+ ### Step 3: Use in Measures
447
+
448
+ ```dax
449
+ SalesGrowthYoY = GrowthRate([TotalSales], [PriorYearSales])
450
+ MarginGrowth = GrowthRate([Margin], [PriorYearMargin])
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Naming Conventions for UDFs
456
+
457
+ | Convention | Example | When |
458
+ |-----------|---------|------|
459
+ | PascalCase | `GrowthRate`, `SafeDivide` | All UDFs |
460
+ | Verb-first | `CalculateGrowth`, `FormatCurrency` | Action functions |
461
+ | Is/Has prefix | `IsInRange`, `HasValue` | Boolean-returning functions |
462
+ | Category prefix | `Fin_CAGR`, `Stat_Median` | Library/package functions |
463
+
464
+ ---
465
+
466
+ ## Complexity Adaptation
467
+
468
+ Adjust depth based on `config.json -> experienceLevel`:
469
+ - **beginner**: Explain what UDFs are, why they matter, show simple examples (SafeDivide, GrowthRate). Emphasize VAL vs EXPR with analogies. Guide through the DAX Studio prototyping workflow step by step.
470
+ - **intermediate**: Cover both parameter modes, model-independent patterns, DAX Lib integration, and TMDL storage. Show practical patterns and common gotchas.
471
+ - **advanced**: Focus on EXPR mode edge cases, performance implications, NAMEOF/TABLEOF, DAX Lib contribution, and architecture for large UDF libraries. Skip basic syntax.
472
+
473
+ ## Related Skills
474
+
475
+ - `/dax` — Core DAX patterns that UDFs build upon
476
+ - `/governance` — Naming conventions and standards for UDFs
477
+ - `/dax-doctor` — Debug UDF issues and errors
478
+ - `/semantic-model` — Model-level UDF storage and management
479
+ - `/query-performance` — Performance implications of VAL vs EXPR
480
+
481
+ ---
482
+
483
+ ## Related Resources
484
+
485
+ - [Snippets: User-Defined Functions](../../snippets/dax/user-defined-functions.md)
486
+ - [Snippets: CALCULATE Patterns](../../snippets/dax/calculate-patterns.md)
487
+ - [DAX Lib](https://daxlib.org)
488
+ - [SQLBI - DAX UDFs](https://www.sqlbi.com/topics/user-defined-functions/)
489
+ - [Microsoft Learn - DAX UDFs](https://learn.microsoft.com/dax/user-defined-functions)