pub const QUALITY_FORMULA_VERSION: &str = "v1";
const W_REVERT: f64 = 0.35;
const W_BUGFIX: f64 = 0.40;
const W_TICKET: f64 = 0.25;
const BAND: f64 = 0.20;
#[derive(Debug, Clone, Copy)]
pub struct QualityInputs {
pub commits: usize,
pub reverts: usize,
pub bugfixes: usize,
pub ticketed: usize,
}
pub fn quality_score(inputs: QualityInputs) -> f64 {
if inputs.commits == 0 {
return W_REVERT + W_BUGFIX;
}
let n = inputs.commits as f64;
let revert_rate = (inputs.reverts as f64 / n).clamp(0.0, 1.0);
let bugfix_rate = (inputs.bugfixes as f64 / n).clamp(0.0, 1.0);
let ticket_rate = (inputs.ticketed as f64 / n).clamp(0.0, 1.0);
let score =
W_REVERT * (1.0 - revert_rate) + W_BUGFIX * (1.0 - bugfix_rate) + W_TICKET * ticket_rate;
score.clamp(0.0, 1.0)
}
pub fn size_for_quality_score(score: f64) -> u8 {
let s = score.clamp(0.0, 1.0);
if s <= BAND {
1
} else if s <= 2.0 * BAND {
2
} else if s <= 3.0 * BAND {
3
} else if s <= 4.0 * BAND {
4
} else {
5
}
}
pub fn score_and_tshirt(inputs: QualityInputs) -> (f64, String) {
let score = quality_score(inputs);
let tshirt = size_for_quality_score(score).to_string();
(score, tshirt)
}
#[cfg(test)]
mod tests {
use super::*;
fn inp(commits: usize, reverts: usize, bugfixes: usize, ticketed: usize) -> QualityInputs {
QualityInputs {
commits,
reverts,
bugfixes,
ticketed,
}
}
#[test]
fn formula_version_is_v1() {
assert_eq!(QUALITY_FORMULA_VERSION, "v1");
}
#[test]
fn all_good_scores_one() {
let (score, tshirt) = score_and_tshirt(inp(10, 0, 0, 10));
assert!((score - 1.0).abs() < 1e-9, "score = {score}");
assert_eq!(tshirt, "5");
}
#[test]
fn all_reverts_and_bugfixes() {
let (score, tshirt) = score_and_tshirt(inp(4, 4, 4, 0));
assert!((score - 0.0).abs() < 1e-9, "score = {score}");
assert_eq!(tshirt, "1");
}
#[test]
fn all_ticketed_no_reverts_or_bugfixes_is_best() {
let score = quality_score(inp(8, 0, 0, 8));
assert!((score - 1.0).abs() < 1e-9);
}
#[test]
fn neutral_midpoint_without_ticketing() {
let score = quality_score(inp(5, 0, 0, 0));
assert!((score - 0.75).abs() < 1e-9, "score = {score}");
assert_eq!(size_for_quality_score(score), 4);
}
#[test]
fn zero_commits_is_neutral() {
let score = quality_score(inp(0, 0, 0, 0));
assert!((score - 0.75).abs() < 1e-9, "score = {score}");
}
#[test]
fn half_reverts_lowers_score() {
let score = quality_score(inp(4, 2, 0, 0));
assert!((score - 0.575).abs() < 1e-9, "score = {score}");
assert_eq!(size_for_quality_score(score), 3);
}
#[test]
fn rates_clamp_when_subcounts_exceed_commits() {
let score = quality_score(inp(2, 3, 3, 5));
assert!((0.0..=1.0).contains(&score), "score = {score}");
}
#[test]
fn bucketing_band_edges() {
assert_eq!(size_for_quality_score(0.0), 1);
assert_eq!(size_for_quality_score(0.20), 1);
assert_eq!(size_for_quality_score(0.2001), 2);
assert_eq!(size_for_quality_score(0.40), 2);
assert_eq!(size_for_quality_score(0.60), 3);
assert_eq!(size_for_quality_score(0.80), 4);
assert_eq!(size_for_quality_score(0.8001), 5);
assert_eq!(size_for_quality_score(1.0), 5);
assert_eq!(size_for_quality_score(-1.0), 1);
assert_eq!(size_for_quality_score(2.0), 5);
}
#[test]
fn score_and_tshirt_pairs() {
let (score, tshirt) = score_and_tshirt(inp(10, 0, 0, 5));
assert!((score - 0.875).abs() < 1e-9, "score = {score}");
assert_eq!(tshirt, "5");
}
}