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
« 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
4from dataclass_wizard import JSONWizard
6from o2.util.helper import CLONE_REGEX, random_string
8if TYPE_CHECKING:
9 from o2.models.timetable.timetable_type import TimetableType
12@dataclass(frozen=True)
13class Resource(JSONWizard):
14 """Represents a resource such as a person, machine, or other entity that performs tasks."""
16 id: str
17 name: str
18 cost_per_hour: int
19 amount: int
20 calendar: str
21 assigned_tasks: list[str]
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
30 def can_safely_be_removed(self, timetable: "TimetableType") -> bool:
31 """Check if the resource can be removed safely.
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
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)
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 )
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 )
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