687 lines
20 KiB
Rust
687 lines
20 KiB
Rust
//! Health variant analysis for genotyping data
|
|
//!
|
|
//! Clinically significant variant interpretation for 17+ health-relevant
|
|
//! SNPs commonly found in 23andMe/genotyping panels. Covers APOE, BRCA1/2,
|
|
//! TP53, MTHFR, COMT, OPRM1, CYP1A2, and more.
|
|
//!
|
|
//! Based on: <https://github.com/ericporres/rvdna-bridge>
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
/// Result of analyzing a single health variant
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HealthVariantResult {
|
|
/// rsid identifier
|
|
pub rsid: String,
|
|
/// Gene name
|
|
pub gene: String,
|
|
/// Variant common name
|
|
pub name: String,
|
|
/// Observed genotype
|
|
pub genotype: String,
|
|
/// Risk allele
|
|
pub risk_allele: char,
|
|
/// Human-readable interpretation
|
|
pub interpretation: String,
|
|
/// Clinical significance
|
|
pub clinical_significance: String,
|
|
}
|
|
|
|
/// APOE genotype determination result
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ApoeResult {
|
|
/// Full APOE genotype string (e.g., "e2/e3")
|
|
pub genotype: String,
|
|
/// rs429358 genotype
|
|
pub rs429358: String,
|
|
/// rs7412 genotype
|
|
pub rs7412: String,
|
|
}
|
|
|
|
/// MTHFR compound status
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MthfrResult {
|
|
/// C677T genotype (rs1801133)
|
|
pub c677t: String,
|
|
/// A1298C genotype (rs1801131)
|
|
pub a1298c: String,
|
|
/// Compound risk score (0-4)
|
|
pub score: u8,
|
|
/// Clinical assessment text
|
|
pub assessment: String,
|
|
}
|
|
|
|
/// Pain sensitivity profile (COMT + OPRM1)
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PainProfile {
|
|
/// COMT genotype (rs4680)
|
|
pub comt: String,
|
|
/// OPRM1 genotype (rs1799971)
|
|
pub oprm1: String,
|
|
/// Combined pain score (0-4)
|
|
pub score: u8,
|
|
/// Sensitivity label
|
|
pub label: String,
|
|
/// COMT interpretation
|
|
pub comt_note: String,
|
|
/// OPRM1 interpretation
|
|
pub oprm1_note: String,
|
|
}
|
|
|
|
// ── Internal definition type ──
|
|
|
|
struct VDef {
|
|
rsid: &'static str,
|
|
gene: &'static str,
|
|
name: &'static str,
|
|
risk_allele: char,
|
|
// (genotype, description, significance)
|
|
interps: &'static [(&'static str, &'static str, &'static str)],
|
|
}
|
|
|
|
static HEALTH_VARIANTS: &[VDef] = &[
|
|
// ── APOE (Alzheimer's) ──
|
|
VDef {
|
|
rsid: "rs429358",
|
|
gene: "APOE",
|
|
name: "APOE e4 determinant",
|
|
risk_allele: 'C',
|
|
interps: &[
|
|
(
|
|
"TT",
|
|
"APOE e3/e3 or e2/e3 (depends on rs7412)",
|
|
"Protective/Normal",
|
|
),
|
|
(
|
|
"CT",
|
|
"One e4 allele present",
|
|
"Increased Alzheimer's risk (~3x)",
|
|
),
|
|
(
|
|
"CC",
|
|
"Two e4 alleles present",
|
|
"Significantly increased Alzheimer's risk (~12x)",
|
|
),
|
|
],
|
|
},
|
|
VDef {
|
|
rsid: "rs7412",
|
|
gene: "APOE",
|
|
name: "APOE e2 determinant",
|
|
risk_allele: 'T',
|
|
interps: &[
|
|
("CC", "No e2 allele", "Normal"),
|
|
(
|
|
"CT",
|
|
"One e2 allele present",
|
|
"Protective - reduced Alzheimer's risk",
|
|
),
|
|
("TT", "Two e2 alleles (e2/e2)", "Protective; monitor lipids"),
|
|
],
|
|
},
|
|
// ── TP53 (cancer) ──
|
|
VDef {
|
|
rsid: "rs1042522",
|
|
gene: "TP53",
|
|
name: "p53 Pro72Arg (R72P)",
|
|
risk_allele: 'G',
|
|
interps: &[
|
|
(
|
|
"CC",
|
|
"Pro/Pro homozygous",
|
|
"Normal apoptosis; slightly increased cancer survival",
|
|
),
|
|
(
|
|
"CG",
|
|
"Pro/Arg heterozygous",
|
|
"Mixed - Arg allele has stronger apoptotic activity",
|
|
),
|
|
(
|
|
"GG",
|
|
"Arg/Arg homozygous",
|
|
"Stronger apoptotic response; variable cancer risk",
|
|
),
|
|
],
|
|
},
|
|
// ── BRCA1 ──
|
|
VDef {
|
|
rsid: "rs80357906",
|
|
gene: "BRCA1",
|
|
name: "BRCA1 5382insC (Ashkenazi founder)",
|
|
risk_allele: 'I',
|
|
interps: &[
|
|
(
|
|
"DD",
|
|
"No insertion detected",
|
|
"Normal - no BRCA1 5382insC mutation",
|
|
),
|
|
(
|
|
"DI",
|
|
"Heterozygous carrier",
|
|
"INCREASED breast/ovarian cancer risk - genetic counseling recommended",
|
|
),
|
|
(
|
|
"II",
|
|
"Homozygous insertion",
|
|
"HIGH breast/ovarian cancer risk - urgent genetic counseling",
|
|
),
|
|
],
|
|
},
|
|
VDef {
|
|
rsid: "rs28897696",
|
|
gene: "BRCA1",
|
|
name: "BRCA1 missense variant",
|
|
risk_allele: 'A',
|
|
interps: &[
|
|
("GG", "Reference genotype", "Normal"),
|
|
(
|
|
"AG",
|
|
"Heterozygous",
|
|
"Variant of uncertain significance - consult genetic counselor",
|
|
),
|
|
("AA", "Homozygous variant", "Consult genetic counselor"),
|
|
],
|
|
},
|
|
// ── BRCA2 ──
|
|
VDef {
|
|
rsid: "rs11571833",
|
|
gene: "BRCA2",
|
|
name: "BRCA2 K3326X",
|
|
risk_allele: 'T',
|
|
interps: &[
|
|
("AA", "Reference genotype", "Normal"),
|
|
(
|
|
"AT",
|
|
"Heterozygous",
|
|
"Modestly increased cancer risk (OR ~1.3)",
|
|
),
|
|
(
|
|
"TT",
|
|
"Homozygous variant",
|
|
"Increased cancer risk - genetic counseling recommended",
|
|
),
|
|
],
|
|
},
|
|
// ── MTHFR (folate metabolism) ──
|
|
VDef {
|
|
rsid: "rs1801133",
|
|
gene: "MTHFR",
|
|
name: "C677T",
|
|
risk_allele: 'A',
|
|
interps: &[
|
|
(
|
|
"GG",
|
|
"CC genotype (normal)",
|
|
"Normal MTHFR enzyme activity (100%)",
|
|
),
|
|
(
|
|
"AG",
|
|
"CT heterozygous",
|
|
"Reduced enzyme activity (~65%). Consider methylfolate.",
|
|
),
|
|
(
|
|
"AA",
|
|
"TT homozygous",
|
|
"Significantly reduced activity (~30%). Methylfolate recommended.",
|
|
),
|
|
],
|
|
},
|
|
VDef {
|
|
rsid: "rs1801131",
|
|
gene: "MTHFR",
|
|
name: "A1298C",
|
|
risk_allele: 'T',
|
|
interps: &[
|
|
("GG", "CC homozygous variant", "Reduced enzyme activity"),
|
|
("GT", "AC heterozygous", "Mildly reduced enzyme activity"),
|
|
(
|
|
"TT",
|
|
"AA reference",
|
|
"Normal MTHFR activity at this position",
|
|
),
|
|
],
|
|
},
|
|
// ── COMT (dopamine/pain) ──
|
|
VDef {
|
|
rsid: "rs4680",
|
|
gene: "COMT",
|
|
name: "Val158Met",
|
|
risk_allele: 'A',
|
|
interps: &[
|
|
(
|
|
"GG",
|
|
"Val/Val",
|
|
"Higher COMT activity, lower dopamine. Better stress resilience.",
|
|
),
|
|
(
|
|
"AG",
|
|
"Val/Met heterozygous",
|
|
"Intermediate COMT activity. Balanced dopamine.",
|
|
),
|
|
(
|
|
"AA",
|
|
"Met/Met",
|
|
"Lower COMT activity, higher dopamine. Higher pain sensitivity.",
|
|
),
|
|
],
|
|
},
|
|
// ── OPRM1 (opioid receptor) ──
|
|
VDef {
|
|
rsid: "rs1799971",
|
|
gene: "OPRM1",
|
|
name: "A118G (Asn40Asp)",
|
|
risk_allele: 'G',
|
|
interps: &[
|
|
("AA", "Asn/Asn", "Normal opioid sensitivity"),
|
|
(
|
|
"AG",
|
|
"Asn/Asp heterozygous",
|
|
"Reduced opioid sensitivity; may need higher doses.",
|
|
),
|
|
("GG", "Asp/Asp", "Significantly reduced opioid sensitivity."),
|
|
],
|
|
},
|
|
// ── CYP1A2 (caffeine) ──
|
|
VDef {
|
|
rsid: "rs762551",
|
|
gene: "CYP1A2",
|
|
name: "Caffeine metabolism",
|
|
risk_allele: 'C',
|
|
interps: &[
|
|
(
|
|
"AA",
|
|
"Fast metabolizer",
|
|
"Rapid caffeine clearance. Coffee may REDUCE heart disease risk.",
|
|
),
|
|
(
|
|
"AC",
|
|
"Intermediate",
|
|
"Moderate caffeine clearance. Moderate coffee intake recommended.",
|
|
),
|
|
(
|
|
"CC",
|
|
"Slow metabolizer",
|
|
"Slow caffeine clearance. Excess coffee may INCREASE heart risk.",
|
|
),
|
|
],
|
|
},
|
|
// ── Lactose ──
|
|
VDef {
|
|
rsid: "rs4988235",
|
|
gene: "MCM6/LCT",
|
|
name: "Lactase persistence (European)",
|
|
risk_allele: 'G',
|
|
interps: &[
|
|
(
|
|
"AA",
|
|
"Lactase persistent",
|
|
"Likely lactose TOLERANT into adulthood",
|
|
),
|
|
(
|
|
"AG",
|
|
"Heterozygous",
|
|
"Likely lactose tolerant (persistence is dominant)",
|
|
),
|
|
(
|
|
"GG",
|
|
"Lactase non-persistent",
|
|
"Likely lactose INTOLERANT in adulthood",
|
|
),
|
|
],
|
|
},
|
|
// ── OXTR (oxytocin receptor) ──
|
|
VDef {
|
|
rsid: "rs53576",
|
|
gene: "OXTR",
|
|
name: "Oxytocin receptor",
|
|
risk_allele: 'A',
|
|
interps: &[
|
|
(
|
|
"GG",
|
|
"GG genotype",
|
|
"Higher empathy scores; better social cognition.",
|
|
),
|
|
(
|
|
"AG",
|
|
"AG heterozygous",
|
|
"Intermediate empathy and social cognition.",
|
|
),
|
|
(
|
|
"AA",
|
|
"AA genotype",
|
|
"May have lower empathy; potentially more resilient to social stress.",
|
|
),
|
|
],
|
|
},
|
|
// ── HTR2A (serotonin) ──
|
|
VDef {
|
|
rsid: "rs6311",
|
|
gene: "HTR2A",
|
|
name: "Serotonin 2A receptor (-1438G/A)",
|
|
risk_allele: 'T',
|
|
interps: &[
|
|
("CC", "GG genotype", "Normal serotonin receptor expression"),
|
|
(
|
|
"CT",
|
|
"GA heterozygous",
|
|
"Slightly altered serotonin signaling",
|
|
),
|
|
(
|
|
"TT",
|
|
"AA genotype",
|
|
"Altered serotonin receptor density; may affect SSRI response",
|
|
),
|
|
],
|
|
},
|
|
// ── ANKK1/DRD2 (dopamine) ──
|
|
VDef {
|
|
rsid: "rs1800497",
|
|
gene: "ANKK1/DRD2",
|
|
name: "Taq1A (dopamine receptor)",
|
|
risk_allele: 'A',
|
|
interps: &[
|
|
("GG", "A2/A2", "Normal dopamine receptor density"),
|
|
(
|
|
"AG",
|
|
"A1/A2 heterozygous",
|
|
"Reduced D2 receptor density (~30% less). Reward-seeking.",
|
|
),
|
|
(
|
|
"AA",
|
|
"A1/A1",
|
|
"Significantly reduced D2 receptor density. Higher addiction risk.",
|
|
),
|
|
],
|
|
},
|
|
// ── SLCO1B1 (statin metabolism) ──
|
|
VDef {
|
|
rsid: "rs4363657",
|
|
gene: "SLCO1B1",
|
|
name: "Statin transporter",
|
|
risk_allele: 'C',
|
|
interps: &[
|
|
(
|
|
"TT",
|
|
"Reference",
|
|
"Normal statin metabolism. Standard dosing.",
|
|
),
|
|
(
|
|
"CT",
|
|
"Heterozygous",
|
|
"Increased statin myopathy risk (~4.5x). Consider lower dose.",
|
|
),
|
|
(
|
|
"CC",
|
|
"Homozygous variant",
|
|
"High statin myopathy risk (~17x). Use lowest effective dose.",
|
|
),
|
|
],
|
|
},
|
|
// ── NQO1 (oxidative stress) ──
|
|
VDef {
|
|
rsid: "rs1800566",
|
|
gene: "NQO1",
|
|
name: "Pro187Ser (oxidative stress)",
|
|
risk_allele: 'T',
|
|
interps: &[
|
|
("CC", "Pro/Pro (reference)", "Normal NQO1 enzyme activity"),
|
|
(
|
|
"CT",
|
|
"Pro/Ser heterozygous",
|
|
"Reduced NQO1 activity (~3x lower). Impaired detox.",
|
|
),
|
|
(
|
|
"TT",
|
|
"Ser/Ser",
|
|
"No NQO1 activity. Significantly impaired quinone detoxification.",
|
|
),
|
|
],
|
|
},
|
|
];
|
|
|
|
/// Analyze health variants from a genotype map (rsid -> genotype string).
|
|
pub fn analyze_health_variants(genotypes: &HashMap<String, String>) -> Vec<HealthVariantResult> {
|
|
let mut results = Vec::new();
|
|
|
|
for def in HEALTH_VARIANTS {
|
|
if let Some(gt) = genotypes.get(def.rsid) {
|
|
let (desc, sig) = def
|
|
.interps
|
|
.iter()
|
|
.find(|(g, _, _)| *g == gt.as_str())
|
|
.map(|(_, d, s)| (d.to_string(), s.to_string()))
|
|
.unwrap_or_else(|| {
|
|
(
|
|
format!("Genotype {} - not in standard table", gt),
|
|
"Consult genetic counselor".to_string(),
|
|
)
|
|
});
|
|
|
|
results.push(HealthVariantResult {
|
|
rsid: def.rsid.to_string(),
|
|
gene: def.gene.to_string(),
|
|
name: def.name.to_string(),
|
|
genotype: gt.clone(),
|
|
risk_allele: def.risk_allele,
|
|
interpretation: desc,
|
|
clinical_significance: sig,
|
|
});
|
|
}
|
|
}
|
|
|
|
results
|
|
}
|
|
|
|
/// Determine APOE genotype from rs429358 + rs7412 combination.
|
|
pub fn determine_apoe(genotypes: &HashMap<String, String>) -> ApoeResult {
|
|
let gt1 = genotypes.get("rs429358").cloned().unwrap_or_default();
|
|
let gt2 = genotypes.get("rs7412").cloned().unwrap_or_default();
|
|
|
|
if gt1.is_empty() || gt2.is_empty() {
|
|
return ApoeResult {
|
|
genotype: "Unable to determine (missing data)".into(),
|
|
rs429358: gt1,
|
|
rs7412: gt2,
|
|
};
|
|
}
|
|
|
|
// e4 alleles = count of 'C' at rs429358
|
|
let e4 = gt1.chars().filter(|&c| c == 'C').count();
|
|
// e2 alleles = count of 'T' at rs7412
|
|
let e2 = gt2.chars().filter(|&c| c == 'T').count();
|
|
|
|
let genotype = match (e4, e2) {
|
|
(0, 0) => "e3/e3 (most common, baseline risk)".into(),
|
|
(0, 1) => "e2/e3 (PROTECTIVE - reduced Alzheimer's risk)".into(),
|
|
(0, 2) => "e2/e2 (protective; monitor for type III hyperlipoproteinemia)".into(),
|
|
(1, 0) => "e3/e4 (increased Alzheimer's risk ~3x)".into(),
|
|
(1, 1) => "e2/e4 (mixed - e2 partially offsets e4 risk)".into(),
|
|
(2, _) => "e4/e4 (significantly increased Alzheimer's risk ~12x)".into(),
|
|
_ => format!("Unusual combination: rs429358={}, rs7412={}", gt1, gt2),
|
|
};
|
|
|
|
ApoeResult {
|
|
genotype,
|
|
rs429358: gt1,
|
|
rs7412: gt2,
|
|
}
|
|
}
|
|
|
|
/// Analyze MTHFR compound status from C677T + A1298C.
|
|
pub fn analyze_mthfr(genotypes: &HashMap<String, String>) -> MthfrResult {
|
|
let c677t = genotypes.get("rs1801133").cloned().unwrap_or_default();
|
|
let a1298c = genotypes.get("rs1801131").cloned().unwrap_or_default();
|
|
|
|
if c677t.is_empty() || a1298c.is_empty() {
|
|
return MthfrResult {
|
|
c677t,
|
|
a1298c,
|
|
score: 0,
|
|
assessment: "Incomplete MTHFR data".into(),
|
|
};
|
|
}
|
|
|
|
let c_risk = match c677t.as_str() {
|
|
"GG" => 0u8,
|
|
"AG" => 1,
|
|
"AA" => 2,
|
|
_ => 0,
|
|
};
|
|
let a_risk = match a1298c.as_str() {
|
|
"TT" => 0u8,
|
|
"GT" => 1,
|
|
"GG" => 2,
|
|
_ => 0,
|
|
};
|
|
let score = c_risk + a_risk;
|
|
|
|
let assessment = match score {
|
|
0 => "Normal MTHFR function. No supplementation needed.",
|
|
1 => "Mildly reduced MTHFR. Consider methylfolate if homocysteine elevated.",
|
|
2 => "Moderately reduced MTHFR. Methylfolate (L-5-MTHF) recommended.",
|
|
3 => "Significantly reduced MTHFR (compound heterozygote). Methylfolate strongly recommended.",
|
|
_ => "Severely reduced MTHFR. Methylfolate essential. Regular homocysteine monitoring.",
|
|
};
|
|
|
|
MthfrResult {
|
|
c677t,
|
|
a1298c,
|
|
score,
|
|
assessment: assessment.into(),
|
|
}
|
|
}
|
|
|
|
/// Analyze pain sensitivity profile from COMT + OPRM1.
|
|
pub fn analyze_pain(genotypes: &HashMap<String, String>) -> Option<PainProfile> {
|
|
let comt = genotypes.get("rs4680")?;
|
|
let oprm1 = genotypes.get("rs1799971")?;
|
|
|
|
let mut score = 0u8;
|
|
if comt == "AA" {
|
|
score += 2;
|
|
} else if comt == "AG" {
|
|
score += 1;
|
|
}
|
|
if oprm1 == "GG" {
|
|
score += 2;
|
|
} else if oprm1 == "AG" {
|
|
score += 1;
|
|
}
|
|
|
|
let label = match score {
|
|
0 => "Low",
|
|
1 => "Low-Moderate",
|
|
2 => "Moderate",
|
|
3 => "Moderate-High",
|
|
_ => "High",
|
|
};
|
|
|
|
let comt_note = if comt.contains('A') {
|
|
"Higher pain sensitivity"
|
|
} else {
|
|
"Lower pain sensitivity"
|
|
};
|
|
let oprm1_note = if oprm1.contains('G') {
|
|
"Reduced opioid response"
|
|
} else {
|
|
"Normal opioid response"
|
|
};
|
|
|
|
Some(PainProfile {
|
|
comt: comt.clone(),
|
|
oprm1: oprm1.clone(),
|
|
score,
|
|
label: label.into(),
|
|
comt_note: comt_note.into(),
|
|
oprm1_note: oprm1_note.into(),
|
|
})
|
|
}
|
|
|
|
/// Category groupings for health variant display
|
|
pub fn variant_categories() -> Vec<(&'static str, Vec<&'static str>)> {
|
|
vec![
|
|
("Cancer Risk", vec!["TP53", "BRCA1", "BRCA2", "NQO1"]),
|
|
("Cardiovascular", vec!["SLCO1B1"]),
|
|
(
|
|
"Neurological",
|
|
vec!["APOE", "COMT", "OPRM1", "OXTR", "HTR2A", "ANKK1/DRD2"],
|
|
),
|
|
("Metabolism", vec!["MTHFR", "CYP1A2", "MCM6/LCT"]),
|
|
]
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn make_map(pairs: &[(&str, &str)]) -> HashMap<String, String> {
|
|
pairs
|
|
.iter()
|
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn test_apoe_e3e3() {
|
|
let gts = make_map(&[("rs429358", "TT"), ("rs7412", "CC")]);
|
|
let r = determine_apoe(>s);
|
|
assert!(r.genotype.contains("e3/e3"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_apoe_e2e3() {
|
|
let gts = make_map(&[("rs429358", "TT"), ("rs7412", "CT")]);
|
|
let r = determine_apoe(>s);
|
|
assert!(r.genotype.contains("e2/e3"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_apoe_e4e4() {
|
|
let gts = make_map(&[("rs429358", "CC"), ("rs7412", "CC")]);
|
|
let r = determine_apoe(>s);
|
|
assert!(r.genotype.contains("e4/e4"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_mthfr_normal() {
|
|
let gts = make_map(&[("rs1801133", "GG"), ("rs1801131", "TT")]);
|
|
let r = analyze_mthfr(>s);
|
|
assert_eq!(r.score, 0);
|
|
assert!(r.assessment.contains("Normal"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_mthfr_compound() {
|
|
let gts = make_map(&[("rs1801133", "AG"), ("rs1801131", "GG")]);
|
|
let r = analyze_mthfr(>s);
|
|
assert_eq!(r.score, 3);
|
|
assert!(r.assessment.contains("compound"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_pain_low() {
|
|
let gts = make_map(&[("rs4680", "GG"), ("rs1799971", "AA")]);
|
|
let p = analyze_pain(>s).unwrap();
|
|
assert_eq!(p.score, 0);
|
|
assert_eq!(p.label, "Low");
|
|
}
|
|
|
|
#[test]
|
|
fn test_pain_high() {
|
|
let gts = make_map(&[("rs4680", "AA"), ("rs1799971", "GG")]);
|
|
let p = analyze_pain(>s).unwrap();
|
|
assert_eq!(p.score, 4);
|
|
assert_eq!(p.label, "High");
|
|
}
|
|
|
|
#[test]
|
|
fn test_health_variants_lookup() {
|
|
let gts = make_map(&[("rs762551", "AA"), ("rs4680", "AG")]);
|
|
let results = analyze_health_variants(>s);
|
|
assert_eq!(results.len(), 2);
|
|
assert_eq!(results[0].gene, "COMT");
|
|
assert_eq!(results[1].gene, "CYP1A2");
|
|
}
|
|
}
|