@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,571 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// DAX USER DEFINED FUNCTIONS LIBRARY
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// A comprehensive collection of reusable UDFs for Power BI and Analysis Services
|
|
5
|
+
// Status: Preview feature (September 2025)
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// ----------------------------------------------------------------------------
|
|
10
|
+
// FINANCIAL FUNCTIONS
|
|
11
|
+
// ----------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
DEFINE
|
|
14
|
+
|
|
15
|
+
/// Adds tax to an amount
|
|
16
|
+
/// @param amount - Base amount before tax
|
|
17
|
+
/// @param rate - Tax rate (e.g., 0.21 for 21%)
|
|
18
|
+
/// @returns Amount including tax
|
|
19
|
+
FUNCTION AddTax = (amount : NUMERIC, rate : NUMERIC) =>
|
|
20
|
+
amount * (1 + rate)
|
|
21
|
+
|
|
22
|
+
/// Removes tax from a gross amount
|
|
23
|
+
/// @param grossAmount - Amount including tax
|
|
24
|
+
/// @param rate - Tax rate (e.g., 0.21 for 21%)
|
|
25
|
+
/// @returns Amount excluding tax
|
|
26
|
+
FUNCTION RemoveTax = (grossAmount : NUMERIC, rate : NUMERIC) =>
|
|
27
|
+
grossAmount / (1 + rate)
|
|
28
|
+
|
|
29
|
+
/// Calculates Compound Annual Growth Rate
|
|
30
|
+
/// @param startValue - Initial value
|
|
31
|
+
/// @param endValue - Final value
|
|
32
|
+
/// @param years - Number of years
|
|
33
|
+
/// @returns Annual growth rate as decimal
|
|
34
|
+
FUNCTION CAGR = (startValue : NUMERIC, endValue : NUMERIC, years : NUMERIC) =>
|
|
35
|
+
IF(
|
|
36
|
+
startValue > 0 && years > 0,
|
|
37
|
+
POWER(endValue / startValue, 1 / years) - 1,
|
|
38
|
+
BLANK()
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
/// Calculates monthly loan payment (PMT equivalent)
|
|
42
|
+
/// @param principal - Loan amount
|
|
43
|
+
/// @param annualRate - Annual interest rate (e.g., 0.05 for 5%)
|
|
44
|
+
/// @param months - Number of monthly payments
|
|
45
|
+
/// @returns Monthly payment amount
|
|
46
|
+
FUNCTION MonthlyPayment = (principal : NUMERIC, annualRate : NUMERIC, months : INT64) =>
|
|
47
|
+
VAR _MonthlyRate = annualRate / 12
|
|
48
|
+
RETURN
|
|
49
|
+
IF(
|
|
50
|
+
_MonthlyRate = 0,
|
|
51
|
+
principal / months,
|
|
52
|
+
principal * _MonthlyRate * POWER(1 + _MonthlyRate, months) /
|
|
53
|
+
(POWER(1 + _MonthlyRate, months) - 1)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
/// Calculates future value of an investment
|
|
57
|
+
/// @param presentValue - Current value
|
|
58
|
+
/// @param rate - Interest rate per period
|
|
59
|
+
/// @param periods - Number of periods
|
|
60
|
+
/// @returns Future value
|
|
61
|
+
FUNCTION FutureValue = (presentValue : NUMERIC, rate : NUMERIC, periods : NUMERIC) =>
|
|
62
|
+
presentValue * POWER(1 + rate, periods)
|
|
63
|
+
|
|
64
|
+
/// Calculates present value of a future amount
|
|
65
|
+
/// @param futureValue - Future value
|
|
66
|
+
/// @param rate - Discount rate per period
|
|
67
|
+
/// @param periods - Number of periods
|
|
68
|
+
/// @returns Present value
|
|
69
|
+
FUNCTION PresentValue = (futureValue : NUMERIC, rate : NUMERIC, periods : NUMERIC) =>
|
|
70
|
+
futureValue / POWER(1 + rate, periods)
|
|
71
|
+
|
|
72
|
+
/// Calculates simple interest
|
|
73
|
+
/// @param principal - Principal amount
|
|
74
|
+
/// @param rate - Annual interest rate
|
|
75
|
+
/// @param years - Time in years
|
|
76
|
+
/// @returns Interest amount
|
|
77
|
+
FUNCTION SimpleInterest = (principal : NUMERIC, rate : NUMERIC, years : NUMERIC) =>
|
|
78
|
+
principal * rate * years
|
|
79
|
+
|
|
80
|
+
/// Calculates compound interest (interest portion only)
|
|
81
|
+
/// @param principal - Principal amount
|
|
82
|
+
/// @param rate - Interest rate per period
|
|
83
|
+
/// @param periods - Number of compounding periods
|
|
84
|
+
/// @returns Interest earned
|
|
85
|
+
FUNCTION CompoundInterest = (principal : NUMERIC, rate : NUMERIC, periods : NUMERIC) =>
|
|
86
|
+
principal * (POWER(1 + rate, periods) - 1)
|
|
87
|
+
|
|
88
|
+
/// Converts nominal rate to effective annual rate
|
|
89
|
+
/// @param nominalRate - Nominal annual rate
|
|
90
|
+
/// @param periodsPerYear - Compounding periods per year
|
|
91
|
+
/// @returns Effective annual rate
|
|
92
|
+
FUNCTION EffectiveRate = (nominalRate : NUMERIC, periodsPerYear : INT64) =>
|
|
93
|
+
POWER(1 + nominalRate / periodsPerYear, periodsPerYear) - 1
|
|
94
|
+
|
|
95
|
+
/// Calculates profit margin percentage
|
|
96
|
+
/// @param revenue - Total revenue
|
|
97
|
+
/// @param cost - Total cost
|
|
98
|
+
/// @returns Margin as decimal
|
|
99
|
+
FUNCTION ProfitMargin = (revenue : NUMERIC, cost : NUMERIC) =>
|
|
100
|
+
IF(revenue <> 0, (revenue - cost) / revenue, BLANK())
|
|
101
|
+
|
|
102
|
+
/// Calculates markup percentage
|
|
103
|
+
/// @param sellingPrice - Selling price
|
|
104
|
+
/// @param cost - Cost price
|
|
105
|
+
/// @returns Markup as decimal
|
|
106
|
+
FUNCTION Markup = (sellingPrice : NUMERIC, cost : NUMERIC) =>
|
|
107
|
+
IF(cost <> 0, (sellingPrice - cost) / cost, BLANK())
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
// ----------------------------------------------------------------------------
|
|
111
|
+
// STATISTICAL FUNCTIONS
|
|
112
|
+
// ----------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
/// Calculates z-score for a value
|
|
115
|
+
/// @param value - The value to standardize
|
|
116
|
+
/// @param mean - Population mean
|
|
117
|
+
/// @param stdDev - Population standard deviation
|
|
118
|
+
/// @returns Z-score
|
|
119
|
+
FUNCTION ZScore = (value : NUMERIC, mean : NUMERIC, stdDev : NUMERIC) =>
|
|
120
|
+
IF(stdDev <> 0, (value - mean) / stdDev, BLANK())
|
|
121
|
+
|
|
122
|
+
/// Normalizes a value to 0-1 range
|
|
123
|
+
/// @param value - Value to normalize
|
|
124
|
+
/// @param minVal - Minimum of range
|
|
125
|
+
/// @param maxVal - Maximum of range
|
|
126
|
+
/// @returns Normalized value (0-1)
|
|
127
|
+
FUNCTION Normalize = (value : NUMERIC, minVal : NUMERIC, maxVal : NUMERIC) =>
|
|
128
|
+
IF(maxVal <> minVal, (value - minVal) / (maxVal - minVal), BLANK())
|
|
129
|
+
|
|
130
|
+
/// Constrains a value to a specified range
|
|
131
|
+
/// @param value - Value to constrain
|
|
132
|
+
/// @param minVal - Minimum allowed value
|
|
133
|
+
/// @param maxVal - Maximum allowed value
|
|
134
|
+
/// @returns Clamped value
|
|
135
|
+
FUNCTION Clamp = (value : NUMERIC, minVal : NUMERIC, maxVal : NUMERIC) =>
|
|
136
|
+
MIN(MAX(value, minVal), maxVal)
|
|
137
|
+
|
|
138
|
+
/// Rounds value to nearest multiple
|
|
139
|
+
/// @param value - Value to round
|
|
140
|
+
/// @param multiple - Multiple to round to
|
|
141
|
+
/// @returns Rounded value
|
|
142
|
+
FUNCTION RoundToNearest = (value : NUMERIC, multiple : NUMERIC) =>
|
|
143
|
+
IF(multiple <> 0, ROUND(value / multiple, 0) * multiple, value)
|
|
144
|
+
|
|
145
|
+
/// Calculates percentage change
|
|
146
|
+
/// @param oldValue - Previous value
|
|
147
|
+
/// @param newValue - Current value
|
|
148
|
+
/// @returns Percentage change as decimal
|
|
149
|
+
FUNCTION PercentChange = (oldValue : NUMERIC, newValue : NUMERIC) =>
|
|
150
|
+
IF(oldValue <> 0, (newValue - oldValue) / ABS(oldValue), BLANK())
|
|
151
|
+
|
|
152
|
+
/// Calculates weighted value contribution
|
|
153
|
+
/// @param value - Individual value
|
|
154
|
+
/// @param weight - Weight for this value
|
|
155
|
+
/// @param totalWeight - Sum of all weights
|
|
156
|
+
/// @returns Weighted contribution
|
|
157
|
+
FUNCTION WeightedContribution = (value : NUMERIC, weight : NUMERIC, totalWeight : NUMERIC) =>
|
|
158
|
+
IF(totalWeight <> 0, value * weight / totalWeight, BLANK())
|
|
159
|
+
|
|
160
|
+
/// Calculates coefficient of variation
|
|
161
|
+
/// @param stdDev - Standard deviation
|
|
162
|
+
/// @param mean - Mean value
|
|
163
|
+
/// @returns CV as decimal
|
|
164
|
+
FUNCTION CoefficientOfVariation = (stdDev : NUMERIC, mean : NUMERIC) =>
|
|
165
|
+
IF(mean <> 0, stdDev / ABS(mean), BLANK())
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
// ----------------------------------------------------------------------------
|
|
169
|
+
// TEXT FUNCTIONS
|
|
170
|
+
// ----------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
/// Formats name as "Last, First"
|
|
173
|
+
/// @param firstName - First name
|
|
174
|
+
/// @param lastName - Last name
|
|
175
|
+
/// @returns Formatted name
|
|
176
|
+
FUNCTION FormatName = (firstName : STRING, lastName : STRING) =>
|
|
177
|
+
TRIM(lastName) & ", " & TRIM(firstName)
|
|
178
|
+
|
|
179
|
+
/// Formats name as "First Last"
|
|
180
|
+
/// @param firstName - First name
|
|
181
|
+
/// @param lastName - Last name
|
|
182
|
+
/// @returns Formatted name
|
|
183
|
+
FUNCTION FormatFullName = (firstName : STRING, lastName : STRING) =>
|
|
184
|
+
TRIM(firstName) & " " & TRIM(lastName)
|
|
185
|
+
|
|
186
|
+
/// Extracts first initial from name
|
|
187
|
+
/// @param name - Full name or first name
|
|
188
|
+
/// @returns First character uppercased
|
|
189
|
+
FUNCTION GetInitial = (name : STRING) =>
|
|
190
|
+
UPPER(LEFT(TRIM(name), 1))
|
|
191
|
+
|
|
192
|
+
/// Null-safe trim operation
|
|
193
|
+
/// @param text - Text to trim
|
|
194
|
+
/// @returns Trimmed text or empty string
|
|
195
|
+
FUNCTION SafeTrim = (text : STRING) =>
|
|
196
|
+
IF(ISBLANK(text), "", TRIM(text))
|
|
197
|
+
|
|
198
|
+
/// Truncates text with ellipsis
|
|
199
|
+
/// @param text - Text to truncate
|
|
200
|
+
/// @param maxLength - Maximum length including ellipsis
|
|
201
|
+
/// @returns Truncated text
|
|
202
|
+
FUNCTION TruncateText = (text : STRING, maxLength : INT64) =>
|
|
203
|
+
IF(
|
|
204
|
+
LEN(text) <= maxLength,
|
|
205
|
+
text,
|
|
206
|
+
LEFT(text, maxLength - 3) & "..."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
/// Left-pads a string to specified length
|
|
210
|
+
/// @param text - Text to pad
|
|
211
|
+
/// @param totalLength - Desired total length
|
|
212
|
+
/// @param padChar - Character to pad with
|
|
213
|
+
/// @returns Padded string
|
|
214
|
+
FUNCTION PadLeft = (text : STRING, totalLength : INT64, padChar : STRING) =>
|
|
215
|
+
IF(
|
|
216
|
+
LEN(text) >= totalLength,
|
|
217
|
+
text,
|
|
218
|
+
REPT(padChar, totalLength - LEN(text)) & text
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
/// Right-pads a string to specified length
|
|
222
|
+
/// @param text - Text to pad
|
|
223
|
+
/// @param totalLength - Desired total length
|
|
224
|
+
/// @param padChar - Character to pad with
|
|
225
|
+
/// @returns Padded string
|
|
226
|
+
FUNCTION PadRight = (text : STRING, totalLength : INT64, padChar : STRING) =>
|
|
227
|
+
IF(
|
|
228
|
+
LEN(text) >= totalLength,
|
|
229
|
+
text,
|
|
230
|
+
text & REPT(padChar, totalLength - LEN(text))
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
/// Repeats text n times with separator
|
|
234
|
+
/// @param text - Text to repeat
|
|
235
|
+
/// @param times - Number of repetitions
|
|
236
|
+
/// @param separator - Separator between repetitions
|
|
237
|
+
/// @returns Repeated text
|
|
238
|
+
FUNCTION RepeatWithSeparator = (text : STRING, times : INT64, separator : STRING) =>
|
|
239
|
+
IF(
|
|
240
|
+
times <= 0,
|
|
241
|
+
"",
|
|
242
|
+
text & REPT(separator & text, times - 1)
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
/// Extracts text between two delimiters
|
|
246
|
+
/// @param text - Source text
|
|
247
|
+
/// @param startDelim - Starting delimiter
|
|
248
|
+
/// @param endDelim - Ending delimiter
|
|
249
|
+
/// @returns Extracted text or blank
|
|
250
|
+
FUNCTION ExtractBetween = (text : STRING, startDelim : STRING, endDelim : STRING) =>
|
|
251
|
+
VAR _StartPos = FIND(startDelim, text, 1, 0)
|
|
252
|
+
VAR _EndPos = FIND(endDelim, text, _StartPos + LEN(startDelim), 0)
|
|
253
|
+
RETURN
|
|
254
|
+
IF(
|
|
255
|
+
_StartPos > 0 && _EndPos > 0,
|
|
256
|
+
MID(text, _StartPos + LEN(startDelim), _EndPos - _StartPos - LEN(startDelim)),
|
|
257
|
+
BLANK()
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
// ----------------------------------------------------------------------------
|
|
262
|
+
// DATE FUNCTIONS
|
|
263
|
+
// ----------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
/// Calculates age in years from birth date
|
|
266
|
+
/// @param birthDate - Date of birth
|
|
267
|
+
/// @returns Age in complete years
|
|
268
|
+
FUNCTION CalculateAge = (birthDate : DATETIME) =>
|
|
269
|
+
DATEDIFF(birthDate, TODAY(), YEAR) -
|
|
270
|
+
IF(
|
|
271
|
+
DATE(YEAR(TODAY()), MONTH(birthDate), DAY(birthDate)) > TODAY(),
|
|
272
|
+
1,
|
|
273
|
+
0
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
/// Gets fiscal year (July 1 start)
|
|
277
|
+
/// @param dateValue - Date to evaluate
|
|
278
|
+
/// @returns Fiscal year number
|
|
279
|
+
FUNCTION FiscalYear = (dateValue : DATETIME) =>
|
|
280
|
+
IF(MONTH(dateValue) >= 7, YEAR(dateValue) + 1, YEAR(dateValue))
|
|
281
|
+
|
|
282
|
+
/// Gets fiscal quarter (July 1 start)
|
|
283
|
+
/// @param dateValue - Date to evaluate
|
|
284
|
+
/// @returns Fiscal quarter (1-4)
|
|
285
|
+
FUNCTION FiscalQuarter = (dateValue : DATETIME) =>
|
|
286
|
+
VAR _Month = MONTH(dateValue)
|
|
287
|
+
RETURN
|
|
288
|
+
SWITCH(
|
|
289
|
+
TRUE(),
|
|
290
|
+
_Month IN {7, 8, 9}, 1,
|
|
291
|
+
_Month IN {10, 11, 12}, 2,
|
|
292
|
+
_Month IN {1, 2, 3}, 3,
|
|
293
|
+
4
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
/// Checks if date is a weekend
|
|
297
|
+
/// @param dateValue - Date to check
|
|
298
|
+
/// @returns TRUE if Saturday or Sunday
|
|
299
|
+
FUNCTION IsWeekend = (dateValue : DATETIME) =>
|
|
300
|
+
WEEKDAY(dateValue, 2) >= 6
|
|
301
|
+
|
|
302
|
+
/// Checks if year is a leap year
|
|
303
|
+
/// @param year - Year to check
|
|
304
|
+
/// @returns TRUE if leap year
|
|
305
|
+
FUNCTION IsLeapYear = (year : INT64) =>
|
|
306
|
+
(MOD(year, 4) = 0 && MOD(year, 100) <> 0) || MOD(year, 400) = 0
|
|
307
|
+
|
|
308
|
+
/// Gets first day of quarter
|
|
309
|
+
/// @param dateValue - Any date in the quarter
|
|
310
|
+
/// @returns First day of that quarter
|
|
311
|
+
FUNCTION QuarterStart = (dateValue : DATETIME) =>
|
|
312
|
+
DATE(YEAR(dateValue), (QUARTER(dateValue) - 1) * 3 + 1, 1)
|
|
313
|
+
|
|
314
|
+
/// Gets last day of quarter
|
|
315
|
+
/// @param dateValue - Any date in the quarter
|
|
316
|
+
/// @returns Last day of that quarter
|
|
317
|
+
FUNCTION QuarterEnd = (dateValue : DATETIME) =>
|
|
318
|
+
EOMONTH(DATE(YEAR(dateValue), QUARTER(dateValue) * 3, 1), 0)
|
|
319
|
+
|
|
320
|
+
/// Gets first day of month
|
|
321
|
+
/// @param dateValue - Any date in the month
|
|
322
|
+
/// @returns First day of that month
|
|
323
|
+
FUNCTION MonthStart = (dateValue : DATETIME) =>
|
|
324
|
+
DATE(YEAR(dateValue), MONTH(dateValue), 1)
|
|
325
|
+
|
|
326
|
+
/// Gets days remaining in month
|
|
327
|
+
/// @param dateValue - Current date
|
|
328
|
+
/// @returns Number of days until month end
|
|
329
|
+
FUNCTION DaysRemainingInMonth = (dateValue : DATETIME) =>
|
|
330
|
+
DATEDIFF(dateValue, EOMONTH(dateValue, 0), DAY)
|
|
331
|
+
|
|
332
|
+
/// Gets days remaining in year
|
|
333
|
+
/// @param dateValue - Current date
|
|
334
|
+
/// @returns Number of days until year end
|
|
335
|
+
FUNCTION DaysRemainingInYear = (dateValue : DATETIME) =>
|
|
336
|
+
DATEDIFF(dateValue, DATE(YEAR(dateValue), 12, 31), DAY)
|
|
337
|
+
|
|
338
|
+
/// Calculates business days between dates (Mon-Fri)
|
|
339
|
+
/// @param startDate - Start date (exclusive)
|
|
340
|
+
/// @param endDate - End date (inclusive)
|
|
341
|
+
/// @returns Approximate business days
|
|
342
|
+
FUNCTION BusinessDays = (startDate : DATETIME, endDate : DATETIME) =>
|
|
343
|
+
VAR _TotalDays = DATEDIFF(startDate, endDate, DAY)
|
|
344
|
+
VAR _FullWeeks = INT(_TotalDays / 7)
|
|
345
|
+
VAR _RemainingDays = MOD(_TotalDays, 7)
|
|
346
|
+
VAR _StartDOW = WEEKDAY(startDate, 2)
|
|
347
|
+
VAR _ExtraWeekendDays =
|
|
348
|
+
SWITCH(
|
|
349
|
+
TRUE(),
|
|
350
|
+
_StartDOW + _RemainingDays <= 5, 0,
|
|
351
|
+
_StartDOW >= 6, MIN(2, _RemainingDays),
|
|
352
|
+
MIN(2, _StartDOW + _RemainingDays - 5)
|
|
353
|
+
)
|
|
354
|
+
RETURN
|
|
355
|
+
_TotalDays - (_FullWeeks * 2) - _ExtraWeekendDays
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
// ----------------------------------------------------------------------------
|
|
359
|
+
// VALIDATION FUNCTIONS
|
|
360
|
+
// ----------------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
/// Basic email format validation
|
|
363
|
+
/// @param email - Email address to validate
|
|
364
|
+
/// @returns TRUE if basic format is valid
|
|
365
|
+
FUNCTION IsValidEmail = (email : STRING) =>
|
|
366
|
+
VAR _Trimmed = TRIM(email)
|
|
367
|
+
VAR _AtPos = FIND("@", _Trimmed, 1, 0)
|
|
368
|
+
VAR _DotPos = FIND(".", _Trimmed, _AtPos + 1, 0)
|
|
369
|
+
VAR _HasSpace = FIND(" ", _Trimmed, 1, 0) > 0
|
|
370
|
+
RETURN
|
|
371
|
+
_AtPos > 1 &&
|
|
372
|
+
_DotPos > _AtPos + 1 &&
|
|
373
|
+
NOT _HasSpace &&
|
|
374
|
+
LEN(_Trimmed) >= 5
|
|
375
|
+
|
|
376
|
+
/// Checks if value is within a range (inclusive)
|
|
377
|
+
/// @param value - Value to check
|
|
378
|
+
/// @param minVal - Minimum allowed
|
|
379
|
+
/// @param maxVal - Maximum allowed
|
|
380
|
+
/// @returns TRUE if value is in range
|
|
381
|
+
FUNCTION InRange = (value : NUMERIC, minVal : NUMERIC, maxVal : NUMERIC) =>
|
|
382
|
+
value >= minVal && value <= maxVal
|
|
383
|
+
|
|
384
|
+
/// Null-safe equality comparison
|
|
385
|
+
/// @param val1 - First value
|
|
386
|
+
/// @param val2 - Second value
|
|
387
|
+
/// @returns TRUE if both null or both equal
|
|
388
|
+
FUNCTION NullSafeEquals = (val1 : ANYVAL, val2 : ANYVAL) =>
|
|
389
|
+
(ISBLANK(val1) && ISBLANK(val2)) ||
|
|
390
|
+
(NOT ISBLANK(val1) && NOT ISBLANK(val2) && val1 = val2)
|
|
391
|
+
|
|
392
|
+
/// Returns first non-blank value
|
|
393
|
+
/// @param value - Primary value
|
|
394
|
+
/// @param defaultValue - Fallback if primary is blank
|
|
395
|
+
/// @returns Non-blank value
|
|
396
|
+
FUNCTION Coalesce = (value : ANYVAL, defaultValue : ANYVAL) =>
|
|
397
|
+
IF(ISBLANK(value), defaultValue, value)
|
|
398
|
+
|
|
399
|
+
/// Safe division with zero handling
|
|
400
|
+
/// @param numerator - Dividend
|
|
401
|
+
/// @param denominator - Divisor
|
|
402
|
+
/// @returns Result or BLANK if division by zero
|
|
403
|
+
FUNCTION SafeDivide = (numerator : NUMERIC, denominator : NUMERIC) =>
|
|
404
|
+
IF(denominator = 0, BLANK(), numerator / denominator)
|
|
405
|
+
|
|
406
|
+
/// Safe division with custom default
|
|
407
|
+
/// @param numerator - Dividend
|
|
408
|
+
/// @param denominator - Divisor
|
|
409
|
+
/// @param defaultValue - Value to return if division fails
|
|
410
|
+
/// @returns Result or default value
|
|
411
|
+
FUNCTION SafeDivideDefault = (numerator : NUMERIC, denominator : NUMERIC, defaultValue : NUMERIC) =>
|
|
412
|
+
IF(denominator = 0, defaultValue, numerator / denominator)
|
|
413
|
+
|
|
414
|
+
/// Checks if string contains only digits
|
|
415
|
+
/// @param text - Text to check
|
|
416
|
+
/// @returns TRUE if only digits (0-9)
|
|
417
|
+
FUNCTION IsNumericString = (text : STRING) =>
|
|
418
|
+
VAR _Cleaned = TRIM(text)
|
|
419
|
+
VAR _Length = LEN(_Cleaned)
|
|
420
|
+
VAR _NumDigits =
|
|
421
|
+
LEN(_Cleaned) -
|
|
422
|
+
LEN(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(
|
|
423
|
+
SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(
|
|
424
|
+
_Cleaned, "0", ""), "1", ""), "2", ""), "3", ""), "4", ""),
|
|
425
|
+
"5", ""), "6", ""), "7", ""), "8", ""), "9", ""))
|
|
426
|
+
RETURN
|
|
427
|
+
_Length > 0 && _Length = _NumDigits
|
|
428
|
+
|
|
429
|
+
/// Validates phone number format (basic - 10 digits)
|
|
430
|
+
/// @param phone - Phone number string
|
|
431
|
+
/// @returns TRUE if contains exactly 10 digits
|
|
432
|
+
FUNCTION IsValidPhone = (phone : STRING) =>
|
|
433
|
+
VAR _DigitsOnly =
|
|
434
|
+
SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(
|
|
435
|
+
phone, "-", ""), "(", ""), ")", ""), " ", "")
|
|
436
|
+
RETURN
|
|
437
|
+
IsNumericString(_DigitsOnly) && LEN(_DigitsOnly) = 10
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
// ----------------------------------------------------------------------------
|
|
441
|
+
// FORMATTING FUNCTIONS
|
|
442
|
+
// ----------------------------------------------------------------------------
|
|
443
|
+
|
|
444
|
+
/// Formats number as currency
|
|
445
|
+
/// @param value - Numeric value
|
|
446
|
+
/// @param decimals - Decimal places
|
|
447
|
+
/// @returns Formatted string
|
|
448
|
+
FUNCTION FormatCurrency = (value : NUMERIC, decimals : INT64) =>
|
|
449
|
+
IF(
|
|
450
|
+
ISBLANK(value),
|
|
451
|
+
"",
|
|
452
|
+
"$" & FORMAT(value, REPT("0", 1) & "." & REPT("0", decimals))
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
/// Formats number as percentage
|
|
456
|
+
/// @param value - Decimal value (0.15 = 15%)
|
|
457
|
+
/// @param decimals - Decimal places
|
|
458
|
+
/// @returns Formatted string
|
|
459
|
+
FUNCTION FormatPercent = (value : NUMERIC, decimals : INT64) =>
|
|
460
|
+
IF(
|
|
461
|
+
ISBLANK(value),
|
|
462
|
+
"",
|
|
463
|
+
FORMAT(value * 100, "#,##0" & IF(decimals > 0, "." & REPT("0", decimals), "")) & "%"
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
/// Formats large numbers with K/M/B suffix
|
|
467
|
+
/// @param value - Numeric value
|
|
468
|
+
/// @returns Formatted string with suffix
|
|
469
|
+
FUNCTION FormatThousands = (value : NUMERIC) =>
|
|
470
|
+
SWITCH(
|
|
471
|
+
TRUE(),
|
|
472
|
+
ISBLANK(value), "",
|
|
473
|
+
ABS(value) >= 1000000000, FORMAT(value / 1000000000, "#,##0.0") & "B",
|
|
474
|
+
ABS(value) >= 1000000, FORMAT(value / 1000000, "#,##0.0") & "M",
|
|
475
|
+
ABS(value) >= 1000, FORMAT(value / 1000, "#,##0.0") & "K",
|
|
476
|
+
FORMAT(value, "#,##0")
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
/// Formats delta with +/- sign
|
|
480
|
+
/// @param value - Change value
|
|
481
|
+
/// @returns Formatted string with sign
|
|
482
|
+
FUNCTION FormatDelta = (value : NUMERIC) =>
|
|
483
|
+
IF(
|
|
484
|
+
ISBLANK(value),
|
|
485
|
+
"",
|
|
486
|
+
IF(value > 0, "+", "") & FORMAT(value, "#,##0.0")
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
/// Formats delta as percentage with +/- sign
|
|
490
|
+
/// @param value - Decimal value
|
|
491
|
+
/// @returns Formatted percentage with sign
|
|
492
|
+
FUNCTION FormatDeltaPercent = (value : NUMERIC) =>
|
|
493
|
+
IF(
|
|
494
|
+
ISBLANK(value),
|
|
495
|
+
"",
|
|
496
|
+
IF(value > 0, "+", "") & FORMAT(value * 100, "#,##0.0") & "%"
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
/// Returns status emoji based on value vs target
|
|
500
|
+
/// @param value - Actual value
|
|
501
|
+
/// @param target - Target value
|
|
502
|
+
/// @param tolerance - Acceptable variance (e.g., 0.05 for 5%)
|
|
503
|
+
/// @returns Status indicator text
|
|
504
|
+
FUNCTION StatusIndicator = (value : NUMERIC, target : NUMERIC, tolerance : NUMERIC) =>
|
|
505
|
+
VAR _Variance = SafeDivide(value - target, ABS(target))
|
|
506
|
+
RETURN
|
|
507
|
+
SWITCH(
|
|
508
|
+
TRUE(),
|
|
509
|
+
ISBLANK(_Variance), "?",
|
|
510
|
+
_Variance >= tolerance, "Above",
|
|
511
|
+
_Variance <= -tolerance, "Below",
|
|
512
|
+
"On Track"
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
/// Formats number with ordinal suffix (1st, 2nd, 3rd, etc.)
|
|
516
|
+
/// @param number - Integer value
|
|
517
|
+
/// @returns Number with ordinal suffix
|
|
518
|
+
FUNCTION FormatOrdinal = (number : INT64) =>
|
|
519
|
+
VAR _Suffix =
|
|
520
|
+
SWITCH(
|
|
521
|
+
TRUE(),
|
|
522
|
+
MOD(number, 100) IN {11, 12, 13}, "th",
|
|
523
|
+
MOD(number, 10) = 1, "st",
|
|
524
|
+
MOD(number, 10) = 2, "nd",
|
|
525
|
+
MOD(number, 10) = 3, "rd",
|
|
526
|
+
"th"
|
|
527
|
+
)
|
|
528
|
+
RETURN
|
|
529
|
+
FORMAT(number, "#,##0") & _Suffix
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
// ----------------------------------------------------------------------------
|
|
533
|
+
// EXAMPLE USAGE
|
|
534
|
+
// ----------------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
// Uncomment any EVALUATE statement to test
|
|
537
|
+
|
|
538
|
+
// Financial
|
|
539
|
+
// EVALUATE { AddTax(100, 0.21) } -- 121
|
|
540
|
+
// EVALUATE { CAGR(1000, 1500, 3) } -- 0.1447 (14.47%)
|
|
541
|
+
// EVALUATE { MonthlyPayment(200000, 0.05, 360) } -- 1073.64
|
|
542
|
+
|
|
543
|
+
// Statistical
|
|
544
|
+
// EVALUATE { ZScore(85, 75, 10) } -- 1.0
|
|
545
|
+
// EVALUATE { Normalize(75, 50, 100) } -- 0.5
|
|
546
|
+
// EVALUATE { Clamp(150, 0, 100) } -- 100
|
|
547
|
+
|
|
548
|
+
// Text
|
|
549
|
+
// EVALUATE { FormatName("John", "Smith") } -- "Smith, John"
|
|
550
|
+
// EVALUATE { TruncateText("Hello World", 8) } -- "Hello..."
|
|
551
|
+
|
|
552
|
+
// Date
|
|
553
|
+
// EVALUATE { CalculateAge(DATE(1990, 5, 15)) } -- depends on today
|
|
554
|
+
// EVALUATE { FiscalYear(DATE(2024, 8, 1)) } -- 2025
|
|
555
|
+
// EVALUATE { IsWeekend(DATE(2024, 1, 6)) } -- TRUE (Saturday)
|
|
556
|
+
|
|
557
|
+
// Validation
|
|
558
|
+
// EVALUATE { IsValidEmail("test@example.com") } -- TRUE
|
|
559
|
+
// EVALUATE { SafeDivide(100, 0) } -- BLANK
|
|
560
|
+
// EVALUATE { Coalesce(BLANK(), "N/A") } -- "N/A"
|
|
561
|
+
|
|
562
|
+
// Formatting
|
|
563
|
+
// EVALUATE { FormatCurrency(1234.56, 2) } -- "$1234.56"
|
|
564
|
+
// EVALUATE { FormatThousands(1500000) } -- "1.5M"
|
|
565
|
+
// EVALUATE { FormatDeltaPercent(0.153) } -- "+15.3%"
|
|
566
|
+
// EVALUATE { FormatOrdinal(23) } -- "23rd"
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
// ============================================================================
|
|
570
|
+
// END OF LIBRARY
|
|
571
|
+
// ============================================================================
|