Skip to content

Commit

Permalink
Merge pull request #152 from bcgsc/bugfix/KBDEV-1202-civic-loader-cro…
Browse files Browse the repository at this point in the history
…n-error

Update to CIViC API v2.4
  • Loading branch information
mathieulemieux authored Apr 24, 2024
2 parents 27511ed + 459369f commit 9052324
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 86 deletions.
14 changes: 10 additions & 4 deletions src/civic/evidenceItems.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,21 @@ query evidenceItems(
id
name
parsedName {
... on Gene { entrezId }
__typename
... on MolecularProfileTextSegment { text }
... on Variant { id }
}
rawName
variants {
gene {
entrezId
name
feature {
featureInstance {
__typename
... on Factor { id }
... on Gene {
entrezId
name
}
}
}
id
name
Expand Down
30 changes: 24 additions & 6 deletions src/civic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ const { EvidenceItem: evidenceSpec } = require('./specs.json');

class NotImplementedError extends ErrorMixin { }

const ajv = new Ajv();

const BASE_URL = 'https://civicdb.org/api/graphql';

/**
Expand All @@ -50,6 +48,8 @@ const VOCAB = {

const EVIDENCE_LEVEL_CACHE = {}; // avoid unecessary requests by caching the evidence levels

// Spec compiler
const ajv = new Ajv();
const validateEvidenceSpec = ajv.compile(evidenceSpec);


Expand Down Expand Up @@ -213,11 +213,28 @@ const processEvidenceRecord = async (opt) => {
conn, rawRecord, sources, variantsCache, oneToOne = false,
} = opt;

const [level, relevance, [feature]] = await Promise.all([
// Relevance & EvidenceLevel
const [level, relevance] = await Promise.all([
getEvidenceLevel(opt),
getRelevance(opt),
_entrezGene.fetchAndLoadByIds(conn, [rawRecord.variant.gene.entrezId]),
]);

// Variant's Feature
let feature;
const civicFeature = rawRecord.variant.feature.featureInstance;

if (civicFeature.__typename === 'Gene') {
[feature] = await _entrezGene.fetchAndLoadByIds(conn, [civicFeature.entrezId]);
} else if (civicFeature.__typename === 'Factor') {
// TODO: Deal with __typename === 'Factor'
// No actual case as April 22nd, 2024
throw new NotImplementedError(
'unable to process variant\'s feature of type Factor',
);
}


// Variant
let variants;

if (variantsCache.records[rawRecord.variant.id]) {
Expand All @@ -236,7 +253,6 @@ const processEvidenceRecord = async (opt) => {
}
}


// get the disease by doid
let disease;

Expand Down Expand Up @@ -382,6 +398,7 @@ const processEvidenceRecord = async (opt) => {
// update the existing record
return conn.updateRecord('Statement', rid(original), content);
}

// create a new record
return conn.addRecord({
content,
Expand Down Expand Up @@ -486,6 +503,7 @@ const downloadEvidenceRecords = async (url, trustedCurators) => {
}
records.push(record);
}
logger.info(`${records.length}/${evidenceItems.length} evidenceItem records successfully validated with the specs`);
return { counts, errorList, records };
};

Expand Down Expand Up @@ -604,7 +622,7 @@ const upload = async ({
record.conditions = Mp.process().conditions;
} catch (err) {
logger.error(`evidence (${record.id}) ${err}`);
counts.skip += 1;
counts.skip++;
continue;
}

Expand Down
4 changes: 2 additions & 2 deletions src/civic/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ const MolecularProfile = (molecularProfile) => ({
`unable to process molecular profile with NOT operator (${this.profile.id || ''})`,
);
}
// Filters out unwanted gene's info from expression
const filteredParsedName = parsedName.filter(el => !el.entrezId);
// Filters out unwanted Feature info from expression
const filteredParsedName = parsedName.filter(el => el.__typename !== 'Feature');

// Parse expression into conditions
this.conditions = this._parse(filteredParsedName);
Expand Down
122 changes: 67 additions & 55 deletions src/civic/specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,32 +84,21 @@
},
"parsedName": {
"items": {
"anyOf": [
{
"properties": {
"entrezId": {
"type": "number"
}
},
"type": "object"
"properties": {
"__typename": {
"type": "string"
},
{
"properties": {
"id": {
"type": "number"
}
},
"type": "object"
"id": {
"type": "number"
},
{
"properties": {
"text": {
"type": "string"
}
},
"type": "object"
"text": {
"type": "string"
}
]
},
"required":[
"__typename"
],
"type": "object"
},
"type": "array"
},
Expand All @@ -122,39 +111,45 @@
"variants": {
"items": {
"properties": {
"gene": {
"feature": {
"properties": {
"entrezId": {
"type": [
"null",
"number"
]
},
"name": {
"type": [
"null",
"string"
]
"featureInstance": {
"properties": {
"__typename": {
"type": "string"
},
"entrezId": {
"type": "number"
},
"name": {
"type": "string"
}
},
"required":[
"__typename",
"entrezId",
"name"
],
"type": "object"
}
},
"type": [
"null",
"object"
]
"required": [
"featureInstance"
],
"type": "object"
},
"id": {
"type": [
"null",
"number"
]
"type": "number"
},
"name": {
"type": [
"null",
"string"
]
"type": "string"
}
},
"required": [
"feature",
"id",
"name"
],
"type": [
"null",
"object"
Expand All @@ -166,10 +161,14 @@
]
}
},
"type": [
"null",
"object"
]
"required": [
"id",
"name",
"parsedName",
"rawName",
"variants"
],
"type": "object"
},
"phenotypes": {
"items": {
Expand Down Expand Up @@ -317,9 +316,22 @@
]
}
},
"type": [
"null",
"object"
]
"required":[
"description",
"disease",
"evidenceDirection",
"evidenceLevel",
"evidenceRating",
"evidenceType",
"id",
"molecularProfile",
"phenotypes",
"significance",
"source",
"status",
"therapies",
"therapyInteractionType"
],
"type": "object"
}
}
39 changes: 27 additions & 12 deletions src/civic/variant.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
const kbParser = require('@bcgsc-pori/graphkb-parser');

const { error: { ParsingError } } = kbParser;
const {
rid,
} = require('../graphkb');
const { rid } = require('../graphkb');
const _entrezGene = require('../entrez/gene');
const _snp = require('../entrez/snp');
const { civic: SOURCE_DEFN } = require('../sources');

const {
civic: SOURCE_DEFN,
} = require('../sources');
const { error: { ErrorMixin, ParsingError } } = kbParser;
class NotImplementedError extends ErrorMixin { }


// based on discussion with cam here: https://www.bcgsc.ca/jira/browse/KBDEV-844
Expand Down Expand Up @@ -229,8 +225,8 @@ const normalizeVariantRecord = ({
};

/**
* Given some normalized variant record from CIViC load into graphkb, create links and
* return the record
* Given some normalized variant record from CIViC,
* load into graphkb, create links and return the record
*
* @param {ApiConnection} conn the connection to GraphKB
* @param {Object} normalizedVariant the normalized variant record
Expand Down Expand Up @@ -328,11 +324,30 @@ const uploadNormalizedVariant = async (conn, normalizedVariant, feature) => {

/**
* Given some variant record and a feature, process the variant and return a GraphKB equivalent
*
* @param {ApiConnection} conn the connection to GraphKB
* @param {Object} civicVariantRecord the raw variant record from CIViC
* @param {Object} feature the gene feature already grabbed from GraphKB
*/
const processVariantRecord = async (conn, civicVariantRecord, feature) => {
const featureInstance = civicVariantRecord.feature.featureInstance;
let entrezId,
entrezName;

if (featureInstance.__typename === 'Gene') {
entrezId = featureInstance.entrezId;
entrezName = featureInstance.name;
} else if (featureInstance.__typename === 'Factor') {
// TODO: Deal with __typename === 'Factor'
// No actual case as April 22nd, 2024
throw new NotImplementedError(
'unable to process variant\'s feature of type Factor',
);
}

const variants = normalizeVariantRecord({
entrezId: civicVariantRecord.gene.entrezId,
entrezName: civicVariantRecord.gene.name,
entrezId,
entrezName,
name: civicVariantRecord.name,
});

Expand Down
14 changes: 7 additions & 7 deletions test/civic.profile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ describe('MolecularProfile._end()', () => {
describe('MolecularProfile._not()', () => {
test('check for presence of NOT operator in expression', () => {
expect(MolecularProfile()._not([
{ entrezId: 9 }, { id: 1 }, { text: 'AND' }, { text: 'NOT' }, { text: '(' },
{ entrezId: 9 }, { id: 2 }, { text: 'OR' }, { entrezId: 9 }, { id: 3 }, { text: ')' },
{ __typename: 'Feature' }, { id: 1 }, { text: 'AND' }, { text: 'NOT' }, { text: '(' },
{ __typename: 'Feature' }, { id: 2 }, { text: 'OR' }, { __typename: 'Feature' }, { id: 3 }, { text: ')' },
])).toBe(true);
expect(MolecularProfile()._not([
{ entrezId: 9 }, { id: 1 }, { text: 'AND' }, { text: '(' }, { entrezId: 9 },
{ id: 2 }, { text: 'OR' }, { entrezId: 9 }, { id: 3 }, { text: ')' },
{ __typename: 'Feature' }, { id: 1 }, { text: 'AND' }, { text: '(' }, { __typename: 'Feature' },
{ id: 2 }, { text: 'OR' }, { __typename: 'Feature' }, { id: 3 }, { text: ')' },
])).toBe(false);
});
});
Expand Down Expand Up @@ -187,7 +187,7 @@ describe('MolecularProfile._variants()', () => {
describe('MolecularProfile.process()', () => {
test('gene infos not interfering', () => {
expect(MolecularProfile({
parsedName: [{ entrezId: 9 }, { id: 1 }],
parsedName: [{ __typename: 'Feature' }, { id: 1 }],
variants: [{ id: 1, name: 'a1' }],
}).process().conditions).toEqual([[{ id: 1, name: 'a1' }]]);
});
Expand Down Expand Up @@ -215,8 +215,8 @@ describe('MolecularProfile.process()', () => {
const molecularProfile = {
id: 1,
parsedName: [
{ entrezId: 9 }, { id: 1 }, { text: 'AND' }, { text: 'NOT' }, { text: '(' },
{ entrezId: 9 }, { id: 2 }, { text: 'OR' }, { entrezId: 9 }, { id: 3 }, { text: ')' },
{ __typename: 'Feature' }, { id: 1 }, { text: 'AND' }, { text: 'NOT' }, { text: '(' },
{ __typename: 'Feature' }, { id: 2 }, { text: 'OR' }, { __typename: 'Feature' }, { id: 3 }, { text: ')' },
],
};
expect(() => MolecularProfile(molecularProfile).process()).toThrow(
Expand Down

0 comments on commit 9052324

Please sign in to comment.