Skip to content

Commit 2e6cf97

Browse files
Enhance validation for graph checks error messages (#142)
1 parent 915c441 commit 2e6cf97

File tree

4 files changed

+86
-66
lines changed

4 files changed

+86
-66
lines changed

src/extensions/score_metamodel/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ def load_metamodel_data():
170170
types_dict = data.get("needs_types", {})
171171
links_dict = data.get("needs_extra_links", {})
172172
graph_check_dict = data.get("graph_checks", {})
173-
174173
global_base_options = data.get("needs_types_base_options", {})
175174
global_base_options_optional_opts = global_base_options.get("optional_options", {})
176175

src/extensions/score_metamodel/checks/graph_checks.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
# *******************************************************************************
1313
import operator
1414
from collections.abc import Callable
15-
from typing import Any, Literal
16-
17-
from sphinx.application import Sphinx
18-
from sphinx_needs.data import NeedsInfoType, NeedsView
15+
from functools import reduce
16+
from typing import Any
1917

2018
from score_metamodel import (
2119
CheckLogger,
2220
graph_check,
2321
)
22+
from sphinx.application import Sphinx
23+
from sphinx_needs.data import NeedsInfoType, NeedsView
2424

2525

2626
def eval_need_check(need: NeedsInfoType, check: str, log: CheckLogger) -> bool:
@@ -70,7 +70,7 @@ def eval_need_condition(
7070
oper: dict[str, Any] = {
7171
"and": operator.and_,
7272
"or": operator.or_,
73-
"not": operator.not_,
73+
"not": lambda x: not x,
7474
"xor": operator.xor,
7575
}
7676

@@ -80,16 +80,17 @@ def eval_need_condition(
8080
cond: str = list(condition.keys())[0]
8181
vals: list[Any] = list(condition.values())[0]
8282

83-
if cond in ["and", "or", "xor", "not"]:
84-
for i in range(len(vals) - 1):
85-
return oper[cond](
86-
eval_need_condition(need, vals[i], log),
87-
eval_need_condition(need, vals[i + 1], log),
88-
)
89-
else:
90-
raise ValueError(f"Binary Operator not defined: {vals}")
83+
if cond == "not":
84+
if not isinstance(vals, list) or len(vals) != 1:
85+
raise ValueError("Operator 'not' requires exactly one operand.")
86+
return oper["not"](eval_need_condition(need, vals[0], log))
9187

92-
return True
88+
if cond in ["and", "or", "xor"]:
89+
return reduce(
90+
lambda a, b: oper[cond](a, b),
91+
(eval_need_condition(need, val, log) for val in vals),
92+
)
93+
raise ValueError(f"Unsupported condition operator: {cond}")
9394

9495

9596
def get_need_selection(
@@ -137,18 +138,25 @@ def check_metamodel_graph(
137138
# Convert list to dictionary for easy lookup
138139
needs_dict_all = {need["id"]: need for need in all_needs.values()}
139140
needs_local = list(all_needs.filter_is_external(False).values())
141+
140142
# Iterate over all graph checks
141-
for check in graph_checks_global.items():
142-
apply, eval = check[1].values()
143-
# Get all needs that match the selection criteria
143+
for check_name, check_config in graph_checks_global.items():
144+
apply = check_config.get("needs")
145+
eval = check_config.get("check")
146+
explanation = check_config.get("explanation", "")
147+
assert explanation != "", (
148+
f"Explanation for graph check {check_name} is missing. Explanations are mandatory for graph checks."
149+
)
150+
# Get all needs matching the selection criteria
144151
selected_needs = get_need_selection(needs_local, apply, log)
145152

146153
for need in selected_needs:
147154
for parent_relation in list(eval.keys()):
148155
if parent_relation not in need:
149-
msg = f"Attribute not defined: {parent_relation}"
156+
msg = f"Attribute not defined: `{parent_relation}` in need `{need['id']}`."
150157
log.warning_for_need(need, msg)
151158
continue
159+
152160
parent_ids = need[parent_relation]
153161

154162
for parent_id in parent_ids:
@@ -160,7 +168,8 @@ def check_metamodel_graph(
160168

161169
if not eval_need_condition(parent_need, eval[parent_relation], log):
162170
msg = (
163-
f"parent need `{parent_id}` does not fulfill "
171+
f"Parent need `{parent_id}` does not fulfill "
164172
f"condition `{eval[parent_relation]}`."
173+
f" Explanation: {explanation}"
165174
)
166175
log.warning_for_need(need, msg)

src/extensions/score_metamodel/metamodel.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ needs_types_base_options:
1818
# Custom semantic validation rules
1919

2020
# Prohibited Word Option Checks
21-
# Follow this schema to write new checks
21+
# Follow this schema to write new checks
2222
# check_name:
2323
# types[optional]:
2424
# - List of tag types ('tags' option)
@@ -821,6 +821,7 @@ graph_checks:
821821
condition: safety == QM
822822
check:
823823
satisfies: safety == QM
824+
explanation: An ASIL requirement must link at least one parent/upstream ASIL requirement for correct decomposition. Please ensure the parent’s safety level is QM and its status is valid.
824825
# If need-req is `ASIL_B`, parent must be `QM` or `ASIL_B`.
825826
req_safety_linkage_asil_b:
826827
needs:
@@ -831,6 +832,7 @@ graph_checks:
831832
or:
832833
- safety == ASIL_B
833834
- safety == QM
835+
explanation: An ASIL requirement must link at least one parent/upstream ASIL requirement for correct decomposition. Please ensure the parent’s safety level is ASIL_B or QM and its status is valid.
834836
# saf - ID gd_req__saf_linkage_safety
835837
# It shall be checked that Safety Analysis (DFA and FMEA) can only be linked via mitigate against
836838
# - <Feature | Component | AoU> Requirements with the same ASIL or
@@ -842,4 +844,4 @@ graph_checks:
842844
condition: safety == ASIL_B
843845
check:
844846
mitigated_by: safety != QM
845-
847+
explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.

src/extensions/score_metamodel/tests/rst/graph/test_metamodel_graph.rst

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,17 @@
3131

3232

3333
.. Positive Test: Child requirement QM. Parent requirement has the correct related safety level. Parent requirement is `QM`.
34-
#EXPECT-NOT: feat_req__child__1: parent need `feat_req__parent__QM` does not fulfill condition `safety == QM`.
34+
#EXPECT-NOT: feat_req__child__1: Parent need `feat_req__parent__QM` does not fulfill condition `safety == QM`. Explanation: An ASIL requirement must link at least one parent/upstream ASIL requirement for correct decomposition. Please ensure the parent’s safety level is QM and its status is valid.
3535

3636
.. feat_req:: Child requirement 1
3737
:id: feat_req__child__1
3838
:safety: QM
3939
:satisfies: feat_req__parent__QM
4040
:status: valid
4141

42+
4243
.. Positive Test: Child requirement ASIL B. Parent requirement has the correct related safety level. Parent requirement is `QM`.
43-
#EXPECT-NOT: feat_req__child__2: parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety == QM`.
44+
#EXPECT-NOT: feat_req__child__2: Parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety == QM`. Explanation: An ASIL requirement must link at least one parent/upstream ASIL requirement for correct decomposition. Please ensure the parent’s safety level is QM and its status is valid.
4445

4546
.. feat_req:: Child requirement 2
4647
:id: feat_req__child__2
@@ -49,130 +50,139 @@
4950
:status: valid
5051

5152

53+
5254
.. Negative Test: Child requirement QM. Parent requirement is `ASIL_B`. Child cant fulfill the safety level of the parent.
53-
#EXPECT: feat_req__child__4: parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety == QM`.
55+
#EXPECT: feat_req__child__3: Parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety == QM`. Explanation: An ASIL requirement must link at least one parent/upstream ASIL requirement for correct decomposition. Please ensure the parent’s safety level is QM and its status is valid.
5456

55-
.. comp_req:: Child requirement 4
56-
:id: feat_req__child__4
57+
.. comp_req:: Child requirement 3
58+
:id: feat_req__child__3
5759
:safety: QM
5860
:satisfies: feat_req__parent__ASIL_B
5961
:status: valid
6062

6163

6264

63-
64-
6565
.. Parent requirement does not exist
66-
#EXPECT: feat_req__child__9: Parent need `feat_req__parent0__abcd` not found in needs_dict.
66+
#EXPECT: feat_req__child__4: Parent need `feat_req__parent0__abcd` not found in needs_dict.
6767

68-
.. feat_req:: Child requirement 9
69-
:id: feat_req__child__9
68+
.. feat_req:: Child requirement 4
69+
:id: feat_req__child__4
7070
:safety: ASIL_B
7171
:status: valid
7272
:satisfies: feat_req__parent0__abcd
7373

7474

75+
7576
.. Mitigation of Safety Analysis (FMEA and DFA) shall be checked. Mitigation shall have the same or higher safety level than the analysed item.
7677
.. Negative Test: Linked to a mitigation that is lower than the safety level of the analysed item.
77-
#EXPECT: feat_saf_dfa__child__10: parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`.
78+
#EXPECT: feat_saf_dfa__child__5: Parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
7879

79-
.. feat_saf_dfa:: Child requirement 10
80-
:id: feat_saf_dfa__child__10
80+
.. feat_saf_dfa:: Child requirement 5
81+
:id: feat_saf_dfa__child__5
8182
:safety: ASIL_B
8283
:status: valid
8384
:mitigated_by: feat_req__parent__QM
8485

86+
8587
.. Positive Test: Linked to a mitigation that is equal to the safety level of the analysed item.
86-
#EXPECT-NOT: feat_saf_dfa__child__11: parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`.
88+
#EXPECT-NOT: feat_saf_dfa__child__6: Parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
8789

88-
.. feat_saf_dfa:: Child requirement 11
89-
:id: feat_saf_dfa__child__11
90+
.. feat_saf_dfa:: Child requirement 6
91+
:id: feat_saf_dfa__child__6
9092
:safety: ASIL_B
9193
:status: valid
9294
:mitigated_by: feat_req__parent__ASIL_B
9395

9496

97+
9598
.. Negative Test: Linked to a mitigation that is lower than the safety level of the analysed item.
96-
#EXPECT: comp_saf_dfa__child__13: parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`.
99+
#EXPECT: comp_saf_dfa__child__7: Parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
97100

98-
.. comp_saf_dfa:: Child requirement 13
99-
:id: comp_saf_dfa__child__13
101+
.. comp_saf_dfa:: Child requirement 7
102+
:id: comp_saf_dfa__child__7
100103
:safety: ASIL_B
101104
:status: valid
102105
:mitigated_by: feat_req__parent__QM
103106

107+
104108
.. Positive Test: Linked to a mitigation that is equal to the safety level of the analysed item.
105-
#EXPECT-NOT: comp_saf_dfa__child__14: parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`.
109+
#EXPECT-NOT: comp_saf_dfa__child__8: Parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
106110

107-
.. comp_saf_dfa:: Child requirement 14
108-
:id: comp_saf_dfa__child__14
111+
.. comp_saf_dfa:: Child requirement 8
112+
:id: comp_saf_dfa__child__8
109113
:safety: ASIL_B
110114
:status: valid
111115
:mitigated_by: feat_req__parent__ASIL_B
112116

113117

118+
114119
.. Negative Test: Linked to a mitigation that is lower than the safety level of the analysed item.
115-
#EXPECT: feat_saf_dfa__child__16: parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`.
120+
#EXPECT: feat_saf_dfa__child__9: Parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
116121

117-
.. feat_saf_dfa:: Child requirement 16
118-
:id: feat_saf_dfa__child__16
122+
.. feat_saf_dfa:: Child requirement 9
123+
:id: feat_saf_dfa__child__9
119124
:safety: ASIL_B
120125
:status: valid
121126
:mitigated_by: feat_req__parent__QM
122127

128+
123129
.. Positive Test: Linked to a mitigation that is equal to the safety level of the analysed item.
124-
#EXPECT-NOT: feat_saf_dfa__child__17: parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`.
130+
#EXPECT-NOT: feat_saf_dfa__child__10: Parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
125131

126-
.. feat_saf_dfa:: Child requirement 17
127-
:id: feat_saf_dfa__child__17
132+
.. feat_saf_dfa:: Child requirement 10
133+
:id: feat_saf_dfa__child__10
128134
:safety: ASIL_B
129135
:status: valid
130136
:mitigated_by: feat_req__parent__ASIL_B
131137

132138

139+
133140
.. Negative Test: Linked to a mitigation that is lower than the safety level of the analysed item.
134-
#EXPECT: feat_saf_fmea__child__19: parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`.
141+
#EXPECT: feat_saf_fmea__child__11: Parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
135142

136-
.. feat_saf_fmea:: Child requirement 19
137-
:id: feat_saf_fmea__child__19
143+
.. feat_saf_fmea:: Child requirement 11
144+
:id: feat_saf_fmea__child__11
138145
:safety: ASIL_B
139146
:status: valid
140147
:mitigated_by: feat_req__parent__QM
141148

149+
142150
.. Positive Test: Linked to a mitigation that is equal to the safety level of the analysed item.
143-
#EXPECT-NOT: feat_saf_fmea__child__20: parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`.
151+
#EXPECT-NOT: feat_saf_fmea__child__12: Parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
144152

145-
.. feat_saf_fmea:: Child requirement 20
146-
:id: feat_saf_fmea__child__20
153+
.. feat_saf_fmea:: Child requirement 12
154+
:id: feat_saf_fmea__child__12
147155
:safety: ASIL_B
148156
:status: valid
149157
:mitigated_by: feat_req__parent__ASIL_B
150158

151159

160+
152161
.. Positive Test: Linked to a mitigation that is higher to the safety level of the analysed item.
153-
#EXPECT-NOT: feat_saf_fmea__child__21: parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`.
162+
#EXPECT-NOT: feat_saf_fmea__child__13: Parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
154163

155-
.. feat_saf_fmea:: Child requirement 21
156-
:id: feat_saf_fmea__child__21
164+
.. feat_saf_fmea:: Child requirement 13
165+
:id: feat_saf_fmea__child__13
157166
:safety: QM
158167
:status: valid
159168
:mitigated_by: feat_req__parent__ASIL_B
160169

170+
161171
.. Negative Test: Linked to a mitigation that is lower than the safety level of the analysed item.
162-
#EXPECT: comp_saf_fmea__child__22: parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`.
172+
#EXPECT: comp_saf_fmea__child__14: Parent need `feat_req__parent__QM` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
163173

164-
.. comp_saf_fmea:: Child requirement 22
165-
:id: comp_saf_fmea__child__22
174+
.. comp_saf_fmea:: Child requirement 14
175+
:id: comp_saf_fmea__child__14
166176
:safety: ASIL_B
167177
:status: valid
168178
:mitigated_by: feat_req__parent__QM
169179

180+
170181
.. Positive Test: Linked to a mitigation that is equal to the safety level of the analysed item.
171-
#EXPECT-NOT: comp_saf_fmea__child__23: parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`.
182+
#EXPECT-NOT: comp_saf_fmea__child__15: Parent need `feat_req__parent__ASIL_B` does not fulfill condition `safety != QM`. Explanation: An ASIL_B safety requirement must link to a ASIL_B requirement. Please ensure that the linked requirements safety level is not QM and it's status is valid.
172183

173-
.. comp_saf_fmea:: Child requirement 23
174-
:id: comp_saf_fmea__child__23
184+
.. comp_saf_fmea:: Child requirement 15
185+
:id: comp_saf_fmea__child__15
175186
:safety: ASIL_B
176187
:status: valid
177188
:mitigated_by: feat_req__parent__ASIL_B
178-

0 commit comments

Comments
 (0)