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.
- scriptplan/__init__.py +22 -0
- scriptplan/cli/__init__.py +7 -0
- scriptplan/cli/main.py +546 -0
- scriptplan/core/__init__.py +0 -0
- scriptplan/core/account.py +125 -0
- scriptplan/core/allocation.py +69 -0
- scriptplan/core/booking.py +39 -0
- scriptplan/core/journal.py +377 -0
- scriptplan/core/leave.py +14 -0
- scriptplan/core/limits.py +354 -0
- scriptplan/core/project.py +924 -0
- scriptplan/core/property.py +1290 -0
- scriptplan/core/resource.py +198 -0
- scriptplan/core/resource_scenario.py +711 -0
- scriptplan/core/scenario.py +5 -0
- scriptplan/core/scenario_data.py +39 -0
- scriptplan/core/shift.py +71 -0
- scriptplan/core/task.py +77 -0
- scriptplan/core/task_scenario.py +1515 -0
- scriptplan/core/timesheet.py +457 -0
- scriptplan/core/working_hours.py +231 -0
- scriptplan/parser/__init__.py +0 -0
- scriptplan/parser/macro_processor.py +264 -0
- scriptplan/parser/tjp.lark +412 -0
- scriptplan/parser/tjp_parser.py +1904 -0
- scriptplan/py.typed +0 -0
- scriptplan/report/__init__.py +75 -0
- scriptplan/report/html_generator.py +477 -0
- scriptplan/report/report.py +466 -0
- scriptplan/report/report_base.py +397 -0
- scriptplan/report/report_context.py +248 -0
- scriptplan/report/resource_report.py +341 -0
- scriptplan/report/table_report.py +693 -0
- scriptplan/report/task_report.py +362 -0
- scriptplan/report/text_report.py +172 -0
- scriptplan/scheduler/__init__.py +0 -0
- scriptplan/scheduler/batch_processor.py +238 -0
- scriptplan/scheduler/scoreboard.py +120 -0
- scriptplan/utils/__init__.py +0 -0
- scriptplan/utils/data_cache.py +46 -0
- scriptplan/utils/logger.py +243 -0
- scriptplan/utils/message_handler.py +515 -0
- scriptplan/utils/time.py +195 -0
- scriptplan-0.9.0.dist-info/METADATA +161 -0
- scriptplan-0.9.0.dist-info/RECORD +49 -0
- scriptplan-0.9.0.dist-info/WHEEL +5 -0
- scriptplan-0.9.0.dist-info/entry_points.txt +2 -0
- scriptplan-0.9.0.dist-info/licenses/LICENSE +201 -0
- 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
|