use super::quote_if_needed;
use crate::analyses::authors::AuthorsRow;
use crate::analyses::communication::CommunicationRow;
use crate::analyses::ownership::OwnershipRow;
use crate::{CodeLoreError, Result};
use std::io::Write;
pub fn write_communication_csv<W: Write>(
rows: &[CommunicationRow],
w: &mut W,
code_maat_compat: bool,
) -> Result<()> {
let header = if code_maat_compat {
"author,peer,shared,average,strength"
} else {
"author-a,author-b,shared,average,strength"
};
writeln!(w, "{header}").map_err(CodeLoreError::Io)?;
for row in rows {
#[allow(clippy::cast_possible_truncation)]
let strength_cell = if code_maat_compat {
format!("{}", row.strength as i64)
} else {
format!("{:.2}", row.strength)
};
writeln!(
w,
"{},{},{},{},{}",
quote_if_needed(&row.author_a),
quote_if_needed(&row.author_b),
row.shared,
row.average,
strength_cell
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_ownership_csv<W: Write>(
rows: &[OwnershipRow],
w: &mut W,
code_maat_compat: bool,
) -> Result<()> {
if code_maat_compat {
writeln!(w, "entity,fractal-value,total-revs").map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{:.2},{}",
quote_if_needed(&row.path),
row.fractal_value,
row.total_revs,
)
.map_err(CodeLoreError::Io)?;
}
} else {
writeln!(w, "entity,main-author,total-revs,fractal-value").map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{},{:.2}",
quote_if_needed(&row.path),
quote_if_needed(&row.main_author),
row.total_revs,
row.fractal_value
)
.map_err(CodeLoreError::Io)?;
}
}
Ok(())
}
pub fn write_authors_csv<W: Write>(
rows: &[AuthorsRow],
w: &mut W,
code_maat_compat: bool,
) -> Result<()> {
if code_maat_compat {
writeln!(w, "entity,n-authors,n-revs").map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{}",
quote_if_needed(&row.entity),
row.n_authors,
row.n_revs
)
.map_err(CodeLoreError::Io)?;
}
} else {
writeln!(
w,
"entity,n_authors,n_humans,n_bots,n_revs,last_author,last_modified"
)
.map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{},{},{},{},{}",
quote_if_needed(&row.entity),
row.n_authors,
row.n_humans,
row.n_bots,
row.n_revs,
quote_if_needed(&row.last_author),
row.last_modified,
)
.map_err(CodeLoreError::Io)?;
}
}
Ok(())
}
pub fn write_top_committers_csv<W: Write>(
rows: &[crate::analyses::top_committers::TopCommittersRow],
w: &mut W,
) -> Result<()> {
writeln!(
w,
"author,commits,loc_added,loc_deleted,first_commit,last_commit,is_bot"
)
.map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{},{},{},{},{}",
quote_if_needed(&row.author),
row.commits,
row.loc_added,
row.loc_deleted,
row.first_commit,
row.last_commit,
row.is_bot,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_code_familiarity_csv<W: Write>(
rows: &[crate::analyses::code_familiarity::CodeFamiliarityRow],
w: &mut W,
) -> Result<()> {
writeln!(
w,
"scope,familiarity-pct,active-authors,total-authors,islands-pct,verdict"
)
.map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{:.2},{},{},{:.2},{}",
quote_if_needed(&row.scope),
row.familiarity_pct,
row.active_authors,
row.total_authors,
row.islands_pct,
quote_if_needed(&row.verdict),
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_coordination_needs_csv<W: Write>(
rows: &[crate::analyses::coordination_needs::CoordinationNeedsRow],
w: &mut W,
) -> Result<()> {
writeln!(
w,
"path,authors,fragmentation,interleave,cochange-entropy,tier,health-band,total-commits"
)
.map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{:.4},{:.4},{:.4},{},{},{}",
quote_if_needed(&row.path),
row.authors,
row.fragmentation,
row.interleave,
row.cochange_entropy,
quote_if_needed(&row.tier),
quote_if_needed(&row.health_band),
row.total_commits,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_pair_programming_csv<W: Write>(
rows: &[crate::analyses::pair_programming::PairRow],
w: &mut W,
) -> Result<()> {
writeln!(w, "author_a,author_b,pair_commits").map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{}",
quote_if_needed(&row.author_a),
quote_if_needed(&row.author_b),
row.pair_commits,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_bus_factor_csv<W: Write>(
rows: &[crate::analyses::bus_factor::BusFactorRow],
w: &mut W,
) -> Result<()> {
writeln!(
w,
"module,total_commits,bus_factor,top_contributor,top_contributor_share,model"
)
.map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{},{},{:.4},{}",
quote_if_needed(&row.module),
row.total_commits,
row.bus_factor,
quote_if_needed(&row.top_contributor),
row.top_contributor_share,
row.model,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_knowledge_islands_csv<W: Write>(
rows: &[crate::analyses::knowledge_islands::KnowledgeIslandRow],
w: &mut W,
) -> Result<()> {
writeln!(
w,
"entity,main_author,ownership_pct,days_since_main_active,last_main_author_commit,n_substantial_others,total_loc"
)
.map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{:.2},{},{},{},{}",
quote_if_needed(&row.entity),
quote_if_needed(&row.main_author),
row.ownership_pct,
row.days_since_main_active,
row.last_main_author_commit,
row.n_substantial_others,
row.total_loc,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
fn write_main_dev_csv_with_headers<W: Write>(
rows: &[crate::analyses::main_dev::MainDevRow],
w: &mut W,
metric_name: &str,
total_name: &str,
) -> Result<()> {
writeln!(w, "entity,main-dev,{metric_name},{total_name},ownership")
.map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{},{},{:.2}",
quote_if_needed(&row.entity),
quote_if_needed(&row.main_dev),
row.metric,
row.total,
row.ownership,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_main_dev_csv<W: Write>(
rows: &[crate::analyses::main_dev::MainDevRow],
w: &mut W,
) -> Result<()> {
write_main_dev_csv_with_headers(rows, w, "added", "total-added")
}
pub fn write_main_dev_by_revs_csv<W: Write>(
rows: &[crate::analyses::main_dev::MainDevRow],
w: &mut W,
code_maat_compat: bool,
) -> Result<()> {
if code_maat_compat {
write_main_dev_csv_with_headers(rows, w, "added", "total-added")
} else {
write_main_dev_csv_with_headers(rows, w, "revisions", "total-revisions")
}
}
pub fn write_main_dev_by_deletions_csv<W: Write>(
rows: &[crate::analyses::main_dev::MainDevRow],
w: &mut W,
) -> Result<()> {
write_main_dev_csv_with_headers(rows, w, "removed", "total-removed")
}
pub fn write_entity_effort_csv<W: Write>(
rows: &[crate::analyses::entity_effort::EntityEffortRow],
w: &mut W,
) -> Result<()> {
writeln!(w, "entity,author,author-revs,total-revs").map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{},{}",
quote_if_needed(&row.entity),
quote_if_needed(&row.author),
row.author_revs,
row.total_revs,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_entity_ownership_csv<W: Write>(
rows: &[crate::analyses::entity_ownership::EntityOwnershipRow],
w: &mut W,
) -> Result<()> {
writeln!(w, "entity,author,added,deleted").map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{},{}",
quote_if_needed(&row.entity),
quote_if_needed(&row.author),
row.added,
row.deleted,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_team_composition_csv<W: Write>(
rows: &[crate::analyses::team_composition::TeamCompositionRow],
w: &mut W,
) -> Result<()> {
writeln!(
w,
"author,tenure-days,bucket,veteran-breadth-ok,active,commits,files-touched,onboarding-weeks"
)
.map_err(CodeLoreError::Io)?;
for row in rows {
if row.author == "__summary__" {
continue;
}
let ob = row
.onboarding_weeks
.map_or_else(String::new, |v| v.to_string());
writeln!(
w,
"{},{},{},{},{},{},{},{}",
quote_if_needed(&row.author),
row.tenure_days,
quote_if_needed(&row.bucket),
row.veteran_breadth_ok,
row.active,
row.commits,
row.files_touched,
ob,
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
pub fn write_marginal_owner_risk_csv<W: Write>(
rows: &[crate::analyses::marginal_owner_risk::MarginalOwnerRiskRow],
w: &mut W,
) -> Result<()> {
writeln!(w, "path,band,top-active-share,risk").map_err(CodeLoreError::Io)?;
for row in rows {
writeln!(
w,
"{},{},{:.4},{}",
quote_if_needed(&row.path),
quote_if_needed(&row.band),
row.top_active_share,
quote_if_needed(&row.risk),
)
.map_err(CodeLoreError::Io)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
#[test]
fn team_composition_csv_skips_summary_row() {
use crate::analyses::team_composition::TeamCompositionRow;
let rows = vec![
TeamCompositionRow {
author: "alice".to_string(),
tenure_days: 110,
bucket: "experienced".to_string(),
veteran_breadth_ok: false,
active: true,
commits: 12,
files_touched: 3,
onboarding_weeks: None,
},
TeamCompositionRow {
author: "__summary__".to_string(),
tenure_days: 0,
bucket: "onboarded=33.3% experienced=66.7% veteran=0.0%".to_string(),
veteran_breadth_ok: false,
active: false,
commits: 0,
files_touched: 0,
onboarding_weeks: None,
},
];
let mut buf = Vec::new();
super::write_team_composition_csv(&rows, &mut buf).expect("write csv");
let out = String::from_utf8(buf).expect("utf8");
assert!(
!out.contains("__summary__"),
"csv output must not contain the __summary__ carrier row: {out}"
);
assert!(
out.contains("alice"),
"csv output must contain real author rows: {out}"
);
}
}