use crate::work_cycle::{Role, WorkerState};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RoleNeed {
pub role: Role,
pub min_reputation: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Candidate {
pub id: u64,
pub role: Role,
pub reputation: u32,
pub available: bool,
}
impl From<WorkerState> for Candidate {
fn from(w: WorkerState) -> Self {
Candidate { id: w.id, role: w.role, reputation: w.reputation, available: w.available }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ranked {
pub id: u64,
pub score: u32,
}
pub fn score_candidate(need: &RoleNeed, candidate: &Candidate) -> Option<u32> {
if candidate.role != need.role
|| !candidate.available
|| candidate.reputation < need.min_reputation
{
return None;
}
Some(candidate.reputation)
}
pub fn rank_candidates(need: &RoleNeed, pool: &[Candidate]) -> Vec<Ranked> {
let mut ranked: Vec<Ranked> = pool
.iter()
.filter_map(|c| score_candidate(need, c).map(|score| Ranked { id: c.id, score }))
.collect();
ranked.sort_by(|a, b| b.score.cmp(&a.score).then(a.id.cmp(&b.id)));
ranked
}
pub fn best_candidate(need: &RoleNeed, pool: &[Candidate]) -> Option<Ranked> {
rank_candidates(need, pool).into_iter().next()
}
#[cfg(test)]
mod tests {
use super::*;
fn need(role: Role, min_rep: u32) -> RoleNeed {
RoleNeed { role, min_reputation: min_rep }
}
fn cand(id: u64, role: Role, rep: u32) -> Candidate {
Candidate { id, role, reputation: rep, available: true }
}
#[test]
fn scores_an_eligible_candidate_its_reputation() {
let n = need(Role::Coder, 2);
assert_eq!(score_candidate(&n, &cand(7, Role::Coder, 5)), Some(5));
assert_eq!(score_candidate(&n, &cand(7, Role::Coder, 2)), Some(2));
}
#[test]
fn rejects_wrong_role() {
let n = need(Role::Coder, 0);
assert_eq!(score_candidate(&n, &cand(7, Role::Reviewer, 9)), None);
assert_eq!(score_candidate(&n, &cand(7, Role::Accounting, 9)), None);
}
#[test]
fn rejects_below_min_reputation() {
let n = need(Role::Coder, 5);
assert_eq!(score_candidate(&n, &cand(7, Role::Coder, 4)), None); assert_eq!(score_candidate(&n, &cand(7, Role::Coder, 0)), None);
}
#[test]
fn rejects_unavailable_even_if_qualified() {
let n = need(Role::Coder, 0);
let busy = Candidate { available: false, ..cand(7, Role::Coder, 9) };
assert_eq!(score_candidate(&n, &busy), None);
}
#[test]
fn ranks_eligible_high_reputation_first() {
let n = need(Role::Coder, 0);
let pool = vec![cand(1, Role::Coder, 3), cand(2, Role::Coder, 9), cand(3, Role::Coder, 5)];
let ranked = rank_candidates(&n, &pool);
assert_eq!(
ranked,
vec![
Ranked { id: 2, score: 9 },
Ranked { id: 3, score: 5 },
Ranked { id: 1, score: 3 },
]
);
}
#[test]
fn rank_filters_out_every_ineligible_kind() {
let n = need(Role::Coder, 4);
let pool = vec![
cand(1, Role::Reviewer, 9), cand(2, Role::Coder, 3), Candidate { available: false, ..cand(3, Role::Coder, 8) }, cand(4, Role::Coder, 6), cand(5, Role::Coder, 4), ];
let ranked = rank_candidates(&n, &pool);
assert_eq!(ranked, vec![Ranked { id: 4, score: 6 }, Ranked { id: 5, score: 4 }]);
}
#[test]
fn rank_tie_breaks_on_lowest_id() {
let n = need(Role::Coder, 0);
let pool = vec![cand(9, Role::Coder, 5), cand(4, Role::Coder, 5), cand(7, Role::Coder, 5)];
let ids: Vec<u64> = rank_candidates(&n, &pool).into_iter().map(|r| r.id).collect();
assert_eq!(ids, vec![4, 7, 9]);
}
#[test]
fn rank_empty_and_all_ineligible_pools_yield_nothing() {
let n = need(Role::Coder, 5);
assert!(rank_candidates(&n, &[]).is_empty());
let none_fit =
vec![cand(1, Role::Marketing, 9), cand(2, Role::Coder, 1)];
assert!(rank_candidates(&n, &none_fit).is_empty());
}
#[test]
fn best_candidate_is_the_top_of_the_ranking() {
let n = need(Role::Reviewer, 1);
let pool =
vec![cand(1, Role::Reviewer, 4), cand(2, Role::Reviewer, 8), cand(3, Role::Coder, 9)];
assert_eq!(best_candidate(&n, &pool), Some(Ranked { id: 2, score: 8 }));
assert_eq!(best_candidate(&need(Role::Hr, 0), &pool), None);
assert_eq!(best_candidate(&n, &[]), None);
}
#[test]
fn best_candidate_tie_breaks_lowest_id_matching_assign_next_task() {
let n = need(Role::Coder, 0);
let pool = vec![cand(8, Role::Coder, 5), cand(3, Role::Coder, 5)];
assert_eq!(best_candidate(&n, &pool), Some(Ranked { id: 3, score: 5 }));
}
#[test]
fn candidate_from_worker_state_preserves_fields() {
let w = WorkerState { id: 42, role: Role::Accounting, reputation: 7, available: true };
let c: Candidate = w.into();
assert_eq!(c, Candidate { id: 42, role: Role::Accounting, reputation: 7, available: true });
let workers = vec![
WorkerState { id: 1, role: Role::Coder, reputation: 2, available: true },
WorkerState { id: 2, role: Role::Coder, reputation: 8, available: true },
];
let pool: Vec<Candidate> = workers.into_iter().map(Candidate::from).collect();
assert_eq!(best_candidate(&need(Role::Coder, 0), &pool), Some(Ranked { id: 2, score: 8 }));
}
}