use std::collections::HashMap;
use crate::analyses::code_health::{CodeHealthRow, run_code_health};
use crate::analyses::hotspots::{HotspotRow, run_hotspots};
use crate::external::{ExternalStore, PathFindings};
use crate::facts::FactsDb;
use crate::{CodeLoreError, Options, Result};
#[derive(Debug, Clone, serde::Serialize)]
pub struct FindingHotspotOverlapRow {
pub path: String,
pub findings: u32,
pub engines: String,
pub worst_level: String,
pub hotspot_score: f64,
pub revs_percentile: f64,
pub health_band: String,
pub priority: String,
}
#[must_use]
pub fn priority_label(findings: usize, revs_percentile: f64, health_band: &str) -> &'static str {
if findings > 0 && revs_percentile >= 0.9 && health_band == "red" {
"act-now"
} else if revs_percentile >= 0.7 || health_band == "red" {
"plan"
} else {
"note"
}
}
pub fn run_finding_hotspot_overlap_with(
store: &ExternalStore,
hotspot_rows: &[HotspotRow],
health_rows: &[CodeHealthRow],
) -> Result<Vec<FindingHotspotOverlapRow>> {
let by_path: HashMap<String, PathFindings> = store.findings_by_path().map_err(|e| {
CodeLoreError::Analysis(format!("finding-hotspot-overlap: read store: {e}"))
})?;
if by_path.is_empty() {
return Err(CodeLoreError::Analysis(
"finding-hotspot-overlap requires prior `codelore ingest-sarif` \
(no external findings found)"
.to_string(),
));
}
let revs: Vec<u32> = hotspot_rows.iter().map(|r| r.revisions).collect();
let rank_by_idx = compute_percent_ranks(&revs);
let mut hotspot_map: HashMap<&str, (f64, f64)> = HashMap::new();
for (i, row) in hotspot_rows.iter().enumerate() {
hotspot_map.insert(row.path.as_str(), (row.hotspot_score, rank_by_idx[i]));
}
let health_band_map: HashMap<&str, &str> = health_rows
.iter()
.map(|r| (r.path.as_str(), r.band.as_str()))
.collect();
let mut rows: Vec<FindingHotspotOverlapRow> = by_path
.into_iter()
.map(|(path, pf)| {
let (hotspot_score, revs_percentile) = hotspot_map
.get(path.as_str())
.copied()
.unwrap_or((0.0, 0.0));
let health_band = health_band_map
.get(path.as_str())
.copied()
.unwrap_or("unknown")
.to_owned();
let mut engines = pf.engines;
engines.sort_unstable();
let priority = priority_label(pf.count, revs_percentile, &health_band).to_owned();
FindingHotspotOverlapRow {
path,
findings: u32::try_from(pf.count).unwrap_or(u32::MAX),
engines: engines.join(","),
worst_level: pf.worst_level,
hotspot_score,
revs_percentile,
health_band,
priority,
}
})
.collect();
rows.sort_by(|a, b| {
priority_rank(&a.priority)
.cmp(&priority_rank(&b.priority))
.then(b.findings.cmp(&a.findings))
.then(a.path.cmp(&b.path))
});
Ok(rows)
}
pub fn run_finding_hotspot_overlap(
db: &FactsDb,
opts: &Options,
store: &ExternalStore,
) -> Result<Vec<FindingHotspotOverlapRow>> {
let full = opts.with_no_row_limit();
let hotspot_rows = run_hotspots(db, &full)?;
let health_rows = run_code_health(db, &full)?;
let mut rows = run_finding_hotspot_overlap_with(store, &hotspot_rows, &health_rows)?;
if let Some(limit) = opts.rows_limit {
rows.truncate(limit as usize);
}
Ok(rows)
}
fn priority_rank(p: &str) -> u8 {
match p {
"act-now" => 0,
"plan" => 1,
_ => 2,
}
}
fn compute_percent_ranks(revs: &[u32]) -> Vec<f64> {
let n = revs.len();
if n == 0 {
return Vec::new();
}
let mut sorted: Vec<(usize, u32)> = revs.iter().copied().enumerate().collect();
sorted.sort_by_key(|&(_, r)| r);
let mut ranks = vec![0.0f64; n];
if n == 1 {
return ranks; }
let mut i = 0usize;
while i < n {
let group_revs = sorted[i].1;
let mut j = i + 1;
while j < n && sorted[j].1 == group_revs {
j += 1;
}
#[allow(clippy::cast_precision_loss)]
let rank = i as f64 / (n - 1) as f64;
for &(orig_idx, _) in &sorted[i..j] {
ranks[orig_idx] = rank;
}
i = j;
}
ranks
}
#[cfg(test)]
mod tests {
use super::{compute_percent_ranks, priority_label};
#[test]
fn tied_revision_counts_get_same_percentile_rank() {
let ranks = compute_percent_ranks(&[5, 5, 10]);
assert_eq!(ranks.len(), 3);
assert!((ranks[0] - 0.0).abs() < f64::EPSILON, "first 5 → 0.0");
assert!((ranks[1] - 0.0).abs() < f64::EPSILON, "second 5 → 0.0");
assert!((ranks[2] - 1.0).abs() < f64::EPSILON, "10 → 1.0");
}
#[test]
fn no_ties_produces_evenly_spaced_ranks() {
let ranks = compute_percent_ranks(&[1, 2, 3]);
assert!((ranks[0] - 0.0).abs() < f64::EPSILON);
assert!((ranks[1] - 0.5).abs() < f64::EPSILON);
assert!((ranks[2] - 1.0).abs() < f64::EPSILON);
}
#[test]
fn single_entry_rank_is_zero() {
let ranks = compute_percent_ranks(&[42]);
assert_eq!(ranks.len(), 1);
assert!((ranks[0] - 0.0).abs() < f64::EPSILON);
}
#[test]
fn empty_input_produces_empty_output() {
assert!(compute_percent_ranks(&[]).is_empty());
}
#[test]
fn all_tied_produces_all_zeros() {
let ranks = compute_percent_ranks(&[7, 7, 7]);
assert!(ranks.iter().all(|&r| r == 0.0));
}
#[test]
fn act_now_requires_all_three_conditions() {
assert_eq!(priority_label(3, 0.9, "red"), "act-now");
assert_eq!(priority_label(1, 1.0, "red"), "act-now");
assert_eq!(priority_label(1, 0.9, "red"), "act-now");
}
#[test]
fn act_now_fails_when_findings_zero() {
assert_ne!(priority_label(0, 0.95, "red"), "act-now");
}
#[test]
fn act_now_fails_when_percentile_below_threshold() {
assert_ne!(priority_label(2, 0.89, "red"), "act-now");
}
#[test]
fn act_now_fails_when_band_not_red() {
assert_ne!(priority_label(2, 0.95, "yellow"), "act-now");
assert_ne!(priority_label(2, 0.95, "green"), "act-now");
assert_ne!(priority_label(2, 0.95, "unknown"), "act-now");
}
#[test]
fn plan_via_high_percentile() {
assert_eq!(priority_label(1, 0.7, "green"), "plan");
assert_eq!(priority_label(1, 0.7, "unknown"), "plan");
assert_eq!(priority_label(1, 0.8, "yellow"), "plan");
}
#[test]
fn plan_via_red_band() {
assert_eq!(priority_label(1, 0.0, "red"), "plan");
assert_eq!(priority_label(1, 0.5, "red"), "plan");
}
#[test]
fn plan_boundary_exactly_0_7() {
assert_eq!(priority_label(1, 0.7, "green"), "plan");
assert_eq!(priority_label(1, 0.699_999, "green"), "note");
}
#[test]
fn note_is_default() {
assert_eq!(priority_label(1, 0.0, "green"), "note");
assert_eq!(priority_label(1, 0.5, "yellow"), "note");
assert_eq!(priority_label(1, 0.5, "unknown"), "note");
}
}