use serde::{Deserialize, Serialize};
use crate::analysis::blast_radius::BlastTier;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EnrichmentDepth {
Fast,
Standard,
Deep,
}
impl EnrichmentDepth {
pub fn as_str(self) -> &'static str {
match self {
EnrichmentDepth::Fast => "fast",
EnrichmentDepth::Standard => "standard",
EnrichmentDepth::Deep => "deep",
}
}
}
mod thresholds {
pub const LARGE_FILE_LOC: u32 = 400;
pub const MEDIUM_FILE_LOC: u32 = 100;
pub const STRONG_CLUSTER_SIZE: u32 = 5;
pub const STRONG_GOTCHA_COUNT: usize = 3;
pub const SIGNAL_RICH_COMMENT_RATIO: f32 = 0.15;
}
pub fn enrichment_depth(
line_count: u32,
blast_tier: BlastTier,
cluster_size: u32,
gotcha_count: usize,
comment_density: Option<f32>,
) -> EnrichmentDepth {
use thresholds::*;
let mut score: u32 = 0;
if line_count >= LARGE_FILE_LOC {
score += 3;
} else if line_count >= MEDIUM_FILE_LOC {
score += 1;
}
if matches!(
blast_tier,
BlastTier::Moderate | BlastTier::High | BlastTier::Critical
) {
score += 2;
}
if cluster_size >= STRONG_CLUSTER_SIZE {
score += 2;
}
if gotcha_count >= STRONG_GOTCHA_COUNT {
score += 2;
}
if let Some(density) = comment_density {
if density >= SIGNAL_RICH_COMMENT_RATIO {
score += 1;
}
}
match score {
0..=1 => EnrichmentDepth::Fast,
2..=4 => EnrichmentDepth::Standard,
_ => EnrichmentDepth::Deep,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tiny_isolated_file_is_fast() {
assert_eq!(
enrichment_depth(50, BlastTier::Isolated, 0, 0, None),
EnrichmentDepth::Fast
);
}
#[test]
fn medium_file_alone_is_standard() {
assert_eq!(
enrichment_depth(200, BlastTier::Isolated, 0, 0, None),
EnrichmentDepth::Fast
);
assert_eq!(
enrichment_depth(200, BlastTier::Isolated, 0, 0, Some(0.2)),
EnrichmentDepth::Standard
);
}
#[test]
fn large_file_alone_is_standard() {
assert_eq!(
enrichment_depth(500, BlastTier::Isolated, 0, 0, None),
EnrichmentDepth::Standard
);
}
#[test]
fn large_file_with_blast_is_deep() {
assert_eq!(
enrichment_depth(500, BlastTier::High, 0, 0, None),
EnrichmentDepth::Deep
);
}
#[test]
fn hotspot_with_many_gotchas_is_deep() {
assert_eq!(
enrichment_depth(200, BlastTier::High, 0, 4, None),
EnrichmentDepth::Deep
);
}
#[test]
fn large_cluster_member_is_at_least_standard() {
assert_eq!(
enrichment_depth(50, BlastTier::Moderate, 6, 0, None),
EnrichmentDepth::Standard
);
}
#[test]
fn boundary_loc_400_qualifies_for_3_points() {
assert_eq!(
enrichment_depth(400, BlastTier::Isolated, 0, 0, None),
EnrichmentDepth::Standard
);
}
#[test]
fn comment_density_below_threshold_does_not_score() {
assert_eq!(
enrichment_depth(200, BlastTier::Isolated, 0, 0, Some(0.14)),
EnrichmentDepth::Fast
);
}
#[test]
fn blast_below_moderate_does_not_score() {
assert_eq!(
enrichment_depth(50, BlastTier::Low, 0, 0, None),
EnrichmentDepth::Fast
);
assert_eq!(
enrichment_depth(50, BlastTier::Isolated, 0, 0, None),
EnrichmentDepth::Fast
);
}
#[test]
fn as_str_returns_stable_labels() {
assert_eq!(EnrichmentDepth::Fast.as_str(), "fast");
assert_eq!(EnrichmentDepth::Standard.as_str(), "standard");
assert_eq!(EnrichmentDepth::Deep.as_str(), "deep");
}
#[test]
fn serialization_matches_label() {
let json = serde_json::to_string(&EnrichmentDepth::Deep).unwrap();
assert_eq!(json, "\"deep\"");
let back: EnrichmentDepth = serde_json::from_str("\"standard\"").unwrap();
assert_eq!(back, EnrichmentDepth::Standard);
}
}