Coverage for o2/actions/base_actions/modify_calendar_base_action.py: 84%

70 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 

16from o2.models.timetable.time_period import TimePeriod 

17from o2.util.indented_printer import print_l2 

18 

19if TYPE_CHECKING: 

20 from o2.models.days import DAY 

21 from o2.store import Store 

22 

23 

24class ModifyCalendarBaseActionParamsType(BaseActionParamsType): 

25 """Parameter for `ModifyCalendarBaseAction`.""" 

26 

27 calendar_id: str 

28 period_id: str 

29 day: "DAY" 

30 shift_hours: NotRequired[int] 

31 add_hours_before: NotRequired[int] 

32 add_hours_after: NotRequired[int] 

33 remove_period: NotRequired[bool] 

34 

35 

36@dataclass(frozen=True) 

37class ModifyCalendarBaseAction(BaseAction, ABC): 

38 """`ModifyCalendarBaseAction` will modify the resource calendars. 

39 

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

41 """ 

42 

43 params: ModifyCalendarBaseActionParamsType 

44 

45 @override 

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

47 calendar_id = self.params["calendar_id"] 

48 period_id = self.params["period_id"] 

49 day = self.params["day"] 

50 

51 calendar = state.timetable.get_calendar(calendar_id) 

52 assert calendar is not None 

53 

54 period_index = calendar.get_period_index_by_id(period_id) 

55 if period_index is None: 

56 # print_l2("Error in applying action " + str(self)) 

57 # print_l3( 

58 # f"Period {period_id} not found in calendar {calendar_id}. Most likely it was modified beforehand." # noqa: E501 

59 # ) 

60 return state 

61 period = calendar.time_periods[period_index] 

62 fixed_day_period = TimePeriod( 

63 from_=period.from_, 

64 to=day, 

65 begin_time=period.begin_time, 

66 end_time=period.end_time, 

67 probability=period.probability, 

68 ) 

69 

70 new_period = fixed_day_period 

71 if "shift_hours" in self.params: 

72 new_period = new_period.shift_hours(self.params["shift_hours"]) 

73 if "add_hours_before" in self.params and new_period is not None: 

74 new_period = new_period.add_hours_before(self.params["add_hours_before"]) 

75 if "add_hours_after" in self.params and new_period is not None: 

76 new_period = new_period.add_hours_after(self.params["add_hours_after"]) 

77 if "remove_period" in self.params and self.params["remove_period"]: 

78 new_period = TimePeriod.empty() 

79 

80 if new_period is None: 

81 return state 

82 

83 if enable_prints: 

84 print_l2(f"Modification Made:{period} to {new_period}") 

85 

86 new_calendar = calendar.replace_time_period(period_index, new_period) 

87 new_timetable = state.timetable.replace_resource_calendar(new_calendar) 

88 

89 return replace(state, timetable=new_timetable) 

90 

91 @override 

92 @staticmethod 

93 @abstractmethod 

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

95 pass 

96 

97 def __str__(self) -> str: 

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

99 if "shift_hours" in self.params: 

100 return f"{self.__class__.__name__}(Calender '{self.params['calendar_id']}' ({self.params['day']}-{self.params['period_id']}) -- Shift {self.params['shift_hours']} hours)" # noqa: E501 

101 elif "add_hours_after" in self.params and "add_hours_before" in self.params: 

102 return f"{self.__class__.__name__}(Calender '{self.params['calendar_id']}' ({self.params['day']}-{self.params['period_id']}) -- Add {self.params['add_hours_before']} hours before, {self.params['add_hours_after']} hours after)" # noqa: E501 

103 elif "add_hours_after" in self.params: 

104 return f"{self.__class__.__name__}(Calender '{self.params['calendar_id']}' ({self.params['day']}-{self.params['period_id']}) -- Add {self.params['add_hours_after']} hours after)" # noqa: E501 

105 elif "add_hours_before" in self.params: 

106 return f"{self.__class__.__name__}(Calender '{self.params['calendar_id']}' ({self.params['day']}-{self.params['period_id']}) -- Add {self.params['add_hours_before']} hours before)" # noqa: E501 

107 elif "remove_period" in self.params: 

108 return f"{self.__class__.__name__}(Calender '{self.params['calendar_id']}' ({self.params['day']}-{self.params['period_id']}) -- Remove)" # noqa: E501 

109 return f"{self.__class__.__name__}(Calender '{self.params['calendar_id']}' ({self.params['day']}-{self.params['period_id']}) -- Unknown)" # noqa: E501 

110 

111 @staticmethod 

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

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

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

115 if store.solution.is_base_solution: 

116 return RATING.HIGH 

117 elif isinstance(store.solution.last_action, ModifyCalendarBaseAction): 

118 return RATING.LOW 

119 else: 

120 return RATING.HIGH 

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

122 return RATING.HIGH 

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

124 return RATING.LOW 

125 elif store.settings.legacy_approach.calendar_is_disabled: 

126 return RATING.NOT_APPLICABLE 

127 return RATING.MEDIUM