Coverage for flowr / domain / condition.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-02 18:23 +0000

1"""Condition evaluation for flow definition guard conditions.""" 

2 

3import re 

4from enum import Enum 

5 

6 

7class ConditionOperator(Enum): 

8 """Operators for guard condition expressions.""" 

9 

10 EQUALS = "==" 

11 NOT_EQUALS = "!=" 

12 GREATER_THAN_OR_EQUAL = ">=" 

13 LESS_THAN_OR_EQUAL = "<=" 

14 GREATER_THAN = ">" 

15 LESS_THAN = "<" 

16 APPROXIMATELY_EQUAL = "~=" 

17 

18 

19_OPERATOR_PREFIXES: list[tuple[str, ConditionOperator]] = [ 

20 (">=", ConditionOperator.GREATER_THAN_OR_EQUAL), 

21 ("<=", ConditionOperator.LESS_THAN_OR_EQUAL), 

22 ("~=", ConditionOperator.APPROXIMATELY_EQUAL), 

23 ("==", ConditionOperator.EQUALS), 

24 ("!=", ConditionOperator.NOT_EQUALS), 

25 (">", ConditionOperator.GREATER_THAN), 

26 ("<", ConditionOperator.LESS_THAN), 

27] 

28 

29 

30def _extract_numeric(s: str) -> float | None: 

31 """Extract the first numeric value from a string.""" 

32 match = re.search(r"[-+]?\d*\.?\d+", s) 

33 if match is None: 

34 return None 

35 return float(match.group()) 

36 

37 

38def parse_condition(condition_str: str) -> tuple[ConditionOperator, str]: 

39 """Parse a condition string into an operator and value.""" 

40 for prefix, op in _OPERATOR_PREFIXES: 

41 if condition_str.startswith(prefix): 

42 return op, condition_str[len(prefix) :] 

43 return ConditionOperator.EQUALS, condition_str 

44 

45 

46def _compare_numeric( 

47 condition_value: str, 

48 evidence_value: str, 

49 op: ConditionOperator, 

50) -> bool | None: 

51 """Compare two values numerically. Returns None if not numeric.""" 

52 c_num = _extract_numeric(condition_value) 

53 e_num = _extract_numeric(evidence_value) 

54 if c_num is None or e_num is None: 

55 return None 

56 match op: 

57 case ConditionOperator.GREATER_THAN_OR_EQUAL: 

58 return e_num >= c_num 

59 case ConditionOperator.LESS_THAN_OR_EQUAL: 

60 return e_num <= c_num 

61 case ConditionOperator.GREATER_THAN: 

62 return e_num > c_num 

63 case ConditionOperator.LESS_THAN: 

64 return e_num < c_num 

65 case ConditionOperator.APPROXIMATELY_EQUAL: 

66 return abs(e_num - c_num) / abs(c_num) <= 0.05 

67 return None # pragma: no cover 

68 

69 

70def evaluate_condition( 

71 operator: ConditionOperator, 

72 condition_value: str, 

73 evidence_value: str, 

74) -> bool: 

75 """Evaluate a condition expression against an evidence value.""" 

76 match operator: 

77 case ConditionOperator.EQUALS: 

78 return evidence_value == condition_value 

79 case ConditionOperator.NOT_EQUALS: 

80 return evidence_value != condition_value 

81 case _: 

82 result = _compare_numeric(condition_value, evidence_value, operator) 

83 if result is None: 

84 return False 

85 return result