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
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-02 18:23 +0000
1"""Condition evaluation for flow definition guard conditions."""
3import re
4from enum import Enum
7class ConditionOperator(Enum):
8 """Operators for guard condition expressions."""
10 EQUALS = "=="
11 NOT_EQUALS = "!="
12 GREATER_THAN_OR_EQUAL = ">="
13 LESS_THAN_OR_EQUAL = "<="
14 GREATER_THAN = ">"
15 LESS_THAN = "<"
16 APPROXIMATELY_EQUAL = "~="
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]
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())
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
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
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