@platforma-open/milaboratories.top-antibodies.workflow 4.0.0 → 4.0.2

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.0 build /home/runner/work/antibody-tcr-lead-selection/antibody-tcr-lead-selection/workflow
3
+ > @platforma-open/milaboratories.top-antibodies.workflow@4.0.2 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,23 @@
1
1
  # @platforma-open/milaboratories.top-antibodies.workflow
2
2
 
3
+ ## 4.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 2a2533d: Fix minor issues
8
+ - 6042e4a: Minor fix
9
+ - 461999c: Fix minor issues
10
+
11
+ ## 4.0.1
12
+
13
+ ### Patch Changes
14
+
15
+ - dd754ae: Accept both pre- and post-peptide-adaptation spec names from upstream blocks so projects using either version remain functional:
16
+
17
+ - Preset filter/ranking allowlists now include `pl7.app/enrichment*` (clonotype-enrichment) and `pl7.app/developability*` (antibody-sequence-liabilities) alongside the legacy `pl7.app/vdj/`-prefixed names.
18
+ - Diversification dropdown, cluster-axis matching, hidden cluster-mapping column, and workflow-side linker matching now recognize both `pl7.app/clusterId` and `pl7.app/vdj/clusterId` axis names (clonotype-clustering rename).
19
+ - Cluster-size query uses a namePattern matching both `pl7.app/clustering/clusterSize` and `pl7.app/vdj/clustering/clusterSize`.
20
+
3
21
  ## 4.0.0
4
22
 
5
23
  ### Major Changes
@@ -1,5 +1,6 @@
1
1
 
2
2
 
3
+ ll := import("@platforma-sdk/workflow-tengo:ll")
3
4
  slices := import("@platforma-sdk/workflow-tengo:slices")
4
5
  json := import("json")
5
6
 
@@ -13,6 +14,13 @@ inVivoScoreSourceColumns := {
13
14
 
14
15
 
15
16
 
17
+ isClusterIdAxis := func(name) {
18
+ return name == "pl7.app/clusterId" || name == "pl7.app/vdj/clusterId"
19
+ }
20
+
21
+
22
+
23
+
16
24
 
17
25
 
18
26
 
@@ -61,7 +69,7 @@ findMatchingLinkerIndex := func(colsSpec, linkerColumns) {
61
69
 
62
70
  rankingClusterIdAxis := undefined
63
71
  for axis in colsSpec.axesSpec {
64
- if axis.name == "pl7.app/clusterId" {
72
+ if isClusterIdAxis(axis.name) {
65
73
  rankingClusterIdAxis = axis
66
74
  break
67
75
  }
@@ -76,7 +84,7 @@ findMatchingLinkerIndex := func(colsSpec, linkerColumns) {
76
84
 
77
85
  linkerClusterIdAxis := undefined
78
86
  for axis in linkerCol.spec.axesSpec {
79
- if axis.name == "pl7.app/clusterId" {
87
+ if isClusterIdAxis(axis.name) {
80
88
  linkerClusterIdAxis = axis
81
89
  break
82
90
  }
@@ -195,7 +203,7 @@ resolveClusterColumnHeader := func(args, columns, sortedLinkers) {
195
203
 
196
204
  selectedClusterIdAxis := undefined
197
205
  for axis in selectedLinkerSpec.axesSpec {
198
- if axis.name == "pl7.app/clusterId" {
206
+ if isClusterIdAxis(axis.name) {
199
207
  selectedClusterIdAxis = axis
200
208
  break
201
209
  }
@@ -209,7 +217,7 @@ resolveClusterColumnHeader := func(args, columns, sortedLinkers) {
209
217
  for linkerIdx, col in sortedLinkers {
210
218
 
211
219
  for axis in col.spec.axesSpec {
212
- if axis.name == "pl7.app/clusterId" {
220
+ if isClusterIdAxis(axis.name) {
213
221
 
214
222
  if clusterAxisDomainsMatch(selectedClusterIdAxis, axis) {
215
223
  return "clusterAxis_" + string(linkerIdx) + "_0"
@@ -409,7 +417,7 @@ initializeCloneTable := func(pframes, columns, args, datasetSpec) {
409
417
  addedCols = true
410
418
  }
411
419
 
412
- if !is_undefined(clusterIdAxis) && clusterIdAxis.name == "pl7.app/clusterId" {
420
+ if !is_undefined(clusterIdAxis) && isClusterIdAxis(clusterIdAxis.name) {
413
421
  linkerClusterIdAxesWithIdx = append(linkerClusterIdAxesWithIdx, {
414
422
  axis: clusterIdAxis,
415
423
  linkerIdx: linkerIdx
@@ -423,7 +431,7 @@ initializeCloneTable := func(pframes, columns, args, datasetSpec) {
423
431
 
424
432
  clusterSizeClusterIdAxis := undefined
425
433
  for axis in col.spec.axesSpec {
426
- if axis.name == "pl7.app/clusterId" {
434
+ if isClusterIdAxis(axis.name) {
427
435
  clusterSizeClusterIdAxis = axis
428
436
  break
429
437
  }
@@ -459,11 +467,18 @@ initializeCloneTable := func(pframes, columns, args, datasetSpec) {
459
467
  }
460
468
 
461
469
 
470
+
462
471
  if !addedCols {
463
472
  cdr3Sequences := columns.getColumns("cdr3Sequences")
464
473
  if len(cdr3Sequences) > 0 {
465
- cloneTable.add(cdr3Sequences[0], {header: "cdr3_fallback"})
474
+ cloneTable.add(cdr3Sequences[0], {header: "sequence_fallback"})
466
475
  addedCols = true
476
+ } else {
477
+ peptideMainSeqs := columns.getColumns("peptideMainSeqs")
478
+ if len(peptideMainSeqs) > 0 {
479
+ cloneTable.add(peptideMainSeqs[0], {header: "sequence_fallback"})
480
+ addedCols = true
481
+ }
467
482
  }
468
483
  }
469
484
 
@@ -594,11 +609,28 @@ detectBulkChain := func(seqCols) {
594
609
 
595
610
 
596
611
 
597
- initializeAssemSeqTable := func(pframes, columns, datasetSpec, isSingleCell) {
612
+
613
+ initializeAssemSeqTable := func(pframes, columns, datasetSpec, isSingleCell, isScFv) {
598
614
  assemSeqTable := pframes.parquetFileBuilder()
599
615
  assemSeqTable.setAxisHeader(datasetSpec.axesSpec[1].name, "clonotypeKey")
600
616
 
601
- seqCols := columns.getColumns("assemblingAaSeqs")
617
+ seqCols := undefined
618
+ if isScFv {
619
+ allowedFeatures := { "VDJRegion": true, "VDJRegionInFrame": true }
620
+ byChain := {}
621
+ for col in columns.getColumns("scFvPerChainSeqs") {
622
+ if is_undefined(allowedFeatures[col.spec.domain["pl7.app/vdj/feature"]]) { continue }
623
+ if col.spec.domain["pl7.app/vdj/scClonotypeChain/index"] != "primary" { continue }
624
+ sc := col.spec.domain["pl7.app/vdj/scClonotypeChain"]
625
+ if sc != "A" && sc != "B" { continue }
626
+ if is_undefined(byChain[sc]) { byChain[sc] = col }
627
+ }
628
+ seqCols = []
629
+ for _, c in byChain { seqCols = append(seqCols, c) }
630
+ } else {
631
+ seqCols = columns.getColumns("assemblingAaSeqs")
632
+ }
633
+
602
634
  for col in seqCols {
603
635
  headerName := makeHeaderName(col, "assemblingFeature", isSingleCell)
604
636
  assemSeqTable.add(col, {header: headerName})
@@ -606,7 +638,7 @@ initializeAssemSeqTable := func(pframes, columns, datasetSpec, isSingleCell) {
606
638
 
607
639
  assemSeqTable.mem("16GiB")
608
640
  assemSeqTable.cpu(1)
609
-
641
+
610
642
 
611
643
  bulkChain := undefined
612
644
  if !isSingleCell {
Binary file
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@platforma-open/milaboratories.top-antibodies.workflow",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "type": "module",
5
5
  "description": "Block Workflow",
6
6
  "dependencies": {
7
7
  "@platforma-sdk/workflow-tengo": "5.20.0",
8
8
  "@platforma-open/milaboratories.software-anarci": "^0.0.3",
9
9
  "@platforma-open/milaboratories.top-antibodies.sample-clonotypes": "2.1.2",
10
- "@platforma-open/milaboratories.top-antibodies.assembling-fasta": "1.3.2",
11
- "@platforma-open/milaboratories.top-antibodies.anarci-kabat": "1.4.3",
12
10
  "@platforma-open/milaboratories.top-antibodies.spectratype": "1.8.3",
13
- "@platforma-open/milaboratories.top-antibodies.umap": "1.2.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"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@platforma-sdk/tengo-builder": "2.5.20"
@@ -75,9 +75,11 @@ wf.prepare(func(args){
75
75
  bundleBuilder.addAnchor("selectedCluster", args.diversificationColumn)
76
76
  }
77
77
 
78
- // Add cluster size columns from clustering blocks
78
+ // Add cluster size columns from clustering blocks. The namePattern matches
79
+ // both the post-peptide-adaptation name and the pre-peptide `pl7.app/vdj/`
80
+ // variant so older clonotype-clustering instances remain usable.
79
81
  bundleBuilder.addMulti({
80
- name: "pl7.app/vdj/clustering/clusterSize",
82
+ namePattern: "^pl7\\.app/(vdj/)?clustering/clusterSize$",
81
83
  partialAxesMatch: true
82
84
  }, "clusterSizes")
83
85
 
@@ -109,12 +111,37 @@ wf.prepare(func(args){
109
111
  }
110
112
  }, "JGenes")
111
113
 
112
- // Add assembling feature aminoacid sequences (bulk, sc, scFv)
114
+ // Add assembling feature aminoacid sequences (bulk, sc).
113
115
  bundleBuilder.addMulti({
114
116
  axes: [{ anchor: "main", idx: 1 }], // Clonotype axis
117
+ name: "pl7.app/vdj/sequence",
115
118
  annotations: { "pl7.app/vdj/isAssemblingFeature": "true" },
116
119
  domain: { "pl7.app/alphabet": "aminoacid" }
117
120
  }, "assemblingAaSeqs")
121
+
122
+ // Detect scFv mode by presence of the combined construct column.
123
+ bundleBuilder.addMulti({
124
+ axes: [{ anchor: "main", idx: 1 }],
125
+ name: "pl7.app/vdj/scFv-sequence"
126
+ }, "scFvDetect")
127
+
128
+ // Per-chain VDJRegion AA sequences for ANARCI/Kabat in scFv mode.
129
+ bundleBuilder.addMulti({
130
+ axes: [{ anchor: "main", idx: 1 }],
131
+ name: "pl7.app/vdj/sequence",
132
+ domain: { "pl7.app/alphabet": "aminoacid" }
133
+ }, "scFvPerChainSeqs")
134
+
135
+ // Peptide main sequence — single-axis column on variantKey, used as
136
+ // modality-aware fallback when no filter/ranking columns load.
137
+ bundleBuilder.addMulti({
138
+ axes: [{ anchor: "main", idx: 1 }],
139
+ annotations: {
140
+ "pl7.app/isAssemblingFeature": "true",
141
+ "pl7.app/isMainSequence": "true"
142
+ },
143
+ domain: { "pl7.app/alphabet": "aminoacid" }
144
+ }, "peptideMainSeqs")
118
145
 
119
146
  return {
120
147
  columns: bundleBuilder.build()
@@ -224,7 +251,8 @@ wf.body(func(args) {
224
251
  if args.kabatNumbering == true {
225
252
  ////////// Assembling AA sequences //////////
226
253
  // Initialize and build assembling sequence table
227
- assemInit := utils.initializeAssemSeqTable(pframes, columns, datasetSpec, isSingleCell)
254
+ isScFv := len(columns.getColumns("scFvDetect")) > 0
255
+ assemInit := utils.initializeAssemSeqTable(pframes, columns, datasetSpec, isSingleCell, isScFv)
228
256
  assemSeqTableBuilt := assemInit.assemSeqTable
229
257
  bulkChain := assemInit.bulkChain
230
258
  seqCols := assemInit.seqCols
@@ -1,5 +1,6 @@
1
1
  // Utility functions for antibody-tcr-lead-selection workflow
2
2
 
3
+ ll := import("@platforma-sdk/workflow-tengo:ll")
3
4
  slices := import("@platforma-sdk/workflow-tengo:slices")
4
5
  json := import("json")
5
6
 
@@ -10,6 +11,13 @@ inVivoScoreSourceColumns := {
10
11
  "nMutations": "pl7.app/vdj/sequence/nMutations"
11
12
  }
12
13
 
14
+ // Cluster-id axis names — both unprefixed (post-peptide-adaptation) and
15
+ // `pl7.app/vdj/`-prefixed (pre-peptide) so older clonotype-clustering
16
+ // instances remain selectable as diversification linkers.
17
+ isClusterIdAxis := func(name) {
18
+ return name == "pl7.app/clusterId" || name == "pl7.app/vdj/clusterId"
19
+ }
20
+
13
21
  /**
14
22
  * Checks if two clusterId axes have matching domains.
15
23
  * Used to determine if two columns belong to the same clustering run.
@@ -61,7 +69,7 @@ findMatchingLinkerIndex := func(colsSpec, linkerColumns) {
61
69
  // Find the clusterId axis in the ranking column
62
70
  rankingClusterIdAxis := undefined
63
71
  for axis in colsSpec.axesSpec {
64
- if axis.name == "pl7.app/clusterId" {
72
+ if isClusterIdAxis(axis.name) {
65
73
  rankingClusterIdAxis = axis
66
74
  break
67
75
  }
@@ -76,7 +84,7 @@ findMatchingLinkerIndex := func(colsSpec, linkerColumns) {
76
84
  // Get the clusterId axis from the linker column
77
85
  linkerClusterIdAxis := undefined
78
86
  for axis in linkerCol.spec.axesSpec {
79
- if axis.name == "pl7.app/clusterId" {
87
+ if isClusterIdAxis(axis.name) {
80
88
  linkerClusterIdAxis = axis
81
89
  break
82
90
  }
@@ -195,7 +203,7 @@ resolveClusterColumnHeader := func(args, columns, sortedLinkers) {
195
203
  // Find the clusterId axis in the selected linker
196
204
  selectedClusterIdAxis := undefined
197
205
  for axis in selectedLinkerSpec.axesSpec {
198
- if axis.name == "pl7.app/clusterId" {
206
+ if isClusterIdAxis(axis.name) {
199
207
  selectedClusterIdAxis = axis
200
208
  break
201
209
  }
@@ -209,7 +217,7 @@ resolveClusterColumnHeader := func(args, columns, sortedLinkers) {
209
217
  for linkerIdx, col in sortedLinkers {
210
218
  // Get the clusterId axis from this linker
211
219
  for axis in col.spec.axesSpec {
212
- if axis.name == "pl7.app/clusterId" {
220
+ if isClusterIdAxis(axis.name) {
213
221
  // Use clusterAxisDomainsMatch for proper domain comparison
214
222
  if clusterAxisDomainsMatch(selectedClusterIdAxis, axis) {
215
223
  return "clusterAxis_" + string(linkerIdx) + "_0"
@@ -409,7 +417,7 @@ initializeCloneTable := func(pframes, columns, args, datasetSpec) {
409
417
  addedCols = true
410
418
  }
411
419
  // Collect clusterId axes from linker columns to match cluster size columns
412
- if !is_undefined(clusterIdAxis) && clusterIdAxis.name == "pl7.app/clusterId" {
420
+ if !is_undefined(clusterIdAxis) && isClusterIdAxis(clusterIdAxis.name) {
413
421
  linkerClusterIdAxesWithIdx = append(linkerClusterIdAxesWithIdx, {
414
422
  axis: clusterIdAxis,
415
423
  linkerIdx: linkerIdx
@@ -423,7 +431,7 @@ initializeCloneTable := func(pframes, columns, args, datasetSpec) {
423
431
  // Find the clusterId axis in this cluster size column
424
432
  clusterSizeClusterIdAxis := undefined
425
433
  for axis in col.spec.axesSpec {
426
- if axis.name == "pl7.app/clusterId" {
434
+ if isClusterIdAxis(axis.name) {
427
435
  clusterSizeClusterIdAxis = axis
428
436
  break
429
437
  }
@@ -458,12 +466,19 @@ initializeCloneTable := func(pframes, columns, args, datasetSpec) {
458
466
  }
459
467
  }
460
468
 
461
- // Fallback: if no columns added, add at least one CDR3 sequence column
469
+ // Fallback: if no columns added, add a single-axis trunk-keyed sequence
470
+ // column. Try VDJ CDR3 first
462
471
  if !addedCols {
463
472
  cdr3Sequences := columns.getColumns("cdr3Sequences")
464
473
  if len(cdr3Sequences) > 0 {
465
- cloneTable.add(cdr3Sequences[0], {header: "cdr3_fallback"})
474
+ cloneTable.add(cdr3Sequences[0], {header: "sequence_fallback"})
466
475
  addedCols = true
476
+ } else {
477
+ peptideMainSeqs := columns.getColumns("peptideMainSeqs")
478
+ if len(peptideMainSeqs) > 0 {
479
+ cloneTable.add(peptideMainSeqs[0], {header: "sequence_fallback"})
480
+ addedCols = true
481
+ }
467
482
  }
468
483
  }
469
484
 
@@ -587,18 +602,35 @@ detectBulkChain := func(seqCols) {
587
602
 
588
603
  /**
589
604
  * Initializes and builds assembling sequence table with assembling AA sequences.
590
- *
605
+
591
606
  * @param pframes - PFrames import
592
607
  * @param columns - PBundle containing all columns
593
608
  * @param datasetSpec - Dataset specification with axes
594
609
  * @param isSingleCell - Whether the data is single cell
610
+ * @param isScFv - Whether the input is scFv data
595
611
  * @return Map with keys: assemSeqTable (built table), bulkChain, seqCols
596
612
  */
597
- initializeAssemSeqTable := func(pframes, columns, datasetSpec, isSingleCell) {
613
+ initializeAssemSeqTable := func(pframes, columns, datasetSpec, isSingleCell, isScFv) {
598
614
  assemSeqTable := pframes.parquetFileBuilder()
599
615
  assemSeqTable.setAxisHeader(datasetSpec.axesSpec[1].name, "clonotypeKey")
600
616
 
601
- seqCols := columns.getColumns("assemblingAaSeqs")
617
+ seqCols := undefined
618
+ if isScFv {
619
+ allowedFeatures := { "VDJRegion": true, "VDJRegionInFrame": true }
620
+ byChain := {}
621
+ for col in columns.getColumns("scFvPerChainSeqs") {
622
+ if is_undefined(allowedFeatures[col.spec.domain["pl7.app/vdj/feature"]]) { continue }
623
+ if col.spec.domain["pl7.app/vdj/scClonotypeChain/index"] != "primary" { continue }
624
+ sc := col.spec.domain["pl7.app/vdj/scClonotypeChain"]
625
+ if sc != "A" && sc != "B" { continue }
626
+ if is_undefined(byChain[sc]) { byChain[sc] = col }
627
+ }
628
+ seqCols = []
629
+ for _, c in byChain { seqCols = append(seqCols, c) }
630
+ } else {
631
+ seqCols = columns.getColumns("assemblingAaSeqs")
632
+ }
633
+
602
634
  for col in seqCols {
603
635
  headerName := makeHeaderName(col, "assemblingFeature", isSingleCell)
604
636
  assemSeqTable.add(col, {header: headerName})
@@ -606,7 +638,7 @@ initializeAssemSeqTable := func(pframes, columns, datasetSpec, isSingleCell) {
606
638
 
607
639
  assemSeqTable.mem("16GiB")
608
640
  assemSeqTable.cpu(1)
609
-
641
+
610
642
  // Detect bulk chain if needed
611
643
  bulkChain := undefined
612
644
  if !isSingleCell {