1use super::{FocusArea, RelevanceScore, TaskContext};
8use crate::scanner::{FileCategory, FileNode};
9use std::collections::HashMap;
10
11pub struct RelevanceEngine {
13 patterns: HashMap<String, f32>,
15 type_weights: HashMap<FileCategory, f32>,
17}
18
19impl RelevanceEngine {
20 pub fn new() -> Self {
22 let mut engine = Self {
23 patterns: HashMap::new(),
24 type_weights: HashMap::new(),
25 };
26
27 engine.initialize_patterns();
28 engine.initialize_type_weights();
29 engine
30 }
31
32 pub fn score_advanced_relevance(
34 &self,
35 file_node: &FileNode,
36 context: &TaskContext,
37 project_context: Option<&ProjectContext>,
38 ) -> RelevanceScore {
39 let mut score = 0.0;
40 let mut reasons = Vec::new();
41 let mut focus_matches = Vec::new();
42
43 if let Some(weight) = self.type_weights.get(&file_node.category) {
45 score += weight;
46 reasons.push(format!("File category {:?} base score", file_node.category));
47 }
48
49 let file_path = file_node.path.to_string_lossy().to_lowercase();
51 for (pattern, pattern_score) in &self.patterns {
52 if file_path.contains(pattern) {
53 score += pattern_score;
54 reasons.push(format!("Matches pattern '{}'", pattern));
55 }
56 }
57
58 for focus_area in &context.focus_areas {
60 let focus_score = self.calculate_focus_score(file_node, focus_area);
61 if focus_score > 0.0 {
62 score += focus_score;
63 focus_matches.push(focus_area.clone());
64 reasons.push(format!("Relevant to {:?}", focus_area));
65 }
66 }
67
68 if let Some(proj_ctx) = project_context {
70 score += self.calculate_project_context_boost(file_node, proj_ctx);
71 }
72
73 score = score.min(1.0);
75
76 RelevanceScore {
77 score,
78 reasons,
79 focus_matches,
80 }
81 }
82
83 fn calculate_focus_score(&self, file_node: &FileNode, focus_area: &FocusArea) -> f32 {
85 let file_name = file_node
86 .path
87 .file_name()
88 .and_then(|name| name.to_str())
89 .unwrap_or("")
90 .to_lowercase();
91 let file_path = file_node.path.to_string_lossy().to_lowercase();
92
93 let keywords = focus_area.keywords();
94 let mut score = 0.0;
95
96 for keyword in keywords {
97 if file_name.contains(keyword) {
98 score += 0.4; } else if file_path.contains(keyword) {
100 score += 0.2; }
102 }
103
104 score
105 }
106
107 fn calculate_project_context_boost(
109 &self,
110 file_node: &FileNode,
111 context: &ProjectContext,
112 ) -> f32 {
113 let mut boost = 0.0;
114
115 if context.recent_files.contains(&file_node.path) {
117 boost += 0.3;
118 }
119
120 if context.core_files.contains(&file_node.path) {
122 boost += 0.4;
123 }
124
125 boost
126 }
127
128 fn initialize_patterns(&mut self) {
130 self.patterns.insert("auth".to_string(), 0.3);
132 self.patterns.insert("login".to_string(), 0.3);
133 self.patterns.insert("session".to_string(), 0.2);
134
135 self.patterns.insert("api".to_string(), 0.3);
137 self.patterns.insert("endpoint".to_string(), 0.3);
138 self.patterns.insert("handler".to_string(), 0.2);
139
140 self.patterns.insert("config".to_string(), 0.2);
142 self.patterns.insert("env".to_string(), 0.2);
143 self.patterns.insert("settings".to_string(), 0.2);
144
145 self.patterns.insert("test".to_string(), 0.2);
147 self.patterns.insert("spec".to_string(), 0.2);
148
149 self.patterns.insert("readme".to_string(), 0.2);
151 self.patterns.insert("doc".to_string(), 0.1);
152 }
153
154 fn initialize_type_weights(&mut self) {
156 self.type_weights.insert(FileCategory::Rust, 0.8);
157 self.type_weights.insert(FileCategory::Python, 0.8);
158 self.type_weights.insert(FileCategory::JavaScript, 0.7);
159 self.type_weights.insert(FileCategory::TypeScript, 0.7);
160 self.type_weights.insert(FileCategory::Json, 0.5);
161 self.type_weights.insert(FileCategory::Yaml, 0.5);
162 self.type_weights.insert(FileCategory::Markdown, 0.4);
163 self.type_weights.insert(FileCategory::Unknown, 0.3);
164 }
165}
166
167#[derive(Debug, Clone)]
169pub struct ProjectContext {
170 pub recent_files: Vec<std::path::PathBuf>,
172 pub core_files: Vec<std::path::PathBuf>,
174 pub project_type: ProjectType,
176}
177
178#[derive(Debug, Clone, PartialEq)]
180pub enum ProjectType {
181 Rust,
182 Node,
183 Python,
184 Go,
185 Java,
186 Mixed,
187 Unknown,
188}
189
190impl Default for RelevanceEngine {
191 fn default() -> Self {
192 Self::new()
193 }
194}