@platforma-open/milaboratories.mixcr-clonotyping-2.workflow 3.26.3 → 3.27.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +6 -0
- package/dist/tengo/lib/calculate-export-specs.lib.tengo +103 -5
- package/dist/tengo/tpl/aggregate-by-clonotype-key.plj.gz +0 -0
- package/dist/tengo/tpl/calculate-preset-info.plj.gz +0 -0
- package/dist/tengo/tpl/export-report.plj.gz +0 -0
- package/dist/tengo/tpl/list-presets.plj.gz +0 -0
- package/dist/tengo/tpl/main.plj.gz +0 -0
- package/dist/tengo/tpl/mixcr-analyze.plj.gz +0 -0
- package/dist/tengo/tpl/mixcr-export.plj.gz +0 -0
- package/dist/tengo/tpl/prerun.plj.gz +0 -0
- package/dist/tengo/tpl/process-single-cell.plj.gz +0 -0
- package/dist/tengo/tpl/process.plj.gz +0 -0
- package/dist/tengo/tpl/test.columns-calculate.plj.gz +0 -0
- package/dist/tengo/tpl/test.columns.test.plj.gz +0 -0
- package/package.json +2 -2
- package/src/calculate-export-specs.lib.tengo +103 -5
- package/src/main.tpl.tengo +1 -0
- package/src/process.tpl.tengo +1 -1
- package/src/test/columns-calculate.tpl.tengo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -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@3.
|
|
3
|
+
> @platforma-open/milaboratories.mixcr-clonotyping-2.workflow@3.27.0 build /home/runner/work/mixcr-clonotyping/mixcr-clonotyping/workflow
|
|
4
4
|
> shx 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,11 @@
|
|
|
1
1
|
# @platforma-open/milaboratories.mixcr-clonotyping.workflow
|
|
2
2
|
|
|
3
|
+
## 3.27.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1ed23a7: Add "Impute non-covered parts from germline" option for generic amplicon presets. When enabled, the block exports additional germline-imputed sequence columns for the gene-feature regions outside the assembling feature span (plus the full VDJRegion), reconstructing non-covered parts from the assigned V/J germline. Imputed sequences are not used for clonotype assembly. The option is shown only for presets that expose "Assemble clones by".
|
|
8
|
+
|
|
3
9
|
## 3.26.3
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -105,6 +105,71 @@ formatId := func(input) {
|
|
|
105
105
|
return result
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
germlineRegionOrder := ["FR1", "CDR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
|
|
111
|
+
|
|
112
|
+
germlineRegionIndex := func(region) {
|
|
113
|
+
for i, r in germlineRegionOrder {
|
|
114
|
+
if r == region {
|
|
115
|
+
return i
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return -1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
imputedFlankFeatures := func(assembleClonesBy) {
|
|
130
|
+
if is_undefined(assembleClonesBy) || assembleClonesBy == "CDR3" || assembleClonesBy == "VDJRegion" {
|
|
131
|
+
return []
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
begin := undefined
|
|
135
|
+
end := undefined
|
|
136
|
+
|
|
137
|
+
toParts := text.split(assembleClonesBy, "_TO_")
|
|
138
|
+
if len(toParts) == 2 {
|
|
139
|
+
begin = toParts[0]
|
|
140
|
+
end = toParts[1]
|
|
141
|
+
} else if text.has_prefix(assembleClonesBy, "{") && text.has_suffix(assembleClonesBy, "}") {
|
|
142
|
+
inner := assembleClonesBy[1:len(assembleClonesBy) - 1]
|
|
143
|
+
colonParts := text.split(inner, ":")
|
|
144
|
+
if len(colonParts) != 2 {
|
|
145
|
+
return []
|
|
146
|
+
}
|
|
147
|
+
begin = text.replace(colonParts[0], "Begin", "", -1)
|
|
148
|
+
end = text.replace(colonParts[1], "End", "", -1)
|
|
149
|
+
} else {
|
|
150
|
+
return []
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
iBegin := germlineRegionIndex(begin)
|
|
154
|
+
iEnd := germlineRegionIndex(end)
|
|
155
|
+
if iBegin == -1 || iEnd == -1 || iBegin > iEnd {
|
|
156
|
+
return []
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
imputed := []
|
|
160
|
+
for i := 0; i < iBegin; i++ {
|
|
161
|
+
imputed = append(imputed, germlineRegionOrder[i])
|
|
162
|
+
}
|
|
163
|
+
for i := iEnd + 1; i < len(germlineRegionOrder); i++ {
|
|
164
|
+
imputed = append(imputed, germlineRegionOrder[i])
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if !(begin == "FR1" && end == "FR4") {
|
|
168
|
+
imputed = append(imputed, "VDJRegion")
|
|
169
|
+
}
|
|
170
|
+
return imputed
|
|
171
|
+
}
|
|
172
|
+
|
|
108
173
|
exportSpecOpsFromPreset := func(presetSpecForBack) {
|
|
109
174
|
assemblingFeature := undefined
|
|
110
175
|
if !is_undefined(presetSpecForBack.assemblingFeature) {
|
|
@@ -129,7 +194,7 @@ addSpec := func(columns, additionalSpec) {
|
|
|
129
194
|
|
|
130
195
|
|
|
131
196
|
|
|
132
|
-
calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, exportMinQuality) {
|
|
197
|
+
calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, exportMinQuality, imputeGermline, assembleClonesBy) {
|
|
133
198
|
ops := exportSpecOpsFromPreset(presetSpecForBack)
|
|
134
199
|
|
|
135
200
|
assemblingFeature := ops.assemblingFeature
|
|
@@ -137,11 +202,14 @@ calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, expor
|
|
|
137
202
|
cellTags := ops.cellTags
|
|
138
203
|
hasUmi := ops.hasUmi
|
|
139
204
|
splitByC := ops.splitByC
|
|
140
|
-
|
|
205
|
+
|
|
141
206
|
|
|
142
207
|
if is_undefined(exportMinQuality) {
|
|
143
208
|
exportMinQuality = false
|
|
144
209
|
}
|
|
210
|
+
if is_undefined(imputeGermline) {
|
|
211
|
+
imputeGermline = false
|
|
212
|
+
}
|
|
145
213
|
|
|
146
214
|
isSingleCell := !is_undefined(cellTags) && len(cellTags) > 0
|
|
147
215
|
hashCellKey := isSingleCell && len(cellTags) > 1
|
|
@@ -496,10 +564,40 @@ calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, expor
|
|
|
496
564
|
|
|
497
565
|
annotationTypes := assemblingFeature == "CDR3" ? ["Segments"] : ["CDRs", "Segments"]
|
|
498
566
|
|
|
499
|
-
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
imputedFeatures := imputeGermline ? imputedFlankFeatures(assembleClonesBy) : []
|
|
574
|
+
doImpute := len(imputedFeatures) > 0
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
if doImpute {
|
|
583
|
+
imputedSet := {}
|
|
584
|
+
for f in imputedFeatures {
|
|
585
|
+
imputedSet[f] = true
|
|
586
|
+
}
|
|
587
|
+
nonImputedFeatures := []
|
|
588
|
+
for f in features {
|
|
589
|
+
if is_undefined(imputedSet[f]) {
|
|
590
|
+
nonImputedFeatures = append(nonImputedFeatures, f)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
features = nonImputedFeatures
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
for isImputed in ( doImpute ? [false, true] : [false] ) {
|
|
500
597
|
imputedU := isImputed ? "Imputed" : ""
|
|
501
598
|
imputedL := text.to_lower(imputedU)
|
|
502
|
-
|
|
599
|
+
featuresList := isImputed ? imputedFeatures : features
|
|
600
|
+
for featureU in featuresList {
|
|
503
601
|
featureL := text.to_lower(formatId(featureU))
|
|
504
602
|
for isAminoAcid in [true, false] {
|
|
505
603
|
featureInFrameU := isAminoAcid ? inFrameFeatures[featureU] : featureU
|
|
@@ -539,7 +637,7 @@ calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, expor
|
|
|
539
637
|
"pl7.app/vdj/isMainSequence": featureU == anchorFeature ? "true" : "false",
|
|
540
638
|
"pl7.app/vdj/imputed": string(isImputed),
|
|
541
639
|
"pl7.app/table/fontFamily": "monospace",
|
|
542
|
-
"pl7.app/label": featureInFrameU + " " + alphabetShort
|
|
640
|
+
"pl7.app/label": (isImputed ? "Imputed " : "") + featureInFrameU + " " + alphabetShort
|
|
543
641
|
})
|
|
544
642
|
}
|
|
545
643
|
} ]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platforma-open/milaboratories.mixcr-clonotyping-2.workflow",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.27.0",
|
|
4
4
|
"description": "Tengo-based template",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@platforma-sdk/workflow-tengo": "5.12.0",
|
|
7
7
|
"@platforma-open/milaboratories.software-mixcr": "4.7.0-347-develop"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
|
-
"@platforma-sdk/tengo-builder": "
|
|
10
|
+
"@platforma-sdk/tengo-builder": "4.0.8"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "shx rm -rf dist && pl-tengo check && pl-tengo build",
|
|
@@ -105,6 +105,71 @@ formatId := func(input) {
|
|
|
105
105
|
return result
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Gene-feature regions in 5'->3' order, used to derive which regions fall outside an
|
|
109
|
+
// assembled span.
|
|
110
|
+
germlineRegionOrder := ["FR1", "CDR1", "FR2", "CDR2", "FR3", "CDR3", "FR4"]
|
|
111
|
+
|
|
112
|
+
germlineRegionIndex := func(region) {
|
|
113
|
+
for i, r in germlineRegionOrder {
|
|
114
|
+
if r == region {
|
|
115
|
+
return i
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return -1
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Derives the gene-feature regions lying OUTSIDE the assembled span (5' and 3' flanks),
|
|
122
|
+
// plus the full VDJRegion, for germline imputation. Mirrors the mixcr-amplicon-alignment
|
|
123
|
+
// block's parseAssemblingFeature, adapted to the "Assemble clones by" value formats:
|
|
124
|
+
// CDR3, VDJRegion -> nothing to impute (minimal / full feature)
|
|
125
|
+
// <X>_TO_<Y> -> span X..Y (e.g. CDR1_TO_FR4)
|
|
126
|
+
// {<X>Begin:<Y>End} -> span X..Y (e.g. {FR1Begin:CDR3End})
|
|
127
|
+
// The assembled span itself is observed, not imputed, so it never gets a duplicate
|
|
128
|
+
// imputed column. Returns [] when there is nothing to impute or the value is unrecognised.
|
|
129
|
+
imputedFlankFeatures := func(assembleClonesBy) {
|
|
130
|
+
if is_undefined(assembleClonesBy) || assembleClonesBy == "CDR3" || assembleClonesBy == "VDJRegion" {
|
|
131
|
+
return []
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
begin := undefined
|
|
135
|
+
end := undefined
|
|
136
|
+
|
|
137
|
+
toParts := text.split(assembleClonesBy, "_TO_")
|
|
138
|
+
if len(toParts) == 2 {
|
|
139
|
+
begin = toParts[0]
|
|
140
|
+
end = toParts[1]
|
|
141
|
+
} else if text.has_prefix(assembleClonesBy, "{") && text.has_suffix(assembleClonesBy, "}") {
|
|
142
|
+
inner := assembleClonesBy[1:len(assembleClonesBy) - 1]
|
|
143
|
+
colonParts := text.split(inner, ":")
|
|
144
|
+
if len(colonParts) != 2 {
|
|
145
|
+
return []
|
|
146
|
+
}
|
|
147
|
+
begin = text.replace(colonParts[0], "Begin", "", -1)
|
|
148
|
+
end = text.replace(colonParts[1], "End", "", -1)
|
|
149
|
+
} else {
|
|
150
|
+
return []
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
iBegin := germlineRegionIndex(begin)
|
|
154
|
+
iEnd := germlineRegionIndex(end)
|
|
155
|
+
if iBegin == -1 || iEnd == -1 || iBegin > iEnd {
|
|
156
|
+
return []
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
imputed := []
|
|
160
|
+
for i := 0; i < iBegin; i++ {
|
|
161
|
+
imputed = append(imputed, germlineRegionOrder[i])
|
|
162
|
+
}
|
|
163
|
+
for i := iEnd + 1; i < len(germlineRegionOrder); i++ {
|
|
164
|
+
imputed = append(imputed, germlineRegionOrder[i])
|
|
165
|
+
}
|
|
166
|
+
// VDJRegion is the full FR1..FR4; impute it unless the span already covers everything.
|
|
167
|
+
if !(begin == "FR1" && end == "FR4") {
|
|
168
|
+
imputed = append(imputed, "VDJRegion")
|
|
169
|
+
}
|
|
170
|
+
return imputed
|
|
171
|
+
}
|
|
172
|
+
|
|
108
173
|
exportSpecOpsFromPreset := func(presetSpecForBack) {
|
|
109
174
|
assemblingFeature := undefined
|
|
110
175
|
if !is_undefined(presetSpecForBack.assemblingFeature) {
|
|
@@ -129,7 +194,7 @@ addSpec := func(columns, additionalSpec) {
|
|
|
129
194
|
// Ordering rules
|
|
130
195
|
// AA Sequences
|
|
131
196
|
|
|
132
|
-
calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, exportMinQuality) {
|
|
197
|
+
calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, exportMinQuality, imputeGermline, assembleClonesBy) {
|
|
133
198
|
ops := exportSpecOpsFromPreset(presetSpecForBack)
|
|
134
199
|
|
|
135
200
|
assemblingFeature := ops.assemblingFeature
|
|
@@ -137,11 +202,14 @@ calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, expor
|
|
|
137
202
|
cellTags := ops.cellTags
|
|
138
203
|
hasUmi := ops.hasUmi
|
|
139
204
|
splitByC := ops.splitByC
|
|
140
|
-
|
|
205
|
+
|
|
141
206
|
// Default to false if not provided
|
|
142
207
|
if is_undefined(exportMinQuality) {
|
|
143
208
|
exportMinQuality = false
|
|
144
209
|
}
|
|
210
|
+
if is_undefined(imputeGermline) {
|
|
211
|
+
imputeGermline = false
|
|
212
|
+
}
|
|
145
213
|
|
|
146
214
|
isSingleCell := !is_undefined(cellTags) && len(cellTags) > 0
|
|
147
215
|
hashCellKey := isSingleCell && len(cellTags) > 1
|
|
@@ -496,10 +564,40 @@ calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, expor
|
|
|
496
564
|
// aaAnnotationOfSegmentsForVDJRegionInFrame
|
|
497
565
|
annotationTypes := assemblingFeature == "CDR3" ? ["Segments"] : ["CDRs", "Segments"]
|
|
498
566
|
|
|
499
|
-
|
|
567
|
+
// Germline imputation (generic amplicon presets only): when enabled, emit additional
|
|
568
|
+
// germline-imputed sequence columns for the gene-feature regions OUTSIDE the assembled
|
|
569
|
+
// span (plus the full VDJRegion). imputedFlankFeatures returns [] for non-generic presets
|
|
570
|
+
// (assembleClonesBy undefined) and for full/minimal spans, so this never fires outside the
|
|
571
|
+
// intended scope. The clonotype key, built from the non-imputed assembling feature, is
|
|
572
|
+
// unaffected.
|
|
573
|
+
imputedFeatures := imputeGermline ? imputedFlankFeatures(assembleClonesBy) : []
|
|
574
|
+
doImpute := len(imputedFeatures) > 0
|
|
575
|
+
|
|
576
|
+
// Keep the imputed and non-imputed feature sets disjoint (as mixcr-amplicon-alignment does).
|
|
577
|
+
// A flank feature (e.g. FR1) lives in the fixed non-imputed `features` list AND in the imputed
|
|
578
|
+
// set; emitting both yields two columns with identical PColumn identity (name + domain - the
|
|
579
|
+
// `imputed` flag is annotation-only, so it does not disambiguate). The empty non-imputed column
|
|
580
|
+
// (region_not_covered) then shadows the populated germline-imputed one. So when imputing, drop
|
|
581
|
+
// those features from the non-imputed list - only the imputed column remains for the flanks.
|
|
582
|
+
if doImpute {
|
|
583
|
+
imputedSet := {}
|
|
584
|
+
for f in imputedFeatures {
|
|
585
|
+
imputedSet[f] = true
|
|
586
|
+
}
|
|
587
|
+
nonImputedFeatures := []
|
|
588
|
+
for f in features {
|
|
589
|
+
if is_undefined(imputedSet[f]) {
|
|
590
|
+
nonImputedFeatures = append(nonImputedFeatures, f)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
features = nonImputedFeatures
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
for isImputed in ( doImpute ? [false, true] : [false] ) {
|
|
500
597
|
imputedU := isImputed ? "Imputed" : ""
|
|
501
598
|
imputedL := text.to_lower(imputedU)
|
|
502
|
-
|
|
599
|
+
featuresList := isImputed ? imputedFeatures : features
|
|
600
|
+
for featureU in featuresList {
|
|
503
601
|
featureL := text.to_lower(formatId(featureU))
|
|
504
602
|
for isAminoAcid in [true, false] {
|
|
505
603
|
featureInFrameU := isAminoAcid ? inFrameFeatures[featureU] : featureU
|
|
@@ -539,7 +637,7 @@ calculateExportSpecs := func(presetSpecForBack, sampleIdAxisSpec, blockId, expor
|
|
|
539
637
|
"pl7.app/vdj/isMainSequence": featureU == anchorFeature ? "true" : "false",
|
|
540
638
|
"pl7.app/vdj/imputed": string(isImputed),
|
|
541
639
|
"pl7.app/table/fontFamily": "monospace",
|
|
542
|
-
"pl7.app/label": featureInFrameU + " " + alphabetShort
|
|
640
|
+
"pl7.app/label": (isImputed ? "Imputed " : "") + featureInFrameU + " " + alphabetShort
|
|
543
641
|
})
|
|
544
642
|
}
|
|
545
643
|
} ]
|
package/src/main.tpl.tengo
CHANGED
|
@@ -134,6 +134,7 @@ wf.body(func(args) {
|
|
|
134
134
|
materialType: args.materialType,
|
|
135
135
|
tagPattern: args.tagPattern,
|
|
136
136
|
assembleClonesBy: args.assembleClonesBy,
|
|
137
|
+
imputeGermline: args.imputeGermline,
|
|
137
138
|
exportMinQuality: args.exportMinQuality,
|
|
138
139
|
stopCodonTypes: args.stopCodonTypes,
|
|
139
140
|
stopCodonReplacements: args.stopCodonReplacements
|
package/src/process.tpl.tengo
CHANGED
|
@@ -203,7 +203,7 @@ self.body(func(inputs) {
|
|
|
203
203
|
}
|
|
204
204
|
} ]
|
|
205
205
|
|
|
206
|
-
exportSpecs := calculateExportSpecs(presetSpecForBack, sampleIdAxisSpec, blockId, params.exportMinQuality)
|
|
206
|
+
exportSpecs := calculateExportSpecs(presetSpecForBack, sampleIdAxisSpec, blockId, params.exportMinQuality, params.imputeGermline, params.assembleClonesBy)
|
|
207
207
|
|
|
208
208
|
columnsSpecPerSample := exportSpecs.columnsSpecPerSample
|
|
209
209
|
columnsSpecPerSampleSc := exportSpecs.columnsSpecPerSampleSc
|
|
@@ -9,7 +9,7 @@ self.defineOutputs("exportSpecs")
|
|
|
9
9
|
self.body(func(inputs) {
|
|
10
10
|
presetSpecForBack := inputs.presetSpecForBack.getDataAsJson()
|
|
11
11
|
|
|
12
|
-
exportSpecs := calculateExportSpecs(presetSpecForBack, {}, "test", false)
|
|
12
|
+
exportSpecs := calculateExportSpecs(presetSpecForBack, {}, "test", false, false, undefined)
|
|
13
13
|
exportSpecs = maps.deepMerge(exportSpecs, {
|
|
14
14
|
axisByClonotypeKeyGen: undefined,
|
|
15
15
|
axisByScClonotypeKeyGen: undefined,
|