use crate::error::Result;
use crate::intent::{self, IntentConfig, IntentInferenceResult, ScoredIntent};
use crate::state::{CognitiveEdge, CognitiveEdgeKind, CognitiveNode, NodeKind};
use super::{now, YantrikDB};
impl YantrikDB {
pub fn infer_intents(&self, config: &IntentConfig) -> Result<IntentInferenceResult> {
let ts = now();
let mut all_nodes: Vec<CognitiveNode> = Vec::new();
for kind in &[
NodeKind::Goal,
NodeKind::Routine,
NodeKind::Need,
NodeKind::Episode,
NodeKind::Opportunity,
] {
all_nodes.extend(self.load_cognitive_nodes_by_kind(*kind)?);
}
let edges = self.load_intent_relevant_edges()?;
let refs: Vec<&CognitiveNode> = all_nodes.iter().collect();
let result = intent::infer_intents(&refs, &edges, ts, config);
Ok(result)
}
pub fn infer_and_materialize_intents(
&self,
config: &IntentConfig,
) -> Result<(IntentInferenceResult, Vec<CognitiveNode>)> {
let result = self.infer_intents(config)?;
let mut alloc = self.load_node_id_allocator()?;
let mut materialized = Vec::new();
for hypothesis in &result.hypotheses {
let node = intent::intent_to_node(hypothesis, &mut alloc);
self.persist_cognitive_node(&node)?;
materialized.push(node);
}
self.persist_node_id_allocator(&alloc)?;
Ok((result, materialized))
}
pub fn infer_intents_default(&self) -> Result<IntentInferenceResult> {
self.infer_intents(&IntentConfig::default())
}
pub fn top_intent(&self, config: &IntentConfig) -> Result<Option<ScoredIntent>> {
let result = self.infer_intents(config)?;
Ok(result.hypotheses.into_iter().next())
}
fn load_intent_relevant_edges(&self) -> Result<Vec<CognitiveEdge>> {
let relevant_kinds = [
CognitiveEdgeKind::AdvancesGoal,
CognitiveEdgeKind::BlocksGoal,
CognitiveEdgeKind::Triggers,
CognitiveEdgeKind::Supports,
CognitiveEdgeKind::Contradicts,
CognitiveEdgeKind::AssociatedWith,
];
let mut all_edges = Vec::new();
for kind in &relevant_kinds {
all_edges.extend(self.load_cognitive_edges_by_kind(*kind)?);
}
Ok(all_edges)
}
}
#[cfg(test)]
mod tests {
use crate::engine::YantrikDB;
use crate::intent::IntentConfig;
use crate::state::*;
fn test_db() -> YantrikDB {
YantrikDB::new(":memory:", 8).unwrap()
}
fn make_goal_node(alloc: &mut NodeIdAllocator, desc: &str, urgency: f64) -> CognitiveNode {
let id = alloc.alloc(NodeKind::Goal);
let mut node = CognitiveNode::new(
id,
desc.to_string(),
NodePayload::Goal(GoalPayload {
description: desc.to_string(),
status: GoalStatus::Active,
progress: 0.4,
deadline: None,
priority: Priority::High,
parent_goal: None,
completion_criteria: "Done".to_string(),
}),
);
node.attrs.urgency = urgency;
node
}
fn make_need_node(alloc: &mut NodeIdAllocator, desc: &str, intensity: f64) -> CognitiveNode {
let id = alloc.alloc(NodeKind::Need);
CognitiveNode::new(
id,
desc.to_string(),
NodePayload::Need(NeedPayload {
description: desc.to_string(),
category: NeedCategory::Professional,
intensity,
last_satisfied: None,
satisfaction_pattern: "Do it".to_string(),
}),
)
}
#[test]
fn test_infer_intents_empty_graph() {
let db = test_db();
let config = IntentConfig::default();
let result = db.infer_intents(&config).unwrap();
assert_eq!(result.total_generated, 0);
assert!(result.hypotheses.is_empty());
}
#[test]
fn test_infer_intents_with_goals() {
let db = test_db();
let mut alloc = NodeIdAllocator::new();
let goal = make_goal_node(&mut alloc, "Finish quarterly report", 0.8);
db.persist_cognitive_node(&goal).unwrap();
db.persist_node_id_allocator(&alloc).unwrap();
let config = IntentConfig::default();
let result = db.infer_intents(&config).unwrap();
assert!(result.total_generated >= 1);
assert!(result
.hypotheses
.iter()
.any(|h| h.description.contains("quarterly report")));
}
#[test]
fn test_infer_and_materialize() {
let db = test_db();
let mut alloc = NodeIdAllocator::new();
let goal = make_goal_node(&mut alloc, "Learn Rust", 0.7);
let need = make_need_node(&mut alloc, "Practice coding", 0.6);
db.persist_cognitive_node(&goal).unwrap();
db.persist_cognitive_node(&need).unwrap();
db.persist_node_id_allocator(&alloc).unwrap();
let config = IntentConfig::default();
let (result, materialized) = db.infer_and_materialize_intents(&config).unwrap();
assert!(!result.hypotheses.is_empty());
assert_eq!(materialized.len(), result.hypotheses.len());
for node in &materialized {
assert_eq!(node.id.kind(), NodeKind::IntentHypothesis);
assert!(matches!(node.payload, NodePayload::IntentHypothesis(_)));
}
}
#[test]
fn test_top_intent() {
let db = test_db();
let mut alloc = NodeIdAllocator::new();
let goal = make_goal_node(&mut alloc, "Ship feature", 0.9);
db.persist_cognitive_node(&goal).unwrap();
db.persist_node_id_allocator(&alloc).unwrap();
let config = IntentConfig::default();
let top = db.top_intent(&config).unwrap();
assert!(top.is_some());
assert!(top.unwrap().description.contains("Ship feature"));
}
#[test]
fn test_infer_intents_default() {
let db = test_db();
let result = db.infer_intents_default().unwrap();
assert_eq!(result.total_generated, 0);
}
}