@platforma-open/milaboratories.mixcr-clonotyping-2.workflow 2.17.0 → 2.18.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/mixcr-clonotyping/mixcr-clonotyping/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @platforma-open/milaboratories.mixcr-clonotyping-2.workflow@2.17.0 build /home/runner/work/mixcr-clonotyping/mixcr-clonotyping/workflow
3
+ > @platforma-open/milaboratories.mixcr-clonotyping-2.workflow@2.18.0 build /home/runner/work/mixcr-clonotyping/mixcr-clonotyping/workflow
4
4
  > rm -rf dist && pl-tengo check && pl-tengo build
5
5
 
6
6
  info: Skipping unknown file type: test/columns.test.ts
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @platforma-open/milaboratories.mixcr-clonotyping.workflow
2
2
 
3
+ ## 2.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2a9ce2a: - added two aggregated columns: Mean Fraction Of UMIs/Reads, Supprting UMIs/Reads
8
+ - support for RNA-Seq preset
9
+ - fallback for in-frame features for amino-acid sequence columns (for FR4 and VDJRegion)
10
+ - annotation strings for nucleotide and amino-acid sequences of assembling feature
11
+
3
12
  ## 2.17.0
4
13
 
5
14
  ### Minor Changes
@@ -7,10 +7,24 @@ json := import("json")
7
7
  a := func(order, defaultVisibility, spec) {
8
8
  return maps.merge(spec, {
9
9
  "pl7.app/table/orderPriority": string(order),
10
- "pl7.app/table/visibility": defaultVisibility ? "default" : "optional"
10
+ "pl7.app/table/visibility": is_undefined(defaultVisibility) ? "hidden" : defaultVisibility ? "default" : "optional"
11
11
  })
12
12
  }
13
13
 
14
+
15
+
16
+
17
+
18
+ inFrameFeatures := {
19
+ "FR4": "FR4InFrame",
20
+ "VDJRegion": "VDJRegionInFrame"
21
+ }
22
+
23
+ annotationMappings := {
24
+ "CDRs": "{\"1\":\"CDR1\",\"2\":\"CDR2\",\"3\":\"CDR3\"}",
25
+ "Segments": "{\"1\":\"V\",\"2\":\"D\",\"3\":\"J\",\"4\":\"C\"}"
26
+ }
27
+
14
28
  toCombinedDomainValue := func(spec) {
15
29
  result := [spec.name]
16
30
 
@@ -42,7 +56,7 @@ assemblingFeatureInfo := func(assemblingFeature) {
42
56
  if assemblingFeature == "CDR3" {
43
57
  productiveFeature = "CDR3"
44
58
  } else if assemblingFeature == "VDJRegion" {
45
- productiveFeature = "VDJRegion(0,-1)"
59
+ productiveFeature = "VDJRegionInFrame"
46
60
  coreVFeature = "{FR1Begin:FR3End}"
47
61
  coreJFeature = "FR4"
48
62
  } else if len(splittedFeature1) == 2 {
@@ -116,8 +130,31 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
116
130
  productiveFeature := assemblingFeatureInfo.productiveFeature
117
131
  coreGeneFeatures := assemblingFeatureInfo.coreGeneFeatures
118
132
 
133
+
134
+ anchorFeature := undefined;
135
+
136
+ features := undefined
137
+ if is_undefined(assemblingFeature) {
138
+ features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
139
+
140
+
141
+
142
+
143
+
144
+ assemblingFeature = "CDR3"
145
+
146
+ anchorFeature = "CDR3"
147
+ } else if assemblingFeature != "CDR3" {
148
+ features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4", assemblingFeature]
149
+ anchorFeature = assemblingFeature
150
+ } else {
151
+ features = ["CDR3"]
152
+ anchorFeature = "CDR3"
153
+ }
154
+
119
155
  clonotypeKeyColumns := undefined
120
156
  clonotypeKeyArgs := undefined
157
+
121
158
  if !is_undefined(assemblingFeature) {
122
159
  clonotypeKeyColumns = ["nSeq" + assemblingFeature, "bestVGene", "bestJGene"]
123
160
  clonotypeKeyArgs = [
@@ -129,7 +166,6 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
129
166
  clonotypeKeyColumns += ["bestCGene"]
130
167
  clonotypeKeyArgs += [ [ "-cGene" ] ]
131
168
  }
132
-
133
169
  }
134
170
 
135
171
  columnsSpecPerSample := []
@@ -181,7 +217,42 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
181
217
  [ "-readCount" ],
182
218
  [ "-readFraction" ]
183
219
  ]
184
- mainAbundanceColumn := "readFraction"
220
+
221
+ mainAbundanceColumnUnnormalized := "readCount"
222
+ mainAbundanceColumnNormalized := "readFraction"
223
+
224
+ mainAbundanceColumnAggregates := [{
225
+ column: mainAbundanceColumnUnnormalized + "Sum",
226
+ id: "read-count-total",
227
+ allowNA: false,
228
+ spec: {
229
+ name: "pl7.app/vdj/readCountTotal",
230
+ valueType: "Int",
231
+ annotations: a(87120, true, {
232
+ "pl7.app/min": "1",
233
+ "pl7.app/isAbundance": "true",
234
+ "pl7.app/abundance/unit": "reads",
235
+ "pl7.app/abundance/normalized": "false",
236
+ "pl7.app/label": "Supporting Reads"
237
+ })
238
+ }
239
+ }, {
240
+ column: mainAbundanceColumnNormalized + "Mean",
241
+ id: "read-fraction-mean",
242
+ allowNA: false,
243
+ spec: {
244
+ name: "pl7.app/vdj/readFractionMean",
245
+ valueType: "Double",
246
+ annotations: a(87130, true, {
247
+ "pl7.app/min": "0",
248
+ "pl7.app/max": "1",
249
+ "pl7.app/isAbundance": "true",
250
+ "pl7.app/abundance/unit": "reads",
251
+ "pl7.app/abundance/normalized": "true",
252
+ "pl7.app/label": "Mean Fraction of Reads"
253
+ })
254
+ }
255
+ }]
185
256
 
186
257
  if hasUmi {
187
258
  columnsSpecPerSample += [ {
@@ -223,7 +294,40 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
223
294
  [ "-uniqueTagCount", "Molecule" ],
224
295
  [ "-uniqueTagFraction", "Molecule" ]
225
296
  ]
226
- mainAbundanceColumn = "uniqueMoleculeFraction"
297
+ mainAbundanceColumnNormalized = "uniqueMoleculeFraction"
298
+ mainAbundanceColumnUnnormalized = "uniqueMoleculeCount"
299
+ mainAbundanceColumnAggregates = [ {
300
+ column: mainAbundanceColumnUnnormalized + "Sum",
301
+ id: "umi-count-total",
302
+ allowNA: false,
303
+ spec: {
304
+ name: "pl7.app/vdj/uniqueMoleculeCountTotal",
305
+ valueType: "Long",
306
+ annotations: a(87120, true, {
307
+ "pl7.app/min": "1",
308
+ "pl7.app/isAbundance": "true",
309
+ "pl7.app/abundance/unit": "molecules",
310
+ "pl7.app/abundance/normalized": "false",
311
+ "pl7.app/label": "Supporting UMIs"
312
+ })
313
+ }
314
+ }, {
315
+ column: mainAbundanceColumnNormalized + "Mean",
316
+ id: "umi-fraction-mean",
317
+ allowNA: false,
318
+ spec: {
319
+ name: "pl7.app/vdj/uniqueMoleculeFractionMean",
320
+ valueType: "Double",
321
+ annotations: a(87130, true, {
322
+ "pl7.app/min": "0",
323
+ "pl7.app/max": "1",
324
+ "pl7.app/isAbundance": "true",
325
+ "pl7.app/abundance/unit": "molecules",
326
+ "pl7.app/abundance/normalized": "true",
327
+ "pl7.app/label": "Mean Fraction of UMIs"
328
+ })
329
+ }
330
+ } ]
227
331
  }
228
332
 
229
333
  sampleCountColumn := {
@@ -291,6 +395,7 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
291
395
  columnsSpecPerClonotypeSc = [ sampleCountColumn ]
292
396
  } else {
293
397
  columnsSpecPerClonotype += [ sampleCountColumn ]
398
+ columnsSpecPerClonotype += mainAbundanceColumnAggregates
294
399
  }
295
400
 
296
401
  orderP := 80000
@@ -298,19 +403,8 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
298
403
 
299
404
 
300
405
 
301
- anchorFeature := undefined;
302
406
 
303
- features := undefined
304
- if is_undefined(assemblingFeature) {
305
- features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
306
- anchorFeature = "CDR3"
307
- } else if assemblingFeature != "CDR3" {
308
- features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4", assemblingFeature]
309
- anchorFeature = assemblingFeature
310
- } else {
311
- features = ["CDR3"]
312
- anchorFeature = "CDR3"
313
- }
407
+ annotationTypes := assemblingFeature == "CDR3" ? ["Segments"] : ["CDRs", "Segments"]
314
408
 
315
409
  for isImputed in ( is_undefined(assemblingFeature) ? [false, true] : [false] ) {
316
410
  imputedU := isImputed ? "Imputed" : ""
@@ -318,33 +412,68 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
318
412
  for featureU in features {
319
413
  featureL := text.to_lower(formatId(featureU))
320
414
  for isAminoAcid in [true, false] {
415
+ featureInFrameU := isAminoAcid ? inFrameFeatures[featureU] : featureU
416
+ if is_undefined(featureInFrameU) {
417
+ featureInFrameU = featureU
418
+ }
419
+ featureInFrameL := text.to_lower(formatId(featureInFrameU))
420
+
321
421
  alphabet := isAminoAcid ? "aminoacid" : "nucleotide"
322
422
  alphabetShort := isAminoAcid ? "aa" : "nt"
323
423
  alphabetShortMixcr := isAminoAcid ? "aa" : "n"
324
- columnName := alphabetShortMixcr + "Seq" + imputedU + featureU
424
+ columnName := alphabetShortMixcr + "Seq" + imputedU + featureInFrameU
325
425
  visibility := featureU == "CDR3" && (!isSingleCell || isAminoAcid) // isSingleCell ? (featureU == "CDR3") && isAminoAcid : (featureU == "CDR3") || (featureU == assemblingFeature)
326
426
  columnsSpecPerClonotype += [ {
327
427
  column: columnName,
328
- id: alphabetShortMixcr + "-seq-" + featureL + (isImputed ? "-imputed" : ""),
428
+ id: alphabetShortMixcr + "-seq-" + featureInFrameL + (isImputed ? "-imputed" : ""),
329
429
  naRegex: "region_not_covered",
330
430
  spec: {
331
431
  name: "pl7.app/vdj/sequence",
332
432
  valueType: "String",
333
433
  domain: {
334
- "pl7.app/vdj/feature": featureU,
434
+ "pl7.app/vdj/feature": featureInFrameU,
335
435
  "pl7.app/alphabet": alphabet
336
436
  },
337
437
  annotations: a(orderP, visibility, {
338
438
  "pl7.app/vdj/isAssemblingFeature": featureU == anchorFeature ? "true" : "false",
439
+ "pl7.app/vdj/isMainSequence": featureU == anchorFeature ? "true" : "false",
339
440
  "pl7.app/vdj/imputed": string(isImputed),
340
- "pl7.app/label": featureU + " " + alphabetShort
441
+ "pl7.app/label": featureInFrameU + " " + alphabetShort
341
442
  })
342
443
  }
343
444
  } ]
344
- exportArgs += [ [ "-" + alphabetShortMixcr + "Feature" + imputedU, featureU ] ]
445
+ exportArgs += [ [ "-" + alphabetShortMixcr + "Feature" + imputedU, featureInFrameU ] ]
345
446
  orderP -= 100
346
447
 
347
448
 
449
+ if !isImputed && featureU == assemblingFeature {
450
+ for annotationType in annotationTypes {
451
+ columnName := alphabetShortMixcr + "AnnotationOf" + annotationType + "For" + featureInFrameU
452
+ columnsSpecPerClonotype += [ {
453
+ column: columnName,
454
+ id: alphabetShortMixcr + "-annotation-" + annotationType + "-" + featureInFrameL,
455
+ naRegex: "region_not_covered",
456
+ spec: {
457
+ name: "pl7.app/vdj/sequence/annotation",
458
+ valueType: "String",
459
+ domain: {
460
+ "pl7.app/vdj/feature": featureInFrameU,
461
+ "pl7.app/alphabet": alphabet,
462
+ "pl7.app/sequence/annotation/type": annotationType
463
+ },
464
+ annotations: a(orderP, undefined, {
465
+ "pl7.app/label": annotationType + " annotation for " + featureInFrameU + " " + alphabetShort,
466
+ "pl7.app/sequence/annotation/mapping": annotationMappings[annotationType],
467
+ "pl7.app/sequence/isAnnotation": "true"
468
+ })
469
+ }
470
+ } ]
471
+ exportArgs += [ [ "-" + alphabetShortMixcr + "AnnotationString", annotationType, featureInFrameU ] ]
472
+ orderP -= 100
473
+ }
474
+ }
475
+
476
+
348
477
  if featureU == "CDR3" {
349
478
  columnsSpecPerClonotype += [ {
350
479
  column: alphabetShortMixcr + "Length" + featureU,
@@ -701,7 +830,9 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
701
830
 
702
831
  columnsSpec: columnsSpec,
703
832
 
704
- mainAbundanceColumn: mainAbundanceColumn,
833
+ mainAbundanceColumnNormalized: mainAbundanceColumnNormalized,
834
+ mainAbundanceColumnUnnormalized: mainAbundanceColumnUnnormalized,
835
+
705
836
  mainProductiveColumn: mainProductiveColumn,
706
837
  mainProductiveArgs: mainProductiveArgs,
707
838
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@platforma-open/milaboratories.mixcr-clonotyping-2.workflow",
3
- "version": "2.17.0",
3
+ "version": "2.18.0",
4
4
  "description": "Tengo-based template",
5
5
  "dependencies": {
6
- "@platforma-sdk/workflow-tengo": "^4.1.2",
6
+ "@platforma-sdk/workflow-tengo": "^4.3.2",
7
7
  "@platforma-open/milaboratories.mixcr-clonotyping-2.single-cell-scripts": "1.1.3",
8
8
  "@platforma-open/milaboratories.mixcr-clonotyping-2.hash-column": "1.1.1"
9
9
  },
10
10
  "devDependencies": {
11
- "@platforma-sdk/tengo-builder": "^2.1.0",
12
- "@platforma-open/milaboratories.software-mixcr": "4.7.0-165-develop",
11
+ "@platforma-sdk/tengo-builder": "^2.1.3",
12
+ "@platforma-open/milaboratories.software-mixcr": "4.7.0-169-develop",
13
13
  "@platforma-open/milaboratories.software-ptransform": "^1.4.2",
14
- "@platforma-sdk/test": "^1.29.17",
14
+ "@platforma-sdk/test": "^1.30.24",
15
15
  "vitest": "~2.1.8",
16
16
  "typescript": "~5.5.4"
17
17
  },
@@ -19,12 +19,13 @@ self.body(func(inputs) {
19
19
  ll.assert(inputDataMeta.keyLength == 1, "unexpected number of aggregation axes")
20
20
 
21
21
  params := inputs.params
22
- mainAbundanceColumn := params.mainAbundanceColumn
22
+ mainAbundanceColumnNormalized := params.mainAbundanceColumnNormalized
23
+ mainAbundanceColumnUnnormalized := params.mainAbundanceColumnUnnormalized
23
24
  clonotypeColumns := params.clonotypeColumns
24
25
 
25
26
  pickCols := []
26
27
  for col in clonotypeColumns {
27
- if col == "sampleCount" {
28
+ if col == "sampleCount" || col == mainAbundanceColumnNormalized + "Mean" || col == mainAbundanceColumnUnnormalized + "Sum" {
28
29
  continue
29
30
  }
30
31
  pickCols = append(pickCols, [col, col])
@@ -37,13 +38,21 @@ self.body(func(inputs) {
37
38
  groupBy: ["clonotypeKey"],
38
39
  aggregations: [ {
39
40
  type: "max_by",
40
- rankingCol: mainAbundanceColumn,
41
+ rankingCol: mainAbundanceColumnNormalized,
41
42
  pickCols: pickCols
42
43
  }, {
43
44
  type: "count",
44
- src: mainAbundanceColumn,
45
+ src: mainAbundanceColumnNormalized,
45
46
  dst: "sampleCount"
46
- } ]
47
+ }, {
48
+ type: "sum",
49
+ src: mainAbundanceColumnUnnormalized,
50
+ dst: mainAbundanceColumnUnnormalized + "Sum"
51
+ }, {
52
+ type: "mean",
53
+ src: mainAbundanceColumnNormalized,
54
+ dst: mainAbundanceColumnNormalized + "Mean"
55
+ }]
47
56
  } ]
48
57
  }
49
58
 
@@ -7,10 +7,24 @@ json := import("json")
7
7
  a := func(order, defaultVisibility, spec) {
8
8
  return maps.merge(spec, {
9
9
  "pl7.app/table/orderPriority": string(order),
10
- "pl7.app/table/visibility": defaultVisibility ? "default" : "optional"
10
+ "pl7.app/table/visibility": is_undefined(defaultVisibility) ? "hidden" : defaultVisibility ? "default" : "optional"
11
11
  })
12
12
  }
13
13
 
14
+ /**
15
+ * Mapping from original featuers to their in-frame variants when available.
16
+ * This is used to avoid showing non-informative features in the clonotype browser.
17
+ */
18
+ inFrameFeatures := {
19
+ "FR4": "FR4InFrame",
20
+ "VDJRegion": "VDJRegionInFrame"
21
+ }
22
+
23
+ annotationMappings := {
24
+ "CDRs": "{\"1\":\"CDR1\",\"2\":\"CDR2\",\"3\":\"CDR3\"}",
25
+ "Segments": "{\"1\":\"V\",\"2\":\"D\",\"3\":\"J\",\"4\":\"C\"}"
26
+ }
27
+
14
28
  toCombinedDomainValue := func(spec) {
15
29
  result := [spec.name]
16
30
  // getKeys sort keys
@@ -42,7 +56,7 @@ assemblingFeatureInfo := func(assemblingFeature) {
42
56
  if assemblingFeature == "CDR3" {
43
57
  productiveFeature = "CDR3"
44
58
  } else if assemblingFeature == "VDJRegion" {
45
- productiveFeature = "VDJRegion(0,-1)"
59
+ productiveFeature = "VDJRegionInFrame"
46
60
  coreVFeature = "{FR1Begin:FR3End}"
47
61
  coreJFeature = "FR4"
48
62
  } else if len(splittedFeature1) == 2 {
@@ -116,8 +130,31 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
116
130
  productiveFeature := assemblingFeatureInfo.productiveFeature
117
131
  coreGeneFeatures := assemblingFeatureInfo.coreGeneFeatures
118
132
 
133
+ // column with nucleotide sequence of this feature will be marked as anchor
134
+ anchorFeature := undefined;
135
+
136
+ features := undefined
137
+ if is_undefined(assemblingFeature) {
138
+ features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
139
+
140
+ // override assembling feature returned by the mixcr, we assume that if it is undefined,
141
+ // it means that the assembling feature is CDR3, and assemble contigs is executed with null
142
+ // subcloning region
143
+ // TODO return more detailed information from MiXCR
144
+ assemblingFeature = "CDR3"
145
+
146
+ anchorFeature = "CDR3"
147
+ } else if assemblingFeature != "CDR3" {
148
+ features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4", assemblingFeature]
149
+ anchorFeature = assemblingFeature
150
+ } else {
151
+ features = ["CDR3"]
152
+ anchorFeature = "CDR3"
153
+ }
154
+
119
155
  clonotypeKeyColumns := undefined
120
156
  clonotypeKeyArgs := undefined
157
+
121
158
  if !is_undefined(assemblingFeature) {
122
159
  clonotypeKeyColumns = ["nSeq" + assemblingFeature, "bestVGene", "bestJGene"]
123
160
  clonotypeKeyArgs = [
@@ -129,7 +166,6 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
129
166
  clonotypeKeyColumns += ["bestCGene"]
130
167
  clonotypeKeyArgs += [ [ "-cGene" ] ]
131
168
  }
132
-
133
169
  }
134
170
 
135
171
  columnsSpecPerSample := []
@@ -181,7 +217,42 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
181
217
  [ "-readCount" ],
182
218
  [ "-readFraction" ]
183
219
  ]
184
- mainAbundanceColumn := "readFraction"
220
+
221
+ mainAbundanceColumnUnnormalized := "readCount"
222
+ mainAbundanceColumnNormalized := "readFraction"
223
+
224
+ mainAbundanceColumnAggregates := [{
225
+ column: mainAbundanceColumnUnnormalized + "Sum",
226
+ id: "read-count-total",
227
+ allowNA: false,
228
+ spec: {
229
+ name: "pl7.app/vdj/readCountTotal",
230
+ valueType: "Int",
231
+ annotations: a(87120, true, {
232
+ "pl7.app/min": "1",
233
+ "pl7.app/isAbundance": "true",
234
+ "pl7.app/abundance/unit": "reads",
235
+ "pl7.app/abundance/normalized": "false",
236
+ "pl7.app/label": "Supporting Reads"
237
+ })
238
+ }
239
+ }, {
240
+ column: mainAbundanceColumnNormalized + "Mean",
241
+ id: "read-fraction-mean",
242
+ allowNA: false,
243
+ spec: {
244
+ name: "pl7.app/vdj/readFractionMean",
245
+ valueType: "Double",
246
+ annotations: a(87130, true, {
247
+ "pl7.app/min": "0",
248
+ "pl7.app/max": "1",
249
+ "pl7.app/isAbundance": "true",
250
+ "pl7.app/abundance/unit": "reads",
251
+ "pl7.app/abundance/normalized": "true",
252
+ "pl7.app/label": "Mean Fraction of Reads"
253
+ })
254
+ }
255
+ }]
185
256
 
186
257
  if hasUmi {
187
258
  columnsSpecPerSample += [ {
@@ -223,7 +294,40 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
223
294
  [ "-uniqueTagCount", "Molecule" ],
224
295
  [ "-uniqueTagFraction", "Molecule" ]
225
296
  ]
226
- mainAbundanceColumn = "uniqueMoleculeFraction"
297
+ mainAbundanceColumnNormalized = "uniqueMoleculeFraction"
298
+ mainAbundanceColumnUnnormalized = "uniqueMoleculeCount"
299
+ mainAbundanceColumnAggregates = [ {
300
+ column: mainAbundanceColumnUnnormalized + "Sum",
301
+ id: "umi-count-total",
302
+ allowNA: false,
303
+ spec: {
304
+ name: "pl7.app/vdj/uniqueMoleculeCountTotal",
305
+ valueType: "Long",
306
+ annotations: a(87120, true, {
307
+ "pl7.app/min": "1",
308
+ "pl7.app/isAbundance": "true",
309
+ "pl7.app/abundance/unit": "molecules",
310
+ "pl7.app/abundance/normalized": "false",
311
+ "pl7.app/label": "Supporting UMIs"
312
+ })
313
+ }
314
+ }, {
315
+ column: mainAbundanceColumnNormalized + "Mean",
316
+ id: "umi-fraction-mean",
317
+ allowNA: false,
318
+ spec: {
319
+ name: "pl7.app/vdj/uniqueMoleculeFractionMean",
320
+ valueType: "Double",
321
+ annotations: a(87130, true, {
322
+ "pl7.app/min": "0",
323
+ "pl7.app/max": "1",
324
+ "pl7.app/isAbundance": "true",
325
+ "pl7.app/abundance/unit": "molecules",
326
+ "pl7.app/abundance/normalized": "true",
327
+ "pl7.app/label": "Mean Fraction of UMIs"
328
+ })
329
+ }
330
+ } ]
227
331
  }
228
332
 
229
333
  sampleCountColumn := {
@@ -291,26 +395,16 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
291
395
  columnsSpecPerClonotypeSc = [ sampleCountColumn ]
292
396
  } else {
293
397
  columnsSpecPerClonotype += [ sampleCountColumn ]
398
+ columnsSpecPerClonotype += mainAbundanceColumnAggregates
294
399
  }
295
400
 
296
401
  orderP := 80000
297
402
 
298
403
  // Sequences
299
404
 
300
- // column with nucleotide sequence of this feature will be marked as anchor
301
- anchorFeature := undefined;
302
-
303
- features := undefined
304
- if is_undefined(assemblingFeature) {
305
- features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
306
- anchorFeature = "CDR3"
307
- } else if assemblingFeature != "CDR3" {
308
- features = ["CDR1", "FR1", "FR2", "CDR2", "FR3", "CDR3", "FR4", assemblingFeature]
309
- anchorFeature = assemblingFeature
310
- } else {
311
- features = ["CDR3"]
312
- anchorFeature = "CDR3"
313
- }
405
+ // nAnnotationOfCDRsForVDJRegionInFrame
406
+ // aaAnnotationOfSegmentsForVDJRegionInFrame
407
+ annotationTypes := assemblingFeature == "CDR3" ? ["Segments"] : ["CDRs", "Segments"]
314
408
 
315
409
  for isImputed in ( is_undefined(assemblingFeature) ? [false, true] : [false] ) {
316
410
  imputedU := isImputed ? "Imputed" : ""
@@ -318,32 +412,67 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
318
412
  for featureU in features {
319
413
  featureL := text.to_lower(formatId(featureU))
320
414
  for isAminoAcid in [true, false] {
415
+ featureInFrameU := isAminoAcid ? inFrameFeatures[featureU] : featureU
416
+ if is_undefined(featureInFrameU) {
417
+ featureInFrameU = featureU
418
+ }
419
+ featureInFrameL := text.to_lower(formatId(featureInFrameU))
420
+
321
421
  alphabet := isAminoAcid ? "aminoacid" : "nucleotide"
322
422
  alphabetShort := isAminoAcid ? "aa" : "nt"
323
423
  alphabetShortMixcr := isAminoAcid ? "aa" : "n"
324
- columnName := alphabetShortMixcr + "Seq" + imputedU + featureU
424
+ columnName := alphabetShortMixcr + "Seq" + imputedU + featureInFrameU
325
425
  visibility := featureU == "CDR3" && (!isSingleCell || isAminoAcid) // isSingleCell ? (featureU == "CDR3") && isAminoAcid : (featureU == "CDR3") || (featureU == assemblingFeature)
326
426
  columnsSpecPerClonotype += [ {
327
427
  column: columnName,
328
- id: alphabetShortMixcr + "-seq-" + featureL + (isImputed ? "-imputed" : ""),
428
+ id: alphabetShortMixcr + "-seq-" + featureInFrameL + (isImputed ? "-imputed" : ""),
329
429
  naRegex: "region_not_covered",
330
430
  spec: {
331
431
  name: "pl7.app/vdj/sequence",
332
432
  valueType: "String",
333
433
  domain: {
334
- "pl7.app/vdj/feature": featureU,
434
+ "pl7.app/vdj/feature": featureInFrameU,
335
435
  "pl7.app/alphabet": alphabet
336
436
  },
337
437
  annotations: a(orderP, visibility, {
338
438
  "pl7.app/vdj/isAssemblingFeature": featureU == anchorFeature ? "true" : "false",
439
+ "pl7.app/vdj/isMainSequence": featureU == anchorFeature ? "true" : "false",
339
440
  "pl7.app/vdj/imputed": string(isImputed),
340
- "pl7.app/label": featureU + " " + alphabetShort
441
+ "pl7.app/label": featureInFrameU + " " + alphabetShort
341
442
  })
342
443
  }
343
444
  } ]
344
- exportArgs += [ [ "-" + alphabetShortMixcr + "Feature" + imputedU, featureU ] ]
445
+ exportArgs += [ [ "-" + alphabetShortMixcr + "Feature" + imputedU, featureInFrameU ] ]
345
446
  orderP -= 100
346
447
 
448
+ // Adding sequence annotation columns for assembling feature
449
+ if !isImputed && featureU == assemblingFeature {
450
+ for annotationType in annotationTypes {
451
+ columnName := alphabetShortMixcr + "AnnotationOf" + annotationType + "For" + featureInFrameU
452
+ columnsSpecPerClonotype += [ {
453
+ column: columnName,
454
+ id: alphabetShortMixcr + "-annotation-" + annotationType + "-" + featureInFrameL,
455
+ naRegex: "region_not_covered",
456
+ spec: {
457
+ name: "pl7.app/vdj/sequence/annotation",
458
+ valueType: "String",
459
+ domain: {
460
+ "pl7.app/vdj/feature": featureInFrameU,
461
+ "pl7.app/alphabet": alphabet,
462
+ "pl7.app/sequence/annotation/type": annotationType
463
+ },
464
+ annotations: a(orderP, undefined, {
465
+ "pl7.app/label": annotationType + " annotation for " + featureInFrameU + " " + alphabetShort,
466
+ "pl7.app/sequence/annotation/mapping": annotationMappings[annotationType],
467
+ "pl7.app/sequence/isAnnotation": "true"
468
+ })
469
+ }
470
+ } ]
471
+ exportArgs += [ [ "-" + alphabetShortMixcr + "AnnotationString", annotationType, featureInFrameU ] ]
472
+ orderP -= 100
473
+ }
474
+ }
475
+
347
476
  // For now calculate length only for CDR3 to keep the number of columns manageable
348
477
  if featureU == "CDR3" {
349
478
  columnsSpecPerClonotype += [ {
@@ -701,7 +830,9 @@ calculateExportSpecs := func(presetSpecForBack, blockId) {
701
830
 
702
831
  columnsSpec: columnsSpec,
703
832
 
704
- mainAbundanceColumn: mainAbundanceColumn,
833
+ mainAbundanceColumnNormalized: mainAbundanceColumnNormalized,
834
+ mainAbundanceColumnUnnormalized: mainAbundanceColumnUnnormalized,
835
+
705
836
  mainProductiveColumn: mainProductiveColumn,
706
837
  mainProductiveArgs: mainProductiveArgs,
707
838
 
@@ -214,7 +214,8 @@ self.body(func(inputs) {
214
214
 
215
215
  exportArgs := exportSpecs.exportArgs
216
216
 
217
- mainAbundanceColumn := exportSpecs.mainAbundanceColumn
217
+ mainAbundanceColumnNormalized := exportSpecs.mainAbundanceColumnNormalized
218
+ mainAbundanceColumnUnnormalized := exportSpecs.mainAbundanceColumnUnnormalized
218
219
 
219
220
  if is_undefined(axesByClonotypeKey) {
220
221
  ll.panic("Absent clonotype key not supported")
@@ -408,7 +409,8 @@ self.body(func(inputs) {
408
409
  aggregate: ["pl7.app/sampleId"],
409
410
  extra: {
410
411
  params: {
411
- mainAbundanceColumn: mainAbundanceColumn,
412
+ mainAbundanceColumnNormalized: mainAbundanceColumnNormalized,
413
+ mainAbundanceColumnUnnormalized: mainAbundanceColumnUnnormalized,
412
414
  clonotypeColumns: slices.map(columnsSpecPerClonotype, func(col) {
413
415
  return col.column
414
416
  })
@@ -59,7 +59,7 @@ const testCases: TestCase[] = [
59
59
  expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqCDR2')).toBeDefined();
60
60
  expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqFR3')).toBeDefined();
61
61
  expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqCDR3')).toBeDefined();
62
- expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqFR4')).toBeDefined();
62
+ expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqFR4InFrame')).toBeDefined();
63
63
  expect(config.columnsSpec.find((c: any) => c.column === 'topChains')).toBeDefined();
64
64
  }
65
65
  },
@@ -96,7 +96,7 @@ const testCases: TestCase[] = [
96
96
  expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqCDR2')).toBeDefined();
97
97
  expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqFR3')).toBeDefined();
98
98
  expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqCDR3')).toBeDefined();
99
- expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqFR4')).toBeDefined();
99
+ expect(config.columnsSpec.find((c: any) => c.column === 'aaSeqFR4InFrame')).toBeDefined();
100
100
  expect(config.columnsSpec.find((c: any) => c.column === 'isotypePrimary')).toBeDefined();
101
101
  }
102
102
  },
@@ -1,21 +0,0 @@
1
-  WARN  Issue while reading "/home/runner/work/mixcr-clonotyping/mixcr-clonotyping/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
-
3
- > @platforma-open/milaboratories.mixcr-clonotyping-2.workflow@2.17.0 test /home/runner/work/mixcr-clonotyping/mixcr-clonotyping/workflow
4
- > vitest
5
-
6
-
7
-  RUN  v2.1.8 /home/runner/work/mixcr-clonotyping/mixcr-clonotyping/workflow
8
-
9
- ✓ src/test/columns.test.ts (6 tests) 52852ms
10
- ✓ checking preset for 'milab-human-dna-xcr-7genes-multiplex' 13107ms
11
- ✓ checking preset for '10x-sc-xcr-vdj' 8442ms
12
- ✓ checking preset for 'cellecta-human-rna-xcr-umi-drivermap-…' 7979ms
13
- ✓ checking preset for 'takara-human-rna-bcr-umi-smartseq' 7987ms
14
- ✓ checking preset for 'rna-seq' 7550ms
15
- ✓ checking preset for 'generic-single-cell-gex' 7787ms
16
-
17
-  Test Files  1 passed (1)
18
-  Tests  6 passed (6)
19
-  Start at  15:43:08
20
-  Duration  53.65s (transform 43ms, setup 0ms, collect 567ms, tests 52.85s, environment 0ms, prepare 58ms)
21
-