Skip to main content

sysml_core/
vocabulary.rs

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}