@xano/developer-mcp 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/LICENSE +21 -0
- package/README.md +261 -0
- package/api_docs/addon.md +193 -0
- package/api_docs/agent.md +154 -0
- package/api_docs/api_group.md +236 -0
- package/api_docs/authentication.md +68 -0
- package/api_docs/file.md +190 -0
- package/api_docs/function.md +217 -0
- package/api_docs/history.md +263 -0
- package/api_docs/index.md +104 -0
- package/api_docs/mcp_server.md +139 -0
- package/api_docs/middleware.md +205 -0
- package/api_docs/realtime.md +153 -0
- package/api_docs/table.md +151 -0
- package/api_docs/task.md +191 -0
- package/api_docs/tool.md +216 -0
- package/api_docs/triggers.md +344 -0
- package/api_docs/workspace.md +246 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +495 -0
- package/package.json +49 -0
- package/xanoscript_docs/README.md +1 -0
- package/xanoscript_docs/api_query_examples.md +1255 -0
- package/xanoscript_docs/api_query_guideline.md +129 -0
- package/xanoscript_docs/build_from_lovable.md +715 -0
- package/xanoscript_docs/db_query_guideline.md +427 -0
- package/xanoscript_docs/ephemeral_environment_guideline.md +529 -0
- package/xanoscript_docs/expression_guideline.md +1086 -0
- package/xanoscript_docs/frontend_guideline.md +67 -0
- package/xanoscript_docs/function_examples.md +1406 -0
- package/xanoscript_docs/function_guideline.md +130 -0
- package/xanoscript_docs/functions.md +2155 -0
- package/xanoscript_docs/input_guideline.md +227 -0
- package/xanoscript_docs/mcp_server_examples.md +36 -0
- package/xanoscript_docs/mcp_server_guideline.md +69 -0
- package/xanoscript_docs/query_filter.md +489 -0
- package/xanoscript_docs/table_examples.md +586 -0
- package/xanoscript_docs/table_guideline.md +137 -0
- package/xanoscript_docs/task_examples.md +511 -0
- package/xanoscript_docs/task_guideline.md +103 -0
- package/xanoscript_docs/tips_and_tricks.md +144 -0
- package/xanoscript_docs/tool_examples.md +69 -0
- package/xanoscript_docs/tool_guideline.md +139 -0
- package/xanoscript_docs/unit_testing_guideline.md +328 -0
- package/xanoscript_docs/version.json +3 -0
- package/xanoscript_docs/workspace.md +17 -0
|
@@ -0,0 +1,1406 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "functions/**/*.xs"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Xanoscript Function Examples
|
|
6
|
+
|
|
7
|
+
Below are some examples of custom functions defined in Xanoscript.
|
|
8
|
+
|
|
9
|
+
Function can be called using
|
|
10
|
+
|
|
11
|
+
```xs
|
|
12
|
+
function.run "add_numbers" {
|
|
13
|
+
input = { a: 5, b: $input.b }
|
|
14
|
+
} as $return_value
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## youtube_url_analyzer_utility
|
|
18
|
+
|
|
19
|
+
```xs
|
|
20
|
+
function "utilities/youtube_url_parser" {
|
|
21
|
+
description = "This utility function parses various YouTube URL formats (including short, mobile, and embed links) to extract the video ID. It then uses the ID to generate a list of corresponding thumbnail URLs in multiple quality options, all while providing robust input validation."
|
|
22
|
+
input {
|
|
23
|
+
text youtube_url? filters=trim
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
stack {
|
|
27
|
+
var $youtube_id {
|
|
28
|
+
value = ("/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/"|regex_get_all_matches:$input.youtube_url)[5]|first
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
precondition (($youtube_id|strlen) > 5) {
|
|
32
|
+
error = "No valid ID found"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var $default_thumbnail {
|
|
36
|
+
description = "Default quality thumbnail URL"
|
|
37
|
+
value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/default.jpg"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var $medium_thumbnail {
|
|
41
|
+
description = "Medium quality thumbnail URL"
|
|
42
|
+
value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/mqdefault.jpg"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var $high_thumbnail {
|
|
46
|
+
description = "High quality thumbnail URL"
|
|
47
|
+
value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/hqdefault.jpg"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
var $standard_thumbnail {
|
|
51
|
+
description = "Standard quality thumbnail URL"
|
|
52
|
+
value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/sddefault.jpg"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
var $maxres_thumbnail {
|
|
56
|
+
description = "Maximum resolution thumbnail URL"
|
|
57
|
+
value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/maxresdefault.jpg"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
var $thumbnail_urls {
|
|
61
|
+
description = "Object containing different quality thumbnail URLs"
|
|
62
|
+
value = {
|
|
63
|
+
default: $default_thumbnail
|
|
64
|
+
medium: $medium_thumbnail
|
|
65
|
+
high: $high_thumbnail
|
|
66
|
+
standard: $standard_thumbnail
|
|
67
|
+
maxres: $maxres_thumbnail
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
response = {
|
|
73
|
+
youtube_id: $youtube_id,
|
|
74
|
+
thumbnail_urls: $thumbnail_urls
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## get_linear_labels_paginated
|
|
80
|
+
|
|
81
|
+
```xs
|
|
82
|
+
function "Linear/GetLabelsPaginated" {
|
|
83
|
+
input {
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
stack {
|
|
87
|
+
group {
|
|
88
|
+
description = "Declare Initial Variables"
|
|
89
|
+
stack {
|
|
90
|
+
var $labels {
|
|
91
|
+
description = "Empty array to collect the labels with each loop"
|
|
92
|
+
value = []
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var $has_next_page {
|
|
96
|
+
description = "Start with a true value for the initial loop"
|
|
97
|
+
value = true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
var $cursor {
|
|
101
|
+
description = "The cursor will be provided by the first API call"
|
|
102
|
+
value = null
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
while ($has_next_page) {
|
|
108
|
+
each {
|
|
109
|
+
api.request {
|
|
110
|
+
description = "Get Labels from Linear"
|
|
111
|
+
url = "https://api.linear.app/graphql"
|
|
112
|
+
method = "POST"
|
|
113
|
+
params = {}|set:"query":"""
|
|
114
|
+
query ($per_page: Int, $after: String) {
|
|
115
|
+
issueLabels(first:$per_page, after:$after) {
|
|
116
|
+
pageInfo {
|
|
117
|
+
hasNextPage
|
|
118
|
+
endCursor
|
|
119
|
+
}
|
|
120
|
+
nodes {
|
|
121
|
+
id
|
|
122
|
+
name
|
|
123
|
+
parent {
|
|
124
|
+
id
|
|
125
|
+
name
|
|
126
|
+
}
|
|
127
|
+
team {
|
|
128
|
+
name
|
|
129
|
+
id
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
"""|set:"variables":({}|set:"per_page":50|set:"after":$cursor)
|
|
135
|
+
|
|
136
|
+
headers = []|push:"Content-Type: application/json"|push:"Authorization: " ~ $env.linear_key
|
|
137
|
+
} as $api_labels
|
|
138
|
+
|
|
139
|
+
var $has_next_page {
|
|
140
|
+
description = "Check the pagination to see if there's another page"
|
|
141
|
+
value = $api_labels|get:"data.issueLabels.pageInfo.hasNextPage":null
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
var $cursor {
|
|
145
|
+
description = "Get the current page cursor and store it for the next loop"
|
|
146
|
+
value = $api_labels|get:"data.issueLabels.pageInfo.endCursor":null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
array.merge $labels {
|
|
150
|
+
description = "Merge the labels from this loop into the main Labels array"
|
|
151
|
+
value = $api_labels|get:"data.issueLabels.nodes":null
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
response = $labels
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## phone_number_formatter
|
|
162
|
+
|
|
163
|
+
```xs
|
|
164
|
+
function "utilities/phone_number_formatter" {
|
|
165
|
+
description = "Format phone numbers into standardized US format with basic validation"
|
|
166
|
+
input {
|
|
167
|
+
text phone_number filters=min:10|max:10|digitOk {
|
|
168
|
+
description = "Raw phone number string (10 digits only for this version)"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
enum? format_type?=standard {
|
|
172
|
+
values = ["standard", "dotted", "spaced"]
|
|
173
|
+
description = "Format type: 'standard', 'dotted', 'spaced'. Defaults to 'standard'"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
stack {
|
|
178
|
+
var $area_code {
|
|
179
|
+
description = "Extract area code (first 3 digits)"
|
|
180
|
+
value = $input.phone_number|substr:0:3
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
var $exchange {
|
|
184
|
+
description = "Extract exchange code (next 3 digits)"
|
|
185
|
+
value = $input.phone_number|substr:3:3
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
var $number {
|
|
189
|
+
description = "Extract last 4 digits"
|
|
190
|
+
value = $input.phone_number|substr:6:4
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var $formatted_phone {
|
|
194
|
+
description = "Initialize formatted phone variable"
|
|
195
|
+
value = ""
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
switch ($input.format_type) {
|
|
199
|
+
case ("dotted") {
|
|
200
|
+
var.update $formatted_phone {
|
|
201
|
+
description = "Format as 123.456.7890"
|
|
202
|
+
value = $area_code ~ "." ~ $exchange ~ "." ~ $number
|
|
203
|
+
}
|
|
204
|
+
} break
|
|
205
|
+
|
|
206
|
+
case ("spaced") {
|
|
207
|
+
var.update $formatted_phone {
|
|
208
|
+
description = "Format as 123 456 7890"
|
|
209
|
+
value = $area_code ~ " " ~ $exchange ~ " " ~ $number
|
|
210
|
+
}
|
|
211
|
+
} break
|
|
212
|
+
|
|
213
|
+
default {
|
|
214
|
+
var.update $formatted_phone {
|
|
215
|
+
description = "Format as (123) 456-7890"
|
|
216
|
+
value = "(" ~ $area_code ~ ") " ~ $exchange ~ "-" ~ $number
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
response = {
|
|
223
|
+
formatted_phone: $formatted_phone
|
|
224
|
+
original_input : $input.phone_number
|
|
225
|
+
format_type : $input.format_type
|
|
226
|
+
area_code : $area_code
|
|
227
|
+
exchange : $exchange
|
|
228
|
+
number : $number
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## weighted_average_function
|
|
234
|
+
|
|
235
|
+
```xs
|
|
236
|
+
function "maths/weighted_average" {
|
|
237
|
+
description = "Calculates the weighted average of values in an array"
|
|
238
|
+
input {
|
|
239
|
+
decimal[] values {
|
|
240
|
+
description = "Array of numbers representing the values to be averaged"
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
decimal[] weights {
|
|
244
|
+
description = "Array of numbers representing the weights for each value"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
stack {
|
|
249
|
+
var $values_length {
|
|
250
|
+
description = "Get the length of the values array"
|
|
251
|
+
value = $input.values|count
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
var $weights_length {
|
|
255
|
+
description = "Get the length of the weights array"
|
|
256
|
+
value = $input.weights|count
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
precondition ($values_length == $weights_length) {
|
|
260
|
+
error_type = "inputerror"
|
|
261
|
+
payload = "Values and weights arrays must have the same length"
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
var $has_negative_weights {
|
|
265
|
+
description = "Check if any weights are negative"
|
|
266
|
+
value = $input.weights|some:$$ < 0
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
precondition ($has_negative_weights == false) {
|
|
270
|
+
error_type = "inputerror"
|
|
271
|
+
payload = "All weights must be non-negative"
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
var $weighted_sum {
|
|
275
|
+
description = "Initialize the weighted sum"
|
|
276
|
+
value = 0
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
var $total_weight {
|
|
280
|
+
description = "Initialize the total weight"
|
|
281
|
+
value = 0
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
var $size {
|
|
285
|
+
description = "Size of the arrays for iteration"
|
|
286
|
+
value = $values_length
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for ($size) {
|
|
290
|
+
each as $index {
|
|
291
|
+
var $current_value {
|
|
292
|
+
description = "Get the value at the current index"
|
|
293
|
+
value = $input.values[$index]
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
var $current_weight {
|
|
297
|
+
description = "Get the weight at the current index"
|
|
298
|
+
value = $input.weights[$index]
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
math.add $weighted_sum {
|
|
302
|
+
description = "Add the weighted value to the sum"
|
|
303
|
+
value = $current_value * $current_weight
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
math.add $total_weight {
|
|
307
|
+
description = "Add the current weight to the total"
|
|
308
|
+
value = $current_weight
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
precondition ($total_weight > 0) {
|
|
314
|
+
error_type = "inputerror"
|
|
315
|
+
payload = "Total weight cannot be zero"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
var $weighted_average {
|
|
319
|
+
description = "Calculate the weighted average"
|
|
320
|
+
value = $weighted_sum / $total_weight
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
response = $weighted_average
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## url_parser_utility
|
|
329
|
+
|
|
330
|
+
```xs
|
|
331
|
+
function "utilities/url_parser_utility" {
|
|
332
|
+
description = "Parse and validate URLs, extracting protocol, domain, path, and basic components"
|
|
333
|
+
input {
|
|
334
|
+
text url filters=trim|min:1 {
|
|
335
|
+
description = "The URL to parse and validate"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
stack {
|
|
340
|
+
var $url_lower {
|
|
341
|
+
description = "Convert to lowercase for consistent processing"
|
|
342
|
+
value = $input.url|to_lower
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
var $protocol {
|
|
346
|
+
description = "Default protocol"
|
|
347
|
+
value = "http"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
var $domain {
|
|
351
|
+
description = "Extract domain from URL"
|
|
352
|
+
value = ""
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
var $path {
|
|
356
|
+
description = "Extract path from URL"
|
|
357
|
+
value = ""
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
var $is_valid {
|
|
361
|
+
description = "URL validation result"
|
|
362
|
+
value = false
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
var $validation_errors {
|
|
366
|
+
description = "List of validation errors"
|
|
367
|
+
value = []
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
conditional {
|
|
371
|
+
description = "Check if URL contains protocol"
|
|
372
|
+
if ($url_lower|contains:"://") {
|
|
373
|
+
var $url_parts {
|
|
374
|
+
description = "Split URL by protocol separator"
|
|
375
|
+
value = $url_lower|split:"://"
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
var.update $protocol {
|
|
379
|
+
description = "Extract protocol"
|
|
380
|
+
value = $url_parts[0]
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
var $remaining_url {
|
|
384
|
+
description = "Get URL without protocol"
|
|
385
|
+
value = $url_parts[1]
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
conditional {
|
|
389
|
+
description = "Check if URL contains path"
|
|
390
|
+
if ($remaining_url|contains:"/") {
|
|
391
|
+
var $path_parts {
|
|
392
|
+
description = "Split by first slash"
|
|
393
|
+
value = $remaining_url|split:"/"
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
var.update $domain {
|
|
397
|
+
description = "Extract domain"
|
|
398
|
+
value = $path_parts[0]
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
var.update $path {
|
|
402
|
+
description = "Extract path"
|
|
403
|
+
value = "/" ~ ($path_parts|slice:1:($path_parts|count)|join:"/")
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
else {
|
|
408
|
+
var.update $domain {
|
|
409
|
+
description = "No path, entire remaining is domain"
|
|
410
|
+
value = $remaining_url
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
else {
|
|
417
|
+
var.update $domain {
|
|
418
|
+
description = "Use entire URL as domain"
|
|
419
|
+
value = $url_lower
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
conditional {
|
|
425
|
+
description = "Validate protocol"
|
|
426
|
+
if ($protocol != "") {
|
|
427
|
+
var $valid_protocols {
|
|
428
|
+
description = "List of valid protocols"
|
|
429
|
+
value = ["http", "https", "ftp", "sftp", "ws", "wss"]
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
conditional {
|
|
433
|
+
description = "Check if protocol is valid"
|
|
434
|
+
if ($valid_protocols|in:$protocol) {
|
|
435
|
+
var $protocol_valid {
|
|
436
|
+
description = "Protocol is valid"
|
|
437
|
+
value = true
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
else {
|
|
442
|
+
var $protocol_valid {
|
|
443
|
+
description = "Protocol is not valid"
|
|
444
|
+
value = false
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
conditional {
|
|
450
|
+
description = "Add protocol error if invalid"
|
|
451
|
+
if ($protocol_valid == false) {
|
|
452
|
+
var.update $validation_errors {
|
|
453
|
+
description = "Add protocol error to validation errors"
|
|
454
|
+
value = $validation_errors|append:"Invalid protocol: " ~ $protocol
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
conditional {
|
|
462
|
+
description = "Validate domain"
|
|
463
|
+
if ($domain != "") {
|
|
464
|
+
var $domain_pattern {
|
|
465
|
+
description = "Domain validation pattern"
|
|
466
|
+
value = "/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/"
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
conditional {
|
|
470
|
+
description = "Check if domain matches pattern"
|
|
471
|
+
if (($domain_pattern|regex_matches:$domain) == false) {
|
|
472
|
+
var.update $validation_errors {
|
|
473
|
+
description = "Add domain error to validation errors"
|
|
474
|
+
value = $validation_errors|append:"Invalid domain format: " ~ $domain
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
var.update $is_valid {
|
|
482
|
+
description = "Set validation result based on error count"
|
|
483
|
+
value = ($validation_errors|count) == 0
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
response = {
|
|
488
|
+
url : $input.url
|
|
489
|
+
is_valid : $is_valid
|
|
490
|
+
protocol : $protocol
|
|
491
|
+
domain : $domain
|
|
492
|
+
path : $path
|
|
493
|
+
validation_errors: $validation_errors
|
|
494
|
+
base_url : $protocol ~ "://" ~ $domain
|
|
495
|
+
full_url : $protocol ~ "://" ~ $domain ~ $path
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
## random_text_selector
|
|
501
|
+
|
|
502
|
+
```xs
|
|
503
|
+
function "utilities/random_text_selector" {
|
|
504
|
+
description = "Select a random text value from an array with optional exclusion of previously used index"
|
|
505
|
+
input {
|
|
506
|
+
json text_array {
|
|
507
|
+
description = "Array of text values to choose from"
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
int? last_used_index? {
|
|
511
|
+
description = "Optional index of previously used value to exclude from selection"
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
stack {
|
|
516
|
+
var $text_array {
|
|
517
|
+
description = "Convert input to safe array"
|
|
518
|
+
value = $input.text_array|safe_array
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
precondition (($text_array|count) > 0) {
|
|
522
|
+
error_type = "inputerror"
|
|
523
|
+
error = "Text array cannot be empty"
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
var $available_options {
|
|
527
|
+
description = "Start with full array"
|
|
528
|
+
value = $text_array
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
conditional {
|
|
532
|
+
description = "Filter out last used index if provided"
|
|
533
|
+
if ($input.last_used_index !== null) {
|
|
534
|
+
var.update $available_options {
|
|
535
|
+
description = "Remove last used index from options"
|
|
536
|
+
value = $text_array|filter:"return $index != " ~ $input.last_used_index ~ ";"
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
var $selected_text {
|
|
542
|
+
description = "Get random text from available options"
|
|
543
|
+
value = $available_options|shuffle|first
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
response = {
|
|
548
|
+
selected_text : $selected_text
|
|
549
|
+
total_options : $available_options|count
|
|
550
|
+
available_options: $available_options
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## process_csv_import
|
|
556
|
+
|
|
557
|
+
```xs
|
|
558
|
+
function "csv_import/process_customers" {
|
|
559
|
+
description = "Processes a CSV file to import customer data. Creates user records for email/name data and customer records with address information. Handles duplicate detection and provides import statistics."
|
|
560
|
+
input {
|
|
561
|
+
file csv_file {
|
|
562
|
+
description = "The CSV file containing customer data."
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
stack {
|
|
567
|
+
var $successful_imports {
|
|
568
|
+
value = 0
|
|
569
|
+
description = "Counter for successfully imported records."
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
var $failed_imports {
|
|
573
|
+
value = 0
|
|
574
|
+
description = "Counter for failed imports."
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
var $total_processed {
|
|
578
|
+
value = 0
|
|
579
|
+
description = "Total number of records processed."
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
debug.log {
|
|
583
|
+
value = "CSV import process started."
|
|
584
|
+
description = "Log the start of the import process."
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
stream.from_csv {
|
|
588
|
+
value = $input.csv_file
|
|
589
|
+
separator = ","
|
|
590
|
+
enclosure = '"'
|
|
591
|
+
escape_char = '"'
|
|
592
|
+
description = "CSV File Data"
|
|
593
|
+
} as $csv_data
|
|
594
|
+
|
|
595
|
+
foreach ($csv_data) {
|
|
596
|
+
each as $row {
|
|
597
|
+
var.update $total_processed {
|
|
598
|
+
value = $total_processed + 1
|
|
599
|
+
description = "Increment total processed records."
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
var $email {
|
|
603
|
+
value = ($row|get:"email":null)|trim|lower
|
|
604
|
+
description = "Extract and normalize email from the current row."
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
try_catch {
|
|
608
|
+
description = "Process customer record with error handling."
|
|
609
|
+
try {
|
|
610
|
+
var $full_name {
|
|
611
|
+
value = (($row|get:"first_name":null)|trim) ~ " " ~ (($row|get:"last_name":null)|trim)
|
|
612
|
+
description = "Combine first and last name for user record."
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
group {
|
|
616
|
+
description = "Check for existing user and customer records. Add new record if they do not exist."
|
|
617
|
+
stack {
|
|
618
|
+
db.get "user" {
|
|
619
|
+
field_name = "email"
|
|
620
|
+
field_value = $email
|
|
621
|
+
description = "Check if user already exists by email."
|
|
622
|
+
} as $user
|
|
623
|
+
|
|
624
|
+
conditional {
|
|
625
|
+
description = "Create user if they don't already exist."
|
|
626
|
+
if ($user == null) {
|
|
627
|
+
db.add user {
|
|
628
|
+
data = {
|
|
629
|
+
created_at: "now"
|
|
630
|
+
name : $full_name
|
|
631
|
+
email : $email
|
|
632
|
+
is_active : true
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
description = "Create new user record with name and email."
|
|
636
|
+
} as $new_user
|
|
637
|
+
|
|
638
|
+
var.update $user {
|
|
639
|
+
value = $new_user
|
|
640
|
+
description = "Update user variable with newly created user record."
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
db.get "customer" {
|
|
646
|
+
field_name = "user_id"
|
|
647
|
+
field_value = $user.id
|
|
648
|
+
description = "Check if user is already a customer"
|
|
649
|
+
} as $customer
|
|
650
|
+
|
|
651
|
+
conditional {
|
|
652
|
+
description = "Create customer record if user is not already a customer"
|
|
653
|
+
if ($customer == null) {
|
|
654
|
+
db.add customer {
|
|
655
|
+
data = {
|
|
656
|
+
created_at : "now"
|
|
657
|
+
user_id : $user.id
|
|
658
|
+
customer_type : "individual"
|
|
659
|
+
street_address: ($row|get:"street_address":null)|trim
|
|
660
|
+
zip_code : ($row|get:"zip_code":null)|trim
|
|
661
|
+
city : ($row|get:"city":null)|trim
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
description = "Create new customer record linked to user."
|
|
665
|
+
} as $customer
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
var.update $successful_imports {
|
|
672
|
+
value = $successful_imports + 1
|
|
673
|
+
description = "Increment successful imports counter."
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
debug.log {
|
|
677
|
+
value = "Successfully imported customer: " ~ $email ~ " (User ID: " ~ $user_record.id ~ ")"
|
|
678
|
+
description = "Log successful customer import."
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
catch {
|
|
683
|
+
var.update $failed_imports {
|
|
684
|
+
value = $failed_imports + 1
|
|
685
|
+
description = "Increment failed imports on error."
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
debug.log {
|
|
689
|
+
value = "Failed to process row: " ~ ($row|json_encode) ~ ", Error: " ~ $error
|
|
690
|
+
description = "Log row processing failure."
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
var $result {
|
|
698
|
+
value = {
|
|
699
|
+
total_processed : $total_processed
|
|
700
|
+
successful_imports: $successful_imports
|
|
701
|
+
failed_imports : $failed_imports
|
|
702
|
+
message : "CSV import completed successfully."
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
response = $result
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
## survey_analytics_system
|
|
712
|
+
|
|
713
|
+
```xs
|
|
714
|
+
function "analytics/survey_analytics" {
|
|
715
|
+
description = "Calculate survey completion rates, response statistics, and question-level analytics for survey system analysis"
|
|
716
|
+
input {
|
|
717
|
+
int survey_id {
|
|
718
|
+
description = "The ID of the survey to analyze"
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
stack {
|
|
723
|
+
precondition ($input.survey_id > 0) {
|
|
724
|
+
error_type = "inputerror"
|
|
725
|
+
error = "Survey ID must be positive"
|
|
726
|
+
description = "Validate survey_id is valid"
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
db.get "surveys" {
|
|
730
|
+
field_name = "id"
|
|
731
|
+
field_value = $input.survey_id
|
|
732
|
+
description = "Get survey information and verify it exists"
|
|
733
|
+
} as $survey_info
|
|
734
|
+
|
|
735
|
+
precondition ($survey_info != null) {
|
|
736
|
+
error_type = "notfound"
|
|
737
|
+
error = "Survey not found"
|
|
738
|
+
description = "Ensure survey exists"
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
db.query "survey_questions" {
|
|
742
|
+
description = "Get all questions for this survey ordered by display order"
|
|
743
|
+
where = $db.survey_questions.survey_id == $input.survey_id
|
|
744
|
+
} as $questions
|
|
745
|
+
|
|
746
|
+
db.query "survey_responses" {
|
|
747
|
+
description = "Get all responses for this survey"
|
|
748
|
+
where = $db.survey_responses.survey_id == $input.survey_id
|
|
749
|
+
} as $all_responses
|
|
750
|
+
|
|
751
|
+
var $total_questions {
|
|
752
|
+
value = $questions|count
|
|
753
|
+
description = "Total number of questions in survey"
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
db.query "survey_responses" {
|
|
757
|
+
description = "Get all responses for this survey"
|
|
758
|
+
where = $db.survey_responses.survey_id == $input.survey_id
|
|
759
|
+
} as $responses
|
|
760
|
+
|
|
761
|
+
var $unique_respondents {
|
|
762
|
+
value = $all_responses.respondent_id|unique
|
|
763
|
+
description = "Get unique respondent IDs"
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
var $total_respondents {
|
|
767
|
+
value = $unique_respondents|count
|
|
768
|
+
description = "Total number of unique respondents"
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
var $completion_analysis {
|
|
772
|
+
value = []
|
|
773
|
+
description = "Array to store completion analysis for each respondent"
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
foreach ($unique_respondents) {
|
|
777
|
+
each as $respondent_id {
|
|
778
|
+
var $respondent_responses {
|
|
779
|
+
value = $all_responses|filter:"return $this.respondent_id == '" ~ $respondent_id ~ "';"
|
|
780
|
+
description = "Get responses for current respondent"
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
var $response_count {
|
|
784
|
+
value = $respondent_responses|count
|
|
785
|
+
description = "Count responses for this respondent"
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
var $completion_percentage {
|
|
789
|
+
value = 0
|
|
790
|
+
description = "Initialize completion percentage"
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
conditional {
|
|
794
|
+
if ($total_questions > 0) {
|
|
795
|
+
var.update $completion_percentage {
|
|
796
|
+
value = (($response_count / $total_questions) * 100)|number_format:2:".":","
|
|
797
|
+
description = "Calculate completion percentage"
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
var $is_complete {
|
|
803
|
+
value = ($response_count == $total_questions)
|
|
804
|
+
description = "Check if survey is complete"
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
var $respondent_analysis {
|
|
808
|
+
value = {
|
|
809
|
+
respondent_id: $respondent_id
|
|
810
|
+
responses_given: $response_count
|
|
811
|
+
completion_percentage: $completion_percentage
|
|
812
|
+
is_complete: $is_complete
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
description = "Analysis for current respondent"
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
array.push $completion_analysis {
|
|
819
|
+
value = $respondent_analysis
|
|
820
|
+
description = "Add respondent analysis to results"
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
var $completed_surveys {
|
|
826
|
+
value = $completion_analysis|filter:"return $this.is_complete == true;"
|
|
827
|
+
description = "Filter to only completed surveys"
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
var $completion_rate {
|
|
831
|
+
value = 0
|
|
832
|
+
description = "Overall completion rate percentage"
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
conditional {
|
|
836
|
+
if ($total_respondents > 0) {
|
|
837
|
+
var $completed_count {
|
|
838
|
+
value = $completed_surveys|count
|
|
839
|
+
description = "Number of completed surveys"
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
var.update $completion_rate {
|
|
843
|
+
value = (($completed_count / $total_respondents) * 100)|number_format:2:".":","
|
|
844
|
+
description = "Calculate completion rate"
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
var $question_stats {
|
|
850
|
+
value = []
|
|
851
|
+
description = "Array to store response statistics for each question"
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
foreach ($questions) {
|
|
855
|
+
each as $question {
|
|
856
|
+
var $question_responses {
|
|
857
|
+
value = $all_responses|filter:"return $this.question_id == " ~ $question.id ~ ";"
|
|
858
|
+
description = "Get responses for current question"
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
var $response_count {
|
|
862
|
+
value = $question_responses|count
|
|
863
|
+
description = "Count responses for this question"
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
var $response_rate {
|
|
867
|
+
value = 0
|
|
868
|
+
description = "Initialize response rate"
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
conditional {
|
|
872
|
+
if ($total_respondents > 0) {
|
|
873
|
+
var.update $response_rate {
|
|
874
|
+
value = (($response_count / $total_respondents) * 100)|number_format:2:".":","
|
|
875
|
+
description = "Calculate response rate percentage"
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
var $question_analysis {
|
|
881
|
+
value = {
|
|
882
|
+
question_id: $question.id
|
|
883
|
+
question_text: $question.question_text
|
|
884
|
+
question_type: $question.question_type
|
|
885
|
+
is_required: $question.is_required
|
|
886
|
+
total_responses: $response_count
|
|
887
|
+
response_rate: $response_rate
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
description = "Analysis for current question"
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
array.push $question_stats {
|
|
894
|
+
value = $question_analysis
|
|
895
|
+
description = "Add question analysis to results"
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
var $average_answered {
|
|
901
|
+
value = 0
|
|
902
|
+
description = "Average questions answered per respondent"
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
conditional {
|
|
906
|
+
if ($total_respondents > 0) {
|
|
907
|
+
var $total_responses_given {
|
|
908
|
+
value = $completion_analysis.responses_given|sum
|
|
909
|
+
description = "Sum of all responses given"
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
var.update $average_answered {
|
|
913
|
+
value = ($total_responses_given / $total_respondents)|number_format:2:".":","
|
|
914
|
+
description = "Calculate average questions answered"
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
var $analytics_summary {
|
|
920
|
+
value = {
|
|
921
|
+
survey_id: $input.survey_id
|
|
922
|
+
survey_title: $survey_info.title
|
|
923
|
+
survey_status: $survey_info.status
|
|
924
|
+
total_questions: $total_questions
|
|
925
|
+
total_respondents: $total_respondents
|
|
926
|
+
completed_responses: ($completed_surveys|count)
|
|
927
|
+
completion_rate_percent: $completion_rate
|
|
928
|
+
partial_responses: ($total_respondents - ($completed_surveys|count))
|
|
929
|
+
average_questions_answered: $average_answered
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
description = "Summary analytics for the survey"
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
debug.log {
|
|
936
|
+
value = "Survey analytics calculated for survey " ~ $input.survey_id ~ " - " ~ $total_respondents ~ " respondents, " ~ $completion_rate ~ "% completion rate"
|
|
937
|
+
description = "Log analytics completion"
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
response = {
|
|
942
|
+
summary : $analytics_summary
|
|
943
|
+
question_breakdown: $question_stats
|
|
944
|
+
completion_details: $completion_analysis
|
|
945
|
+
generated_at : "now"
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
## calculate_customer_lifetime_value
|
|
951
|
+
|
|
952
|
+
```xs
|
|
953
|
+
function "analytics/calculate_customer_lifetime_value" {
|
|
954
|
+
description = "Calculates the total monetary value of all orders made by a specific customer, providing a measure of their lifetime value to the business. This function queries the customer's order history and sums up all order totals to determine their overall spending."
|
|
955
|
+
input {
|
|
956
|
+
int customer_id {
|
|
957
|
+
description = "The ID of the customer to calculate lifetime value for"
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
stack {
|
|
962
|
+
db.query "customer" {
|
|
963
|
+
description = "Query the customer table to get customer information"
|
|
964
|
+
where = $db.customer.id == $input.customer_id
|
|
965
|
+
mock = {
|
|
966
|
+
"should return customer": [{id: 5, name: "John"}]
|
|
967
|
+
}
|
|
968
|
+
} as $customer
|
|
969
|
+
|
|
970
|
+
db.query "orders" {
|
|
971
|
+
description = "Query the orders table to get all orders for this customer"
|
|
972
|
+
where = $db.orders.customer_id == $input.customer_id
|
|
973
|
+
mock = {
|
|
974
|
+
"should return customer": [
|
|
975
|
+
{id: 1, customer_id: 5, order_total: 100},
|
|
976
|
+
{id: 2, customer_id: 5, order_total: 50}
|
|
977
|
+
],
|
|
978
|
+
"should return zero for no orders": []
|
|
979
|
+
}
|
|
980
|
+
} as $orders
|
|
981
|
+
|
|
982
|
+
var $total {
|
|
983
|
+
value = $orders[$$].order_total|sum
|
|
984
|
+
description = "Calculate the total value by summing all order totals"
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
response = $total
|
|
989
|
+
|
|
990
|
+
test "should return customer" {
|
|
991
|
+
input = {customer_id: 5}
|
|
992
|
+
expect.to_equal ($response) {
|
|
993
|
+
value = 150
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
test "should return zero for no orders" {
|
|
998
|
+
input = {customer_id: 99}
|
|
999
|
+
expect.to_equal ($response) {
|
|
1000
|
+
value = 0
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
## employee_performance_bonus_calculator
|
|
1007
|
+
|
|
1008
|
+
```xs
|
|
1009
|
+
function "hr/employee_performance_bonus_calculator" {
|
|
1010
|
+
description = "Calculates employee performance bonuses based on performance score, years of service, department, and additional factors"
|
|
1011
|
+
input {
|
|
1012
|
+
decimal performance_score filters=min:0|max:100 {
|
|
1013
|
+
description = "Employee's performance score (0-100)"
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
int years_of_service filters=min:0|max:50 {
|
|
1017
|
+
description = "Number of years the employee has worked"
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
text department {
|
|
1021
|
+
description = "Employee's department (engineering, sales, management, marketing, hr, finance)"
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
decimal base_salary filters=min:0 {
|
|
1025
|
+
description = "Employee's annual base salary"
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
bool exceeded_targets? {
|
|
1029
|
+
description = "Whether employee exceeded their annual targets"
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
text performance_tier? {
|
|
1033
|
+
description = "Performance tier: exceptional, high, satisfactory, needs_improvement"
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
decimal bonus_cap_percentage?=25 filters=min:0|max:50 {
|
|
1037
|
+
description = "Maximum bonus as percentage of base salary (default 25%)"
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
stack {
|
|
1042
|
+
var $department_multiplier {
|
|
1043
|
+
value = 1
|
|
1044
|
+
description = "Initialize department multiplier"
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
switch ($input.department|to_lower) {
|
|
1048
|
+
case ("engineering") {
|
|
1049
|
+
var.update $department_multiplier {
|
|
1050
|
+
value = 1.2
|
|
1051
|
+
description = "Engineering department gets 20% multiplier"
|
|
1052
|
+
}
|
|
1053
|
+
} break
|
|
1054
|
+
|
|
1055
|
+
case ("sales") {
|
|
1056
|
+
var.update $department_multiplier {
|
|
1057
|
+
value = 1.3
|
|
1058
|
+
description = "Sales department gets 30% multiplier"
|
|
1059
|
+
}
|
|
1060
|
+
} break
|
|
1061
|
+
|
|
1062
|
+
case ("management") {
|
|
1063
|
+
var.update $department_multiplier {
|
|
1064
|
+
value = 1.4
|
|
1065
|
+
description = "Management gets 40% multiplier"
|
|
1066
|
+
}
|
|
1067
|
+
} break
|
|
1068
|
+
|
|
1069
|
+
case ("marketing") {
|
|
1070
|
+
var.update $department_multiplier {
|
|
1071
|
+
value = 1.15
|
|
1072
|
+
description = "Marketing gets 15% multiplier"
|
|
1073
|
+
}
|
|
1074
|
+
} break
|
|
1075
|
+
|
|
1076
|
+
case ("hr") {
|
|
1077
|
+
var.update $department_multiplier {
|
|
1078
|
+
value = 1.1
|
|
1079
|
+
description = "HR gets 10% multiplier"
|
|
1080
|
+
}
|
|
1081
|
+
} break
|
|
1082
|
+
|
|
1083
|
+
case ("finance") {
|
|
1084
|
+
var.update $department_multiplier {
|
|
1085
|
+
value = 1.25
|
|
1086
|
+
description = "Finance gets 25% multiplier"
|
|
1087
|
+
}
|
|
1088
|
+
} break
|
|
1089
|
+
|
|
1090
|
+
default {
|
|
1091
|
+
var.update $department_multiplier {
|
|
1092
|
+
value = 1
|
|
1093
|
+
description = "Default multiplier for other departments"
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
var $years_multiplier {
|
|
1099
|
+
value = 1
|
|
1100
|
+
description = "Initialize years of service multiplier"
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
conditional {
|
|
1104
|
+
if ($input.years_of_service >= 20) {
|
|
1105
|
+
var.update $years_multiplier {
|
|
1106
|
+
value = 1.5
|
|
1107
|
+
description = "20+ years gets 50% multiplier"
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
elseif ($input.years_of_service >= 15) {
|
|
1112
|
+
var.update $years_multiplier {
|
|
1113
|
+
value = 1.4
|
|
1114
|
+
description = "15+ years gets 40% multiplier"
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
elseif ($input.years_of_service >= 10) {
|
|
1119
|
+
var.update $years_multiplier {
|
|
1120
|
+
value = 1.3
|
|
1121
|
+
description = "10+ years gets 30% multiplier"
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
elseif ($input.years_of_service >= 5) {
|
|
1126
|
+
var.update $years_multiplier {
|
|
1127
|
+
value = 1.2
|
|
1128
|
+
description = "5+ years gets 20% multiplier"
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
elseif ($input.years_of_service >= 2) {
|
|
1133
|
+
var.update $years_multiplier {
|
|
1134
|
+
value = 1.1
|
|
1135
|
+
description = "2+ years gets 10% multiplier"
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
var $performance_multiplier {
|
|
1141
|
+
value = $input.performance_score / 100
|
|
1142
|
+
description = "Convert performance score to decimal multiplier"
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
var $target_bonus {
|
|
1146
|
+
value = 0
|
|
1147
|
+
description = "Initialize target achievement bonus"
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
conditional {
|
|
1151
|
+
if ($input.exceeded_targets) {
|
|
1152
|
+
var.update $target_bonus {
|
|
1153
|
+
value = $input.base_salary * 0.05
|
|
1154
|
+
description = "5% bonus for exceeding targets"
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
var $tier_multiplier {
|
|
1160
|
+
value = 1
|
|
1161
|
+
description = "Initialize performance tier multiplier"
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
conditional {
|
|
1165
|
+
if ($input.performance_tier != "") {
|
|
1166
|
+
switch ($input.performance_tier|to_lower) {
|
|
1167
|
+
case ("exceptional") {
|
|
1168
|
+
var.update $tier_multiplier {
|
|
1169
|
+
value = 1.25
|
|
1170
|
+
description = "Exceptional performance gets 25% multiplier"
|
|
1171
|
+
}
|
|
1172
|
+
} break
|
|
1173
|
+
|
|
1174
|
+
case ("high") {
|
|
1175
|
+
var.update $tier_multiplier {
|
|
1176
|
+
value = 1.15
|
|
1177
|
+
description = "High performance gets 15% multiplier"
|
|
1178
|
+
}
|
|
1179
|
+
} break
|
|
1180
|
+
|
|
1181
|
+
case ("satisfactory") {
|
|
1182
|
+
var.update $tier_multiplier {
|
|
1183
|
+
value = 1
|
|
1184
|
+
description = "Satisfactory performance gets standard multiplier"
|
|
1185
|
+
}
|
|
1186
|
+
} break
|
|
1187
|
+
|
|
1188
|
+
case ("needs_improvement") {
|
|
1189
|
+
var.update $tier_multiplier {
|
|
1190
|
+
value = 0.5
|
|
1191
|
+
description = "Needs improvement gets reduced multiplier"
|
|
1192
|
+
}
|
|
1193
|
+
} break
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
var $calculated_bonus {
|
|
1199
|
+
value = $input.base_salary * $performance_multiplier * $department_multiplier * $years_multiplier * $tier_multiplier
|
|
1200
|
+
description = "Calculate base bonus before caps and additions"
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
var $total_bonus_before_cap {
|
|
1204
|
+
value = $calculated_bonus + $target_bonus
|
|
1205
|
+
description = "Add target achievement bonus"
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
var $bonus_cap {
|
|
1209
|
+
value = $input.base_salary * ($input.bonus_cap_percentage / 100)
|
|
1210
|
+
description = "Calculate maximum allowed bonus"
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
var $final_bonus {
|
|
1214
|
+
value = ($total_bonus_before_cap > $bonus_cap) ? $bonus_cap : $total_bonus_before_cap
|
|
1215
|
+
description = "Apply bonus cap if necessary"
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
var $was_capped {
|
|
1219
|
+
value = $total_bonus_before_cap > $bonus_cap
|
|
1220
|
+
description = "Track if bonus was capped"
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
var $bonus_as_percentage {
|
|
1224
|
+
value = ($final_bonus / $input.base_salary) * 100
|
|
1225
|
+
description = "Calculate bonus as percentage of base salary"
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
var $result {
|
|
1229
|
+
value = {
|
|
1230
|
+
final_bonus_amount: $final_bonus
|
|
1231
|
+
bonus_percentage: $bonus_as_percentage
|
|
1232
|
+
was_capped: $was_capped
|
|
1233
|
+
calculation_details: {
|
|
1234
|
+
base_salary: $input.base_salary
|
|
1235
|
+
performance_score: $input.performance_score
|
|
1236
|
+
years_of_service: $input.years_of_service
|
|
1237
|
+
department: $input.department
|
|
1238
|
+
performance_tier: $input.performance_tier
|
|
1239
|
+
exceeded_targets: $input.exceeded_targets
|
|
1240
|
+
}
|
|
1241
|
+
multipliers_applied: {
|
|
1242
|
+
department_multiplier: $department_multiplier
|
|
1243
|
+
years_multiplier: $years_multiplier
|
|
1244
|
+
performance_multiplier: $performance_multiplier
|
|
1245
|
+
tier_multiplier: $tier_multiplier
|
|
1246
|
+
}
|
|
1247
|
+
bonus_breakdown: {
|
|
1248
|
+
calculated_base_bonus: $calculated_bonus
|
|
1249
|
+
target_achievement_bonus: $target_bonus
|
|
1250
|
+
total_before_cap: $total_bonus_before_cap
|
|
1251
|
+
bonus_cap: $bonus_cap
|
|
1252
|
+
final_bonus: $final_bonus
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
description = "Comprehensive bonus calculation result with detailed breakdown"
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
response = $result
|
|
1261
|
+
}
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
## compute_user_engagement_score
|
|
1265
|
+
|
|
1266
|
+
```xs
|
|
1267
|
+
function "analytics/compute_user_engagement_score" {
|
|
1268
|
+
description = "Computes a user engagement score based on their activity data. Accepts a user ID and activity array, validates inputs, calculates weighted engagement score (posts * 10 + comments * 5 + likes * 2), and ensures all values are non-negative using array validation."
|
|
1269
|
+
input {
|
|
1270
|
+
int user_id {
|
|
1271
|
+
description = "Unique identifier for the user"
|
|
1272
|
+
}
|
|
1273
|
+
decimal[] posts {
|
|
1274
|
+
description = "Array of post counts for each period"
|
|
1275
|
+
}
|
|
1276
|
+
decimal[] comments {
|
|
1277
|
+
description = "Array of comment counts for each period"
|
|
1278
|
+
}
|
|
1279
|
+
decimal[] likes {
|
|
1280
|
+
description = "Array of like counts for each period"
|
|
1281
|
+
}
|
|
1282
|
+
decimal post_weight?=10 {
|
|
1283
|
+
description = "Weight multiplier for posts (default: 10)"
|
|
1284
|
+
}
|
|
1285
|
+
decimal comment_weight?=5 {
|
|
1286
|
+
description = "Weight multiplier for comments (default: 5)"
|
|
1287
|
+
}
|
|
1288
|
+
decimal like_weight?=2 {
|
|
1289
|
+
description = "Weight multiplier for likes (default: 2)"
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
stack {
|
|
1293
|
+
precondition ($input.user_id > 0) {
|
|
1294
|
+
error_type = "inputerror"
|
|
1295
|
+
error = "User ID must be positive"
|
|
1296
|
+
description = "Validate user_id is positive"
|
|
1297
|
+
}
|
|
1298
|
+
var $posts_length {
|
|
1299
|
+
value = $input.posts|count
|
|
1300
|
+
description = "Length of posts array"
|
|
1301
|
+
}
|
|
1302
|
+
var $comments_length {
|
|
1303
|
+
value = $input.comments|count
|
|
1304
|
+
description = "Length of comments array"
|
|
1305
|
+
}
|
|
1306
|
+
var $likes_length {
|
|
1307
|
+
value = $input.likes|count
|
|
1308
|
+
description = "Length of likes array"
|
|
1309
|
+
}
|
|
1310
|
+
precondition ($posts_length > 0) {
|
|
1311
|
+
error_type = "inputerror"
|
|
1312
|
+
error = "Posts array cannot be empty"
|
|
1313
|
+
description = "Validate arrays are not empty"
|
|
1314
|
+
}
|
|
1315
|
+
precondition ($posts_length == $comments_length && $posts_length == $likes_length) {
|
|
1316
|
+
error_type = "inputerror"
|
|
1317
|
+
error = "All activity arrays must have the same length"
|
|
1318
|
+
description = "Validate all arrays have the same length"
|
|
1319
|
+
}
|
|
1320
|
+
precondition ($input.post_weight >= 0) {
|
|
1321
|
+
error_type = "inputerror"
|
|
1322
|
+
error = "Post weight must be non-negative"
|
|
1323
|
+
description = "Validate post weight is non-negative"
|
|
1324
|
+
}
|
|
1325
|
+
precondition ($input.comment_weight >= 0) {
|
|
1326
|
+
error_type = "inputerror"
|
|
1327
|
+
error = "Comment weight must be non-negative"
|
|
1328
|
+
description = "Validate comment weight is non-negative"
|
|
1329
|
+
}
|
|
1330
|
+
precondition ($input.like_weight >= 0) {
|
|
1331
|
+
error_type = "inputerror"
|
|
1332
|
+
error = "Like weight must be non-negative"
|
|
1333
|
+
description = "Validate like weight is non-negative"
|
|
1334
|
+
}
|
|
1335
|
+
var $min_post_value {
|
|
1336
|
+
value = $input.posts|min
|
|
1337
|
+
description = "Minimum value in posts array"
|
|
1338
|
+
}
|
|
1339
|
+
var $min_comment_value {
|
|
1340
|
+
value = $input.comments|min
|
|
1341
|
+
description = "Minimum value in comments array"
|
|
1342
|
+
}
|
|
1343
|
+
var $min_like_value {
|
|
1344
|
+
value = $input.likes|min
|
|
1345
|
+
description = "Minimum value in likes array"
|
|
1346
|
+
}
|
|
1347
|
+
precondition ($min_post_value >= 0) {
|
|
1348
|
+
error_type = "inputerror"
|
|
1349
|
+
error = "All post counts must be non-negative"
|
|
1350
|
+
description = "Check if all post counts are valid"
|
|
1351
|
+
}
|
|
1352
|
+
precondition ($min_comment_value >= 0) {
|
|
1353
|
+
error_type = "inputerror"
|
|
1354
|
+
error = "All comment counts must be non-negative"
|
|
1355
|
+
description = "Check if all comment counts are valid"
|
|
1356
|
+
}
|
|
1357
|
+
precondition ($min_like_value >= 0) {
|
|
1358
|
+
error_type = "inputerror"
|
|
1359
|
+
error = "All like counts must be non-negative"
|
|
1360
|
+
description = "Check if all like counts are valid"
|
|
1361
|
+
}
|
|
1362
|
+
var $total_score {
|
|
1363
|
+
value = 0
|
|
1364
|
+
description = "Total engagement score"
|
|
1365
|
+
}
|
|
1366
|
+
for ($posts_length) {
|
|
1367
|
+
each as $index {
|
|
1368
|
+
var $period_score {
|
|
1369
|
+
value = ($input.posts[$index] * $input.post_weight) + ($input.comments[$index] * $input.comment_weight) + ($input.likes[$index] * $input.like_weight)
|
|
1370
|
+
description = "Weighted score for period $index"
|
|
1371
|
+
}
|
|
1372
|
+
math.add $total_score {
|
|
1373
|
+
value = $period_score
|
|
1374
|
+
description = "Add period score to total"
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
response = $total_score
|
|
1381
|
+
|
|
1382
|
+
test "should calculate correct engagement score" {
|
|
1383
|
+
input = {
|
|
1384
|
+
user_id: 1,
|
|
1385
|
+
posts: [2, 1],
|
|
1386
|
+
comments: [5, 3],
|
|
1387
|
+
likes: [10, 4]
|
|
1388
|
+
}
|
|
1389
|
+
expect.to_equal ($response) {
|
|
1390
|
+
value = 2*10+5*5+10*2 + 1*10+3*5+4*2
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
test "should throw error for negative post count" {
|
|
1395
|
+
input = {
|
|
1396
|
+
user_id: 1,
|
|
1397
|
+
posts: [-1, 2],
|
|
1398
|
+
comments: [1, 2],
|
|
1399
|
+
likes: [1, 2]
|
|
1400
|
+
}
|
|
1401
|
+
expect.to_throw {
|
|
1402
|
+
value = "All post counts must be non-negative"
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
```
|