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,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