Skip to content

Commit

Permalink
add clinvar endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
tedil committed Jan 2, 2025
1 parent b0af0ea commit 4097675
Showing 5 changed files with 260 additions and 32 deletions.
77 changes: 68 additions & 9 deletions src/annotate/seqvars/mod.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ use std::str::FromStr;
use std::sync::Arc;
use std::time::Instant;

use self::ann::{AnnField, Consequence, FeatureBiotype};
use crate::annotate::cli::{Sources, TranscriptSettings};
use crate::annotate::genotype_string;
use crate::annotate::seqvars::csq::{
@@ -52,8 +53,6 @@ use strum::Display;
use thousands::Separable;
use tokio::io::AsyncWriteExt;

use self::ann::{AnnField, Consequence, FeatureBiotype};

pub mod ann;
pub mod binning;
pub mod csq;
@@ -1527,8 +1526,9 @@ impl FrequencyAnnotator {
pub(crate) fn annotate_variant(
&self,
vcf_var: &VcfVariant,
) -> anyhow::Result<Option<crate::server::run::actix_server::frequencies::FrequencyResultEntry>>
{
) -> anyhow::Result<
Option<crate::server::run::actix_server::seqvars_frequencies::FrequencyResultEntry>,
> {
// Only attempt lookups into RocksDB for canonical contigs.
if !is_canonical(&vcf_var.chromosome) {
return Ok(None);
@@ -1542,7 +1542,7 @@ impl FrequencyAnnotator {
&vcf_var.alternative,
);
let key: Vec<u8> = vcf_var.clone().into();
use crate::server::run::actix_server::frequencies::*;
use crate::server::run::actix_server::seqvars_frequencies::*;
// Annotate with frequency.
if CHROM_AUTO.contains(vcf_var.chrom.as_str()) {
if let Some(freq) = self
@@ -1613,6 +1613,7 @@ impl FrequencyAnnotator {
}
}

#[derive(Debug)]
pub struct ClinvarAnnotator {
db: DBWithThreadMode<rocksdb::MultiThreaded>,
}
@@ -1723,6 +1724,64 @@ impl ClinvarAnnotator {
}
Ok(())
}

pub(crate) fn annotate_variant(
&self,
vcf_var: &VcfVariant,
) -> anyhow::Result<Option<crate::server::run::actix_server::seqvars_clinvar::ClinvarResultEntry>>
{
// Only attempt lookups into RocksDB for canonical contigs.
if !is_canonical(&vcf_var.chromosome) {
return Ok(None);
}

// Build key for RocksDB database
let vcf_var = keys::Var::from(
&vcf_var.chromosome,
vcf_var.position,
&vcf_var.reference,
&vcf_var.alternative,
);
let key: Vec<u8> = vcf_var.clone().into();

if let Some(raw_value) = self
.db
.get_cf(self.db.cf_handle("clinvar").as_ref().unwrap(), key)?
{
let record_list = annonars::pbs::clinvar::minimal::ExtractedVcvRecordList::decode(
&mut Cursor::new(&raw_value),
)?;

let mut clinvar_vcvs = Vec::new();
let mut clinvar_germline_classifications = Vec::new();
for clinvar_record in record_list.records.iter() {
let accession = clinvar_record.accession.as_ref().expect("must have VCV");
let vcv = format!("{}.{}", accession.accession, accession.version);
let classifications = clinvar_record
.classifications
.as_ref()
.expect("must have classifications");
if let Some(germline_classification) = &classifications.germline_classification {
let description = germline_classification
.description
.as_ref()
.expect("description missing")
.to_string();
clinvar_vcvs.push(vcv);
clinvar_germline_classifications.push(description);
}
}

Ok(Some(
crate::server::run::actix_server::seqvars_clinvar::ClinvarResultEntry {
clinvar_vcv: clinvar_vcvs,
clinvar_germline_classification: clinvar_germline_classifications,
},
))
} else {
Ok(None)
}
}
}

pub(crate) struct ConsequenceAnnotator {
@@ -1971,15 +2030,15 @@ pub(crate) fn setup_seqvars_annotator(

// Add the frequency annotator if requested.
if let Some(rocksdb_paths) = &sources.frequencies {
let freq_dbs = load_frequency_dbs_for_assembly(rocksdb_paths, assembly)?;
let freq_dbs = initialize_frequency_annotators_for_assembly(rocksdb_paths, assembly)?;
for freq_db in freq_dbs {
annotators.push(AnnotatorEnum::Frequency(freq_db))
}
}

// Add the ClinVar annotator if requested.
if let Some(rocksdb_paths) = &sources.clinvar {
let clinvar_dbs = load_clinvar_dbs_for_assembly(rocksdb_paths, assembly)?;
let clinvar_dbs = intialize_clinvar_annotators_for_assembly(rocksdb_paths, assembly)?;
for clinvar_db in clinvar_dbs {
annotators.push(AnnotatorEnum::Clinvar(clinvar_db))
}
@@ -2010,7 +2069,7 @@ pub(crate) fn setup_seqvars_annotator(
Ok(annotator)
}

pub(crate) fn load_clinvar_dbs_for_assembly(
pub(crate) fn intialize_clinvar_annotators_for_assembly(
rocksdb_paths: &[String],
assembly: Option<Assembly>,
) -> Result<Vec<ClinvarAnnotator>, Error> {
@@ -2036,7 +2095,7 @@ pub(crate) fn load_clinvar_dbs_for_assembly(
.collect()
}

pub(crate) fn load_frequency_dbs_for_assembly(
pub(crate) fn initialize_frequency_annotators_for_assembly(
rocksdb_paths: &[String],
assembly: Option<Assembly>,
) -> Result<Vec<FrequencyAnnotator>, Error> {
20 changes: 15 additions & 5 deletions src/server/run/actix_server/mod.rs
Original file line number Diff line number Diff line change
@@ -6,13 +6,14 @@ use actix_web::ResponseError;
use utoipa::OpenApi as _;

use crate::annotate::seqvars::provider::Provider as MehariProvider;
use crate::annotate::seqvars::FrequencyAnnotator;
use crate::annotate::seqvars::{ClinvarAnnotator, FrequencyAnnotator};
use crate::annotate::strucvars::csq::ConsequencePredictor as StrucvarConsequencePredictor;
use crate::{annotate::seqvars::csq::ConsequencePredictor, common::GenomeRelease};

pub mod frequencies;
pub mod gene_txs;
pub mod seqvars_clinvar;
pub mod seqvars_csq;
pub mod seqvars_frequencies;
pub mod strucvars_csq;
pub mod versions;

@@ -44,12 +45,19 @@ pub struct WebServerData {
/// `MehariProvider` to provide the transcript info.
#[derivative(Debug = "ignore")]
pub provider: std::collections::HashMap<GenomeRelease, Arc<MehariProvider>>,

/// The sequence variant consequence predictors for each assembly.
pub seqvars_predictors: std::collections::HashMap<GenomeRelease, ConsequencePredictor>,
/// The structural variant consequence predictors for eacha ssembly.

/// The structural variant consequence predictors for each assembly.
pub strucvars_predictors:
std::collections::HashMap<GenomeRelease, StrucvarConsequencePredictor>,

/// The frequency annotators for each assembly.
pub frequency_annotators: std::collections::HashMap<GenomeRelease, FrequencyAnnotator>,

/// The clinvar annotators for each assembly.
pub clinvar_annotators: std::collections::HashMap<GenomeRelease, ClinvarAnnotator>,
}

/// Main entry point for running the REST server.
@@ -67,8 +75,10 @@ pub async fn main(
.service(seqvars_csq::handle_with_openapi)
.service(strucvars_csq::handle)
.service(strucvars_csq::handle_with_openapi)
.service(frequencies::handle)
.service(frequencies::handle_with_openapi)
.service(seqvars_frequencies::handle)
.service(seqvars_frequencies::handle_with_openapi)
.service(seqvars_clinvar::handle)
.service(seqvars_clinvar::handle_with_openapi)
.service(versions::handle)
.service(
utoipa_swagger_ui::SwaggerUi::new("/swagger-ui/{_:.*}")
132 changes: 132 additions & 0 deletions src/server/run/actix_server/seqvars_clinvar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//! Implementation of endpoint `/api/v1/seqvars/clinvar`.
//!
//! Also includes the implementation of the `/seqvars/clinvar` endpoint (deprecated).
use actix_web::{
get,
web::{self, Data, Json, Path},
};

use crate::{annotate::seqvars::csq::VcfVariant, common::GenomeRelease};

use super::{versions::VersionsInfoResponse, CustomError};

/// Query parameters of the `/api/v1/seqvars/clinvar` endpoint.
#[derive(
Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::IntoParams, utoipa::ToSchema,
)]
#[serde(rename_all = "snake_case")]
#[serde_with::skip_serializing_none]
pub(crate) struct ClinvarQuery {
/// The assembly.
pub genome_release: GenomeRelease,
/// SPDI sequence.
pub chromosome: String,
/// SPDI position.
pub position: u32,
/// SPDI deletion.
pub reference: String,
/// SPDI insertion.
pub alternative: String,
}

/// One entry in `ClinvarResponse`.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
pub(crate) struct ClinvarResultEntry {
pub clinvar_vcv: Vec<String>,
pub clinvar_germline_classification: Vec<String>,
}

/// Response of the `/api/v1/seqvars/clinvar` endpoint.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
pub(crate) struct ClinvarResponse {
/// Version information.
pub version: VersionsInfoResponse,

/// The original query records.
pub query: ClinvarQuery,

/// The resulting records for the scored genes.
pub result: Vec<ClinvarResultEntry>,
}

/// Implementation of endpoints.
async fn handle_impl(
data: Data<super::WebServerData>,
_path: Path<()>,
query: web::Query<ClinvarQuery>,
) -> actix_web::Result<Json<ClinvarResponse>, super::CustomError> {
let ClinvarQuery {
genome_release,
chromosome,
position,
reference,
alternative,
} = query.clone().into_inner();

let annotator = data
.clinvar_annotators
.get(&genome_release)
.ok_or_else(|| {
super::CustomError::new(anyhow::anyhow!(
"genome release not supported: {:?}",
&query.genome_release
))
})?;

let mut result = Vec::new();
let g_var = VcfVariant {
chromosome,
position: position as i32,
reference,
alternative,
};
let annotations = annotator
.annotate_variant(&g_var)
.map_err(|e| super::CustomError::new(anyhow::anyhow!("annotation failed: {}", &e)))?;
if let Some(annotations) = annotations {
result.push(annotations);
}

let result = ClinvarResponse {
version: VersionsInfoResponse::from_web_server_data(data.into_inner().as_ref())
.map_err(|e| CustomError::new(anyhow::anyhow!("Problem determining version: {}", e)))?,
query: query.into_inner(),
result,
};

Ok(Json(result))
}

/// Query for gnomAD frequencies of a variant.
#[allow(clippy::unused_async)]
#[get("/seqvars/clinvar")]
async fn handle(
data: Data<super::WebServerData>,
_path: Path<()>,
query: web::Query<ClinvarQuery>,
) -> actix_web::Result<Json<ClinvarResponse>, super::CustomError> {
handle_impl(data, _path, query).await
}

/// Query for gnomAD frequencies of a variant.
#[allow(clippy::unused_async)]
#[utoipa::path(
get,
operation_id = "seqvarsClinvar",
params(
ClinvarQuery
),
responses(
(status = 200, description = "Clinvar information.", body = ClinvarResponse),
(status = 500, description = "Internal server error.", body = CustomError)
)
)]
#[get("/api/v1/seqvars/clinvar")]
async fn handle_with_openapi(
data: Data<super::WebServerData>,
_path: Path<()>,
query: web::Query<ClinvarQuery>,
) -> actix_web::Result<Json<ClinvarResponse>, super::CustomError> {
handle_impl(data, _path, query).await
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Implementation of endpoint `/api/v1/frequency`.
//! Implementation of endpoint `/api/v1/seqvars/frequency`.
//!
//! Also includes the implementation of the `/frequency` endpoint (deprecated).
//! Also includes the implementation of the `/seqvars/frequency` endpoint (deprecated).
use actix_web::{
get,
@@ -11,7 +11,7 @@ use crate::{annotate::seqvars::csq::VcfVariant, common::GenomeRelease};

use super::{versions::VersionsInfoResponse, CustomError};

/// Query parameters of the `/api/v1/seqvars/csq` endpoint.
/// Query parameters of the `/api/v1/seqvars/frequency` endpoint.
#[derive(
Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::IntoParams, utoipa::ToSchema,
)]
@@ -87,7 +87,7 @@ pub(crate) struct MitochondrialResultEntry {
pub gnomad_genomes_het: u32,
}

/// Response of the `/api/v1/frequency` endpoint.
/// Response of the `/api/v1/seqvars/frequency` endpoint.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
pub(crate) struct FrequencyResponse {
/// Version information.
@@ -148,7 +148,7 @@ async fn handle_impl(

/// Query for gnomAD frequencies of a variant.
#[allow(clippy::unused_async)]
#[get("/frequency")]
#[get("/seqvars/frequency")]
async fn handle(
data: Data<super::WebServerData>,
_path: Path<()>,
@@ -161,7 +161,7 @@ async fn handle(
#[allow(clippy::unused_async)]
#[utoipa::path(
get,
operation_id = "frequency",
operation_id = "seqvarsFrequency",
params(
FrequencyQuery
),
@@ -170,7 +170,7 @@ async fn handle(
(status = 500, description = "Internal server error.", body = CustomError)
)
)]
#[get("/api/v1/frequency")]
#[get("/api/v1/seqvars/frequency")]
async fn handle_with_openapi(
data: Data<super::WebServerData>,
_path: Path<()>,
Loading

0 comments on commit 4097675

Please sign in to comment.