Skip to content

Commit 871c33c

Browse files
Merge pull request #186 from bcgsc/bugfix/KBDEV-1333-update-cgl-loader
Update CGL loader
2 parents fd6c2fd + 434d93c commit 871c33c

1 file changed

Lines changed: 158 additions & 61 deletions

File tree

src/cgl/index.js

Lines changed: 158 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable one-var */
12
const fs = require('fs');
23

34
const { jsonifyVariant, parseVariant } = require('@bcgsc-pori/graphkb-parser');
@@ -7,53 +8,108 @@ const {
78
hashRecordToId,
89
} = require('../util');
910
const {
11+
// eslint-disable-next-line no-unused-vars
12+
ApiConnection,
1013
orderPreferredOntologyTerms,
1114
rid,
1215
} = require('../graphkb');
1316
const _refseq = require('../entrez/refseq');
1417
const { 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
*/
185248
const 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

Comments
 (0)