use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use crate::db::config::Config;
use crate::output::Format;
const ALPHAGENOME_API_BASE: &str = "https://alphagenome.deepmind.com/v1";
pub struct AlphaGenomeClient {
api_key: String,
client: reqwest::Client,
}
#[derive(Debug, Serialize)]
struct PredictRequest {
chromosome: String,
position: u64,
reference: String,
alternate: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Prediction {
pub variant: String,
pub expression_impact: Option<ExpressionImpact>,
pub splicing_impact: Option<SplicingImpact>,
pub chromatin_impact: Option<ChromatinImpact>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ExpressionImpact {
pub gene: String,
pub effect_size: f64,
pub tissue: String,
pub confidence: f64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct SplicingImpact {
pub splice_site_affected: bool,
pub exon_skipping_risk: f64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ChromatinImpact {
pub accessibility_change: String,
pub regulatory_region: String,
}
impl AlphaGenomeClient {
pub fn new() -> Result<Self> {
let config = Config::load()?;
let api_key = config
.get("alphagenome-api-key")
.map(String::from)
.context(
"AlphaGenome API key not set. Run:\n genome config set alphagenome-api-key <key>",
)?;
eprintln!("Warning: Variant data will be sent to Google DeepMind's AlphaGenome API.");
eprintln!("No personal identifiers are included, only genomic coordinates.");
eprintln!();
Ok(Self {
api_key,
client: reqwest::Client::new(),
})
}
pub async fn predict_variant(&self, variant_str: &str) -> Result<Prediction> {
let parts: Vec<&str> = variant_str.split(':').collect();
if parts.len() < 4 {
anyhow::bail!(
"Variant must be in chr:pos:ref:alt format (e.g., chr17:43092919:G:A)"
);
}
let request = PredictRequest {
chromosome: parts[0].to_string(),
position: parts[1].parse().context("Invalid position")?,
reference: parts[2].to_string(),
alternate: parts[3].to_string(),
};
let response = self
.client
.post(format!("{ALPHAGENOME_API_BASE}/predict/variant"))
.bearer_auth(&self.api_key)
.json(&request)
.send()
.await
.context("Failed to reach AlphaGenome API")?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
anyhow::bail!("AlphaGenome API error ({status}): {body}");
}
let prediction: Prediction = response
.json()
.await
.context("Failed to parse AlphaGenome response")?;
Ok(prediction)
}
}
impl Prediction {
pub fn print(&self, format: Format) -> Result<()> {
match format {
Format::Json => {
println!("{}", serde_json::to_string_pretty(self)?);
}
_ => {
println!("AlphaGenome Prediction: {}", self.variant);
println!();
if let Some(expr) = &self.expression_impact {
println!(" Gene Expression Impact");
let direction = if expr.effect_size < 0.0 {
"decrease"
} else {
"increase"
};
println!(
" {} expression: {:.1} SD ({direction})",
expr.gene,
expr.effect_size.abs()
);
println!(" Tissue most affected: {}", expr.tissue);
println!(" Confidence: {:.2}", expr.confidence);
println!();
}
if let Some(splice) = &self.splicing_impact {
println!(" Splicing Impact");
println!(" Splice site affected: {}", splice.splice_site_affected);
println!(
" Exon skipping risk: {:.2}",
splice.exon_skipping_risk
);
println!();
}
if let Some(chrom) = &self.chromatin_impact {
println!(" Chromatin Impact");
println!(" Accessibility change: {}", chrom.accessibility_change);
println!(" Regulatory region: {}", chrom.regulatory_region);
println!();
}
}
}
Ok(())
}
}