monte 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fe28c6cadcd331c236ef5dd53e43b9d32d18b9585fbcc7debc820e1abcb28394
4
+ data.tar.gz: 998b21dbf4105649811833eef488e9ad858876759f9bb974f5805937655bacad
5
+ SHA512:
6
+ metadata.gz: 26eb9d0115fad3fc93ecf4dd3d007d35573da5e0468ca6fa04c91fedc85499a79edda481a7af08a1be643e0a14229ce5b5a23d8e5388d61e652f63ea55b177b8
7
+ data.tar.gz: 8c1bc63cefcde03c48e2d2217415b20469ed9937d75c428b9ad701c12bff625ff59040025ae2954597f6fe3e7ad434980a39180c4cbcd3cdcdff3c4d97c01f16
@@ -0,0 +1,13 @@
1
+ /.yardoc
2
+ /.bundle
3
+ /coverage/
4
+ /Gemfile.lock
5
+ /rdoc/
6
+ /doc/
7
+ /tmp/
8
+ /pkg/
9
+ /log/
10
+ /lib/github_cli/man/u
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.6.3
6
+ before_install: gem install bundler -v 2.1.2
@@ -0,0 +1,6 @@
1
+ # Change log
2
+
3
+ ## [v0.0.1] - 2020-11-08
4
+
5
+ ### Added
6
+ * Added core command: `carlo`
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in monte.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
7
+ gem "rspec", "~> 3.0"
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Andrew Werner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,65 @@
1
+ # Monte
2
+
3
+ Monte is a simple gem designed to help software engineers and developers answer
4
+ the question 'When will it be done?'.
5
+
6
+ The tool uses the Monte Carlo method to provide a reasonable forecast of the
7
+ future based on statistically relevant data from the past.
8
+ ## Installation
9
+
10
+ You must have ruby installed to use this tool.
11
+
12
+ start by installing the gem.
13
+
14
+ ```sh
15
+ $ gem install monte
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ The following is the simplest version of Monte which should be used when you
21
+ have little previous data with which to build your forecast.
22
+
23
+ ```sh
24
+ $ monte carlo
25
+ >
26
+ > __ __ _
27
+ > | \/ | ___ _ __ | |_ ___
28
+ > | |\/| | / _ \ | '_ \ | __| / _ \
29
+ > | | | | | (_) | | | | | | |_ | __/
30
+ > |_| |_| \___/ |_| |_| \__| \___|
31
+ >
32
+ > Please answer the following:
33
+ >
34
+ > How many items do you have in your backlog? 40
35
+ > How certain are you with regard to the scope of the work? medium
36
+ > When will you start work (e.g. 28/04/2021) 2020-11-15
37
+ > What is the smallest number of tasks/tickets you have completed in a week? 2
38
+ > What is the largest number of tasks/tickets you have completed in a week? 6
39
+ > How many simulations would you like to run 10000
40
+ >
41
+ >
42
+ > Your Results
43
+ >
44
+ > ┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
45
+ > │ 5% │ 15% │ 30% │ 50% │ 70% │ 85% │ 95% │
46
+ > ├──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
47
+ > │2021-02-14│2021-02-21│2021-02-28│2021-02-28│2021-03-07│2021-03-14│2021-03-21│
48
+ > └──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
49
+ ```
50
+
51
+ ## Development
52
+
53
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
54
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
55
+ prompt that will allow you to experiment.
56
+
57
+ To install this gem onto your local machine, run `bundle exec rake install`. To
58
+ release a new version, update the version number in `version.rb`, and then run
59
+ `bundle exec rake release`, which will create a git tag for the version, push
60
+ git commits and tags, and push the `.gem` file to
61
+ [rubygems.org](https://rubygems.org).
62
+
63
+ ## Copyright
64
+
65
+ Copyright (c) 2020 Andrew Werner. See [MIT License](LICENSE.txt) for further details.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "monte"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_path = File.expand_path('../lib', __dir__)
5
+ $:.unshift(lib_path) if !$:.include?(lib_path)
6
+ require 'monte/cli'
7
+
8
+ Signal.trap('INT') do
9
+ warn("\n#{caller.join("\n")}: interrupted")
10
+ exit(1)
11
+ end
12
+
13
+ begin
14
+ Monte::CLI.start
15
+ rescue Monte::CLI::Error => err
16
+ puts "ERROR: #{err.message}"
17
+ exit 1
18
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'monte/version'
4
+
5
+ module Monte
6
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Monte
6
+ # Handle the application command line parsing
7
+ # and the dispatch to various command objects
8
+ #
9
+ # @api public
10
+ class CLI < Thor
11
+ # Error raised by this runner
12
+ Error = Class.new(StandardError)
13
+
14
+ desc 'version', 'monte version'
15
+ def version
16
+ require_relative 'version'
17
+ puts "v#{Monte::VERSION}"
18
+ end
19
+ map %w[--version -v] => :version
20
+
21
+ desc 'carlo', 'Runs through a set of questions to generate a forecast for project completion'
22
+ method_option :help, aliases: '-h', type: :boolean,
23
+ desc: 'Display usage information'
24
+ def carlo
25
+ if options[:help]
26
+ invoke :help, ['carlo']
27
+ else
28
+ require_relative 'commands/carlo'
29
+ Monte::Commands::Carlo.new(options).execute
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Monte
6
+ class Command
7
+ extend Forwardable
8
+
9
+ def_delegators :command, :run
10
+
11
+ # Execute this command
12
+ #
13
+ # @api public
14
+ def execute(*)
15
+ raise(
16
+ NotImplementedError,
17
+ "#{self.class}##{__method__} must be implemented"
18
+ )
19
+ end
20
+
21
+ # The external commands runner
22
+ #
23
+ # @see http://www.rubydoc.info/gems/tty-command
24
+ #
25
+ # @api public
26
+ def command(**options)
27
+ require 'tty-command'
28
+ TTY::Command.new(options)
29
+ end
30
+
31
+ # The cursor movement
32
+ #
33
+ # @see http://www.rubydoc.info/gems/tty-cursor
34
+ #
35
+ # @api public
36
+ def cursor
37
+ require 'tty-cursor'
38
+ TTY::Cursor
39
+ end
40
+
41
+ # Open a file or text in the user's preferred editor
42
+ #
43
+ # @see http://www.rubydoc.info/gems/tty-editor
44
+ #
45
+ # @api public
46
+ def editor
47
+ require 'tty-editor'
48
+ TTY::Editor
49
+ end
50
+
51
+ # File manipulation utility methods
52
+ #
53
+ # @see http://www.rubydoc.info/gems/tty-file
54
+ #
55
+ # @api public
56
+ def generator
57
+ require 'tty-file'
58
+ TTY::File
59
+ end
60
+
61
+ # Terminal output paging
62
+ #
63
+ # @see http://www.rubydoc.info/gems/tty-pager
64
+ #
65
+ # @api public
66
+ def pager(**options)
67
+ require 'tty-pager'
68
+ TTY::Pager.new(options)
69
+ end
70
+
71
+ # Terminal platform and OS properties
72
+ #
73
+ # @see http://www.rubydoc.info/gems/tty-pager
74
+ #
75
+ # @api public
76
+ def platform
77
+ require 'tty-platform'
78
+ TTY::Platform.new
79
+ end
80
+
81
+ # The interactive prompt
82
+ #
83
+ # @see http://www.rubydoc.info/gems/tty-prompt
84
+ #
85
+ # @api public
86
+ def prompt(**options)
87
+ require 'tty-prompt'
88
+ TTY::Prompt.new(options)
89
+ end
90
+
91
+ # Get terminal screen properties
92
+ #
93
+ # @see http://www.rubydoc.info/gems/tty-screen
94
+ #
95
+ # @api public
96
+ def screen
97
+ require 'tty-screen'
98
+ TTY::Screen
99
+ end
100
+
101
+ # The unix which utility
102
+ #
103
+ # @see http://www.rubydoc.info/gems/tty-which
104
+ #
105
+ # @api public
106
+ def which(*args)
107
+ require 'tty-which'
108
+ TTY::Which.which(*args)
109
+ end
110
+
111
+ # Check if executable exists
112
+ #
113
+ # @see http://www.rubydoc.info/gems/tty-which
114
+ #
115
+ # @api public
116
+ def exec_exist?(*args)
117
+ require 'tty-which'
118
+ TTY::Which.exist?(*args)
119
+ end
120
+
121
+ def table(headers, rows)
122
+ require 'tty-table'
123
+ TTY::Table.new(headers, rows)
124
+ end
125
+
126
+ def large_title(title)
127
+ require 'tty-font'
128
+ TTY::Font.new(:standard).write(title)
129
+ end
130
+ end
131
+ end
@@ -0,0 +1 @@
1
+ #
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative '../command'
5
+ require_relative '../simulation'
6
+
7
+ module Monte
8
+ module Commands
9
+ # Runs Monte Carlo Simulation to estimate how long a piece of work will take
10
+ class Carlo < Monte::Command
11
+ include Simulation
12
+ CERTAINTY = { 'low' => 1.8, 'medium' => 1.5, 'high' => 1.2 }.freeze
13
+ RUNS = { '10000' => 10_000, '1000' => 1000, '500' => 500 }.freeze
14
+ HEADERS = ['5%', '15%', '30%', '50%', '70%', '85%', '95%'].freeze
15
+ PERCENTILES = [0.05, 0.15, 0.3, 0.5, 0.7, 0.85, 0.95].freeze
16
+
17
+ def initialize(options)
18
+ @options = options
19
+ end
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!
25
+ results = percentiles(user_input)
26
+ output.puts("\n\nYour Results\n\n")
27
+ output.puts(create_table(results))
28
+ end
29
+
30
+ def create_table(rows)
31
+ table(HEADERS, [rows]).render(:unicode, alignment: [:center])
32
+ end
33
+
34
+ def create_header
35
+ large_title('Monte')
36
+ end
37
+
38
+ def ask_questions!
39
+ prompt.collect do
40
+ key(:backlog).ask('How many items do you have in your backlog?', convert: :int)
41
+ key(:split_factor).select('How certain are you with regard to the scope of the work?', CERTAINTY)
42
+ key(:start_date).ask('When will you start work (e.g. 28/04/2021)') do |q|
43
+ q.default Date.today
44
+ q.convert ->(input) { Date.parse(input.to_s) }
45
+ end
46
+ key(:low).ask('What is the smallest number of tasks/tickets you have completed in a week?', convert: :int)
47
+ key(:high).ask('What is the largest number of tasks/tickets you have completed in a week?', convert: :int)
48
+ key(:runs).select('How many simulations would you like to run', RUNS)
49
+ end
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The code to run n monte carlo simulations
4
+ # args is a hash that must contain the following:
5
+ # :backlog
6
+ # :split_factor
7
+ # :runs
8
+ # :low
9
+ # :high
10
+ module Simulation
11
+ PERCENTILES = [0.05, 0.15, 0.3, 0.5, 0.7, 0.85, 0.95].freeze
12
+
13
+ def percentiles(args)
14
+ results = run_simulations(args).sort
15
+ PERCENTILES.map do |percentile|
16
+ index = args[:runs] * (percentile - 1)
17
+ results[index]
18
+ end
19
+ end
20
+
21
+ def run_simulations(args)
22
+ estimated_backlog = args[:backlog] * args[:split_factor]
23
+ Array.new(args[:runs]) do |_|
24
+ args[:start_date] + simulate(
25
+ estimated_backlog,
26
+ args[:low],
27
+ args[:high]
28
+ ) * 7
29
+ end
30
+ end
31
+
32
+ def simulate(backlog, low, high, result = 0)
33
+ return result if backlog <= 0
34
+
35
+ simulate(
36
+ backlog - rand(low..high),
37
+ low,
38
+ high,
39
+ result + 1
40
+ )
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module Monte
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/monte/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'monte'
7
+ spec.license = 'MIT'
8
+ spec.version = Monte::VERSION
9
+ spec.authors = ['Andrew Werner']
10
+ spec.email = ['awerner1@googlemail.com']
11
+
12
+ spec.summary = 'Monte Carlo forecasting for engineering projects'
13
+ spec.description = "If you are an engineer who is being asked, 'When will
14
+ it be done?' then Monte can help by using the Monte Carlo method to provide
15
+ you with plausile forecasts based on historic data."
16
+ spec.homepage = 'https://github.com/ALRW/monte'
17
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/ALRW/monte.git'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/ALRW/monte/blob/master/CHANGELOG.md'
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: monte
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Werner
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-11-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |-
14
+ If you are an engineer who is being asked, 'When will
15
+ it be done?' then Monte can help by using the Monte Carlo method to provide
16
+ you with plausile forecasts based on historic data.
17
+ email:
18
+ - awerner1@googlemail.com
19
+ executables:
20
+ - monte
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - ".gitignore"
25
+ - ".rspec"
26
+ - ".travis.yml"
27
+ - CHANGELOG.md
28
+ - Gemfile
29
+ - LICENSE.txt
30
+ - README.md
31
+ - Rakefile
32
+ - bin/console
33
+ - bin/setup
34
+ - exe/monte
35
+ - lib/monte.rb
36
+ - lib/monte/cli.rb
37
+ - lib/monte/command.rb
38
+ - lib/monte/commands/.gitkeep
39
+ - lib/monte/commands/carlo.rb
40
+ - lib/monte/simulation.rb
41
+ - lib/monte/version.rb
42
+ - monte.gemspec
43
+ homepage: https://github.com/ALRW/monte
44
+ licenses:
45
+ - MIT
46
+ metadata:
47
+ homepage_uri: https://github.com/ALRW/monte
48
+ source_code_uri: https://github.com/ALRW/monte.git
49
+ changelog_uri: https://github.com/ALRW/monte/blob/master/CHANGELOG.md
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: 2.3.0
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubygems_version: 3.1.2
66
+ signing_key:
67
+ specification_version: 4
68
+ summary: Monte Carlo forecasting for engineering projects
69
+ test_files: []