@platforma-sdk/workflow-tengo 5.4.5 → 5.5.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 (55) hide show
  1. package/dist/index.cjs +1 -0
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/tengo/lib/annotations.lib.tengo +92 -0
  5. package/dist/tengo/lib/annotations.util.lib.tengo +117 -0
  6. package/dist/tengo/lib/pframes.bundle.lib.tengo +83 -17
  7. package/dist/tengo/lib/pframes.lib.tengo +1 -0
  8. package/dist/tengo/lib/pframes.spec.lib.tengo +40 -24
  9. package/dist/tengo/lib/pframes.util.lib.tengo +1 -1
  10. package/dist/tengo/lib/pt.expression-traverse.lib.tengo +198 -0
  11. package/dist/tengo/lib/pt.lib.tengo +35 -24
  12. package/dist/tengo/lib/pt.util.lib.tengo +2 -4
  13. package/dist/tengo/lib/slices.lib.tengo +17 -7
  14. package/dist/tengo/tpl/annotations.compute.plj.gz +0 -0
  15. package/dist/tengo/tpl/anonymize.deanonymize.plj.gz +0 -0
  16. package/dist/tengo/tpl/anonymize.impl.plj.gz +0 -0
  17. package/dist/tengo/tpl/check_network.create_workdir_from_storage.plj.gz +0 -0
  18. package/dist/tengo/tpl/check_network.download_blob.plj.gz +0 -0
  19. package/dist/tengo/tpl/check_network.run_hello_world.plj.gz +0 -0
  20. package/dist/tengo/tpl/check_network.run_hello_world_py.plj.gz +0 -0
  21. package/dist/tengo/tpl/check_network.upload_blob.plj.gz +0 -0
  22. package/dist/tengo/tpl/check_network.upload_template.plj.gz +0 -0
  23. package/dist/tengo/tpl/check_network.workdir_type_name_extractor.plj.gz +0 -0
  24. package/dist/tengo/tpl/exec.R.create-renv.plj.gz +0 -0
  25. package/dist/tengo/tpl/exec.conda.create-env.plj.gz +0 -0
  26. package/dist/tengo/tpl/exec.delayed-compute-allocation.impl.plj.gz +0 -0
  27. package/dist/tengo/tpl/exec.exec-impl.plj.gz +0 -0
  28. package/dist/tengo/tpl/exec.exec.plj.gz +0 -0
  29. package/dist/tengo/tpl/exec.load-software.impl.plj.gz +0 -0
  30. package/dist/tengo/tpl/exec.monetization_info.plj.gz +0 -0
  31. package/dist/tengo/tpl/exec.monetization_to_json.plj.gz +0 -0
  32. package/dist/tengo/tpl/exec.python.create-venv.plj.gz +0 -0
  33. package/dist/tengo/tpl/exec.python.install-deps.plj.gz +0 -0
  34. package/dist/tengo/tpl/exec.python.venv-bin-path.plj.gz +0 -0
  35. package/dist/tengo/tpl/ll.get-field.plj.gz +0 -0
  36. package/dist/tengo/tpl/pframes.export-multiple-pcolumns.plj.gz +0 -0
  37. package/dist/tengo/tpl/pframes.export-parquet-pcolumn-chunk.plj.gz +0 -0
  38. package/dist/tengo/tpl/pframes.export-pframe-for-ui.plj.gz +0 -0
  39. package/dist/tengo/tpl/pframes.export-pframe.plj.gz +0 -0
  40. package/dist/tengo/tpl/pframes.export-single-pcolumn.plj.gz +0 -0
  41. package/dist/tengo/tpl/pframes.import-dir.plj.gz +0 -0
  42. package/dist/tengo/tpl/pframes.parse-to-json.plj.gz +0 -0
  43. package/dist/tengo/tpl/pframes.process-pcolumn-data.plj.gz +0 -0
  44. package/dist/tengo/tpl/pframes.remove-nulls.plj.gz +0 -0
  45. package/dist/tengo/tpl/pframes.slice-data.plj.gz +0 -0
  46. package/dist/tengo/tpl/pframes.xsv-export-pframe.plj.gz +0 -0
  47. package/dist/tengo/tpl/pframes.xsv-import-if-not-null.plj.gz +0 -0
  48. package/dist/tengo/tpl/pframes.xsv-import-map.plj.gz +0 -0
  49. package/dist/tengo/tpl/pt.import-dir.plj.gz +0 -0
  50. package/dist/tengo/tpl/pt.workflow-run.plj.gz +0 -0
  51. package/dist/tengo/tpl/waiter.waiter.plj.gz +0 -0
  52. package/dist/tengo/tpl/workdir.save.plj.gz +0 -0
  53. package/dist/tengo/tpl/workflow.build-ctx.plj.gz +0 -0
  54. package/dist/tengo/tpl/workflow.query-anchored.plj.gz +0 -0
  55. package/package.json +4 -4
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ module.exports = { Templates: {
31
31
  'exec.load-software.impl': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.load-software.impl.plj.gz') },
32
32
  'exec.exec': { type: 'from-file', path: require.resolve('./tengo/tpl/exec.exec.plj.gz') },
33
33
  'pt.workflow-run': { type: 'from-file', path: require.resolve('./tengo/tpl/pt.workflow-run.plj.gz') },
34
+ 'annotations.compute': { type: 'from-file', path: require.resolve('./tengo/tpl/annotations.compute.plj.gz') },
34
35
  'check_network.run_hello_world': { type: 'from-file', path: require.resolve('./tengo/tpl/check_network.run_hello_world.plj.gz') },
35
36
  'check_network.run_hello_world_py': { type: 'from-file', path: require.resolve('./tengo/tpl/check_network.run_hello_world_py.plj.gz') },
36
37
  'pframes.xsv-export-pframe': { type: 'from-file', path: require.resolve('./tengo/tpl/pframes.xsv-export-pframe.plj.gz') },
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  declare type TemplateFromFile = { readonly type: "from-file"; readonly path: string; };
2
- declare type TplName = "ll.get-field" | "pframes.export-parquet-pcolumn-chunk" | "pframes.export-single-pcolumn" | "pframes.import-dir" | "pframes.remove-nulls" | "pt.import-dir" | "waiter.waiter" | "workdir.save" | "workflow.build-ctx" | "workflow.query-anchored" | "anonymize.deanonymize" | "anonymize.impl" | "check_network.workdir_type_name_extractor" | "exec.delayed-compute-allocation.impl" | "exec.monetization_to_json" | "exec.python.install-deps" | "exec.python.venv-bin-path" | "pframes.export-multiple-pcolumns" | "pframes.slice-data" | "check_network.create_workdir_from_storage" | "check_network.download_blob" | "check_network.upload_blob" | "check_network.upload_template" | "exec.R.create-renv" | "exec.conda.create-env" | "exec.exec-impl" | "exec.monetization_info" | "exec.python.create-venv" | "pframes.parse-to-json" | "exec.load-software.impl" | "exec.exec" | "pt.workflow-run" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "pframes.xsv-export-pframe" | "pframes.xsv-import-if-not-null" | "pframes.xsv-import-map" | "pframes.process-pcolumn-data" | "pframes.export-pframe" | "pframes.export-pframe-for-ui";
2
+ declare type TplName = "ll.get-field" | "pframes.export-parquet-pcolumn-chunk" | "pframes.export-single-pcolumn" | "pframes.import-dir" | "pframes.remove-nulls" | "pt.import-dir" | "waiter.waiter" | "workdir.save" | "workflow.build-ctx" | "workflow.query-anchored" | "anonymize.deanonymize" | "anonymize.impl" | "check_network.workdir_type_name_extractor" | "exec.delayed-compute-allocation.impl" | "exec.monetization_to_json" | "exec.python.install-deps" | "exec.python.venv-bin-path" | "pframes.export-multiple-pcolumns" | "pframes.slice-data" | "check_network.create_workdir_from_storage" | "check_network.download_blob" | "check_network.upload_blob" | "check_network.upload_template" | "exec.R.create-renv" | "exec.conda.create-env" | "exec.exec-impl" | "exec.monetization_info" | "exec.python.create-venv" | "pframes.parse-to-json" | "exec.load-software.impl" | "exec.exec" | "pt.workflow-run" | "annotations.compute" | "check_network.run_hello_world" | "check_network.run_hello_world_py" | "pframes.xsv-export-pframe" | "pframes.xsv-import-if-not-null" | "pframes.xsv-import-map" | "pframes.process-pcolumn-data" | "pframes.export-pframe" | "pframes.export-pframe-for-ui";
3
3
  declare const Templates: Record<TplName, TemplateFromFile>;
4
4
  export { Templates };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ export const Templates = {
32
32
  'exec.load-software.impl': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.load-software.impl.plj.gz') },
33
33
  'exec.exec': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/exec.exec.plj.gz') },
34
34
  'pt.workflow-run': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pt.workflow-run.plj.gz') },
35
+ 'annotations.compute': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/annotations.compute.plj.gz') },
35
36
  'check_network.run_hello_world': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/check_network.run_hello_world.plj.gz') },
36
37
  'check_network.run_hello_world_py': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/check_network.run_hello_world_py.plj.gz') },
37
38
  'pframes.xsv-export-pframe': { type: 'from-file', path: resolve(import.meta.dirname, './tengo/tpl/pframes.xsv-export-pframe.plj.gz') },
@@ -0,0 +1,92 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ wf := import("@platforma-sdk/workflow-tengo:workflow")
3
+ util := import("@platforma-sdk/workflow-tengo:annotations.util")
4
+ render := import("@platforma-sdk/workflow-tengo:render")
5
+ assets := import("@platforma-sdk/workflow-tengo:assets")
6
+ slices := import("@platforma-sdk/workflow-tengo:slices")
7
+ validation := import("@platforma-sdk/workflow-tengo:validation")
8
+
9
+ compute := assets.importTemplate("@platforma-sdk/workflow-tengo:annotations.compute")
10
+
11
+ _STEP_SCHEMA := {
12
+ type: "string",
13
+ name: "string",
14
+ value: { "any": "any" }
15
+ }
16
+
17
+ _STEP_SCHEMAS := [_STEP_SCHEMA]
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+ computeAnnotations := func(inputs) {
36
+ if len(inputs.steps) == 0 {
37
+ return {}
38
+ }
39
+
40
+ validation.assertType(inputs.steps, _STEP_SCHEMAS)
41
+
42
+ steps := inputs.steps
43
+ title := inputs.title
44
+ inputAnchor := inputs.inputAnchor
45
+ resultAxesSpec := inputs.resultAxesSpec
46
+ resultColumnSpec := util.prepareResultColumnSpec(inputs)
47
+ shouldComputeFilters := is_bool(inputs.shouldComputeFilters) ? inputs.shouldComputeFilters : false
48
+
49
+ columnsFromExpressions := util.extractColumnsFromExpressions(slices.map(steps, func(step) { return step.value }))
50
+ additionalColumns := is_array(inputs.additionalColumns) ? inputs.additionalColumns : []
51
+ columns := columnsFromExpressions + additionalColumns
52
+
53
+ bundleBuilder := wf.createPBundleBuilder()
54
+ bundleBuilder.addAnchor("main", inputAnchor)
55
+ for column in columns { bundleBuilder.addById(column) }
56
+ columnBundle := bundleBuilder.build()
57
+
58
+ result := render.create(compute, {
59
+ columnBundle: columnBundle,
60
+ params: {
61
+ steps: steps,
62
+ columns: columns,
63
+ resultColumnSpec: resultColumnSpec,
64
+ shouldComputeFilters: shouldComputeFilters
65
+ }
66
+ })
67
+
68
+ annotationsData := result.output("annotationsData")
69
+ annotationsColumn := { column: util.ANNOTATION_HEADER, data: annotationsData, spec: resultColumnSpec }
70
+
71
+ idx := 0
72
+ annotationsFiltersColumns := shouldComputeFilters ? slices.map(steps, func(v) {
73
+ idx += 1
74
+ column := util.encodeColumnName(v.name, idx)
75
+ filterData := result.resolveOutput(["filtersData", column])
76
+ filterSpec := util.createFilterColumnSpec(resultColumnSpec, column, v.name)
77
+ return {
78
+ column: v.name,
79
+ data: filterData,
80
+ spec: filterSpec
81
+ }
82
+ }) : []
83
+
84
+ return {
85
+ annotationsColumn: annotationsColumn,
86
+ annotationsFiltersColumns: annotationsFiltersColumns
87
+ }
88
+ }
89
+
90
+ export ll.toStrict({
91
+ computeAnnotations: computeAnnotations
92
+ })
@@ -0,0 +1,117 @@
1
+ hex := import("hex")
2
+ rand := import("rand")
3
+ text := import("text")
4
+
5
+ ll := import("@platforma-sdk/workflow-tengo:ll")
6
+ wf := import("@platforma-sdk/workflow-tengo:workflow")
7
+ sets := import("@platforma-sdk/workflow-tengo:sets")
8
+ maps := import("@platforma-sdk/workflow-tengo:maps")
9
+ slices := import("@platforma-sdk/workflow-tengo:slices")
10
+
11
+ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
12
+
13
+ expr := import("@platforma-sdk/workflow-tengo:pt.expression-traverse")
14
+
15
+ ANNOTATION_HEADER := "annotation"
16
+
17
+ createFilterFrameName := func(colName) {
18
+ return colName + "_filter_frame"
19
+ }
20
+
21
+ createFilterColumnSpec := func(columnSpec, colName, label) {
22
+ return maps.deepMerge(columnSpec, {
23
+ kind: "PColumn",
24
+ valueType: "Int",
25
+ name: columnSpec.name + "/filter/" + colName,
26
+ annotations: {
27
+ "pl7.app/label": columnSpec.annotations["pl7.app/label"] + "/" + label,
28
+ "pl7.app/isSubset": "true"
29
+ }
30
+ });
31
+ }
32
+
33
+ ENCODE_SEPARATOR := "_encoded_"
34
+
35
+ encodeColumnName := func(name, idx) {
36
+ return hex.encode(name) + ENCODE_SEPARATOR + string(idx)
37
+ }
38
+
39
+ decodeColumnName := func(encoded) {
40
+ parts := text.split(encoded, ENCODE_SEPARATOR)
41
+ if len(parts) != 2 {
42
+ ll.panic("Invalid encoded column name: " + encoded)
43
+ }
44
+
45
+ return string(hex.decode(parts[0]))
46
+ }
47
+
48
+ addColumnsFromExpression := func(expression, columnsSet) {
49
+ expr.expressionTraverse(expression, func(node) {
50
+ if node.type == "col" {
51
+
52
+ if is_string(node.name) {
53
+ sets.add(columnsSet, node.name)
54
+ }
55
+ return
56
+ }
57
+ })
58
+ }
59
+
60
+ mapExpressionColumnNames := func(expression, transformFn) {
61
+ copy := maps.clone(expression)
62
+
63
+ expr.expressionTraverse(copy, func(node) {
64
+ if node.type == "col" {
65
+
66
+ if is_string(node.name) {
67
+ node.name = transformFn(node.name)
68
+ }
69
+ }
70
+ })
71
+
72
+ return copy
73
+ }
74
+
75
+ extractColumnsFromExpressions := func(expressions) {
76
+ columnsSet := {}
77
+
78
+ for expression in expressions {
79
+ addColumnsFromExpression(expression, columnsSet)
80
+ }
81
+
82
+ return sets.toSlice(columnsSet)
83
+ }
84
+
85
+ prepareResultColumnSpec := func(args) {
86
+ blockId := wf.getBlockId()
87
+
88
+ steps := slices.filter([args.trace, {
89
+ id: blockId,
90
+ type: "milaboratories.annotation",
91
+ label: args.title,
92
+ importance: 20
93
+ }], func(v) { return !is_undefined(v) })
94
+
95
+ trace := pSpec.makeTrace(steps...)
96
+
97
+ return trace.inject({
98
+ kind: "PColumn",
99
+ valueType: "String",
100
+ name: "pl7.app/annotation/result",
101
+ domain: { "pl7.app/annotationRunId": blockId },
102
+ axesSpec: args.resultAxesSpec,
103
+ annotations: { "pl7.app/label": args.title }
104
+ })
105
+ }
106
+
107
+ export ll.toStrict({
108
+ ANNOTATION_HEADER: ANNOTATION_HEADER,
109
+
110
+ encodeColumnName: encodeColumnName,
111
+ decodeColumnName: decodeColumnName,
112
+ createFilterFrameName: createFilterFrameName,
113
+ prepareResultColumnSpec: prepareResultColumnSpec,
114
+ createFilterColumnSpec: createFilterColumnSpec,
115
+ mapExpressionColumnNames: mapExpressionColumnNames,
116
+ extractColumnsFromExpressions: extractColumnsFromExpressions
117
+ })
@@ -394,11 +394,6 @@ pColumnBundleUnmarshaller := func() {
394
394
  }
395
395
  }
396
396
 
397
-
398
-
399
-
400
- axisHeaderOverrides := []
401
-
402
397
  get := func(resultKey) {
403
398
  if !is_string(resultKey) {
404
399
  resultKey = canonical.encode(resultKey)
@@ -435,7 +430,11 @@ pColumnBundleUnmarshaller := func() {
435
430
 
436
431
 
437
432
 
438
- getColumn: func(resultKey) {
433
+
434
+
435
+
436
+ getColumn: func(resultKey, ...options) {
437
+ options = is_array(options) && is_map(options[0]) ? options[0] : {}
439
438
  processedId := processColumnId(resultKey)
440
439
  columnKeyForPool := processedId.columnKey // Key to fetch from the pool (source for filtered)
441
440
  columnAxisFilters := processedId.axisFilters // Filters from FilteredPColumnId
@@ -450,7 +449,7 @@ pColumnBundleUnmarshaller := func() {
450
449
  ll.panic("Cannot get a single column from a multi-column result: %v. Use getColumns() instead.", columnKeyForPool)
451
450
  }
452
451
 
453
- currentSpec := bundle.getSpec(resultKey) // getSpec already handles filtering for the spec
452
+ currentSpec := bundle.getSpec(resultKey, options) // getSpec already handles filtering for the spec
454
453
  currentData := result.data
455
454
 
456
455
 
@@ -458,7 +457,9 @@ pColumnBundleUnmarshaller := func() {
458
457
  sliceDataTemplate := assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.slice-data")
459
458
  currentData = render.createEphemeral(sliceDataTemplate, {
460
459
  data: currentData,
461
- slicingParams: smart.createJsonResource(columnAxisFilters)
460
+ slicingParams: smart.createJsonResource(columnAxisFilters),
461
+ keepColumns: options.keepColumns,
462
+ ignoreNonPartitioned: options.ignoreNonPartitioned
462
463
  }).output("result")
463
464
  }
464
465
 
@@ -475,11 +476,16 @@ pColumnBundleUnmarshaller := func() {
475
476
 
476
477
 
477
478
 
478
- getSpec: func(id) {
479
+
480
+
481
+ getSpec: func(id, ...options) {
479
482
  processedId := processColumnId(id)
480
483
  columnKey := processedId.columnKey
481
484
  columnAxisFilters := processedId.axisFilters // Filters from FilteredPColumnId
482
485
 
486
+ options = is_array(options) && is_map(options[0]) ? options[0] : {}
487
+ keepColumns := is_bool(options.keepColumns) ? options.keepColumns : false
488
+
483
489
  if is_undefined(pool[columnKey]) {
484
490
  ll.panic("Column not found in bundle: " + columnKey)
485
491
  }
@@ -492,9 +498,11 @@ pColumnBundleUnmarshaller := func() {
492
498
  baseSpec := result.spec
493
499
 
494
500
 
495
- if !is_undefined(columnAxisFilters) && len(columnAxisFilters) > 0 {
496
- prepared := pSpec.prepareAxisFilters(columnAxisFilters, baseSpec)
497
- return prepared.columnSpec
501
+ if !keepColumns && !is_undefined(columnAxisFilters) && len(columnAxisFilters) > 0 {
502
+ prepared := pSpec.prepareAxisFiltersAndAxesSpec(columnAxisFilters, baseSpec.axesSpec)
503
+ clonedSpec := maps.clone(baseSpec)
504
+ clonedSpec.axesSpec = prepared.axesSpec
505
+ return clonedSpec
498
506
  } else {
499
507
 
500
508
  return baseSpec
@@ -505,15 +513,73 @@ pColumnBundleUnmarshaller := func() {
505
513
 
506
514
 
507
515
 
516
+ getColumnKeys: func() {
517
+ return maps.getKeys(pool)
518
+ },
519
+
520
+
521
+
522
+
523
+
524
+
525
+
526
+
508
527
 
509
528
 
510
- setAxisHeader: func(matcher, header) {
511
- if is_string(matcher) {
512
- matcher = { name: matcher, domain: {} }
529
+ getAxesSpec: func(...columnIds) {
530
+
531
+ uniqueAxesMap := {}
532
+ allAxes := []
533
+
534
+
535
+ processAxesSpec := func(spec) {
536
+ if !is_undefined(spec) && !is_undefined(spec.axesSpec) {
537
+ for axis in spec.axesSpec {
538
+ axisKey := pSpec.getAxisId(axis)
539
+
540
+
541
+ if is_undefined(uniqueAxesMap[axisKey]) {
542
+ uniqueAxesMap[axisKey] = true
543
+ allAxes = append(allAxes, axis)
544
+ }
545
+ }
546
+ }
547
+ }
548
+
549
+
550
+ processResult := func(result) {
551
+ if is_array(result) {
552
+
553
+ for _, col in result {
554
+ processAxesSpec(col.spec)
555
+ }
556
+ } else {
557
+
558
+ processAxesSpec(result.spec)
559
+ }
560
+ }
561
+
562
+
563
+ if len(columnIds) > 0 {
564
+ for id in columnIds {
565
+ processedId := processColumnId(id)
566
+ columnKey := processedId.columnKey
567
+
568
+ result := pool[columnKey]
569
+ if is_undefined(result) {
570
+ ll.panic("Column not found: %v. Pool: %v", columnKey, maps.getKeys(pool))
571
+ }
572
+
573
+ processResult(result)
574
+ }
575
+ } else {
576
+
577
+ for result in pool {
578
+ processResult(result)
579
+ }
513
580
  }
514
581
 
515
- axisHeaderOverrides = [[matcher, header]] + axisHeaderOverrides
516
- return bundle
582
+ return allAxes
517
583
  }
518
584
  })
519
585
 
@@ -165,6 +165,7 @@ transformTsvSettings := func(settings) {
165
165
 
166
166
 
167
167
  exportFrame := func(pf) {
168
+ ll.assert(!is_undefined(pf), "pframe reference is required")
168
169
  return render.createEphemeral(assets.importTemplate("@platforma-sdk/workflow-tengo:pframes.export-pframe"), { pf: pf }).output("result")
169
170
  }
170
171
 
@@ -10,6 +10,7 @@ sets := import("@platforma-sdk/workflow-tengo:sets")
10
10
  slices := import("@platforma-sdk/workflow-tengo:slices")
11
11
  validation := import("@platforma-sdk/workflow-tengo:validation")
12
12
  json := import("json")
13
+ canonical := import("@platforma-sdk/workflow-tengo:canonical")
13
14
 
14
15
 
15
16
 
@@ -234,27 +235,33 @@ _TRACE_SCHEMA := [_TRACE_STEP_SCHEMA]
234
235
 
235
236
 
236
237
 
237
- makeTrace := func(input, ...steps) {
238
-
239
- validation.assertType(steps, _TRACE_SCHEMA)
240
-
241
- currentTraceContent := undefined
242
- if is_string(input) {
243
- currentTraceContent = input
244
- } else if is_string(input[A_TRACE]) {
245
- currentTraceContent = input[A_TRACE]
246
- } else if is_string(input["annotations"][A_TRACE]) {
247
- currentTraceContent = input["annotations"][A_TRACE]
248
- }
238
+ makeTrace := func(...steps) {
239
+ ll.assert(len(steps) > 0, "makeTrace: expects at least one argument")
240
+
249
241
 
242
+ input := steps[0]
243
+ steps = steps[1:]
250
244
  currentTrace := []
251
- if !is_undefined(currentTraceContent) {
252
- currentTrace = json.decode(currentTraceContent)
245
+
246
+ if (!validation.isValid(input, _TRACE_STEP_SCHEMA)) {
247
+ inputTraceContent := undefined
248
+
249
+ if is_string(input) {
250
+ inputTraceContent = input
251
+ } else if is_map(input) && is_string(input[A_TRACE]) {
252
+ inputTraceContent = input[A_TRACE]
253
+ } else if is_map(input["annotations"]) && is_map(input["annotations"]) && is_string(input["annotations"][A_TRACE]) {
254
+ inputTraceContent = input["annotations"][A_TRACE]
255
+ }
253
256
 
254
- validation.assertType(currentTrace, _TRACE_SCHEMA)
257
+ if !is_undefined(inputTraceContent) {
258
+ currentTrace = json.decode(inputTraceContent)
259
+ validation.assertType(currentTrace, _TRACE_SCHEMA)
260
+ }
255
261
  }
256
262
 
257
263
  if len(steps) > 0 {
264
+ validation.assertType(steps, _TRACE_SCHEMA)
258
265
  currentTrace = append(currentTrace, steps...)
259
266
  }
260
267
  currentTraceStr := string(json.encode(currentTrace))
@@ -465,7 +472,7 @@ createSpecDistiller := func(specs) {
465
472
 
466
473
 
467
474
 
468
- prepareAxisFilters := func(rules, spec) {
475
+ prepareAxisFiltersAndAxesSpec := func(rules, axesSpec) {
469
476
  axisFilters := []
470
477
  filteredIndices := {}
471
478
 
@@ -480,7 +487,7 @@ prepareAxisFilters := func(rules, spec) {
480
487
  axisIdx := axisIdentifier
481
488
  if is_string(axisIdentifier) {
482
489
  axisIdx = -1
483
- for idx, axis in spec.axesSpec {
490
+ for idx, axis in axesSpec {
484
491
  if axis.name == axisIdentifier {
485
492
  axisIdx = idx
486
493
  break
@@ -488,7 +495,7 @@ prepareAxisFilters := func(rules, spec) {
488
495
  }
489
496
 
490
497
  if axisIdx == -1 {
491
- ll.panic("Cannot find axis with name '%v' in column spec %v", axisIdentifier, spec)
498
+ ll.panic("Cannot find axis with name '%v' in column spec %v", axisIdentifier, axesSpec)
492
499
  }
493
500
  }
494
501
 
@@ -501,18 +508,16 @@ prepareAxisFilters := func(rules, spec) {
501
508
  filteredIndices[axisIdx] = true
502
509
  }
503
510
 
504
- transformedSpec := maps.clone(spec)
505
511
  newAxesSpec := []
506
- for idx, axis in spec.axesSpec {
512
+ for idx, axis in axesSpec {
507
513
  if !filteredIndices[idx] {
508
514
  newAxesSpec = append(newAxesSpec, maps.clone(axis))
509
515
  }
510
516
  }
511
- transformedSpec.axesSpec = newAxesSpec
512
517
 
513
518
  return {
514
519
  axisFilters: axisFilters,
515
- columnSpec: transformedSpec
520
+ axesSpec: newAxesSpec
516
521
  }
517
522
  }
518
523
 
@@ -535,6 +540,15 @@ axisSpecToMatcher := func(axisSpec) {
535
540
  return matcher
536
541
  }
537
542
 
543
+ getAxisId := func(axisSpec) {
544
+ validation.assertType(axisSpec, P_AXIS_SPEC_SCHEMA)
545
+ return canonical.encode({
546
+ name: axisSpec.name,
547
+ type: axisSpec.type,
548
+ domain: axisSpec.domain
549
+ })
550
+ }
551
+
538
552
  export ll.toStrict({
539
553
  P_UNTYPED_SPEC_SCHEMA: P_UNTYPED_SPEC_SCHEMA,
540
554
  P_AXIS_SPEC_SCHEMA: P_AXIS_SPEC_SCHEMA,
@@ -561,7 +575,9 @@ export ll.toStrict({
561
575
 
562
576
  createSpecDistiller: createSpecDistiller,
563
577
 
564
- prepareAxisFilters: prepareAxisFilters,
565
- axisSpecToMatcher: axisSpecToMatcher
578
+ prepareAxisFiltersAndAxesSpec: prepareAxisFiltersAndAxesSpec,
579
+ axisSpecToMatcher: axisSpecToMatcher,
580
+
581
+ getAxisId: getAxisId
566
582
  })
567
583
 
@@ -436,7 +436,7 @@ pFrameToColumnsMap := func(pf) {
436
436
  ll.assert(pf.checkResourceType(constants.RTYPE_P_FRAME), "Unexpected resource type: %v", pf.info().Type)
437
437
  ll.assert(pf.info().InputsLocked, "PFrame resource does not have inputs locked")
438
438
  for key, field in pf.inputs() {
439
- addColumnSegment(key, field.getValue())
439
+ addColumnSegment(key, field)
440
440
  }
441
441
  } else if ll.isMap(pf) {
442
442
  for key, value in pf {
@@ -0,0 +1,198 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+
3
+ isExpression := func(v) {
4
+ return !is_undefined(v) && is_map(v) && is_string(v.type)
5
+ }
6
+
7
+ expressionTraverse := func(expression, visitFn) {
8
+ if (!isExpression(expression)) {
9
+ return
10
+ }
11
+
12
+ visitFn(expression)
13
+
14
+
15
+ t := expression.type
16
+
17
+ if t == "col" {
18
+ return
19
+ }
20
+
21
+ if t == "const" {
22
+ return
23
+ }
24
+
25
+
26
+ if t == "eq" || t == "neq" || t == "gt" || t == "lt" || t == "ge" || t == "le" {
27
+ expressionTraverse(expression.lhs, visitFn)
28
+ expressionTraverse(expression.rhs, visitFn)
29
+ return
30
+ }
31
+
32
+
33
+ if t == "and" || t == "or" {
34
+ if is_array(expression.operands) {
35
+ for _, op in expression.operands { expressionTraverse(op, visitFn) }
36
+ }
37
+ return
38
+ }
39
+ if t == "not" {
40
+ expressionTraverse(expression.value, visitFn)
41
+ return
42
+ }
43
+
44
+
45
+ if t == "plus" || t == "minus" || t == "multiply" || t == "truediv" || t == "floordiv" {
46
+ expressionTraverse(expression.lhs, visitFn)
47
+ expressionTraverse(expression.rhs, visitFn)
48
+ return
49
+ }
50
+
51
+
52
+ if t == "log10" || t == "log" || t == "log2" || t == "abs" || t == "sqrt" || t == "negate" || t == "floor" || t == "round" || t == "ceil" {
53
+ expressionTraverse(expression.value, visitFn)
54
+ return
55
+ }
56
+
57
+ if t == "cast" {
58
+ expressionTraverse(expression.value, visitFn)
59
+ return
60
+ }
61
+
62
+
63
+ if t == "is_na" || t == "is_not_na" {
64
+ expressionTraverse(expression.value, visitFn)
65
+ return
66
+ }
67
+
68
+
69
+ if t == "str_join" {
70
+ if is_array(expression.operands) {
71
+ for _, op in expression.operands { expressionTraverse(op, visitFn) }
72
+ }
73
+ return
74
+ }
75
+ if t == "hash" {
76
+ expressionTraverse(expression.value, visitFn)
77
+ return
78
+ }
79
+ if t == "to_upper" || t == "to_lower" || t == "str_len" {
80
+ expressionTraverse(expression.value, visitFn)
81
+ return
82
+ }
83
+ if t == "string_distance" {
84
+ expressionTraverse(expression.string1, visitFn)
85
+ expressionTraverse(expression.string2, visitFn)
86
+ return
87
+ }
88
+ if t == "fuzzy_string_filter" {
89
+ expressionTraverse(expression.value, visitFn)
90
+ if isExpression(expression.pattern) { expressionTraverse(expression.pattern, visitFn) }
91
+ return
92
+ }
93
+ if t == "when_then_otherwise" {
94
+ if is_array(expression.conditions) {
95
+ for _, c in expression.conditions {
96
+
97
+ if is_map(c) {
98
+ expressionTraverse(c.when, visitFn)
99
+ expressionTraverse(c.then, visitFn)
100
+ }
101
+ }
102
+ }
103
+ expressionTraverse(expression.otherwise, visitFn)
104
+ return
105
+ }
106
+ if t == "substring" {
107
+ expressionTraverse(expression.value, visitFn)
108
+ expressionTraverse(expression.start, visitFn)
109
+ if !is_undefined(expression.length) { expressionTraverse(expression.length, visitFn) }
110
+ if !is_undefined(expression.end) { expressionTraverse(expression.end, visitFn) }
111
+ return
112
+ }
113
+ if t == "str_replace" {
114
+ expressionTraverse(expression.value, visitFn)
115
+ if isExpression(expression.pattern) { expressionTraverse(expression.pattern, visitFn) }
116
+ if isExpression(expression.replacement) { expressionTraverse(expression.replacement, visitFn) }
117
+ return
118
+ }
119
+ if t == "str_contains" {
120
+ expressionTraverse(expression.value, visitFn)
121
+ if isExpression(expression.pattern) { expressionTraverse(expression.pattern, visitFn) }
122
+ return
123
+ }
124
+ if t == "str_starts_with" {
125
+ expressionTraverse(expression.value, visitFn)
126
+ if isExpression(expression.prefix) { expressionTraverse(expression.prefix, visitFn) }
127
+ return
128
+ }
129
+ if t == "str_ends_with" {
130
+ expressionTraverse(expression.value, visitFn)
131
+ if isExpression(expression.suffix) { expressionTraverse(expression.suffix, visitFn) }
132
+ return
133
+ }
134
+ if t == "str_contains_any" {
135
+ expressionTraverse(expression.value, visitFn)
136
+ return
137
+ }
138
+ if t == "str_count_matches" {
139
+ expressionTraverse(expression.value, visitFn)
140
+ if isExpression(expression.pattern) { expressionTraverse(expression.pattern, visitFn) }
141
+ return
142
+ }
143
+ if t == "str_extract" {
144
+ expressionTraverse(expression.value, visitFn)
145
+ if isExpression(expression.pattern) { expressionTraverse(expression.pattern, visitFn) }
146
+ return
147
+ }
148
+
149
+
150
+ if t == "min" || t == "max" {
151
+ if is_array(expression.operands) {
152
+ for _, op in expression.operands { expressionTraverse(op, visitFn) }
153
+ }
154
+ return
155
+ }
156
+ if t == "fill_null" || t == "fill_nan" {
157
+ expressionTraverse(expression.input, visitFn)
158
+ expressionTraverse(expression.fillValue, visitFn)
159
+ return
160
+ }
161
+ if t == "aggregate" {
162
+ expressionTraverse(expression.value, visitFn)
163
+ if is_array(expression.partitionBy) {
164
+ for _, p in expression.partitionBy { expressionTraverse(p, visitFn) }
165
+ }
166
+ return
167
+ }
168
+ if t == "rank" {
169
+ if is_array(expression.partitionBy) {
170
+ for _, p in expression.partitionBy { expressionTraverse(p, visitFn) }
171
+ }
172
+ if is_array(expression.orderBy) {
173
+ for _, o in expression.orderBy { expressionTraverse(o, visitFn) }
174
+ }
175
+ return
176
+ }
177
+ if t == "cumsum" {
178
+ expressionTraverse(expression.value, visitFn)
179
+ if is_array(expression.additionalOrderBy) {
180
+ for _, o in expression.additionalOrderBy { expressionTraverse(o, visitFn) }
181
+ }
182
+ if is_array(expression.partitionBy) {
183
+ for _, p in expression.partitionBy { expressionTraverse(p, visitFn) }
184
+ }
185
+ return
186
+ }
187
+ if t == "struct_field" {
188
+ expressionTraverse(expression.struct, visitFn)
189
+ return
190
+ }
191
+
192
+ ll.panic("Unknown expression type: " + t)
193
+ }
194
+
195
+ export ll.toStrict({
196
+ expressionTraverse: expressionTraverse,
197
+ isExpression: isExpression
198
+ })
@@ -58,7 +58,6 @@ _newDataFrameGroupBy := undefined
58
58
 
59
59
  workflowCounter := 0
60
60
 
61
-
62
61
  _validateColumn := func(name, columnStruct) {
63
62
  ll.assert(is_string(name), "column name must be a string")
64
63
  ll.assert(is_map(columnStruct), "columnStruct must be a map")
@@ -646,25 +645,35 @@ workflow := func() {
646
645
 
647
646
 
648
647
 
649
- frameFromColumnBundle: func(bundle, options) {
648
+ frameFromColumnBundle: func(bundle, ...options) {
649
+ options = len(options) > 0 && is_map(options[0]) ? options[0] : {}
650
650
  util.validateParamsForFrameFromColumnBundle(options)
651
651
 
652
+ columns := is_array(options.columns) ? options.columns : bundle.getColumnKeys()
653
+
654
+ axes := bundle.getAxesSpec(columns...)
655
+
652
656
  entries := []
653
657
  aliases := []
654
- for axis in options.axes {
655
- aliases = append(aliases, exp.sc.axis(axis.spec).alias(axis.column))
658
+
659
+ for axis in axes {
660
+ aliases = append(aliases, exp.sc.axis(axis).alias(pframesSpec.getAxisId(axis)))
656
661
  }
657
- for column in options.columns {
662
+
663
+ for column in columns {
658
664
  decoded := json.decode(column)
665
+ aliases = append(aliases, exp.col(column))
666
+
659
667
  if (is_undefined(decoded.axisFilters)) {
660
- aliases = append(aliases, exp.col(decoded.name))
661
- entry := p.column(decoded.name, bundle.getColumn(decoded))
668
+ entry := p.column(column, bundle.getColumn(decoded))
662
669
  entries = append(entries, entry)
663
670
  } else {
664
- aliases = append(aliases, exp.col(decoded.source.name))
665
671
  entry := p.slicedColumn(
666
- decoded.source.name,
667
- bundle.getColumn(decoded.source),
672
+ column,
673
+ bundle.getColumn(decoded, {
674
+ keepColumns: true,
675
+ ignoreNonPartitioned: true
676
+ }),
668
677
  slices.map(decoded.axisFilters, func(axisFilter) {
669
678
  return {
670
679
  axisIndex: axisFilter[0],
@@ -675,6 +684,7 @@ workflow := func() {
675
684
  entries = append(entries, entry)
676
685
  }
677
686
  }
687
+
678
688
  return self.frame(p.full(entries...)).select(aliases...)
679
689
  },
680
690
 
@@ -978,30 +988,31 @@ _newDataFrameGroupBy = func(parentWorkflow, dfName, groupByExpressions) {
978
988
  }
979
989
 
980
990
  _newDataFrame = func(parentWorkflow, dfName) {
981
- _mapExprsToStepCols := func(exprs, methodName, ctx) {
982
- ll.assert(len(exprs) > 0, methodName + " requires at least one expression argument.")
991
+ _mapExprsToStepCols := func(items, methodName, ctx) {
992
+ ll.assert(len(items) > 0, methodName + " requires at least one expression argument.")
983
993
  cols := []
984
- for expr in exprs {
985
- if !exp._isExpression(expr) {
986
- ll.panic("Invalid argument to " + methodName + ": Expected an expression object, got %v", expr)
994
+ for item in items {
995
+ if exp._isExpression(item) {
996
+ cols = append(cols, item._getExpression(ctx))
997
+ } else {
998
+ ll.panic("Invalid argument to " + methodName + ": Expected an expression/column object, got %v", item)
987
999
  }
988
- cols = append(cols, expr._getExpression(ctx))
989
1000
  }
990
1001
  return cols
991
1002
  }
992
1003
 
993
- _mapExprsToStepColsAllowingStrings := func(exprs, methodName, ctx) {
994
- ll.assert(len(exprs) > 0, methodName + " requires at least one expression argument.")
1004
+ _mapExprsToStepColsAllowingStrings := func(items, methodName, ctx) {
1005
+ ll.assert(len(items) > 0, methodName + " requires at least one expression argument.")
995
1006
  cols := []
996
- for expr in exprs {
997
- if is_string(expr) {
1007
+ for item in items {
1008
+ if is_string(item) {
998
1009
 
999
- colExpr := exp.col(expr)
1010
+ colExpr := exp.col(item)
1000
1011
  cols = append(cols, colExpr._getExpression(ctx))
1001
- } else if exp._isExpression(expr) {
1002
- cols = append(cols, expr._getExpression(ctx))
1012
+ } else if exp._isExpression(item) {
1013
+ cols = append(cols, item._getExpression(ctx))
1003
1014
  } else {
1004
- ll.panic("Invalid argument to " + methodName + ": Expected an expression object or string column name, got %v", expr)
1015
+ ll.panic("Invalid argument to " + methodName + ": Expected an expression/column object or string column name, got %v", item)
1005
1016
  }
1006
1017
  }
1007
1018
  return cols
@@ -84,15 +84,13 @@ validateParamsForSaveFrameDirect := func(params) {
84
84
 
85
85
  _FRAME_FROM_COLUMN_BUNDLE_PARAMS_SCHEMA := {
86
86
 
87
- `axes`: [_AXIS_SCHEMA],
87
+ `axes,?`: [spec.P_AXIS_SPEC_SCHEMA],
88
88
 
89
- `columns`: [`string`]
89
+ `columns,?`: [`string`]
90
90
  }
91
91
 
92
92
  validateParamsForFrameFromColumnBundle := func(params) {
93
93
  validation.assertType(params, _FRAME_FROM_COLUMN_BUNDLE_PARAMS_SCHEMA, "Invalid params structure")
94
- ll.assert(len(params.axes) > 0, "At least one axis is required")
95
- ll.assert(len(params.columns) > 0, "At least one column is required")
96
94
  }
97
95
 
98
96
  makeFrameSpecs := func(params) {
@@ -140,15 +140,10 @@ map := func(slice, fn) {
140
140
 
141
141
 
142
142
  reduce := func(slice, fn, ...init) {
143
- result := undefined
143
+ result := len(init) > 0 ? init[0] : undefined
144
144
  for i, elem in slice {
145
- if i == 0 {
146
- result = len(init) > 0 ? init : elem
147
- } else {
148
- result = fn(result, elem)
149
- }
145
+ result = fn(result, elem)
150
146
  }
151
-
152
147
  return result
153
148
  }
154
149
 
@@ -158,6 +153,20 @@ reduce := func(slice, fn, ...init) {
158
153
 
159
154
 
160
155
 
156
+ reverse := func(slice) {
157
+ rev := []
158
+ for i := len(slice)-1; i >= 0; i-- {
159
+ rev = append(rev, slice[i])
160
+ }
161
+ return rev
162
+ }
163
+
164
+
165
+
166
+
167
+
168
+
169
+
161
170
 
162
171
 
163
172
 
@@ -376,6 +385,7 @@ export ll.toStrict({
376
385
  filter: filter,
377
386
  map: map,
378
387
  reduce: reduce,
388
+ reverse: reverse,
379
389
  flatten: flatten,
380
390
  every: every,
381
391
  quickSortInPlace: quickSortInPlace,
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/workflow-tengo",
3
- "version": "5.4.5",
3
+ "version": "5.5.0",
4
4
  "description": "Tengo SDK library",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -18,11 +18,11 @@
18
18
  "dependencies": {
19
19
  "@milaboratories/software-pframes-conv": "2.2.9",
20
20
  "@platforma-open/milaboratories.software-small-binaries": "^1.15.23",
21
- "@platforma-open/milaboratories.software-ptabler": "1.12.12",
22
- "@platforma-open/milaboratories.software-ptexter": "1.1.1"
21
+ "@platforma-open/milaboratories.software-ptabler": "1.13.0",
22
+ "@platforma-open/milaboratories.software-ptexter": "1.2.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@platforma-sdk/tengo-builder": "2.3.2"
25
+ "@platforma-sdk/tengo-builder": "2.3.3"
26
26
  },
27
27
  "scripts": {
28
28
  "type-check": "pl-tengo check",