kitt_score 0.1.0

Decision engine at the core of Project KITT — in-memory stateful matching with pluggable scoring backends.
Documentation
//! Default decider: argmax over `score`, ignoring `priority`.

use crate::scoring::Candidate;

/// Pick the candidate with the highest score; ties broken by position. Ignores priority.
pub struct DefaultDecider;

impl super::Decide for DefaultDecider {
    fn decide(&self, candidates: &[Candidate]) -> Option<usize> {
        if candidates.is_empty() {
            return None;
        }
        let mut best = 0;
        for i in 1..candidates.len() {
            if candidates[i].score.score > candidates[best].score.score {
                best = i;
            }
        }
        Some(best)
    }
}

#[cfg(test)]
mod tests {
    use super::super::Decide;
    use super::*;
    use crate::scoring::ScoreResult;
    use crate::ActionId;

    #[test]
    fn picks_highest_score_ignoring_priority() {
        let cs = vec![
            Candidate {
                action_id: ActionId::from("action-1"),
                score: ScoreResult {
                    priority: 99,
                    score: 0.1,
                },
            },
            Candidate {
                action_id: ActionId::from("action-2"),
                score: ScoreResult {
                    priority: 0,
                    score: 0.9,
                },
            },
        ];
        assert_eq!(DefaultDecider.decide(&cs), Some(1));
    }

    #[test]
    fn none_on_empty() {
        let d = DefaultDecider;
        assert!(d.decide(&[]).is_none());
    }
}