File size: 1,777 Bytes
9dbc5de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
"""Biological rule engine — hard and soft constraint checking.

Hard constraints block action execution entirely.
Soft constraints allow execution but degrade output quality and incur penalties.
"""

from __future__ import annotations

from typing import List

try:
    from ...models import ActionType, ExperimentAction
    from ..simulator.latent_state import FullLatentState
except ImportError:  # pragma: no cover
    from models import ActionType, ExperimentAction
    from server.simulator.latent_state import FullLatentState

from .causal import check_causal_validity
from .prerequisites import check_prerequisites
from .redundancy import check_redundancy
from .resources import check_resource_constraints
from .tool_compatibility import check_tool_compatibility
from .types import RuleViolation, Severity


class RuleEngine:
    """Evaluates biological and resource constraints against the current
    latent state before each action is applied.
    """

    def check(
        self, action: ExperimentAction, state: FullLatentState
    ) -> List[RuleViolation]:
        violations: List[RuleViolation] = []
        violations.extend(check_prerequisites(action, state))
        violations.extend(check_resource_constraints(action, state))
        violations.extend(check_redundancy(action, state))
        violations.extend(check_causal_validity(action, state))
        violations.extend(check_tool_compatibility(action, state))
        return violations

    def hard_violations(self, violations: List[RuleViolation]) -> List[str]:
        return [v.message for v in violations if v.severity == Severity.HARD]

    def soft_violations(self, violations: List[RuleViolation]) -> List[str]:
        return [v.message for v in violations if v.severity == Severity.SOFT]