@platforma-open/milaboratories.top-antibodies.workflow 1.17.4 → 1.17.6

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.
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-open/milaboratories.top-antibodies.workflow@1.17.4 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
3
+ > @platforma-open/milaboratories.top-antibodies.workflow@1.17.6 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
4
4
  > shx rm -rf dist && pl-tengo check && pl-tengo build
5
5
 
6
6
  Processing "src/assembling-fasta.tpl.tengo"...
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @platforma-open/milaboratories.top-antibodies.workflow
2
2
 
3
+ ## 1.17.6
4
+
5
+ ### Patch Changes
6
+
7
+ - 65cbdd5: Minor fix to prevent leads spec multiple match
8
+
9
+ ## 1.17.5
10
+
11
+ ### Patch Changes
12
+
13
+ - ff606b5: Implement multi-selection filters
14
+ - Updated dependencies [ff606b5]
15
+ - @platforma-open/milaboratories.top-antibodies.sample-clonotypes@1.9.3
16
+
3
17
  ## 1.17.4
4
18
 
5
19
  ### Patch Changes
@@ -6,13 +6,14 @@ getColumns := func(datasetSpec, addRanking) {
6
6
  id: "link",
7
7
  allowNA: false,
8
8
  spec: {
9
- name: "pl7.app/vdj/sampling-column",
9
+ name: "pl7.app/vdj/lead-selection",
10
10
  valueType: "Int",
11
11
  domain: {},
12
12
  annotations: {
13
- "pl7.app/label": "Sampling column",
13
+ "pl7.app/label": "Selected Leads",
14
14
  "pl7.app/table/visibility": "hidden",
15
- "pl7.app/isSubset": "true"
15
+ "pl7.app/isSubset": "true",
16
+ "pl7.app/toPlot": "true"
16
17
  }
17
18
  }
18
19
  }]
@@ -1,6 +1,7 @@
1
1
 
2
2
 
3
3
  slices := import("@platforma-sdk/workflow-tengo:slices")
4
+ json := import("json")
4
5
 
5
6
 
6
7
 
@@ -545,6 +546,89 @@ initializeAssemSeqTable := func(pframes, columns, datasetSpec, isSingleCell) {
545
546
  }
546
547
  }
547
548
 
549
+
550
+ filterOperatorMap := {
551
+ "number_greaterThan": ">",
552
+ "number_greaterThanOrEqualTo": ">=",
553
+ "number_lessThan": "<",
554
+ "number_lessThanOrEqualTo": "<=",
555
+ "number_equals": "=",
556
+ "number_notEquals": "!=",
557
+ "string_equals": "=",
558
+ "string_notEquals": "!=",
559
+ "string_contains": "contains",
560
+ "string_doesNotContain": "not contains",
561
+ "string_in": "in",
562
+ "string_notIn": "not in"
563
+ }
564
+
565
+
566
+
567
+
568
+
569
+
570
+
571
+
572
+
573
+ formatFilterDescription := func(columnLabel, filter) {
574
+ if is_undefined(filter) || is_undefined(filter.type) {
575
+ return columnLabel
576
+ }
577
+
578
+
579
+ op := filterOperatorMap[filter.type]
580
+ if is_undefined(op) {
581
+ op = filter.type
582
+ }
583
+
584
+ ref := filter.reference
585
+
586
+ if filter.type == "string_in" || filter.type == "string_notIn" {
587
+ values := json.decode(string(ref))
588
+ valStr := "["
589
+ for i, v in values {
590
+ if i > 0 {
591
+ valStr = valStr + ", "
592
+ }
593
+ valStr = valStr + string(v)
594
+ }
595
+ ref = valStr + "]"
596
+ }
597
+
598
+ return columnLabel + " " + op + " " + string(ref)
599
+ }
600
+
601
+
602
+
603
+
604
+
605
+
606
+
607
+
608
+
609
+ buildFilterTraceLabel := func(filters, columns) {
610
+ result := ""
611
+ count := 0
612
+ for filter in filters {
613
+ if !is_undefined(filter.value) {
614
+ colSpec := columns.getSpec(filter.value.column)
615
+ if !is_undefined(colSpec) {
616
+ colLabel := colSpec.annotations["pl7.app/label"]
617
+ if is_undefined(colLabel) {
618
+ colLabel = colSpec.name
619
+ }
620
+ desc := formatFilterDescription(colLabel, filter.filter)
621
+ if count > 0 {
622
+ result = result + ", "
623
+ }
624
+ result = result + desc
625
+ count = count + 1
626
+ }
627
+ }
628
+ }
629
+ return result
630
+ }
631
+
548
632
  export {
549
633
  clusterAxisDomainsMatch: clusterAxisDomainsMatch,
550
634
  findMatchingLinkerIndex: findMatchingLinkerIndex,
@@ -555,5 +639,7 @@ export {
555
639
  makeHeaderName: makeHeaderName,
556
640
  initializeCdr3SeqTable: initializeCdr3SeqTable,
557
641
  detectBulkChain: detectBulkChain,
558
- initializeAssemSeqTable: initializeAssemSeqTable
642
+ initializeAssemSeqTable: initializeAssemSeqTable,
643
+ formatFilterDescription: formatFilterDescription,
644
+ buildFilterTraceLabel: buildFilterTraceLabel
559
645
  }
Binary file
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@platforma-open/milaboratories.top-antibodies.workflow",
3
- "version": "1.17.4",
3
+ "version": "1.17.6",
4
4
  "type": "module",
5
5
  "description": "Block Workflow",
6
6
  "dependencies": {
7
- "@platforma-sdk/workflow-tengo": "5.8.1",
7
+ "@platforma-sdk/workflow-tengo": "5.8.2",
8
8
  "@platforma-open/milaboratories.software-anarci": "^0.0.3",
9
- "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "1.9.2",
9
+ "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "1.9.3",
10
10
  "@platforma-open/milaboratories.top-antibodies.spectratype": "1.8.1",
11
11
  "@platforma-open/milaboratories.top-antibodies.assembling-fasta": "1.3.0",
12
- "@platforma-open/milaboratories.top-antibodies.umap": "1.2.1",
13
- "@platforma-open/milaboratories.top-antibodies.anarci-kabat": "1.3.0"
12
+ "@platforma-open/milaboratories.top-antibodies.anarci-kabat": "1.3.0",
13
+ "@platforma-open/milaboratories.top-antibodies.umap": "1.2.1"
14
14
  },
15
15
  "devDependencies": {
16
- "@platforma-sdk/tengo-builder": "2.4.14"
16
+ "@platforma-sdk/tengo-builder": "2.4.18"
17
17
  },
18
18
  "scripts": {
19
19
  "build": "shx rm -rf dist && pl-tengo check && pl-tengo build",
@@ -4,6 +4,7 @@ exec := import("@platforma-sdk/workflow-tengo:exec")
4
4
  assets := import("@platforma-sdk/workflow-tengo:assets")
5
5
  pframes := import("@platforma-sdk/workflow-tengo:pframes")
6
6
  xsv := import("@platforma-sdk/workflow-tengo:pframes.xsv")
7
+ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
7
8
  render := import("@platforma-sdk/workflow-tengo:render")
8
9
  sampledColsConv := import(":sampled-cols-conv")
9
10
  sampledExportConv := import(":sampled-export-conv")
@@ -93,7 +94,21 @@ self.body(func(inputs) {
93
94
  rankingMap, inputs.disableClusterRanking, inputs.rawClusterColumn, topClonotypes)
94
95
  sampledColumnsOnlyPf := xsv.importFile(finalClonotypes, "parquet", sampledExportsParams,
95
96
  {cpu: 1, mem: "16GiB"})
96
- outputs["sampledColumnsExport"] = sampledColumnsOnlyPf
97
+
98
+ // Create trace with filter descriptions and inject into exported column
99
+ traceLabel := inputs.filterTraceLabel
100
+ if is_undefined(traceLabel) || traceLabel == "" {
101
+ traceLabel = "Selected Leads"
102
+ }
103
+ trace := pSpec.makeTrace(datasetSpec,
104
+ {
105
+ type: "milaboratories.antibody-tcr-lead-selection",
106
+ importance: 30,
107
+ label: traceLabel
108
+ })
109
+ exportPf := pframes.pFrameBuilder()
110
+ exportPf.add("link", trace.inject(sampledColumnsOnlyPf["link.spec"]), sampledColumnsOnlyPf["link.data"])
111
+ outputs["sampledColumnsExport"] = exportPf.build()
97
112
 
98
113
  return outputs
99
114
  })
@@ -6,6 +6,7 @@ xsv := import("@platforma-sdk/workflow-tengo:pframes.xsv")
6
6
  pframes := import("@platforma-sdk/workflow-tengo:pframes")
7
7
  slices := import("@platforma-sdk/workflow-tengo:slices")
8
8
  render := import("@platforma-sdk/workflow-tengo:render")
9
+ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
9
10
  ll := import("@platforma-sdk/workflow-tengo:ll")
10
11
  kabatConv := import(":pf-kabat-conv")
11
12
 
@@ -132,10 +133,13 @@ wf.body(func(args) {
132
133
  addedCols := tableInit.addedCols
133
134
 
134
135
  // Continue only if we have at least a column
135
- // This condition prevents temporal intermittent error while filters are
136
+ // This condition prevents temporal intermittent error while filters are
136
137
  // being processed and possibly in other situations too
137
138
  if addedCols {
138
139
 
140
+ // Build trace label from filter descriptions
141
+ filterTraceLabel := utils.buildFilterTraceLabel(args.filters, columns)
142
+
139
143
  // Use ender.create to call the filter-clonotypes template
140
144
  filterSampleResult := render.create(filterAndSampleTpl, {
141
145
  inputAnchor: args.inputAnchor,
@@ -148,9 +152,10 @@ wf.body(func(args) {
148
152
  topClonotypes: args.topClonotypes,
149
153
  disableClusterRanking: args.disableClusterRanking,
150
154
  clusterColumn: clusterColumnHeader,
151
- rawClusterColumn: args.clusterColumn
155
+ rawClusterColumn: args.clusterColumn,
156
+ filterTraceLabel: filterTraceLabel
152
157
  })
153
-
158
+
154
159
  // Get the filtered clonotypes from the template result
155
160
  outputs["sampledRows"] = filterSampleResult.output("sampledRows", 24 * 60 * 60 * 1000)
156
161
 
@@ -6,13 +6,14 @@ getColumns := func(datasetSpec, addRanking) {
6
6
  id: "link",
7
7
  allowNA: false,
8
8
  spec: {
9
- name: "pl7.app/vdj/sampling-column",
9
+ name: "pl7.app/vdj/lead-selection",
10
10
  valueType: "Int",
11
11
  domain: {},
12
12
  annotations: {
13
- "pl7.app/label": "Sampling column",
13
+ "pl7.app/label": "Selected Leads",
14
14
  "pl7.app/table/visibility": "hidden",
15
- "pl7.app/isSubset": "true"
15
+ "pl7.app/isSubset": "true",
16
+ "pl7.app/toPlot": "true"
16
17
  }
17
18
  }
18
19
  }]
@@ -1,6 +1,7 @@
1
1
  // Utility functions for antibody-tcr-lead-selection workflow
2
2
 
3
3
  slices := import("@platforma-sdk/workflow-tengo:slices")
4
+ json := import("json")
4
5
 
5
6
  /**
6
7
  * Checks if two clusterId axes have matching domains.
@@ -545,6 +546,89 @@ initializeAssemSeqTable := func(pframes, columns, datasetSpec, isSingleCell) {
545
546
  }
546
547
  }
547
548
 
549
+ // Maps filter type to short operator symbol for trace labels.
550
+ filterOperatorMap := {
551
+ "number_greaterThan": ">",
552
+ "number_greaterThanOrEqualTo": ">=",
553
+ "number_lessThan": "<",
554
+ "number_lessThanOrEqualTo": "<=",
555
+ "number_equals": "=",
556
+ "number_notEquals": "!=",
557
+ "string_equals": "=",
558
+ "string_notEquals": "!=",
559
+ "string_contains": "contains",
560
+ "string_doesNotContain": "not contains",
561
+ "string_in": "in",
562
+ "string_notIn": "not in"
563
+ }
564
+
565
+ /**
566
+ * Formats a single filter as a short description for trace labels.
567
+ * e.g. "Min quality FR1 > 0", "Productive = true", "Chain in Heavy, Light"
568
+ *
569
+ * @param columnLabel - Human-readable column name
570
+ * @param filter - Filter object with type and reference fields
571
+ * @return Formatted filter description string
572
+ */
573
+ formatFilterDescription := func(columnLabel, filter) {
574
+ if is_undefined(filter) || is_undefined(filter.type) {
575
+ return columnLabel
576
+ }
577
+
578
+ // Return filter.type if short operator is not found
579
+ op := filterOperatorMap[filter.type]
580
+ if is_undefined(op) {
581
+ op = filter.type
582
+ }
583
+
584
+ ref := filter.reference
585
+ // In multiple select filters, parse JSON array and join values
586
+ if filter.type == "string_in" || filter.type == "string_notIn" {
587
+ values := json.decode(string(ref))
588
+ valStr := "["
589
+ for i, v in values {
590
+ if i > 0 {
591
+ valStr = valStr + ", "
592
+ }
593
+ valStr = valStr + string(v)
594
+ }
595
+ ref = valStr + "]"
596
+ }
597
+
598
+ return columnLabel + " " + op + " " + string(ref)
599
+ }
600
+
601
+ /**
602
+ * Builds a comma-separated filter description string for use in trace labels.
603
+ * e.g. "Min quality FR1 > 0, Productive = true, Chain in Heavy, Light"
604
+ *
605
+ * @param filters - Array of filter objects from block args
606
+ * @param columns - PBundle to look up column specs for labels
607
+ * @return Filter description string, or empty string if no valid filters
608
+ */
609
+ buildFilterTraceLabel := func(filters, columns) {
610
+ result := ""
611
+ count := 0
612
+ for filter in filters {
613
+ if !is_undefined(filter.value) {
614
+ colSpec := columns.getSpec(filter.value.column)
615
+ if !is_undefined(colSpec) {
616
+ colLabel := colSpec.annotations["pl7.app/label"]
617
+ if is_undefined(colLabel) {
618
+ colLabel = colSpec.name
619
+ }
620
+ desc := formatFilterDescription(colLabel, filter.filter)
621
+ if count > 0 {
622
+ result = result + ", "
623
+ }
624
+ result = result + desc
625
+ count = count + 1
626
+ }
627
+ }
628
+ }
629
+ return result
630
+ }
631
+
548
632
  export {
549
633
  clusterAxisDomainsMatch: clusterAxisDomainsMatch,
550
634
  findMatchingLinkerIndex: findMatchingLinkerIndex,
@@ -555,5 +639,7 @@ export {
555
639
  makeHeaderName: makeHeaderName,
556
640
  initializeCdr3SeqTable: initializeCdr3SeqTable,
557
641
  detectBulkChain: detectBulkChain,
558
- initializeAssemSeqTable: initializeAssemSeqTable
642
+ initializeAssemSeqTable: initializeAssemSeqTable,
643
+ formatFilterDescription: formatFilterDescription,
644
+ buildFilterTraceLabel: buildFilterTraceLabel
559
645
  }