kitt_score 0.1.0

Decision engine at the core of Project KITT — in-memory stateful matching with pluggable scoring backends.
Documentation
//! Predicate DSL → bytecode scorer backend.

pub mod ast;
pub mod bytecode;
pub mod parser;
pub mod typecheck;
pub mod vm;

use crate::location::state::LocationView;
use crate::{
    scoring::{BuildErr, Scorer, ScorerBuilder, ScorerSpec},
    Schema,
};

/// Builder for predicate-DSL scorers.
pub struct PredicateBuilder;

/// Compiled predicate-DSL scorer.
pub struct PredicateScorer {
    program: bytecode::Program,
}

impl Scorer for PredicateScorer {
    fn score(&self, state: &LocationView<'_>) -> f32 {
        vm::run(&self.program, state)
    }
}

impl ScorerBuilder for PredicateBuilder {
    type Scorer = PredicateScorer;

    fn build(&self, spec: &ScorerSpec<'_>, schema: &Schema) -> Result<Self::Scorer, BuildErr> {
        let src = match spec {
            ScorerSpec::Predicate(s) => *s,
            ScorerSpec::Vector { .. } => {
                return Err(BuildErr::Type(
                    "PredicateBuilder requires ScorerSpec::Predicate".into(),
                ))
            }
        };
        let ast = parser::Parser::new(src).parse().map_err(BuildErr::Parse)?;
        let program = typecheck::compile(&ast, schema)?;
        Ok(PredicateScorer { program })
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use super::*;
    use crate::event::{AttrSet, KindRef};
    use crate::location::state::LocationState;
    use crate::schema::attr::Value;
    use crate::schema::{AttrType, SchemaBuilder};
    use smallvec::smallvec;

    #[test]
    fn end_to_end_predicate_backend() {
        let mut b = SchemaBuilder::new();
        let _kid_builder = b.kind(
            "audience",
            &[("male_frac", AttrType::F32), ("dwell", AttrType::Int)],
        );
        let schema = b.build();

        let scorer = PredicateBuilder
            .build(
                &ScorerSpec::Predicate("$audience.male_frac * $audience.dwell"),
                &schema,
            )
            .unwrap();

        let mut st: LocationState<()> = LocationState::new(schema.clone());
        let kid = schema.kind("audience").unwrap();
        let attrs = AttrSet {
            entries: smallvec![
                (schema.attr("male_frac").unwrap(), Value::F32(0.4)),
                (schema.attr("dwell").unwrap(), Value::Int(30)),
            ],
        };
        st.apply_update(KindRef::Id(kid), &attrs);

        assert!((scorer.score(&st.view()) - 12.0).abs() < 1e-4);
    }
}