1use crate::core_traits::Vocabulary;
2
3const ELEMENT_KIND_MAP: &[(&str, &[&str])] = &[
4 (
5 "requirement",
6 &["requirement_definition", "requirement_usage"],
7 ),
8 ("part", &["part_definition", "part_usage"]),
9 ("port", &["port_definition", "port_usage"]),
10 ("connection", &["connection_definition", "connection_usage"]),
11 ("interface", &["connection_definition", "connection_usage"]),
12 ("constraint", &["constraint_definition", "constraint_usage"]),
13 ("action", &["action_definition", "action_usage"]),
14 ("behavior", &["action_definition", "action_usage"]),
15 ("state", &["state_definition", "state_usage"]),
16 ("mode", &["state_definition", "state_usage"]),
17 ("attribute", &["attribute_definition", "attribute_usage"]),
18 ("property", &["attribute_definition", "attribute_usage"]),
19 ("use_case", &["use_case_definition", "use_case_usage"]),
20 (
21 "analysis",
22 &["analysis_case_definition", "analysis_case_usage"],
23 ),
24 ("view", &["view_definition", "view_usage"]),
25 ("viewpoint", &["viewpoint_definition"]),
26 ("concern", &["concern_definition", "concern_usage"]),
27 ("stakeholder", &["stakeholder_usage"]),
28 (
29 "enumeration",
30 &["enumeration_definition", "enumeration_usage"],
31 ),
32 ("enum", &["enumeration_definition", "enumeration_usage"]),
33 ("calc", &["calc_definition", "calc_usage"]),
34 ("calculation", &["calc_definition", "calc_usage"]),
35 ("item", &["item_definition", "item_usage"]),
36 ("metadata", &["metadata_definition", "metadata_usage"]),
37 ("annotation", &["metadata_definition", "metadata_usage"]),
38 ("flow", &["item_flow_usage"]),
39 ("allocation", &["part_usage"]),
40 ("package", &["package_definition", "library_package"]),
41];
42
43const SYSML_VOCABULARY: &[(&[&str], &[&str])] = &[
44 (
45 &["requirement", "req"],
46 &["requirement_definition", "requirement_usage"],
47 ),
48 (&["satisfy", "satisfaction"], &["Satisfy"]),
49 (&["verify", "verification"], &["Verify"]),
50 (&["allocate", "allocation"], &["Allocate"]),
51 (
52 &["connect", "connection", "interface"],
53 &["connection_definition", "connection_usage", "Connect"],
54 ),
55 (&["port"], &["port_definition", "port_usage"]),
56 (&["flow"], &["Flow"]),
57 (
58 &["constraint"],
59 &["constraint_definition", "constraint_usage"],
60 ),
61 (
62 &["action", "behavior"],
63 &["action_definition", "action_usage"],
64 ),
65 (&["state", "mode"], &["state_definition", "state_usage"]),
66 (&["part", "component"], &["part_definition", "part_usage"]),
67 (
68 &["attribute", "property"],
69 &["attribute_definition", "attribute_usage"],
70 ),
71 (&["import", "dependency"], &["Import", "Dependency"]),
72 (&["performance", "perform"], &["Perform"]),
73 (&["use case"], &["use_case_definition", "use_case_usage"]),
74 (
75 &["analysis"],
76 &["analysis_case_definition", "analysis_case_usage"],
77 ),
78 (
79 &["view", "viewpoint"],
80 &["view_definition", "view_usage", "viewpoint_definition"],
81 ),
82 (
83 &["concern", "stakeholder"],
84 &["concern_definition", "concern_usage", "stakeholder_usage"],
85 ),
86 (
87 &["enumeration", "enum"],
88 &["enumeration_definition", "enumeration_usage"],
89 ),
90 (&["calculation", "calc"], &["calc_definition", "calc_usage"]),
91 (&["item"], &["item_definition", "item_usage"]),
92 (
93 &["metadata", "annotation"],
94 &["metadata_definition", "metadata_usage"],
95 ),
96];
97
98pub(crate) const STRUCTURAL_RELATIONSHIP_KINDS: &[&str] = &["Import", "Member"];
99
100pub(crate) const RELATIONSHIP_KIND_NAMES: &[&str] = &[
101 "Satisfy",
102 "Verify",
103 "Import",
104 "Specialize",
105 "Allocate",
106 "Connect",
107 "Bind",
108 "Flow",
109 "Stream",
110 "Dependency",
111 "Redefine",
112 "Expose",
113 "Perform",
114 "Exhibit",
115 "Include",
116 "Succession",
117 "Transition",
118 "Send",
119 "Accept",
120 "Require",
121 "Assume",
122 "Assert",
123 "Assign",
124 "Subject",
125 "Render",
126 "Frame",
127 "Message",
128 "TypedBy",
129 "Member",
130];
131
132pub(crate) const ELEMENT_KIND_NAMES: &[&str] = &[
133 "requirement_definition",
134 "requirement_usage",
135 "part_definition",
136 "part_usage",
137 "port_definition",
138 "port_usage",
139 "connection_definition",
140 "connection_usage",
141 "constraint_definition",
142 "constraint_usage",
143 "action_definition",
144 "action_usage",
145 "state_definition",
146 "state_usage",
147 "attribute_definition",
148 "attribute_usage",
149 "use_case_definition",
150 "use_case_usage",
151 "analysis_case_definition",
152 "analysis_case_usage",
153 "view_definition",
154 "view_usage",
155 "viewpoint_definition",
156 "concern_definition",
157 "concern_usage",
158 "stakeholder_usage",
159 "enumeration_definition",
160 "enumeration_usage",
161 "calc_definition",
162 "calc_usage",
163 "item_definition",
164 "item_usage",
165 "metadata_definition",
166 "metadata_usage",
167 "item_flow_usage",
168 "interface_definition",
169 "interface_usage",
170 "verification_definition",
171 "verification_usage",
172 "analysis_definition",
173 "analysis_usage",
174 "occurrence_definition",
175 "actor_usage",
176 "objective_usage",
177 "event_occurrence_usage",
178 "exhibit_usage",
179 "end_usage",
180 "parameter_usage",
181 "generic_usage",
182 "feature_usage",
183 "timeslice_usage",
184 "snapshot_usage",
185 "package_definition",
186 "library_package",
187];
188
189const STOP_WORDS: &[&str] = &[
190 "a",
191 "an",
192 "the",
193 "and",
194 "or",
195 "for",
196 "to",
197 "of",
198 "in",
199 "is",
200 "it",
201 "its",
202 "are",
203 "was",
204 "were",
205 "be",
206 "been",
207 "do",
208 "does",
209 "did",
210 "has",
211 "have",
212 "had",
213 "with",
214 "that",
215 "this",
216 "what",
217 "how",
218 "which",
219 "where",
220 "when",
221 "who",
222 "why",
223 "find",
224 "show",
225 "get",
226 "list",
227 "describe",
228 "identify",
229 "determine",
230 "explain",
231 "i",
232 "me",
233 "my",
234 "we",
235 "us",
236 "you",
237 "your",
238 "use",
239 "using",
240 "from",
241 "by",
242 "not",
243 "no",
244 "any",
245 "all",
246 "each",
247 "if",
248 "then",
249 "than",
250 "but",
251 "so",
252 "as",
253 "on",
254 "at",
255 "about",
256 "into",
257 "can",
258 "should",
259 "would",
260 "could",
261 "will",
262];
263
264const STEM_SUFFIXES: &[&str] = &[
265 "ation", "ment", "ness", "tion", "sion", "ance", "ence", "ity", "ing", "ies", "ied", "ous",
266 "ive", "ful", "ion", "ed", "ly", "er", "es", "al", "s",
267];
268
269#[derive(Debug, Clone)]
270pub struct ExpandedQuery {
271 pub original: String,
272 pub tokens: Vec<String>,
273 pub element_kinds: Vec<String>,
274 pub relationship_kinds: Vec<String>,
275}
276
277fn naive_stem(word: &str) -> &str {
278 for suffix in STEM_SUFFIXES {
279 if let Some(stem) = word.strip_suffix(suffix) {
280 if stem.len() >= 3 {
281 return stem;
282 }
283 }
284 }
285 word
286}
287
288fn stem_match(token: &str, term: &str) -> bool {
289 let t_stem = naive_stem(token);
290 let v_stem = naive_stem(term);
291 t_stem == v_stem
292 || t_stem.starts_with(v_stem)
293 || v_stem.starts_with(t_stem)
294 || token.starts_with(term)
295 || term.starts_with(token)
296}
297
298pub fn expand_query(query: &str) -> ExpandedQuery {
299 let lower = query.to_lowercase();
300 let tokens: Vec<String> = lower
301 .split(|c: char| !c.is_alphanumeric() && c != '_')
302 .filter(|s| !s.is_empty() && !STOP_WORDS.contains(s))
303 .map(|s| s.to_string())
304 .collect();
305
306 let mut element_kinds = Vec::new();
307 let mut relationship_kinds = Vec::new();
308
309 for (terms, mappings) in SYSML_VOCABULARY {
310 let matched = terms.iter().any(|term| {
311 if term.contains(' ') {
312 lower.contains(term)
313 } else {
314 tokens.iter().any(|t| t == term || stem_match(t, term))
315 }
316 });
317
318 if matched {
319 for mapping in *mappings {
320 let is_rel = RELATIONSHIP_KIND_NAMES
321 .iter()
322 .any(|rk| rk.eq_ignore_ascii_case(mapping));
323 if is_rel {
324 if !relationship_kinds.contains(&mapping.to_string()) {
325 relationship_kinds.push(mapping.to_string());
326 }
327 } else if !element_kinds.contains(&mapping.to_string()) {
328 element_kinds.push(mapping.to_string());
329 }
330 }
331 }
332 }
333
334 ExpandedQuery {
335 original: query.to_string(),
336 tokens,
337 element_kinds,
338 relationship_kinds,
339 }
340}
341
342use crate::element::RflpLayer;
343
344pub fn classify_layer(kind: &str) -> Option<RflpLayer> {
345 match kind {
346 "requirement_definition"
347 | "requirement_usage"
348 | "concern_definition"
349 | "concern_usage"
350 | "stakeholder_usage"
351 | "constraint_definition"
352 | "constraint_usage"
353 | "verification_definition"
354 | "verification_usage"
355 | "objective_usage" => Some(RflpLayer::Requirements),
356 "use_case_definition"
357 | "use_case_usage"
358 | "action_definition"
359 | "action_usage"
360 | "state_definition"
361 | "state_usage"
362 | "calc_definition"
363 | "calc_usage"
364 | "analysis_case_definition"
365 | "analysis_case_usage"
366 | "analysis_definition"
367 | "analysis_usage"
368 | "item_flow_usage"
369 | "actor_usage"
370 | "event_occurrence_usage"
371 | "timeslice_usage"
372 | "snapshot_usage"
373 | "exhibit_usage" => Some(RflpLayer::Functional),
374 "part_definition"
375 | "part_usage"
376 | "port_definition"
377 | "port_usage"
378 | "connection_definition"
379 | "connection_usage"
380 | "interface_definition"
381 | "interface_usage"
382 | "attribute_definition"
383 | "attribute_usage"
384 | "item_definition"
385 | "item_usage"
386 | "occurrence_definition"
387 | "end_usage"
388 | "parameter_usage" => Some(RflpLayer::Logical),
389 _ => None,
390 }
391}
392
393pub struct SysmlVocabulary;
394
395impl Vocabulary for SysmlVocabulary {
396 fn expand_kind(&self, kind: &str) -> Vec<&str> {
397 let lower = kind.to_lowercase();
398 for (key, kinds) in ELEMENT_KIND_MAP {
399 if *key == lower.as_str() {
400 return kinds.to_vec();
401 }
402 }
403 vec![]
404 }
405
406 fn normalize_kind<'a>(&self, kind: &'a str) -> &'a str {
407 for (normalized, specifics) in ELEMENT_KIND_MAP {
408 if specifics.contains(&kind) {
409 return normalized;
410 }
411 }
412 kind
413 }
414
415 fn relationship_kinds(&self) -> &[&str] {
416 RELATIONSHIP_KIND_NAMES
417 }
418
419 fn element_kinds(&self) -> &[&str] {
420 ELEMENT_KIND_NAMES
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn test_expand_requirement_satisfy_query() {
430 let eq = expand_query("What requirements does the engine satisfy?");
431 assert!(eq
432 .element_kinds
433 .iter()
434 .any(|k| k == "requirement_definition"),);
435 assert!(eq.element_kinds.iter().any(|k| k == "requirement_usage"),);
436 assert!(eq
437 .relationship_kinds
438 .iter()
439 .any(|k| k.eq_ignore_ascii_case("Satisfy")),);
440 }
441
442 #[test]
443 fn test_expand_allocate_query() {
444 let eq = expand_query("How are functions allocated to components?");
445 assert!(eq
446 .relationship_kinds
447 .iter()
448 .any(|k| k.eq_ignore_ascii_case("Allocate")),);
449 assert!(eq.element_kinds.iter().any(|k| k == "part_definition"),);
450 }
451
452 #[test]
453 fn test_expand_multi_word_term() {
454 let eq = expand_query("Show me the use case diagram");
455 assert!(eq.element_kinds.iter().any(|k| k == "use_case_definition"),);
456 }
457
458 #[test]
459 fn test_expand_no_matches() {
460 let eq = expand_query("hello world");
461 assert!(eq.element_kinds.is_empty());
462 assert!(eq.relationship_kinds.is_empty());
463 }
464
465 #[test]
466 fn test_expand_preserves_tokens() {
467 let eq = expand_query("What requirements exist?");
468 assert!(!eq.tokens.contains(&"what".to_string()));
469 assert!(eq.tokens.contains(&"requirements".to_string()));
470 assert!(eq.tokens.contains(&"exist".to_string()));
471 }
472
473 #[test]
474 fn test_expand_deduplicates() {
475 let eq = expand_query("requirement req requirement");
476 let req_def_count = eq
477 .element_kinds
478 .iter()
479 .filter(|k| k.as_str() == "requirement_definition")
480 .count();
481 assert_eq!(req_def_count, 1);
482 }
483
484 #[test]
485 fn test_stop_words_filtered() {
486 let eq = expand_query("find the shield module for this system");
487 assert!(!eq.tokens.contains(&"find".to_string()));
488 assert!(!eq.tokens.contains(&"the".to_string()));
489 assert!(!eq.tokens.contains(&"for".to_string()));
490 assert!(!eq.tokens.contains(&"this".to_string()));
491 assert!(eq.tokens.contains(&"shield".to_string()));
492 assert!(eq.tokens.contains(&"module".to_string()));
493 assert!(eq.tokens.contains(&"system".to_string()));
494 }
495
496 #[test]
497 fn test_naive_stem() {
498 assert_eq!(naive_stem("requirements"), "requirement");
499 assert_eq!(naive_stem("satisfaction"), "satisfac");
500 assert_eq!(naive_stem("verification"), "verific");
501 assert_eq!(naive_stem("mining"), "min");
502 assert_eq!(naive_stem("ore"), "ore");
503 }
504
505 #[test]
506 fn test_stem_match_across_forms() {
507 assert!(stem_match("requirements", "requirement"));
508 assert!(stem_match("satisfies", "satisfy"));
509 assert!(stem_match("connections", "connect"));
510 }
511
512 #[test]
513 fn test_classify_layer_requirements() {
514 assert_eq!(
515 classify_layer("requirement_definition"),
516 Some(RflpLayer::Requirements)
517 );
518 assert_eq!(
519 classify_layer("requirement_usage"),
520 Some(RflpLayer::Requirements)
521 );
522 assert_eq!(
523 classify_layer("constraint_definition"),
524 Some(RflpLayer::Requirements)
525 );
526 assert_eq!(
527 classify_layer("concern_definition"),
528 Some(RflpLayer::Requirements)
529 );
530 assert_eq!(
531 classify_layer("stakeholder_usage"),
532 Some(RflpLayer::Requirements)
533 );
534 }
535
536 #[test]
537 fn test_classify_layer_functional() {
538 assert_eq!(
539 classify_layer("action_definition"),
540 Some(RflpLayer::Functional)
541 );
542 assert_eq!(classify_layer("action_usage"), Some(RflpLayer::Functional));
543 assert_eq!(
544 classify_layer("state_definition"),
545 Some(RflpLayer::Functional)
546 );
547 assert_eq!(
548 classify_layer("use_case_definition"),
549 Some(RflpLayer::Functional)
550 );
551 assert_eq!(
552 classify_layer("calc_definition"),
553 Some(RflpLayer::Functional)
554 );
555 assert_eq!(
556 classify_layer("item_flow_usage"),
557 Some(RflpLayer::Functional)
558 );
559 }
560
561 #[test]
562 fn test_classify_layer_logical() {
563 assert_eq!(classify_layer("part_definition"), Some(RflpLayer::Logical));
564 assert_eq!(classify_layer("part_usage"), Some(RflpLayer::Logical));
565 assert_eq!(classify_layer("port_definition"), Some(RflpLayer::Logical));
566 assert_eq!(
567 classify_layer("connection_definition"),
568 Some(RflpLayer::Logical)
569 );
570 assert_eq!(
571 classify_layer("attribute_definition"),
572 Some(RflpLayer::Logical)
573 );
574 assert_eq!(classify_layer("item_definition"), Some(RflpLayer::Logical));
575 }
576
577 #[test]
578 fn test_classify_layer_none() {
579 assert_eq!(classify_layer("package_definition"), None);
580 assert_eq!(classify_layer("library_package"), None);
581 assert_eq!(classify_layer("metadata_definition"), None);
582 assert_eq!(classify_layer("enumeration_definition"), None);
583 assert_eq!(classify_layer("view_definition"), None);
584 assert_eq!(classify_layer("unknown_kind"), None);
585 }
586}