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

45 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-22 18:42 +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 

17 

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

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

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

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

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

23 (">", ConditionOperator.GREATER_THAN), 

24 ("<", ConditionOperator.LESS_THAN), 

25] 

26 

27 

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

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

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

31 if match is None: 

32 return None 

33 return float(match.group()) 

34 

35 

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

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

38 for prefix, op in _OPERATOR_PREFIXES: 

39 if condition_str.startswith(prefix): 

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

41 return ConditionOperator.EQUALS, condition_str 

42 

43 

44def _compare_numeric( 

45 condition_value: str, 

46 evidence_value: str, 

47 op: ConditionOperator, 

48) -> bool | None: 

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

50 c_num = _extract_numeric(condition_value) 

51 e_num = _extract_numeric(evidence_value) 

52 if c_num is None or e_num is None: 

53 return None 

54 match op: 

55 case ConditionOperator.GREATER_THAN_OR_EQUAL: 

56 return e_num >= c_num 

57 case ConditionOperator.LESS_THAN_OR_EQUAL: 

58 return e_num <= c_num 

59 case ConditionOperator.GREATER_THAN: 

60 return e_num > c_num 

61 case ConditionOperator.LESS_THAN: 

62 return e_num < c_num 

63 

64 return None # pragma: no cover 

65 

66 

67def evaluate_condition( 

68 operator: ConditionOperator, 

69 condition_value: str, 

70 evidence_value: str, 

71) -> bool: 

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

73 match operator: 

74 case ConditionOperator.EQUALS: 

75 return evidence_value == condition_value 

76 case ConditionOperator.NOT_EQUALS: 

77 return evidence_value != condition_value 

78 case _: 

79 result = _compare_numeric(condition_value, evidence_value, operator) 

80 if result is None: 

81 return False 

82 return result