scriptplan 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. scriptplan/__init__.py +22 -0
  2. scriptplan/cli/__init__.py +7 -0
  3. scriptplan/cli/main.py +546 -0
  4. scriptplan/core/__init__.py +0 -0
  5. scriptplan/core/account.py +125 -0
  6. scriptplan/core/allocation.py +69 -0
  7. scriptplan/core/booking.py +39 -0
  8. scriptplan/core/journal.py +377 -0
  9. scriptplan/core/leave.py +14 -0
  10. scriptplan/core/limits.py +354 -0
  11. scriptplan/core/project.py +924 -0
  12. scriptplan/core/property.py +1290 -0
  13. scriptplan/core/resource.py +198 -0
  14. scriptplan/core/resource_scenario.py +711 -0
  15. scriptplan/core/scenario.py +5 -0
  16. scriptplan/core/scenario_data.py +39 -0
  17. scriptplan/core/shift.py +71 -0
  18. scriptplan/core/task.py +77 -0
  19. scriptplan/core/task_scenario.py +1515 -0
  20. scriptplan/core/timesheet.py +457 -0
  21. scriptplan/core/working_hours.py +231 -0
  22. scriptplan/parser/__init__.py +0 -0
  23. scriptplan/parser/macro_processor.py +264 -0
  24. scriptplan/parser/tjp.lark +412 -0
  25. scriptplan/parser/tjp_parser.py +1904 -0
  26. scriptplan/py.typed +0 -0
  27. scriptplan/report/__init__.py +75 -0
  28. scriptplan/report/html_generator.py +477 -0
  29. scriptplan/report/report.py +466 -0
  30. scriptplan/report/report_base.py +397 -0
  31. scriptplan/report/report_context.py +248 -0
  32. scriptplan/report/resource_report.py +341 -0
  33. scriptplan/report/table_report.py +693 -0
  34. scriptplan/report/task_report.py +362 -0
  35. scriptplan/report/text_report.py +172 -0
  36. scriptplan/scheduler/__init__.py +0 -0
  37. scriptplan/scheduler/batch_processor.py +238 -0
  38. scriptplan/scheduler/scoreboard.py +120 -0
  39. scriptplan/utils/__init__.py +0 -0
  40. scriptplan/utils/data_cache.py +46 -0
  41. scriptplan/utils/logger.py +243 -0
  42. scriptplan/utils/message_handler.py +515 -0
  43. scriptplan/utils/time.py +195 -0
  44. scriptplan-0.9.0.dist-info/METADATA +161 -0
  45. scriptplan-0.9.0.dist-info/RECORD +49 -0
  46. scriptplan-0.9.0.dist-info/WHEEL +5 -0
  47. scriptplan-0.9.0.dist-info/entry_points.txt +2 -0
  48. scriptplan-0.9.0.dist-info/licenses/LICENSE +201 -0
  49. scriptplan-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,198 @@
1
+ """
2
+ Resource - Represents a resource in the project.
3
+
4
+ This module implements the Resource class which represents any kind of
5
+ resource that can be allocated to tasks (people, equipment, etc.).
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Optional, List, Any
9
+
10
+ from scriptplan.core.property import PropertyTreeNode
11
+ from scriptplan.core.resource_scenario import ResourceScenario
12
+
13
+ if TYPE_CHECKING:
14
+ from scriptplan.core.project import Project
15
+
16
+
17
+ class Resource(PropertyTreeNode):
18
+ """
19
+ Represents a resource in the project.
20
+
21
+ A Resource is a PropertyTreeNode that can be allocated to tasks.
22
+ Resources can be organized hierarchically (e.g., teams containing
23
+ individual team members).
24
+
25
+ Attributes:
26
+ data: List of ResourceScenario objects, one per scenario
27
+ """
28
+
29
+ def __init__(self, project: 'Project', id: str, name: str,
30
+ parent: Optional['Resource'] = None):
31
+ """
32
+ Create a new Resource.
33
+
34
+ Args:
35
+ project: The Project this resource belongs to
36
+ id: Unique identifier for the resource
37
+ name: Display name of the resource
38
+ parent: Optional parent resource (for hierarchical organization)
39
+ """
40
+ super().__init__(project.resources, id, name, parent)
41
+
42
+ # Register with project
43
+ if hasattr(project, 'addResource'):
44
+ project.addResource(self)
45
+
46
+ # Initialize scenario data
47
+ scenario_count = project.scenarioCount() if hasattr(project, 'scenarioCount') else 1
48
+ self.data = [None] * scenario_count
49
+
50
+ for i in range(scenario_count):
51
+ ResourceScenario(self, i, self._scenarioAttributes[i])
52
+
53
+ def book(self, scenario_idx: int, sb_idx: int, task: Any) -> bool:
54
+ """
55
+ Book a time slot for a task.
56
+
57
+ This is a shortcut to avoid slower calls via __getattr__.
58
+
59
+ Args:
60
+ scenario_idx: The scenario index
61
+ sb_idx: The scoreboard index (time slot)
62
+ task: The task to book
63
+
64
+ Returns:
65
+ True if booking succeeded, False otherwise
66
+ """
67
+ if self.data[scenario_idx]:
68
+ return self.data[scenario_idx].book(sb_idx, task)
69
+ return False
70
+
71
+ def __getattr__(self, name: str):
72
+ """
73
+ Forward unknown method calls to ResourceScenario.
74
+
75
+ Many Resource functions are scenario specific. These functions are
76
+ provided by the class ResourceScenario. In case we can't find a
77
+ function called for the Resource class we try to find it in
78
+ ResourceScenario.
79
+
80
+ Args:
81
+ name: The method name
82
+
83
+ Returns:
84
+ A callable that forwards to ResourceScenario
85
+ """
86
+ # Avoid infinite recursion for special attributes
87
+ if name.startswith('_') or name == 'data':
88
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
89
+
90
+ def method_forwarder(scenario_idx: int = 0, *args, **kwargs):
91
+ if self.data and self.data[scenario_idx]:
92
+ method = getattr(self.data[scenario_idx], name, None)
93
+ if method and callable(method):
94
+ return method(*args, **kwargs)
95
+ raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
96
+
97
+ return method_forwarder
98
+
99
+ def prepareScheduling(self, scenario_idx: int) -> None:
100
+ """
101
+ Prepare the resource for scheduling.
102
+
103
+ Args:
104
+ scenario_idx: The scenario index
105
+ """
106
+ if self.data[scenario_idx]:
107
+ self.data[scenario_idx].prepareScheduling()
108
+
109
+ def finishScheduling(self, scenario_idx: int) -> None:
110
+ """
111
+ Finish scheduling for this resource.
112
+
113
+ This method does housekeeping work after scheduling is completed.
114
+ It's meant to be called for top-level resources and then recursively
115
+ descends into all child resources.
116
+
117
+ Args:
118
+ scenario_idx: The scenario index
119
+ """
120
+ # Recursively descend into all child resources
121
+ for child in self.children:
122
+ child.finishScheduling(scenario_idx)
123
+
124
+ if self.data[scenario_idx]:
125
+ self.data[scenario_idx].finishScheduling()
126
+
127
+ def bookedEffort(self, scenario_idx: int) -> float:
128
+ """
129
+ Get the booked effort for this resource.
130
+
131
+ Args:
132
+ scenario_idx: The scenario index
133
+
134
+ Returns:
135
+ The booked effort value
136
+ """
137
+ if self.data[scenario_idx]:
138
+ return self.data[scenario_idx].bookedEffort()
139
+ return 0.0
140
+
141
+ def query_dashboard(self, query: Any) -> None:
142
+ """
143
+ Handle dashboard query.
144
+
145
+ Args:
146
+ query: The query object
147
+ """
148
+ self.dashboard(query)
149
+
150
+ def dashboard(self, query: Any) -> None:
151
+ """
152
+ Create a dashboard-like list of all tasks that have a current alert status.
153
+
154
+ Args:
155
+ query: The query object
156
+ """
157
+ scenario_idx = self.project.attributes.get('trackingScenarioIdx')
158
+ task_list = []
159
+
160
+ if scenario_idx is None:
161
+ r_text = "No 'trackingscenario' defined."
162
+ else:
163
+ journal = self.project.attributes.get('journal')
164
+ for task in self.project.tasks:
165
+ responsible = task.get('responsible', scenario_idx) or []
166
+ if self in responsible:
167
+ # Check for current entries
168
+ if journal:
169
+ entries = [] # journal.currentEntries(...)
170
+ if entries:
171
+ task_list.append(task)
172
+
173
+ if not task_list:
174
+ r_text = (f"We have no current status for any task that {self.name} "
175
+ "is responsible for.")
176
+ else:
177
+ r_text = ''
178
+ for task in task_list:
179
+ # Build rich text output
180
+ r_text += f"=== [{task.fullId}] Task: {task.name} ===\n\n"
181
+
182
+ # Set query result
183
+ if hasattr(query, 'rti'):
184
+ query.rti = r_text
185
+
186
+ def scenario(self, scenario_idx: int) -> Optional['ResourceScenario']:
187
+ """
188
+ Get the ResourceScenario for a given scenario index.
189
+
190
+ Args:
191
+ scenario_idx: The scenario index
192
+
193
+ Returns:
194
+ The ResourceScenario or None
195
+ """
196
+ if self.data and 0 <= scenario_idx < len(self.data):
197
+ return self.data[scenario_idx]
198
+ return None