use crate::analyses::code_health::{SMELL_WEIGHTS, run_code_health};
use crate::analyses::coupling::run_coupling;
use crate::analyses::cycle_health::run_cycle_health;
use crate::analyses::function_xray::run_function_xray;
use crate::analyses::hotspots::run_hotspots;
use crate::analyses::ownership::run_ownership;
use crate::defect_calibration;
use crate::defect_calibration::validate::capture_intensities;
use crate::facts::FactsDb;
use crate::repo::Repo;
use crate::{CodeLoreError, Options, Result};
use sha2::{Digest, Sha256};
type Kv = (String, String);
type Section = (String, Vec<Kv>);
const TOP_N: usize = 5;
#[must_use]
pub fn fmt_num(value: f64) -> String {
let formatted = format!("{value:.6}");
let trimmed = formatted.trim_end_matches('0').trim_end_matches('.');
trimmed.to_string()
}
fn render_canonical(sections: &[Section]) -> String {
let mut out = String::new();
for (section, facts) in sections {
out.push_str(section);
out.push('\n');
for (key, value) in facts {
out.push_str(" ");
out.push_str(key);
out.push_str(" = ");
out.push_str(value);
out.push('\n');
}
}
out
}
fn collect_numeric(sections: &[Section]) -> Vec<f64> {
let mut out = Vec::new();
for (_, facts) in sections {
for (_, value) in facts {
if let Ok(n) = value.trim().parse::<f64>()
&& n.is_finite()
{
out.push(n);
}
}
}
out
}
fn digest_of(text: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(text.as_bytes());
hex::encode(hasher.finalize())
}
#[derive(Debug, Clone)]
pub struct FileFactSheet {
pub path: String,
pub sections: Vec<(String, Vec<(String, String)>)>,
}
impl FileFactSheet {
pub fn build<R: Repo>(db: &FactsDb, repo: &R, opts: &Options, path: &str) -> Result<Self> {
let opts = opts.with_no_row_limit();
let mut sections: Vec<Section> = Vec::new();
sections.push(code_health_section(db, &opts, path)?);
if let Some(section) = biomarkers_section(db, path)? {
sections.push(section);
}
if let Some(section) = hotspots_section(db, &opts, path)? {
sections.push(section);
}
if let Some(section) = coupling_section(db, &opts, path)? {
sections.push(section);
}
if let Some(section) = ownership_section(db, &opts, path)? {
sections.push(section);
}
if let Some(section) = functions_section(db, repo, &opts, path) {
sections.push(section);
}
if let Some(section) = cycle_section(db, &opts, path)? {
sections.push(section);
}
if let Some(section) = defect_evidence_section(&opts)? {
sections.push(section);
}
Ok(Self {
path: path.to_string(),
sections,
})
}
#[must_use]
pub fn to_canonical_text(&self) -> String {
render_canonical(&self.sections)
}
#[must_use]
pub fn to_human_text(&self) -> String {
let mut out = format!("fact sheet for {}\n", self.path);
for (section, facts) in &self.sections {
out.push('\n');
out.push('[');
out.push_str(section);
out.push_str("]\n");
for (key, value) in facts {
out.push_str(" ");
out.push_str(key);
out.push_str(" = ");
out.push_str(value);
out.push('\n');
}
}
out
}
#[must_use]
pub fn digest(&self) -> String {
digest_of(&self.to_canonical_text())
}
#[must_use]
pub fn numeric_values(&self) -> Vec<f64> {
collect_numeric(&self.sections)
}
}
#[derive(Debug, Clone)]
pub struct DiffFactSheet {
pub sections: Vec<(String, Vec<(String, String)>)>,
}
impl DiffFactSheet {
#[must_use]
pub fn from_sections(sections: Vec<(String, Vec<(String, String)>)>) -> Self {
Self { sections }
}
#[must_use]
pub fn to_canonical_text(&self) -> String {
render_canonical(&self.sections)
}
#[must_use]
pub fn digest(&self) -> String {
digest_of(&self.to_canonical_text())
}
#[must_use]
pub fn numeric_values(&self) -> Vec<f64> {
collect_numeric(&self.sections)
}
}
fn code_health_section(db: &FactsDb, opts: &Options, path: &str) -> Result<Section> {
let rows = run_code_health(db, opts)?;
let row = rows.iter().find(|r| r.path == path).ok_or_else(|| {
CodeLoreError::Analysis(format!(
"no code-health data for {path} — is it a tracked source file?"
))
})?;
let mut facts = vec![
("score".to_string(), fmt_num(row.score)),
("band".to_string(), row.band.clone()),
("structural_risk".to_string(), fmt_num(row.structural_risk)),
("percentile".to_string(), fmt_num(row.percentile)),
];
if let Some(corpus) = row.corpus_percentile {
facts.push(("corpus_percentile".to_string(), fmt_num(corpus)));
if let (Some(lo), Some(hi)) = (row.corpus_percentile_ci_low, row.corpus_percentile_ci_high)
{
facts.push((
"corpus_percentile_ci".to_string(),
format!("{}–{}", fmt_num(lo), fmt_num(hi)),
));
}
}
facts.push(("cognitive".to_string(), fmt_num(row.cognitive)));
Ok(("code-health".to_string(), facts))
}
fn biomarkers_section(db: &FactsDb, path: &str) -> Result<Option<Section>> {
let intensities = capture_intensities(db)?;
let Some(values) = intensities.get(path) else {
return Ok(None);
};
let facts = SMELL_WEIGHTS
.iter()
.enumerate()
.map(|(i, &(name, _))| (name.to_string(), fmt_num(values[i])))
.collect();
Ok(Some(("biomarkers".to_string(), facts)))
}
fn hotspots_section(db: &FactsDb, opts: &Options, path: &str) -> Result<Option<Section>> {
let rows = run_hotspots(db, opts)?;
let Some(rank) = rows.iter().position(|r| r.path == path) else {
return Ok(None);
};
let row = &rows[rank];
let facts = vec![
("rank".to_string(), (rank + 1).to_string()),
("hotspot_score".to_string(), fmt_num(row.hotspot_score)),
];
Ok(Some(("hotspots".to_string(), facts)))
}
fn coupling_section(db: &FactsDb, opts: &Options, path: &str) -> Result<Option<Section>> {
let rows = run_coupling(db, opts)?;
let mut partners: Vec<(&str, u32, f64, f64)> = rows
.iter()
.filter_map(|r| {
if r.entity_a == path {
Some((r.entity_b.as_str(), r.shared, r.degree, r.fisher_p))
} else if r.entity_b == path {
Some((r.entity_a.as_str(), r.shared, r.degree, r.fisher_p))
} else {
None
}
})
.collect();
if partners.is_empty() {
return Ok(None);
}
partners.sort_by(|a, b| b.2.total_cmp(&a.2).then_with(|| a.0.cmp(b.0)));
partners.truncate(TOP_N);
let mut facts = Vec::new();
for (i, (partner, shared, degree, fisher_p)) in partners.iter().enumerate() {
let n = i + 1;
facts.push((format!("{n}.partner"), (*partner).to_string()));
facts.push((format!("{n}.shared"), shared.to_string()));
facts.push((format!("{n}.degree"), fmt_num(*degree)));
facts.push((format!("{n}.fisher_p"), fmt_num(*fisher_p)));
}
Ok(Some(("coupling".to_string(), facts)))
}
fn ownership_section(db: &FactsDb, opts: &Options, path: &str) -> Result<Option<Section>> {
let rows = run_ownership(db, opts)?;
let Some(row) = rows.iter().find(|r| r.path == path) else {
return Ok(None);
};
let facts = vec![
("main_author".to_string(), row.main_author.clone()),
("total_revs".to_string(), row.total_revs.to_string()),
("fractal_value".to_string(), fmt_num(row.fractal_value)),
];
Ok(Some(("ownership".to_string(), facts)))
}
fn functions_section<R: Repo>(
db: &FactsDb,
repo: &R,
opts: &Options,
path: &str,
) -> Option<Section> {
let rows = run_function_xray(db, repo, opts, path).ok()?;
if rows.is_empty() {
return None;
}
let mut facts = Vec::new();
for (i, row) in rows.iter().take(TOP_N).enumerate() {
let n = i + 1;
facts.push((format!("{n}.function"), row.function.clone()));
facts.push((format!("{n}.change_freq"), row.change_freq.to_string()));
facts.push((format!("{n}.loc"), row.loc.to_string()));
if let Some(cyclomatic) = row.cyclomatic {
facts.push((format!("{n}.cyclomatic"), cyclomatic.to_string()));
}
if let Some(cognitive) = row.cognitive {
facts.push((format!("{n}.cognitive"), cognitive.to_string()));
}
}
Some(("functions".to_string(), facts))
}
fn cycle_section(db: &FactsDb, opts: &Options, path: &str) -> Result<Option<Section>> {
let rows = run_cycle_health(db, opts)?;
let mut facts = Vec::new();
let mut n = 0;
for row in &rows {
if row.extract_candidate != path && !row.members_preview.contains(path) {
continue;
}
n += 1;
facts.push((format!("{n}.cycle_id"), row.cycle_id.to_string()));
facts.push((format!("{n}.size"), row.size.to_string()));
facts.push((format!("{n}.heat_pct"), fmt_num(row.heat_pct)));
facts.push((format!("{n}.verdict"), row.verdict.clone()));
facts.push((
format!("{n}.extract_candidate"),
row.extract_candidate.clone(),
));
if let Some(drop) = row.predicted_pc_drop {
facts.push((format!("{n}.predicted_pc_drop"), fmt_num(drop)));
}
}
if facts.is_empty() {
return Ok(None);
}
Ok(Some(("cycle".to_string(), facts)))
}
fn defect_evidence_section(opts: &Options) -> Result<Option<Section>> {
let Some(artifact_path) = &opts.defect_calibration else {
return Ok(None);
};
let artifact = defect_calibration::load(artifact_path)?;
defect_calibration::check_repo_identity(
&artifact,
&opts.repo_path,
opts.allow_foreign_calibration,
)?;
let validation = &artifact.validation;
let mut facts = vec![("vintage".to_string(), artifact.vintage.clone())];
if let Some(auc) = validation.auc_default {
facts.push(("auc_default".to_string(), fmt_num(auc)));
}
if let Some(precision) = validation.precision_at_10 {
facts.push(("precision_at_10".to_string(), fmt_num(precision)));
}
if let Some(precision) = validation.precision_at_red {
facts.push(("precision_at_red".to_string(), fmt_num(precision)));
}
facts.push((
"implicated_files".to_string(),
validation.implicated_files.to_string(),
));
facts.push((
"linked_defects".to_string(),
validation.linked_defects.to_string(),
));
for (band, changes, share) in &validation.band_table {
facts.push((format!("band:{band}:changes"), changes.to_string()));
facts.push((format!("band:{band}:share"), fmt_num(*share)));
}
Ok(Some(("defect-evidence".to_string(), facts)))
}
#[cfg(test)]
mod tests {
use super::{DiffFactSheet, FileFactSheet, fmt_num};
#[test]
fn fmt_num_trims_trailing_zeros_and_point() {
assert_eq!(fmt_num(2.0), "2");
assert_eq!(fmt_num(0.0), "0");
assert_eq!(fmt_num(0.803), "0.803");
assert_eq!(fmt_num(87.5), "87.5");
assert_eq!(fmt_num(0.000_001), "0.000001");
}
#[test]
fn canonical_text_is_section_key_value_lines() {
let sheet = FileFactSheet {
path: "x.rs".to_string(),
sections: vec![(
"code-health".to_string(),
vec![
("score".to_string(), "87.5".to_string()),
("band".to_string(), "green".to_string()),
],
)],
};
assert_eq!(
sheet.to_canonical_text(),
"code-health\n score = 87.5\n band = green\n"
);
}
#[test]
fn numeric_values_parses_only_whole_number_values() {
let sheet = FileFactSheet {
path: "x.rs".to_string(),
sections: vec![
(
"code-health".to_string(),
vec![
("score".to_string(), "87.5".to_string()),
("band".to_string(), "green".to_string()),
],
),
(
"biomarkers".to_string(),
vec![
("dry".to_string(), "0.5".to_string()),
("count".to_string(), "3".to_string()),
],
),
],
};
assert_eq!(sheet.numeric_values(), vec![87.5, 0.5, 3.0]);
}
#[test]
fn diff_sheet_shares_the_canonical_renderer() {
let sections = vec![(
"verdict".to_string(),
vec![("ratio".to_string(), "1.25".to_string())],
)];
let sheet = DiffFactSheet::from_sections(sections);
assert_eq!(sheet.to_canonical_text(), "verdict\n ratio = 1.25\n");
assert_eq!(sheet.numeric_values(), vec![1.25]);
assert_eq!(sheet.digest().len(), 64);
assert!(sheet.digest().chars().all(|c| c.is_ascii_hexdigit()));
}
}