@apollo-annotation/shared 0.1.19 → 0.1.20
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/dist/Changes/AddAssemblyAndFeaturesFromFileChange.d.ts +33 -0
- package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.js +83 -0
- package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.js.map +1 -0
- package/dist/Changes/AddAssemblyFromExternalChange.d.ts +34 -0
- package/dist/Changes/AddAssemblyFromExternalChange.js +97 -0
- package/dist/Changes/AddAssemblyFromExternalChange.js.map +1 -0
- package/dist/Changes/AddAssemblyFromFileChange.d.ts +43 -0
- package/dist/Changes/AddAssemblyFromFileChange.js +140 -0
- package/dist/Changes/AddAssemblyFromFileChange.js.map +1 -0
- package/dist/Changes/AddFeatureChange.d.ts +35 -0
- package/dist/Changes/AddFeatureChange.js +169 -0
- package/dist/Changes/AddFeatureChange.js.map +1 -0
- package/dist/Changes/AddFeaturesFromFileChange.d.ts +32 -0
- package/dist/Changes/AddFeaturesFromFileChange.js +76 -0
- package/dist/Changes/AddFeaturesFromFileChange.js.map +1 -0
- package/dist/Changes/AddRefSeqAliasesChange.d.ts +20 -0
- package/dist/Changes/AddRefSeqAliasesChange.js +61 -0
- package/dist/Changes/AddRefSeqAliasesChange.js.map +1 -0
- package/dist/Changes/DeleteAssemblyChange.d.ts +18 -0
- package/dist/Changes/DeleteAssemblyChange.js +72 -0
- package/dist/Changes/DeleteAssemblyChange.js.map +1 -0
- package/dist/Changes/DeleteFeatureChange.d.ts +42 -0
- package/dist/Changes/DeleteFeatureChange.js +143 -0
- package/dist/Changes/DeleteFeatureChange.js.map +1 -0
- package/dist/Changes/DeleteUserChange.d.ts +25 -0
- package/dist/Changes/DeleteUserChange.js +53 -0
- package/dist/Changes/DeleteUserChange.js.map +1 -0
- package/dist/Changes/FeatureAttributeChange.d.ts +31 -0
- package/dist/Changes/FeatureAttributeChange.js +112 -0
- package/dist/Changes/FeatureAttributeChange.js.map +1 -0
- package/dist/Changes/FromFileBaseChange.d.ts +9 -0
- package/dist/Changes/FromFileBaseChange.js +187 -0
- package/dist/Changes/FromFileBaseChange.js.map +1 -0
- package/dist/Changes/ImportJBrowseConfigChange.d.ts +43 -0
- package/dist/Changes/ImportJBrowseConfigChange.js +67 -0
- package/dist/Changes/ImportJBrowseConfigChange.js.map +1 -0
- package/dist/Changes/LocationEndChange.d.ts +32 -0
- package/dist/Changes/LocationEndChange.js +129 -0
- package/dist/Changes/LocationEndChange.js.map +1 -0
- package/dist/Changes/LocationStartChange.d.ts +32 -0
- package/dist/Changes/LocationStartChange.js +129 -0
- package/dist/Changes/LocationStartChange.js.map +1 -0
- package/dist/Changes/StrandChange.d.ts +31 -0
- package/dist/Changes/StrandChange.js +114 -0
- package/dist/Changes/StrandChange.js.map +1 -0
- package/dist/Changes/TypeChange.d.ts +31 -0
- package/dist/Changes/TypeChange.js +114 -0
- package/dist/Changes/TypeChange.js.map +1 -0
- package/dist/Changes/UserChange.d.ts +25 -0
- package/dist/Changes/UserChange.js +51 -0
- package/dist/Changes/UserChange.js.map +1 -0
- package/dist/Changes/index.d.ts +50 -0
- package/dist/Changes/index.js +55 -0
- package/dist/Changes/index.js.map +1 -0
- package/dist/Checks/CDSCheck.d.ts +8 -0
- package/dist/Checks/CDSCheck.js +197 -0
- package/dist/Checks/CDSCheck.js.map +1 -0
- package/dist/Checks/index.d.ts +1 -0
- package/dist/Checks/index.js +5 -0
- package/dist/Checks/index.js.map +1 -0
- package/dist/Common/index.d.ts +1 -0
- package/dist/Common/index.js +5 -0
- package/dist/Common/index.js.map +1 -0
- package/dist/Common/jwtPayload.d.ts +12 -0
- package/dist/Common/jwtPayload.js +16 -0
- package/dist/Common/jwtPayload.js.map +1 -0
- package/dist/GFF3/gff3ToAnnotationFeature.d.ts +3 -0
- package/dist/GFF3/gff3ToAnnotationFeature.js +211 -0
- package/dist/GFF3/gff3ToAnnotationFeature.js.map +1 -0
- package/dist/GFF3/gff3ToAnnotationFeature.test.d.ts +1 -0
- package/dist/GFF3/gff3ToAnnotationFeature.test.js +174 -0
- package/dist/GFF3/gff3ToAnnotationFeature.test.js.map +1 -0
- package/dist/GFF3/gffReservedKeys.d.ts +5 -0
- package/dist/GFF3/gffReservedKeys.js +34 -0
- package/dist/GFF3/gffReservedKeys.js.map +1 -0
- package/dist/GFF3/index.d.ts +2 -0
- package/dist/GFF3/index.js +6 -0
- package/dist/GFF3/index.js.map +1 -0
- package/dist/Messages.d.ts +28 -0
- package/dist/Messages.js +3 -0
- package/dist/Messages.js.map +1 -0
- package/dist/Operations/GetAssembliesOperation.d.ts +13 -0
- package/dist/Operations/GetAssembliesOperation.js +20 -0
- package/dist/Operations/GetAssembliesOperation.js.map +1 -0
- package/dist/Operations/GetFeaturesOperation.d.ts +26 -0
- package/dist/Operations/GetFeaturesOperation.js +42 -0
- package/dist/Operations/GetFeaturesOperation.js.map +1 -0
- package/dist/Operations/index.d.ts +8 -0
- package/dist/Operations/index.js +10 -0
- package/dist/Operations/index.js.map +1 -0
- package/dist/Validations/CoreValidation.d.ts +17 -0
- package/dist/Validations/CoreValidation.js +36 -0
- package/dist/Validations/CoreValidation.js.map +1 -0
- package/dist/Validations/ParentChildValidation.d.ts +17 -0
- package/dist/Validations/ParentChildValidation.js +61 -0
- package/dist/Validations/ParentChildValidation.js.map +1 -0
- package/dist/Validations/Validation.d.ts +27 -0
- package/dist/Validations/Validation.js +27 -0
- package/dist/Validations/Validation.js.map +1 -0
- package/dist/Validations/ValidationSet.d.ts +24 -0
- package/dist/Validations/ValidationSet.js +86 -0
- package/dist/Validations/ValidationSet.js.map +1 -0
- package/dist/Validations/index.d.ts +4 -0
- package/dist/Validations/index.js +8 -0
- package/dist/Validations/index.js.map +1 -0
- package/dist/Validations/soSequenceTypes.d.ts +2 -0
- package/dist/Validations/soSequenceTypes.js +1868 -0
- package/dist/Validations/soSequenceTypes.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/util.d.ts +4 -0
- package/dist/util.js +102 -0
- package/dist/util.js.map +1 -0
- package/package.json +8 -5
- package/src/Changes/AddAssemblyAndFeaturesFromFileChange.ts +6 -5
- package/src/Changes/AddAssemblyFromFileChange.ts +126 -41
- package/src/Checks/CDSCheck.ts +202 -252
- package/src/GFF3/gff3ToAnnotationFeature.test.ts +165 -5
- package/src/GFF3/gff3ToAnnotationFeature.ts +16 -16
- package/src/util.ts +1 -0
- package/test_data/braker.gff +13 -0
- package/test_data/example01.gff3 +26 -0
- package/test_data/example01.json +236 -0
- package/test_data/example02.json +225 -0
- package/test_data/example04.json +225 -0
- package/test_data/example05.gff3 +30 -0
- package/test_data/example06.gff3 +30 -0
- package/test_data/example07.gff3 +30 -0
- package/test_data/gene_mrna.gff3 +4 -0
- package/test_data/gene_representations.gff3 +1781 -0
- package/test_data/one_cds.gff3 +9 -0
- package/test_data/one_cds.json +67 -0
- package/test_data/two_cds.gff3 +9 -0
- package/test_data/two_cds.json +67 -0
package/src/Checks/CDSCheck.ts
CHANGED
|
@@ -3,225 +3,203 @@ import {
|
|
|
3
3
|
AnnotationFeatureSnapshot,
|
|
4
4
|
CheckResultSnapshot,
|
|
5
5
|
} from '@apollo-annotation/mst'
|
|
6
|
-
|
|
6
|
+
import { intersection2 } from '@jbrowse/core/util'
|
|
7
|
+
import ObjectID from 'bson-objectid'
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
enum STOP_CODONS {
|
|
10
|
+
'TAG',
|
|
11
|
+
'TAA',
|
|
12
|
+
'TGA',
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
const iupacComplements: Record<string, string | undefined> = {
|
|
16
|
+
G: 'C',
|
|
17
|
+
A: 'T',
|
|
18
|
+
T: 'A',
|
|
19
|
+
C: 'G',
|
|
20
|
+
R /* G or A */: 'Y',
|
|
21
|
+
Y /* T or C */: 'R',
|
|
22
|
+
M /* A or C */: 'K',
|
|
23
|
+
K /* G or T */: 'M',
|
|
24
|
+
S /* G or C */: 'S',
|
|
25
|
+
W /* A or T */: 'W',
|
|
26
|
+
H /* A or C or T */: 'D',
|
|
27
|
+
B /* G or T or C */: 'V',
|
|
28
|
+
V /* G or C or A */: 'B',
|
|
29
|
+
D /* G or A or T */: 'H',
|
|
30
|
+
N /* G or A or T or C */: 'N',
|
|
31
|
+
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// for (const nt of dna) {
|
|
35
|
-
// const rc = iupacComplements[nt.toUpperCase()]
|
|
36
|
-
// if (rc === undefined) {
|
|
37
|
-
// throw new TypeError(`Cannot complement nucleotide: "${nt}"`)
|
|
38
|
-
// }
|
|
39
|
-
// if (nt === nt.toLowerCase()) {
|
|
40
|
-
// complement.push(rc.toLowerCase())
|
|
41
|
-
// } else {
|
|
42
|
-
// complement.push(rc)
|
|
43
|
-
// }
|
|
44
|
-
// }
|
|
45
|
-
// return complement.reverse().join('')
|
|
46
|
-
// }
|
|
33
|
+
type CDSLocation = { min: number; max: number; phase: 0 | 1 | 2 }[]
|
|
34
|
+
type CDSLocations = CDSLocation[]
|
|
47
35
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// if (feature.strand === -1) {
|
|
64
|
-
// return reverseComplement(seq)
|
|
65
|
-
// }
|
|
66
|
-
// return seq
|
|
67
|
-
// }
|
|
36
|
+
function reverseComplement(dna: string): string {
|
|
37
|
+
const complement: string[] = []
|
|
38
|
+
for (const nt of dna) {
|
|
39
|
+
const rc = iupacComplements[nt.toUpperCase()]
|
|
40
|
+
if (rc === undefined) {
|
|
41
|
+
throw new TypeError(`Cannot complement nucleotide: "${nt}"`)
|
|
42
|
+
}
|
|
43
|
+
if (nt === nt.toLowerCase()) {
|
|
44
|
+
complement.push(rc.toLowerCase())
|
|
45
|
+
} else {
|
|
46
|
+
complement.push(rc)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return complement.reverse().join('')
|
|
50
|
+
}
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// for (const feature of features) {
|
|
83
|
-
// seq = seq + (await getSequence(feature.start, feature.end))
|
|
84
|
-
// }
|
|
85
|
-
// if (strands[0] === -1) {
|
|
86
|
-
// return reverseComplement(seq)
|
|
87
|
-
// }
|
|
88
|
-
// return seq
|
|
89
|
-
// }
|
|
52
|
+
async function getCDSSequence(
|
|
53
|
+
cdsLocation: CDSLocation,
|
|
54
|
+
strand: 1 | -1 | undefined,
|
|
55
|
+
getSequence: (start: number, end: number) => Promise<string>,
|
|
56
|
+
): Promise<string> {
|
|
57
|
+
const sequences = await Promise.all(
|
|
58
|
+
cdsLocation.map(({ max, min }) => getSequence(min, max)),
|
|
59
|
+
)
|
|
60
|
+
if (strand === -1) {
|
|
61
|
+
return sequences.map((seq) => reverseComplement(seq)).join('')
|
|
62
|
+
}
|
|
63
|
+
return sequences.join('')
|
|
64
|
+
}
|
|
90
65
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
66
|
+
function splitSequenceInCodons(cds: string): string[] {
|
|
67
|
+
const codons: string[] = []
|
|
68
|
+
for (let i = 0; i <= cds.length - 3; i += 3) {
|
|
69
|
+
codons.push(cds.slice(i, i + 3))
|
|
70
|
+
}
|
|
71
|
+
return codons
|
|
72
|
+
}
|
|
98
73
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// let startLocation: number | undefined = undefined,
|
|
107
|
-
// endLocation: number | undefined = undefined
|
|
108
|
-
// for (const f of feature) {
|
|
109
|
-
// const featureLength = f.end - f.start
|
|
110
|
-
// if (startLocation === undefined && featureLength > lengthToStart) {
|
|
111
|
-
// startLocation = f.start + lengthToStart
|
|
112
|
-
// } else {
|
|
113
|
-
// lengthToStart -= featureLength
|
|
114
|
-
// }
|
|
115
|
-
// if (endLocation === undefined && featureLength > lengthToEnd) {
|
|
116
|
-
// endLocation = f.start + lengthToEnd
|
|
117
|
-
// } else {
|
|
118
|
-
// lengthToEnd -= featureLength
|
|
119
|
-
// }
|
|
120
|
-
// if (startLocation !== undefined && endLocation !== undefined) {
|
|
121
|
-
// return [startLocation, endLocation]
|
|
122
|
-
// }
|
|
123
|
-
// }
|
|
124
|
-
// throw new Error('Could not determine original CDS location')
|
|
125
|
-
// } else {
|
|
126
|
-
// if (
|
|
127
|
-
// feature.discontinuousLocations !== undefined &&
|
|
128
|
-
// feature.discontinuousLocations.length > 0
|
|
129
|
-
// ) {
|
|
130
|
-
// let startLocation: number | undefined = undefined,
|
|
131
|
-
// endLocation: number | undefined = undefined
|
|
132
|
-
// for (const loc of feature.discontinuousLocations) {
|
|
133
|
-
// const locLength = loc.end - loc.start
|
|
134
|
-
// if (startLocation === undefined && locLength > lengthToStart) {
|
|
135
|
-
// startLocation = loc.start + lengthToStart
|
|
136
|
-
// } else {
|
|
137
|
-
// lengthToStart -= locLength
|
|
138
|
-
// }
|
|
139
|
-
// if (endLocation === undefined && locLength > lengthToEnd) {
|
|
140
|
-
// endLocation = loc.start + lengthToEnd
|
|
141
|
-
// } else {
|
|
142
|
-
// lengthToEnd -= locLength
|
|
143
|
-
// }
|
|
144
|
-
// if (startLocation !== undefined && endLocation !== undefined) {
|
|
145
|
-
// return [startLocation, endLocation]
|
|
146
|
-
// }
|
|
147
|
-
// }
|
|
148
|
-
// throw new Error('Could not determine original CDS location')
|
|
149
|
-
// } else {
|
|
150
|
-
// return [feature.start + lengthToStart, feature.start + lengthToEnd]
|
|
151
|
-
// }
|
|
152
|
-
// }
|
|
153
|
-
// }
|
|
74
|
+
function getOriginalCodonLocation(
|
|
75
|
+
cdsLocation: CDSLocation,
|
|
76
|
+
strand: 1 | -1 | undefined,
|
|
77
|
+
index: number,
|
|
78
|
+
): [number, number] | undefined {
|
|
79
|
+
let lengthToStart = index * 3
|
|
80
|
+
let lengthToEnd = lengthToStart + 3
|
|
154
81
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
82
|
+
let startLocation: number | undefined = undefined,
|
|
83
|
+
endLocation: number | undefined = undefined
|
|
84
|
+
for (const loc of cdsLocation) {
|
|
85
|
+
const locLength = loc.max - loc.min
|
|
86
|
+
if (startLocation === undefined && locLength > lengthToStart) {
|
|
87
|
+
startLocation = loc.min + lengthToStart
|
|
88
|
+
} else {
|
|
89
|
+
lengthToStart -= locLength
|
|
90
|
+
}
|
|
91
|
+
if (endLocation === undefined && locLength > lengthToEnd) {
|
|
92
|
+
endLocation = loc.min + lengthToEnd
|
|
93
|
+
} else {
|
|
94
|
+
lengthToEnd -= locLength
|
|
95
|
+
}
|
|
96
|
+
if (startLocation !== undefined && endLocation !== undefined) {
|
|
97
|
+
return [startLocation, endLocation]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function checkMRNA(
|
|
104
|
+
feature: AnnotationFeatureSnapshot,
|
|
105
|
+
getSequence: (start: number, end: number) => Promise<string>,
|
|
106
|
+
): Promise<CheckResultSnapshot[]> {
|
|
107
|
+
const checkResults: CheckResultSnapshot[] = []
|
|
108
|
+
const { _id, max, min, refSeq, strand } = feature
|
|
109
|
+
const cdsLocations = getCDSLocations(feature)
|
|
110
|
+
if (!cdsLocations) {
|
|
111
|
+
return checkResults
|
|
112
|
+
}
|
|
113
|
+
const ids = [_id]
|
|
114
|
+
for (const cdsLocation of cdsLocations) {
|
|
115
|
+
const sequence = await getCDSSequence(cdsLocation, strand, getSequence)
|
|
116
|
+
const codons = splitSequenceInCodons(sequence)
|
|
117
|
+
if (sequence.length % 3 === 0) {
|
|
118
|
+
const lastCodon = codons.pop() // Last codon is supposed to be a stop
|
|
119
|
+
if (lastCodon && !(lastCodon.toUpperCase() in STOP_CODONS)) {
|
|
120
|
+
checkResults.push({
|
|
121
|
+
_id: new ObjectID().toHexString(),
|
|
122
|
+
name: 'MissingStopCodonCheck',
|
|
123
|
+
ids,
|
|
124
|
+
refSeq: refSeq.toString(),
|
|
125
|
+
start: max,
|
|
126
|
+
end: max,
|
|
127
|
+
message: `Feature "${_id}" is missing a stop codon`,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
checkResults.push({
|
|
132
|
+
_id: new ObjectID().toHexString(),
|
|
133
|
+
name: 'MultipleOfThreeCheck',
|
|
134
|
+
ids,
|
|
135
|
+
refSeq: refSeq.toString(),
|
|
136
|
+
start: min,
|
|
137
|
+
end: max,
|
|
138
|
+
message: `The coding sequence for feature "${_id}" is not a multiple of three`,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
for (const [idx, codon] of codons.entries()) {
|
|
142
|
+
const location = getOriginalCodonLocation(cdsLocation, strand, idx)
|
|
143
|
+
if (location && codon.toUpperCase() in STOP_CODONS) {
|
|
144
|
+
const [codonStart, codonEnd] = location
|
|
145
|
+
checkResults.push({
|
|
146
|
+
_id: new ObjectID().toHexString(),
|
|
147
|
+
name: 'InternalStopCodonCheck',
|
|
148
|
+
ids,
|
|
149
|
+
refSeq: refSeq.toString(),
|
|
150
|
+
start: codonStart,
|
|
151
|
+
end: codonEnd,
|
|
152
|
+
message: `The coding sequence for feature "${_id}" has an internal stop codon`,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return checkResults
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getCDSLocations(
|
|
161
|
+
feature: AnnotationFeatureSnapshot,
|
|
162
|
+
): CDSLocations | undefined {
|
|
163
|
+
if (feature.type !== 'mRNA') {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
const { children, strand } = feature
|
|
167
|
+
if (!children) {
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
const cdsChildren = Object.values(children).filter(
|
|
171
|
+
(child) => child.type === 'CDS',
|
|
172
|
+
)
|
|
173
|
+
if (cdsChildren.length === 0) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
const cdsLocations: CDSLocations = []
|
|
177
|
+
for (const cds of cdsChildren) {
|
|
178
|
+
const { max: cdsMax, min: cdsMin } = cds
|
|
179
|
+
const locs: { min: number; max: number }[] = []
|
|
180
|
+
for (const child of Object.values(children)) {
|
|
181
|
+
if (child.type !== 'exon') {
|
|
182
|
+
continue
|
|
183
|
+
}
|
|
184
|
+
const [start, end] = intersection2(cdsMin, cdsMax, child.min, child.max)
|
|
185
|
+
if (start !== undefined && end !== undefined) {
|
|
186
|
+
locs.push({ min: start, max: end })
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
locs.sort(({ min: a }, { min: b }) => a - b)
|
|
190
|
+
if (strand === -1) {
|
|
191
|
+
locs.reverse()
|
|
192
|
+
}
|
|
193
|
+
let nextPhase: 0 | 1 | 2 = 0
|
|
194
|
+
const phasedLocs = locs.map((loc) => {
|
|
195
|
+
const phase = nextPhase
|
|
196
|
+
nextPhase = ((3 - ((loc.max - loc.min - phase + 3) % 3)) % 3) as 0 | 1 | 2
|
|
197
|
+
return { ...loc, phase }
|
|
198
|
+
})
|
|
199
|
+
cdsLocations.push(phasedLocs)
|
|
200
|
+
}
|
|
201
|
+
return cdsLocations
|
|
202
|
+
}
|
|
225
203
|
|
|
226
204
|
export class CDSCheck extends Check {
|
|
227
205
|
name = 'CDSCheck'
|
|
@@ -229,49 +207,21 @@ export class CDSCheck extends Check {
|
|
|
229
207
|
default = true
|
|
230
208
|
|
|
231
209
|
async checkFeature(
|
|
232
|
-
|
|
233
|
-
|
|
210
|
+
feature: AnnotationFeatureSnapshot,
|
|
211
|
+
getSequence: (start: number, end: number) => Promise<string>,
|
|
234
212
|
): Promise<CheckResultSnapshot[]> {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// return checkCDS(feature, getSequence)
|
|
239
|
-
// }
|
|
240
|
-
|
|
241
|
-
// if (!feature.children) {
|
|
242
|
-
// return []
|
|
243
|
-
// }
|
|
213
|
+
if (feature.type === 'mRNA') {
|
|
214
|
+
return checkMRNA(feature, getSequence)
|
|
215
|
+
}
|
|
244
216
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
// checkResults.push(...(await this.checkFeature(child, getSequence)))
|
|
249
|
-
// }
|
|
250
|
-
// return checkResults
|
|
251
|
-
// }
|
|
217
|
+
if (!feature.children) {
|
|
218
|
+
return []
|
|
219
|
+
}
|
|
252
220
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// }
|
|
259
|
-
// const cdsChildrenWithDiscontinuousLocations = cdsChildren.filter(
|
|
260
|
-
// (child) =>
|
|
261
|
-
// child.discontinuousLocations && child.discontinuousLocations.length > 0,
|
|
262
|
-
// )
|
|
263
|
-
// if (cdsChildrenWithDiscontinuousLocations.length === 0) {
|
|
264
|
-
// return checkCDS(cdsChildren, getSequence)
|
|
265
|
-
// }
|
|
266
|
-
// if (cdsChildrenWithDiscontinuousLocations.length === cdsChildren.length) {
|
|
267
|
-
// const checkResults: CheckResultSnapshot[] = []
|
|
268
|
-
// for (const child of cdsChildren) {
|
|
269
|
-
// checkResults.push(...(await this.checkFeature(child, getSequence)))
|
|
270
|
-
// }
|
|
271
|
-
// return checkResults
|
|
272
|
-
// }
|
|
273
|
-
// throw new Error(
|
|
274
|
-
// `Mix of CDS with and without discontinuous locations found in mRNA "${feature._id}"`,
|
|
275
|
-
// )
|
|
221
|
+
const checkResults: CheckResultSnapshot[] = []
|
|
222
|
+
for (const child of Object.values(feature.children)) {
|
|
223
|
+
checkResults.push(...(await this.checkFeature(child, getSequence)))
|
|
224
|
+
}
|
|
225
|
+
return checkResults
|
|
276
226
|
}
|
|
277
227
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
2
|
-
import { strict as assert } from 'node:assert'
|
|
3
2
|
import { describe, it } from 'node:test'
|
|
4
|
-
import
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
4
|
+
|
|
5
|
+
import gff, { GFF3Feature } from '@gmod/gff'
|
|
6
|
+
import { assert, use } from 'chai'
|
|
7
|
+
import chaiExclude from 'chai-exclude'
|
|
5
8
|
|
|
6
9
|
import { gff3ToAnnotationFeature } from './gff3ToAnnotationFeature'
|
|
7
10
|
import { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
8
11
|
|
|
12
|
+
use(chaiExclude)
|
|
13
|
+
|
|
9
14
|
const testCases: [string, string, AnnotationFeatureSnapshot][] = [
|
|
10
15
|
[
|
|
11
16
|
'a feature with no children',
|
|
@@ -23,18 +28,173 @@ const testCases: [string, string, AnnotationFeatureSnapshot][] = [
|
|
|
23
28
|
},
|
|
24
29
|
},
|
|
25
30
|
],
|
|
31
|
+
[
|
|
32
|
+
'a feature with two children',
|
|
33
|
+
`ctgA est EST_match 1050 3202 . + . ID=Match1;Name=agt830.5;Target=agt830.5 1 654
|
|
34
|
+
ctgA est match_part 1050 1500 . + . Parent=Match1;Name=agt830.5;Target=agt830.5 1 451
|
|
35
|
+
ctgA est match_part 3000 3202 . + . Parent=Match1;Name=agt830.5;Target=agt830.5 452 654
|
|
36
|
+
`,
|
|
37
|
+
{
|
|
38
|
+
_id: '66cf9fbb4e947fa2c27d3d6a',
|
|
39
|
+
refSeq: 'ctgA',
|
|
40
|
+
type: 'EST_match',
|
|
41
|
+
min: 1049,
|
|
42
|
+
max: 3202,
|
|
43
|
+
strand: 1,
|
|
44
|
+
children: {
|
|
45
|
+
'66cf9fbb4e947fa2c27d3d68': {
|
|
46
|
+
_id: '66cf9fbb4e947fa2c27d3d68',
|
|
47
|
+
refSeq: 'ctgA',
|
|
48
|
+
type: 'match_part',
|
|
49
|
+
min: 1049,
|
|
50
|
+
max: 1500,
|
|
51
|
+
strand: 1,
|
|
52
|
+
attributes: {
|
|
53
|
+
gff_source: ['est'],
|
|
54
|
+
gff_name: ['agt830.5'],
|
|
55
|
+
gff_target: ['agt830.5 1 451'],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
'66cf9fbb4e947fa2c27d3d69': {
|
|
59
|
+
_id: '66cf9fbb4e947fa2c27d3d69',
|
|
60
|
+
refSeq: 'ctgA',
|
|
61
|
+
type: 'match_part',
|
|
62
|
+
min: 2999,
|
|
63
|
+
max: 3202,
|
|
64
|
+
strand: 1,
|
|
65
|
+
attributes: {
|
|
66
|
+
gff_source: ['est'],
|
|
67
|
+
gff_name: ['agt830.5'],
|
|
68
|
+
gff_target: ['agt830.5 452 654'],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
attributes: {
|
|
73
|
+
gff_source: ['est'],
|
|
74
|
+
gff_id: ['Match1'],
|
|
75
|
+
gff_name: ['agt830.5'],
|
|
76
|
+
gff_target: ['agt830.5 1 654'],
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
26
80
|
]
|
|
27
81
|
|
|
82
|
+
interface AnnotationFeatureSnapshotWithChildrenArray
|
|
83
|
+
extends Omit<AnnotationFeatureSnapshot, 'children'> {
|
|
84
|
+
children?: AnnotationFeatureSnapshotWithChildrenArray[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function childrenToArray(
|
|
88
|
+
feature: AnnotationFeatureSnapshot,
|
|
89
|
+
): AnnotationFeatureSnapshotWithChildrenArray {
|
|
90
|
+
const { children } = feature
|
|
91
|
+
if (!children) {
|
|
92
|
+
return feature as AnnotationFeatureSnapshotWithChildrenArray
|
|
93
|
+
}
|
|
94
|
+
const childrenArray = Object.values(children).map((child) =>
|
|
95
|
+
childrenToArray(child),
|
|
96
|
+
)
|
|
97
|
+
return { ...feature, children: childrenArray }
|
|
98
|
+
}
|
|
99
|
+
|
|
28
100
|
function compareFeatures(
|
|
29
101
|
feature1: AnnotationFeatureSnapshot,
|
|
30
102
|
feature2: AnnotationFeatureSnapshot,
|
|
31
103
|
) {
|
|
32
|
-
assert.
|
|
33
|
-
|
|
34
|
-
|
|
104
|
+
assert.deepEqualExcludingEvery(
|
|
105
|
+
childrenToArray(feature1),
|
|
106
|
+
childrenToArray(feature2),
|
|
107
|
+
'_id',
|
|
35
108
|
)
|
|
36
109
|
}
|
|
37
110
|
|
|
111
|
+
function readFeatureFile(fn: string): GFF3Feature[] {
|
|
112
|
+
const lines = readFileSync(fn).toString().split('\n')
|
|
113
|
+
const feature: string[] = []
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
if (!line.startsWith('#')) {
|
|
116
|
+
feature.push(line)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const inGff = gff.parseStringSync(feature.join('\n')) as GFF3Feature[]
|
|
120
|
+
return inGff
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readAnnotationFeatureSnapshot(fn: string): AnnotationFeatureSnapshot {
|
|
124
|
+
const lines = readFileSync(fn).toString()
|
|
125
|
+
return JSON.parse(lines) as AnnotationFeatureSnapshot
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const [ex1, ex2, ex3, ex4] = readFeatureFile(
|
|
129
|
+
'test_data/gene_representations.gff3',
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
describe('gff3ToAnnotationFeature examples', () => {
|
|
133
|
+
it('Convert one CDS', () => {
|
|
134
|
+
const actual = gff3ToAnnotationFeature(
|
|
135
|
+
readFeatureFile('test_data/one_cds.gff3')[0],
|
|
136
|
+
)
|
|
137
|
+
const expected = readAnnotationFeatureSnapshot('test_data/one_cds.json')
|
|
138
|
+
compareFeatures(actual, expected)
|
|
139
|
+
})
|
|
140
|
+
it('Convert two CDSs', () => {
|
|
141
|
+
const actual = gff3ToAnnotationFeature(
|
|
142
|
+
readFeatureFile('test_data/two_cds.gff3')[0],
|
|
143
|
+
)
|
|
144
|
+
const expected = readAnnotationFeatureSnapshot('test_data/two_cds.json')
|
|
145
|
+
compareFeatures(actual, expected)
|
|
146
|
+
})
|
|
147
|
+
it('Convert example 1', () => {
|
|
148
|
+
const actual = gff3ToAnnotationFeature(ex1)
|
|
149
|
+
const txt = JSON.stringify(actual, null, 2)
|
|
150
|
+
|
|
151
|
+
assert.equal(txt.match(/"type": "CDS"/g)?.length, 4)
|
|
152
|
+
assert.equal(txt.match(/"type": "TF_binding_site"/g)?.length, 1)
|
|
153
|
+
|
|
154
|
+
const expected = readAnnotationFeatureSnapshot('test_data/example01.json')
|
|
155
|
+
compareFeatures(actual, expected)
|
|
156
|
+
})
|
|
157
|
+
it('Convert example 2', () => {
|
|
158
|
+
const actual = gff3ToAnnotationFeature(ex2)
|
|
159
|
+
const txt = JSON.stringify(actual, null, 2)
|
|
160
|
+
assert.equal(txt.match(/"type": "CDS"/g)?.length, 4)
|
|
161
|
+
const expected = readAnnotationFeatureSnapshot('test_data/example02.json')
|
|
162
|
+
compareFeatures(actual, expected)
|
|
163
|
+
})
|
|
164
|
+
it('Convert example 3', () => {
|
|
165
|
+
// NB: In example 3 (and in the other examples) mRNA10003 produces two proteins.
|
|
166
|
+
// In the other examples the two proteins are identified by sharing the same cds id.
|
|
167
|
+
// In example 3 instead each cds has a unique id so the two proteins are identified by the order they
|
|
168
|
+
// appear in the gff.
|
|
169
|
+
const actual = gff3ToAnnotationFeature(ex3)
|
|
170
|
+
const txt = JSON.stringify(actual, null, 2)
|
|
171
|
+
assert.equal(txt.match(/"type": "CDS"/g)?.length, 4)
|
|
172
|
+
|
|
173
|
+
// const expected = readAnnotationFeatureSnapshot('test_data/example03.json')
|
|
174
|
+
// compareFeatures(actual, expected)
|
|
175
|
+
})
|
|
176
|
+
it('Convert example 4', () => {
|
|
177
|
+
const ft = JSON.stringify(ex4, null, 2)
|
|
178
|
+
assert.equal(ft.match(/"type": "five_prime_UTR"/g)?.length, 6)
|
|
179
|
+
assert.equal(ft.match(/"type": "three_prime_UTR"/g)?.length, 3)
|
|
180
|
+
|
|
181
|
+
const actual = gff3ToAnnotationFeature(ex4)
|
|
182
|
+
const txt = JSON.stringify(actual, null, 2)
|
|
183
|
+
assert.equal(txt.match(/"type": "CDS"/g)?.length, 4)
|
|
184
|
+
assert.equal(txt.match(/prime_UTR/g), null)
|
|
185
|
+
|
|
186
|
+
const expected = readAnnotationFeatureSnapshot('test_data/example04.json')
|
|
187
|
+
compareFeatures(actual, expected)
|
|
188
|
+
})
|
|
189
|
+
it('Convert braker gff', () => {
|
|
190
|
+
const [gffFeature] = readFeatureFile('test_data/braker.gff')
|
|
191
|
+
const actual = gff3ToAnnotationFeature(gffFeature)
|
|
192
|
+
const txt = JSON.stringify(actual, null, 2)
|
|
193
|
+
assert.equal(txt.match(/intron/g), null)
|
|
194
|
+
assert.equal(txt.match(/_codon/g), null)
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
38
198
|
describe('gff3ToAnnotationFeature', () => {
|
|
39
199
|
for (const testCase of testCases) {
|
|
40
200
|
const [description, featureLine, convertedFeature] = testCase
|