diff --git a/packages/cbioportal-utils/src/civic/CivicDataFetcher.ts b/packages/cbioportal-utils/src/civic/CivicDataFetcher.ts index 6a5260aad3c..01e4e4f41b7 100644 --- a/packages/cbioportal-utils/src/civic/CivicDataFetcher.ts +++ b/packages/cbioportal-utils/src/civic/CivicDataFetcher.ts @@ -1,193 +1,274 @@ -import * as request from 'superagent'; - import { - EvidenceLevel, - ICivicEvidenceSummary, + ICivivEvidenceCountsByType, ICivicGeneSummary, ICivicVariantSummary, } from '../model/Civic'; +import _ from 'lodash'; -type CivicAPIGene = { - id: number; - name: string; - description: string; - variants: Array; +type CivicAPIGenes = { + pageInfo: PageInfo; + nodes: Array; }; -type CivicAPIGeneVariant = { - id: number; - name: string; - evidence_items: Evidence[]; +type PageInfo = { + endCursor: string; + hasNextPage: boolean; + startCursor: string; + hasPreviousPage: boolean; }; -type Evidence = { +type CivicAPIGene = { id: number; name: string; - evidence_type: string; - clinical_significance: string; - evidence_direction: EvidenceDirection; - evidence_level: EvidenceLevel; - drugs: Drug[]; - disease: Disease; + link: string; + description: string; + variants: CivicVariantCollection; }; -type Disease = { - id: number; - name: string; - display_name: string; - url: string; +type CivicVariantCollection = { + pageInfo: PageInfo; + nodes: Array; }; -type Drug = { +type CivicVariant = { id: number; name: string; - ncit_id: string; - aliases: string[]; + link: string; + singleVariantMolecularProfile: CivicMolecularProfile; }; -enum EvidenceDirection { - Supports = 'Supports', - DoesNotSupport = 'Does Not Support', -} +type CivicMolecularProfile = { + description: string; + evidenceCountsByType: ICivivEvidenceCountsByType; +}; -enum ClinicalSignificance { - // Clinical Significance For Predictive Evidence - Sensitivity = 'Sensitivity/Response', - Resistance = 'Resistance', - AdverseResponse = 'Adverse Response', - ReducedSensitivity = 'Reduced Sensitivity', - NA = 'N/A', +function transformCivicVariantsToEvidenceCountMap( + variants: CivicVariantCollection +): { [variantName: string]: ICivicVariantSummary } { + // Transform the variants into the desired map format + const map: { [variantName: string]: ICivicVariantSummary } = {}; + for (const variant of variants.nodes) { + map[variant.name] = { + id: variant.id, + name: variant.name, + url: 'https://civicdb.org' + variant.link + '/summary', + description: variant.singleVariantMolecularProfile.description, + evidenceCounts: + variant.singleVariantMolecularProfile.evidenceCountsByType, + }; + } + return map; } -/** - * Returns a map with the different types of evidence and the number of times that each evidence happens. - */ -function countEvidenceTypes( - evidenceItems: Evidence[] -): { [evidenceType: string]: number } { - const counts: { [evidenceType: string]: number } = {}; - - evidenceItems.forEach(function(evidenceItem: Evidence) { - const evidenceType = evidenceItem.evidence_type; - if (counts.hasOwnProperty(evidenceType)) { - counts[evidenceType] += 1; - } else { - counts[evidenceType] = 1; - } - }); - - return counts; -} +export class CivicAPI { + /** + * Retrieves the gene entries for the hugo symbols given, if they are in the Civic API. + * If more than 100 variants available for the gene, send new query for Variants. + */ -function findSupportingEvidences( - evidences: Evidence[], - filter: (evidence: Evidence) => boolean = () => true -): Evidence[] { - const filteredEvidences = evidences.filter( - evidence => - evidence.evidence_direction === EvidenceDirection.Supports && - filter(evidence) - ); - - filteredEvidences.sort((a, b) => { - const aLevel = a.evidence_level; - const bLevel = b.evidence_level; - - if (aLevel === undefined && bLevel === undefined) { - return 0; - } else if (aLevel === undefined) { - return -1; - } else if (bLevel === undefined) { - return 1; - } else if (bLevel > aLevel) { - return -1; - } else if (aLevel > bLevel) { - return 1; - } else { - return 0; + async createCivicVariantSummaryMap( + variants: CivicVariantCollection, + geneId: number + ): Promise<{ [variantName: string]: ICivicVariantSummary }> { + // Check if hasNextPage is false, if true, call fetchCivicAPIVariants and update the map + if (!variants.pageInfo.hasNextPage) { + return transformCivicVariantsToEvidenceCountMap(variants); } - }); - return filteredEvidences; -} + // Call fetchCivicAPIVariants with geneId + let after: string | null = null; + let civicVariantSummaryMap: { + [variantName: string]: ICivicVariantSummary; + } = {}; + let needToFetch = true; -function summarizeEvidence(evidence: Evidence): ICivicEvidenceSummary { - return { - id: evidence.id, - type: evidence.evidence_type, - clinicalSignificance: evidence.clinical_significance, - level: evidence.evidence_level, - drugs: (evidence.drugs || []).map(d => d.name), - disease: evidence.disease?.display_name || evidence.disease?.name, - }; -} + while (needToFetch) { + const variantsByGeneId: CivicVariantCollection = await this.fetchCivicAPIVariants( + geneId, + after + ); + civicVariantSummaryMap = { + ...civicVariantSummaryMap, + ...transformCivicVariantsToEvidenceCountMap(variantsByGeneId), + }; -/** - * Returns a map with the different variant names and their variant id. - */ -function createVariantMap( - variantArray: CivicAPIGeneVariant[] -): { [variantName: string]: number } { - let variantMap: { [variantName: string]: number } = {}; - if (variantArray && variantArray.length > 0) { - variantArray.forEach(function(variant) { - variantMap[variant.name] = variant.id; - }); + if (variantsByGeneId.pageInfo.hasNextPage) { + after = variantsByGeneId.pageInfo.endCursor; + } else { + needToFetch = false; + } + } + return civicVariantSummaryMap; } - return variantMap; -} -/** - * CIViC - */ -export class CivicAPI { - /** - * Retrieves the gene entries for the ids given, if they are in the Civic API. - */ - getCivicGenesBatch(ids: string): Promise { - return request - .get('https://civicdb.org/api/genes/' + ids) - .query({ identifier_type: 'entrez_id' }) - .then(res => { - const response = res.body; - const result: CivicAPIGene[] = - response instanceof Array ? response : [response]; - return result.map((record: CivicAPIGene) => ({ + async getCivicGeneSummaries( + hugoGeneSymbols: string[] + ): Promise { + let result: ICivicGeneSummary[] = []; + // civic genes api can return up to 100 civic genes in a single request (no limit on sending) + // if result contains more than 100 genes, the first 100 genes will be returned, other queries (genes after 100) will need to be sent again + // the next query will start from the last gene of previous query (by giving "after" parameter), which is the "endCursor" field returned in previous query + // set needToFetch to true to make sure the query will be sent at least for the first time + let needToFetch = true; + let after = null; + while (needToFetch) { + const civicGenes: CivicAPIGenes = await this.fetchCivicAPIGenes( + hugoGeneSymbols, + after + ); + // hasNextPage is true means there are more than 100 genes in the return, and need to make another request + if (civicGenes.pageInfo?.hasNextPage) { + // Update endCursor when next page is available + after = civicGenes.pageInfo.endCursor; + needToFetch = true; + } else { + needToFetch = false; + } + const filteredCivicGenes = _.compact(civicGenes.nodes); + const geneSummaries = _.map(filteredCivicGenes, async record => { + const variants = await this.createCivicVariantSummaryMap( + record.variants, + record.id + ); + const geneSummary: ICivicGeneSummary = { id: record.id, name: record.name, description: record.description, url: 'https://civicdb.org/genes/' + record.id + '/summary', - variants: createVariantMap(record.variants), - })); + variants, + }; + return geneSummary; }); + result.push(...(await Promise.all(geneSummaries))); + } + return Promise.resolve(result); } - /** - * Returns a promise that resolves with the variants for the parameters given. - */ - getCivicVariantSummary( - id: number, - name: string, - geneId: number - ): Promise { - return request - .get('https://civicdb.org/api/variants/' + id) + // Call Variants if have more than 100 variants. The Variants query can return up to 300 variants in a single query. + fetchCivicAPIVariants( + geneId: number, + after: string | null + ): Promise { + const url = 'https://civicdb.org/api/graphql'; + const headers = { + 'content-type': 'application/json', + }; + const body = JSON.stringify({ + variables: { + geneId: geneId, + after: after, + }, + query: `query variants($after: String, $geneId: Int) { + variants(after: $after, geneId: $geneId) { + pageInfo { + endCursor + hasNextPage + startCursor + hasPreviousPage + } + nodes { + id + name + link + singleVariantMolecularProfile { + description + evidenceCountsByType { + diagnosticCount + predictiveCount + prognosticCount + predisposingCount + oncogenicCount + functionalCount + } + } + } + } + }`, + }); + return fetch(url, { + headers, + body, + method: 'POST', + }) .then(response => { - const result = response.body; - const supportingEvidences = findSupportingEvidences( - result.evidence_items - ); + if (!response.ok) { + throw new Error(`Civic error! status: ${response.status}`); + } + return response.json(); + }) + .then(result => + Promise.resolve(result.data.variants as CivicVariantCollection) + ); + } - return { - id, - name, - geneId, - description: result.description, - url: 'https://civicdb.org/variants/' + id + '/summary', - evidenceCounts: countEvidenceTypes(result.evidence_items), - evidences: supportingEvidences.map(summarizeEvidence), - }; - }); + fetchCivicAPIGenes( + entrezSymbols: string[], + after: string | null + ): Promise { + const url = 'https://civicdb.org/api/graphql'; + const headers = { + 'content-type': 'application/json', + }; + const body = JSON.stringify({ + variables: { + entrezSymbols: entrezSymbols, + after: after, + }, + query: `query genes($after: String, $entrezSymbols: [String!]) { + genes(after: $after, entrezSymbols: $entrezSymbols) { + pageInfo { + endCursor + hasNextPage + startCursor + hasPreviousPage + } + nodes { + id + description + link + name + variants { + pageInfo { + endCursor + hasNextPage + startCursor + hasPreviousPage + } + nodes { + name + id + link + singleVariantMolecularProfile { + description + evidenceCountsByType { + diagnosticCount + predictiveCount + prognosticCount + predisposingCount + oncogenicCount + functionalCount + } + } + } + } + } + } + }`, + }); + return fetch(url, { + headers, + body, + method: 'POST', + }) + .then(response => { + if (!response.ok) { + throw new Error(`Civic error! status: ${response.status}`); + } + return response.json(); + }) + .then(result => + Promise.resolve(result.data.genes as CivicAPIGenes) + ); } } diff --git a/packages/cbioportal-utils/src/civic/CivicUtils.spec.ts b/packages/cbioportal-utils/src/civic/CivicUtils.spec.ts new file mode 100644 index 00000000000..4a8510147b5 --- /dev/null +++ b/packages/cbioportal-utils/src/civic/CivicUtils.spec.ts @@ -0,0 +1,196 @@ +import { assert } from 'chai'; +import { ICivicGeneIndex, ICivicVariantIndex } from '../model/Civic'; +import { + MutationSpec, + getCivicVariants, + splitProteinChange, +} from './CivicUtils'; + +describe('CivicUtils', () => { + const proteinChangeWithSplittingSymbol = + 'proteinchange1/proteinchange2+proteinchange3'; + const proteinChangeWithoutSplittingSymbol = 'protein.change'; + + describe('splitProteinChange', () => { + it('Match any other variants after splitting the name on + or /', () => { + const splittedProteinChanges = splitProteinChange( + proteinChangeWithSplittingSymbol + ); + + assert.equal( + splittedProteinChanges.length, + 3, + 'Split by "/" and "+"' + ); + + assert.equal( + splittedProteinChanges[0], + 'proteinchange1', + 'First protein change is "proteinchange1"' + ); + + assert.equal( + splittedProteinChanges[1], + 'proteinchange2', + 'Second protein change is "proteinchange2"' + ); + + assert.equal( + splittedProteinChanges[2], + 'proteinchange3', + 'Third protein change is "proteinchange3"' + ); + }); + + it('Returns protein change when no + or /', () => { + const splittedProteinChanges = splitProteinChange( + proteinChangeWithoutSplittingSymbol + ); + + assert.equal(splittedProteinChanges.length, 1, 'No + or / matched'); + + assert.equal( + splittedProteinChanges[0], + 'protein.change', + 'Protein change is "protein.change"' + ); + }); + }); + + describe('getCivicVariants', () => { + const civicGenes: ICivicGeneIndex = { + PIK3CA: { + id: 37, + name: 'PIK3CA', + description: + "PIK3CA is the most recurrently mutated gene in breast cancer, and has been found to important in a number of cancer types. An integral part of the PI3K pathway, PIK3CA has long been described as an oncogene, with two main hotspots for activating mutations, the 542/545 region of the helical domain, and the 1047 region of the kinase domain. PIK3CA, and its interaction with the AKT and mTOR pathways, is the subject of an immense amount of research and development, and PI3K inhibition has seen some limited success in recent clinical trials. While monotherapies seem to be limited in their potential, there is a recent interest in pursuing PI3K inhibition as part of a combination therapy regiment with inhibition partners including TKI's, MEK inhibitors, PARP inhibitors, and in breast cancer, aromatase inhibitors.", + url: 'https://civicdb.org/genes/37/summary', + variants: { + E545K: { + id: 104, + name: 'E545K', + geneId: 37, + description: + 'PIK3CA E545K/E542K are the second most recurrent PIK3CA mutations in breast cancer, and are highly recurrent mutations in many other cancer types. E545K, and possibly the other mutations in the E545 region, may present patients with a poorer prognosis than patients with either patients with other PIK3CA variant or wild-type PIK3CA. There is also data to suggest that E545/542 mutations may confer resistance to EGFR inhibitors like cetuximab. While very prevalent, targeted therapies for variants in PIK3CA are still in early clinical trial phases.', + url: 'https://civicdb.org/variants/104/summary', + evidenceCounts: { + prognosticCount: 1, + predictiveCount: 14, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, + }, + }, + }, + } as any; + + const civicCnaGenes: ICivicGeneIndex = { + RAF1: { + id: 4767, + name: 'RAF1', + description: '', + url: 'https://civicdb.org/genes/4767/summary', + variants: { + id: 591, + name: 'AMPLIFICATION', + geneId: 4767, + description: '', + url: 'https://civicdb.org/variants/591/summary', + evidenceCounts: { + prognosticCount: 1, + predictiveCount: 1, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, + }, + }, + } as any; + + const cnaCivicVariants: ICivicVariantIndex = { + RAF1: { + AMPLIFICATION: { + id: 591, + name: 'AMPLIFICATION', + description: '', + url: 'https://civicdb.org/variants/591/summary', + evidenceCounts: { + prognosticCount: 1, + predictiveCount: 1, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, + }, + }, + }; + + const mutationCivicVariants: ICivicVariantIndex = { + PIK3CA: { + E545K: { + id: 104, + name: 'E545K', + description: + 'PIK3CA E545K/E542K are the second most recurrent PIK3CA mutations in breast cancer, and are highly recurrent mutations in many other cancer types. E545K, and possibly the other mutations in the E545 region, may present patients with a poorer prognosis than patients with either patients with other PIK3CA variant or wild-type PIK3CA. There is also data to suggest that E545/542 mutations may confer resistance to EGFR inhibitors like cetuximab. While very prevalent, targeted therapies for variants in PIK3CA are still in early clinical trial phases.', + url: 'https://civicdb.org/variants/104/summary', + evidenceCounts: { + prognosticCount: 1, + predictiveCount: 14, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, + }, + }, + }; + + const mutationData: MutationSpec[] = [ + { + gene: { + hugoGeneSymbol: 'PIK3CA', + }, + proteinChange: 'E545K', + }, + ]; + + it('Returns civicVariants map for PIK3CA', () => { + const civicVariantsPromise = getCivicVariants( + civicGenes, + mutationData + ); + civicVariantsPromise + .then(civicVariants => { + assert.equal( + civicVariants, + mutationCivicVariants, + 'PIK3CA E545K civic variants' + ); + }) + .catch(() => { + /*do nothing*/ + }); + }); + it('Returns civicVariants map for CNA', () => { + const civicVariantsPromise = getCivicVariants( + civicCnaGenes, + undefined + ); + civicVariantsPromise + .then(civicVariants => { + assert.equal( + civicVariants, + cnaCivicVariants, + 'CNA civic variants' + ); + }) + .catch(() => { + /*do nothing*/ + }); + }); + }); +}); diff --git a/packages/cbioportal-utils/src/civic/CivicUtils.ts b/packages/cbioportal-utils/src/civic/CivicUtils.ts index 77196a62ca8..f923b672d5c 100644 --- a/packages/cbioportal-utils/src/civic/CivicUtils.ts +++ b/packages/cbioportal-utils/src/civic/CivicUtils.ts @@ -24,131 +24,93 @@ export enum CivicAlterationType { export const CIVIC_NA_VALUE = 'NA'; -/** - * Asynchronously adds the given variant from a gene to the variant map specified. - */ -function lookupCivicVariantAndAddToMap( - variantMap: ICivicVariantIndex, - variantId: number, - variantName: string, - geneSymbol: string, - geneId: number -): Promise { - return civicClient - .getCivicVariantSummary(variantId, variantName, geneId) - .then((result: ICivicVariantSummary) => { - if (result) { - if (!variantMap[geneSymbol]) { - variantMap[geneSymbol] = {}; - } - variantMap[geneSymbol][variantName] = result; - } - }); -} - /** * Asynchronously return a map with Civic information from the genes given. */ -export function getCivicGenes( - entrezGeneIds: number[] -): Promise { - // Assemble a list of promises, each of which will retrieve a batch of genes - let promises: Array>> = []; - - // To prevent the request from growing too large, we send it off in multiple chunks - const chunkedIds: number[][] = _.chunk(_.uniq(entrezGeneIds), 400); - - chunkedIds.forEach(entrezGeneIds => - promises.push(civicClient.getCivicGenesBatch(entrezGeneIds.join(','))) +export function getCivicGenes(hugoSymbols: string[]): Promise { + return Promise.resolve(civicClient.getCivicGeneSummaries(hugoSymbols)).then( + (responses: ICivicGeneSummary[]) => { + return responses.reduce( + ( + acc: { [name: string]: ICivicGeneSummary }, + civicGene: ICivicGeneSummary + ) => { + acc[civicGene.name] = civicGene; + return acc; + }, + {} + ); + } ); - - // We're waiting for all promises to finish, then return civicGenes - return Promise.all(promises).then((responses: ICivicGeneSummary[][]) => { - return responses.reduce( - ( - acc: { [name: string]: ICivicGeneSummary }, - civicGenes: ICivicGeneSummary[] - ) => { - civicGenes.forEach( - civicGene => (acc[civicGene.name] = civicGene) - ); - return acc; - }, - {} - ); - }); } /** * Asynchronously retrieve a map with Civic information from the mutationSpecs given for all genes in civicGenes. * If no mutationSpecs are given, then return the Civic information of all the CNA variants of the genes in civicGenes. */ +export function splitProteinChange(proteinChange: string): string[] { + // Match any other variants after splitting the name on + or / + return proteinChange.split(/[+\/]/); +} + export function getCivicVariants( civicGenes: ICivicGeneIndex, mutationSpecs?: Array ): Promise { let civicVariants: ICivicVariantIndex = {}; - let promises: Array> = []; - if (mutationSpecs) { - let calledVariants: Set = new Set([]); - for (let mutation of mutationSpecs) { - let geneSymbol = mutation.gene.hugoGeneSymbol; - let geneEntry = civicGenes[geneSymbol]; - let proteinChanges = [mutation.proteinChange]; - // Match any other variants after splitting the name on + or / - let split = mutation.proteinChange.split(/[+\/]/); - proteinChanges.push(split[0]); - for (let proteinChange of proteinChanges) { - if (geneEntry && geneEntry.variants[proteinChange]) { - if ( - !calledVariants.has(geneEntry.variants[proteinChange]) - ) { - //Avoid calling the same variant - calledVariants.add(geneEntry.variants[proteinChange]); - promises.push( - lookupCivicVariantAndAddToMap( - civicVariants, - geneEntry.variants[proteinChange], - proteinChange, - geneSymbol, - geneEntry.id - ) - ); + const geneToProteinChangeSet: { + [geneSymbol: string]: Set; + } = mutationSpecs.reduce((acc, mutation) => { + const geneSymbol = mutation.gene.hugoGeneSymbol; + const splittedProteinChanges = splitProteinChange( + mutation.proteinChange + ); + if (!acc[geneSymbol]) { + acc[geneSymbol] = new Set(splittedProteinChanges); + } else { + for (const splitProteinChange of splittedProteinChanges) { + acc[geneSymbol].add(splitProteinChange); + } + } + return acc; + }, {} as { [geneSymbol: string]: Set }); + + // civicGenes is fetched from civic by giving mutation gene symbols as input + // so all genes in the civicGenes should be in geneToProteinChangeSet too + for (const geneSymbol in civicGenes) { + const proteinChangeSet = geneToProteinChangeSet[geneSymbol]; + const geneVariants = civicGenes[geneSymbol].variants; + for (const variantName in geneVariants) { + if (proteinChangeSet.has(variantName)) { + if (!civicVariants[geneSymbol]) { + civicVariants[geneSymbol] = {}; } + civicVariants[geneSymbol][variantName] = + geneVariants[variantName]; } } } } else { - for (let geneName in civicGenes) { - let geneEntry = civicGenes[geneName]; - let geneVariants = geneEntry.variants; + for (const geneSymbol in civicGenes) { + const geneEntry = civicGenes[geneSymbol]; + const geneVariants = geneEntry.variants; if (!_.isEmpty(geneVariants)) { - for (let variantName in geneVariants) { + for (const variantName in geneVariants) { // Only retrieve CNA variants if ( variantName == CivicAlterationType.AMPLIFICATION || variantName == CivicAlterationType.DELETION ) { - promises.push( - lookupCivicVariantAndAddToMap( - civicVariants, - geneVariants[variantName], - variantName, - geneName, - geneEntry.id - ) - ); + civicVariants[geneSymbol][variantName] = + geneVariants[variantName]; } } } } } - // We're explicitly waiting for all promises to finish (done or fail). - // We are wrapping them in another promise separately, to make sure we also - // wait in case one of the promises fails and the other is still busy. - return Promise.all(promises).then(() => civicVariants); + return Promise.resolve(civicVariants); } /** @@ -194,17 +156,16 @@ export function getCivicEntry( } export function fetchCivicGenes( - mutations: Partial[], - getEntrezGeneId: (mutation: Partial) => number + mutations: Partial[] ): Promise { if (mutations.length === 0) { return Promise.resolve({}); } - - const entrezGeneSymbols = _.uniq( - mutations.map(mutation => getEntrezGeneId(mutation)) - ); - + const entrezGeneSymbols = _.chain(mutations) + .map(mutation => mutation.gene?.hugoGeneSymbol) + .compact() + .uniq() + .value(); return getCivicGenes(entrezGeneSymbols); } diff --git a/packages/cbioportal-utils/src/model/Civic.ts b/packages/cbioportal-utils/src/model/Civic.ts index 4ed79c26c07..52ff99fd9a7 100644 --- a/packages/cbioportal-utils/src/model/Civic.ts +++ b/packages/cbioportal-utils/src/model/Civic.ts @@ -3,34 +3,23 @@ export interface ICivicGeneSummary { name: string; description: string; url: string; - variants: { [variantName: string]: number }; + variants: { [variantName: string]: ICivicVariantSummary }; } export interface ICivicVariantSummary { id: number; name: string; - geneId: number; - description: string; url: string; - evidenceCounts: { [evidenceType: string]: number }; - evidences: ICivicEvidenceSummary[]; -} - -export enum EvidenceLevel { - A = 'A', - B = 'B', - C = 'C', - D = 'D', - E = 'E', + description: string; + evidenceCounts: ICivivEvidenceCountsByType; } - -export interface ICivicEvidenceSummary { - id: number; - type: string; - clinicalSignificance: string; - level: EvidenceLevel; - drugs: string[]; - disease?: string; +export interface ICivivEvidenceCountsByType { + diagnosticCount?: number; + predictiveCount?: number; + prognosticCount?: number; + predisposingCount?: number; + oncogenicCount?: number; + functionalCount?: number; } export interface ICivicGeneIndex { diff --git a/packages/react-mutation-mapper/src/component/civic/CivicCard.spec.tsx b/packages/react-mutation-mapper/src/component/civic/CivicCard.spec.tsx index 8e737dc23c5..ce27c039898 100644 --- a/packages/react-mutation-mapper/src/component/civic/CivicCard.spec.tsx +++ b/packages/react-mutation-mapper/src/component/civic/CivicCard.spec.tsx @@ -8,11 +8,11 @@ export function getCivicVariantData() { return { id: 0, name: 'variantdata', - geneId: 124, description: 'descr', url: 'http://', - evidenceCounts: { type1: 1 }, - evidences: [], + evidenceCounts: { + predictiveCount: 1, + }, }; } diff --git a/packages/react-mutation-mapper/src/component/civic/CivicCard.tsx b/packages/react-mutation-mapper/src/component/civic/CivicCard.tsx index 63ec676d593..e962e01c510 100644 --- a/packages/react-mutation-mapper/src/component/civic/CivicCard.tsx +++ b/packages/react-mutation-mapper/src/component/civic/CivicCard.tsx @@ -29,16 +29,10 @@ export default class CivicCard extends React.Component { } else { for (let name in variantMap) { let variant = variantMap[name]; - let entryTypes: string = ''; - for (let evidenceType in variant.evidenceCounts) { - entryTypes += - evidenceType.toLowerCase() + - ': ' + - variant.evidenceCounts[evidenceType] + - ', '; - } - entryTypes = entryTypes.slice(0, -2) + '.'; - + let entryTypes = Object.entries(variant.evidenceCounts) + .filter(([key, value]) => value !== 0) + .map(([key, value]) => `${key.slice(0, -5)}: ${value}`) // Remove "Count" substring from key + .join(', '); list.push( this.variantItem( name, diff --git a/packages/react-mutation-mapper/src/store/DefaultMutationMapperStore.ts b/packages/react-mutation-mapper/src/store/DefaultMutationMapperStore.ts index d6c8b08ca9d..e2acadcc53c 100644 --- a/packages/react-mutation-mapper/src/store/DefaultMutationMapperStore.ts +++ b/packages/react-mutation-mapper/src/store/DefaultMutationMapperStore.ts @@ -1031,10 +1031,7 @@ class DefaultMutationMapperStore await: () => [this.mutationData], invoke: async () => this.config.enableCivic - ? fetchCivicGenes( - this.mutationData.result || [], - this.getDefaultEntrezGeneId - ) + ? fetchCivicGenes(this.mutationData.result || []) : {}, onError: () => { // fail silently diff --git a/src/shared/lib/CivicUtils.ts b/src/shared/lib/CivicUtils.ts index 545aa8c5906..fbc44635ff5 100644 --- a/src/shared/lib/CivicUtils.ts +++ b/src/shared/lib/CivicUtils.ts @@ -47,10 +47,7 @@ export function fetchCivicGenes( uncalledMutationData ); - return fetchDefaultCivicGenes( - mutationDataResult, - (m: Mutation) => m.gene.entrezGeneId - ); + return fetchDefaultCivicGenes(mutationDataResult); } export function fetchCivicVariants( @@ -70,13 +67,12 @@ export function fetchCnaCivicGenes( discreteCNAData: MobxPromise ): Promise { if (discreteCNAData.result && discreteCNAData.result.length > 0) { - let entrezGeneSymbols: Set = new Set([]); - + let hugoGeneSymbols: Set = new Set([]); discreteCNAData.result.forEach(function(cna: DiscreteCopyNumberData) { - entrezGeneSymbols.add(cna.gene.entrezGeneId); + hugoGeneSymbols.add(cna.gene.hugoGeneSymbol); }); - let querySymbols: Array = Array.from(entrezGeneSymbols); + let querySymbols: Array = Array.from(hugoGeneSymbols); return getCivicGenes(querySymbols); } else { diff --git a/src/test/CivicMockUtils.ts b/src/test/CivicMockUtils.ts index 81a71eb7965..5ce9817adb7 100644 --- a/src/test/CivicMockUtils.ts +++ b/src/test/CivicMockUtils.ts @@ -10,11 +10,16 @@ export function getCivicVariantData(): ICivicVariantSummary { return { id: 0, name: 'variantdata', - geneId: 124, description: 'descr', url: 'http://', - evidenceCounts: { type1: 1 }, - evidences: [], + evidenceCounts: { + predisposingCount: 1, + diagnosticCount: 0, + predictiveCount: 0, + prognosticCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, }; } @@ -27,31 +32,22 @@ export function getCivicGenes(): ICivicGeneIndex { "PIK3CA is the most recurrently mutated gene in breast cancer, and has been found to important in a number of cancer types. An integral part of the PI3K pathway, PIK3CA has long been described as an oncogene, with two main hotspots for activating mutations, the 542/545 region of the helical domain, and the 1047 region of the kinase domain. PIK3CA, and its interaction with the AKT and mTOR pathways, is the subject of an immense amount of research and development, and PI3K inhibition has seen some limited success in recent clinical trials. While monotherapies seem to be limited in their potential, there is a recent interest in pursuing PI3K inhibition as part of a combination therapy regiment with inhibition partners including TKI's, MEK inhibitors, PARP inhibitors, and in breast cancer, aromatase inhibitors.", url: 'https://civicdb.org/genes/37/summary', variants: { - AMPLIFICATION: 212, - C420R: 931, - E542K: 103, - E542Q: 933, - E545A: 882, - E545D: 934, - E545G: 883, - E545K: 104, - E545Q: 881, - E545V: 884, - 'EXON 10 MUTATION': 106, - 'EXON 21 MUTATION': 105, - G1049R: 940, - G1049S: 939, - H1047L: 1151, - H1047R: 107, - H1047Y: 938, - I391M: 1235, - K111N: 1234, - M1043I: 937, - MUTATION: 311, - P471L: 294, - Q546E: 886, - Q546K: 885, - Y1021C: 935, + H1047R: { + id: 107, + name: 'H1047R', + geneId: 37, + description: + 'PIK3CA H1047R is one of the most recurrent single nucleotide variants in cancer, especially breast cancer. Of PIK3CA-mutant breast cancers, over half harbor this mutation. Meta-analyses have shown that patients harboring this mutation may have worse overall survival, but other studies have shown no difference between H1047R and other PIK3CA mutants from a prognostic standpoint. While very prevalent, targeted therapies for this particular mutation are still in early clinical trial phases.', + url: 'https://civicdb.org/variants/107/summary', + evidenceCounts: { + predictiveCount: 34, + functionalCount: 1, + prognosticCount: 2, + diagnosticCount: 1, + predisposingCount: 0, + oncogenicCount: 0, + }, + }, }, }, RAF1: { @@ -59,9 +55,25 @@ export function getCivicGenes(): ICivicGeneIndex { name: 'RAF1', description: '', url: 'https://civicdb.org/genes/4767/summary', - variants: { AMPLIFICATION: 591 }, + variants: { + R391W: { + id: 1680, + name: 'R391W', + geneId: 4767, + description: '', + url: 'https://civicdb.org/variants/1680/summary', + evidenceCounts: { + predictiveCount: 1, + functionalCount: 1, + prognosticCount: 0, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + }, + }, + }, }, - }; + } as any; } export function getCnaCivicVariants(): ICivicVariantIndex { @@ -70,11 +82,16 @@ export function getCnaCivicVariants(): ICivicVariantIndex { AMPLIFICATION: { id: 591, name: 'AMPLIFICATION', - geneId: 4767, description: '', url: 'https://civicdb.org/variants/591/summary', - evidenceCounts: { Predictive: 1 }, - evidences: [], + evidenceCounts: { + prognosticCount: 1, + predictiveCount: 1, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, }, }, }; @@ -90,12 +107,17 @@ export function getMutationCivicVariants(): ICivicVariantIndex { E545K: { id: 104, name: 'E545K', - geneId: 37, description: 'PIK3CA E545K/E542K are the second most recurrent PIK3CA mutations in breast cancer, and are highly recurrent mutations in many other cancer types. E545K, and possibly the other mutations in the E545 region, may present patients with a poorer prognosis than patients with either patients with other PIK3CA variant or wild-type PIK3CA. There is also data to suggest that E545/542 mutations may confer resistance to EGFR inhibitors like cetuximab. While very prevalent, targeted therapies for variants in PIK3CA are still in early clinical trial phases.', url: 'https://civicdb.org/variants/104/summary', - evidenceCounts: { Prognostic: 1, Predictive: 14 }, - evidences: [], + evidenceCounts: { + prognosticCount: 1, + predictiveCount: 14, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, }, }, }; @@ -191,12 +213,17 @@ export function getExpectedCivicEntry(): ICivicEntry { E545K: { id: 104, name: 'E545K', - geneId: 37, description: 'PIK3CA E545K/E542K are the second most recurrent PIK3CA mutations in breast cancer, and are highly recurrent mutations in many other cancer types. E545K, and possibly the other mutations in the E545 region, may present patients with a poorer prognosis than patients with either patients with other PIK3CA variant or wild-type PIK3CA. There is also data to suggest that E545/542 mutations may confer resistance to EGFR inhibitors like cetuximab. While very prevalent, targeted therapies for variants in PIK3CA are still in early clinical trial phases.', url: 'https://civicdb.org/variants/104/summary', - evidenceCounts: { Prognostic: 1, Predictive: 14 }, - evidences: [], + evidenceCounts: { + prognosticCount: 1, + predictiveCount: 14, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, }, }, }; @@ -211,11 +238,16 @@ export function getExpectedCnaCivicEntry(): ICivicEntry { RAF1: { id: 591, name: 'AMPLIFICATION', - geneId: 4767, description: '', url: 'https://civicdb.org/variants/591/summary', - evidenceCounts: { Predictive: 1 }, - evidences: [], + evidenceCounts: { + predictiveCount: 1, + prognosticCount: 1, + diagnosticCount: 0, + predisposingCount: 0, + oncogenicCount: 0, + functionalCount: 0, + }, }, }, };