scriptplan 0.9.1__tar.gz → 0.10.0__tar.gz
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-0.10.0/CHANGELOG.md +118 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/MANIFEST.in +1 -0
- {scriptplan-0.9.1/scriptplan.egg-info → scriptplan-0.10.0}/PKG-INFO +72 -35
- scriptplan-0.10.0/README.md +237 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/pyproject.toml +9 -2
- scriptplan-0.10.0/scriptplan/__init__.py +29 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/cli/__init__.py +1 -1
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/cli/main.py +72 -121
- scriptplan-0.10.0/scriptplan/cli/plan.py +380 -0
- scriptplan-0.10.0/scriptplan/cli/plan_files.py +195 -0
- scriptplan-0.10.0/scriptplan/cli/plan_help.py +194 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/account.py +21 -32
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/allocation.py +3 -3
- scriptplan-0.10.0/scriptplan/core/allocation_plan.py +171 -0
- scriptplan-0.10.0/scriptplan/core/attributes.py +464 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/booking.py +2 -7
- scriptplan-0.10.0/scriptplan/core/exceptions.py +319 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/journal.py +61 -40
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/leave.py +7 -7
- scriptplan-0.10.0/scriptplan/core/limits.py +335 -0
- scriptplan-0.10.0/scriptplan/core/project.py +519 -0
- scriptplan-0.10.0/scriptplan/core/project_attributes.py +212 -0
- scriptplan-0.10.0/scriptplan/core/project_calendar.py +126 -0
- scriptplan-0.10.0/scriptplan/core/project_tree.py +256 -0
- scriptplan-0.10.0/scriptplan/core/property.py +100 -0
- scriptplan-0.10.0/scriptplan/core/property_list.py +358 -0
- scriptplan-0.10.0/scriptplan/core/property_set.py +338 -0
- scriptplan-0.10.0/scriptplan/core/property_tree_node.py +334 -0
- scriptplan-0.10.0/scriptplan/core/resolution.py +95 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/resource.py +11 -18
- scriptplan-0.10.0/scriptplan/core/resource_availability.py +187 -0
- scriptplan-0.10.0/scriptplan/core/resource_reporting.py +311 -0
- scriptplan-0.10.0/scriptplan/core/resource_scenario.py +436 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/scenario.py +1 -1
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/scenario_data.py +9 -13
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/shift.py +6 -12
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/task.py +5 -11
- scriptplan-0.10.0/scriptplan/core/task_booking.py +320 -0
- scriptplan-0.10.0/scriptplan/core/task_dependencies.py +267 -0
- scriptplan-0.10.0/scriptplan/core/task_placement.py +415 -0
- scriptplan-0.10.0/scriptplan/core/task_resources.py +335 -0
- scriptplan-0.10.0/scriptplan/core/task_scenario.py +424 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/timesheet.py +91 -85
- scriptplan-0.10.0/scriptplan/core/timezones.py +59 -0
- scriptplan-0.10.0/scriptplan/core/units.py +190 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/working_hours.py +86 -58
- scriptplan-0.10.0/scriptplan/parser/attribute_apply.py +302 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/parser/macro_processor.py +34 -33
- scriptplan-0.10.0/scriptplan/parser/model_pieces.py +227 -0
- scriptplan-0.10.0/scriptplan/parser/report_builder.py +223 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/parser/tjp.lark +29 -6
- scriptplan-0.10.0/scriptplan/parser/tjp_parser.py +416 -0
- scriptplan-0.10.0/scriptplan/parser/transformer_project.py +193 -0
- scriptplan-0.10.0/scriptplan/parser/transformer_report.py +285 -0
- scriptplan-0.10.0/scriptplan/parser/transformer_resource.py +224 -0
- scriptplan-0.10.0/scriptplan/parser/transformer_task.py +339 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/report/__init__.py +17 -17
- scriptplan-0.10.0/scriptplan/report/logical_expression.py +392 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/report/report.py +102 -60
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/report/report_base.py +88 -87
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/report/report_context.py +26 -25
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/report/resource_report.py +39 -52
- scriptplan-0.10.0/scriptplan/report/table_cells.py +165 -0
- scriptplan-0.10.0/scriptplan/report/table_headers.py +69 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/report/table_report.py +124 -257
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/report/task_report.py +52 -64
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/report/text_report.py +34 -34
- scriptplan-0.10.0/scriptplan/scheduler/postcheck.py +106 -0
- scriptplan-0.10.0/scriptplan/scheduler/preflight.py +121 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/scheduler/scoreboard.py +2 -11
- scriptplan-0.10.0/scriptplan/utils/data_cache.py +9 -0
- scriptplan-0.10.0/scriptplan/utils/logger.py +39 -0
- scriptplan-0.10.0/scriptplan/utils/message_handler.py +461 -0
- scriptplan-0.10.0/scriptplan/utils/messages.py +192 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/utils/time.py +30 -28
- {scriptplan-0.9.1 → scriptplan-0.10.0/scriptplan.egg-info}/PKG-INFO +72 -35
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan.egg-info/SOURCES.txt +40 -3
- scriptplan-0.10.0/setup.py +17 -0
- scriptplan-0.10.0/tests/test_ansi_color.py +24 -0
- scriptplan-0.10.0/tests/test_audit_probes.py +78 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_core_resource.py +1 -1
- scriptplan-0.10.0/tests/test_fixture_golden.py +63 -0
- scriptplan-0.10.0/tests/test_plan_cli.py +139 -0
- scriptplan-0.10.0/tests/test_project_isolation.py +89 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_property_set.py +4 -2
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_report.py +2 -2
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_scheduling.py +3 -3
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_scheduling_validation.py +130 -210
- scriptplan-0.9.1/README.md +0 -201
- scriptplan-0.9.1/scriptplan/__init__.py +0 -22
- scriptplan-0.9.1/scriptplan/cli/plan.py +0 -678
- scriptplan-0.9.1/scriptplan/core/limits.py +0 -376
- scriptplan-0.9.1/scriptplan/core/project.py +0 -936
- scriptplan-0.9.1/scriptplan/core/property.py +0 -1292
- scriptplan-0.9.1/scriptplan/core/resource_scenario.py +0 -709
- scriptplan-0.9.1/scriptplan/core/task_scenario.py +0 -1551
- scriptplan-0.9.1/scriptplan/parser/tjp_parser.py +0 -1903
- scriptplan-0.9.1/scriptplan/scheduler/batch_processor.py +0 -260
- scriptplan-0.9.1/scriptplan/utils/data_cache.py +0 -50
- scriptplan-0.9.1/scriptplan/utils/logger.py +0 -245
- scriptplan-0.9.1/scriptplan/utils/message_handler.py +0 -535
- scriptplan-0.9.1/tests/test_batch_processor.py +0 -118
- scriptplan-0.9.1/tests/test_logger.py +0 -148
- {scriptplan-0.9.1 → scriptplan-0.10.0}/LICENSE +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/core/__init__.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/parser/__init__.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/py.typed +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/scheduler/__init__.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan/utils/__init__.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan.egg-info/dependency_links.txt +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan.egg-info/entry_points.txt +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan.egg-info/requires.txt +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/scriptplan.egg-info/top_level.txt +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/setup.cfg +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_account.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_attribute_base.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_cli.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_core_allocation.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_core_booking.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_core_project.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_core_scenario.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_core_task.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_journal.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_macro_processor.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_message_handler.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_parser.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_property_list.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_property_tree_node.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_scoreboard.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_shift.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_timesheet.py +0 -0
- {scriptplan-0.9.1 → scriptplan-0.10.0}/tests/test_utils_time.py +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
A readiness audit of 0.9.2 turned every documented claim into a probe that
|
|
6
|
+
drives ScriptPlan through its own API or CLI. 32 of those probes failed. This
|
|
7
|
+
release is what it took to make them pass, and it changes scheduling results.
|
|
8
|
+
|
|
9
|
+
### Breaking: `effort` is person-time
|
|
10
|
+
|
|
11
|
+
`effort 16h allocate a, b` used to occupy both resources for sixteen hours and
|
|
12
|
+
credit sixteen hours of work. Two resources booked thirty-two hours, four booked
|
|
13
|
+
sixty-four, and the completion date never moved however many were assigned. The
|
|
14
|
+
engine took the largest contribution in each slot rather than the sum, so the
|
|
15
|
+
labour of every resource but one was booked, billed and discarded.
|
|
16
|
+
|
|
17
|
+
Effort is now the amount of work to be done, and each allocated resource
|
|
18
|
+
contributes its own time towards it, as TaskJuggler defines it. The same task
|
|
19
|
+
with two resources finishes in half the time and costs what one resource's worth
|
|
20
|
+
of work costs.
|
|
21
|
+
|
|
22
|
+
**Upgrading.** A task whose allocations were meant to work side by side is
|
|
23
|
+
written with `mandatory` and its effort in person-hours:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
# before 0.10.0: four hours of a pair working together
|
|
27
|
+
task deploy "Deploy" { effort 4h allocate dev, qa }
|
|
28
|
+
|
|
29
|
+
# 0.10.0 and after
|
|
30
|
+
task deploy "Deploy" { effort 8h allocate dev { mandatory }, qa { mandatory } }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Tasks with a single allocation are unaffected. Of the 24 example projects, 21
|
|
34
|
+
schedule identically to 0.9.2; the three that differ are those that allocate
|
|
35
|
+
more than one resource to a task.
|
|
36
|
+
|
|
37
|
+
### Breaking: failures are reported as failures
|
|
38
|
+
|
|
39
|
+
`Project.schedule()` always returned `True`, and the CLI exited 0 with empty
|
|
40
|
+
start and end cells. A circular dependency, an effort task with no allocation,
|
|
41
|
+
or a task that did not fit the project window produced a warning nobody saw.
|
|
42
|
+
|
|
43
|
+
Scheduling now returns the real result and names each failing task and why; the
|
|
44
|
+
CLIs exit non-zero. A dependency cycle is rejected before scheduling, naming the
|
|
45
|
+
cycle. An attribute or reference the parser cannot place is an error rather than
|
|
46
|
+
a silent drop, which is how `duration`, `length`, `complete` and `note` reached
|
|
47
|
+
the model for the first time.
|
|
48
|
+
|
|
49
|
+
### Breaking: the declared project period is honoured
|
|
50
|
+
|
|
51
|
+
The project end was grown by a heuristic before scheduling. It is now taken as
|
|
52
|
+
written, and a task that does not fit is reported. Projects that relied on the
|
|
53
|
+
extension need their period stated honestly.
|
|
54
|
+
|
|
55
|
+
### Breaking: unimplemented report formats are rejected
|
|
56
|
+
|
|
57
|
+
`ical`, `tjp`, `niku`, `ctags` and MS Project XML were accepted, wrote no file,
|
|
58
|
+
and left the CLI blaming a scheduling problem that did not exist. They now raise
|
|
59
|
+
at parse time. Use `json` or `csv`.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- Durations follow one table: `m` is months and `min` is minutes everywhere;
|
|
64
|
+
`duration` and `gapduration` are calendar time, `effort`, `length` and
|
|
65
|
+
`gaplength` working time derived from `dailyworkinghours` and
|
|
66
|
+
`yearlyworkingdays`. `gapduration 2d` was 16 hours; `gaplength 30min` was no
|
|
67
|
+
gap at all; `limits { dailymax 30min }` was a 4800-hour cap.
|
|
68
|
+
- Limits count seconds in the resource's own calendar and admit part of a slot,
|
|
69
|
+
so `dailymax 6.4h` is exact. A resource in Los Angeles capped at four hours a
|
|
70
|
+
day was working five in one of its own days. `dailymin`, `weeklymin`,
|
|
71
|
+
`monthlymax`, `monthlymin`, `maximum` and `minimum` reach the grammar, as do
|
|
72
|
+
`maxend`, `maxstart`, `minend` and `minstart`, and violations are reported.
|
|
73
|
+
- Dates are read in the project's timezone and scheduled in UTC; reports convert
|
|
74
|
+
back. The default working day was compared against the hour of a UTC slot,
|
|
75
|
+
putting a New York project five hours out.
|
|
76
|
+
- Slot size is derived from the times a project uses, so `start 09:30` and a
|
|
77
|
+
shift of `08:13 - 11:59` are exact without setting `timingresolution`. That
|
|
78
|
+
shift was credited with seven hours a day instead of eight and a half.
|
|
79
|
+
- A working-hours interval crossing midnight applies to the following day.
|
|
80
|
+
`fri 22:00 - 06:00` marked Friday morning instead of Saturday.
|
|
81
|
+
- Chains mixing ASAP and ALAP schedule instead of deadlocking.
|
|
82
|
+
- Scheduling twice gives the same dates and the same bookings. The second run
|
|
83
|
+
kept the dates and discarded every booking.
|
|
84
|
+
- Reports honour their own `sorttasks`, `hidetask`, column options and formats,
|
|
85
|
+
and `plan report` renders the reports a project declares rather than
|
|
86
|
+
substituting a three-column one. Descending sort was a no-op, and any filter
|
|
87
|
+
hid every row.
|
|
88
|
+
- `report_id` is a SHA-256 of the rendered result. It hashed the input file, so
|
|
89
|
+
a correct and a broken schedule of the same input shared an id.
|
|
90
|
+
- `mandatory`, `persistent` and per-resource allocation options work; they
|
|
91
|
+
parsed to nothing.
|
|
92
|
+
|
|
93
|
+
### Removed
|
|
94
|
+
|
|
95
|
+
- The Cython extensions. Under `cdivision` the compiled path computed
|
|
96
|
+
`(0 - 1) % 7` as `-1`, so a resource with a Sunday night shift lost its Monday
|
|
97
|
+
morning in every published wheel and kept it in every source checkout: the
|
|
98
|
+
same file, two schedules, and no test covering the compiled one. The wheel is
|
|
99
|
+
now pure Python and cannot diverge from its source.
|
|
100
|
+
- `scheduler.batch_processor`, `utils.data_cache.DataCache` and `utils.Log`,
|
|
101
|
+
none of which were reachable.
|
|
102
|
+
|
|
103
|
+
### Performance
|
|
104
|
+
|
|
105
|
+
500 chained tasks at minute resolution schedule in 3.3s, from 14.4s. A year at
|
|
106
|
+
minute resolution with ten resources takes about 13s, from 58.7s.
|
|
107
|
+
|
|
108
|
+
### Verification
|
|
109
|
+
|
|
110
|
+
`audit/evaluations/` holds 74 probes across 12 files, each named after the
|
|
111
|
+
behaviour it would falsify and each driving ScriptPlan through its own
|
|
112
|
+
interface. `scripts/ci.sh` runs lint, types, a file-size cap, 411 tests, a
|
|
113
|
+
showcase currency check and the probes. The probes also pass against the built
|
|
114
|
+
wheel installed in a clean virtualenv, so what is verified is what ships.
|
|
115
|
+
|
|
116
|
+
## 0.9.2 and earlier
|
|
117
|
+
|
|
118
|
+
See the git history.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptplan
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
4
4
|
Summary: A precise project scheduling engine with minute-level accuracy for resource allocation and dependency management.
|
|
5
5
|
Author-email: Farshid Ashouri <farsheed.ashouri@gmail.com>
|
|
6
6
|
Maintainer-email: Farshid Ashouri <farsheed.ashouri@gmail.com>
|
|
@@ -9,6 +9,7 @@ Project-URL: Homepage, https://github.com/rodmena-limited/scriptplan
|
|
|
9
9
|
Project-URL: Documentation, https://github.com/rodmena-limited/scriptplan#readme
|
|
10
10
|
Project-URL: Bug Tracker, https://github.com/rodmena-limited/scriptplan/issues
|
|
11
11
|
Project-URL: Source, https://github.com/rodmena-limited/scriptplan
|
|
12
|
+
Project-URL: Changelog, https://github.com/rodmena-limited/scriptplan/blob/main/CHANGELOG.md
|
|
12
13
|
Keywords: scheduling,project-management,resource-allocation,gantt,critical-path
|
|
13
14
|
Classifier: Development Status :: 4 - Beta
|
|
14
15
|
Classifier: Intended Audience :: Developers
|
|
@@ -48,11 +49,10 @@ Dynamic: license-file
|
|
|
48
49
|
|
|
49
50
|
# ScriptPlan
|
|
50
51
|
|
|
51
|
-

|
|
52
|
+

|
|
53
|
+

|
|
54
|
+

|
|
55
|
+

|
|
56
56
|
|
|
57
57
|
A precise project scheduling engine with minute-level accuracy for resource allocation and dependency management. The syntax is compatible with TaskJuggler (.tjp files).
|
|
58
58
|
|
|
@@ -109,11 +109,11 @@ done
|
|
|
109
109
|
**Features**:
|
|
110
110
|
- Output to stdout (Unix philosophy)
|
|
111
111
|
- Messages to stderr
|
|
112
|
-
- SHA256 report_id
|
|
112
|
+
- SHA256 `report_id` over the rendered result, so it changes when the schedule does
|
|
113
113
|
- Lowercase column names
|
|
114
114
|
- No HTML metadata
|
|
115
115
|
- No file pollution (uses temp directories)
|
|
116
|
-
-
|
|
116
|
+
- Separate processes do not collide: temporary files and directories are unique per run
|
|
117
117
|
|
|
118
118
|
### Python API
|
|
119
119
|
|
|
@@ -131,39 +131,69 @@ for task in project.tasks:
|
|
|
131
131
|
print(f"{task.id}: {start} -> {end}")
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
-
##
|
|
134
|
+
## What is verified
|
|
135
135
|
|
|
136
|
-
|
|
136
|
+
Each behaviour below has a probe in `audit/evaluations/` that drives ScriptPlan
|
|
137
|
+
through its own API or CLI and asserts the result. They run as part of the test
|
|
138
|
+
suite, and can be run on their own against an installed release:
|
|
137
139
|
|
|
138
|
-
|
|
140
|
+
```bash
|
|
141
|
+
audit/evaluations/run_all.sh
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Time**
|
|
145
|
+
|
|
146
|
+
- Dates are read in the project's timezone and scheduled in UTC; reports convert back.
|
|
147
|
+
- Resource shifts follow their own timezone across daylight-saving changes in both directions.
|
|
148
|
+
- A working-hours interval that crosses midnight applies to the following day.
|
|
149
|
+
- Slot size is derived from the times a project uses, so `start 09:30` is honoured
|
|
150
|
+
exactly and a shift of `08:13 - 11:59, 13:07 - 17:47` yields its true 8h26m a day.
|
|
151
|
+
`timingresolution` overrides this.
|
|
139
152
|
|
|
140
|
-
|
|
153
|
+
**Units**
|
|
141
154
|
|
|
142
|
-
-
|
|
155
|
+
- Calendar time (`duration`, `gapduration`) and working time (`effort`, `length`,
|
|
156
|
+
`gaplength`) are distinguished, with working time derived from
|
|
157
|
+
`dailyworkinghours` and `yearlyworkingdays`. `m` means months; `min` means minutes.
|
|
143
158
|
|
|
144
|
-
|
|
159
|
+
**Resources**
|
|
145
160
|
|
|
146
|
-
-
|
|
161
|
+
- `effort` is person-time: two resources available in a slot contribute two
|
|
162
|
+
slots of it. `mandatory` requires a resource for the task to progress at all;
|
|
163
|
+
`persistent` keeps the one first chosen; `alternative` falls back.
|
|
164
|
+
- `dailymax`, `weeklymax`, `monthlymax`, `maximum` and their `min` counterparts
|
|
165
|
+
block at the cap **and** release in the next period. Periods are counted in the
|
|
166
|
+
resource's own calendar, and a cap smaller than one slot is honoured exactly.
|
|
147
167
|
|
|
148
|
-
|
|
168
|
+
**Scheduling**
|
|
149
169
|
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
170
|
+
- ASAP and ALAP, including chains that mix the two.
|
|
171
|
+
- Contention is resolved by priority, then criticalness, then declaration order.
|
|
172
|
+
- The declared project period is never rewritten. A task that does not fit is
|
|
173
|
+
reported, with its name and the reason.
|
|
174
|
+
- Scheduling the same project twice produces the same dates and the same bookings.
|
|
175
|
+
- `maxgapduration`, `maxgaplength`, `maxstart`, `minstart`, `maxend` and `minend`
|
|
176
|
+
are checked once the schedule is complete, and a violation is an error.
|
|
156
177
|
|
|
157
|
-
|
|
178
|
+
**Honesty**
|
|
158
179
|
|
|
159
|
-
|
|
180
|
+
- A project that cannot be scheduled exits non-zero and says why. A dependency
|
|
181
|
+
cycle is rejected before scheduling, naming the cycle.
|
|
182
|
+
- An attribute or reference the parser cannot place is an error, never a silent
|
|
183
|
+
drop.
|
|
184
|
+
- `report_id` is a SHA-256 of the rendered result, so it differs when the
|
|
185
|
+
schedule differs.
|
|
160
186
|
|
|
161
|
-
|
|
162
|
-
- Prime-number effort durations with prime-number gaps
|
|
163
|
-
- Resource contention across multi-day tasks
|
|
164
|
-
- Calendar gaps (weekends, holidays) vs working time gaps
|
|
187
|
+
## Known limits
|
|
165
188
|
|
|
166
|
-
|
|
189
|
+
- `include`, `supplement`, `leaveallowance` and `statussheet` are not implemented.
|
|
190
|
+
- Output formats are JSON and CSV. `ical`, `tjp`, `niku` and MS Project XML are
|
|
191
|
+
rejected at parse time rather than silently producing nothing.
|
|
192
|
+
- Accounts are parsed but not yet modelled, so `chargeset` and `charge` record an
|
|
193
|
+
amount without posting it anywhere.
|
|
194
|
+
- `onstart` dependencies are read as "the dependent task must finish before the
|
|
195
|
+
predecessor starts", which is ScriptPlan's own reading rather than
|
|
196
|
+
TaskJuggler's start-to-start one.
|
|
167
197
|
|
|
168
198
|
## Example Project
|
|
169
199
|
|
|
@@ -232,14 +262,21 @@ delivery.assemble_b: 2025-07-16-08:00 -> 2025-07-17-16:00
|
|
|
232
262
|
|
|
233
263
|
## License
|
|
234
264
|
|
|
235
|
-
Apache
|
|
265
|
+
Apache-2.0
|
|
266
|
+
|
|
267
|
+
## Acknowledgments
|
|
268
|
+
|
|
269
|
+
ScriptPlan references [TaskJuggler](https://taskjuggler.org/) solely for **file format compatibility**. We have not used, modified, or copied any TaskJuggler source code. ScriptPlan is an independent, clean-room implementation.
|
|
270
|
+
|
|
271
|
+
- The `.tjp` file format is documented publicly and widely used in the project management community
|
|
272
|
+
- All scheduling algorithms, parser, and report generation in ScriptPlan are original implementations
|
|
273
|
+
- TaskJuggler is mentioned only to indicate that ScriptPlan can read the same project file format
|
|
274
|
+
|
|
275
|
+
If you're looking for the original TaskJuggler with its full feature set including interactive HTML reports and GUI tools, please visit [taskjuggler.org](https://taskjuggler.org/).
|
|
236
276
|
|
|
277
|
+
## Why ScriptPlan?
|
|
237
278
|
|
|
238
|
-
|
|
239
|
-
This scheduler is part of Highway Workflow Egnine's worker capacity management system.
|
|
240
|
-
I decided to open-source it for the community to benefit from its precise scheduling capabilities.
|
|
241
|
-
Thanks to TaskJuggler's established syntax, users can easily adopt ScriptPlan without learning a new format.
|
|
242
|
-
Credits to TaskJuggler for the original project definition language and concepts.
|
|
279
|
+
This scheduler is part of Highway Workflow Engine's worker capacity management system. I decided to open-source it for the community to benefit from its precise scheduling capabilities. Thanks to TaskJuggler's established syntax, users can easily adopt ScriptPlan without learning a new format.
|
|
243
280
|
|
|
244
281
|
Yours,
|
|
245
282
|
Farshid.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/rodmena-limited/scriptplan/main/icons/logo.svg" alt="ScriptPlan" width="400"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# ScriptPlan
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
A precise project scheduling engine with minute-level accuracy for resource allocation and dependency management. The syntax is compatible with TaskJuggler (.tjp files).
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install scriptplan
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Report Generation (Unix-style)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Generate JSON report to stdout
|
|
26
|
+
plan report project.tjp
|
|
27
|
+
|
|
28
|
+
# Generate CSV report
|
|
29
|
+
plan report --csv project.tjp
|
|
30
|
+
|
|
31
|
+
# Save to file
|
|
32
|
+
plan report project.tjp > output.json
|
|
33
|
+
plan report --csv project.tjp > output.csv
|
|
34
|
+
|
|
35
|
+
# Read from stdin
|
|
36
|
+
cat project.tjp | plan report
|
|
37
|
+
plan report - < project.tjp
|
|
38
|
+
|
|
39
|
+
# Pipe to other tools
|
|
40
|
+
plan report project.tjp | jq '.data[0]'
|
|
41
|
+
plan report --csv project.tjp | csvkit
|
|
42
|
+
|
|
43
|
+
# Process multiple files
|
|
44
|
+
for f in projects/*.tjp; do
|
|
45
|
+
plan report "$f" | jq -r '.report_id'
|
|
46
|
+
done
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Output Format** (JSON):
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"data": [
|
|
53
|
+
{
|
|
54
|
+
"id": "project.task1",
|
|
55
|
+
"start": "2024-01-01-09:00",
|
|
56
|
+
"end": "2024-01-05-17:00"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"columns": ["id", "start", "end"],
|
|
60
|
+
"report_id": "ea3f901dd6426dfa58288d945819c75485fd9ff1875db59def350f219e2d62ca"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Features**:
|
|
65
|
+
- Output to stdout (Unix philosophy)
|
|
66
|
+
- Messages to stderr
|
|
67
|
+
- SHA256 `report_id` over the rendered result, so it changes when the schedule does
|
|
68
|
+
- Lowercase column names
|
|
69
|
+
- No HTML metadata
|
|
70
|
+
- No file pollution (uses temp directories)
|
|
71
|
+
- Separate processes do not collide: temporary files and directories are unique per run
|
|
72
|
+
|
|
73
|
+
### Python API
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from scriptplan.parser.tjp_parser import ProjectFileParser
|
|
77
|
+
|
|
78
|
+
parser = ProjectFileParser()
|
|
79
|
+
project = parser.parse(open('project.tjp').read())
|
|
80
|
+
|
|
81
|
+
# Access scheduled tasks
|
|
82
|
+
for task in project.tasks:
|
|
83
|
+
if task.leaf():
|
|
84
|
+
start = task.get('start', 0)
|
|
85
|
+
end = task.get('end', 0)
|
|
86
|
+
print(f"{task.id}: {start} -> {end}")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## What is verified
|
|
90
|
+
|
|
91
|
+
Each behaviour below has a probe in `audit/evaluations/` that drives ScriptPlan
|
|
92
|
+
through its own API or CLI and asserts the result. They run as part of the test
|
|
93
|
+
suite, and can be run on their own against an installed release:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
audit/evaluations/run_all.sh
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Time**
|
|
100
|
+
|
|
101
|
+
- Dates are read in the project's timezone and scheduled in UTC; reports convert back.
|
|
102
|
+
- Resource shifts follow their own timezone across daylight-saving changes in both directions.
|
|
103
|
+
- A working-hours interval that crosses midnight applies to the following day.
|
|
104
|
+
- Slot size is derived from the times a project uses, so `start 09:30` is honoured
|
|
105
|
+
exactly and a shift of `08:13 - 11:59, 13:07 - 17:47` yields its true 8h26m a day.
|
|
106
|
+
`timingresolution` overrides this.
|
|
107
|
+
|
|
108
|
+
**Units**
|
|
109
|
+
|
|
110
|
+
- Calendar time (`duration`, `gapduration`) and working time (`effort`, `length`,
|
|
111
|
+
`gaplength`) are distinguished, with working time derived from
|
|
112
|
+
`dailyworkinghours` and `yearlyworkingdays`. `m` means months; `min` means minutes.
|
|
113
|
+
|
|
114
|
+
**Resources**
|
|
115
|
+
|
|
116
|
+
- `effort` is person-time: two resources available in a slot contribute two
|
|
117
|
+
slots of it. `mandatory` requires a resource for the task to progress at all;
|
|
118
|
+
`persistent` keeps the one first chosen; `alternative` falls back.
|
|
119
|
+
- `dailymax`, `weeklymax`, `monthlymax`, `maximum` and their `min` counterparts
|
|
120
|
+
block at the cap **and** release in the next period. Periods are counted in the
|
|
121
|
+
resource's own calendar, and a cap smaller than one slot is honoured exactly.
|
|
122
|
+
|
|
123
|
+
**Scheduling**
|
|
124
|
+
|
|
125
|
+
- ASAP and ALAP, including chains that mix the two.
|
|
126
|
+
- Contention is resolved by priority, then criticalness, then declaration order.
|
|
127
|
+
- The declared project period is never rewritten. A task that does not fit is
|
|
128
|
+
reported, with its name and the reason.
|
|
129
|
+
- Scheduling the same project twice produces the same dates and the same bookings.
|
|
130
|
+
- `maxgapduration`, `maxgaplength`, `maxstart`, `minstart`, `maxend` and `minend`
|
|
131
|
+
are checked once the schedule is complete, and a violation is an error.
|
|
132
|
+
|
|
133
|
+
**Honesty**
|
|
134
|
+
|
|
135
|
+
- A project that cannot be scheduled exits non-zero and says why. A dependency
|
|
136
|
+
cycle is rejected before scheduling, naming the cycle.
|
|
137
|
+
- An attribute or reference the parser cannot place is an error, never a silent
|
|
138
|
+
drop.
|
|
139
|
+
- `report_id` is a SHA-256 of the rendered result, so it differs when the
|
|
140
|
+
schedule differs.
|
|
141
|
+
|
|
142
|
+
## Known limits
|
|
143
|
+
|
|
144
|
+
- `include`, `supplement`, `leaveallowance` and `statussheet` are not implemented.
|
|
145
|
+
- Output formats are JSON and CSV. `ical`, `tjp`, `niku` and MS Project XML are
|
|
146
|
+
rejected at parse time rather than silently producing nothing.
|
|
147
|
+
- Accounts are parsed but not yet modelled, so `chargeset` and `charge` record an
|
|
148
|
+
amount without posting it anywhere.
|
|
149
|
+
- `onstart` dependencies are read as "the dependent task must finish before the
|
|
150
|
+
predecessor starts", which is ScriptPlan's own reading rather than
|
|
151
|
+
TaskJuggler's start-to-start one.
|
|
152
|
+
|
|
153
|
+
## Example Project
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
project "Manufacturing" 2025-07-01 +1m {
|
|
157
|
+
timezone "Etc/UTC"
|
|
158
|
+
timeformat "%Y-%m-%d %H:%M"
|
|
159
|
+
scheduling alap
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
shift factory_hours "Factory Hours" {
|
|
163
|
+
workinghours mon - fri 08:00 - 16:00
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
resource machine "Press" {
|
|
167
|
+
workinghours factory_hours
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
task delivery "Product Launch" {
|
|
171
|
+
end 2025-07-18-16:00
|
|
172
|
+
|
|
173
|
+
task pack "Packaging" {
|
|
174
|
+
effort 8h
|
|
175
|
+
allocate machine
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
task assemble_b "Body Assembly" {
|
|
179
|
+
effort 16h
|
|
180
|
+
allocate machine
|
|
181
|
+
depends !!pack { onstart }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
task assemble_a "Engine Assembly" {
|
|
185
|
+
effort 16h
|
|
186
|
+
allocate machine
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
task connection_setup "Logic" {
|
|
191
|
+
task set_deps "Apply" {
|
|
192
|
+
depends !delivery.assemble_a, !delivery.assemble_b
|
|
193
|
+
precedes !delivery.pack
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
taskreport output "output" {
|
|
198
|
+
formats csv
|
|
199
|
+
columns id, start, end
|
|
200
|
+
timeformat "%Y-%m-%d-%H:%M"
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
This project schedules backward from the delivery deadline (ALAP mode). The scheduler:
|
|
205
|
+
|
|
206
|
+
1. Anchors packaging to end at 16:00 on July 18
|
|
207
|
+
2. Schedules both assembly tasks to complete before packaging starts
|
|
208
|
+
3. Resolves resource contention (single machine) by sequencing assemblies back-to-back
|
|
209
|
+
4. Respects weekend boundaries (Mon-Fri working hours)
|
|
210
|
+
|
|
211
|
+
Result:
|
|
212
|
+
```
|
|
213
|
+
delivery.pack: 2025-07-18-08:00 -> 2025-07-18-16:00
|
|
214
|
+
delivery.assemble_a: 2025-07-14-08:00 -> 2025-07-15-16:00
|
|
215
|
+
delivery.assemble_b: 2025-07-16-08:00 -> 2025-07-17-16:00
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
Apache-2.0
|
|
221
|
+
|
|
222
|
+
## Acknowledgments
|
|
223
|
+
|
|
224
|
+
ScriptPlan references [TaskJuggler](https://taskjuggler.org/) solely for **file format compatibility**. We have not used, modified, or copied any TaskJuggler source code. ScriptPlan is an independent, clean-room implementation.
|
|
225
|
+
|
|
226
|
+
- The `.tjp` file format is documented publicly and widely used in the project management community
|
|
227
|
+
- All scheduling algorithms, parser, and report generation in ScriptPlan are original implementations
|
|
228
|
+
- TaskJuggler is mentioned only to indicate that ScriptPlan can read the same project file format
|
|
229
|
+
|
|
230
|
+
If you're looking for the original TaskJuggler with its full feature set including interactive HTML reports and GUI tools, please visit [taskjuggler.org](https://taskjuggler.org/).
|
|
231
|
+
|
|
232
|
+
## Why ScriptPlan?
|
|
233
|
+
|
|
234
|
+
This scheduler is part of Highway Workflow Engine's worker capacity management system. I decided to open-source it for the community to benefit from its precise scheduling capabilities. Thanks to TaskJuggler's established syntax, users can easily adopt ScriptPlan without learning a new format.
|
|
235
|
+
|
|
236
|
+
Yours,
|
|
237
|
+
Farshid.
|
|
@@ -4,7 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scriptplan"
|
|
7
|
-
|
|
7
|
+
# Single source of truth: scriptplan/__init__.py
|
|
8
|
+
dynamic = ["version"]
|
|
8
9
|
description = "A precise project scheduling engine with minute-level accuracy for resource allocation and dependency management."
|
|
9
10
|
authors = [
|
|
10
11
|
{ name = "Farshid Ashouri", email = "farsheed.ashouri@gmail.com" },
|
|
@@ -61,10 +62,14 @@ plan = "scriptplan.cli.plan:main"
|
|
|
61
62
|
"Documentation" = "https://github.com/rodmena-limited/scriptplan#readme"
|
|
62
63
|
"Bug Tracker" = "https://github.com/rodmena-limited/scriptplan/issues"
|
|
63
64
|
"Source" = "https://github.com/rodmena-limited/scriptplan"
|
|
65
|
+
"Changelog" = "https://github.com/rodmena-limited/scriptplan/blob/main/CHANGELOG.md"
|
|
64
66
|
|
|
65
67
|
[tool.setuptools]
|
|
66
68
|
include-package-data = true
|
|
67
69
|
|
|
70
|
+
[tool.setuptools.dynamic]
|
|
71
|
+
version = { attr = "scriptplan.__version__" }
|
|
72
|
+
|
|
68
73
|
[tool.setuptools.packages.find]
|
|
69
74
|
where = ["."]
|
|
70
75
|
include = ["scriptplan*"]
|
|
@@ -77,7 +82,9 @@ testpaths = ["tests"]
|
|
|
77
82
|
python_files = ["test_*.py"]
|
|
78
83
|
python_classes = ["Test*"]
|
|
79
84
|
python_functions = ["test_*"]
|
|
80
|
-
|
|
85
|
+
# No -n here: pytest-xdist is an optional extra, and requiring it means
|
|
86
|
+
# the suite cannot start without it. Pass -n yourself to parallelise.
|
|
87
|
+
addopts = "--tb=short"
|
|
81
88
|
filterwarnings = [
|
|
82
89
|
"ignore::DeprecationWarning",
|
|
83
90
|
"ignore::PendingDeprecationWarning",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ScriptPlan - A Python implementation inspired by TaskJuggler.
|
|
3
|
+
|
|
4
|
+
This package provides project scheduling and resource management capabilities
|
|
5
|
+
compatible with the TaskJuggler Project Definition Language (.tjp files).
|
|
6
|
+
|
|
7
|
+
ScriptPlan owes its existence to TaskJuggler (https://taskjuggler.org/),
|
|
8
|
+
the pioneering open-source project management tool created by Chris Schlaeger.
|
|
9
|
+
The TJP file format and scheduling concepts originate from TaskJuggler.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# The single source of truth for the version; pyproject reads it from here.
|
|
13
|
+
# A semantic change: effort is now person-time, failures are reported as
|
|
14
|
+
# failures, and the compiled extensions are gone.
|
|
15
|
+
__version__ = "0.10.0"
|
|
16
|
+
__author__ = "ScriptPlan Team"
|
|
17
|
+
|
|
18
|
+
from scriptplan.core.project import Project
|
|
19
|
+
from scriptplan.core.resource import Resource
|
|
20
|
+
from scriptplan.core.scenario import Scenario
|
|
21
|
+
from scriptplan.core.task import Task
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"Project",
|
|
25
|
+
"Resource",
|
|
26
|
+
"Scenario",
|
|
27
|
+
"Task",
|
|
28
|
+
"__version__",
|
|
29
|
+
]
|