@platforma-open/milaboratories.top-antibodies.workflow 1.17.3 → 1.17.5

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.3 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
3
+ > @platforma-open/milaboratories.top-antibodies.workflow@1.17.5 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.5
4
+
5
+ ### Patch Changes
6
+
7
+ - ff606b5: Implement multi-selection filters
8
+ - Updated dependencies [ff606b5]
9
+ - @platforma-open/milaboratories.top-antibodies.sample-clonotypes@1.9.3
10
+
11
+ ## 1.17.4
12
+
13
+ ### Patch Changes
14
+
15
+ - 605fdf0: Add domain to exported filter
16
+
3
17
  ## 1.17.3
4
18
 
5
19
  ### Patch Changes
@@ -6,11 +6,11 @@ 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
15
  "pl7.app/isSubset": "true"
16
16
  }
@@ -1,6 +1,18 @@
1
1
  ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ json := import("json")
3
+
4
+ getColumns := func(datasetSpec, filterMap, rankingMap, disableClusterRanking, clusterColumn, topClonotypes) {
5
+
6
+
7
+ domain := {
8
+ "pl7.app/vdj/filter-map": string(json.encode(filterMap)),
9
+ "pl7.app/vdj/ranking-map": string(json.encode(rankingMap)),
10
+ "pl7.app/vdj/top-clonotypes": string(topClonotypes)
11
+ }
12
+ if !disableClusterRanking && clusterColumn != undefined && clusterColumn != "" {
13
+ domain["pl7.app/vdj/diversity-column"] = string(json.encode(clusterColumn))
14
+ }
2
15
 
3
- getColumns := func(datasetSpec) {
4
16
  columns := [{
5
17
  column: "top",
6
18
  id: "link",
@@ -8,7 +20,7 @@ getColumns := func(datasetSpec) {
8
20
  spec: {
9
21
  name: "pl7.app/vdj/lead-selection",
10
22
  valueType: "Int",
11
- domain: {},
23
+ domain: domain,
12
24
  annotations: {
13
25
  "pl7.app/label": "Selected Leads",
14
26
  "pl7.app/table/visibility": "hidden",
@@ -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.3",
3
+ "version": "1.17.5",
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",
10
- "@platforma-open/milaboratories.top-antibodies.spectratype": "1.8.1",
9
+ "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "1.9.3",
11
10
  "@platforma-open/milaboratories.top-antibodies.umap": "1.2.1",
12
- "@platforma-open/milaboratories.top-antibodies.anarci-kabat": "1.3.0",
13
- "@platforma-open/milaboratories.top-antibodies.assembling-fasta": "1.3.0"
11
+ "@platforma-open/milaboratories.top-antibodies.spectratype": "1.8.1",
12
+ "@platforma-open/milaboratories.top-antibodies.assembling-fasta": "1.3.0",
13
+ "@platforma-open/milaboratories.top-antibodies.anarci-kabat": "1.3.0"
14
14
  },
15
15
  "devDependencies": {
16
- "@platforma-sdk/tengo-builder": "2.4.13"
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")
@@ -89,10 +90,25 @@ self.body(func(inputs) {
89
90
  outputs["finalClonotypes"] = finalClonotypes
90
91
 
91
92
  // Export only the sampling column to be used as a filter downstream
92
- sampledExportsParams := sampledExportConv.getColumns(datasetSpec)
93
+ sampledExportsParams := sampledExportConv.getColumns(datasetSpec, filterMap,
94
+ rankingMap, inputs.disableClusterRanking, inputs.rawClusterColumn, topClonotypes)
93
95
  sampledColumnsOnlyPf := xsv.importFile(finalClonotypes, "parquet", sampledExportsParams,
94
96
  {cpu: 1, mem: "16GiB"})
95
- 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()
96
112
 
97
113
  return outputs
98
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,
@@ -147,9 +151,11 @@ wf.body(func(args) {
147
151
  datasetSpec: datasetSpec,
148
152
  topClonotypes: args.topClonotypes,
149
153
  disableClusterRanking: args.disableClusterRanking,
150
- clusterColumn: clusterColumnHeader
154
+ clusterColumn: clusterColumnHeader,
155
+ rawClusterColumn: args.clusterColumn,
156
+ filterTraceLabel: filterTraceLabel
151
157
  })
152
-
158
+
153
159
  // Get the filtered clonotypes from the template result
154
160
  outputs["sampledRows"] = filterSampleResult.output("sampledRows", 24 * 60 * 60 * 1000)
155
161
 
@@ -6,11 +6,11 @@ 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
15
  "pl7.app/isSubset": "true"
16
16
  }
@@ -1,6 +1,18 @@
1
1
  ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ json := import("json")
3
+
4
+ getColumns := func(datasetSpec, filterMap, rankingMap, disableClusterRanking, clusterColumn, topClonotypes) {
5
+
6
+ // Define domain
7
+ domain := {
8
+ "pl7.app/vdj/filter-map": string(json.encode(filterMap)),
9
+ "pl7.app/vdj/ranking-map": string(json.encode(rankingMap)),
10
+ "pl7.app/vdj/top-clonotypes": string(topClonotypes)
11
+ }
12
+ if !disableClusterRanking && clusterColumn != undefined && clusterColumn != "" {
13
+ domain["pl7.app/vdj/diversity-column"] = string(json.encode(clusterColumn))
14
+ }
2
15
 
3
- getColumns := func(datasetSpec) {
4
16
  columns := [{
5
17
  column: "top",
6
18
  id: "link",
@@ -8,7 +20,7 @@ getColumns := func(datasetSpec) {
8
20
  spec: {
9
21
  name: "pl7.app/vdj/lead-selection",
10
22
  valueType: "Int",
11
- domain: {},
23
+ domain: domain,
12
24
  annotations: {
13
25
  "pl7.app/label": "Selected Leads",
14
26
  "pl7.app/table/visibility": "hidden",
@@ -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
  }