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