monte 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +7 -1
- data/README.md +55 -24
- data/lib/monte/cli.rb +1 -1
- data/lib/monte/commands/carlo.rb +56 -24
- data/lib/monte/csv_data.rb +19 -0
- data/lib/monte/simulation.rb +11 -13
- data/lib/monte/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ecddb357e33227ebed1a3149df0a58bc61b0762a76c895a10d329cb27c504460
|
|
4
|
+
data.tar.gz: 0163ead370cdc96747a039961fcb5a8835cf93998bf3737d05ca0f8d409b6fe1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0298468a2c201f869396178d9429bab012a9fc2f873912079c07d0952af3c66e435915b5d804efbae30855669cffcb6c15ace1430cc29b7f4b6eecc2dfd052db'
|
|
7
|
+
data.tar.gz: 1ed6f3bf20fe4a997493a453263372ecc46a4bdbad30db511423c71c32d26b3ea49567edd4f8eaf218522569c3dd95d53c269e01f4990068e6e8d44201642bfd
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
|
-
## [v0.0
|
|
3
|
+
## [v0.2.0] - 2020-11-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
* Updated the `carlo` command to ask for a JIRA data export that can be used to
|
|
7
|
+
build a distribution of historic throughput based on a JQL filter
|
|
8
|
+
|
|
9
|
+
## [v0.1.0] - 2020-11-08
|
|
4
10
|
|
|
5
11
|
### Added
|
|
6
12
|
* Added core command: `carlo`
|
data/README.md
CHANGED
|
@@ -22,32 +22,63 @@ have little previous data with which to build your forecast.
|
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
24
|
$ monte carlo
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
> ├──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
|
|
47
|
-
> │2021-02-14│2021-02-21│2021-02-28│2021-02-28│2021-03-07│2021-03-14│2021-03-21│
|
|
48
|
-
> └──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
|
|
25
|
+
| \/ | ___ _ __ | |_ ___
|
|
26
|
+
| |\/| | / _ \ | '_ \ | __| / _ \
|
|
27
|
+
| | | | | (_) | | | | | | |_ | __/
|
|
28
|
+
|_| |_| \___/ |_| |_| \__| \___|
|
|
29
|
+
|
|
30
|
+
Welcome to Monte, a tool to help you answer the question: 'When will the work be done?'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Please answer the following questions
|
|
34
|
+
How many tasks/tickets do you have left to complete? 40
|
|
35
|
+
Do you have a JIRA csv export to use? no
|
|
36
|
+
Enter the smallest number of tasks/tickets you have finished in a week? 2
|
|
37
|
+
Enter the largest number of tasks/tickets you have finished in a week? 6
|
|
38
|
+
When will you start work (e.g. 28/04/2021) 2020-11-21
|
|
39
|
+
How certain are you with regard to the scope of the work? high
|
|
40
|
+
How many simulations would you like to run? 10000
|
|
41
|
+
┌──────────────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
|
|
42
|
+
│Forecast Certainty│ 5% │ 15% │ 30% │ 50% │ 70% │ 85% │ 95% │
|
|
43
|
+
├──────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
|
|
44
|
+
│ Forecast Date │2021-02-06│2021-02-06│2021-02-13│2021-02-13│2021-02-20│2021-02-27│2021-03-06│
|
|
45
|
+
└──────────────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
|
|
49
46
|
```
|
|
50
47
|
|
|
48
|
+
Forecasting like this works better when you use real data from the past to model
|
|
49
|
+
what the future will look like. To enable this Monte can use a JIRA export of
|
|
50
|
+
tasks completed over whatever historic timeframe you would like. Once you have
|
|
51
|
+
created your JQL filter ensure that you include the "Resolved" column and then
|
|
52
|
+
export as a csv. When running the app you will be given the option to specify
|
|
53
|
+
the path of the file and this will then build a distribution of previous
|
|
54
|
+
throughputs for use in the simulations. The following is a runthrough using a
|
|
55
|
+
test data file.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
__ __ _
|
|
59
|
+
| \/ | ___ _ __ | |_ ___
|
|
60
|
+
| |\/| | / _ \ | '_ \ | __| / _ \
|
|
61
|
+
| | | | | (_) | | | | | | |_ | __/
|
|
62
|
+
|_| |_| \___/ |_| |_| \__| \___|
|
|
63
|
+
|
|
64
|
+
Welcome to Monte, a tool to help you answer the question: 'When will the work be done?'
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
Please answer the following questions
|
|
68
|
+
How many tasks/tickets do you have left to complete? 40
|
|
69
|
+
Do you have a JIRA csv export to use? Yes
|
|
70
|
+
what is the absolute file path to the csv file /Users/user/directory/monte/spec/test_data/data.csv
|
|
71
|
+
When will you start work (e.g. 28/04/2021) 2020-11-21
|
|
72
|
+
How certain are you with regard to the scope of the work? medium
|
|
73
|
+
How many simulations would you like to run? 10000
|
|
74
|
+
┌──────────────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
|
|
75
|
+
│Forecast Certainty│ 5% │ 15% │ 30% │ 50% │ 70% │ 85% │ 95% │
|
|
76
|
+
├──────────────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
|
|
77
|
+
│ Forecast Date │2021-02-06│2021-02-13│2021-02-20│2021-03-06│2021-03-13│2021-03-27│2021-04-10│
|
|
78
|
+
└──────────────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
|
|
51
82
|
## Development
|
|
52
83
|
|
|
53
84
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
data/lib/monte/cli.rb
CHANGED
data/lib/monte/commands/carlo.rb
CHANGED
|
@@ -3,52 +3,84 @@
|
|
|
3
3
|
require 'date'
|
|
4
4
|
require_relative '../command'
|
|
5
5
|
require_relative '../simulation'
|
|
6
|
+
require_relative '../csv_data'
|
|
6
7
|
|
|
7
8
|
module Monte
|
|
8
9
|
module Commands
|
|
9
10
|
# Runs Monte Carlo Simulation to estimate how long a piece of work will take
|
|
10
11
|
class Carlo < Monte::Command
|
|
11
12
|
include Simulation
|
|
12
|
-
|
|
13
|
+
include CSVData
|
|
14
|
+
BLURB = %(Welcome to Monte, a tool to help you answer the question: 'When will the work be done?'\n\n
|
|
15
|
+
Please answer the following questions\n)
|
|
16
|
+
CERTAINTY = { 'certain' => 1.0, 'high' => 1.2, 'medium' => 1.5, 'low' => 1.8 }.freeze
|
|
13
17
|
RUNS = { '10000' => 10_000, '1000' => 1000, '500' => 500 }.freeze
|
|
14
|
-
HEADERS = ['5%', '15%', '30%', '50%', '70%', '85%', '95%'].freeze
|
|
18
|
+
HEADERS = ['Forecast Certainty', '5%', '15%', '30%', '50%', '70%', '85%', '95%'].freeze
|
|
15
19
|
PERCENTILES = [0.05, 0.15, 0.3, 0.5, 0.7, 0.85, 0.95].freeze
|
|
16
20
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def execute(output: $stdout)
|
|
22
|
-
output.puts(create_header)
|
|
23
|
-
output.puts("Please answer the following:\n\n")
|
|
24
|
-
user_input = ask_questions!
|
|
21
|
+
def execute
|
|
22
|
+
puts(create_header, BLURB)
|
|
23
|
+
user_input = ask_questions({})
|
|
25
24
|
results = percentiles(user_input)
|
|
26
|
-
|
|
27
|
-
output.puts(create_table(results))
|
|
25
|
+
puts(create_table(results))
|
|
28
26
|
end
|
|
29
27
|
|
|
30
28
|
def create_table(rows)
|
|
31
|
-
table(HEADERS, [rows
|
|
29
|
+
table(HEADERS, [rows.prepend('Forecast Date')])
|
|
30
|
+
.render(:unicode, alignment: [:center])
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
def create_header
|
|
35
34
|
large_title('Monte')
|
|
36
35
|
end
|
|
37
36
|
|
|
38
|
-
def ask_questions
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
37
|
+
def ask_questions(options)
|
|
38
|
+
q1 = ask_backlog(options)
|
|
39
|
+
q2 = ask_throughput(q1)
|
|
40
|
+
q3 = ask_start(q2)
|
|
41
|
+
q4 = ask_split(q3)
|
|
42
|
+
ask_runs(q4)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def ask_backlog(options)
|
|
46
|
+
options.merge(backlog:
|
|
47
|
+
prompt.ask('How many tasks/tickets do you have left to complete?',
|
|
48
|
+
required: true,
|
|
49
|
+
convert: :int))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def ask_split(options)
|
|
53
|
+
options.merge(split:
|
|
54
|
+
prompt.select('How certain are you with regard to the scope of the work?', CERTAINTY))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ask_start(options)
|
|
58
|
+
options.merge(start:
|
|
59
|
+
prompt.ask('When will you start work (e.g. 28/04/2021)') do |q|
|
|
60
|
+
q.required true
|
|
61
|
+
q.default Date.today
|
|
62
|
+
q.convert ->(input) { Date.parse(input.to_s) }
|
|
63
|
+
end)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def ask_throughput(options)
|
|
67
|
+
data_exists = prompt.yes?('Do you have a JIRA csv export to use?', required: true)
|
|
68
|
+
if data_exists
|
|
69
|
+
path = prompt.ask('what is the absolute file path to the csv file', required: true)
|
|
70
|
+
throughput = historic_throughput(path)
|
|
71
|
+
else
|
|
72
|
+
low = prompt.ask('Enter the smallest number of tasks/tickets you have finished in a week?', convert: :int)
|
|
73
|
+
high = prompt.ask('Enter the largest number of tasks/tickets you have finished in a week?', convert: :int)
|
|
74
|
+
throughput = (low..high).to_a
|
|
49
75
|
end
|
|
76
|
+
options.merge(throughput: throughput)
|
|
50
77
|
end
|
|
51
78
|
|
|
79
|
+
def ask_runs(options)
|
|
80
|
+
options.merge(
|
|
81
|
+
runs: prompt.select('How many simulations would you like to run?', RUNS)
|
|
82
|
+
)
|
|
83
|
+
end
|
|
52
84
|
end
|
|
53
85
|
end
|
|
54
86
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'csv'
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
6
|
+
# This module supports parsing an export of Jira data to build a sample of
|
|
7
|
+
# historic throughputs that provide a more accurate forecast
|
|
8
|
+
# It takes the absolute path to the csv export and returns an array containing
|
|
9
|
+
# the number of tasks/tickets completed each week
|
|
10
|
+
module CSVData
|
|
11
|
+
RESOLVED = 'Resolved'
|
|
12
|
+
def historic_throughput(path)
|
|
13
|
+
headers, *data = CSV.read(path)
|
|
14
|
+
resolved_column = headers.index(RESOLVED)
|
|
15
|
+
resolved_dates = data.flat_map { |row| Date.parse row[resolved_column] }
|
|
16
|
+
grouped_into_weeks = resolved_dates.group_by { |date| date - date.wday }
|
|
17
|
+
grouped_into_weeks.values.map(&:length)
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/monte/simulation.rb
CHANGED
|
@@ -10,32 +10,30 @@
|
|
|
10
10
|
module Simulation
|
|
11
11
|
PERCENTILES = [0.05, 0.15, 0.3, 0.5, 0.7, 0.85, 0.95].freeze
|
|
12
12
|
|
|
13
|
-
def percentiles(
|
|
14
|
-
results = run_simulations(
|
|
13
|
+
def percentiles(options)
|
|
14
|
+
results = run_simulations(options).sort
|
|
15
15
|
PERCENTILES.map do |percentile|
|
|
16
|
-
index =
|
|
16
|
+
index = options[:runs] * (percentile - 1)
|
|
17
17
|
results[index]
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def run_simulations(
|
|
22
|
-
estimated_backlog =
|
|
23
|
-
Array.new(
|
|
24
|
-
|
|
21
|
+
def run_simulations(options)
|
|
22
|
+
estimated_backlog = options[:backlog] * options[:split]
|
|
23
|
+
Array.new(options[:runs]) do |_|
|
|
24
|
+
options[:start] + simulate(
|
|
25
25
|
estimated_backlog,
|
|
26
|
-
|
|
27
|
-
args[:high]
|
|
26
|
+
options[:throughput]
|
|
28
27
|
) * 7
|
|
29
28
|
end
|
|
30
29
|
end
|
|
31
30
|
|
|
32
|
-
def simulate(backlog,
|
|
31
|
+
def simulate(backlog, throughput, result = 0)
|
|
33
32
|
return result if backlog <= 0
|
|
34
33
|
|
|
35
34
|
simulate(
|
|
36
|
-
backlog -
|
|
37
|
-
|
|
38
|
-
high,
|
|
35
|
+
backlog - throughput.sample,
|
|
36
|
+
throughput,
|
|
39
37
|
result + 1
|
|
40
38
|
)
|
|
41
39
|
end
|
data/lib/monte/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: monte
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Werner
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-11-
|
|
11
|
+
date: 2020-11-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |-
|
|
14
14
|
If you are an engineer who is being asked, 'When will
|
|
@@ -37,6 +37,7 @@ files:
|
|
|
37
37
|
- lib/monte/command.rb
|
|
38
38
|
- lib/monte/commands/.gitkeep
|
|
39
39
|
- lib/monte/commands/carlo.rb
|
|
40
|
+
- lib/monte/csv_data.rb
|
|
40
41
|
- lib/monte/simulation.rb
|
|
41
42
|
- lib/monte/version.rb
|
|
42
43
|
- monte.gemspec
|