use crate::Summary;
pub fn apply_prior_counts_to_summary(out: &mut Summary, prior: Summary, target_calls: u64) {
if target_calls == 0 || out.calls >= target_calls {
return;
}
if prior.calls == 0 {
return;
}
let need = target_calls.saturating_sub(out.calls);
if need == 0 {
return;
}
let need_f = need as f64;
let ok = (need_f * prior.ok_rate()).round() as u64;
let junk = (need_f * prior.junk_rate()).round() as u64;
let hard_junk = (need_f * prior.hard_junk_rate()).round() as u64;
let cost_units = (need_f * prior.mean_cost_units()).round() as u64;
let elapsed_ms_sum = (need_f * prior.mean_elapsed_ms()).round() as u64;
out.calls = out.calls.saturating_add(need);
out.ok = out.ok.saturating_add(ok.min(need));
out.junk = out.junk.saturating_add(junk.min(need));
out.hard_junk = out.hard_junk.saturating_add(hard_junk.min(need));
out.cost_units = out.cost_units.saturating_add(cost_units);
out.elapsed_ms_sum = out.elapsed_ms_sum.saturating_add(elapsed_ms_sum);
out.ok = out.ok.min(out.calls);
out.junk = out.junk.min(out.calls);
out.hard_junk = out.hard_junk.min(out.calls);
let out_obs = out.calls.saturating_sub(need) as f64; let prior_obs = need_f;
match (out.mean_quality_score, prior.mean_quality_score) {
(None, Some(prior_q)) => {
out.mean_quality_score = Some(prior_q.clamp(0.0, 1.0));
}
(Some(out_q), Some(prior_q)) => {
let total = out_obs + prior_obs;
if total > 0.0 {
out.mean_quality_score = Some(
((out_q * out_obs + prior_q.clamp(0.0, 1.0) * prior_obs) / total)
.clamp(0.0, 1.0),
);
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_summary(calls: u64, ok: u64, junk: u64, hard_junk: u64) -> Summary {
Summary {
calls,
ok,
junk,
hard_junk,
cost_units: 0,
elapsed_ms_sum: 0,
mean_quality_score: None,
}
}
#[test]
fn no_op_when_already_at_target() {
let mut out = make_summary(10, 8, 1, 0);
let prior = make_summary(100, 80, 10, 5);
apply_prior_counts_to_summary(&mut out, prior, 10);
assert_eq!(out.calls, 10, "should not change when calls >= target");
}
#[test]
fn no_op_when_target_zero() {
let mut out = make_summary(0, 0, 0, 0);
let prior = make_summary(100, 80, 10, 5);
apply_prior_counts_to_summary(&mut out, prior, 0);
assert_eq!(out.calls, 0);
}
#[test]
fn no_op_when_prior_empty() {
let mut out = make_summary(0, 0, 0, 0);
let prior = make_summary(0, 0, 0, 0);
apply_prior_counts_to_summary(&mut out, prior, 10);
assert_eq!(out.calls, 0, "empty prior should not add counts");
}
#[test]
fn adds_pseudo_counts() {
let mut out = make_summary(0, 0, 0, 0);
let prior = make_summary(100, 50, 20, 10);
apply_prior_counts_to_summary(&mut out, prior, 10);
assert_eq!(out.calls, 10);
assert_eq!(out.ok, 5); assert_eq!(out.junk, 2); assert_eq!(out.hard_junk, 1); }
#[test]
fn invariant_counts_do_not_exceed_calls() {
let mut out = make_summary(5, 5, 0, 0);
let prior = make_summary(10, 10, 10, 10);
apply_prior_counts_to_summary(&mut out, prior, 20);
assert!(out.ok <= out.calls);
assert!(out.junk <= out.calls);
assert!(out.hard_junk <= out.calls);
}
#[test]
fn prior_inherits_quality_score_when_out_has_none() {
let mut out = Summary {
calls: 0,
ok: 0,
junk: 0,
hard_junk: 0,
cost_units: 0,
elapsed_ms_sum: 0,
mean_quality_score: None,
};
let prior = Summary {
calls: 100,
ok: 80,
junk: 10,
hard_junk: 5,
cost_units: 0,
elapsed_ms_sum: 0,
mean_quality_score: Some(0.85),
};
apply_prior_counts_to_summary(&mut out, prior, 10);
let q = out.mean_quality_score.unwrap();
assert!(
(q - 0.85).abs() < 1e-10,
"should inherit prior quality: {q}"
);
}
#[test]
fn prior_blends_quality_score_weighted_by_calls() {
let mut out = Summary {
calls: 10,
ok: 9,
junk: 0,
hard_junk: 0,
cost_units: 0,
elapsed_ms_sum: 0,
mean_quality_score: Some(0.90),
};
let prior = Summary {
calls: 100,
ok: 80,
junk: 10,
hard_junk: 5,
cost_units: 0,
elapsed_ms_sum: 0,
mean_quality_score: Some(0.50),
};
apply_prior_counts_to_summary(&mut out, prior, 20); let q = out.mean_quality_score.unwrap();
assert!((q - 0.70).abs() < 1e-6, "blended quality: {q}");
}
#[test]
fn prior_quality_none_does_not_overwrite() {
let mut out = Summary {
calls: 10,
ok: 9,
junk: 0,
hard_junk: 0,
cost_units: 0,
elapsed_ms_sum: 0,
mean_quality_score: Some(0.90),
};
let prior = Summary {
calls: 100,
ok: 80,
junk: 10,
hard_junk: 5,
cost_units: 0,
elapsed_ms_sum: 0,
mean_quality_score: None,
};
apply_prior_counts_to_summary(&mut out, prior, 20);
assert_eq!(out.mean_quality_score, Some(0.90));
}
}