1use std::collections::HashMap;
14
15use crate::{SimilaritySearch, SymbolTable};
16
17pub struct AutoCompleter {
22 similarity_search: SimilaritySearch,
24 patterns: PatternDatabase,
26 max_suggestions: usize,
28}
29
30impl AutoCompleter {
31 pub fn new() -> Self {
33 Self {
34 similarity_search: SimilaritySearch::new(),
35 patterns: PatternDatabase::default(),
36 max_suggestions: 5,
37 }
38 }
39
40 pub fn with_max_suggestions(mut self, max: usize) -> Self {
42 self.max_suggestions = max;
43 self
44 }
45
46 pub fn index_table(&mut self, table: &SymbolTable) {
48 self.similarity_search.index_table(table);
49 }
50
51 pub fn suggest_domain_names(&self, partial: &str) -> Vec<DomainSuggestion> {
53 let mut suggestions = Vec::new();
54
55 let pattern_suggestions = self.patterns.suggest_domain_names(partial);
57 for (name, confidence) in pattern_suggestions.into_iter().take(self.max_suggestions) {
58 suggestions.push(DomainSuggestion {
59 name,
60 estimated_cardinality: 100, description: None,
62 confidence,
63 source: SuggestionSource::Pattern,
64 });
65 }
66
67 suggestions
68 }
69
70 pub fn suggest_predicates(
72 &self,
73 domains: &[String],
74 partial: &str,
75 ) -> Vec<PredicateSuggestion> {
76 let mut suggestions = Vec::new();
77
78 let pattern_suggestions = self.patterns.suggest_predicates(domains, partial);
80
81 for (name, arg_domains, confidence) in
82 pattern_suggestions.into_iter().take(self.max_suggestions)
83 {
84 suggestions.push(PredicateSuggestion {
85 name,
86 arg_domains,
87 description: None,
88 confidence,
89 source: SuggestionSource::Pattern,
90 });
91 }
92
93 suggestions
94 }
95
96 pub fn suggest_variable_names(&self, domain: &str, partial: &str) -> Vec<VariableSuggestion> {
98 let mut suggestions = Vec::new();
99
100 let pattern_suggestions = self.patterns.suggest_variable_names(domain, partial);
102
103 for (name, confidence) in pattern_suggestions.into_iter().take(self.max_suggestions) {
104 suggestions.push(VariableSuggestion {
105 name,
106 domain: domain.to_string(),
107 confidence,
108 source: SuggestionSource::Pattern,
109 });
110 }
111
112 suggestions
113 }
114
115 pub fn suggest_domain_for_predicate_arg(
120 &self,
121 predicate_name: &str,
122 existing_args: &[String],
123 _position: usize,
124 ) -> Vec<DomainSuggestion> {
125 let mut suggestions = Vec::new();
126
127 let pattern_suggestions = self
129 .patterns
130 .suggest_domain_for_predicate(predicate_name, existing_args);
131
132 for (name, confidence) in pattern_suggestions.into_iter().take(self.max_suggestions) {
133 suggestions.push(DomainSuggestion {
134 name,
135 estimated_cardinality: 100,
136 description: None,
137 confidence,
138 source: SuggestionSource::Pattern,
139 });
140 }
141
142 suggestions
143 }
144
145 pub fn stats(&self) -> AutoCompleterStats {
147 AutoCompleterStats {
148 num_indexed_domains: self.similarity_search.stats().num_domains,
149 num_indexed_predicates: self.similarity_search.stats().num_predicates,
150 num_patterns: self.patterns.num_patterns(),
151 }
152 }
153}
154
155impl Default for AutoCompleter {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161#[derive(Clone, Debug)]
163pub struct DomainSuggestion {
164 pub name: String,
166 pub estimated_cardinality: usize,
168 pub description: Option<String>,
170 pub confidence: f64,
172 pub source: SuggestionSource,
174}
175
176#[derive(Clone, Debug)]
178pub struct PredicateSuggestion {
179 pub name: String,
181 pub arg_domains: Vec<String>,
183 pub description: Option<String>,
185 pub confidence: f64,
187 pub source: SuggestionSource,
189}
190
191#[derive(Clone, Debug)]
193pub struct VariableSuggestion {
194 pub name: String,
196 pub domain: String,
198 pub confidence: f64,
200 pub source: SuggestionSource,
202}
203
204#[derive(Clone, Copy, Debug, PartialEq, Eq)]
206pub enum SuggestionSource {
207 Pattern,
209 Similarity,
211 Learned,
213 Template,
215}
216
217#[derive(Clone, Debug)]
219pub struct AutoCompleterStats {
220 pub num_indexed_domains: usize,
222 pub num_indexed_predicates: usize,
224 pub num_patterns: usize,
226}
227
228struct PatternDatabase {
233 common_domains: HashMap<String, Vec<(String, usize)>>,
235 common_predicates: HashMap<String, Vec<(String, Vec<String>)>>,
237 variable_patterns: HashMap<String, Vec<String>>,
239}
240
241impl Default for PatternDatabase {
242 fn default() -> Self {
243 let mut db = Self {
244 common_domains: HashMap::new(),
245 common_predicates: HashMap::new(),
246 variable_patterns: HashMap::new(),
247 };
248
249 db.init_common_domains();
250 db.init_common_predicates();
251 db.init_variable_patterns();
252
253 db
254 }
255}
256
257impl PatternDatabase {
258 fn init_common_domains(&mut self) {
260 self.add_domain_pattern(
262 "person",
263 vec![("Person", 1000), ("User", 1000), ("Agent", 500)],
264 );
265 self.add_domain_pattern(
266 "user",
267 vec![("User", 1000), ("Person", 1000), ("Account", 500)],
268 );
269 self.add_domain_pattern(
270 "student",
271 vec![("Student", 500), ("Person", 1000), ("User", 1000)],
272 );
273 self.add_domain_pattern(
274 "teacher",
275 vec![("Teacher", 200), ("Instructor", 200), ("Person", 1000)],
276 );
277
278 self.add_domain_pattern(
280 "course",
281 vec![("Course", 100), ("Class", 100), ("Subject", 50)],
282 );
283 self.add_domain_pattern("class", vec![("Class", 100), ("Course", 100)]);
284
285 self.add_domain_pattern(
287 "company",
288 vec![("Company", 500), ("Organization", 500), ("Business", 500)],
289 );
290 self.add_domain_pattern(
291 "department",
292 vec![("Department", 50), ("Division", 50), ("Unit", 50)],
293 );
294
295 self.add_domain_pattern("book", vec![("Book", 5000), ("Publication", 10000)]);
297 self.add_domain_pattern("product", vec![("Product", 10000), ("Item", 10000)]);
298 self.add_domain_pattern("resource", vec![("Resource", 1000), ("Asset", 1000)]);
299
300 self.add_domain_pattern("time", vec![("Time", 86400), ("Timestamp", 86400)]);
302 self.add_domain_pattern("date", vec![("Date", 365), ("Day", 365)]);
303
304 self.add_domain_pattern("location", vec![("Location", 1000), ("Place", 1000)]);
306 self.add_domain_pattern("city", vec![("City", 1000), ("Location", 1000)]);
307 self.add_domain_pattern("country", vec![("Country", 200), ("Nation", 200)]);
308 }
309
310 fn init_common_predicates(&mut self) {
312 self.add_predicate_pattern(
314 "person",
315 vec![
316 ("knows", vec!["Person", "Person"]),
317 ("likes", vec!["Person", "Person"]),
318 ("works_with", vec!["Person", "Person"]),
319 ("manages", vec!["Person", "Person"]),
320 ],
321 );
322
323 self.add_predicate_pattern(
324 "student",
325 vec![
326 ("enrolled_in", vec!["Student", "Course"]),
327 ("takes", vec!["Student", "Course"]),
328 ("attends", vec!["Student", "Course"]),
329 ],
330 );
331
332 self.add_predicate_pattern(
333 "teach",
334 vec![
335 ("teaches", vec!["Teacher", "Course"]),
336 ("instructs", vec!["Teacher", "Student"]),
337 ],
338 );
339
340 self.add_predicate_pattern(
342 "is",
343 vec![
344 ("is_active", vec!["User"]),
345 ("is_admin", vec!["User"]),
346 ("is_public", vec!["Resource"]),
347 ],
348 );
349 }
350
351 fn init_variable_patterns(&mut self) {
353 self.add_variable_pattern("Person", vec!["p", "person", "x", "user"]);
354 self.add_variable_pattern("Student", vec!["s", "student", "x"]);
355 self.add_variable_pattern("Teacher", vec!["t", "teacher", "instructor"]);
356 self.add_variable_pattern("Course", vec!["c", "course", "class"]);
357 self.add_variable_pattern("Book", vec!["b", "book"]);
358 self.add_variable_pattern("Time", vec!["t", "time", "timestamp"]);
359 self.add_variable_pattern("Date", vec!["d", "date", "day"]);
360 self.add_variable_pattern("Location", vec!["l", "loc", "location", "place"]);
361 }
362
363 fn add_domain_pattern(&mut self, key: &str, patterns: Vec<(&str, usize)>) {
364 self.common_domains.insert(
365 key.to_string(),
366 patterns
367 .into_iter()
368 .map(|(name, card)| (name.to_string(), card))
369 .collect(),
370 );
371 }
372
373 fn add_predicate_pattern(&mut self, key: &str, patterns: Vec<(&str, Vec<&str>)>) {
374 self.common_predicates.insert(
375 key.to_string(),
376 patterns
377 .into_iter()
378 .map(|(name, args)| {
379 (
380 name.to_string(),
381 args.into_iter().map(|s| s.to_string()).collect(),
382 )
383 })
384 .collect(),
385 );
386 }
387
388 fn add_variable_pattern(&mut self, key: &str, patterns: Vec<&str>) {
389 self.variable_patterns.insert(
390 key.to_string(),
391 patterns.into_iter().map(|s| s.to_string()).collect(),
392 );
393 }
394
395 fn suggest_domain_names(&self, partial: &str) -> Vec<(String, f64)> {
396 let mut suggestions = Vec::new();
397 let partial_lower = partial.to_lowercase();
398
399 for (key, patterns) in &self.common_domains {
400 if key.contains(&partial_lower) {
401 for (name, _card) in patterns {
402 if name.to_lowercase().starts_with(&partial_lower) {
403 let confidence = 0.9;
404 suggestions.push((name.clone(), confidence));
405 }
406 }
407 }
408 }
409
410 suggestions.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
411 suggestions
412 }
413
414 fn suggest_predicates(
415 &self,
416 domains: &[String],
417 partial: &str,
418 ) -> Vec<(String, Vec<String>, f64)> {
419 let mut suggestions = Vec::new();
420 let partial_lower = partial.to_lowercase();
421
422 for domain in domains {
424 let domain_lower = domain.to_lowercase();
425 if let Some(patterns) = self.common_predicates.get(&domain_lower) {
426 for (name, args) in patterns {
427 if name.to_lowercase().starts_with(&partial_lower) {
428 let confidence = 0.85;
429 suggestions.push((name.clone(), args.clone(), confidence));
430 }
431 }
432 }
433 }
434
435 suggestions.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());
436 suggestions
437 }
438
439 fn suggest_variable_names(&self, domain: &str, partial: &str) -> Vec<(String, f64)> {
440 let mut suggestions = Vec::new();
441 let partial_lower = partial.to_lowercase();
442
443 if let Some(patterns) = self.variable_patterns.get(domain) {
444 for name in patterns {
445 if name.starts_with(&partial_lower) {
446 let confidence = 0.9;
447 suggestions.push((name.clone(), confidence));
448 }
449 }
450 }
451
452 if suggestions.is_empty() {
454 let first_char = domain
455 .chars()
456 .next()
457 .unwrap_or('x')
458 .to_lowercase()
459 .to_string();
460 suggestions.push((first_char, 0.5));
461 suggestions.push((domain.to_lowercase(), 0.6));
462 }
463
464 suggestions.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
465 suggestions
466 }
467
468 fn suggest_domain_for_predicate(
469 &self,
470 predicate_name: &str,
471 existing_args: &[String],
472 ) -> Vec<(String, f64)> {
473 let mut suggestions = Vec::new();
474
475 for patterns in self.common_predicates.values() {
477 for (name, args) in patterns {
478 if name == predicate_name && args.len() > existing_args.len() {
479 let matches = existing_args.iter().zip(args.iter()).all(|(a, b)| a == b);
481
482 if matches {
483 if let Some(next_domain) = args.get(existing_args.len()) {
485 let confidence = 0.8;
486 suggestions.push((next_domain.clone(), confidence));
487 }
488 }
489 }
490 }
491 }
492
493 suggestions.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
494 suggestions
495 }
496
497 fn num_patterns(&self) -> usize {
498 self.common_domains.len() + self.common_predicates.len() + self.variable_patterns.len()
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use super::*;
505 use crate::DomainInfo;
506
507 #[test]
508 fn test_autocompleter_creation() {
509 let ac = AutoCompleter::new();
510 let stats = ac.stats();
511 assert!(stats.num_patterns > 0);
512 }
513
514 #[test]
515 fn test_suggest_domain_names() {
516 let ac = AutoCompleter::new();
517 let suggestions = ac.suggest_domain_names("per");
518
519 assert!(!suggestions.is_empty());
520 assert!(suggestions.iter().any(|s| s.name == "Person"));
522 }
523
524 #[test]
525 fn test_suggest_predicates() {
526 let ac = AutoCompleter::new();
527 let suggestions = ac.suggest_predicates(&["Person".to_string()], "know");
528
529 assert!(!suggestions.is_empty());
530 assert!(suggestions.iter().any(|s| s.name == "knows"));
532 }
533
534 #[test]
535 fn test_suggest_variable_names() {
536 let ac = AutoCompleter::new();
537 let suggestions = ac.suggest_variable_names("Person", "p");
538
539 assert!(!suggestions.is_empty());
540 assert!(suggestions
542 .iter()
543 .any(|s| s.name == "p" || s.name == "person"));
544 }
545
546 #[test]
547 fn test_suggest_domain_for_predicate() {
548 let ac = AutoCompleter::new();
549 let suggestions =
550 ac.suggest_domain_for_predicate_arg("teaches", &["Teacher".to_string()], 1);
551
552 assert!(!suggestions.is_empty());
553 assert!(suggestions.iter().any(|s| s.name == "Course"));
555 }
556
557 #[test]
558 fn test_max_suggestions_limit() {
559 let ac = AutoCompleter::new().with_max_suggestions(3);
560 let suggestions = ac.suggest_domain_names("p");
561
562 assert!(suggestions.len() <= 3);
563 }
564
565 #[test]
566 fn test_index_table() {
567 let mut ac = AutoCompleter::new();
568 let mut table = SymbolTable::new();
569 table
570 .add_domain(DomainInfo::new("CustomDomain", 100))
571 .unwrap();
572
573 ac.index_table(&table);
574
575 let stats = ac.stats();
576 assert_eq!(stats.num_indexed_domains, 1);
577 }
578
579 #[test]
580 fn test_suggestion_confidence() {
581 let ac = AutoCompleter::new();
582 let suggestions = ac.suggest_domain_names("person");
583
584 for suggestion in &suggestions {
585 assert!(suggestion.confidence >= 0.0 && suggestion.confidence <= 1.0);
586 }
587 }
588
589 #[test]
590 fn test_empty_partial() {
591 let ac = AutoCompleter::new();
592 let suggestions = ac.suggest_domain_names("");
593
594 assert!(!suggestions.is_empty());
596 }
597
598 #[test]
599 fn test_case_insensitive_matching() {
600 let ac = AutoCompleter::new();
601 let suggestions_lower = ac.suggest_domain_names("person");
602 let suggestions_upper = ac.suggest_domain_names("PERSON");
603
604 assert!(!suggestions_lower.is_empty());
606 assert!(!suggestions_upper.is_empty());
607 }
608
609 #[test]
610 fn test_pattern_database_initialization() {
611 let db = PatternDatabase::default();
612 assert!(db.num_patterns() > 0);
613 assert!(!db.common_domains.is_empty());
614 assert!(!db.common_predicates.is_empty());
615 assert!(!db.variable_patterns.is_empty());
616 }
617
618 #[test]
619 fn test_multiple_domain_contexts() {
620 let ac = AutoCompleter::new();
621 let suggestions =
622 ac.suggest_predicates(&["Student".to_string(), "Course".to_string()], "enroll");
623
624 assert!(!suggestions.is_empty());
625 }
626}