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
« 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
5from typing_extensions import NotRequired, override
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
19if TYPE_CHECKING:
20 from o2.models.days import DAY
21 from o2.store import Store
24class ModifyCalendarBaseActionParamsType(BaseActionParamsType):
25 """Parameter for `ModifyCalendarBaseAction`."""
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]
36@dataclass(frozen=True)
37class ModifyCalendarBaseAction(BaseAction, ABC):
38 """`ModifyCalendarBaseAction` will modify the resource calendars.
40 It's rating function will be implemented by it's subclasses.
41 """
43 params: ModifyCalendarBaseActionParamsType
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"]
51 calendar = state.timetable.get_calendar(calendar_id)
52 assert calendar is not None
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 )
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()
80 if new_period is None:
81 return state
83 if enable_prints:
84 print_l2(f"Modification Made:{period} to {new_period}")
86 new_calendar = calendar.replace_time_period(period_index, new_period)
87 new_timetable = state.timetable.replace_resource_calendar(new_calendar)
89 return replace(state, timetable=new_timetable)
91 @override
92 @staticmethod
93 @abstractmethod
94 def rate_self(store: "Store", input: "Solution") -> RateSelfReturnType["ModifyCalendarBaseAction"]:
95 pass
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
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