Coverage for o2/models/timetable/resource.py: 95%

37 statements  

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

1from dataclasses import dataclass, replace 

2from typing import TYPE_CHECKING 

3 

4from dataclass_wizard import JSONWizard 

5 

6from o2.util.helper import CLONE_REGEX, random_string 

7 

8if TYPE_CHECKING: 

9 from o2.models.timetable.timetable_type import TimetableType 

10 

11 

12@dataclass(frozen=True) 

13class Resource(JSONWizard): 

14 """Represents a resource such as a person, machine, or other entity that performs tasks.""" 

15 

16 id: str 

17 name: str 

18 cost_per_hour: int 

19 amount: int 

20 calendar: str 

21 assigned_tasks: list[str] 

22 

23 def get_total_cost(self, timetable: "TimetableType") -> int: 

24 """Get the total cost of the resource.""" 

25 calendar = timetable.get_calendar(self.calendar) 

26 if calendar is None: 

27 return 0 

28 return self.cost_per_hour * calendar.total_hours 

29 

30 def can_safely_be_removed(self, timetable: "TimetableType") -> bool: 

31 """Check if the resource can be removed safely. 

32 

33 A resource can be removed safely if it's assigned tasks all have 

34 other resources that can do the task. 

35 """ 

36 for task_id in self.assigned_tasks: 

37 profile = timetable.get_resource_profile(task_id) 

38 if profile is None: 

39 continue 

40 if len(profile.resource_list) <= 1: 

41 return False 

42 return True 

43 

44 def clone(self, assigned_tasks: list[str]) -> "Resource": 

45 """Clone the resource with new assigned tasks.""" 

46 base_name = self.name 

47 match = CLONE_REGEX.match(self.name) 

48 if match is not None: 

49 base_name = match.group(1) 

50 

51 new_name_id = f"{base_name}_clone_{random_string(8)}" 

52 return replace( 

53 self, 

54 id=new_name_id, 

55 name=new_name_id, 

56 calendar=f"{new_name_id}timetable", 

57 assigned_tasks=assigned_tasks, 

58 ) 

59 

60 def remove_task(self, task_id: str) -> "Resource": 

61 """Remove a task from the resource.""" 

62 return replace( 

63 self, 

64 assigned_tasks=[task for task in self.assigned_tasks if task != task_id], 

65 ) 

66 

67 def is_clone_of(self, resource: "Resource") -> bool: 

68 """Check if the resource is a clone of another resource.""" 

69 match = CLONE_REGEX.match(self.name) 

70 return match is not None and match.group(1) == resource.name