@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.
- package/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +25 -0
- package/AGENTS.md +244 -0
- package/CHANGELOG.md +265 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/bin/build-plugin.js +30 -0
- package/bin/cli.js +1064 -0
- package/bin/commands/add.js +533 -0
- package/bin/commands/add.test.js +77 -0
- package/bin/commands/build-desktop.js +166 -0
- package/bin/commands/changelog.js +443 -0
- package/bin/commands/diff.js +325 -0
- package/bin/commands/lint.js +419 -0
- package/bin/commands/lint.test.js +103 -0
- package/bin/commands/mcp-setup.js +246 -0
- package/bin/commands/pull.js +287 -0
- package/bin/commands/pull.test.js +36 -0
- package/bin/commands/push.js +231 -0
- package/bin/commands/push.test.js +14 -0
- package/bin/commands/search.js +344 -0
- package/bin/commands/search.test.js +115 -0
- package/bin/commands/setup.js +545 -0
- package/bin/commands/setup.test.js +46 -0
- package/bin/commands/sync-profile.js +405 -0
- package/bin/commands/sync-profile.test.js +14 -0
- package/bin/commands/sync-source.js +418 -0
- package/bin/commands/sync-source.test.js +14 -0
- package/bin/commands/watch.js +206 -0
- package/bin/lib/generators/claude-plugin.js +266 -0
- package/bin/lib/generators/claude-plugin.test.js +110 -0
- package/bin/lib/generators/index.js +116 -0
- package/bin/lib/generators/shared.js +282 -0
- package/bin/lib/licensing/index.js +35 -0
- package/bin/lib/licensing/storage.js +364 -0
- package/bin/lib/licensing/storage.test.js +55 -0
- package/bin/lib/licensing/validator.js +213 -0
- package/bin/lib/licensing/validator.test.js +137 -0
- package/bin/lib/microsoft-mcp.js +176 -0
- package/bin/lib/microsoft-mcp.test.js +106 -0
- package/bin/lib/skills.js +84 -0
- package/bin/mcp/powerbi-modeling-launcher.js +38 -0
- package/bin/postinstall.js +44 -0
- package/bin/utils/errors.js +159 -0
- package/bin/utils/git.js +298 -0
- package/bin/utils/logger.js +142 -0
- package/bin/utils/mcp-detect.js +274 -0
- package/bin/utils/mcp-detect.test.js +105 -0
- package/bin/utils/pbix.js +305 -0
- package/bin/utils/pbix.test.js +37 -0
- package/bin/utils/profiles.js +312 -0
- package/bin/utils/projects.js +168 -0
- package/bin/utils/readline.js +206 -0
- package/bin/utils/readline.test.js +47 -0
- package/bin/utils/tui.js +314 -0
- package/bin/utils/tui.test.js +127 -0
- package/commands/contributions.md +265 -0
- package/commands/data-model-design.md +468 -0
- package/commands/dax-doctor.md +248 -0
- package/commands/fabric-scripts.md +452 -0
- package/commands/migration-assistant.md +290 -0
- package/commands/model-documenter.md +242 -0
- package/commands/pbi-connect.md +239 -0
- package/commands/project-kickoff.md +905 -0
- package/commands/report-layout.md +296 -0
- package/commands/rls-design.md +533 -0
- package/commands/theme-tweaker.md +624 -0
- package/config.example.json +23 -0
- package/config.json +23 -0
- package/desktop-extension/manifest.json +37 -0
- package/desktop-extension/package.json +10 -0
- package/desktop-extension/server.js +95 -0
- package/docs/openrouter-free-models.md +92 -0
- package/library/examples/README.md +151 -0
- package/library/examples/finance-reporting/README.md +351 -0
- package/library/examples/finance-reporting/data-model.md +267 -0
- package/library/examples/finance-reporting/measures.dax +557 -0
- package/library/examples/hr-analytics/README.md +371 -0
- package/library/examples/hr-analytics/data-model.md +315 -0
- package/library/examples/hr-analytics/measures.dax +460 -0
- package/library/examples/marketing-analytics/README.md +37 -0
- package/library/examples/marketing-analytics/data-model.md +62 -0
- package/library/examples/marketing-analytics/measures.dax +110 -0
- package/library/examples/retail-analytics/README.md +439 -0
- package/library/examples/retail-analytics/data-model.md +288 -0
- package/library/examples/retail-analytics/measures.dax +481 -0
- package/library/examples/supply-chain/README.md +37 -0
- package/library/examples/supply-chain/data-model.md +69 -0
- package/library/examples/supply-chain/measures.dax +77 -0
- package/library/examples/udf-library/README.md +228 -0
- package/library/examples/udf-library/functions.dax +571 -0
- package/library/snippets/dax/README.md +292 -0
- package/library/snippets/dax/business-domains.md +576 -0
- package/library/snippets/dax/calculate-patterns.md +276 -0
- package/library/snippets/dax/calculation-groups.md +489 -0
- package/library/snippets/dax/error-handling.md +495 -0
- package/library/snippets/dax/iterators-and-aggregations.md +474 -0
- package/library/snippets/dax/kpis-and-metrics.md +293 -0
- package/library/snippets/dax/rankings-and-topn.md +235 -0
- package/library/snippets/dax/security-patterns.md +413 -0
- package/library/snippets/dax/text-and-formatting.md +316 -0
- package/library/snippets/dax/time-intelligence.md +196 -0
- package/library/snippets/dax/user-defined-functions.md +477 -0
- package/library/snippets/dax/virtual-tables.md +546 -0
- package/library/snippets/excel-formulas/README.md +84 -0
- package/library/snippets/excel-formulas/aggregations.md +330 -0
- package/library/snippets/excel-formulas/dates-and-times.md +361 -0
- package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
- package/library/snippets/excel-formulas/lookups.md +169 -0
- package/library/snippets/excel-formulas/text-functions.md +363 -0
- package/library/snippets/governance/naming-conventions.md +97 -0
- package/library/snippets/governance/review-checklists.md +107 -0
- package/library/snippets/power-query/README.md +389 -0
- package/library/snippets/power-query/api-integration.md +707 -0
- package/library/snippets/power-query/connections.md +434 -0
- package/library/snippets/power-query/data-cleaning.md +298 -0
- package/library/snippets/power-query/error-handling.md +526 -0
- package/library/snippets/power-query/parameters.md +350 -0
- package/library/snippets/power-query/performance.md +506 -0
- package/library/snippets/power-query/transformations.md +330 -0
- package/library/snippets/report-design/accessibility.md +78 -0
- package/library/snippets/report-design/chart-selection.md +54 -0
- package/library/snippets/report-design/layout-patterns.md +87 -0
- package/library/templates/data-models/README.md +93 -0
- package/library/templates/data-models/finance-model.md +627 -0
- package/library/templates/data-models/retail-star-schema.md +473 -0
- package/library/templates/excel/README.md +83 -0
- package/library/templates/excel/budget-tracker.md +432 -0
- package/library/templates/excel/data-entry-form.md +533 -0
- package/library/templates/power-bi/README.md +72 -0
- package/library/templates/power-bi/finance-report.md +449 -0
- package/library/templates/power-bi/kpi-scorecard.md +461 -0
- package/library/templates/power-bi/sales-dashboard.md +281 -0
- package/library/themes/excel/README.md +436 -0
- package/library/themes/power-bi/README.md +271 -0
- package/library/themes/power-bi/accessible.json +307 -0
- package/library/themes/power-bi/bi-superpowers-default.json +858 -0
- package/library/themes/power-bi/corporate-blue.json +291 -0
- package/library/themes/power-bi/dark-mode.json +291 -0
- package/library/themes/power-bi/minimal.json +292 -0
- package/library/themes/power-bi/print-friendly.json +309 -0
- package/package.json +93 -0
- package/skills/contributions/SKILL.md +267 -0
- package/skills/data-model-design/SKILL.md +470 -0
- package/skills/data-modeling/SKILL.md +254 -0
- package/skills/data-quality/SKILL.md +664 -0
- package/skills/dax/SKILL.md +708 -0
- package/skills/dax-doctor/SKILL.md +250 -0
- package/skills/dax-udf/SKILL.md +489 -0
- package/skills/deployment/SKILL.md +320 -0
- package/skills/excel-formulas/SKILL.md +463 -0
- package/skills/fabric-scripts/SKILL.md +454 -0
- package/skills/fast-standard/SKILL.md +509 -0
- package/skills/governance/SKILL.md +205 -0
- package/skills/migration-assistant/SKILL.md +292 -0
- package/skills/model-documenter/SKILL.md +244 -0
- package/skills/pbi-connect/SKILL.md +241 -0
- package/skills/power-query/SKILL.md +406 -0
- package/skills/project-kickoff/SKILL.md +907 -0
- package/skills/query-performance/SKILL.md +480 -0
- package/skills/report-design/SKILL.md +207 -0
- package/skills/report-layout/SKILL.md +298 -0
- package/skills/rls-design/SKILL.md +535 -0
- package/skills/semantic-model/SKILL.md +237 -0
- package/skills/testing-validation/SKILL.md +643 -0
- package/skills/theme-tweaker/SKILL.md +626 -0
- package/src/content/base.md +237 -0
- package/src/content/mcp-requirements.json +69 -0
- package/src/content/routing.md +203 -0
- package/src/content/skills/contributions.md +259 -0
- package/src/content/skills/data-model-design.md +462 -0
- package/src/content/skills/data-modeling.md +246 -0
- package/src/content/skills/data-quality.md +656 -0
- package/src/content/skills/dax-doctor.md +242 -0
- package/src/content/skills/dax-udf.md +481 -0
- package/src/content/skills/dax.md +700 -0
- package/src/content/skills/deployment.md +312 -0
- package/src/content/skills/excel-formulas.md +455 -0
- package/src/content/skills/fabric-scripts.md +446 -0
- package/src/content/skills/fast-standard.md +501 -0
- package/src/content/skills/governance.md +197 -0
- package/src/content/skills/migration-assistant.md +284 -0
- package/src/content/skills/model-documenter.md +236 -0
- package/src/content/skills/pbi-connect.md +233 -0
- package/src/content/skills/power-query.md +398 -0
- package/src/content/skills/project-kickoff.md +899 -0
- package/src/content/skills/query-performance.md +472 -0
- package/src/content/skills/report-design.md +199 -0
- package/src/content/skills/report-layout.md +290 -0
- package/src/content/skills/rls-design.md +527 -0
- package/src/content/skills/semantic-model.md +229 -0
- package/src/content/skills/testing-validation.md +635 -0
- 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`.*
|