repopilot 0.11.0

Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.
Documentation
use crate::findings::types::Finding;
use serde::{Deserialize, Serialize};

use super::model::RiskPriority;

#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct RiskPriorityCounts {
    pub p0: usize,
    pub p1: usize,
    pub p2: usize,
    pub p3: usize,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RiskSummary {
    pub total: usize,
    pub counts: RiskPriorityCounts,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub highest_priority: Option<RiskPriority>,
    pub average_score: u8,
}

impl RiskSummary {
    pub fn from_findings(findings: &[Finding]) -> Self {
        if findings.is_empty() {
            return Self::default();
        }

        let mut counts = RiskPriorityCounts::default();
        let mut highest_priority = RiskPriority::P3;
        let mut score_sum = 0usize;

        for finding in findings {
            counts.increment(finding.risk.priority);
            if priority_rank(finding.risk.priority) < priority_rank(highest_priority) {
                highest_priority = finding.risk.priority;
            }
            score_sum += finding.risk.score as usize;
        }

        let average_score = ((score_sum as f64) / (findings.len() as f64)).round() as u8;

        Self {
            total: findings.len(),
            counts,
            highest_priority: Some(highest_priority),
            average_score,
        }
    }

    pub fn is_empty(&self) -> bool {
        self.total == 0
    }
}

impl RiskPriorityCounts {
    pub fn count(&self, priority: RiskPriority) -> usize {
        match priority {
            RiskPriority::P0 => self.p0,
            RiskPriority::P1 => self.p1,
            RiskPriority::P2 => self.p2,
            RiskPriority::P3 => self.p3,
        }
    }

    fn increment(&mut self, priority: RiskPriority) {
        match priority {
            RiskPriority::P0 => self.p0 += 1,
            RiskPriority::P1 => self.p1 += 1,
            RiskPriority::P2 => self.p2 += 1,
            RiskPriority::P3 => self.p3 += 1,
        }
    }
}

fn priority_rank(priority: RiskPriority) -> u8 {
    match priority {
        RiskPriority::P0 => 0,
        RiskPriority::P1 => 1,
        RiskPriority::P2 => 2,
        RiskPriority::P3 => 3,
    }
}