/// Inference engine /// Converts technology detections into infrastructure requirements /// Uses rule-based system to suggest taskservs based on what was detected use crate::models::{Detection, Requirement, Technology}; /// Inference rule: detect pattern → infer requirement pub struct InferenceRule { pub name: &'static str, pub condition: fn(&[Detection]) -> bool, pub infer: fn(&[Detection]) -> Vec, } /// Main inference engine pub struct InferenceEngine { rules: Vec, } impl InferenceEngine { pub fn new(rules: Vec) -> Self { Self { rules } } /// Create engine with default rules pub fn with_default_rules() -> Self { let rules = vec![ // Rule 1: Node.js + Express → Redis (API caching) InferenceRule { name: "NodeJS API recommends Redis", condition: |detections| { detections .iter() .any(|d| d.technology == Technology::NodeJs) && detections .iter() .any(|d| d.technology == Technology::Express) }, infer: |_| { vec![Requirement::new( "redis", "Express.js APIs benefit from caching layer", 0.85, false, )] }, }, // Rule 2: Any database detected → needs backups InferenceRule { name: "Databases need backup strategy", condition: |detections| { detections.iter().any(|d| { matches!( d.technology, Technology::Postgres | Technology::Mysql | Technology::Mongodb ) }) }, infer: |detections| { detections .iter() .filter(|d| { matches!( d.technology, Technology::Postgres | Technology::Mysql | Technology::Mongodb ) }) .map(|d| { let taskserv = match d.technology { Technology::Postgres => "postgres-backup", Technology::Mysql => "mysql-backup", Technology::Mongodb => "mongodb-backup", _ => "backup", }; Requirement::new( taskserv, "Production databases require backup strategy", 0.90, false, ) }) .collect() }, }, // Rule 3: Containerized apps need reverse proxy InferenceRule { name: "Docker apps need reverse proxy", condition: |detections| { detections .iter() .any(|d| d.technology == Technology::Docker) }, infer: |_| { vec![Requirement::new( "nginx", "Containerized applications should run behind reverse proxy", 0.75, false, )] }, }, // Rule 4: Any language detected → needs runtime InferenceRule { name: "Languages need runtime", condition: |detections| { detections.iter().any(|d| { matches!( d.technology, Technology::NodeJs | Technology::Python | Technology::Rust ) }) }, infer: |detections| { let lang = detections.iter().find(|d| { matches!( d.technology, Technology::NodeJs | Technology::Python | Technology::Rust ) }); match lang.map(|d| d.technology) { Some(Technology::NodeJs) => vec![], // Already detected Some(Technology::Python) => vec![], // Already detected Some(Technology::Rust) => vec![], // Already detected _ => vec![], } }, }, // Rule 5: PostgreSQL detected → add monitoring InferenceRule { name: "PostgreSQL should have monitoring", condition: |detections| { detections .iter() .any(|d| d.technology == Technology::Postgres) }, infer: |_| { vec![Requirement::new( "pg-monitoring", "Monitor PostgreSQL performance in production", 0.70, false, ) .with_min_version("14.0".to_string())] }, }, ]; Self::new(rules) } /// Infer requirements from detections pub fn infer_requirements(&self, detections: &[Detection]) -> Vec { let mut requirements = Vec::new(); for rule in &self.rules { if (rule.condition)(detections) { let inferred = (rule.infer)(detections); requirements.extend(inferred); } } // Deduplicate and keep highest confidence requirements.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap()); requirements.dedup_by(|a, b| a.taskserv == b.taskserv); requirements } } #[cfg(test)] mod tests { use super::*; #[test] fn test_nodejs_express_recommends_redis() { let detections = vec![ Detection::new(Technology::NodeJs), Detection::new(Technology::Express), ]; let engine = InferenceEngine::with_default_rules(); let requirements = engine.infer_requirements(&detections); assert!(requirements.iter().any(|r| r.taskserv == "redis")); } #[test] fn test_docker_recommends_nginx() { let detections = vec![Detection::new(Technology::Docker)]; let engine = InferenceEngine::with_default_rules(); let requirements = engine.infer_requirements(&detections); assert!(requirements.iter().any(|r| r.taskserv == "nginx")); } #[test] fn test_postgres_detected() { let detections = vec![Detection::new(Technology::Postgres)]; let engine = InferenceEngine::with_default_rules(); let requirements = engine.infer_requirements(&detections); assert!(!requirements.is_empty()); } }