@apollo-annotation/shared 0.3.8 → 0.3.10
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 +3 -0
- package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.js +5 -4
- package/dist/Changes/AddAssemblyAndFeaturesFromFileChange.js.map +1 -1
- package/dist/Changes/AddFeatureChange.js +23 -4
- package/dist/Changes/AddFeatureChange.js.map +1 -1
- package/dist/Changes/AddFeaturesFromFileChange.d.ts +3 -0
- package/dist/Changes/AddFeaturesFromFileChange.js +5 -2
- package/dist/Changes/AddFeaturesFromFileChange.js.map +1 -1
- package/dist/Changes/AddRefSeqAliasesChange.js +1 -4
- package/dist/Changes/AddRefSeqAliasesChange.js.map +1 -1
- package/dist/Changes/FromFileBaseChange.d.ts +2 -0
- package/dist/Changes/FromFileBaseChange.js +26 -11
- package/dist/Changes/FromFileBaseChange.js.map +1 -1
- package/dist/Checks/CDSCheck.js +5 -4
- package/dist/Checks/CDSCheck.js.map +1 -1
- package/dist/Checks/TranscriptCheck.js +12 -12
- package/dist/Checks/TranscriptCheck.js.map +1 -1
- package/dist/Checks/util.d.ts +2 -0
- package/dist/Checks/util.js +15 -0
- package/dist/Checks/util.js.map +1 -0
- package/dist/GFF3/annotationFeatureToGFF3.js +3 -1
- package/dist/GFF3/annotationFeatureToGFF3.js.map +1 -1
- package/dist/GFF3/annotationFeatureToGFF3.test.js +19 -5
- package/dist/GFF3/annotationFeatureToGFF3.test.js.map +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.d.ts +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.js +9 -12
- package/dist/GFF3/gff3ToAnnotationFeature.js.map +1 -1
- package/dist/GFF3/gff3ToAnnotationFeature.test.d.ts +1 -2
- package/dist/GFF3/gff3ToAnnotationFeature.test.js +25 -98
- package/dist/GFF3/gff3ToAnnotationFeature.test.js.map +1 -1
- package/dist/GFF3/testUtil.d.ts +6 -0
- package/dist/GFF3/testUtil.js +24 -0
- package/dist/GFF3/testUtil.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/Changes/AddAssemblyAndFeaturesFromFileChange.ts +6 -5
- package/src/Changes/AddFeatureChange.ts +26 -4
- package/src/Changes/AddFeaturesFromFileChange.ts +6 -1
- package/src/Changes/AddRefSeqAliasesChange.ts +1 -5
- package/src/Changes/FromFileBaseChange.ts +28 -16
- package/src/Checks/CDSCheck.ts +6 -4
- package/src/Checks/TranscriptCheck.ts +11 -12
- package/src/Checks/util.ts +13 -0
- package/src/GFF3/annotationFeatureToGFF3.test.ts +24 -2
- package/src/GFF3/annotationFeatureToGFF3.ts +4 -1
- package/src/GFF3/gff3ToAnnotationFeature.test.ts +26 -97
- package/src/GFF3/gff3ToAnnotationFeature.ts +7 -16
- package/src/GFF3/testUtil.ts +25 -0
- package/test_data/gene_with_two_cds.gff3 +6 -0
- package/test_data/gene_with_two_cds.json +69 -0
- package/test_data/single_feature_no_children.gff3 +1 -0
- package/test_data/single_feature_no_children.json +14 -0
- package/test_data/single_feature_two_children.gff3 +3 -0
- package/test_data/single_feature_two_children.json +44 -0
- package/test_data/two_cds.gff3 +0 -9
- package/test_data/two_cds.json +0 -67
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollo-annotation/shared",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "yarn clean && tsc --build",
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
"test:ci": "NODE_V8_COVERAGE=./coverage glob -c \"tsx --test --test-reporter spec --experimental-test-coverage \" \"**/*.test.ts\""
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@apollo-annotation/common": "^0.3.
|
|
14
|
-
"@apollo-annotation/mst": "^0.3.
|
|
15
|
-
"@apollo-annotation/schemas": "^0.3.
|
|
16
|
-
"@gmod/gff": "
|
|
13
|
+
"@apollo-annotation/common": "^0.3.10",
|
|
14
|
+
"@apollo-annotation/mst": "^0.3.10",
|
|
15
|
+
"@apollo-annotation/schemas": "^0.3.10",
|
|
16
|
+
"@gmod/gff": "^2.0.0",
|
|
17
17
|
"@gmod/indexedfasta": "^2.0.4",
|
|
18
18
|
"@jbrowse/core": "^3.6.5",
|
|
19
19
|
"bson-objectid": "^2.0.4",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@nestjs/common": "^10.1.0",
|
|
26
26
|
"@nestjs/core": "^10.1.0",
|
|
27
27
|
"@types/chai": "^4.3.19",
|
|
28
|
-
"@types/node": "^
|
|
28
|
+
"@types/node": "^20.19.15",
|
|
29
29
|
"chai": "^5.1.1",
|
|
30
30
|
"chai-exclude": "^3.0.0",
|
|
31
31
|
"glob": "^11.0.0",
|
|
@@ -19,6 +19,7 @@ export interface SerializedAddAssemblyAndFeaturesFromFileChangeBase
|
|
|
19
19
|
export interface AddAssemblyAndFeaturesFromFileChangeDetails {
|
|
20
20
|
assemblyName: string
|
|
21
21
|
fileIds: { fa: string }
|
|
22
|
+
parseOptions?: { bufferSize: number }
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface SerializedAddAssemblyAndFeaturesFromFileChangeSingle
|
|
@@ -68,7 +69,7 @@ export class AddAssemblyAndFeaturesFromFileChange extends FromFileBaseChange {
|
|
|
68
69
|
const { assemblyModel, checkModel, fileModel, filesService, user } = backend
|
|
69
70
|
const { assembly, changes, logger } = this
|
|
70
71
|
for (const change of changes) {
|
|
71
|
-
const { assemblyName, fileIds } = change
|
|
72
|
+
const { assemblyName, fileIds, parseOptions } = change
|
|
72
73
|
const fileId = fileIds.fa
|
|
73
74
|
|
|
74
75
|
const { FILE_UPLOAD_FOLDER } = process.env
|
|
@@ -110,13 +111,13 @@ export class AddAssemblyAndFeaturesFromFileChange extends FromFileBaseChange {
|
|
|
110
111
|
backend,
|
|
111
112
|
)
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
logger.debug?.(
|
|
115
|
-
`**************** LOOPATAAN KAIKKI FEATURET SEURAAVAKSI File type: "${fileDoc.type}"`,
|
|
116
|
-
)
|
|
114
|
+
const { bufferSize = 10_000 } = parseOptions ?? {}
|
|
117
115
|
const featureStream = filesService.parseGFF3(
|
|
118
116
|
filesService.getFileStream(fileDoc),
|
|
117
|
+
{ bufferSize },
|
|
119
118
|
)
|
|
119
|
+
// @ts-expect-error type is wrong here
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
120
121
|
for await (const f of featureStream) {
|
|
121
122
|
const gff3Feature = f as GFF3Feature
|
|
122
123
|
logger.verbose?.(`ENTRY=${JSON.stringify(gff3Feature)}`)
|
|
@@ -89,6 +89,12 @@ export class AddFeatureChange extends FeatureChange {
|
|
|
89
89
|
let featureCnt = 0
|
|
90
90
|
logger.debug?.(`changes: ${JSON.stringify(changes)}`)
|
|
91
91
|
|
|
92
|
+
const { INDEXED_IDS } = process.env
|
|
93
|
+
let idsToIndex: string[] | undefined
|
|
94
|
+
if (INDEXED_IDS) {
|
|
95
|
+
idsToIndex = INDEXED_IDS.split(',')
|
|
96
|
+
}
|
|
97
|
+
|
|
92
98
|
// Loop the changes
|
|
93
99
|
for (const change of changes) {
|
|
94
100
|
logger.debug?.(`change: ${JSON.stringify(change)}`)
|
|
@@ -106,9 +112,10 @@ export class AddFeatureChange extends FeatureChange {
|
|
|
106
112
|
|
|
107
113
|
// CopyFeature is called from CopyFeature.tsx
|
|
108
114
|
if (copyFeature) {
|
|
115
|
+
const indexedIds = this.getIndexedIds(addedFeature, idsToIndex)
|
|
109
116
|
// Add into Mongo
|
|
110
117
|
const [newFeatureDoc] = await featureModel.create(
|
|
111
|
-
[{ ...addedFeature, allIds, status: -1, user }],
|
|
118
|
+
[{ ...addedFeature, allIds, indexedIds, status: -1, user }],
|
|
112
119
|
{ session },
|
|
113
120
|
)
|
|
114
121
|
logger.debug?.(
|
|
@@ -143,8 +150,9 @@ export class AddFeatureChange extends FeatureChange {
|
|
|
143
150
|
} else {
|
|
144
151
|
const childIds = this.getChildFeatureIds(addedFeature)
|
|
145
152
|
const allIdsV2 = [_id, ...childIds]
|
|
153
|
+
const indexedIds = this.getIndexedIds(addedFeature, idsToIndex)
|
|
146
154
|
const [newFeatureDoc] = await featureModel.create(
|
|
147
|
-
[{ allIds: allIdsV2, status: 0, ...addedFeature }],
|
|
155
|
+
[{ allIds: allIdsV2, indexedIds, status: 0, ...addedFeature }],
|
|
148
156
|
{ session },
|
|
149
157
|
)
|
|
150
158
|
logger.verbose?.(`Added docId "${newFeatureDoc._id}"`)
|
|
@@ -167,9 +175,23 @@ export class AddFeatureChange extends FeatureChange {
|
|
|
167
175
|
for (const change of changes) {
|
|
168
176
|
const { addedFeature, parentFeatureId } = change
|
|
169
177
|
if (parentFeatureId) {
|
|
170
|
-
|
|
178
|
+
let parentFeature = dataStore.getFeature(parentFeatureId)
|
|
179
|
+
// maybe the parent feature hasn't been loaded yet
|
|
171
180
|
if (!parentFeature) {
|
|
172
|
-
|
|
181
|
+
await dataStore.loadFeatures([
|
|
182
|
+
{
|
|
183
|
+
assemblyName: assembly,
|
|
184
|
+
refName: addedFeature.refSeq,
|
|
185
|
+
start: addedFeature.min,
|
|
186
|
+
end: addedFeature.max,
|
|
187
|
+
},
|
|
188
|
+
])
|
|
189
|
+
parentFeature = dataStore.getFeature(parentFeatureId)
|
|
190
|
+
if (!parentFeature) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Could not find parent feature "${parentFeatureId}"`,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
173
195
|
}
|
|
174
196
|
// create an ID for the parent feature if it does not have one
|
|
175
197
|
if (!parentFeature.attributes.get('_id')) {
|
|
@@ -18,6 +18,7 @@ export interface SerializedAddFeaturesFromFileChangeBase
|
|
|
18
18
|
|
|
19
19
|
export interface AddFeaturesFromFileChangeDetails {
|
|
20
20
|
fileId: string
|
|
21
|
+
parseOptions?: { bufferSize: number }
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export interface SerializedAddFeaturesFromFileChangeSingle
|
|
@@ -75,7 +76,7 @@ export class AddFeaturesFromFileChange extends FromFileBaseChange {
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
for (const change of changes) {
|
|
78
|
-
const { fileId } = change
|
|
79
|
+
const { fileId, parseOptions } = change
|
|
79
80
|
|
|
80
81
|
const { FILE_UPLOAD_FOLDER } = process.env
|
|
81
82
|
if (!FILE_UPLOAD_FOLDER) {
|
|
@@ -89,10 +90,14 @@ export class AddFeaturesFromFileChange extends FromFileBaseChange {
|
|
|
89
90
|
logger.debug?.(`FileId "${fileId}", checksum "${fileDoc.checksum}"`)
|
|
90
91
|
|
|
91
92
|
// Read data from compressed file and parse the content
|
|
93
|
+
const { bufferSize = 10_000 } = parseOptions ?? {}
|
|
92
94
|
const featureStream = filesService.parseGFF3(
|
|
93
95
|
filesService.getFileStream(fileDoc),
|
|
96
|
+
{ bufferSize },
|
|
94
97
|
)
|
|
95
98
|
let featureCount = 0
|
|
99
|
+
// @ts-expect-error type is wrong here
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
96
101
|
for await (const f of featureStream) {
|
|
97
102
|
const gff3Feature = f as GFF3Feature
|
|
98
103
|
|
|
@@ -35,11 +35,7 @@ export class AddRefSeqAliasesChange extends AssemblySpecificChange {
|
|
|
35
35
|
if (!assembly) {
|
|
36
36
|
throw new Error(`assembly ${this.assembly} not found`)
|
|
37
37
|
}
|
|
38
|
-
const sessionAliases = assembly.refNameAliases
|
|
39
|
-
|
|
40
|
-
if (!sessionAliases) {
|
|
41
|
-
throw new Error('Session refNameAliases not found in assembly')
|
|
42
|
-
}
|
|
38
|
+
const sessionAliases = assembly.refNameAliases ?? {}
|
|
43
39
|
|
|
44
40
|
for (const refSeqAlias of this.refSeqAliases) {
|
|
45
41
|
const { aliases, refName } = refSeqAlias
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
|
2
1
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
3
|
-
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
4
|
-
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
5
2
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
6
3
|
import {
|
|
7
4
|
AssemblySpecificChange,
|
|
8
5
|
type ServerDataStore,
|
|
9
6
|
} from '@apollo-annotation/common'
|
|
7
|
+
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
10
8
|
import {
|
|
11
9
|
type FileDocument,
|
|
12
10
|
type RefSeqDocument,
|
|
@@ -38,8 +36,11 @@ export abstract class FromFileBaseChange extends AssemblySpecificChange {
|
|
|
38
36
|
let parsingStarted = false
|
|
39
37
|
logger.debug?.('starting sequence stream')
|
|
40
38
|
let lineCount = 0
|
|
39
|
+
const decoder = new TextDecoder()
|
|
40
|
+
// @ts-expect-error type is wrong here
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
41
42
|
for await (const data of sequenceStream) {
|
|
42
|
-
const chunk =
|
|
43
|
+
const chunk = decoder.decode(data)
|
|
43
44
|
lastLineIsIncomplete = !chunk.endsWith('\n')
|
|
44
45
|
// chunk is small enough that you can split the whole thing into lines without having to make it into smaller chunks first.
|
|
45
46
|
const lines = chunk.split(/\r?\n/)
|
|
@@ -48,9 +49,9 @@ export abstract class FromFileBaseChange extends AssemblySpecificChange {
|
|
|
48
49
|
incompleteLine = ''
|
|
49
50
|
}
|
|
50
51
|
if (lastLineIsIncomplete) {
|
|
51
|
-
incompleteLine = lines.pop()
|
|
52
|
+
incompleteLine = lines.pop() ?? ''
|
|
52
53
|
}
|
|
53
|
-
for
|
|
54
|
+
for (const line of lines) {
|
|
54
55
|
lineCount++
|
|
55
56
|
if (lineCount % 1_000_000 === 0) {
|
|
56
57
|
logger.debug?.(`Processed ${lineCount} lines`)
|
|
@@ -183,8 +184,13 @@ export abstract class FromFileBaseChange extends AssemblySpecificChange {
|
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
async addFeatureIntoDb(gff3Feature: GFF3Feature, backend: ServerDataStore) {
|
|
186
|
-
const {
|
|
187
|
+
const { INDEXED_IDS } = process.env
|
|
188
|
+
let idsToIndex: string[] | undefined
|
|
189
|
+
if (INDEXED_IDS) {
|
|
190
|
+
idsToIndex = INDEXED_IDS.split(',')
|
|
191
|
+
}
|
|
187
192
|
const { assembly, refSeqCache } = this
|
|
193
|
+
const { featureModel, refSeqModel, user } = backend
|
|
188
194
|
|
|
189
195
|
const [{ seq_id: refName }] = gff3Feature
|
|
190
196
|
if (!refName) {
|
|
@@ -206,19 +212,25 @@ export abstract class FromFileBaseChange extends AssemblySpecificChange {
|
|
|
206
212
|
`RefSeq was not found by assembly "${assembly}" and seq_id "${refName}" not found`,
|
|
207
213
|
)
|
|
208
214
|
}
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
const newFeature = gff3ToAnnotationFeature(
|
|
213
|
-
gff3Feature,
|
|
214
|
-
refSeqDoc._id,
|
|
215
|
-
featureIds,
|
|
216
|
-
)
|
|
215
|
+
const newFeature = gff3ToAnnotationFeature(gff3Feature, refSeqDoc._id)
|
|
216
|
+
const allIds = this.getAllIds(newFeature)
|
|
217
|
+
const indexedIds = this.getIndexedIds(newFeature, idsToIndex)
|
|
217
218
|
|
|
218
219
|
// Add into Mongo
|
|
219
220
|
// We cannot use Mongo 'session' / transaction here because Mongo has 16 MB limit for transaction
|
|
220
221
|
await featureModel.create([
|
|
221
|
-
{ allIds
|
|
222
|
+
{ allIds, indexedIds, ...newFeature, user, status: -1 },
|
|
222
223
|
])
|
|
223
224
|
}
|
|
225
|
+
|
|
226
|
+
getAllIds(feature: AnnotationFeatureSnapshot): string[] {
|
|
227
|
+
const allIds = [feature._id]
|
|
228
|
+
if (feature.children) {
|
|
229
|
+
for (const child of Object.values(feature.children)) {
|
|
230
|
+
const childIds = this.getAllIds(child)
|
|
231
|
+
allIds.push(...childIds)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return allIds
|
|
235
|
+
}
|
|
224
236
|
}
|
package/src/Checks/CDSCheck.ts
CHANGED
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
import { intersection2 } from '@jbrowse/core/util'
|
|
7
7
|
import ObjectID from 'bson-objectid'
|
|
8
8
|
|
|
9
|
+
import { getPrintableId } from './util'
|
|
10
|
+
|
|
9
11
|
enum STOP_CODONS {
|
|
10
12
|
'TAG',
|
|
11
13
|
'TAA',
|
|
@@ -175,7 +177,7 @@ async function checkMRNA(
|
|
|
175
177
|
refSeq: refSeq.toString(),
|
|
176
178
|
start: cdsStart,
|
|
177
179
|
end: cdsStart,
|
|
178
|
-
message: `Unexpected start codon in feature "${
|
|
180
|
+
message: `Unexpected start codon "${start_codon}" in feature "${getPrintableId(feature)}": `,
|
|
179
181
|
})
|
|
180
182
|
}
|
|
181
183
|
|
|
@@ -189,7 +191,7 @@ async function checkMRNA(
|
|
|
189
191
|
refSeq: refSeq.toString(),
|
|
190
192
|
start: cdsEnd,
|
|
191
193
|
end: cdsEnd,
|
|
192
|
-
message: `Missing stop codon
|
|
194
|
+
message: `Missing stop codon in feature "${getPrintableId(feature)}"`,
|
|
193
195
|
})
|
|
194
196
|
}
|
|
195
197
|
} else {
|
|
@@ -201,7 +203,7 @@ async function checkMRNA(
|
|
|
201
203
|
refSeq: refSeq.toString(),
|
|
202
204
|
start: cdsEnd,
|
|
203
205
|
end: cdsEnd,
|
|
204
|
-
message: `Missing stop codon
|
|
206
|
+
message: `Missing stop codon in feature "${getPrintableId(feature)}"`,
|
|
205
207
|
})
|
|
206
208
|
}
|
|
207
209
|
for (const [idx, codon] of codons.entries()) {
|
|
@@ -220,7 +222,7 @@ async function checkMRNA(
|
|
|
220
222
|
refSeq: refSeq.toString(),
|
|
221
223
|
start: codonStart,
|
|
222
224
|
end: codonEnd,
|
|
223
|
-
message: `
|
|
225
|
+
message: `Internal stop codon in feature "${getPrintableId(feature)}"`,
|
|
224
226
|
})
|
|
225
227
|
}
|
|
226
228
|
}
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
import { revcom } from '@jbrowse/core/util'
|
|
7
7
|
import ObjectID from 'bson-objectid'
|
|
8
8
|
|
|
9
|
+
import { getPrintableId } from './util'
|
|
10
|
+
|
|
9
11
|
interface SpliceSequence {
|
|
10
12
|
fivePrimeSeq: string
|
|
11
13
|
fivePrimeMin: number
|
|
@@ -35,15 +37,17 @@ async function getSpliceSequences(
|
|
|
35
37
|
if (exons.length < 2) {
|
|
36
38
|
return []
|
|
37
39
|
}
|
|
40
|
+
exons.sort((a, b) => (a.min < b.min ? -1 : 1))
|
|
38
41
|
|
|
39
42
|
const spliceSeq: SpliceSequence[] = []
|
|
40
43
|
for (let i = 0; i < exons.length - 1; i++) {
|
|
41
44
|
let fivePrimeMin = exons[i].max
|
|
42
|
-
let threePrimeMin = exons[i + 1].min
|
|
45
|
+
let threePrimeMin = exons[i + 1].min
|
|
43
46
|
if (transcript.strand === -1) {
|
|
44
|
-
|
|
45
|
-
fivePrimeMin
|
|
46
|
-
|
|
47
|
+
;[threePrimeMin, fivePrimeMin] = [fivePrimeMin, threePrimeMin]
|
|
48
|
+
fivePrimeMin -= 2
|
|
49
|
+
} else {
|
|
50
|
+
threePrimeMin -= 2
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
let fivePrimeSeq = await getSequence(fivePrimeMin, fivePrimeMin + 2)
|
|
@@ -53,12 +57,7 @@ async function getSpliceSequences(
|
|
|
53
57
|
fivePrimeSeq = revcom(fivePrimeSeq)
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
spliceSeq.push({
|
|
57
|
-
fivePrimeSeq,
|
|
58
|
-
fivePrimeMin,
|
|
59
|
-
threePrimeSeq,
|
|
60
|
-
threePrimeMin,
|
|
61
|
-
})
|
|
60
|
+
spliceSeq.push({ fivePrimeSeq, fivePrimeMin, threePrimeSeq, threePrimeMin })
|
|
62
61
|
}
|
|
63
62
|
return spliceSeq
|
|
64
63
|
}
|
|
@@ -82,7 +81,7 @@ async function checkTranscript(
|
|
|
82
81
|
refSeq: feature.refSeq.toString(),
|
|
83
82
|
start: spliceSequence.fivePrimeMin,
|
|
84
83
|
end: spliceSequence.fivePrimeMin + 2,
|
|
85
|
-
message: `Unexpected 5
|
|
84
|
+
message: `Unexpected 5′ splice site in "${getPrintableId(feature)}". Expected: ${[...VALID_FIVE_PRIME_SEQ].join('|')}, got: ${spliceSequence.fivePrimeSeq}`,
|
|
86
85
|
})
|
|
87
86
|
}
|
|
88
87
|
if (
|
|
@@ -96,7 +95,7 @@ async function checkTranscript(
|
|
|
96
95
|
refSeq: feature.refSeq.toString(),
|
|
97
96
|
start: spliceSequence.threePrimeMin,
|
|
98
97
|
end: spliceSequence.threePrimeMin + 2,
|
|
99
|
-
message: `Unexpected 3
|
|
98
|
+
message: `Unexpected 3′ splice site in "${getPrintableId(feature)}". Expected: ${[...VALID_THREE_PRIME_SEQ].join('|')}, got: ${spliceSequence.threePrimeSeq}`,
|
|
100
99
|
})
|
|
101
100
|
}
|
|
102
101
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
2
|
+
|
|
3
|
+
export function getPrintableId(feature: AnnotationFeatureSnapshot): string {
|
|
4
|
+
const gff_id = feature.attributes?.gff_id?.join(', ')
|
|
5
|
+
if (gff_id) {
|
|
6
|
+
return `${gff_id} (_id: ${feature._id.toString()})`
|
|
7
|
+
}
|
|
8
|
+
const gff_name = feature.attributes?.gff_name?.join(', ')
|
|
9
|
+
if (gff_name) {
|
|
10
|
+
return `${gff_name} (_id: ${feature._id.toString()})`
|
|
11
|
+
}
|
|
12
|
+
return `_id: ${feature._id.toString()}`
|
|
13
|
+
}
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
/* eslint-disable prefer-destructuring */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
3
|
+
import { readFileSync } from 'node:fs'
|
|
3
4
|
import { describe, it } from 'node:test'
|
|
4
5
|
|
|
5
6
|
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
6
|
-
import {
|
|
7
|
+
import { formatSync } from '@gmod/gff'
|
|
8
|
+
import { assert, expect } from 'chai'
|
|
7
9
|
|
|
8
10
|
import { annotationFeatureToGFF3 } from './annotationFeatureToGFF3'
|
|
9
|
-
import { readAnnotationFeatureSnapshot } from './
|
|
11
|
+
import { readAnnotationFeatureSnapshot, testCases } from './testUtil'
|
|
12
|
+
|
|
13
|
+
describe('Converts AnnotationFeatureSnapshot JSON to GFF3 when', () => {
|
|
14
|
+
for (const testCase of testCases) {
|
|
15
|
+
const { filenameStem, description } = testCase
|
|
16
|
+
it(description, () => {
|
|
17
|
+
const annotationFeatures = JSON.parse(
|
|
18
|
+
readFileSync(`test_data/${filenameStem}.json`, 'utf8'),
|
|
19
|
+
) as AnnotationFeatureSnapshot[]
|
|
20
|
+
const expectedGFF3 = readFileSync(
|
|
21
|
+
`test_data/${filenameStem}.gff3`,
|
|
22
|
+
'utf8',
|
|
23
|
+
)
|
|
24
|
+
const gffFeatures = annotationFeatures.map((annotationFeature) =>
|
|
25
|
+
annotationFeatureToGFF3(annotationFeature),
|
|
26
|
+
)
|
|
27
|
+
const gff3 = formatSync(gffFeatures)
|
|
28
|
+
expect(gff3).to.equal(expectedGFF3)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
})
|
|
10
32
|
|
|
11
33
|
describe('annotationFeatureToGFF3', () => {
|
|
12
34
|
it('Test mandatory columns', () => {
|
|
@@ -101,7 +101,10 @@ export function annotationFeatureToGFF3(
|
|
|
101
101
|
score,
|
|
102
102
|
strand: feature.strand ? (feature.strand === 1 ? '+' : '-') : null,
|
|
103
103
|
phase: null,
|
|
104
|
-
attributes:
|
|
104
|
+
attributes:
|
|
105
|
+
Object.keys(attributes).length > 0
|
|
106
|
+
? (attributes as Record<string, string[]>)
|
|
107
|
+
: null,
|
|
105
108
|
derived_features: [],
|
|
106
109
|
child_features: prepareChildFeatures(
|
|
107
110
|
feature,
|
|
@@ -3,82 +3,15 @@ import { readFileSync } from 'node:fs'
|
|
|
3
3
|
import { describe, it } from 'node:test'
|
|
4
4
|
|
|
5
5
|
import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
6
|
-
import
|
|
6
|
+
import { type GFF3Feature, parseStringSync } from '@gmod/gff'
|
|
7
7
|
import { assert, use } from 'chai'
|
|
8
8
|
import chaiExclude from 'chai-exclude'
|
|
9
9
|
|
|
10
10
|
import { gff3ToAnnotationFeature } from './gff3ToAnnotationFeature'
|
|
11
|
+
import { readAnnotationFeatureSnapshot, testCases } from './testUtil'
|
|
11
12
|
|
|
12
13
|
use(chaiExclude)
|
|
13
14
|
|
|
14
|
-
const testCases: [string, string, AnnotationFeatureSnapshot][] = [
|
|
15
|
-
[
|
|
16
|
-
'a feature with no children',
|
|
17
|
-
'ctgA example remark 1000 2000 . . . Name=Remark:hga;Alias=hga\n',
|
|
18
|
-
{
|
|
19
|
-
_id: '66c51f3e002c683eaf98a223',
|
|
20
|
-
refSeq: 'ctgA',
|
|
21
|
-
type: 'remark',
|
|
22
|
-
min: 999,
|
|
23
|
-
max: 2000,
|
|
24
|
-
attributes: {
|
|
25
|
-
gff_source: ['example'],
|
|
26
|
-
gff_name: ['Remark:hga'],
|
|
27
|
-
gff_alias: ['hga'],
|
|
28
|
-
},
|
|
29
|
-
},
|
|
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
|
-
],
|
|
80
|
-
]
|
|
81
|
-
|
|
82
15
|
interface AnnotationFeatureSnapshotWithChildrenArray
|
|
83
16
|
extends Omit<AnnotationFeatureSnapshot, 'children'> {
|
|
84
17
|
children?: AnnotationFeatureSnapshotWithChildrenArray[]
|
|
@@ -108,6 +41,29 @@ function compareFeatures(
|
|
|
108
41
|
)
|
|
109
42
|
}
|
|
110
43
|
|
|
44
|
+
describe('Converts GFF3 to AnnotationFeatureSnapshot JSON when', () => {
|
|
45
|
+
for (const testCase of testCases) {
|
|
46
|
+
const { filenameStem, description } = testCase
|
|
47
|
+
it(description, () => {
|
|
48
|
+
const fileText = readFileSync(`test_data/${filenameStem}.gff3`, 'utf8')
|
|
49
|
+
const gffFeatures = parseStringSync(fileText, { parseSequences: false })
|
|
50
|
+
const annotationFeatures = gffFeatures.map((gff3Feature) =>
|
|
51
|
+
gff3ToAnnotationFeature(gff3Feature),
|
|
52
|
+
)
|
|
53
|
+
const annotationFeaturesExpected = JSON.parse(
|
|
54
|
+
readFileSync(`test_data/${filenameStem}.json`, 'utf8'),
|
|
55
|
+
) as AnnotationFeatureSnapshot[]
|
|
56
|
+
for (const [
|
|
57
|
+
i,
|
|
58
|
+
annotationFeatureExpected,
|
|
59
|
+
] of annotationFeaturesExpected.entries()) {
|
|
60
|
+
const annotationFeature = annotationFeatures[i]
|
|
61
|
+
compareFeatures(annotationFeature, annotationFeatureExpected)
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
111
67
|
function readFeatureFile(fn: string): GFF3Feature[] {
|
|
112
68
|
const lines = readFileSync(fn).toString().split('\n')
|
|
113
69
|
const feature: string[] = []
|
|
@@ -116,17 +72,10 @@ function readFeatureFile(fn: string): GFF3Feature[] {
|
|
|
116
72
|
feature.push(line)
|
|
117
73
|
}
|
|
118
74
|
}
|
|
119
|
-
const inGff =
|
|
75
|
+
const inGff = parseStringSync(feature.join('\n')) as GFF3Feature[]
|
|
120
76
|
return inGff
|
|
121
77
|
}
|
|
122
78
|
|
|
123
|
-
export function readAnnotationFeatureSnapshot(
|
|
124
|
-
fn: string,
|
|
125
|
-
): AnnotationFeatureSnapshot {
|
|
126
|
-
const lines = readFileSync(fn).toString()
|
|
127
|
-
return JSON.parse(lines) as AnnotationFeatureSnapshot
|
|
128
|
-
}
|
|
129
|
-
|
|
130
79
|
const [ex1, , ex2, , ex3, , ex4] = readFeatureFile(
|
|
131
80
|
'test_data/gene_representations.gff3',
|
|
132
81
|
)
|
|
@@ -139,13 +88,6 @@ describe('gff3ToAnnotationFeature examples', () => {
|
|
|
139
88
|
const expected = readAnnotationFeatureSnapshot('test_data/one_cds.json')
|
|
140
89
|
compareFeatures(actual, expected)
|
|
141
90
|
})
|
|
142
|
-
it('Convert two CDSs', () => {
|
|
143
|
-
const actual = gff3ToAnnotationFeature(
|
|
144
|
-
readFeatureFile('test_data/two_cds.gff3')[0],
|
|
145
|
-
)
|
|
146
|
-
const expected = readAnnotationFeatureSnapshot('test_data/two_cds.json')
|
|
147
|
-
compareFeatures(actual, expected)
|
|
148
|
-
})
|
|
149
91
|
it('Convert example 1', () => {
|
|
150
92
|
const actual = gff3ToAnnotationFeature(ex1)
|
|
151
93
|
const txt = JSON.stringify(actual, null, 2)
|
|
@@ -228,19 +170,6 @@ describe('CDS without exons', () => {
|
|
|
228
170
|
})
|
|
229
171
|
})
|
|
230
172
|
|
|
231
|
-
describe('gff3ToAnnotationFeature', () => {
|
|
232
|
-
for (const testCase of testCases) {
|
|
233
|
-
const [description, featureLine, convertedFeature] = testCase
|
|
234
|
-
it(`converts ${description}`, () => {
|
|
235
|
-
const gff3Feature = gff.parseStringSync(featureLine, {
|
|
236
|
-
parseSequences: false,
|
|
237
|
-
})
|
|
238
|
-
const feature = gff3ToAnnotationFeature(gff3Feature[0])
|
|
239
|
-
compareFeatures(convertedFeature, feature)
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
})
|
|
243
|
-
|
|
244
173
|
describe('Source and score', () => {
|
|
245
174
|
it('Convert score and source', () => {
|
|
246
175
|
const gffFeature: GFF3Feature = [
|