1use nomograph_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 "package_definition",
183 "library_package",
184];
185
186const STOP_WORDS: &[&str] = &[
187 "a",
188 "an",
189 "the",
190 "and",
191 "or",
192 "for",
193 "to",
194 "of",
195 "in",
196 "is",
197 "it",
198 "its",
199 "are",
200 "was",
201 "were",
202 "be",
203 "been",
204 "do",
205 "does",
206 "did",
207 "has",
208 "have",
209 "had",
210 "with",
211 "that",
212 "this",
213 "what",
214 "how",
215 "which",
216 "where",
217 "when",
218 "who",
219 "why",
220 "find",
221 "show",
222 "get",
223 "list",
224 "describe",
225 "identify",
226 "determine",
227 "explain",
228 "i",
229 "me",
230 "my",
231 "we",
232 "us",
233 "you",
234 "your",
235 "use",
236 "using",
237 "from",
238 "by",
239 "not",
240 "no",
241 "any",
242 "all",
243 "each",
244 "if",
245 "then",
246 "than",
247 "but",
248 "so",
249 "as",
250 "on",
251 "at",
252 "about",
253 "into",
254 "can",
255 "should",
256 "would",
257 "could",
258 "will",
259];
260
261const STEM_SUFFIXES: &[&str] = &[
262 "ation", "ment", "ness", "tion", "sion", "ance", "ence", "ity", "ing", "ies", "ied", "ous",
263 "ive", "ful", "ion", "ed", "ly", "er", "es", "al", "s",
264];
265
266#[derive(Debug, Clone)]
267pub struct ExpandedQuery {
268 pub original: String,
269 pub tokens: Vec<String>,
270 pub element_kinds: Vec<String>,
271 pub relationship_kinds: Vec<String>,
272}
273
274fn naive_stem(word: &str) -> &str {
275 for suffix in STEM_SUFFIXES {
276 if let Some(stem) = word.strip_suffix(suffix) {
277 if stem.len() >= 3 {
278 return stem;
279 }
280 }
281 }
282 word
283}
284
285fn stem_match(token: &str, term: &str) -> bool {
286 let t_stem = naive_stem(token);
287 let v_stem = naive_stem(term);
288 t_stem == v_stem
289 || t_stem.starts_with(v_stem)
290 || v_stem.starts_with(t_stem)
291 || token.starts_with(term)
292 || term.starts_with(token)
293}
294
295pub fn expand_query(query: &str) -> ExpandedQuery {
296 let lower = query.to_lowercase();
297 let tokens: Vec<String> = lower
298 .split(|c: char| !c.is_alphanumeric() && c != '_')
299 .filter(|s| !s.is_empty() && !STOP_WORDS.contains(s))
300 .map(|s| s.to_string())
301 .collect();
302
303 let mut element_kinds = Vec::new();
304 let mut relationship_kinds = Vec::new();
305
306 for (terms, mappings) in SYSML_VOCABULARY {
307 let matched = terms.iter().any(|term| {
308 if term.contains(' ') {
309 lower.contains(term)
310 } else {
311 tokens.iter().any(|t| t == term || stem_match(t, term))
312 }
313 });
314
315 if matched {
316 for mapping in *mappings {
317 let is_rel = RELATIONSHIP_KIND_NAMES
318 .iter()
319 .any(|rk| rk.eq_ignore_ascii_case(mapping));
320 if is_rel {
321 if !relationship_kinds.contains(&mapping.to_string()) {
322 relationship_kinds.push(mapping.to_string());
323 }
324 } else if !element_kinds.contains(&mapping.to_string()) {
325 element_kinds.push(mapping.to_string());
326 }
327 }
328 }
329 }
330
331 ExpandedQuery {
332 original: query.to_string(),
333 tokens,
334 element_kinds,
335 relationship_kinds,
336 }
337}
338
339use crate::element::RflpLayer;
340
341pub fn classify_layer(kind: &str) -> Option<RflpLayer> {
342 match kind {
343 "requirement_definition"
344 | "requirement_usage"
345 | "concern_definition"
346 | "concern_usage"
347 | "stakeholder_usage"
348 | "constraint_definition"
349 | "constraint_usage"
350 | "verification_definition"
351 | "verification_usage"
352 | "objective_usage" => Some(RflpLayer::Requirements),
353 "use_case_definition"
354 | "use_case_usage"
355 | "action_definition"
356 | "action_usage"
357 | "state_definition"
358 | "state_usage"
359 | "calc_definition"
360 | "calc_usage"
361 | "analysis_case_definition"
362 | "analysis_case_usage"
363 | "analysis_definition"
364 | "analysis_usage"
365 | "item_flow_usage"
366 | "actor_usage"
367 | "event_occurrence_usage"
368 | "exhibit_usage" => Some(RflpLayer::Functional),
369 "part_definition"
370 | "part_usage"
371 | "port_definition"
372 | "port_usage"
373 | "connection_definition"
374 | "connection_usage"
375 | "interface_definition"
376 | "interface_usage"
377 | "attribute_definition"
378 | "attribute_usage"
379 | "item_definition"
380 | "item_usage"
381 | "occurrence_definition"
382 | "end_usage"
383 | "parameter_usage" => Some(RflpLayer::Logical),
384 _ => None,
385 }
386}
387
388pub struct SysmlVocabulary;
389
390impl Vocabulary for SysmlVocabulary {
391 fn expand_kind(&self, kind: &str) -> Vec<&str> {
392 let lower = kind.to_lowercase();
393 for (key, kinds) in ELEMENT_KIND_MAP {
394 if *key == lower.as_str() {
395 return kinds.to_vec();
396 }
397 }
398 vec![]
399 }
400
401 fn normalize_kind<'a>(&self, kind: &'a str) -> &'a str {
402 for (normalized, specifics) in ELEMENT_KIND_MAP {
403 if specifics.contains(&kind) {
404 return normalized;
405 }
406 }
407 kind
408 }
409
410 fn relationship_kinds(&self) -> &[&str] {
411 RELATIONSHIP_KIND_NAMES
412 }
413
414 fn element_kinds(&self) -> &[&str] {
415 ELEMENT_KIND_NAMES
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[test]
424 fn test_expand_requirement_satisfy_query() {
425 let eq = expand_query("What requirements does the engine satisfy?");
426 assert!(eq
427 .element_kinds
428 .iter()
429 .any(|k| k == "requirement_definition"),);
430 assert!(eq.element_kinds.iter().any(|k| k == "requirement_usage"),);
431 assert!(eq
432 .relationship_kinds
433 .iter()
434 .any(|k| k.eq_ignore_ascii_case("Satisfy")),);
435 }
436
437 #[test]
438 fn test_expand_allocate_query() {
439 let eq = expand_query("How are functions allocated to components?");
440 assert!(eq
441 .relationship_kinds
442 .iter()
443 .any(|k| k.eq_ignore_ascii_case("Allocate")),);
444 assert!(eq.element_kinds.iter().any(|k| k == "part_definition"),);
445 }
446
447 #[test]
448 fn test_expand_multi_word_term() {
449 let eq = expand_query("Show me the use case diagram");
450 assert!(eq.element_kinds.iter().any(|k| k == "use_case_definition"),);
451 }
452
453 #[test]
454 fn test_expand_no_matches() {
455 let eq = expand_query("hello world");
456 assert!(eq.element_kinds.is_empty());
457 assert!(eq.relationship_kinds.is_empty());
458 }
459
460 #[test]
461 fn test_expand_preserves_tokens() {
462 let eq = expand_query("What requirements exist?");
463 assert!(!eq.tokens.contains(&"what".to_string()));
464 assert!(eq.tokens.contains(&"requirements".to_string()));
465 assert!(eq.tokens.contains(&"exist".to_string()));
466 }
467
468 #[test]
469 fn test_expand_deduplicates() {
470 let eq = expand_query("requirement req requirement");
471 let req_def_count = eq
472 .element_kinds
473 .iter()
474 .filter(|k| k.as_str() == "requirement_definition")
475 .count();
476 assert_eq!(req_def_count, 1);
477 }
478
479 #[test]
480 fn test_stop_words_filtered() {
481 let eq = expand_query("find the shield module for this system");
482 assert!(!eq.tokens.contains(&"find".to_string()));
483 assert!(!eq.tokens.contains(&"the".to_string()));
484 assert!(!eq.tokens.contains(&"for".to_string()));
485 assert!(!eq.tokens.contains(&"this".to_string()));
486 assert!(eq.tokens.contains(&"shield".to_string()));
487 assert!(eq.tokens.contains(&"module".to_string()));
488 assert!(eq.tokens.contains(&"system".to_string()));
489 }
490
491 #[test]
492 fn test_naive_stem() {
493 assert_eq!(naive_stem("requirements"), "requirement");
494 assert_eq!(naive_stem("satisfaction"), "satisfac");
495 assert_eq!(naive_stem("verification"), "verific");
496 assert_eq!(naive_stem("mining"), "min");
497 assert_eq!(naive_stem("ore"), "ore");
498 }
499
500 #[test]
501 fn test_stem_match_across_forms() {
502 assert!(stem_match("requirements", "requirement"));
503 assert!(stem_match("satisfies", "satisfy"));
504 assert!(stem_match("connections", "connect"));
505 }
506
507 #[test]
508 fn test_classify_layer_requirements() {
509 assert_eq!(
510 classify_layer("requirement_definition"),
511 Some(RflpLayer::Requirements)
512 );
513 assert_eq!(
514 classify_layer("requirement_usage"),
515 Some(RflpLayer::Requirements)
516 );
517 assert_eq!(
518 classify_layer("constraint_definition"),
519 Some(RflpLayer::Requirements)
520 );
521 assert_eq!(
522 classify_layer("concern_definition"),
523 Some(RflpLayer::Requirements)
524 );
525 assert_eq!(
526 classify_layer("stakeholder_usage"),
527 Some(RflpLayer::Requirements)
528 );
529 }
530
531 #[test]
532 fn test_classify_layer_functional() {
533 assert_eq!(
534 classify_layer("action_definition"),
535 Some(RflpLayer::Functional)
536 );
537 assert_eq!(classify_layer("action_usage"), Some(RflpLayer::Functional));
538 assert_eq!(
539 classify_layer("state_definition"),
540 Some(RflpLayer::Functional)
541 );
542 assert_eq!(
543 classify_layer("use_case_definition"),
544 Some(RflpLayer::Functional)
545 );
546 assert_eq!(
547 classify_layer("calc_definition"),
548 Some(RflpLayer::Functional)
549 );
550 assert_eq!(
551 classify_layer("item_flow_usage"),
552 Some(RflpLayer::Functional)
553 );
554 }
555
556 #[test]
557 fn test_classify_layer_logical() {
558 assert_eq!(classify_layer("part_definition"), Some(RflpLayer::Logical));
559 assert_eq!(classify_layer("part_usage"), Some(RflpLayer::Logical));
560 assert_eq!(classify_layer("port_definition"), Some(RflpLayer::Logical));
561 assert_eq!(
562 classify_layer("connection_definition"),
563 Some(RflpLayer::Logical)
564 );
565 assert_eq!(
566 classify_layer("attribute_definition"),
567 Some(RflpLayer::Logical)
568 );
569 assert_eq!(classify_layer("item_definition"), Some(RflpLayer::Logical));
570 }
571
572 #[test]
573 fn test_classify_layer_none() {
574 assert_eq!(classify_layer("package_definition"), None);
575 assert_eq!(classify_layer("library_package"), None);
576 assert_eq!(classify_layer("metadata_definition"), None);
577 assert_eq!(classify_layer("enumeration_definition"), None);
578 assert_eq!(classify_layer("view_definition"), None);
579 assert_eq!(classify_layer("unknown_kind"), None);
580 }
581}