1+ /* eslint-disable one-var */
12const fs = require ( 'fs' ) ;
23
34const { jsonifyVariant, parseVariant } = require ( '@bcgsc-pori/graphkb-parser' ) ;
@@ -7,53 +8,108 @@ const {
78 hashRecordToId,
89} = require ( '../util' ) ;
910const {
11+ // eslint-disable-next-line no-unused-vars
12+ ApiConnection,
1013 orderPreferredOntologyTerms,
1114 rid,
1215} = require ( '../graphkb' ) ;
1316const _refseq = require ( '../entrez/refseq' ) ;
1417const { logger } = require ( '../logging' ) ;
1518
16- const { cgl : SOURCE_DEFN } = require ( '../sources' ) ;
19+ const {
20+ cgl : SOURCE_DEFN ,
21+ entrezGene : ENTREZGENE_SOURCE_DEFN ,
22+ refseq : REFSEQ_SOURCE_DEFN ,
23+ } = require ( '../sources' ) ;
1724
1825
19- const loadCdsVariant = async ( graphkbConn , transcriptId , cdsNotation ) => {
26+ const getTranscript = async ( graphkbConn , transcriptId ) => {
27+ let newVersionedTranscript = false ;
2028 let reference1 ;
2129
30+ const unversionedId = transcriptId . split ( '.' ) [ 0 ] ;
31+ const version = transcriptId . split ( '.' ) [ 1 ] ;
32+
2233 try {
34+ // Try to fetch from GraphKB first
2335 reference1 = await graphkbConn . getUniqueRecordBy ( {
2436 filters : {
2537 AND : [
26- { source : { filters : { name : SOURCE_DEFN . name } , target : 'Source' } } ,
27- { sourceId : transcriptId . split ( '.' ) [ 0 ] } ,
28- { sourceIdVersion : transcriptId . split ( '.' ) [ 1 ] || null } ,
38+ { source : { filters : { name : REFSEQ_SOURCE_DEFN . name } , target : 'Source' } } ,
39+ { sourceId : unversionedId } ,
40+ { sourceIdVersion : version || null } ,
2941 { biotype : 'transcript' } ,
3042 ] ,
3143 } ,
3244 target : 'Feature' ,
3345 } ) ;
3446 } catch ( err ) {
35- const transcripts = await _refseq . fetchAndLoadByIds ( graphkbConn , [ transcriptId ] ) ;
47+ // If it fail, try to fetch from RefSeq instead
48+ if ( version ) {
49+ const transcripts = await _refseq . fetchAndLoadByIds ( graphkbConn , [ transcriptId ] ) ;
3650
37- if ( transcripts . length !== 1 ) {
38- throw new Error ( `unable to find unique transcript (${ transcriptId } ) (found: ${ transcripts . length } )` ) ;
51+ if ( transcripts . length !== 1 ) {
52+ throw new Error ( `unable to find unique transcript (${ transcriptId } ) from RefSeq (found: ${ transcripts . length } )` ) ;
53+ }
54+ [ reference1 ] = transcripts ;
55+ newVersionedTranscript = true ;
3956 }
40- [ reference1 ] = transcripts ;
4157 }
4258
59+ // If a new versioned transcript gets added,
60+ // make sure it is linked to the corresponding unversioned transcript
61+ let reference0 ;
62+
63+ if ( version && newVersionedTranscript ) {
64+ // unversioned transcript
65+ try {
66+ reference0 = await getTranscript ( graphkbConn , unversionedId ) ; // recursive call
67+ } catch ( err ) {
68+ logger . warn ( `Unable to fetch unversionized transcript ${ transcriptId } ` ) ;
69+ }
70+
71+ // GeneralizationOf edge
72+ if ( reference0 ) {
73+ try {
74+ await graphkbConn . addRecord ( {
75+ content : { in : rid ( reference1 ) , out : rid ( reference0 ) } ,
76+ existsOk : true ,
77+ fetchExisting : false ,
78+ target : 'GeneralizationOf' ,
79+ } ) ;
80+ logger . info ( `link: unversioned transcript ${ rid ( reference0 ) } to versioned transcript ${ rid ( reference1 ) } ` ) ;
81+ } catch ( err ) {
82+ logger . warn ( `failed to link the unversionized transcript ${ err } ` ) ;
83+ }
84+ }
85+ }
86+
87+ return reference1 ;
88+ } ;
89+
90+ const loadCdsVariant = async ( graphkbConn , transcriptId , cdsNotation ) => {
4391 if ( ! cdsNotation . startsWith ( 'c.' ) ) {
4492 throw new Error ( `invalid HGVSc notation (${ cdsNotation } )` ) ;
4593 }
46- // add the cds variant
94+
95+ // get the reference
96+ const reference1 = await getTranscript ( graphkbConn , transcriptId ) ;
97+
98+ // get the object representation of the variant from parsing
4799 const {
48100 noFeatures, multiFeature, prefix, ...variant
49101 } = parseVariant ( cdsNotation , false ) ;
50102 variant . reference1 = reference1 ;
51103 variant . type = rid ( await graphkbConn . getVocabularyTerm ( variant . type ) ) ;
104+
105+ // add the cds variant
52106 const cds = rid ( await graphkbConn . addVariant ( {
53107 content : { ...jsonifyVariant ( variant ) } ,
54108 existsOk : true ,
55109 target : 'PositionalVariant' ,
56110 } ) ) ;
111+ logger . info ( `cds: ${ transcriptId } ${ cdsNotation } ; PositionalVariant ${ cds } ` ) ;
112+
57113 return cds ;
58114} ;
59115
@@ -65,36 +121,38 @@ const loadProteinVariant = async (graphkbConn, gene, proteinNotation) => {
65121 if ( ! proteinNotation . startsWith ( 'p.' ) ) {
66122 throw new Error ( `invalid HGVSp notation (${ proteinNotation } )` ) ;
67123 }
68- proteinNotation = proteinNotation . replace ( / ^ p \. \( / , 'p.' ) . replace ( / \) $ / , '' ) ;
124+ if ( proteinNotation . includes ( '=' ) ) {
125+ throw new Error ( `unsupported wildtype variant (${ proteinNotation } )` ) ;
126+ }
127+ let proteinNotationFixed = proteinNotation . replace ( / ^ p \. \( / , 'p.' ) . replace ( / \) $ / , '' ) ;
69128
70- if ( ! proteinNotation . includes ( 'fs' ) ) {
71- proteinNotation = proteinNotation . replace ( / \* $ / , 'Ter' ) ;
129+ if ( ! proteinNotationFixed . includes ( 'fs' ) ) {
130+ proteinNotationFixed = proteinNotationFixed . replace ( / \* $ / , 'Ter' ) ;
72131 }
73132 const reference1 = await graphkbConn . getUniqueRecordBy ( {
74133 filters : [
75- {
76- name : gene ,
77- } ,
78- {
79- biotype : 'gene' ,
80- } ,
81- {
82- source : { filters : { name : 'entrez gene' } , target : 'Source' } ,
83- } ,
134+ { name : gene } ,
135+ { biotype : 'gene' } ,
136+ { source : { filters : { name : ENTREZGENE_SOURCE_DEFN . name } , target : 'Source' } } ,
84137 ] ,
85138 target : 'Feature' ,
86139 } ) ;
87- // add the cds variant
140+
141+ // get the object representation of the variant from parsing
88142 const {
89143 noFeatures, multiFeature, prefix, ...variant
90- } = parseVariant ( proteinNotation , false ) ;
144+ } = parseVariant ( proteinNotationFixed , false ) ;
91145 variant . reference1 = reference1 ;
92146 variant . type = rid ( await graphkbConn . getVocabularyTerm ( variant . type ) ) ;
147+
148+ // add the protein variant
93149 const protein = rid ( await graphkbConn . addVariant ( {
94150 content : { ...jsonifyVariant ( variant ) } ,
95151 existsOk : true ,
96152 target : 'PositionalVariant' ,
97153 } ) ) ;
154+ logger . info ( `protein: ${ gene } ${ proteinNotation } ; PositionalVariant ${ protein } ` ) ;
155+
98156 return protein ;
99157} ;
100158
@@ -112,43 +170,43 @@ const loadGenomicVariant = async (graphkbConn, chromosome, position, ref, alt) =
112170 throw new Error ( `unexpected ref (${ ref } ) vs alt (${ alt } ) combination, do not match on first base` ) ;
113171 }
114172 let [ start , end ] = position . split ( '_' ) . map ( p => Number . parseInt ( p , 10 ) ) ;
115- ref = ref . slice ( 1 ) ;
116- alt = alt . slice ( 1 ) ;
173+ const refTrunc = ref . slice ( 1 ) ;
174+ const altTrunc = alt . slice ( 1 ) ;
117175
118- if ( ! ref . length ) {
176+ if ( ! refTrunc . length ) {
119177 // insertion or duplication
120178 if ( ! end ) {
121179 end = start + 1 ;
122180 }
123- notation = `g.${ start } _${ end } ins${ ref } ` ;
124- } else if ( ! alt . length ) {
181+ notation = `g.${ start } _${ end } ins${ refTrunc } ` ;
182+ } else if ( ! altTrunc . length ) {
125183 // deletion
126- if ( ref . length > 1 ) {
184+ if ( refTrunc . length > 1 ) {
127185 if ( ! end ) {
128- end = start + ref . length - 1 ;
186+ end = start + refTrunc . length - 1 ;
129187 }
130- if ( ref . length !== end - start + 1 ) {
188+ if ( refTrunc . length !== end - start + 1 ) {
131189 throw new Error ( `deletion position (${ position } ) span (${ end - start + 1 } ) does not match the length of reference sequence (${ ref . length } ) deleted` ) ;
132190 }
133191 }
134192 end = ( ! end || end === start )
135193 ? ''
136194 : `_${ end } ` ;
137- notation = `g.${ start } ${ end } del${ ref } ` ;
195+ notation = `g.${ start } ${ end } del${ refTrunc } ` ;
138196 } else {
139197 // indel
140- if ( ref . length > 1 ) {
198+ if ( refTrunc . length > 1 ) {
141199 if ( ! end ) {
142- end = start + ref . length - 1 ;
200+ end = start + refTrunc . length - 1 ;
143201 }
144- if ( ref . length !== end - start + 1 ) {
202+ if ( refTrunc . length !== end - start + 1 ) {
145203 throw new Error ( `indel position (${ position } ) span (${ end - start + 1 } ) does not match the length of reference sequence (${ ref . length } ) deleted` ) ;
146204 }
147205 }
148206 end = ( ! end || end === start )
149207 ? ''
150208 : `_${ end } ` ;
151- notation = `g.${ start } ${ end } del${ ref } ins${ alt } ` ;
209+ notation = `g.${ start } ${ end } del${ refTrunc } ins${ altTrunc } ` ;
152210 }
153211 }
154212 const reference1 = await graphkbConn . getUniqueRecordBy ( {
@@ -160,17 +218,22 @@ const loadGenomicVariant = async (graphkbConn, chromosome, position, ref, alt) =
160218 ] ,
161219 target : 'Feature' ,
162220 } ) ;
163- // add the cds variant
221+
222+ // get the object representation of the variant from parsing
164223 const {
165224 noFeatures, multiFeature, prefix, ...variant
166225 } = parseVariant ( notation , false ) ;
167226 variant . reference1 = reference1 ;
168227 variant . type = rid ( await graphkbConn . getVocabularyTerm ( variant . type ) ) ;
228+
229+ // add the genomic variant
169230 const genomic = rid ( await graphkbConn . addVariant ( {
170- content : { ...jsonifyVariant ( variant ) , assembly : 'hg19 ' } ,
231+ content : { ...jsonifyVariant ( variant ) , assembly : 'grch38 ' } ,
171232 existsOk : true ,
172233 target : 'PositionalVariant' ,
173234 } ) ) ;
235+ logger . info ( `genomic: ${ chromosome } , ${ position } , ${ ref } , ${ alt } ; Parsed as ${ notation } ; PositionalVariant ${ genomic } ` ) ;
236+
174237 return genomic ;
175238} ;
176239
@@ -183,31 +246,52 @@ const loadGenomicVariant = async (graphkbConn, chromosome, position, ref, alt) =
183246 * @param {ApiConnection } opt.conn the API connection object
184247 */
185248const uploadFile = async ( { filename, conn, errorLogPrefix } ) => {
186- const jsonList = await loadDelimToJson ( filename ) ;
187- // get the dbID for the source
188- const source = rid ( await conn . addSource ( SOURCE_DEFN ) ) ;
249+ logger . warn ( `
250+ ATTENTION!
251+ All genomic variants are assumed to be following the HGVS 3'-rule,
252+ not the VCF 5'-rule. Conversion needed from 'position' to 'pos_CGL'.
253+
254+ ATTENTION!
255+ Previously, all genomic variants were assumed to be reported
256+ on the hg19/GRCh37 genome assembly.
257+ Now we're assuming they are reported on the latest GRCh38 assembly
258+
259+ ATTENTION!
260+ Previously, protein variants were prefered over cds ones, which were
261+ prefered over genomic ones.
262+ Now, if provided, the genomic variant is prefered, otherwise the cds one.
263+ Protein variants are only used in last resort` ) ;
264+
189265 const counts = { error : 0 , skip : 0 , success : 0 } ;
190266 const errorList = [ ] ;
267+
268+ // Input file
269+ const jsonList = await loadDelimToJson ( filename ) ;
191270 logger . info ( `Processing ${ jsonList . length } records` ) ;
192- // Upload the list of pubmed IDs
271+
272+ // source, disease & relevance RIDs
273+ const relevance = await conn . getVocabularyTerm ( 'pathogenic' ) ;
274+ const source = rid ( await conn . addSource ( SOURCE_DEFN ) ) ;
193275 const disease = await conn . getUniqueRecordBy ( {
194276 filters : {
195277 name : 'cancer' ,
196278 } ,
197279 sort : orderPreferredOntologyTerms ,
198280 target : 'Disease' ,
199281 } ) ;
200- const relevance = await conn . getVocabularyTerm ( 'pathogenic' ) ;
201282
202- // load all transcripts (entrez sometimes misses requests for single ones for some reason)
283+ // load all transcripts
284+ // (entrez sometimes misses requests for single ones for some reason)
203285 logger . info ( 'loading all transcripts' ) ;
204286 await _refseq . preLoadCache ( conn ) ;
205287
288+ // Main loop over records
206289 for ( let index = 0 ; index < jsonList . length ; index ++ ) {
207290 const sourceId = hashRecordToId ( jsonList [ index ] ) ;
208291 const record = jsonList [ index ] ;
209292 logger . verbose ( `processing (${ index } / ${ jsonList . length } ) ${ sourceId } ` ) ;
210293
294+ /** Uploading variant in CDS, protein and genomic format */
211295 let protein ,
212296 cds ,
213297 genomic ;
@@ -225,49 +309,62 @@ const uploadFile = async ({ filename, conn, errorLogPrefix }) => {
225309 }
226310
227311 try {
228- if ( protein && cds ) {
312+ genomic = await loadGenomicVariant (
313+ conn , record . chr_CGL , record . pos_CGL , record . ref , record . alt ,
314+ ) ;
315+ } catch ( err ) {
316+ logger . warn ( `failed to create genomic representation of variant (${ record . chromosome } :g.${ record . position } ${ record . ref } >${ record . alt } ): ${ err } ` ) ;
317+ }
318+
319+ /** Linking variants together with 'Infers' edges */
320+ if ( protein && cds ) {
321+ try {
229322 await conn . addRecord ( {
230323 content : { in : rid ( protein ) , out : rid ( cds ) } ,
231324 existsOk : true ,
232325 fetchExisting : false ,
233326 target : 'Infers' ,
234327 } ) ;
328+ logger . info ( `link: cds ${ rid ( cds ) } to protein ${ rid ( protein ) } ` ) ;
329+ } catch ( err ) {
330+ logger . warn ( `failed to link the protein variant to the cds one. ${ err } ` ) ;
235331 }
236- } catch ( err ) {
237- logger . warn ( `failed to link the protein variant ${ err } ` ) ;
238- }
239-
240- try {
241- genomic = await loadGenomicVariant ( conn , record . chr_CGL , record . pos_CGL , record . ref , record . alt ) ;
242- } catch ( err ) {
243- logger . warn ( `failed to create genomic representation of variant (${ record . chromosome } :g.${ record . position } ${ record . ref } >${ record . alt } ): ${ err } ` ) ;
244332 }
245333
246- try {
247- if ( genomic ) {
248- if ( cds ) {
334+ if ( genomic ) {
335+ if ( cds ) {
336+ try {
249337 await conn . addRecord ( {
250338 content : { in : rid ( cds ) , out : rid ( genomic ) } ,
251339 existsOk : true ,
252340 fetchExisting : false ,
253341 target : 'Infers' ,
254342 } ) ;
255- } else if ( protein ) {
343+ logger . info ( `link: genomic ${ rid ( genomic ) } to cds ${ rid ( cds ) } ` ) ;
344+ } catch ( err ) {
345+ logger . warn ( `failed to link the genomic variant to the cds one. ${ err } ` ) ;
346+ }
347+ }
348+
349+ if ( protein ) {
350+ try {
256351 await conn . addRecord ( {
257352 content : { in : rid ( protein ) , out : rid ( genomic ) } ,
258353 existsOk : true ,
259354 fetchExisting : false ,
260355 target : 'Infers' ,
261356 } ) ;
357+ logger . info ( `link: genomic ${ rid ( genomic ) } to protein ${ rid ( protein ) } ` ) ;
358+ } catch ( err ) {
359+ logger . warn ( `failed to link the genomic variant to the protein one. ${ err } ` ) ;
262360 }
263361 }
264- } catch ( err ) {
265- logger . warn ( `failed to link the genomic variant ${ err } ` ) ;
266362 }
267363
268364
365+ /** Loading statement */
269366 try {
270- const variant = protein || cds || genomic ;
367+ const variant = genomic || cds || protein ;
271368
272369 if ( ! variant ) {
273370 throw new Error ( 'unable to load any variants' ) ;
0 commit comments