Coverage for o2/actions/base_actions/modify_resource_base_action.py: 82%

50 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-05-16 11:18 +0000

1from abc import ABC, abstractmethod 

2from dataclasses import dataclass, replace 

3from typing import TYPE_CHECKING 

4 

5from typing_extensions import NotRequired, override 

6 

7from o2.actions.base_actions.base_action import ( 

8 BaseAction, 

9 BaseActionParamsType, 

10 RateSelfReturnType, 

11) 

12from o2.models.legacy_approach import LegacyApproach 

13from o2.models.self_rating import RATING 

14from o2.models.solution import Solution 

15from o2.models.state import State 

16 

17if TYPE_CHECKING: 

18 from o2.store import Store 

19 

20 

21class ModifyResourceBaseActionParamsType(BaseActionParamsType): 

22 """Parameter for `ModifyResourceBaseAction`.""" 

23 

24 resource_id: str 

25 task_id: NotRequired[str] 

26 clone_resource: NotRequired[bool] 

27 remove_resource: NotRequired[bool] 

28 remove_task_from_resource: NotRequired[bool] 

29 

30 

31@dataclass(frozen=True) 

32class ModifyResourceBaseAction(BaseAction, ABC): 

33 """`ModifyResourceBaseAction` will modify a resource or it's tasks. 

34 

35 It's rating function will be implemented by it's subclasses. 

36 """ 

37 

38 params: ModifyResourceBaseActionParamsType 

39 

40 @override 

41 def apply(self, state: State, enable_prints: bool = True) -> State: 

42 if "remove_resource" in self.params and self.params["remove_resource"]: 

43 new_timetable = state.timetable.remove_resource(self.params["resource_id"]) 

44 return replace(state, timetable=new_timetable) 

45 elif "clone_resource" in self.params and self.params["clone_resource"]: 

46 new_timetable = state.timetable.clone_resource( 

47 self.params["resource_id"], 

48 [self.params["task_id"]] if "task_id" in self.params else None, 

49 ) 

50 return replace(state, timetable=new_timetable) 

51 elif ( 

52 "remove_task_from_resource" in self.params 

53 and self.params["remove_task_from_resource"] 

54 and "task_id" in self.params 

55 ): 

56 new_timetable = state.timetable.remove_task_from_resource( 

57 self.params["resource_id"], 

58 self.params["task_id"], 

59 ) 

60 return replace(state, timetable=new_timetable) 

61 return state 

62 

63 @override 

64 @staticmethod 

65 @abstractmethod 

66 def rate_self(store: "Store", input: "Solution") -> RateSelfReturnType["ModifyResourceBaseAction"]: 

67 pass 

68 

69 def __str__(self) -> str: 

70 """Return a string representation of the action.""" 

71 if "remove_resource" in self.params: 

72 return f"{self.__class__.__name__}(Resource '{self.params['resource_id']}' -- Remove)" # noqa: E501 

73 elif "clone_resource" in self.params: 

74 return f"{self.__class__.__name__}(Resource '{self.params['resource_id']}' -- Clone)" # noqa: E501 

75 elif "remove_task_from_resource" in self.params and "task_id" in self.params: 

76 return f"{self.__class__.__name__}(Resource '{self.params['resource_id']}' -- Remove Task '{self.params['task_id']}')" # noqa: E501 

77 return f"{self.__class__.__name__}(Resource '{self.params['resource_id']}' -- Unknown)" 

78 

79 @staticmethod 

80 def get_default_rating(store: "Store") -> RATING: 

81 """Return the default rating for this action.""" 

82 if store.settings.legacy_approach == LegacyApproach.COMBINED: 

83 # We start with Calendar actions, so we start with LOW rating 

84 if store.solution.is_base_solution: # noqa: SIM114 

85 return RATING.LOW 

86 elif isinstance(store.solution.last_action, ModifyResourceBaseAction): 

87 return RATING.LOW 

88 else: 

89 return RATING.HIGH 

90 elif store.settings.legacy_approach == LegacyApproach.RESOURCES_FIRST: 

91 return RATING.HIGH 

92 elif store.settings.legacy_approach == LegacyApproach.CALENDAR_FIRST: 

93 return RATING.LOW 

94 elif store.settings.legacy_approach.calendar_is_disabled: 

95 return RATING.NOT_APPLICABLE 

96 return RATING.MEDIUM