@platforma-open/milaboratories.top-antibodies.workflow 1.8.2 → 1.10.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.
@@ -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.8.2 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
3
+ > @platforma-open/milaboratories.top-antibodies.workflow@1.10.0 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
4
4
  > rm -rf dist && pl-tengo check && pl-tengo build
5
5
 
6
6
  Processing "src/filter-and-sample.tpl.tengo"...
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @platforma-open/milaboratories.top-antibodies.workflow
2
2
 
3
+ ## 1.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - c282203: Improved block performance. Fixed increasing ranking order and cluster size ranking
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [c282203]
12
+ - @platforma-open/milaboratories.top-antibodies.sample-clonotypes@1.3.0
13
+
14
+ ## 1.9.0
15
+
16
+ ### Minor Changes
17
+
18
+ - b499ab2: Add rank column
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [b499ab2]
23
+ - @platforma-open/milaboratories.top-antibodies.sample-clonotypes@1.2.0
24
+
3
25
  ## 1.8.2
4
26
 
5
27
  ### Patch Changes
@@ -1,14 +1,7 @@
1
1
  ll := import("@platforma-sdk/workflow-tengo:ll")
2
2
 
3
- getColumns := func(datasetSpec) {
4
- return {
5
- axes: [
6
- {
7
- column: "clonotypeKey",
8
- spec: datasetSpec.axesSpec[1]
9
- }],
10
- columns: [
11
- {
3
+ getColumns := func(datasetSpec, addRanking) {
4
+ columns := [{
12
5
  column: "top",
13
6
  id: "link",
14
7
  allowNA: false,
@@ -22,8 +15,31 @@ getColumns := func(datasetSpec) {
22
15
  "pl7.app/isSubset": "true"
23
16
  }
24
17
  }
25
- }
26
- ],
18
+ }]
19
+ if addRanking {
20
+ columns = columns + [{
21
+ column: "ranked_order",
22
+ spec: {
23
+ name: "pl7.app/vdj/ranking-order",
24
+ valueType: "Int",
25
+ domain: {},
26
+ annotations: {
27
+ "pl7.app/label": "Rank",
28
+ "pl7.app/table/visibility": "optional",
29
+ "pl7.app/isSubset": "true"
30
+ }
31
+ }
32
+ }]
33
+ }
34
+
35
+
36
+ return {
37
+ axes: [
38
+ {
39
+ column: "clonotypeKey",
40
+ spec: datasetSpec.axesSpec[1]
41
+ }],
42
+ columns: columns,
27
43
  storageFormat: "Binary",
28
44
  partitionKeyLength: 0
29
45
  }
Binary file
Binary file
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@platforma-open/milaboratories.top-antibodies.workflow",
3
- "version": "1.8.2",
3
+ "version": "1.10.0",
4
4
  "type": "module",
5
5
  "description": "Block Workflow",
6
6
  "dependencies": {
7
7
  "@platforma-sdk/workflow-tengo": "^4.9.2",
8
- "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "1.1.0",
8
+ "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "1.3.0",
9
9
  "@platforma-open/milaboratories.top-antibodies.umap": "1.0.3",
10
10
  "@platforma-open/milaboratories.top-antibodies.spectratype": "1.4.0"
11
11
  },
@@ -1,4 +1,4 @@
1
- // Template for clonotype filtering
1
+ // Template for clonotype filtering and sampling
2
2
  self := import("@platforma-sdk/workflow-tengo:tpl")
3
3
  exec := import("@platforma-sdk/workflow-tengo:exec")
4
4
  assets := import("@platforma-sdk/workflow-tengo:assets")
@@ -15,6 +15,7 @@ self.body(func(inputs) {
15
15
  cloneTable := inputs.cloneTable
16
16
  datasetSpec := inputs.datasetSpec
17
17
  filterMap := inputs.filterMap
18
+ rankingMap := inputs.rankingMap
18
19
  topClonotypes := inputs.topClonotypes
19
20
 
20
21
  outputs := {}
@@ -39,7 +40,7 @@ self.body(func(inputs) {
39
40
  filteredClonotypesCsv := filterResult.getFile("filteredClonotypes.csv")
40
41
 
41
42
  // Store outputs
42
- sampledColsParams := sampledColsConv.getColumns(datasetSpec)
43
+ sampledColsParams := sampledColsConv.getColumns(datasetSpec, false) // No ranking column
43
44
  filteredClonotypesPf := xsv.importFile(filteredClonotypesCsv, "csv", sampledColsParams,
44
45
  {cpu: 1, mem: "16GiB"})
45
46
 
@@ -58,6 +59,7 @@ self.body(func(inputs) {
58
59
  addFile("filteredClonotypes.csv", filteredClonotypesCsv).
59
60
  arg("--csv").arg("filteredClonotypes.csv").
60
61
  arg("--n").arg(string(topClonotypes)).
62
+ arg("--ranking-map").arg(string(json.encode(rankingMap))).
61
63
  arg("--out").arg("sampledClonotypes_top.csv").
62
64
  saveFile("sampledClonotypes_top.csv").
63
65
  printErrStreamToStdout().
@@ -69,6 +71,7 @@ self.body(func(inputs) {
69
71
  finalClonotypesCsv = sampleClones.getFile("sampledClonotypes_top.csv")
70
72
 
71
73
  // Store outputs
74
+ sampledColsParams := sampledColsConv.getColumns(datasetSpec, true) // Add ranking column
72
75
  sampledColumnsPf := xsv.importFile(finalClonotypesCsv, "csv", sampledColsParams,
73
76
  {cpu: 1, mem: "16GiB"})
74
77
  outputs["sampledRows"] = pframes.exportFrame(sampledColumnsPf)
@@ -4,7 +4,6 @@ exec := import("@platforma-sdk/workflow-tengo:exec")
4
4
  assets:= import("@platforma-sdk/workflow-tengo:assets")
5
5
  xsv := import("@platforma-sdk/workflow-tengo:pframes.xsv")
6
6
  pframes := import("@platforma-sdk/workflow-tengo:pframes")
7
- sampledColsConv := import(":sampled-cols-conv")
8
7
  spectratypeConv := import(":pf-spectratype-conv")
9
8
  vjUsageConv := import(":pf-vj-usage-conv")
10
9
  slices := import("@platforma-sdk/workflow-tengo:slices")
@@ -50,6 +49,12 @@ wf.prepare(func(args){
50
49
  partialAxesMatch: true
51
50
  }, "linkers")
52
51
 
52
+ // Add cluster size columns from clustering blocks
53
+ bundleBuilder.addMulti({
54
+ name: "pl7.app/vdj/clustering/clusterSize",
55
+ partialAxesMatch: true
56
+ }, "clusterSizes")
57
+
53
58
  // Add CDR3 sequences
54
59
  bundleBuilder.addMulti({
55
60
  axes: [{ anchor: "main", idx: 1 }], // Clonotype axis
@@ -88,23 +93,24 @@ wf.body(func(args) {
88
93
  // Input arguments
89
94
  columns := args.columns
90
95
  datasetSpec := columns.getSpec(args.inputAnchor)
91
-
92
96
  topClonotypes := args.topClonotypes
93
97
 
94
- // Needed conditional variables
98
+ // Needed conditional variable
95
99
  isSingleCell := datasetSpec.axesSpec[1].name == "pl7.app/vdj/scClonotypeKey"
96
100
 
97
- // output containers
101
+ // Output container
98
102
  outputs := {}
99
103
 
100
- // Get filtered clonotypes from prerun
101
- // Build clonotype table
104
+ // Build clonotype table csv for filtering script
102
105
  cloneTable := pframes.csvFileBuilder()
103
106
  cloneTable.setAxisHeader(datasetSpec.axesSpec[1].name, "clonotypeKey")
104
107
 
105
- // Add Filters to table
106
- addedAxes := []
107
- filterMap := {}
108
+ // Add filter columns to table
109
+ // Filter structure {id: UI id, value: AnchoredColumnId, filter: Filter criteria, isExpanded: boolean (UI state)}
110
+ // AnchoredColumnId {anchorRef: PlRef, anchorName: string, column: SUniversalPColumnId (unique column id)}
111
+ addedAxes := [] // Keep track of axes that are added to the table to prevent duplicates
112
+ filterMap := {} // Map column headers to filter criteria
113
+ rankingMap := {} // Map column headers to ranking order (increasing/decreasing)
108
114
  if len(args.filters) > 0 {
109
115
  for i, filter in args.filters {
110
116
  if filter.value != undefined {
@@ -114,9 +120,9 @@ wf.body(func(args) {
114
120
  // Store reference value and filter type associated to this column
115
121
  filterMap["Filter_" + string(i)] = filter.filter
116
122
 
117
- // If column does not have main anchor axis we have to include theirs
123
+ // If column does not have main anchor axis we have to include theirs (columns coming from clustering blocks for example)
118
124
  colsSpec := columns.getSpec(filter.value.column)
119
- axesNames := slices.map(colsSpec.axesSpec, func (a) { return a.name})
125
+ axesNames := slices.map(colsSpec.axesSpec, func (a) {return a.name})
120
126
  if !slices.hasElement(axesNames, datasetSpec.axesSpec[1].name) {
121
127
  for na, ax in colsSpec.axesSpec {
122
128
  if ax.name != datasetSpec.axesSpec[1].name {
@@ -133,13 +139,15 @@ wf.body(func(args) {
133
139
  if len(args.rankingOrder) > 0 {
134
140
  for i, col in args.rankingOrder {
135
141
  cloneTable.add(columns.getColumn(col.value.column), {header: "Col" + string(i)})
142
+ // Store ranking order for this column
143
+ rankingMap["Col" + string(i)] = col.rankingOrder
136
144
 
137
- // If column does not have main anchor axis we have to include theirs
145
+ // If column does not have main anchor axis we have to include theirs (columns coming from clustering blocks for example)
138
146
  colsSpec := columns.getSpec(col.value.column)
139
- axesNames := slices.map(colsSpec.axesSpec, func (a) { return a.name})
147
+ axesNames := slices.map(colsSpec.axesSpec, func (a) {return a.name})
140
148
  if !slices.hasElement(axesNames, datasetSpec.axesSpec[1].name) {
141
149
  for na, ax in colsSpec.axesSpec {
142
- if ax.name != datasetSpec.axesSpec[1].name && !slices.hasElement(addedAxes, ax.name) {
150
+ if ax.name != datasetSpec.axesSpec[1].name && !slices.hasElement(addedAxes, ax.name) { // Prevent duplicates
143
151
  cloneTable.setAxisHeader(ax.name, "cluster_" + string(i) + string(na))
144
152
  }
145
153
  }
@@ -148,6 +156,8 @@ wf.body(func(args) {
148
156
  } else {
149
157
  i := 0
150
158
  cloneTable.add(columns.getColumn(args.rankingOrderDefault.value.column), {header: "Col" + string(i)})
159
+ // Store default ranking order
160
+ rankingMap["Col" + string(i)] = args.rankingOrderDefault.rankingOrder
151
161
 
152
162
  // If column does not have main anchor axis we have to include theirs
153
163
  colsSpec := columns.getSpec(args.rankingOrderDefault.value.column)
@@ -161,8 +171,8 @@ wf.body(func(args) {
161
171
  }
162
172
  }
163
173
 
164
- // Get linker columns if needed
165
- linkerAxisSpec := {}
174
+ // Add linker columns when needed
175
+ linkerAxisSpec := {} // Map cluster axis names to specs. Is it needed????
166
176
  if len(columns.getColumns("linkers")) > 0 {
167
177
  for i, col in columns.getColumns("linkers") {
168
178
  if datasetSpec.axesSpec[1].name == col.spec.axesSpec[1].name {
@@ -176,25 +186,40 @@ wf.body(func(args) {
176
186
  }
177
187
  }
178
188
  }
189
+
190
+ // Add cluster size columns if available
191
+ if len(columns.getColumns("clusterSizes")) > 0 {
192
+ for i, col in columns.getColumns("clusterSizes") {
193
+ cloneTable.add(col, {header: "clusterSize." + string(i)})
194
+ // Add the cluster axis header
195
+ for axisIdx, axis in col.spec.axesSpec {
196
+ if axis.name != datasetSpec.axesSpec[1].name {
197
+ cloneTable.setAxisHeader(axis.name, "clusterAxis_" + string(i) + "_" + string(axisIdx))
198
+ }
199
+ }
200
+ }
201
+ }
202
+
179
203
  cloneTable.mem("16GiB")
180
204
  cloneTable.cpu(1)
181
205
  cloneTable = cloneTable.build()
182
206
 
183
- // Use ender.create to call the filter-clonotypes template
184
- filterResult := render.create(filterAndSampleTpl, {
207
+ // Use render.create to call the filtering and sampling clonotypes template
208
+ filterSampleResult := render.create(filterAndSampleTpl, {
185
209
  inputAnchor: args.inputAnchor,
186
210
  cloneTable: cloneTable,
187
211
  rankingOrder: args.rankingOrder,
188
212
  rankingOrderDefault: args.rankingOrderDefault,
189
213
  filters: args.filters,
190
214
  filterMap: filterMap,
215
+ rankingMap: rankingMap,
191
216
  datasetSpec: datasetSpec,
192
217
  topClonotypes: args.topClonotypes
193
218
  })
194
219
 
195
- // Get the filtered clonotypes P-frame and CSV from the template result
196
- finalClonotypesCsv := filterResult.output("finalClonotypesCsv", 24 * 60 * 60 * 1000)
197
- // outputs["sampledRows"] = filterResult.output("sampledRows", 24 * 60 * 60 * 1000)
220
+ // Get the filtered and sampled clonotypes P-frame and CSV from the template result
221
+ finalClonotypesCsv := filterSampleResult.output("finalClonotypesCsv", 24 * 60 * 60 * 1000)
222
+ // outputs["sampledRows"] = filterSampleResult.output("sampledRows", 24 * 60 * 60 * 1000)
198
223
 
199
224
  ////////// CDR3 Length Calculation //////////
200
225
 
@@ -217,15 +242,15 @@ wf.body(func(args) {
217
242
  chain := col.spec.domain["pl7.app/vdj/scClonotypeChain"] // e.g., "A", "B"
218
243
  receptor := col.spec.axesSpec[0].domain["pl7.app/vdj/receptor"] // e.g., "IG", "TCRAB", "TCRGD"
219
244
  chainLabel := chainMapping[receptor][chain]
220
- return baseHeaderName + "." + chainLabel
245
+ return baseHeaderName + "." + chainLabel // e.g., "cdr3Sequence.Heavy"
221
246
  } else {
222
247
  // For bulk, if chain info is available (e.g. IGH, IGK, IGL)
223
248
  chainFromDomain := col.spec.axesSpec[0].domain["pl7.app/vdj/chain"] // e.g. "IGH", "IGK"
224
249
  if chainFromDomain != undefined {
225
- return baseHeaderName + "." + chainFromDomain
250
+ return baseHeaderName + "." + chainFromDomain // e.g., "cdr3Sequence.IGH"
226
251
  }
227
252
  }
228
- return baseHeaderName // Default header for bulk
253
+ return baseHeaderName
229
254
  };
230
255
 
231
256
  // Process CDR3 sequences
@@ -251,6 +276,7 @@ wf.body(func(args) {
251
276
  headerName := makeHeaderName(col, "jGene", isSingleCell)
252
277
  cdr3SeqTable.add(col, {header: headerName})
253
278
  }
279
+
254
280
  cdr3SeqTable.mem("16GiB")
255
281
  cdr3SeqTable.cpu(1)
256
282
  cdr3SeqTableBuilt := cdr3SeqTable.build()
@@ -280,7 +306,7 @@ wf.body(func(args) {
280
306
  run()
281
307
 
282
308
 
283
- // For spectratype structure is [chain][cdr3Length][vGene] -> count
309
+ // Spectratype PFrame structure is [chain][cdr3Length][vGene] -> count
284
310
 
285
311
  cdr3VspectratypePf := xsv.importFile(cdr3VspectratypeCmd.getFile("spectratype.tsv"),
286
312
  "tsv", spectratypeConv.getColumns(),
@@ -51,6 +51,12 @@ wf.prepare(func(args){
51
51
  partialAxesMatch: true
52
52
  }, "linkers")
53
53
 
54
+ // Add cluster size columns from clustering blocks
55
+ bundleBuilder.addMulti({
56
+ name: "pl7.app/vdj/clustering/clusterSize",
57
+ partialAxesMatch: true
58
+ }, "clusterSizes")
59
+
54
60
  // Add CDR3 sequences
55
61
  bundleBuilder.addMulti({
56
62
  axes: [{ anchor: "main", idx: 1 }], // Clonotype axis
@@ -100,6 +106,7 @@ wf.body(func(args) {
100
106
  // Add Filters to table
101
107
  addedAxes := []
102
108
  filterMap := {}
109
+ rankingMap := {}
103
110
  if len(args.filters) > 0 {
104
111
  for i, filter in args.filters {
105
112
  if filter.value != undefined {
@@ -129,6 +136,8 @@ wf.body(func(args) {
129
136
  for i, col in args.rankingOrder {
130
137
  if col.value != undefined {
131
138
  cloneTable.add(columns.getColumn(col.value.column), {header: "Col" + string(i)})
139
+ // Store ranking order for this column
140
+ rankingMap["Col" + string(i)] = col.rankingOrder
132
141
 
133
142
  // If column does not have main anchor axis we have to include theirs
134
143
  colsSpec := columns.getSpec(col.value.column)
@@ -148,6 +157,8 @@ wf.body(func(args) {
148
157
  if args.rankingOrderDefault.value != undefined {
149
158
  i := 0
150
159
  cloneTable.add(columns.getColumn(args.rankingOrderDefault.value.column), {header: "Col" + string(i)})
160
+ // Store default ranking order
161
+ rankingMap["Col" + string(i)] = args.rankingOrderDefault.rankingOrder
151
162
 
152
163
  // If column does not have main anchor axis we have to include theirs
153
164
  colsSpec := columns.getSpec(args.rankingOrderDefault.value.column)
@@ -177,12 +188,26 @@ wf.body(func(args) {
177
188
  }
178
189
  }
179
190
  }
191
+
192
+ // Add cluster size columns if available
193
+ if len(columns.getColumns("clusterSizes")) > 0 {
194
+ for i, col in columns.getColumns("clusterSizes") {
195
+ cloneTable.add(col, {header: "clusterSize." + string(i)})
196
+ // Add the cluster axis header
197
+ for axisIdx, axis in col.spec.axesSpec {
198
+ if axis.name != datasetSpec.axesSpec[1].name {
199
+ cloneTable.setAxisHeader(axis.name, "clusterAxis_" + string(i) + "_" + string(axisIdx))
200
+ }
201
+ }
202
+ }
203
+ }
204
+
180
205
  cloneTable.mem("16GiB")
181
206
  cloneTable.cpu(1)
182
207
  cloneTable = cloneTable.build()
183
208
 
184
209
  // Use ender.create to call the filter-clonotypes template
185
- filterResult := render.create(filterAndSampleTpl, {
210
+ filterSampleResult := render.create(filterAndSampleTpl, {
186
211
  inputAnchor: args.inputAnchor,
187
212
  cloneTable: cloneTable,
188
213
  topClonotypes: args.topClonotypes,
@@ -190,11 +215,12 @@ wf.body(func(args) {
190
215
  rankingOrderDefault: args.rankingOrderDefault,
191
216
  filters: args.filters,
192
217
  filterMap: filterMap,
218
+ rankingMap: rankingMap,
193
219
  datasetSpec: datasetSpec
194
220
  })
195
221
 
196
222
  // Get the filtered clonotypes from the template result
197
- outputs["sampledRows"] = filterResult.output("sampledRows", 24 * 60 * 60 * 1000)
223
+ outputs["sampledRows"] = filterSampleResult.output("sampledRows", 24 * 60 * 60 * 1000)
198
224
  }
199
225
 
200
226
  return {
@@ -1,14 +1,7 @@
1
1
  ll := import("@platforma-sdk/workflow-tengo:ll")
2
2
 
3
- getColumns := func(datasetSpec) {
4
- return {
5
- axes: [
6
- {
7
- column: "clonotypeKey",
8
- spec: datasetSpec.axesSpec[1]
9
- }],
10
- columns: [
11
- {
3
+ getColumns := func(datasetSpec, addRanking) {
4
+ columns := [{
12
5
  column: "top",
13
6
  id: "link",
14
7
  allowNA: false,
@@ -22,8 +15,31 @@ getColumns := func(datasetSpec) {
22
15
  "pl7.app/isSubset": "true"
23
16
  }
24
17
  }
25
- }
26
- ],
18
+ }]
19
+ if addRanking {
20
+ columns = columns + [{
21
+ column: "ranked_order",
22
+ spec: {
23
+ name: "pl7.app/vdj/ranking-order",
24
+ valueType: "Int",
25
+ domain: {},
26
+ annotations: {
27
+ "pl7.app/label": "Rank",
28
+ "pl7.app/table/visibility": "optional",
29
+ "pl7.app/isSubset": "true"
30
+ }
31
+ }
32
+ }]
33
+ }
34
+
35
+
36
+ return {
37
+ axes: [
38
+ {
39
+ column: "clonotypeKey",
40
+ spec: datasetSpec.axesSpec[1]
41
+ }],
42
+ columns: columns,
27
43
  storageFormat: "Binary",
28
44
  partitionKeyLength: 0
29
45
  }