#![allow(clippy::unwrap_used)]
#![allow(clippy::panic)]
use kitt_score::event::{ActionIngest, AttrSet, KindRef, StateUpdate, Trigger};
use kitt_score::location::LocationDef;
use kitt_score::schema::attr::{AttrType, Value};
use kitt_score::{ActionId, Engine, Ingested, LocId, SchemaBuilder, ScorerSpec, VectorMetric};
const fn ingested_debug<T>(i: &Ingested<T>) -> &'static str {
match i {
Ingested::Updated => "Updated",
Ingested::Registered(_) => "Registered",
Ingested::Decided(_) => "Decided",
Ingested::NoWinner => "NoWinner",
Ingested::ReloadInProgress => "ReloadInProgress",
Ingested::Rejected(_) => "Rejected",
}
}
#[test]
fn vector_cosine_scorer_picks_most_aligned_action() {
let mut b = SchemaBuilder::new();
let loc_kid = b.kind("loc", &[("embed", AttrType::F32Arr)]);
let trig_kid = b.kind("k", &[]);
let schema = b.build();
let embed_aid = schema.attr_names.get("embed").unwrap();
let engine: Engine<()> = Engine::builder()
.schema(schema)
.with_embedding_slot("loc", "embed")
.build()
.unwrap();
engine
.upsert_location(&LocationDef {
id: LocId(1),
kinds_allowed: vec![loc_kid, trig_kid],
ref_attrs: vec![],
})
.unwrap();
let embedding = [1.0_f32, 0.0, 0.0];
let mut attrs = AttrSet::new();
attrs.push(embed_aid, Value::F32Arr(&embedding));
let _ = engine.ingest_update(StateUpdate {
location: LocId(1),
kind: KindRef::Id(loc_kid),
attrs,
});
let _ = engine.ingest_action(ActionIngest {
location: LocId(1),
action_id: ActionId::from("10"),
start: 0,
end: i64::MAX,
priority: 0,
kind: KindRef::Id(trig_kid),
scorer: ScorerSpec::Vector {
target: &[0.0, 1.0, 0.0],
metric: VectorMetric::Cosine,
},
payload: (),
post: None,
});
let _ = engine.ingest_action(ActionIngest {
location: LocId(1),
action_id: ActionId::from("11"),
start: 0,
end: i64::MAX,
priority: 0,
kind: KindRef::Id(trig_kid),
scorer: ScorerSpec::Vector {
target: &[1.0, 0.0, 0.0],
metric: VectorMetric::Cosine,
},
payload: (),
post: None,
});
let t = Trigger {
location: LocId(1),
kind: KindRef::Id(trig_kid),
attrs: AttrSet::new(),
};
match engine.ingest_trigger(t) {
Ingested::Decided(d) => assert_eq!(d.action_id, ActionId::from("11")),
other => panic!("expected Decided(11), got {}", ingested_debug(&other)),
}
}
#[test]
fn vector_scorer_rejected_when_target_exceeds_max_embedding_dim() {
use kitt_score::schema::attr::MAX_EMBEDDING_DIM;
let mut b = SchemaBuilder::new();
let loc_kid = b.kind("loc", &[("embed", AttrType::F32Arr)]);
let trig_kid = b.kind("k", &[]);
let schema = b.build();
let engine: Engine<()> = Engine::builder()
.schema(schema)
.with_embedding_slot("loc", "embed")
.build()
.unwrap();
engine
.upsert_location(&LocationDef {
id: LocId(1),
kinds_allowed: vec![loc_kid, trig_kid],
ref_attrs: vec![],
})
.unwrap();
let oversized: Vec<f32> = vec![0.0; MAX_EMBEDDING_DIM + 1];
let result = engine.ingest_action(ActionIngest {
location: LocId(1),
action_id: ActionId::from("1"),
start: 0,
end: i64::MAX,
priority: 0,
kind: KindRef::Id(trig_kid),
scorer: ScorerSpec::Vector {
target: &oversized,
metric: VectorMetric::Dot,
},
payload: (),
post: None,
});
assert!(
matches!(result, Ingested::Rejected(_)),
"expected Rejected for oversized target, got {}",
ingested_debug(&result)
);
}
#[test]
fn vector_scorer_rejected_without_embedding_slot() {
let mut b = SchemaBuilder::new();
let _ = b.kind("k", &[]);
let schema = b.build();
let engine: Engine<()> = Engine::builder().schema(schema).build().unwrap();
engine
.upsert_location(&LocationDef {
id: LocId(1),
kinds_allowed: vec![],
ref_attrs: vec![],
})
.unwrap();
let result = engine.ingest_action(ActionIngest {
location: LocId(1),
action_id: ActionId::from("1"),
start: 0,
end: i64::MAX,
priority: 0,
kind: KindRef::Name("k"),
scorer: ScorerSpec::Vector {
target: &[1.0, 0.0],
metric: VectorMetric::Dot,
},
payload: (),
post: None,
});
assert!(
matches!(result, Ingested::Rejected(_)),
"expected Rejected, got {}",
ingested_debug(&result)
);
}