monte 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|