Skip to content

Commit

Permalink
feat: adding api/mehari (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe authored Jan 29, 2024
1 parent b29e215 commit 6709873
Show file tree
Hide file tree
Showing 9 changed files with 946 additions and 0 deletions.
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"dependencies": {
"@mdi/font": "^7.4.47",
"@protobuf-ts/runtime": "^2.9.3",
"@reactgular/chunks": "^1.0.1",
"@types/luxon": "^3.4.2",
"luxon": "^3.4.4",
"title-case": "^4.3.1",
Expand Down Expand Up @@ -97,6 +98,7 @@
"vite-plugin-sass-dts": "^1.3.17",
"vitest": "^1.2.2",
"vitest-canvas-mock": "^0.3.3",
"vitest-fetch-mock": "^0.2.2",
"vue-tsc": "^1.8.27"
}
}
125 changes: 125 additions & 0 deletions src/api/mehari/client.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import fs from 'fs'
import path from 'path'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import createFetchMock from 'vitest-fetch-mock'

import { SeqvarImpl } from '@/lib/genomicVars'

import { LinearStrucvarImpl } from '../../lib/genomicVars'
import { MehariClient } from './client'
import { SeqvarResult, StrucvarResult } from './types'

/** Fixture Seqvar */
const seqvar = new SeqvarImpl('grch37', '1', 123, 'A', 'G')

/** Fixture with BRCA1 seqvar consequence. */
const seqvarCsqResponseBrca1 = JSON.parse(
fs.readFileSync(path.resolve(__dirname, './fixture.seqvarCsqResponse.BRCA1.json'), 'utf8')
)

/** Fixture Strucvar affecting BRCA1 */
const strucvar = new LinearStrucvarImpl('DEL', 'grch37', 'chr17', 43044295, 43044297)

/** Fixture with strucvar (BRCA1) consequence */
const strucvarCsqResponseBrca1 = JSON.parse(
fs.readFileSync(path.resolve(__dirname, './fixture.strucvarCsqResponse.BRCA1.json'), 'utf8')
)

/** Initialize mock for `fetch()`. */
const fetchMocker = createFetchMock(vi)

describe.concurrent('MehariClient/seqvar', () => {
beforeEach(() => {
fetchMocker.enableMocks()
fetchMocker.resetMocks()
})

it('fetches TxCsq info correctly', async () => {
// arrange:
fetchMocker.mockResponseOnce(JSON.stringify(seqvarCsqResponseBrca1))

// act:
const client = new MehariClient()
const result = await client.retrieveSeqvarsCsq(seqvar, 'HGNC:1100')

// assert:
expect(JSON.stringify(result)).toEqual(
JSON.stringify(SeqvarResult.fromJson(seqvarCsqResponseBrca1))
)
})

it('fetches TxCsq info correctly without HGNC id', async () => {
// arrange:
fetchMocker.mockResponseOnce(JSON.stringify(seqvarCsqResponseBrca1))

// act:
const client = new MehariClient()
const result = await client.retrieveSeqvarsCsq(seqvar)

// assert:
expect(JSON.stringify(result)).toEqual(
JSON.stringify(SeqvarResult.fromJson(seqvarCsqResponseBrca1))
)
})

it('fails to fetch variant info with wrong variant', async () => {
// arrange:
const seqVarInvalid = new SeqvarImpl('grch37', '1', 123, 'A', 'T')
fetchMocker.mockResponse((req) => {
if (req.url.includes('alternative=G')) {
return Promise.resolve(JSON.stringify(seqvarCsqResponseBrca1))
}
return Promise.reject('failed to fetch seqvar')
})

// act:
const client = new MehariClient()
// (with guard)
await expect(async () => await client.retrieveSeqvarsCsq(seqVarInvalid)).rejects.toThrow(
'failed to fetch seqvar'
)

// assert:
})
})

describe.concurrent('MehariClient/strucvar', () => {
beforeEach(() => {
fetchMocker.enableMocks()
fetchMocker.resetMocks()
})

it('fetches strucvar info correctly', async () => {
// arrange:
fetchMocker.mockResponseOnce(JSON.stringify(strucvarCsqResponseBrca1))

// act:
const client = new MehariClient()
const result = await client.retrieveStrucvarsCsq(strucvar)

// assert:
expect(JSON.stringify(result)).toEqual(
JSON.stringify(StrucvarResult.fromJson(strucvarCsqResponseBrca1))
)
})

it('fails to fetch variant info with wrong variant', async () => {
// arrange:
const strucVarInvalid = new LinearStrucvarImpl('DUP', 'grch37', 'chr17', 43044295, 43044297)
fetchMocker.mockResponse((req) => {
if (req.url.includes('DEL')) {
return Promise.resolve(JSON.stringify(strucvarCsqResponseBrca1))
}
return Promise.reject('failed to fetch strucvar')
})

// act:
const client = new MehariClient()
// (with guard)
await expect(async () => await client.retrieveStrucvarsCsq(strucVarInvalid)).rejects.toThrow(
'failed to fetch strucvar'
)

// assert:
})
})
65 changes: 65 additions & 0 deletions src/api/mehari/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { LinearStrucvar, Seqvar } from '../../lib/genomicVars'
import { SeqvarResult, StrucvarResult } from './types'

/** API base URL to use. */
const API_BASE_URL = '/internal/proxy/pubtator3-api'

/**
* Client for Mehari API requests.
*/
export class MehariClient {
private apiBaseUrl: string

constructor(apiBaseUrl?: string) {
this.apiBaseUrl = apiBaseUrl ?? API_BASE_URL
}

/**
* Retrieve consequences for sequence variants.
*
* @param seqvar Sequence variant to retrieve consequences for.
* @param hgncId HGNC ID of gene to restrict results to.
* @returns The response from the API.
* @throws Error if the API request fails.
*/
async retrieveSeqvarsCsq(seqvar: Seqvar, hgncId?: string): Promise<SeqvarResult> {
const { genomeBuild, chrom, pos, del, ins } = seqvar
const hgncSuffix = hgncId ? `&hgnc_id=${hgncId}` : ''
const url =
`${this.apiBaseUrl}seqvars/csq?genome_release=${genomeBuild}&` +
`chromosome=${chrom}&position=${pos}&reference=${del}&` +
`alternative=${ins}${hgncSuffix}`

const response = await fetch(url, {
method: 'GET'
})
if (!response.ok) {
throw new Error(`Failed to fetch sequence variant consequences: ${response.statusText}`)
}
const responseJson = await response.json()
return SeqvarResult.fromJson(responseJson)
}

/**
* Retrieve consequences for structural variants.
*
* @param strucvar Structural variant to retrieve consequences for.
* @returns The response from the API.
* @throws Error if the API request fails.
*/
async retrieveStrucvarsCsq(strucvar: LinearStrucvar): Promise<StrucvarResult> {
const { genomeBuild, chrom, start, stop, svType } = strucvar
const url =
`${this.apiBaseUrl}strucvars/csq?genome_release=${genomeBuild}&` +
`chromosome=${chrom}&start=${start}&stop=${stop}&sv_type=${svType}`

const response = await fetch(url, {
method: 'GET'
})
if (!response.ok) {
throw new Error(`Failed to fetch structural variant consequences: ${response.statusText}`)
}
const responseJson = await response.json()
return StrucvarResult.fromJson(responseJson)
}
}
98 changes: 98 additions & 0 deletions src/api/mehari/fixture.seqvarCsqResponse.BRCA1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"version": { "tx_db": "0.5.0", "mehari": "0.7.0" },
"query": {
"genome_release": "grch37",
"chromosome": "chr17",
"position": 41197751,
"reference": "G",
"alternative": "T",
"hgnc_id": null
},
"result": [
{
"consequences": ["MissenseVariant"],
"putative_impact": "Moderate",
"gene_symbol": "BRCA1",
"gene_id": "HGNC:1100",
"feature_type": { "SoTerm": { "term": "Transcript" } },
"feature_id": "NM_007294.4",
"feature_biotype": "Coding",
"rank": { "ord": 23, "total": 23 },
"hgvs_t": "c.5536C>A",
"hgvs_p": "p.Q1846K",
"tx_pos": { "ord": 5649, "total": 7088 },
"cds_pos": { "ord": 5536, "total": 5592 },
"protein_pos": { "ord": 1846, "total": 1864 },
"distance": -1440,
"messages": null
},
{
"consequences": ["MissenseVariant"],
"putative_impact": "Moderate",
"gene_symbol": "BRCA1",
"gene_id": "HGNC:1100",
"feature_type": { "SoTerm": { "term": "Transcript" } },
"feature_id": "NM_007297.4",
"feature_biotype": "Coding",
"rank": { "ord": 22, "total": 22 },
"hgvs_t": "c.5395C>A",
"hgvs_p": "p.Q1799K",
"tx_pos": { "ord": 5589, "total": 7028 },
"cds_pos": { "ord": 5395, "total": 5451 },
"protein_pos": { "ord": 1799, "total": 1817 },
"distance": -1440,
"messages": null
},
{
"consequences": ["MissenseVariant"],
"putative_impact": "Moderate",
"gene_symbol": "BRCA1",
"gene_id": "HGNC:1100",
"feature_type": { "SoTerm": { "term": "Transcript" } },
"feature_id": "NM_007298.3",
"feature_biotype": "Coding",
"rank": { "ord": 22, "total": 22 },
"hgvs_t": "c.2224C>A",
"hgvs_p": "p.Q742K",
"tx_pos": { "ord": 2243, "total": 3682 },
"cds_pos": { "ord": 2224, "total": 2280 },
"protein_pos": { "ord": 742, "total": 760 },
"distance": -1440,
"messages": null
},
{
"consequences": ["MissenseVariant"],
"putative_impact": "Moderate",
"gene_symbol": "BRCA1",
"gene_id": "HGNC:1100",
"feature_type": { "SoTerm": { "term": "Transcript" } },
"feature_id": "NM_007300.4",
"feature_biotype": "Coding",
"rank": { "ord": 24, "total": 24 },
"hgvs_t": "c.5599C>A",
"hgvs_p": "p.Q1867K",
"tx_pos": { "ord": 5712, "total": 7151 },
"cds_pos": { "ord": 5599, "total": 5655 },
"protein_pos": { "ord": 1867, "total": 1885 },
"distance": -1440,
"messages": null
},
{
"consequences": ["ThreePrimeUtrVariant"],
"putative_impact": "Modifier",
"gene_symbol": "BRCA1",
"gene_id": "HGNC:1100",
"feature_type": { "SoTerm": { "term": "Transcript" } },
"feature_id": "NM_007299.4",
"feature_biotype": "Coding",
"rank": { "ord": 22, "total": 22 },
"hgvs_t": "c.*50C>A",
"hgvs_p": "p.?",
"tx_pos": { "ord": 2257, "total": 3696 },
"cds_pos": { "ord": 50, "total": 2100 },
"protein_pos": null,
"distance": -1440,
"messages": null
}
]
}
Loading

0 comments on commit 6709873

Please sign in to comment.