Skip to main content

nomograph_sysml_core/
vocabulary.rs

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}