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,341 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ResourceReport - Resource list report content generator.
|
|
3
|
+
|
|
4
|
+
This module provides the ResourceReport class (equivalent to ResourceListRE
|
|
5
|
+
in Ruby) which generates a list of resources that can optionally have the
|
|
6
|
+
assigned tasks nested underneath each resource line.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Optional, List, Any
|
|
10
|
+
|
|
11
|
+
from scriptplan.report.table_report import (
|
|
12
|
+
TableReport, ReportTable, ReportTableLine, ReportTableCell, Alignment
|
|
13
|
+
)
|
|
14
|
+
from scriptplan.core.property import PropertyList
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from scriptplan.report.report import Report
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ResourceReport(TableReport):
|
|
21
|
+
"""
|
|
22
|
+
Resource list report generator.
|
|
23
|
+
|
|
24
|
+
This specialization of TableReport implements a resource listing. It
|
|
25
|
+
generates a list of resources that can optionally have the assigned
|
|
26
|
+
tasks nested underneath each resource line.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
table: The intermediate table representation
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, report: 'Report'):
|
|
33
|
+
"""
|
|
34
|
+
Initialize ResourceReport.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
report: The parent Report object
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(report)
|
|
40
|
+
self.table = ReportTable()
|
|
41
|
+
self.table.self_contained = report.get('selfContained') if report.get('selfContained') is not None else True
|
|
42
|
+
self.table.aux_dir = report.get('auxDir') or ''
|
|
43
|
+
|
|
44
|
+
def generate_intermediate_format(self) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Generate the table in the intermediate format.
|
|
47
|
+
|
|
48
|
+
This method prepares the resource list, optionally filters it,
|
|
49
|
+
generates the header row, and then generates a row for each resource
|
|
50
|
+
(and optionally nested tasks).
|
|
51
|
+
"""
|
|
52
|
+
super().generate_intermediate_format()
|
|
53
|
+
|
|
54
|
+
# Prepare the resource list
|
|
55
|
+
resource_list = self._prepare_resource_list()
|
|
56
|
+
|
|
57
|
+
# Prepare the task list (for nested tasks under resources)
|
|
58
|
+
task_list = self._prepare_task_list()
|
|
59
|
+
|
|
60
|
+
# Generate table header
|
|
61
|
+
columns = self.a('columns') or []
|
|
62
|
+
self._generate_header(columns)
|
|
63
|
+
|
|
64
|
+
# Generate resource list with optional nested tasks
|
|
65
|
+
self._generate_resource_list(resource_list, task_list, columns)
|
|
66
|
+
|
|
67
|
+
def _prepare_resource_list(self) -> PropertyList:
|
|
68
|
+
"""
|
|
69
|
+
Prepare and filter the resource list.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Filtered and sorted PropertyList of resources
|
|
73
|
+
"""
|
|
74
|
+
resource_list = PropertyList(self.project.resources)
|
|
75
|
+
|
|
76
|
+
# Include adopted resources
|
|
77
|
+
if hasattr(resource_list, 'includeAdopted'):
|
|
78
|
+
resource_list.includeAdopted()
|
|
79
|
+
|
|
80
|
+
# Apply sorting
|
|
81
|
+
sort_resources = self.a('sortResources')
|
|
82
|
+
if sort_resources:
|
|
83
|
+
resource_list.setSorting(sort_resources)
|
|
84
|
+
|
|
85
|
+
# Set query for sorting
|
|
86
|
+
if self.project.reportContexts:
|
|
87
|
+
resource_list.query = self.project.reportContexts[-1].query
|
|
88
|
+
|
|
89
|
+
# Filter the list
|
|
90
|
+
resource_list = self.filter_resource_list(
|
|
91
|
+
resource_list,
|
|
92
|
+
task=None,
|
|
93
|
+
hide_expr=self.a('hideResource'),
|
|
94
|
+
rollup_expr=self.a('rollupResource'),
|
|
95
|
+
open_nodes=self.a('openNodes')
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Sort after filtering
|
|
99
|
+
self._sort_resource_list(resource_list)
|
|
100
|
+
|
|
101
|
+
return resource_list
|
|
102
|
+
|
|
103
|
+
def _prepare_task_list(self) -> PropertyList:
|
|
104
|
+
"""
|
|
105
|
+
Prepare the task list for nested display.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Sorted PropertyList of tasks
|
|
109
|
+
"""
|
|
110
|
+
task_list = PropertyList(self.project.tasks)
|
|
111
|
+
|
|
112
|
+
for task in self.project.tasks:
|
|
113
|
+
task_list.append(task)
|
|
114
|
+
|
|
115
|
+
sort_tasks = self.a('sortTasks')
|
|
116
|
+
if sort_tasks:
|
|
117
|
+
task_list.setSorting(sort_tasks)
|
|
118
|
+
|
|
119
|
+
if self.project.reportContexts:
|
|
120
|
+
task_list.query = self.project.reportContexts[-1].query
|
|
121
|
+
|
|
122
|
+
self._sort_task_list(task_list)
|
|
123
|
+
|
|
124
|
+
return task_list
|
|
125
|
+
|
|
126
|
+
def _sort_resource_list(self, resource_list: PropertyList) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Sort the resource list according to report settings.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
resource_list: The list to sort
|
|
132
|
+
"""
|
|
133
|
+
# Use PropertyList's built-in sorting which sorts by seqno by default
|
|
134
|
+
resource_list.sort()
|
|
135
|
+
|
|
136
|
+
def _sort_task_list(self, task_list: PropertyList) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Sort the task list according to report settings.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
task_list: The list to sort
|
|
142
|
+
"""
|
|
143
|
+
# Use PropertyList's built-in sorting which sorts by seqno by default
|
|
144
|
+
task_list.sort()
|
|
145
|
+
|
|
146
|
+
def _generate_header(self, columns: List[Any]) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Generate the table header row.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
columns: List of column definitions
|
|
152
|
+
"""
|
|
153
|
+
header_line = ReportTableLine()
|
|
154
|
+
|
|
155
|
+
for column_def in columns:
|
|
156
|
+
cell = self.generate_header_cell(column_def)
|
|
157
|
+
header_line.add_cell(cell)
|
|
158
|
+
|
|
159
|
+
self.table.add_header_line(header_line)
|
|
160
|
+
|
|
161
|
+
def _generate_resource_list(self, resource_list: PropertyList,
|
|
162
|
+
task_list: PropertyList,
|
|
163
|
+
columns: List[Any]) -> None:
|
|
164
|
+
"""
|
|
165
|
+
Generate rows for each resource in the list.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
resource_list: List of resources to display
|
|
169
|
+
task_list: List of tasks (for nested display)
|
|
170
|
+
columns: Column definitions
|
|
171
|
+
"""
|
|
172
|
+
scenario_indices = self.get_scenario_indices()
|
|
173
|
+
scenario_idx = scenario_indices[0] if scenario_indices else 0
|
|
174
|
+
|
|
175
|
+
for resource in resource_list:
|
|
176
|
+
# Generate resource row
|
|
177
|
+
resource_line = self._generate_resource_line(
|
|
178
|
+
resource, columns, scenario_idx
|
|
179
|
+
)
|
|
180
|
+
self.table.add_body_line(resource_line)
|
|
181
|
+
|
|
182
|
+
# Optionally generate nested task rows
|
|
183
|
+
if self._should_show_tasks():
|
|
184
|
+
nested_tasks = self._get_tasks_for_resource(
|
|
185
|
+
resource, task_list, scenario_idx
|
|
186
|
+
)
|
|
187
|
+
for task in nested_tasks:
|
|
188
|
+
task_line = self._generate_task_line(
|
|
189
|
+
task, resource, columns, scenario_idx
|
|
190
|
+
)
|
|
191
|
+
task_line.css_class = 'nested_task'
|
|
192
|
+
self.table.add_body_line(task_line)
|
|
193
|
+
|
|
194
|
+
def _generate_resource_line(self, resource: Any, columns: List[Any],
|
|
195
|
+
scenario_idx: int) -> ReportTableLine:
|
|
196
|
+
"""
|
|
197
|
+
Generate a table row for a resource.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
resource: The resource property
|
|
201
|
+
columns: Column definitions
|
|
202
|
+
scenario_idx: Scenario index
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
ReportTableLine for the resource
|
|
206
|
+
"""
|
|
207
|
+
line = ReportTableLine(resource, scenario_idx)
|
|
208
|
+
line.css_class = 'resource_row'
|
|
209
|
+
|
|
210
|
+
for column_def in columns:
|
|
211
|
+
cell = self._generate_resource_cell(resource, column_def, scenario_idx)
|
|
212
|
+
line.add_cell(cell)
|
|
213
|
+
|
|
214
|
+
return line
|
|
215
|
+
|
|
216
|
+
def _generate_resource_cell(self, resource: Any, column_def: Any,
|
|
217
|
+
scenario_idx: int) -> ReportTableCell:
|
|
218
|
+
"""
|
|
219
|
+
Generate a cell for a resource column.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
resource: The resource property
|
|
223
|
+
column_def: Column definition
|
|
224
|
+
scenario_idx: Scenario index
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
ReportTableCell for the resource column
|
|
228
|
+
"""
|
|
229
|
+
column_id = column_def.id if hasattr(column_def, 'id') else str(column_def)
|
|
230
|
+
|
|
231
|
+
# Handle special columns
|
|
232
|
+
if column_id == 'chart':
|
|
233
|
+
return self._generate_load_chart_cell(resource, column_def, scenario_idx)
|
|
234
|
+
elif column_id in ('hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly'):
|
|
235
|
+
return self._generate_calendar_cell(resource, column_def, scenario_idx)
|
|
236
|
+
|
|
237
|
+
# Standard cell generation
|
|
238
|
+
return self.generate_cell(resource, column_def, scenario_idx)
|
|
239
|
+
|
|
240
|
+
def _generate_load_chart_cell(self, resource: Any, column_def: Any,
|
|
241
|
+
scenario_idx: int) -> ReportTableCell:
|
|
242
|
+
"""
|
|
243
|
+
Generate a load chart cell for a resource.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
resource: The resource property
|
|
247
|
+
column_def: Column definition
|
|
248
|
+
scenario_idx: Scenario index
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
ReportTableCell with load chart representation
|
|
252
|
+
"""
|
|
253
|
+
# Simplified - show efficiency or FTE for now
|
|
254
|
+
efficiency = resource.get('efficiency', scenario_idx) if hasattr(resource, 'get') else 1.0
|
|
255
|
+
text = f'{efficiency:.0%}' if efficiency else ''
|
|
256
|
+
|
|
257
|
+
return ReportTableCell(text=text, alignment=Alignment.RIGHT)
|
|
258
|
+
|
|
259
|
+
def _generate_calendar_cell(self, resource: Any, column_def: Any,
|
|
260
|
+
scenario_idx: int) -> ReportTableCell:
|
|
261
|
+
"""
|
|
262
|
+
Generate a calendar column cell for a resource.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
resource: The resource property
|
|
266
|
+
column_def: Column definition
|
|
267
|
+
scenario_idx: Scenario index
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
ReportTableCell with calendar data
|
|
271
|
+
"""
|
|
272
|
+
# Placeholder - would show availability per time period
|
|
273
|
+
return ReportTableCell(text='', alignment=Alignment.RIGHT)
|
|
274
|
+
|
|
275
|
+
def _should_show_tasks(self) -> bool:
|
|
276
|
+
"""
|
|
277
|
+
Check if tasks should be shown nested under resources.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
True if tasks should be nested
|
|
281
|
+
"""
|
|
282
|
+
return self.a('showTasks') or False
|
|
283
|
+
|
|
284
|
+
def _get_tasks_for_resource(self, resource: Any, task_list: PropertyList,
|
|
285
|
+
scenario_idx: int) -> List[Any]:
|
|
286
|
+
"""
|
|
287
|
+
Get tasks assigned to a resource.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
resource: The resource
|
|
291
|
+
task_list: All tasks
|
|
292
|
+
scenario_idx: Scenario index
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
List of tasks assigned to the resource
|
|
296
|
+
"""
|
|
297
|
+
result = []
|
|
298
|
+
start = self.a('start')
|
|
299
|
+
end = self.a('end')
|
|
300
|
+
|
|
301
|
+
for task in task_list:
|
|
302
|
+
if hasattr(task, 'hasResourceAllocated'):
|
|
303
|
+
if task.hasResourceAllocated(scenario_idx, (start, end), resource):
|
|
304
|
+
result.append(task)
|
|
305
|
+
|
|
306
|
+
return result
|
|
307
|
+
|
|
308
|
+
def _generate_task_line(self, task: Any, resource: Any,
|
|
309
|
+
columns: List[Any],
|
|
310
|
+
scenario_idx: int) -> ReportTableLine:
|
|
311
|
+
"""
|
|
312
|
+
Generate a nested task row under a resource.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
task: The task
|
|
316
|
+
resource: The parent resource
|
|
317
|
+
columns: Column definitions
|
|
318
|
+
scenario_idx: Scenario index
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
ReportTableLine for the nested task
|
|
322
|
+
"""
|
|
323
|
+
line = ReportTableLine(task, scenario_idx)
|
|
324
|
+
|
|
325
|
+
for column_def in columns:
|
|
326
|
+
col_id = column_def.id if hasattr(column_def, 'id') else str(column_def)
|
|
327
|
+
|
|
328
|
+
if col_id == 'name':
|
|
329
|
+
# Indent the task name
|
|
330
|
+
name = task.get('name') if hasattr(task, 'get') else str(task)
|
|
331
|
+
cell = ReportTableCell(
|
|
332
|
+
text=name,
|
|
333
|
+
alignment=Alignment.LEFT,
|
|
334
|
+
indent=1 # Extra indent for nested
|
|
335
|
+
)
|
|
336
|
+
else:
|
|
337
|
+
cell = self.generate_cell(task, column_def, scenario_idx)
|
|
338
|
+
|
|
339
|
+
line.add_cell(cell)
|
|
340
|
+
|
|
341
|
+
return line
|