@platforma-sdk/workflow-tengo 4.5.3 → 4.6.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 (39) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +6 -6
  3. package/dist/index.mjs +6 -6
  4. package/dist/tengo/lib/pframes.bundle.lib.tengo +37 -300
  5. package/dist/tengo/lib/pframes.lib.tengo +4 -1
  6. package/dist/tengo/lib/pframes.spec.lib.tengo +21 -1
  7. package/dist/tengo/lib/pframes.xsv-builder.lib.tengo +625 -0
  8. package/dist/tengo/lib/pt.expression.lib.tengo +905 -0
  9. package/dist/tengo/lib/pt.lib.tengo +980 -0
  10. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  11. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  12. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  13. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  14. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  15. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  16. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  17. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  18. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  19. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  20. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  21. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  22. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  23. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  24. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  25. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  26. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  27. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  28. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  29. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  30. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  31. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  32. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  33. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  34. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  35. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  36. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  37. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  38. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  39. package/package.json +3 -2
@@ -0,0 +1,625 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ maps := import("@platforma-sdk/workflow-tengo:maps")
3
+ strings := import("@platforma-sdk/workflow-tengo:strings")
4
+ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
5
+ pBuilder := import("@platforma-sdk/workflow-tengo:pframes.builder")
6
+ xsv := import("@platforma-sdk/workflow-tengo:pframes.xsv")
7
+ canonical := import("@platforma-sdk/workflow-tengo:canonical")
8
+
9
+ xsvFileBuilder := func(xsvType) {
10
+ ll.assert(xsvType == "csv" || xsvType == "tsv", "xsvType must be 'csv' or 'tsv'")
11
+
12
+ PTablerNA := "%%NA%%" // Constant for PTabler NA representation
13
+ naValueInternal := "" // Default NA value, can be changed by setNA
14
+
15
+ requests := []
16
+ addedColumnInternalIds := {}
17
+ localAxisHeaderOverrides := []
18
+ distiller := undefined
19
+ nextSequentialId := 1
20
+ nextPFrameKeyIndex := 1
21
+
22
+ getSpecDistiller := func() {
23
+ if is_undefined(distiller) {
24
+ columnsToDistill := []
25
+ for request in requests {
26
+ columnsToDistill = append(columnsToDistill, request.spec)
27
+ }
28
+
29
+ distiller = pSpec.createSpecDistiller(columnsToDistill)
30
+ }
31
+ return distiller
32
+ }
33
+
34
+
35
+
36
+
37
+ parseFlatFrameInput := func(flatFrame) {
38
+ parsedColumns := {}
39
+ processedBaseKeys := {}
40
+
41
+ for key, value in flatFrame {
42
+ if strings.has_suffix(key, ".spec") {
43
+ baseKey := strings.trim_suffix(key, ".spec")
44
+ if is_undefined(parsedColumns[baseKey]) {
45
+ parsedColumns[baseKey] = { key: baseKey }
46
+ }
47
+ parsedColumns[baseKey].spec = value
48
+ processedBaseKeys[baseKey] = true
49
+ } else if strings.has_suffix(key, ".data") {
50
+ baseKey := strings.trim_suffix(key, ".data")
51
+ if is_undefined(parsedColumns[baseKey]) {
52
+ parsedColumns[baseKey] = { key: baseKey }
53
+ }
54
+ parsedColumns[baseKey].data = value
55
+ processedBaseKeys[baseKey] = true
56
+ } else {
57
+ ll.panic("Invalid key format in flat frame input: %s. Expected '.spec' or '.data' suffix.", key)
58
+ }
59
+ }
60
+
61
+ resultArray := []
62
+ for baseKey, colParts in parsedColumns {
63
+ if is_undefined(colParts.spec) {
64
+ ll.panic("Missing .spec for base key '%s' in flat frame input.", baseKey)
65
+ }
66
+ if is_undefined(colParts.data) {
67
+ ll.panic("Missing .data for base key '%s' in flat frame input.", baseKey)
68
+ }
69
+ resultArray = append(resultArray, {
70
+ spec: colParts.spec,
71
+ data: colParts.data,
72
+ key: baseKey
73
+ })
74
+ }
75
+ return resultArray
76
+ }
77
+
78
+ getCanonicalAxisKey := func(axisSpec) {
79
+
80
+ nameStr := axisSpec.name
81
+ typeStr := axisSpec.type
82
+
83
+ domainCanonicalStr := ""
84
+ if is_map(axisSpec.domain) {
85
+ domainCanonicalStr = canonical.encode(axisSpec.domain)
86
+ } else {
87
+
88
+
89
+ domainCanonicalStr = canonical.encode({})
90
+ }
91
+ return nameStr + "|" + typeStr + "|" + domainCanonicalStr
92
+ }
93
+
94
+ findAxisHeaderOverride := func(axisSpec) {
95
+ for _, override in localAxisHeaderOverrides {
96
+ matcher := override[0]
97
+ if matcher.name == axisSpec.name {
98
+ specDistiller := getSpecDistiller()
99
+ discriminativeDomains := specDistiller.getDiscriminativeDomainsSet(axisSpec.name)
100
+ domainsMatch := true
101
+ for domainKey, domainValue in matcher.domain {
102
+ if is_undefined(discriminativeDomains[domainKey]) {
103
+ continue
104
+ }
105
+ if is_undefined(axisSpec.domain) || is_undefined(axisSpec.domain[domainKey]) || axisSpec.domain[domainKey] != domainValue {
106
+ domainsMatch = false
107
+ break
108
+ }
109
+ }
110
+ if domainsMatch {
111
+ return override[1]
112
+ }
113
+ }
114
+ }
115
+ return undefined
116
+ }
117
+
118
+ generateLabelForAxis := func(axisSpec) {
119
+ label := findAxisHeaderOverride(axisSpec)
120
+ if !is_undefined(label) {
121
+ return label
122
+ }
123
+
124
+ specDistiller := getSpecDistiller()
125
+ discriminativeDomains := specDistiller.getDiscriminativeDomains(axisSpec.name)
126
+
127
+ if len(discriminativeDomains) == 0 {
128
+ label = strings.substituteSpecialCharacters(axisSpec.name)
129
+ } else {
130
+
131
+
132
+ ll.panic(
133
+ "Cannot deduce header for axis '%s'. Discriminative domains: %s. Please provide a header override using setAxisHeader().",
134
+ axisSpec.name,
135
+ discriminativeDomains
136
+ )
137
+ }
138
+ return label
139
+ }
140
+
141
+ _constructPFrameAndDistillSpecs := func() {
142
+ frameBuilder := pBuilder.pFrameBuilder()
143
+ currentDistiller := getSpecDistiller() // Ensure distiller is created/updated
144
+ processedAxesMap := {}
145
+
146
+ for request in requests {
147
+ distilledSpec := currentDistiller.distill(request.spec)
148
+
149
+
150
+ distilledSpec = maps.deepMerge(distilledSpec, {
151
+ annotations: { "pl7.app/label": request.header }
152
+ })
153
+
154
+
155
+ if !is_undefined(distilledSpec.axesSpec) && is_array(distilledSpec.axesSpec) {
156
+ newAxesSpec := []
157
+ for axis_idx, axis_orig in distilledSpec.axesSpec {
158
+ labelledAxis := maps.deepMerge(axis_orig, {
159
+ annotations: { "pl7.app/label": generateLabelForAxis(axis_orig) }
160
+ })
161
+ newAxesSpec = append(newAxesSpec, labelledAxis)
162
+
163
+
164
+ canonicalKey := getCanonicalAxisKey(labelledAxis) // Pass the whole axis spec
165
+ if is_undefined(processedAxesMap[canonicalKey]) {
166
+ processedAxesMap[canonicalKey] = {
167
+ name: labelledAxis.name,
168
+ type: labelledAxis.type,
169
+ domain: labelledAxis.domain, // Store original domain from the labelled (effectively distilled) axis
170
+ label: labelledAxis.annotations["pl7.app/label"]
171
+ }
172
+ }
173
+ }
174
+ distilledSpec.axesSpec = newAxesSpec
175
+ }
176
+
177
+
178
+ frameBuilder.add(request.pFrameKey, distilledSpec, request.data)
179
+ }
180
+
181
+ finalProcessedAxes := maps.getValues(processedAxesMap)
182
+ return {
183
+ pFrame: frameBuilder.build(),
184
+ uniqueAxes: finalProcessedAxes
185
+ }
186
+ }
187
+
188
+ self := undefined // Forward declaration for builder methods
189
+
190
+ processSingleCol := func(col, ops) {
191
+ if !is_map(col) || is_undefined(col.spec) || is_undefined(col.data) {
192
+ ll.panic("Invalid single column input. Expected {spec: ..., data: ..., key?: ...}, got: %v", col)
193
+ }
194
+
195
+
196
+
197
+ idPrefix := ops.idPrefix ? ops.idPrefix : ""
198
+ idSuffix := ops.idSuffix ? ops.idSuffix : ""
199
+ headerPrefix := ops.headerPrefix ? ops.headerPrefix : ""
200
+ headerSuffix := ops.headerSuffix ? ops.headerSuffix : ""
201
+
202
+ baseId := undefined
203
+ if !is_undefined(ops.id) {
204
+ if !is_string(ops.id) {
205
+ ll.panic("Option 'id' must be a string.")
206
+ }
207
+ baseId = ops.id
208
+ } else if !is_undefined(col.key) {
209
+ if !is_string(col.key) {
210
+ ll.panic("Column 'key' must be a string if provided.")
211
+ }
212
+ baseId = col.key
213
+ } else {
214
+ baseId = "user_col" + nextSequentialId
215
+ nextSequentialId += 1
216
+ }
217
+
218
+ internalId := idPrefix + baseId + idSuffix
219
+
220
+ if !is_undefined(addedColumnInternalIds[internalId]) {
221
+ ll.panic("Duplicate internalId detected: '%s'. Column IDs must be unique.", internalId)
222
+ }
223
+ addedColumnInternalIds[internalId] = true
224
+
225
+ header := undefined
226
+ if !is_undefined(ops.header) {
227
+ if !is_string(ops.header) {
228
+ ll.panic("Option 'header' must be a string.")
229
+ }
230
+ header = ops.header // Explicit header takes full precedence, no prefix/suffix from ops apply here
231
+ } else {
232
+ baseHeaderForOps := undefined
233
+ if !is_undefined(col.spec) &&
234
+ !is_undefined(col.spec.annotations) &&
235
+ !is_undefined(col.spec.annotations["pl7.app/label"]) &&
236
+ is_string(col.spec.annotations["pl7.app/label"]) &&
237
+ col.spec.annotations["pl7.app/label"] != "" {
238
+ baseHeaderForOps = col.spec.annotations["pl7.app/label"]
239
+ } else {
240
+ baseHeaderForOps = strings.substituteSpecialCharacters(internalId) // Fallback to internalId
241
+ }
242
+ header = headerPrefix + baseHeaderForOps + headerSuffix
243
+ }
244
+
245
+
246
+ pFrameKey := "col" + nextPFrameKeyIndex
247
+ nextPFrameKeyIndex += 1
248
+
249
+ requests = append(requests, {
250
+ internalId: internalId,
251
+ header: header,
252
+ spec: col.spec,
253
+ data: col.data,
254
+ originalKey: col.key, // Store original key if present
255
+ pFrameKey: pFrameKey
256
+ })
257
+ distiller = undefined // Invalidate distiller
258
+ }
259
+
260
+ processMultiCol := func(colsArray, ops) {
261
+ if !is_undefined(ops.id) || !is_undefined(ops.header) {
262
+ ll.panic("Single-column options ('id', 'header') cannot be used with multi-column 'add'.")
263
+ }
264
+ idPrefix := ops.idPrefix ? ops.idPrefix : ""
265
+ idSuffix := ops.idSuffix ? ops.idSuffix : ""
266
+ headerPrefix := ops.headerPrefix ? ops.headerPrefix : ""
267
+ headerSuffix := ops.headerSuffix ? ops.headerSuffix : ""
268
+
269
+ for col in colsArray {
270
+ if !is_map(col) || is_undefined(col.spec) || is_undefined(col.data) {
271
+ ll.panic("Invalid column item in array/frame. Expected {spec: ..., data: ..., key?: ...}, got: %v", col)
272
+ }
273
+
274
+ baseId := undefined
275
+ if !is_undefined(col.key) {
276
+ if !is_string(col.key) {
277
+ ll.panic("Column 'key' must be a string if provided in multi-add.")
278
+ }
279
+ baseId = col.key
280
+ } else {
281
+ baseId = "user_col" + nextSequentialId
282
+ nextSequentialId += 1
283
+ }
284
+
285
+ internalId := idPrefix + baseId + idSuffix
286
+
287
+ if !is_undefined(addedColumnInternalIds[internalId]) {
288
+ ll.panic("Duplicate internalId detected: '%s'. Column IDs must be unique.", internalId)
289
+ }
290
+ addedColumnInternalIds[internalId] = true
291
+
292
+ baseHeader := undefined
293
+ if !is_undefined(col.spec) &&
294
+ !is_undefined(col.spec.annotations) &&
295
+ !is_undefined(col.spec.annotations["pl7.app/label"]) &&
296
+ is_string(col.spec.annotations["pl7.app/label"]) &&
297
+ col.spec.annotations["pl7.app/label"] != "" {
298
+ baseHeader = col.spec.annotations["pl7.app/label"]
299
+ } else {
300
+ baseHeader = strings.substituteSpecialCharacters(internalId) // Fallback to internalId
301
+ }
302
+ header := headerPrefix + baseHeader + headerSuffix
303
+
304
+
305
+ pFrameKey := "col" + nextPFrameKeyIndex
306
+ nextPFrameKeyIndex += 1
307
+
308
+ requests = append(requests, {
309
+ internalId: internalId,
310
+ header: header,
311
+ spec: col.spec,
312
+ data: col.data,
313
+ originalKey: col.key,
314
+ pFrameKey: pFrameKey
315
+ })
316
+ }
317
+ distiller = undefined // Invalidate distiller
318
+ }
319
+
320
+
321
+ self = {
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+ add: func(item, ...options) {
378
+ ops := {}
379
+ if len(options) > 0 {
380
+ if !is_map(options[0]) {
381
+ ll.panic("Options for 'add' method must be a map.")
382
+ }
383
+ ops = options[0]
384
+ }
385
+
386
+ if is_array(item) { // Array of columns
387
+ processMultiCol(item, ops)
388
+ } else if is_map(item) {
389
+
390
+ if !is_undefined(item.spec) && !is_undefined(item.data) {
391
+ processSingleCol(item, ops)
392
+ } else {
393
+
394
+
395
+ isFlatFrame := false
396
+ for k, _ in item {
397
+ if strings.has_suffix(k, ".spec") || strings.has_suffix(k, ".data") {
398
+ isFlatFrame = true
399
+ break
400
+ }
401
+ }
402
+
403
+ colsToProcess := []
404
+ if isFlatFrame {
405
+ colsToProcess = parseFlatFrameInput(item)
406
+ } else { // Assume map of column objects: { "id1": {spec, data}, ... }
407
+ for k, v_col in item {
408
+ if !is_map(v_col) || is_undefined(v_col.spec) || is_undefined(v_col.data) {
409
+ ll.panic("Invalid column value in frame for key '%s'. Expected {spec: ..., data: ...}.", k)
410
+ }
411
+ colsToProcess = append(colsToProcess, {
412
+ spec: v_col.spec,
413
+ data: v_col.data,
414
+ key: k // Use map key as original key
415
+ })
416
+ }
417
+ }
418
+ processMultiCol(colsToProcess, ops)
419
+ }
420
+ } else {
421
+ ll.panic("'add' method input item must be a single column object, an array of columns, or a frame (map). Got: %v", item)
422
+ }
423
+ return self
424
+ },
425
+
426
+
427
+
428
+
429
+
430
+
431
+
432
+
433
+
434
+
435
+
436
+
437
+
438
+
439
+
440
+
441
+ setAxisHeader: func(matcher, header) {
442
+ processedMatcher := undefined
443
+ if is_string(matcher) {
444
+ processedMatcher = { name: matcher, domain: {} }
445
+ } else if is_map(matcher) {
446
+
447
+
448
+
449
+ processedMatcher = pSpec.axisSpecToMatcher(matcher)
450
+ } else {
451
+ ll.panic("Matcher for setAxisHeader must be a string (axis name) or an AxisSpec-like map (with at least 'name' and 'type' fields).")
452
+ }
453
+
454
+ if !is_string(header) {
455
+ ll.panic("Header for setAxisHeader must be a string.")
456
+ }
457
+
458
+ localAxisHeaderOverrides = [[processedMatcher, header]] + localAxisHeaderOverrides
459
+
460
+ return self
461
+ },
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+
470
+ getHeader: func(internalId) {
471
+ if !is_string(internalId) {
472
+ ll.panic("internalId for getHeader must be a string.")
473
+ }
474
+ for request in requests {
475
+ if request.internalId == internalId {
476
+ return request.header
477
+ }
478
+ }
479
+ ll.panic("Header not found for internalId: '%s'. Ensure column was added and ID is correct.", internalId)
480
+ return undefined // Should not reach here
481
+ },
482
+
483
+
484
+
485
+
486
+
487
+
488
+
489
+
490
+ getSpec: func(internalId) {
491
+ if !is_string(internalId) {
492
+ ll.panic("internalId for getSpec must be a string.")
493
+ }
494
+ for request in requests {
495
+ if request.internalId == internalId {
496
+ return request.spec
497
+ }
498
+ }
499
+ ll.panic("Spec not found for internalId: '%s'. Ensure column was added and ID is correct.", internalId)
500
+ return undefined // Should not reach here
501
+ },
502
+
503
+
504
+
505
+
506
+
507
+
508
+
509
+
510
+
511
+
512
+ setNA: func(value) {
513
+ if !is_string(value) {
514
+ ll.panic("NA value for setNA must be a string.")
515
+ }
516
+ naValueInternal = value
517
+ return self
518
+ },
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
527
+
528
+
529
+
530
+
531
+
532
+
533
+
534
+ build: func(...params) {
535
+ if len(requests) == 0 {
536
+ ll.panic("Cannot build XSV: no columns have been added.")
537
+ }
538
+
539
+
540
+ currentExportParams := { naStr: naValueInternal, nullStr: naValueInternal }
541
+
542
+ if len(params) > 0 {
543
+ if !is_map(params[0]) {
544
+ ll.panic("Optional build params must be a map.")
545
+ }
546
+
547
+ currentExportParams = maps.deepMerge(currentExportParams, params[0])
548
+ }
549
+
550
+ pFrame := _constructPFrameAndDistillSpecs().pFrame
551
+ return xsv.exportFrame(pFrame, xsvType, currentExportParams)
552
+ },
553
+
554
+
555
+
556
+
557
+
558
+
559
+
560
+
561
+
562
+
563
+
564
+
565
+
566
+
567
+
568
+
569
+
570
+
571
+
572
+
573
+
574
+
575
+ buildForPT: func() {
576
+ if len(requests) == 0 {
577
+ ll.panic("Cannot build PTabler output: no columns have been added.")
578
+ }
579
+
580
+ finalExportParams := {
581
+ naStr: PTablerNA,
582
+ nullStr: PTablerNA
583
+ }
584
+
585
+ buildOutput := _constructPFrameAndDistillSpecs()
586
+ pFrame := buildOutput.pFrame
587
+ uniqueAxes := buildOutput.uniqueAxes
588
+
589
+ tableResource := xsv.exportFrame(pFrame, xsvType, finalExportParams)
590
+
591
+ schema := []
592
+
593
+
594
+ for axisEntry in uniqueAxes {
595
+ axisSchema := {
596
+ column: axisEntry.label,
597
+ nullValue: PTablerNA
598
+ }
599
+ axisSchema.type = axisEntry.type
600
+ schema = append(schema, axisSchema)
601
+ }
602
+
603
+
604
+ for request in requests {
605
+ colSchema := {
606
+ column: request.header,
607
+ nullValue: PTablerNA
608
+ }
609
+ colSchema.type = request.spec.valueType
610
+ schema = append(schema, colSchema)
611
+ }
612
+
613
+ return {
614
+ xsvType: xsvType,
615
+ file: tableResource,
616
+ schema: schema
617
+ }
618
+ }
619
+ }
620
+ return ll.toStrict(self)
621
+ }
622
+
623
+ export ll.toStrict({
624
+ xsvFileBuilder: xsvFileBuilder
625
+ })