@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,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
+ // ============================================================================