@platforma-open/milaboratories.top-antibodies.workflow 4.0.2 → 4.1.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@4.0.2 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
3
+ > @platforma-open/milaboratories.top-antibodies.workflow@4.1.0 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"...
@@ -11,6 +11,7 @@ Processing "src/pf-spectratype-conv.lib.tengo"...
11
11
  Processing "src/pf-vj-usage-conv.lib.tengo"...
12
12
  Processing "src/sampled-cols-conv.lib.tengo"...
13
13
  Processing "src/sampled-export-conv.lib.tengo"...
14
+ Processing "src/selection-stage-conv.lib.tengo"...
14
15
  Processing "src/utils.lib.tengo"...
15
16
  No syntax errors found.
16
17
  info: Compiling 'dist'...
@@ -19,6 +20,7 @@ No syntax errors found.
19
20
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/lib/pf-vj-usage-conv.lib.tengo
20
21
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/lib/sampled-cols-conv.lib.tengo
21
22
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/lib/sampled-export-conv.lib.tengo
23
+ info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/lib/selection-stage-conv.lib.tengo
22
24
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/lib/utils.lib.tengo
23
25
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/tpl/assembling-fasta.plj.gz
24
26
  info: - writing /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow/dist/tengo/tpl/filter-and-sample.plj.gz
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @platforma-open/milaboratories.top-antibodies.workflow
2
2
 
3
+ ## 4.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b812c7d: Track which filter step eliminated each clonotype (or marks it as a
8
+ survivor) and visualize the attrition in a new Selection page. The
9
+ sample-clonotypes script emits a selectionStage column per clone; the
10
+ workflow exposes it as selectionStagePf, and the block UI renders it
11
+ via GraphMaker's selection chart type.
12
+
13
+ ## 4.0.3
14
+
15
+ ### Patch Changes
16
+
17
+ - 4855fff: dont show column header linker postfix and update sdk
18
+ - Updated dependencies [4855fff]
19
+ - @platforma-open/milaboratories.top-antibodies.anarci-kabat@1.4.4
20
+ - @platforma-open/milaboratories.top-antibodies.assembling-fasta@1.3.3
21
+ - @platforma-open/milaboratories.top-antibodies.sample-clonotypes@2.1.3
22
+ - @platforma-open/milaboratories.top-antibodies.spectratype@1.8.4
23
+ - @platforma-open/milaboratories.top-antibodies.umap@1.2.4
24
+
3
25
  ## 4.0.2
4
26
 
5
27
  ### Patch Changes
@@ -0,0 +1,33 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ json := import("json")
3
+
4
+ getColumns := func(datasetSpec, valueLabels) {
5
+ return {
6
+ axes: [
7
+ {
8
+ column: "clonotypeKey",
9
+ spec: datasetSpec.axesSpec[1]
10
+ }],
11
+ columns: [{
12
+ column: "selectionStage",
13
+ id: "selectionStage",
14
+ allowNA: false,
15
+ spec: {
16
+ name: "pl7.app/selectionStage",
17
+ valueType: "Int",
18
+ domain: {},
19
+ annotations: {
20
+ "pl7.app/label": "Selection Stage",
21
+ "pl7.app/valueLabels": string(json.encode(valueLabels)),
22
+ "pl7.app/isDiscreteFilter": "true"
23
+ }
24
+ }
25
+ }],
26
+ storageFormat: "Parquet",
27
+ partitionKeyLength: 0
28
+ }
29
+ }
30
+
31
+ export ll.toStrict({
32
+ getColumns: getColumns
33
+ })
@@ -742,6 +742,31 @@ buildFilterTraceLabel := func(filters, columns) {
742
742
  return result
743
743
  }
744
744
 
745
+
746
+
747
+
748
+
749
+
750
+
751
+
752
+
753
+ buildFilterStageNames := func(filters, columns) {
754
+ names := []
755
+ for filter in filters {
756
+
757
+ if !is_undefined(filter.value) && !is_undefined(columns.getColumn(filter.value.column).spec) {
758
+ colSpec := columns.getColumn(filter.value.column).spec
759
+ label := colSpec.name
760
+ colLabel := colSpec.annotations["pl7.app/label"]
761
+ if !is_undefined(colLabel) && colLabel != "" {
762
+ label = colLabel
763
+ }
764
+ names = names + [label]
765
+ }
766
+ }
767
+ return names
768
+ }
769
+
745
770
  export {
746
771
  clusterAxisDomainsMatch: clusterAxisDomainsMatch,
747
772
  findMatchingLinkerIndex: findMatchingLinkerIndex,
@@ -755,5 +780,6 @@ export {
755
780
  initializeAssemSeqTable: initializeAssemSeqTable,
756
781
  formatFilterDescription: formatFilterDescription,
757
782
  buildFilterTraceLabel: buildFilterTraceLabel,
783
+ buildFilterStageNames: buildFilterStageNames,
758
784
  inVivoScoreSourceColumns: inVivoScoreSourceColumns
759
785
  }
Binary file
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "@platforma-open/milaboratories.top-antibodies.workflow",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "type": "module",
5
5
  "description": "Block Workflow",
6
6
  "dependencies": {
7
- "@platforma-sdk/workflow-tengo": "5.20.0",
7
+ "@platforma-sdk/workflow-tengo": "5.21.0",
8
8
  "@platforma-open/milaboratories.software-anarci": "^0.0.3",
9
- "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "2.1.2",
10
- "@platforma-open/milaboratories.top-antibodies.spectratype": "1.8.3",
11
- "@platforma-open/milaboratories.top-antibodies.umap": "1.2.3",
12
- "@platforma-open/milaboratories.top-antibodies.anarci-kabat": "1.4.3",
13
- "@platforma-open/milaboratories.top-antibodies.assembling-fasta": "1.3.2"
9
+ "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "2.1.3",
10
+ "@platforma-open/milaboratories.top-antibodies.assembling-fasta": "1.3.3",
11
+ "@platforma-open/milaboratories.top-antibodies.spectratype": "1.8.4",
12
+ "@platforma-open/milaboratories.top-antibodies.umap": "1.2.4",
13
+ "@platforma-open/milaboratories.top-antibodies.anarci-kabat": "1.4.4"
14
14
  },
15
15
  "devDependencies": {
16
- "@platforma-sdk/tengo-builder": "2.5.20"
16
+ "@platforma-sdk/tengo-builder": "2.5.26"
17
17
  },
18
18
  "scripts": {
19
19
  "build": "shx rm -rf dist && pl-tengo check && pl-tengo build",
@@ -8,9 +8,10 @@ pSpec := import("@platforma-sdk/workflow-tengo:pframes.spec")
8
8
  render := import("@platforma-sdk/workflow-tengo:render")
9
9
  sampledColsConv := import(":sampled-cols-conv")
10
10
  sampledExportConv := import(":sampled-export-conv")
11
+ selectionStageConv := import(":selection-stage-conv")
11
12
  json := import("json")
12
13
 
13
- self.defineOutputs("sampledRows", "finalClonotypes", "sampledColumnsExport")
14
+ self.defineOutputs("sampledRows", "finalClonotypes", "sampledColumnsExport", "selectionStagePf")
14
15
 
15
16
  self.body(func(inputs) {
16
17
 
@@ -22,8 +23,8 @@ self.body(func(inputs) {
22
23
 
23
24
  outputs := {}
24
25
  finalClonotypes := undefined
25
-
26
- // Run filtering script
26
+
27
+ // Run filtering script with selection stage tracking
27
28
  filterResult := exec.builder().
28
29
  software(assets.importSoftware("@platforma-open/milaboratories.top-antibodies.sample-clonotypes:filter")).
29
30
  mem("16GiB").
@@ -32,13 +33,16 @@ self.body(func(inputs) {
32
33
  arg("--parquet").arg("clonotypes.parquet").
33
34
  arg("--out").arg("filteredClonotypes.parquet").
34
35
  arg("--filter-map").arg(string(json.encode(filterMap))).
36
+ arg("--emit-selection").arg("selection.parquet").
35
37
  saveFile("filteredClonotypes.parquet").
38
+ saveFile("selection.parquet").
36
39
  printErrStreamToStdout().
37
40
  cache(24 * 60 * 60 * 1000).
38
41
  run()
39
42
 
40
- // Save filtered parquet file
43
+ // Save filtered parquet file and selection stage data
41
44
  filteredClonotypes := filterResult.getFile("filteredClonotypes.parquet")
45
+ selectionParquet := filterResult.getFile("selection.parquet")
42
46
 
43
47
  // Check if In Vivo Score is in the ranking map
44
48
  hasInVivoScore := false
@@ -60,15 +64,18 @@ self.body(func(inputs) {
60
64
  if topClonotypes != undefined {
61
65
 
62
66
  ////////// Top Clonotypes Sampling //////////
63
- // Run sampling script on filtered data
67
+ // Run sampling script on filtered data, with selection stage merging
64
68
  builder := exec.builder().
65
69
  software(assets.importSoftware("@platforma-open/milaboratories.top-antibodies.sample-clonotypes:main")).
66
70
  mem("16GiB").
67
71
  cpu(1).
68
72
  addFile("filteredClonotypes.parquet", filteredClonotypes).
73
+ addFile("selection_in.parquet", selectionParquet).
69
74
  arg("--parquet").arg("filteredClonotypes.parquet").
70
75
  arg("--n").arg(string(topClonotypes)).
71
- arg("--ranking-map").arg(string(json.encode(rankingMap)))
76
+ arg("--ranking-map").arg(string(json.encode(rankingMap))).
77
+ arg("--selection-in").arg("selection_in.parquet").
78
+ arg("--selection-out").arg("selection_out.parquet")
72
79
 
73
80
  // Add diversification column if provided
74
81
  if inputs.diversificationColumn != undefined && inputs.diversificationColumn != "" {
@@ -78,29 +85,57 @@ self.body(func(inputs) {
78
85
  sampleClones := builder.
79
86
  arg("--out").arg("sampledClonotypes_top.parquet").
80
87
  saveFile("sampledClonotypes_top.parquet").
88
+ saveFile("selection_out.parquet").
81
89
  printErrStreamToStdout().
82
90
  cache(24 * 60 * 60 * 1000).
83
91
  run()
84
92
 
85
93
  // Save top clonotypes parquet file
86
94
  finalClonotypes = sampleClones.getFile("sampledClonotypes_top.parquet")
87
-
95
+ // Updated selection stages with "Selected" stage
96
+ selectionParquet = sampleClones.getFile("selection_out.parquet")
97
+
88
98
  // Store outputs
89
99
  sampledColsParams := sampledColsConv.getColumns(datasetSpec, true, hasInVivoScore) // Add ranking column
90
100
  sampledColumnsPf := xsv.importFile(finalClonotypes, "parquet", sampledColsParams,
91
101
  {cpu: 1, mem: "16GiB"})
92
102
  outputs["sampledRows"] = pframes.exportFrame(sampledColumnsPf)
93
- }
103
+ }
94
104
 
95
105
  outputs["finalClonotypes"] = finalClonotypes
96
106
 
107
+ // Build valueLabels for selectionStage from filter stage names
108
+ filterStageNames := inputs.filterStageNames
109
+ valueLabels := {}
110
+ stageIdx := 1
111
+ if !is_undefined(filterStageNames) {
112
+ for name in filterStageNames {
113
+ valueLabels[string(stageIdx)] = name
114
+ stageIdx = stageIdx + 1
115
+ }
116
+ }
117
+
118
+ if topClonotypes != undefined {
119
+ valueLabels[string(stageIdx)] = "Filtered"
120
+ valueLabels[string(stageIdx + 1)] = "Selected"
121
+ } else {
122
+ valueLabels[string(stageIdx)] = "Passed Filters"
123
+ }
124
+
125
+ // Import selection stage parquet as selectionStage PColumn
126
+ selectionStageParams := selectionStageConv.getColumns(datasetSpec, valueLabels)
127
+ selectionStagePf := xsv.importFile(selectionParquet, "parquet", selectionStageParams,
128
+ {cpu: 1, mem: "16GiB"})
129
+ // Expose as a dedicated output for the Selection Plot page's pre-bound pframe.
130
+ outputs["selectionStagePf"] = pframes.exportFrame(selectionStagePf)
131
+
97
132
  // Export only the sampling column to be used as a filter downstream
98
133
  sampledExportsParams := sampledExportConv.getColumns(datasetSpec, filterMap,
99
134
  rankingMap, inputs.diversificationColumn, topClonotypes)
100
135
  sampledColumnsOnlyPf := xsv.importFile(finalClonotypes, "parquet", sampledExportsParams,
101
136
  {cpu: 1, mem: "16GiB"})
102
137
 
103
- // Create trace with filter descriptions and inject into exported column
138
+ // Create trace with filter descriptions and inject into exported columns
104
139
  traceLabel := inputs.filterTraceLabel
105
140
  if is_undefined(traceLabel) || traceLabel == "" {
106
141
  traceLabel = "Selected Leads"
@@ -117,6 +152,7 @@ self.body(func(inputs) {
117
152
  })
118
153
  exportPf := pframes.pFrameBuilder()
119
154
  exportPf.add("link", trace.inject(sampledColumnsOnlyPf["link.spec"]), sampledColumnsOnlyPf["link.data"])
155
+ exportPf.add("selectionStage", trace.inject(selectionStagePf["selectionStage.spec"]), selectionStagePf["selectionStage.data"])
120
156
  outputs["sampledColumnsExport"] = exportPf.build()
121
157
 
122
158
  return outputs
@@ -180,6 +180,8 @@ wf.body(func(args) {
180
180
 
181
181
  // Build trace label from filter descriptions
182
182
  filterTraceLabel := utils.buildFilterTraceLabel(args.filters, columns)
183
+ // Build per-filter stage names for Selection Plot
184
+ filterStageNames := utils.buildFilterStageNames(args.filters, columns)
183
185
 
184
186
  // Use ender.create to call the filter-clonotypes template
185
187
  filterSampleResult := render.create(filterAndSampleTpl, {
@@ -193,6 +195,7 @@ wf.body(func(args) {
193
195
  topClonotypes: args.topClonotypes,
194
196
  diversificationColumn: clusterColumnHeader,
195
197
  filterTraceLabel: filterTraceLabel,
198
+ filterStageNames: filterStageNames,
196
199
  customBlockLabel: args.customBlockLabel
197
200
  })
198
201
 
@@ -205,6 +208,9 @@ wf.body(func(args) {
205
208
  // Export only the sampling column for downstream (built inside sub-template to avoid hang)
206
209
  exports["sampledColumnsPf"] = filterSampleResult.output("sampledColumnsExport", 24 * 60 * 60 * 1000)
207
210
 
211
+ // Selection stage pframe for the Selection Plot page's pre-bound input
212
+ outputs["selectionStagePf"] = filterSampleResult.output("selectionStagePf", 24 * 60 * 60 * 1000)
213
+
208
214
  // CDR3 spectratype, V/J gene usage, and Kabat numbering are VDJ-only — skip for peptide inputs.
209
215
  if !isPeptide {
210
216
  ////////// CDR3 Length Calculation //////////
@@ -0,0 +1,33 @@
1
+ ll := import("@platforma-sdk/workflow-tengo:ll")
2
+ json := import("json")
3
+
4
+ getColumns := func(datasetSpec, valueLabels) {
5
+ return {
6
+ axes: [
7
+ {
8
+ column: "clonotypeKey",
9
+ spec: datasetSpec.axesSpec[1]
10
+ }],
11
+ columns: [{
12
+ column: "selectionStage",
13
+ id: "selectionStage",
14
+ allowNA: false,
15
+ spec: {
16
+ name: "pl7.app/selectionStage",
17
+ valueType: "Int",
18
+ domain: {},
19
+ annotations: {
20
+ "pl7.app/label": "Selection Stage",
21
+ "pl7.app/valueLabels": string(json.encode(valueLabels)),
22
+ "pl7.app/isDiscreteFilter": "true"
23
+ }
24
+ }
25
+ }],
26
+ storageFormat: "Parquet",
27
+ partitionKeyLength: 0
28
+ }
29
+ }
30
+
31
+ export ll.toStrict({
32
+ getColumns: getColumns
33
+ })
@@ -742,6 +742,31 @@ buildFilterTraceLabel := func(filters, columns) {
742
742
  return result
743
743
  }
744
744
 
745
+ /**
746
+ * Builds an ordered list of filter stage names for the Selection Plot valueLabels.
747
+ * Returns one label per filter, in the same order filters are applied.
748
+ *
749
+ * @param filters - Array of filter objects from block args
750
+ * @param columns - PBundle to look up column specs for labels
751
+ * @return Array of strings, one per filter stage
752
+ */
753
+ buildFilterStageNames := func(filters, columns) {
754
+ names := []
755
+ for filter in filters {
756
+ // Same guard as initializeCloneTable: skip filters with missing upstream columns
757
+ if !is_undefined(filter.value) && !is_undefined(columns.getColumn(filter.value.column).spec) {
758
+ colSpec := columns.getColumn(filter.value.column).spec
759
+ label := colSpec.name
760
+ colLabel := colSpec.annotations["pl7.app/label"]
761
+ if !is_undefined(colLabel) && colLabel != "" {
762
+ label = colLabel
763
+ }
764
+ names = names + [label]
765
+ }
766
+ }
767
+ return names
768
+ }
769
+
745
770
  export {
746
771
  clusterAxisDomainsMatch: clusterAxisDomainsMatch,
747
772
  findMatchingLinkerIndex: findMatchingLinkerIndex,
@@ -755,5 +780,6 @@ export {
755
780
  initializeAssemSeqTable: initializeAssemSeqTable,
756
781
  formatFilterDescription: formatFilterDescription,
757
782
  buildFilterTraceLabel: buildFilterTraceLabel,
783
+ buildFilterStageNames: buildFilterStageNames,
758
784
  inVivoScoreSourceColumns: inVivoScoreSourceColumns
759
785
  }