planaco 0.2.2__tar.gz → 0.2.3__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.
Files changed (24) hide show
  1. {planaco-0.2.2/src/planaco.egg-info → planaco-0.2.3}/PKG-INFO +31 -26
  2. {planaco-0.2.2 → planaco-0.2.3}/README.md +30 -25
  3. {planaco-0.2.2 → planaco-0.2.3}/pyproject.toml +4 -1
  4. {planaco-0.2.2 → planaco-0.2.3}/src/planaco/__init__.py +17 -20
  5. {planaco-0.2.2 → planaco-0.2.3}/src/planaco/config.py +1 -1
  6. {planaco-0.2.2 → planaco-0.2.3}/src/planaco/project.py +4 -5
  7. {planaco-0.2.2 → planaco-0.2.3}/src/planaco/task.py +4 -6
  8. {planaco-0.2.2 → planaco-0.2.3/src/planaco.egg-info}/PKG-INFO +31 -26
  9. {planaco-0.2.2 → planaco-0.2.3}/src/planaco.egg-info/SOURCES.txt +1 -0
  10. {planaco-0.2.2 → planaco-0.2.3}/tests/test_cli.py +2 -1
  11. {planaco-0.2.2 → planaco-0.2.3}/tests/test_distributions.py +6 -2
  12. planaco-0.2.3/tests/test_readme_examples.py +69 -0
  13. {planaco-0.2.2 → planaco-0.2.3}/tests/test_refactoring.py +3 -1
  14. {planaco-0.2.2 → planaco-0.2.3}/LICENSE.md +0 -0
  15. {planaco-0.2.2 → planaco-0.2.3}/setup.cfg +0 -0
  16. {planaco-0.2.2 → planaco-0.2.3}/src/planaco/cli.py +0 -0
  17. {planaco-0.2.2 → planaco-0.2.3}/src/planaco/distributions.py +0 -0
  18. {planaco-0.2.2 → planaco-0.2.3}/src/planaco.egg-info/dependency_links.txt +0 -0
  19. {planaco-0.2.2 → planaco-0.2.3}/src/planaco.egg-info/entry_points.txt +0 -0
  20. {planaco-0.2.2 → planaco-0.2.3}/src/planaco.egg-info/requires.txt +0 -0
  21. {planaco-0.2.2 → planaco-0.2.3}/src/planaco.egg-info/top_level.txt +0 -0
  22. {planaco-0.2.2 → planaco-0.2.3}/tests/test_config.py +0 -0
  23. {planaco-0.2.2 → planaco-0.2.3}/tests/test_project.py +0 -0
  24. {planaco-0.2.2 → planaco-0.2.3}/tests/test_task.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: planaco
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Task and project estimation using Monte Carlo simulation
5
5
  Author: Martijn Wieriks
6
6
  License: MIT
@@ -39,15 +39,20 @@ Dynamic: license-file
39
39
 
40
40
  <div align="center">
41
41
  <picture>
42
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/brand/planaco-lockup-dark.svg">
43
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/brand/planaco-lockup-light.svg" alt="planaco" width="320">
42
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/brand/planaco-lockup-dark.svg">
43
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/brand/planaco-lockup-light.svg" alt="planaco" width="320">
44
44
  </picture>
45
45
 
46
46
  **Probabilistic Project Planning with Monte Carlo Simulation**
47
47
 
48
- [![Python](https://img.shields.io/badge/python-3.8+-blue.svg?style=flat-square)](https://www.python.org)
48
+ [![CI](https://img.shields.io/github/actions/workflow/status/sepam/planaco/ci.yml?branch=master&style=flat-square&label=CI)](https://github.com/sepam/planaco/actions/workflows/ci.yml)
49
+ [![PyPI version](https://img.shields.io/pypi/v/planaco.svg?style=flat-square)](https://pypi.org/project/planaco/)
50
+ [![Python versions](https://img.shields.io/pypi/pyversions/planaco.svg?style=flat-square)](https://pypi.org/project/planaco/)
49
51
  [![License](https://img.shields.io/github/license/sepam/planaco?style=flat-square)](LICENSE.md)
50
52
  ![Status](https://img.shields.io/badge/status-alpha-orange?style=flat-square)
53
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black)
54
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=flat-square)](https://github.com/astral-sh/ruff)
55
+ [![Checked with mypy](https://img.shields.io/badge/mypy-checked-blue?style=flat-square)](https://mypy-lang.org/)
51
56
 
52
57
  <p align="center">
53
58
  <a href="#why-planaco">Why Planaco?</a> •
@@ -69,7 +74,7 @@ Estimating the time it takes to complete a task or project is hard. Traditional
69
74
 
70
75
  **Planaco helps you make better estimates by modeling tasks as random processes**, accounting for uncertainty and task dependencies through Monte Carlo simulation.
71
76
 
72
- > **Status:** Planaco is in **alpha** (v0.2.2). The public API may still change between releases.
77
+ > **Status:** Planaco is in **alpha** (v0.2.3). The public API may still change between releases.
73
78
 
74
79
  ## Features
75
80
 
@@ -118,7 +123,7 @@ task = Task(
118
123
  ```
119
124
 
120
125
  <div align="center">
121
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/task_definition.png" alt="Task Definition" width="600"/>
126
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/task_definition.png" alt="Task Definition" width="600"/>
122
127
  </div>
123
128
 
124
129
  ### Build Projects with Dependencies
@@ -147,7 +152,7 @@ project.add_task(deploy, depends_on=[testing])
147
152
  ```
148
153
 
149
154
  <div align="center">
150
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/project_estimation.png" alt="Project Estimation" width="700"/>
155
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/project_estimation.png" alt="Project Estimation" width="700"/>
151
156
  </div>
152
157
 
153
158
  ---
@@ -191,7 +196,7 @@ project.plot(n=10000, hist=True)
191
196
  ```
192
197
 
193
198
  <div align="center">
194
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/monte_carlo_estimation.png" alt="Monte Carlo Histogram" width="550"/>
199
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/monte_carlo_estimation.png" alt="Monte Carlo Histogram" width="550"/>
195
200
  </div>
196
201
 
197
202
  ### Cumulative Distribution
@@ -204,7 +209,7 @@ project.plot(n=10000, hist=False)
204
209
  ```
205
210
 
206
211
  <div align="center">
207
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/monte_carlo_cumulative.png" alt="Cumulative Distribution" width="550"/>
212
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/monte_carlo_cumulative.png" alt="Cumulative Distribution" width="550"/>
208
213
  </div>
209
214
 
210
215
  The cumulative distribution shows the probability of completing the project within a given timeframe, accounting for both parallel and sequential task execution.
@@ -217,10 +222,10 @@ The cumulative distribution shows the probability of completing the project with
217
222
 
218
223
  ```python
219
224
  # Export to JSON
220
- project.export_results(n=10000, format='json', filename='results.json')
225
+ project.export_results(n=10000, format='json', output='results.json')
221
226
 
222
227
  # Export to CSV
223
- project.export_results(n=10000, format='csv', filename='results.csv')
228
+ project.export_results(n=10000, format='csv', output='results.csv')
224
229
  ```
225
230
 
226
231
  ### Probability Distributions
@@ -366,20 +371,20 @@ project:
366
371
  seed: 42 # optional, for reproducible runs
367
372
 
368
373
  tasks:
369
- - name: Design
370
- distribution:
371
- type: pert
372
- minimum: 5
373
- mode: 7
374
- maximum: 14
375
-
376
- - name: Development
377
- depends_on: [Design]
378
- distribution:
379
- type: triangular
380
- minimum: 10
381
- mode: 15
382
- maximum: 25
374
+ design:
375
+ name: Design
376
+ estimator: pert
377
+ min_duration: 5
378
+ mode_duration: 7
379
+ max_duration: 14
380
+
381
+ development:
382
+ name: Development
383
+ estimator: triangular
384
+ min_duration: 10
385
+ mode_duration: 15
386
+ max_duration: 25
387
+ depends_on: [design]
383
388
  ```
384
389
 
385
390
  Run `planaco --help` or `planaco <command> --help` to see all available options, including `--seed` for reproducible results and `-p/--percentile` markers for plots.
@@ -421,7 +426,7 @@ Please open an issue to discuss larger changes before starting work.
421
426
 
422
427
  ## License
423
428
 
424
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
429
+ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
425
430
 
426
431
  ---
427
432
 
@@ -1,14 +1,19 @@
1
1
  <div align="center">
2
2
  <picture>
3
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/brand/planaco-lockup-dark.svg">
4
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/brand/planaco-lockup-light.svg" alt="planaco" width="320">
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/brand/planaco-lockup-dark.svg">
4
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/brand/planaco-lockup-light.svg" alt="planaco" width="320">
5
5
  </picture>
6
6
 
7
7
  **Probabilistic Project Planning with Monte Carlo Simulation**
8
8
 
9
- [![Python](https://img.shields.io/badge/python-3.8+-blue.svg?style=flat-square)](https://www.python.org)
9
+ [![CI](https://img.shields.io/github/actions/workflow/status/sepam/planaco/ci.yml?branch=master&style=flat-square&label=CI)](https://github.com/sepam/planaco/actions/workflows/ci.yml)
10
+ [![PyPI version](https://img.shields.io/pypi/v/planaco.svg?style=flat-square)](https://pypi.org/project/planaco/)
11
+ [![Python versions](https://img.shields.io/pypi/pyversions/planaco.svg?style=flat-square)](https://pypi.org/project/planaco/)
10
12
  [![License](https://img.shields.io/github/license/sepam/planaco?style=flat-square)](LICENSE.md)
11
13
  ![Status](https://img.shields.io/badge/status-alpha-orange?style=flat-square)
14
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black)
15
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=flat-square)](https://github.com/astral-sh/ruff)
16
+ [![Checked with mypy](https://img.shields.io/badge/mypy-checked-blue?style=flat-square)](https://mypy-lang.org/)
12
17
 
13
18
  <p align="center">
14
19
  <a href="#why-planaco">Why Planaco?</a> •
@@ -30,7 +35,7 @@ Estimating the time it takes to complete a task or project is hard. Traditional
30
35
 
31
36
  **Planaco helps you make better estimates by modeling tasks as random processes**, accounting for uncertainty and task dependencies through Monte Carlo simulation.
32
37
 
33
- > **Status:** Planaco is in **alpha** (v0.2.2). The public API may still change between releases.
38
+ > **Status:** Planaco is in **alpha** (v0.2.3). The public API may still change between releases.
34
39
 
35
40
  ## Features
36
41
 
@@ -79,7 +84,7 @@ task = Task(
79
84
  ```
80
85
 
81
86
  <div align="center">
82
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/task_definition.png" alt="Task Definition" width="600"/>
87
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/task_definition.png" alt="Task Definition" width="600"/>
83
88
  </div>
84
89
 
85
90
  ### Build Projects with Dependencies
@@ -108,7 +113,7 @@ project.add_task(deploy, depends_on=[testing])
108
113
  ```
109
114
 
110
115
  <div align="center">
111
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/project_estimation.png" alt="Project Estimation" width="700"/>
116
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/project_estimation.png" alt="Project Estimation" width="700"/>
112
117
  </div>
113
118
 
114
119
  ---
@@ -152,7 +157,7 @@ project.plot(n=10000, hist=True)
152
157
  ```
153
158
 
154
159
  <div align="center">
155
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/monte_carlo_estimation.png" alt="Monte Carlo Histogram" width="550"/>
160
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/monte_carlo_estimation.png" alt="Monte Carlo Histogram" width="550"/>
156
161
  </div>
157
162
 
158
163
  ### Cumulative Distribution
@@ -165,7 +170,7 @@ project.plot(n=10000, hist=False)
165
170
  ```
166
171
 
167
172
  <div align="center">
168
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/monte_carlo_cumulative.png" alt="Cumulative Distribution" width="550"/>
173
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/monte_carlo_cumulative.png" alt="Cumulative Distribution" width="550"/>
169
174
  </div>
170
175
 
171
176
  The cumulative distribution shows the probability of completing the project within a given timeframe, accounting for both parallel and sequential task execution.
@@ -178,10 +183,10 @@ The cumulative distribution shows the probability of completing the project with
178
183
 
179
184
  ```python
180
185
  # Export to JSON
181
- project.export_results(n=10000, format='json', filename='results.json')
186
+ project.export_results(n=10000, format='json', output='results.json')
182
187
 
183
188
  # Export to CSV
184
- project.export_results(n=10000, format='csv', filename='results.csv')
189
+ project.export_results(n=10000, format='csv', output='results.csv')
185
190
  ```
186
191
 
187
192
  ### Probability Distributions
@@ -327,20 +332,20 @@ project:
327
332
  seed: 42 # optional, for reproducible runs
328
333
 
329
334
  tasks:
330
- - name: Design
331
- distribution:
332
- type: pert
333
- minimum: 5
334
- mode: 7
335
- maximum: 14
336
-
337
- - name: Development
338
- depends_on: [Design]
339
- distribution:
340
- type: triangular
341
- minimum: 10
342
- mode: 15
343
- maximum: 25
335
+ design:
336
+ name: Design
337
+ estimator: pert
338
+ min_duration: 5
339
+ mode_duration: 7
340
+ max_duration: 14
341
+
342
+ development:
343
+ name: Development
344
+ estimator: triangular
345
+ min_duration: 10
346
+ mode_duration: 15
347
+ max_duration: 25
348
+ depends_on: [design]
344
349
  ```
345
350
 
346
351
  Run `planaco --help` or `planaco <command> --help` to see all available options, including `--seed` for reproducible results and `-p/--percentile` markers for plots.
@@ -382,7 +387,7 @@ Please open an issue to discuss larger changes before starting work.
382
387
 
383
388
  ## License
384
389
 
385
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
390
+ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
386
391
 
387
392
  ---
388
393
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "planaco"
7
- version = "0.2.2"
7
+ dynamic = ["version"]
8
8
  description = "Task and project estimation using Monte Carlo simulation"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -63,6 +63,9 @@ Repository = "https://github.com/sepam/planaco"
63
63
  [tool.setuptools.packages.find]
64
64
  where = ["src"]
65
65
 
66
+ [tool.setuptools.dynamic]
67
+ version = {attr = "planaco.__version__"}
68
+
66
69
  [tool.mypy]
67
70
  python_version = "3.9"
68
71
  warn_return_any = true
@@ -72,7 +72,7 @@ Using Distribution Objects
72
72
  >>> from planaco import Task, PERTDistribution
73
73
  >>>
74
74
  >>> # Use PERT distribution for smoother estimates
75
- >>> dist = PERTDistribution(minimum=5, mode=7, maximum=14)
75
+ >>> dist = PERTDistribution(min_value=5, mode_value=7, max_value=14)
76
76
  >>> task = Task(name="Design", distribution=dist)
77
77
 
78
78
  Configuration Files
@@ -87,20 +87,21 @@ Example YAML::
87
87
  unit: "days"
88
88
 
89
89
  tasks:
90
- - name: "Design"
91
- distribution:
92
- type: "pert"
93
- minimum: 5
94
- mode: 7
95
- maximum: 14
96
-
97
- - name: "Development"
98
- depends_on: ["Design"]
99
- distribution:
100
- type: "triangular"
101
- minimum: 10
102
- mode: 15
103
- maximum: 25
90
+ design:
91
+ name: "Design"
92
+ estimator: "pert"
93
+ min_duration: 5
94
+ mode_duration: 7
95
+ max_duration: 14
96
+
97
+ development:
98
+ name: "Development"
99
+ estimator: "triangular"
100
+ min_duration: 10
101
+ mode_duration: 15
102
+ max_duration: 25
103
+ depends_on:
104
+ - design
104
105
 
105
106
  Command Line Interface
106
107
  ----------------------
@@ -119,10 +120,6 @@ planaco.task : Task class for individual tasks.
119
120
  planaco.project : Project class for task collections.
120
121
  planaco.config : YAML configuration loading.
121
122
  planaco.cli : Command-line interface.
122
-
123
- Version
124
- -------
125
- 0.2.2
126
123
  """
127
124
 
128
125
  # Import order matters: distributions -> task -> project to avoid circular imports
@@ -138,7 +135,7 @@ from planaco.distributions import (
138
135
  from planaco.task import Task
139
136
  from planaco.project import Project
140
137
 
141
- __version__ = "0.2.2"
138
+ __version__ = "0.2.3"
142
139
 
143
140
  __all__ = [
144
141
  # Core classes
@@ -104,7 +104,7 @@ def load_config(config_path: str) -> Dict[str, Any]:
104
104
  raise FileNotFoundError(f"Configuration file not found: {config_path}")
105
105
 
106
106
  with open(path) as f:
107
- config = yaml.safe_load(f)
107
+ config: Dict[str, Any] = yaml.safe_load(f)
108
108
 
109
109
  _validate_config(config)
110
110
  return config
@@ -74,7 +74,6 @@ import seaborn as sns
74
74
  from planaco.task import Task
75
75
 
76
76
 
77
-
78
77
  class Project:
79
78
  """A collection of tasks with optional dependencies for Monte Carlo simulation.
80
79
 
@@ -115,7 +114,6 @@ class Project:
115
114
  >>> print(f"P85: {stats['percentiles']['p85']:.1f} {stats['unit']}")
116
115
  """
117
116
 
118
-
119
117
  def __init__(self, name: Optional[str] = None, unit: str = "days") -> None:
120
118
  """Initialize a new Project.
121
119
 
@@ -843,7 +841,7 @@ class Project:
843
841
  if show_percentiles:
844
842
  colors = ["red", "orange", "purple", "green", "blue"]
845
843
  for i, p in enumerate(percentiles):
846
- p_value = np.percentile(sims, p)
844
+ p_value = float(np.percentile(sims, p))
847
845
  color = colors[i % len(colors)]
848
846
  ax.axvline(
849
847
  x=p_value,
@@ -861,8 +859,9 @@ class Project:
861
859
  )
862
860
  else:
863
861
  # Default: show only median
864
- ax.axvline(x=np.median(sims), color="red", label="50%")
865
- ax.text(np.median(sims) - 0.5, ax.get_ylim()[0], "50%", color="red")
862
+ median = float(np.median(sims))
863
+ ax.axvline(x=median, color="red", label="50%")
864
+ ax.text(median - 0.5, ax.get_ylim()[0], "50%", color="red")
866
865
 
867
866
  ax.legend()
868
867
 
@@ -45,7 +45,7 @@ Example
45
45
 
46
46
  >>> # Modern style: use a Distribution object
47
47
  >>> from planaco import PERTDistribution
48
- >>> dist = PERTDistribution(minimum=5, mode=8, maximum=15)
48
+ >>> dist = PERTDistribution(min_value=5, mode_value=8, max_value=15)
49
49
  >>> task = Task(name="Development", distribution=dist)
50
50
 
51
51
  See Also
@@ -78,8 +78,6 @@ class Task:
78
78
  ----------
79
79
  task_id : str
80
80
  Unique identifier (UUID) for the task.
81
- cdate : datetime
82
- Creation timestamp.
83
81
  name : str or None
84
82
  Human-readable name for the task.
85
83
 
@@ -233,7 +231,7 @@ class Task:
233
231
  """
234
232
  if self._distribution is not None:
235
233
  if hasattr(self._distribution, "min_value"):
236
- return self._distribution.min_value
234
+ return float(self._distribution.min_value)
237
235
  return self._min_duration
238
236
 
239
237
  @property
@@ -254,7 +252,7 @@ class Task:
254
252
  """
255
253
  if self._distribution is not None:
256
254
  if hasattr(self._distribution, "mode_value"):
257
- return self._distribution.mode_value
255
+ return float(self._distribution.mode_value)
258
256
  return self._mode_duration
259
257
 
260
258
  @property
@@ -275,7 +273,7 @@ class Task:
275
273
  """
276
274
  if self._distribution is not None:
277
275
  if hasattr(self._distribution, "max_value"):
278
- return self._distribution.max_value
276
+ return float(self._distribution.max_value)
279
277
  return self._max_duration
280
278
 
281
279
  def estimate(self) -> float:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: planaco
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Task and project estimation using Monte Carlo simulation
5
5
  Author: Martijn Wieriks
6
6
  License: MIT
@@ -39,15 +39,20 @@ Dynamic: license-file
39
39
 
40
40
  <div align="center">
41
41
  <picture>
42
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/brand/planaco-lockup-dark.svg">
43
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/brand/planaco-lockup-light.svg" alt="planaco" width="320">
42
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/brand/planaco-lockup-dark.svg">
43
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/brand/planaco-lockup-light.svg" alt="planaco" width="320">
44
44
  </picture>
45
45
 
46
46
  **Probabilistic Project Planning with Monte Carlo Simulation**
47
47
 
48
- [![Python](https://img.shields.io/badge/python-3.8+-blue.svg?style=flat-square)](https://www.python.org)
48
+ [![CI](https://img.shields.io/github/actions/workflow/status/sepam/planaco/ci.yml?branch=master&style=flat-square&label=CI)](https://github.com/sepam/planaco/actions/workflows/ci.yml)
49
+ [![PyPI version](https://img.shields.io/pypi/v/planaco.svg?style=flat-square)](https://pypi.org/project/planaco/)
50
+ [![Python versions](https://img.shields.io/pypi/pyversions/planaco.svg?style=flat-square)](https://pypi.org/project/planaco/)
49
51
  [![License](https://img.shields.io/github/license/sepam/planaco?style=flat-square)](LICENSE.md)
50
52
  ![Status](https://img.shields.io/badge/status-alpha-orange?style=flat-square)
53
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black)
54
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json&style=flat-square)](https://github.com/astral-sh/ruff)
55
+ [![Checked with mypy](https://img.shields.io/badge/mypy-checked-blue?style=flat-square)](https://mypy-lang.org/)
51
56
 
52
57
  <p align="center">
53
58
  <a href="#why-planaco">Why Planaco?</a> •
@@ -69,7 +74,7 @@ Estimating the time it takes to complete a task or project is hard. Traditional
69
74
 
70
75
  **Planaco helps you make better estimates by modeling tasks as random processes**, accounting for uncertainty and task dependencies through Monte Carlo simulation.
71
76
 
72
- > **Status:** Planaco is in **alpha** (v0.2.2). The public API may still change between releases.
77
+ > **Status:** Planaco is in **alpha** (v0.2.3). The public API may still change between releases.
73
78
 
74
79
  ## Features
75
80
 
@@ -118,7 +123,7 @@ task = Task(
118
123
  ```
119
124
 
120
125
  <div align="center">
121
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/task_definition.png" alt="Task Definition" width="600"/>
126
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/task_definition.png" alt="Task Definition" width="600"/>
122
127
  </div>
123
128
 
124
129
  ### Build Projects with Dependencies
@@ -147,7 +152,7 @@ project.add_task(deploy, depends_on=[testing])
147
152
  ```
148
153
 
149
154
  <div align="center">
150
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/project_estimation.png" alt="Project Estimation" width="700"/>
155
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/project_estimation.png" alt="Project Estimation" width="700"/>
151
156
  </div>
152
157
 
153
158
  ---
@@ -191,7 +196,7 @@ project.plot(n=10000, hist=True)
191
196
  ```
192
197
 
193
198
  <div align="center">
194
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/monte_carlo_estimation.png" alt="Monte Carlo Histogram" width="550"/>
199
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/monte_carlo_estimation.png" alt="Monte Carlo Histogram" width="550"/>
195
200
  </div>
196
201
 
197
202
  ### Cumulative Distribution
@@ -204,7 +209,7 @@ project.plot(n=10000, hist=False)
204
209
  ```
205
210
 
206
211
  <div align="center">
207
- <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.2/example/monte_carlo_cumulative.png" alt="Cumulative Distribution" width="550"/>
212
+ <img src="https://raw.githubusercontent.com/sepam/planaco/v0.2.3/example/monte_carlo_cumulative.png" alt="Cumulative Distribution" width="550"/>
208
213
  </div>
209
214
 
210
215
  The cumulative distribution shows the probability of completing the project within a given timeframe, accounting for both parallel and sequential task execution.
@@ -217,10 +222,10 @@ The cumulative distribution shows the probability of completing the project with
217
222
 
218
223
  ```python
219
224
  # Export to JSON
220
- project.export_results(n=10000, format='json', filename='results.json')
225
+ project.export_results(n=10000, format='json', output='results.json')
221
226
 
222
227
  # Export to CSV
223
- project.export_results(n=10000, format='csv', filename='results.csv')
228
+ project.export_results(n=10000, format='csv', output='results.csv')
224
229
  ```
225
230
 
226
231
  ### Probability Distributions
@@ -366,20 +371,20 @@ project:
366
371
  seed: 42 # optional, for reproducible runs
367
372
 
368
373
  tasks:
369
- - name: Design
370
- distribution:
371
- type: pert
372
- minimum: 5
373
- mode: 7
374
- maximum: 14
375
-
376
- - name: Development
377
- depends_on: [Design]
378
- distribution:
379
- type: triangular
380
- minimum: 10
381
- mode: 15
382
- maximum: 25
374
+ design:
375
+ name: Design
376
+ estimator: pert
377
+ min_duration: 5
378
+ mode_duration: 7
379
+ max_duration: 14
380
+
381
+ development:
382
+ name: Development
383
+ estimator: triangular
384
+ min_duration: 10
385
+ mode_duration: 15
386
+ max_duration: 25
387
+ depends_on: [design]
383
388
  ```
384
389
 
385
390
  Run `planaco --help` or `planaco <command> --help` to see all available options, including `--seed` for reproducible results and `-p/--percentile` markers for plots.
@@ -421,7 +426,7 @@ Please open an issue to discuss larger changes before starting work.
421
426
 
422
427
  ## License
423
428
 
424
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
429
+ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
425
430
 
426
431
  ---
427
432
 
@@ -17,5 +17,6 @@ tests/test_cli.py
17
17
  tests/test_config.py
18
18
  tests/test_distributions.py
19
19
  tests/test_project.py
20
+ tests/test_readme_examples.py
20
21
  tests/test_refactoring.py
21
22
  tests/test_task.py
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  import pytest
7
7
  from click.testing import CliRunner
8
8
 
9
+ from planaco import __version__
9
10
  from planaco.cli import main
10
11
 
11
12
  # Get path to fixtures directory
@@ -43,7 +44,7 @@ class TestMainGroup:
43
44
  """Test --version flag."""
44
45
  result = runner.invoke(main, ["--version"])
45
46
  assert result.exit_code == 0
46
- assert "0.2.2" in result.output
47
+ assert __version__ in result.output
47
48
 
48
49
 
49
50
  class TestInitCommand:
@@ -726,7 +726,9 @@ class TestDistributionRoundTrip:
726
726
 
727
727
  def test_normal_round_trip_with_bounds(self):
728
728
  """Normal distribution with explicit bounds survives round-trip."""
729
- original = NormalDistribution(mean=7.0, std_dev=2.0, min_value=3.0, max_value=11.0)
729
+ original = NormalDistribution(
730
+ mean=7.0, std_dev=2.0, min_value=3.0, max_value=11.0
731
+ )
730
732
  d = original.to_dict()
731
733
  restored = create_distribution(estimator=d.pop("type"), **d)
732
734
  assert isinstance(restored, NormalDistribution)
@@ -772,7 +774,9 @@ class TestDistributionRoundTrip:
772
774
 
773
775
  def test_lognormal_round_trip_with_bounds(self):
774
776
  """LogNormal with explicit bounds survives round-trip."""
775
- original = LogNormalDistribution(mean=8.0, std_dev=3.0, min_value=2.0, max_value=20.0)
777
+ original = LogNormalDistribution(
778
+ mean=8.0, std_dev=3.0, min_value=2.0, max_value=20.0
779
+ )
776
780
  d = original.to_dict()
777
781
  restored = create_distribution(estimator=d.pop("type"), **d)
778
782
  assert isinstance(restored, LogNormalDistribution)
@@ -0,0 +1,69 @@
1
+ """Ensure code examples in README.md stay in sync with the actual API.
2
+
3
+ Extracts fenced ``python`` and ``yaml`` code blocks from README.md and
4
+ executes them, so a README example that no longer matches the library
5
+ fails the test suite instead of failing for a new user.
6
+ """
7
+
8
+ import re
9
+ from pathlib import Path
10
+
11
+ import matplotlib
12
+
13
+ matplotlib.use("Agg") # Non-interactive backend for testing
14
+
15
+ import matplotlib.pyplot as plt # noqa: E402
16
+ import pytest # noqa: E402
17
+
18
+ from planaco.config import build_project_from_config, load_config # noqa: E402
19
+
20
+ README_PATH = Path(__file__).resolve().parent.parent / "README.md"
21
+
22
+
23
+ def _extract_code_blocks(language):
24
+ """Return the contents of all fenced code blocks for a language."""
25
+ text = README_PATH.read_text(encoding="utf-8")
26
+ pattern = re.compile(rf"```{language}\n(.*?)```", re.DOTALL)
27
+ return pattern.findall(text)
28
+
29
+
30
+ def test_readme_python_examples_run(tmp_path, monkeypatch):
31
+ """All Python examples must run in order without errors.
32
+
33
+ Blocks are executed cumulatively in a shared namespace because later
34
+ examples build on objects defined in earlier ones (e.g. ``project``).
35
+ """
36
+ monkeypatch.chdir(tmp_path) # exported files land in a temp dir
37
+ blocks = _extract_code_blocks("python")
38
+ assert blocks, "No Python code blocks found in README.md"
39
+
40
+ namespace = {}
41
+ for i, block in enumerate(blocks, start=1):
42
+ try:
43
+ exec(compile(block, f"README.md python block {i}", "exec"), namespace)
44
+ except Exception as e:
45
+ pytest.fail(
46
+ f"README.md python block {i} raised {type(e).__name__}: {e}\n"
47
+ f"---\n{block}---"
48
+ )
49
+ finally:
50
+ plt.close("all")
51
+
52
+
53
+ def test_readme_yaml_examples_load(tmp_path):
54
+ """All YAML project examples must pass config validation and simulate."""
55
+ blocks = [b for b in _extract_code_blocks("yaml") if "project:" in b]
56
+ assert blocks, "No YAML project config blocks found in README.md"
57
+
58
+ for i, block in enumerate(blocks, start=1):
59
+ config_file = tmp_path / f"readme_example_{i}.yaml"
60
+ config_file.write_text(block, encoding="utf-8")
61
+ try:
62
+ config = load_config(str(config_file))
63
+ project = build_project_from_config(config)
64
+ project.statistics(n=100)
65
+ except Exception as e:
66
+ pytest.fail(
67
+ f"README.md yaml block {i} raised {type(e).__name__}: {e}\n"
68
+ f"---\n{block}---"
69
+ )
@@ -254,7 +254,9 @@ class TestExportResultsEfficiency:
254
254
  call_count += 1
255
255
  return original_estimate()
256
256
 
257
- with tempfile.NamedTemporaryFile(suffix=".json", delete=True) as tmp: # noqa: SIM117
257
+ with tempfile.NamedTemporaryFile( # noqa: SIM117
258
+ suffix=".json", delete=True
259
+ ) as tmp:
258
260
  with patch.object(p, "estimate", side_effect=counting_estimate):
259
261
  p.export_results(n=n, format="json", output=tmp.name)
260
262
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes