planaco 0.2.1__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.
- planaco/__init__.py +158 -0
- planaco/cli.py +336 -0
- planaco/config.py +431 -0
- planaco/distributions.py +657 -0
- planaco/project.py +1085 -0
- planaco/task.py +310 -0
- planaco-0.2.1.dist-info/METADATA +430 -0
- planaco-0.2.1.dist-info/RECORD +12 -0
- planaco-0.2.1.dist-info/WHEEL +5 -0
- planaco-0.2.1.dist-info/entry_points.txt +2 -0
- planaco-0.2.1.dist-info/licenses/LICENSE.md +21 -0
- planaco-0.2.1.dist-info/top_level.txt +1 -0
planaco/__init__.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Planaco: Probabilistic Project Planning with Monte Carlo Simulation.
|
|
3
|
+
|
|
4
|
+
Planaco is a Python library for estimating project completion times using
|
|
5
|
+
Monte Carlo simulation. It models task durations as probability distributions
|
|
6
|
+
and calculates project timelines while accounting for uncertainty and
|
|
7
|
+
task dependencies.
|
|
8
|
+
|
|
9
|
+
Key Features
|
|
10
|
+
------------
|
|
11
|
+
- **Multiple distribution types**: Choose from 6 probability distributions
|
|
12
|
+
to model task duration uncertainty.
|
|
13
|
+
- **Task dependencies**: Define complex workflows with parallel and
|
|
14
|
+
sequential task execution.
|
|
15
|
+
- **Critical path analysis**: Identify which tasks drive your timeline.
|
|
16
|
+
- **Monte Carlo simulation**: Run thousands of simulations to understand
|
|
17
|
+
probability distributions.
|
|
18
|
+
- **Statistical analysis**: Get mean, median, percentiles, and confidence
|
|
19
|
+
intervals.
|
|
20
|
+
- **Visualization**: Generate histograms, cumulative plots, and dependency
|
|
21
|
+
graphs.
|
|
22
|
+
- **Configuration files**: Define projects in YAML for easy sharing.
|
|
23
|
+
- **CLI tool**: Run simulations from the command line.
|
|
24
|
+
|
|
25
|
+
Main Components
|
|
26
|
+
---------------
|
|
27
|
+
Distribution classes (from ``planaco.distributions``):
|
|
28
|
+
- ``TriangularDistribution``: Three-point estimate (min, mode, max).
|
|
29
|
+
Best for tasks where you can estimate optimistic, likely, and
|
|
30
|
+
pessimistic durations.
|
|
31
|
+
- ``UniformDistribution``: Equal probability between bounds.
|
|
32
|
+
Use when all durations in a range are equally likely.
|
|
33
|
+
- ``NormalDistribution``: Gaussian/bell curve with optional truncation.
|
|
34
|
+
Good for well-understood, repeatable tasks.
|
|
35
|
+
- ``PERTDistribution``: Smooth version of triangular (Program Evaluation
|
|
36
|
+
Review Technique). More weight on the mode than triangular.
|
|
37
|
+
- ``LogNormalDistribution``: Right-skewed distribution.
|
|
38
|
+
Good for tasks that may have unexpected delays.
|
|
39
|
+
- ``BetaDistribution``: Highly flexible with alpha/beta parameters.
|
|
40
|
+
For advanced modeling when you need specific shapes.
|
|
41
|
+
|
|
42
|
+
Task:
|
|
43
|
+
Represents a single task with probabilistic duration. Tasks are the
|
|
44
|
+
building blocks of projects.
|
|
45
|
+
|
|
46
|
+
Project:
|
|
47
|
+
Collection of tasks with optional dependencies. Provides Monte Carlo
|
|
48
|
+
simulation, statistics, critical path analysis, and visualization.
|
|
49
|
+
|
|
50
|
+
Quick Start
|
|
51
|
+
-----------
|
|
52
|
+
>>> from planaco import Project, Task
|
|
53
|
+
>>>
|
|
54
|
+
>>> # Create tasks with uncertainty (min, mode, max)
|
|
55
|
+
>>> design = Task(name="Design", min_duration=5, mode_duration=7, max_duration=14)
|
|
56
|
+
>>> develop = Task(name="Develop", min_duration=10, mode_duration=15, max_duration=25)
|
|
57
|
+
>>> test = Task(name="Test", min_duration=3, mode_duration=5, max_duration=10)
|
|
58
|
+
>>>
|
|
59
|
+
>>> # Create project with dependencies
|
|
60
|
+
>>> project = Project(name="My Project", unit="days")
|
|
61
|
+
>>> project.add_task(design)
|
|
62
|
+
>>> project.add_task(develop, depends_on=[design])
|
|
63
|
+
>>> project.add_task(test, depends_on=[develop])
|
|
64
|
+
>>>
|
|
65
|
+
>>> # Run simulation and get statistics
|
|
66
|
+
>>> stats = project.statistics(n=10000)
|
|
67
|
+
>>> print(f"Expected duration: {stats['mean']:.1f} days")
|
|
68
|
+
>>> print(f"P85 estimate: {stats['percentiles']['p85']:.1f} days")
|
|
69
|
+
|
|
70
|
+
Using Distribution Objects
|
|
71
|
+
--------------------------
|
|
72
|
+
>>> from planaco import Task, PERTDistribution
|
|
73
|
+
>>>
|
|
74
|
+
>>> # Use PERT distribution for smoother estimates
|
|
75
|
+
>>> dist = PERTDistribution(minimum=5, mode=7, maximum=14)
|
|
76
|
+
>>> task = Task(name="Design", distribution=dist)
|
|
77
|
+
|
|
78
|
+
Configuration Files
|
|
79
|
+
-------------------
|
|
80
|
+
Projects can be defined in YAML configuration files for easy sharing
|
|
81
|
+
and version control. See ``planaco.config`` for loading configurations.
|
|
82
|
+
|
|
83
|
+
Example YAML::
|
|
84
|
+
|
|
85
|
+
project:
|
|
86
|
+
name: "Website Redesign"
|
|
87
|
+
unit: "days"
|
|
88
|
+
|
|
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
|
|
104
|
+
|
|
105
|
+
Command Line Interface
|
|
106
|
+
----------------------
|
|
107
|
+
Planaco provides a CLI for running simulations::
|
|
108
|
+
|
|
109
|
+
$ planaco init # Create template config file
|
|
110
|
+
$ planaco run config.yaml # Run simulation
|
|
111
|
+
$ planaco stats config.yaml # Show statistics
|
|
112
|
+
$ planaco plot config.yaml # Generate visualization
|
|
113
|
+
$ planaco graph config.yaml # Show dependency graph
|
|
114
|
+
|
|
115
|
+
See Also
|
|
116
|
+
--------
|
|
117
|
+
planaco.distributions : Probability distribution classes.
|
|
118
|
+
planaco.task : Task class for individual tasks.
|
|
119
|
+
planaco.project : Project class for task collections.
|
|
120
|
+
planaco.config : YAML configuration loading.
|
|
121
|
+
planaco.cli : Command-line interface.
|
|
122
|
+
|
|
123
|
+
Version
|
|
124
|
+
-------
|
|
125
|
+
0.2.1
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
# Import order matters: distributions -> task -> project to avoid circular imports
|
|
129
|
+
from planaco.distributions import (
|
|
130
|
+
BetaDistribution,
|
|
131
|
+
Distribution,
|
|
132
|
+
LogNormalDistribution,
|
|
133
|
+
NormalDistribution,
|
|
134
|
+
PERTDistribution,
|
|
135
|
+
TriangularDistribution,
|
|
136
|
+
UniformDistribution,
|
|
137
|
+
)
|
|
138
|
+
from planaco.task import Task
|
|
139
|
+
from planaco.project import Project
|
|
140
|
+
|
|
141
|
+
__version__ = "0.2.1"
|
|
142
|
+
|
|
143
|
+
__all__ = [
|
|
144
|
+
# Core classes
|
|
145
|
+
"Project",
|
|
146
|
+
"Task",
|
|
147
|
+
# Distribution base
|
|
148
|
+
"Distribution",
|
|
149
|
+
# Distribution types
|
|
150
|
+
"BetaDistribution",
|
|
151
|
+
"LogNormalDistribution",
|
|
152
|
+
"NormalDistribution",
|
|
153
|
+
"PERTDistribution",
|
|
154
|
+
"TriangularDistribution",
|
|
155
|
+
"UniformDistribution",
|
|
156
|
+
# Version
|
|
157
|
+
"__version__",
|
|
158
|
+
]
|
planaco/cli.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Planaco CLI - Monte Carlo simulation for project estimation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from planaco import __version__
|
|
11
|
+
from planaco.config import (
|
|
12
|
+
ConfigError,
|
|
13
|
+
build_project_from_config,
|
|
14
|
+
get_seed_from_config,
|
|
15
|
+
get_template_config,
|
|
16
|
+
load_config,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _setup_seed(config: dict, cli_seed: Optional[int]) -> None:
|
|
21
|
+
"""Set up random seed from config or CLI, with warning if none specified.
|
|
22
|
+
|
|
23
|
+
Priority: CLI --seed > YAML config seed > random (no seed)
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
config : dict
|
|
28
|
+
Parsed configuration dictionary
|
|
29
|
+
cli_seed : Optional[int]
|
|
30
|
+
Seed from CLI --seed option (takes priority)
|
|
31
|
+
"""
|
|
32
|
+
import random
|
|
33
|
+
|
|
34
|
+
# CLI seed takes priority over config seed
|
|
35
|
+
if cli_seed is not None:
|
|
36
|
+
random.seed(cli_seed)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
# Try config seed
|
|
40
|
+
config_seed = get_seed_from_config(config)
|
|
41
|
+
if config_seed is not None:
|
|
42
|
+
random.seed(config_seed)
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
# No seed specified - show warning
|
|
46
|
+
click.secho(
|
|
47
|
+
"Note: No seed specified. Results will vary between runs.",
|
|
48
|
+
fg="yellow",
|
|
49
|
+
err=True,
|
|
50
|
+
)
|
|
51
|
+
click.secho(
|
|
52
|
+
" Add 'seed: <number>' to your project config for reproducible results.",
|
|
53
|
+
fg="yellow",
|
|
54
|
+
err=True,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@click.group()
|
|
59
|
+
@click.version_option(version=__version__)
|
|
60
|
+
def main() -> None:
|
|
61
|
+
"""Planaco - Monte Carlo simulation for project estimation.
|
|
62
|
+
|
|
63
|
+
Define projects and tasks in YAML, run simulations, and get
|
|
64
|
+
probabilistic completion time estimates.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
|
|
68
|
+
planaco init my_project.yaml
|
|
69
|
+
|
|
70
|
+
planaco stats my_project.yaml
|
|
71
|
+
|
|
72
|
+
planaco plot my_project.yaml -o chart.png
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@main.command()
|
|
78
|
+
@click.argument("output", default="planaco_project.yaml")
|
|
79
|
+
@click.option(
|
|
80
|
+
"--name", "-n", default="My Project", help="Project name for the template"
|
|
81
|
+
)
|
|
82
|
+
def init(output: str, name: str) -> None:
|
|
83
|
+
"""Create a template YAML project file.
|
|
84
|
+
|
|
85
|
+
OUTPUT: Path for the new YAML file (default: planaco_project.yaml)
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
planaco init my_project.yaml --name "Web App Development"
|
|
89
|
+
"""
|
|
90
|
+
output_path = Path(output)
|
|
91
|
+
|
|
92
|
+
if output_path.exists():
|
|
93
|
+
click.secho(f"Error: File '{output}' already exists", fg="red", err=True)
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
template = get_template_config(name)
|
|
97
|
+
|
|
98
|
+
with open(output_path, "w") as f:
|
|
99
|
+
f.write(template)
|
|
100
|
+
|
|
101
|
+
click.secho(f"Created template project file: {output}", fg="green")
|
|
102
|
+
click.echo("\nEdit the file to define your tasks, then run:")
|
|
103
|
+
click.echo(f" planaco stats {output}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@main.command()
|
|
107
|
+
@click.argument("config_file", type=click.Path(exists=True))
|
|
108
|
+
@click.option("-n", "--simulations", default=10000, help="Number of simulations to run")
|
|
109
|
+
@click.option(
|
|
110
|
+
"-o", "--output", default=None, help="Output file for results (default: stdout)"
|
|
111
|
+
)
|
|
112
|
+
@click.option(
|
|
113
|
+
"-f",
|
|
114
|
+
"--format",
|
|
115
|
+
"output_format",
|
|
116
|
+
type=click.Choice(["json", "csv"]),
|
|
117
|
+
default="json",
|
|
118
|
+
help="Output format",
|
|
119
|
+
)
|
|
120
|
+
@click.option("--seed", default=None, type=int, help="Random seed for reproducibility")
|
|
121
|
+
def run(
|
|
122
|
+
config_file: str,
|
|
123
|
+
simulations: int,
|
|
124
|
+
output: Optional[str],
|
|
125
|
+
output_format: str,
|
|
126
|
+
seed: Optional[int],
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Run Monte Carlo simulation and export results.
|
|
129
|
+
|
|
130
|
+
CONFIG_FILE: Path to the YAML project configuration file.
|
|
131
|
+
|
|
132
|
+
Example:
|
|
133
|
+
planaco run project.yaml -n 10000 -o results.json
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
config = load_config(config_file)
|
|
137
|
+
_setup_seed(config, seed)
|
|
138
|
+
project = build_project_from_config(config)
|
|
139
|
+
|
|
140
|
+
if output:
|
|
141
|
+
project.export_results(n=simulations, format=output_format, output=output)
|
|
142
|
+
click.secho(f"Results exported to {output}", fg="green")
|
|
143
|
+
else:
|
|
144
|
+
stats = project.statistics(n=simulations)
|
|
145
|
+
_print_stats(project.name, stats)
|
|
146
|
+
|
|
147
|
+
except (ConfigError, FileNotFoundError) as e:
|
|
148
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
149
|
+
sys.exit(1)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
152
|
+
sys.exit(1)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@main.command()
|
|
156
|
+
@click.argument("config_file", type=click.Path(exists=True))
|
|
157
|
+
@click.option("-n", "--simulations", default=10000, help="Number of simulations to run")
|
|
158
|
+
@click.option(
|
|
159
|
+
"--json", "as_json", is_flag=True, help="Output as JSON instead of formatted text"
|
|
160
|
+
)
|
|
161
|
+
@click.option(
|
|
162
|
+
"--seed",
|
|
163
|
+
default=None,
|
|
164
|
+
type=int,
|
|
165
|
+
help="Random seed for reproducibility (overrides config)",
|
|
166
|
+
)
|
|
167
|
+
def stats(
|
|
168
|
+
config_file: str, simulations: int, as_json: bool, seed: Optional[int]
|
|
169
|
+
) -> None:
|
|
170
|
+
"""Calculate and display project statistics.
|
|
171
|
+
|
|
172
|
+
CONFIG_FILE: Path to the YAML project configuration file.
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
planaco stats project.yaml -n 10000
|
|
176
|
+
"""
|
|
177
|
+
try:
|
|
178
|
+
config = load_config(config_file)
|
|
179
|
+
_setup_seed(config, seed)
|
|
180
|
+
project = build_project_from_config(config)
|
|
181
|
+
|
|
182
|
+
stats_result = project.statistics(n=simulations)
|
|
183
|
+
|
|
184
|
+
if as_json:
|
|
185
|
+
click.echo(json.dumps(stats_result, indent=2))
|
|
186
|
+
else:
|
|
187
|
+
_print_stats(project.name, stats_result)
|
|
188
|
+
|
|
189
|
+
except (ConfigError, FileNotFoundError) as e:
|
|
190
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
191
|
+
sys.exit(1)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
194
|
+
sys.exit(1)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@main.command()
|
|
198
|
+
@click.argument("config_file", type=click.Path(exists=True))
|
|
199
|
+
@click.option("-n", "--simulations", default=1000, help="Number of simulations to run")
|
|
200
|
+
@click.option(
|
|
201
|
+
"-o", "--output", default=None, help="Save plot to file instead of displaying"
|
|
202
|
+
)
|
|
203
|
+
@click.option(
|
|
204
|
+
"--cumulative",
|
|
205
|
+
is_flag=True,
|
|
206
|
+
help="Show cumulative distribution instead of histogram",
|
|
207
|
+
)
|
|
208
|
+
@click.option("--kde", is_flag=True, help="Show kernel density estimate on histogram")
|
|
209
|
+
@click.option(
|
|
210
|
+
"-p",
|
|
211
|
+
"--percentile",
|
|
212
|
+
"percentiles",
|
|
213
|
+
multiple=True,
|
|
214
|
+
type=int,
|
|
215
|
+
help="Percentile markers to show (e.g., -p 50 -p 85 -p 95)",
|
|
216
|
+
)
|
|
217
|
+
@click.option(
|
|
218
|
+
"--seed",
|
|
219
|
+
default=None,
|
|
220
|
+
type=int,
|
|
221
|
+
help="Random seed for reproducibility (overrides config)",
|
|
222
|
+
)
|
|
223
|
+
def plot(
|
|
224
|
+
config_file: str,
|
|
225
|
+
simulations: int,
|
|
226
|
+
output: Optional[str],
|
|
227
|
+
cumulative: bool,
|
|
228
|
+
kde: bool,
|
|
229
|
+
percentiles: Tuple[int, ...],
|
|
230
|
+
seed: Optional[int],
|
|
231
|
+
) -> None:
|
|
232
|
+
"""Generate visualization of simulation results.
|
|
233
|
+
|
|
234
|
+
CONFIG_FILE: Path to the YAML project configuration file.
|
|
235
|
+
|
|
236
|
+
Examples:
|
|
237
|
+
planaco plot project.yaml -o chart.png
|
|
238
|
+
|
|
239
|
+
planaco plot project.yaml --cumulative
|
|
240
|
+
|
|
241
|
+
planaco plot project.yaml -p 50 -p 85 -p 95
|
|
242
|
+
"""
|
|
243
|
+
try:
|
|
244
|
+
config = load_config(config_file)
|
|
245
|
+
_setup_seed(config, seed)
|
|
246
|
+
project = build_project_from_config(config)
|
|
247
|
+
|
|
248
|
+
percentiles_list = list(percentiles) if percentiles else None
|
|
249
|
+
show_percentiles = bool(percentiles_list)
|
|
250
|
+
|
|
251
|
+
project.plot(
|
|
252
|
+
n=simulations,
|
|
253
|
+
hist=not cumulative,
|
|
254
|
+
kde=kde,
|
|
255
|
+
save_path=output,
|
|
256
|
+
show_percentiles=show_percentiles,
|
|
257
|
+
percentiles=percentiles_list,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if output:
|
|
261
|
+
click.secho(f"Plot saved to {output}", fg="green")
|
|
262
|
+
|
|
263
|
+
except (ConfigError, FileNotFoundError) as e:
|
|
264
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
265
|
+
sys.exit(1)
|
|
266
|
+
except Exception as e:
|
|
267
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
268
|
+
sys.exit(1)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@main.command()
|
|
272
|
+
@click.argument("config_file", type=click.Path(exists=True))
|
|
273
|
+
@click.option(
|
|
274
|
+
"-o", "--output", default=None, help="Save graph to file instead of displaying"
|
|
275
|
+
)
|
|
276
|
+
@click.option("--no-durations", is_flag=True, help="Hide duration information on nodes")
|
|
277
|
+
def graph(config_file: str, output: Optional[str], no_durations: bool) -> None:
|
|
278
|
+
"""Visualize the task dependency graph.
|
|
279
|
+
|
|
280
|
+
CONFIG_FILE: Path to the YAML project configuration file.
|
|
281
|
+
|
|
282
|
+
Examples:
|
|
283
|
+
planaco graph project.yaml
|
|
284
|
+
|
|
285
|
+
planaco graph project.yaml -o dependencies.png
|
|
286
|
+
"""
|
|
287
|
+
try:
|
|
288
|
+
config = load_config(config_file)
|
|
289
|
+
project = build_project_from_config(config)
|
|
290
|
+
|
|
291
|
+
project.plot_dependency_graph(save_path=output, show_durations=not no_durations)
|
|
292
|
+
|
|
293
|
+
if output:
|
|
294
|
+
click.secho(f"Dependency graph saved to {output}", fg="green")
|
|
295
|
+
|
|
296
|
+
except (ConfigError, FileNotFoundError) as e:
|
|
297
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
298
|
+
sys.exit(1)
|
|
299
|
+
except Exception as e:
|
|
300
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
301
|
+
sys.exit(1)
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _print_stats(project_name: Optional[str], stats: dict) -> None:
|
|
305
|
+
"""Print formatted statistics to stdout."""
|
|
306
|
+
click.echo()
|
|
307
|
+
click.secho(f"Project: {project_name or 'Unnamed'}", fg="cyan", bold=True)
|
|
308
|
+
click.secho("=" * 50, fg="cyan")
|
|
309
|
+
click.echo()
|
|
310
|
+
|
|
311
|
+
click.echo(f"Simulations: {stats['n_simulations']:,}")
|
|
312
|
+
click.echo(f"Time Unit: {stats['unit']}")
|
|
313
|
+
click.echo()
|
|
314
|
+
|
|
315
|
+
click.secho("Duration Estimates:", bold=True)
|
|
316
|
+
click.echo(f" Mean: {stats['mean']:.1f} {stats['unit']}")
|
|
317
|
+
click.echo(f" Median (P50): {stats['median']:.1f} {stats['unit']}")
|
|
318
|
+
click.echo(f" Std Deviation: {stats['std_dev']:.1f} {stats['unit']}")
|
|
319
|
+
click.echo(f" Min: {stats['min']:.1f} {stats['unit']}")
|
|
320
|
+
click.echo(f" Max: {stats['max']:.1f} {stats['unit']}")
|
|
321
|
+
click.echo()
|
|
322
|
+
|
|
323
|
+
click.secho("Percentiles:", bold=True)
|
|
324
|
+
for key, value in stats["percentiles"].items():
|
|
325
|
+
label = key.upper()
|
|
326
|
+
click.echo(f" {label}: {value:.1f} {stats['unit']}")
|
|
327
|
+
click.echo()
|
|
328
|
+
|
|
329
|
+
ci = stats["confidence_intervals"]["95%"]
|
|
330
|
+
click.secho("Confidence Interval:", bold=True)
|
|
331
|
+
click.echo(f" 95% CI: [{ci[0]:.1f}, {ci[1]:.1f}] {stats['unit']}")
|
|
332
|
+
click.echo()
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
if __name__ == "__main__":
|
|
336
|
+
main()
|