smcty 0.0.1
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 +7 -0
- data/bin/smcty +4 -0
- data/lib/smcty.rb +1 -0
- data/lib/smcty/allocation.rb +26 -0
- data/lib/smcty/configuration.rb +61 -0
- data/lib/smcty/configurator.rb +178 -0
- data/lib/smcty/console.rb +229 -0
- data/lib/smcty/factory.rb +105 -0
- data/lib/smcty/helpers.rb +16 -0
- data/lib/smcty/job.rb +72 -0
- data/lib/smcty/output.rb +71 -0
- data/lib/smcty/planner.rb +13 -0
- data/lib/smcty/production.rb +31 -0
- data/lib/smcty/project.rb +23 -0
- data/lib/smcty/resource.rb +46 -0
- data/lib/smcty/scheduling.rb +198 -0
- data/lib/smcty/store.rb +115 -0
- data/lib/smcty/version.rb +3 -0
- metadata +62 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 066404cac54ab6ea59d05f19655509a73fe25879
|
4
|
+
data.tar.gz: 9fb49f6ed6a8d580f4f63d8558ce012b5ebc1fab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79c2493d00915a80980b58991bd7f13a38fdb5f0fd5ebb79884c29f09cbcb584d359824b01085a5a7488a1a458c5bd5d27659f130cae8cda89751e0d13a9e969
|
7
|
+
data.tar.gz: dde05154fcd122b9f13c0907716bfb8f0cebfdcad6b4a8756bcdf8238ab3407deb760266b556bcbb20eabb848fa1504b4bc075ad38ac8c294c05a011abd7e5a3
|
data/bin/smcty
ADDED
data/lib/smcty.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'smcty/planner'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Allocation
|
3
|
+
attr_reader :store, :resource, :amount
|
4
|
+
|
5
|
+
def initialize(store, resource, amount)
|
6
|
+
@store = store
|
7
|
+
@resource = resource
|
8
|
+
@amount = amount
|
9
|
+
end
|
10
|
+
|
11
|
+
def valid?
|
12
|
+
@store.valid?(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def get
|
16
|
+
@store.get(self)
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
{
|
21
|
+
"resource" => @resource.name,
|
22
|
+
"amount" => @amount
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Configuration
|
3
|
+
attr_reader :created_at, :store, :scheduler
|
4
|
+
|
5
|
+
def initialize(store)
|
6
|
+
@created_at = Time.new
|
7
|
+
@store = store
|
8
|
+
@factories = {}
|
9
|
+
@scheduler = Scheduling.new(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def register_factory(factory)
|
13
|
+
@factories[factory.name] = factory
|
14
|
+
end
|
15
|
+
|
16
|
+
def resource_by_name(resource_name)
|
17
|
+
@factories.values.each do |factory|
|
18
|
+
resource = factory.resource_by_name(resource_name)
|
19
|
+
return resource if resource
|
20
|
+
end
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def resources
|
25
|
+
result = []
|
26
|
+
@factories.values.each do |f|
|
27
|
+
result += f.resources
|
28
|
+
end
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
def factory_for(resource)
|
33
|
+
@factories.values.each do |factory|
|
34
|
+
found = factory.resource_by_name(resource.name)
|
35
|
+
return factory if found
|
36
|
+
end
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def factories
|
41
|
+
@factories.keys.sort
|
42
|
+
end
|
43
|
+
|
44
|
+
def factory(name)
|
45
|
+
@factories[name]
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"Configuration created at #{@created_at}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_hash
|
53
|
+
{
|
54
|
+
"store" => @store.to_hash,
|
55
|
+
"factories" => @factories.values.map{|f| f.to_hash},
|
56
|
+
"scheduling" => @scheduler.to_hash
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'smcty/store'
|
4
|
+
require 'smcty/configuration'
|
5
|
+
require 'smcty/factory'
|
6
|
+
require 'smcty/resource'
|
7
|
+
|
8
|
+
module Smcty
|
9
|
+
class Configurator
|
10
|
+
attr_reader :configuration
|
11
|
+
|
12
|
+
def initialize(config_path)
|
13
|
+
@config_path = config_path
|
14
|
+
@configuration = Configurator.read_configuration(config_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def save
|
18
|
+
Configurator.write_configuration(@configuration, @config_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.read_configuration(path)
|
22
|
+
file = File.read(path)
|
23
|
+
data_hash = JSON.parse(file)
|
24
|
+
|
25
|
+
if (data_hash && data_hash != {})
|
26
|
+
store_hash = read_value(data_hash, "store", true)
|
27
|
+
store_name = read_value(store_hash, "name", true)
|
28
|
+
store_capacity = read_value(store_hash, "capacity", true)
|
29
|
+
store = Store.new(store_name, store_capacity)
|
30
|
+
|
31
|
+
configuration = Configuration.new(store)
|
32
|
+
|
33
|
+
deferred_dependencies = []
|
34
|
+
|
35
|
+
factories_list = read_value(data_hash, "factories", true)
|
36
|
+
factories_list.each do |factory_hash|
|
37
|
+
factory_name = read_value(factory_hash, "name", true)
|
38
|
+
factory_capacity = read_value(factory_hash, "capacity", true)
|
39
|
+
factory_sequential = read_value(factory_hash, "sequential", false, false)
|
40
|
+
|
41
|
+
factory = Factory.new(factory_name, factory_capacity, factory_sequential)
|
42
|
+
configuration.register_factory(factory)
|
43
|
+
|
44
|
+
factory_resources = read_value(factory_hash, "resources", true)
|
45
|
+
factory_resources.each do |resources_hash|
|
46
|
+
resource_name = read_value(resources_hash, "name", true)
|
47
|
+
resource_description = read_value(resources_hash, "description", true)
|
48
|
+
resource_time = read_value(resources_hash, "time", true)
|
49
|
+
|
50
|
+
resource = Resource.new(resource_name, resource_description)
|
51
|
+
factory.register_resource(resource, resource_time)
|
52
|
+
|
53
|
+
resource_deps = read_value(resources_hash, "deps", false, [])
|
54
|
+
resource_deps.each do |dependency_hash|
|
55
|
+
dependency_name = read_value(dependency_hash, "name", true)
|
56
|
+
dependency_amount = read_value(dependency_hash, "amount", true)
|
57
|
+
|
58
|
+
if dep_resource = configuration.resource_by_name(dependency_name)
|
59
|
+
resource.register_dependency(dep_resource, dependency_amount)
|
60
|
+
else
|
61
|
+
deferred_dependencies << {resource: resource,
|
62
|
+
dependency_name: dependency_name,
|
63
|
+
dependency_amount: dependency_amount
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# post process deferred dependencies
|
71
|
+
deferred_dependencies.each do |deferred_dep|
|
72
|
+
resource = deferred_dep[:resource]
|
73
|
+
dependency_name = deferred_dep[:dependency_name]
|
74
|
+
amount = deferred_dep[:dependency_amount]
|
75
|
+
|
76
|
+
dependent_resource = configuration.resource_by_name(dependency_name)
|
77
|
+
unless dependent_resource
|
78
|
+
raise "Cannot fullfill dependency from #{resource.name} -> #{dependency_name}"
|
79
|
+
end
|
80
|
+
|
81
|
+
resource.register_dependency(dependent_resource, amount)
|
82
|
+
end
|
83
|
+
|
84
|
+
# post process stock of store
|
85
|
+
read_value(store_hash, "stock", false, []).each do |stock_hash|
|
86
|
+
resource_name = read_value(stock_hash, "name", true)
|
87
|
+
resource_amount = read_value(stock_hash, "amount", true)
|
88
|
+
|
89
|
+
resource = configuration.resource_by_name(resource_name) || Resource.new(resource_name, "unmanaged")
|
90
|
+
store.put(resource, resource_amount)
|
91
|
+
end
|
92
|
+
|
93
|
+
# post process the scheduling information
|
94
|
+
scheduling_hash = read_value(data_hash, "scheduling", true)
|
95
|
+
read_value(scheduling_hash, "projects", false, []).each do |project_hash|
|
96
|
+
project_name = read_value(project_hash, "name", true)
|
97
|
+
project = Project.new(project_name)
|
98
|
+
read_value(project_hash, "requirements", true).each do |req|
|
99
|
+
resource_name = read_value(req, "resource", true)
|
100
|
+
amount = read_value(req, "amount", true).to_i
|
101
|
+
resource = configuration.resource_by_name(resource_name)
|
102
|
+
unless resource
|
103
|
+
raise "Cannot register requirement for unknown resource #{resource_name}"
|
104
|
+
end
|
105
|
+
project.add_requirement(resource, amount)
|
106
|
+
end
|
107
|
+
|
108
|
+
job_dict = {}
|
109
|
+
job_dependencies = {}
|
110
|
+
jobs = []
|
111
|
+
|
112
|
+
# first pass to create the single jobs
|
113
|
+
read_value(project_hash, "jobs", true).each do |job_hash|
|
114
|
+
reference = read_value(job_hash, "id", true)
|
115
|
+
resource_name = read_value(job_hash, "resource", true)
|
116
|
+
resource = configuration.resource_by_name(resource_name)
|
117
|
+
unless resource
|
118
|
+
raise "Cannot create job for unknown resource #{resource_name}"
|
119
|
+
end
|
120
|
+
job = Job.new(resource)
|
121
|
+
job_dict[reference] = job
|
122
|
+
jobs << job
|
123
|
+
|
124
|
+
allocation_hash = read_value(job_hash, "allocation", false, nil)
|
125
|
+
if allocation_hash
|
126
|
+
amount = read_value(allocation_hash, "amount", true).to_i
|
127
|
+
allocation = store.allocate(resource, amount)
|
128
|
+
job.allocate(allocation) if allocation
|
129
|
+
end
|
130
|
+
|
131
|
+
production_hash = read_value(job_hash, "production", false, nil)
|
132
|
+
if production_hash
|
133
|
+
start_time = read_value(production_hash, "start_time", true).to_i
|
134
|
+
duration = read_value(production_hash, "duration", true).to_i
|
135
|
+
production = Production.new(resource, duration, start_time)
|
136
|
+
job.produce(production)
|
137
|
+
end
|
138
|
+
|
139
|
+
dependency_list = read_value(job_hash, "dependent_jobs", false, nil)
|
140
|
+
job_dependencies[job] = dependency_list if dependency_list
|
141
|
+
end
|
142
|
+
|
143
|
+
# second pass to connect dependent jobs
|
144
|
+
job_dependencies.keys.each do |job|
|
145
|
+
job_dependencies[job].each do |dep_ref|
|
146
|
+
dep_job = job_dict[dep_ref]
|
147
|
+
unless dep_job
|
148
|
+
raise "job references another job which is not known (#{dep_ref})"
|
149
|
+
end
|
150
|
+
job.add_dependent(dep_job)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
configuration.scheduler.load_project(project, jobs)
|
155
|
+
end
|
156
|
+
|
157
|
+
configuration
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.write_configuration(configuration, path)
|
162
|
+
File.open(path,"w") do |f|
|
163
|
+
f.write(configuration.to_hash.to_json)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def self.read_value(hash, key, force, default=nil)
|
170
|
+
value = hash[key]
|
171
|
+
if force && (value == nil)
|
172
|
+
raise "The hash #{hash} does not contain the key #{key}"
|
173
|
+
end
|
174
|
+
value || default
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'smcty/configurator'
|
2
|
+
require 'smcty/output'
|
3
|
+
require 'smcty/helpers'
|
4
|
+
require 'smcty/production'
|
5
|
+
require 'smcty/project'
|
6
|
+
require 'smcty/scheduling'
|
7
|
+
|
8
|
+
module Smcty
|
9
|
+
class Console
|
10
|
+
|
11
|
+
# Initialize the console with the input stream (e.g. $stdin)
|
12
|
+
# and a path to smcty configuration file.
|
13
|
+
def initialize(input_stream, config_path)
|
14
|
+
@in = input_stream
|
15
|
+
@configurator = Configurator.new(config_path)
|
16
|
+
@configuration = @configurator.configuration
|
17
|
+
@productions = {}
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def prompt!
|
22
|
+
run = true
|
23
|
+
while run
|
24
|
+
"-> ".display
|
25
|
+
run = dispatch(@in.gets.chomp)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Commands
|
30
|
+
#
|
31
|
+
# quit
|
32
|
+
#
|
33
|
+
# store -> list the inventory of the store
|
34
|
+
# store put #item #amount
|
35
|
+
#
|
36
|
+
# factory -> list the registered factories
|
37
|
+
# factory #name -> list the resources and the production of the named factory
|
38
|
+
#
|
39
|
+
# resources -> list all managed resources
|
40
|
+
#
|
41
|
+
# project add #label [#resource:#amount]+
|
42
|
+
# project #label
|
43
|
+
#
|
44
|
+
# produce #resource
|
45
|
+
#
|
46
|
+
# pick #production-number
|
47
|
+
#
|
48
|
+
# next
|
49
|
+
#
|
50
|
+
def dispatch(input)
|
51
|
+
continue = true
|
52
|
+
commands = input.split(" ")
|
53
|
+
case commands[0].downcase
|
54
|
+
when "store"
|
55
|
+
continue = process_store(commands[1..-1])
|
56
|
+
when "factory"
|
57
|
+
continue = process_factory(commands[1..-1])
|
58
|
+
when "quit"
|
59
|
+
continue = process_quit
|
60
|
+
when "resources"
|
61
|
+
continue = process_resources
|
62
|
+
when "project"
|
63
|
+
continue = process_projects(commands[1..-1])
|
64
|
+
when "produce"
|
65
|
+
continue = process_production(commands[1..-1])
|
66
|
+
when "pick"
|
67
|
+
continue = process_pick(commands[1..-1])
|
68
|
+
when "next"
|
69
|
+
continue = process_next
|
70
|
+
when "save"
|
71
|
+
continue = process_save
|
72
|
+
when "scheduling"
|
73
|
+
continue = process_scheduling
|
74
|
+
else
|
75
|
+
puts "unknown command: #{input}"
|
76
|
+
end
|
77
|
+
return continue
|
78
|
+
end
|
79
|
+
|
80
|
+
def process_scheduling
|
81
|
+
@configuration.scheduler.print_job_lists
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def process_next
|
86
|
+
puts "DO > #{@configuration.scheduler.next}"
|
87
|
+
true
|
88
|
+
end
|
89
|
+
|
90
|
+
def process_projects(commands)
|
91
|
+
if commands.size == 1
|
92
|
+
puts "print the project"
|
93
|
+
elsif commands.size > 2
|
94
|
+
unless commands[0] == "add"
|
95
|
+
puts "unknow command #{commands[0]} for project is not known"
|
96
|
+
return true
|
97
|
+
end
|
98
|
+
project = Project.new(commands[1])
|
99
|
+
requirements = commands[2..-1]
|
100
|
+
requirements.each do |r|
|
101
|
+
items = r.split(":")
|
102
|
+
r_name = items[0]
|
103
|
+
amount = items[1].to_i
|
104
|
+
resource = @configuration.resource_by_name(r_name)
|
105
|
+
unless resource
|
106
|
+
puts "the resource #{resource_name} is not known"
|
107
|
+
return true
|
108
|
+
end
|
109
|
+
project.add_requirement(resource, amount)
|
110
|
+
end
|
111
|
+
@configuration.scheduler.plan_project(project)
|
112
|
+
puts "planned the project"
|
113
|
+
else
|
114
|
+
puts "not enough parameters for project command"
|
115
|
+
end
|
116
|
+
true
|
117
|
+
end
|
118
|
+
|
119
|
+
def process_pick(commands)
|
120
|
+
if commands.size != 1
|
121
|
+
puts "please pass the number of production to pick"
|
122
|
+
else
|
123
|
+
number = commands[0].to_i
|
124
|
+
production = @productions[number]
|
125
|
+
unless production
|
126
|
+
puts "the production number #{commands[0]} is not known"
|
127
|
+
return true
|
128
|
+
end
|
129
|
+
unless production.finished?
|
130
|
+
puts "the production is not finished yet"
|
131
|
+
return true
|
132
|
+
end
|
133
|
+
if @configuration.store.free_capacity > 0
|
134
|
+
factory = @configuration.factory_for(production.resource)
|
135
|
+
factory.pick(production)
|
136
|
+
@productions.delete(number)
|
137
|
+
@configuration.store.put(production.resource, 1)
|
138
|
+
puts "the item was stored"
|
139
|
+
else
|
140
|
+
puts "no storage left"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
true
|
144
|
+
end
|
145
|
+
|
146
|
+
def process_production(commands)
|
147
|
+
if commands.size != 1
|
148
|
+
puts "please name the resource to produce"
|
149
|
+
else
|
150
|
+
resource_name = commands[0]
|
151
|
+
resource = @configuration.resource_by_name(resource_name)
|
152
|
+
unless resource
|
153
|
+
puts "the resource #{resource_name} is not known"
|
154
|
+
return true
|
155
|
+
end
|
156
|
+
factory = @configuration.factory_for(resource)
|
157
|
+
allocations = []
|
158
|
+
resource.dependent_resources.each do |r|
|
159
|
+
allocation = @configuration.store.allocate(r, resource.dependent_resource_amount(r))
|
160
|
+
unless allocation
|
161
|
+
puts "Not enough of #{r.name} to produce #{resource_name}"
|
162
|
+
return true
|
163
|
+
end
|
164
|
+
allocations << allocation
|
165
|
+
end
|
166
|
+
production = factory.produce(resource, allocations)
|
167
|
+
@productions[production.object_id] = production
|
168
|
+
puts "Now producing: #{production.object_id} in factory: #{factory.name}"
|
169
|
+
end
|
170
|
+
true
|
171
|
+
end
|
172
|
+
|
173
|
+
def process_resources
|
174
|
+
list_resources(@configuration.resources)
|
175
|
+
true
|
176
|
+
end
|
177
|
+
|
178
|
+
def process_quit
|
179
|
+
puts "Storing configuration back to file and exit."
|
180
|
+
@configurator.save
|
181
|
+
false
|
182
|
+
end
|
183
|
+
|
184
|
+
def process_save
|
185
|
+
puts "Storing configuration back to file"
|
186
|
+
@configurator.save
|
187
|
+
true
|
188
|
+
end
|
189
|
+
|
190
|
+
def process_store(commands)
|
191
|
+
if commands.size == 0
|
192
|
+
list_inventory(@configuration.store)
|
193
|
+
elsif commands.size == 3
|
194
|
+
resource = @configuration.resource_by_name(commands[1])
|
195
|
+
amount = commands[2].to_i
|
196
|
+
if resource && amount > 0
|
197
|
+
case commands[0].downcase
|
198
|
+
when "put"
|
199
|
+
puts "put #{commands[2]} units of #{commands[1]} to store"
|
200
|
+
result = @configuration.store.put(resource, amount)
|
201
|
+
puts "new stock is: #{@configuration.store.stock(resource)}"
|
202
|
+
else
|
203
|
+
puts "operator on store was not recognized"
|
204
|
+
end
|
205
|
+
else
|
206
|
+
puts "resource '#{commands[1]}' or amount '#{commands[2]}' not valid"
|
207
|
+
end
|
208
|
+
else
|
209
|
+
puts "command not recognized"
|
210
|
+
end
|
211
|
+
true
|
212
|
+
end
|
213
|
+
|
214
|
+
def process_factory(commands)
|
215
|
+
if commands.size == 0
|
216
|
+
list_factories(@configuration.factories)
|
217
|
+
else
|
218
|
+
factory = @configuration.factory(commands[0])
|
219
|
+
if factory
|
220
|
+
list_factory(factory)
|
221
|
+
else
|
222
|
+
puts "The factory '#{commands[0]}' is not registered"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Factory
|
3
|
+
attr_reader :name, :capacity, :productions, :sequential
|
4
|
+
|
5
|
+
def initialize(name, capacity, sequential=false)
|
6
|
+
@name = name
|
7
|
+
@capacity = capacity
|
8
|
+
@resources = {}
|
9
|
+
@production_times = {}
|
10
|
+
@productions = []
|
11
|
+
@sequential = sequential
|
12
|
+
end
|
13
|
+
|
14
|
+
def free_capacity
|
15
|
+
capacity - @productions.size
|
16
|
+
end
|
17
|
+
|
18
|
+
def register_resource(resource, production_time)
|
19
|
+
@production_times[resource] = production_time
|
20
|
+
@resources[resource.name] = resource
|
21
|
+
end
|
22
|
+
|
23
|
+
def resources
|
24
|
+
@production_times.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def production_time(resource)
|
28
|
+
@production_times[resource]
|
29
|
+
end
|
30
|
+
|
31
|
+
def resource_by_name(name)
|
32
|
+
@resources[name]
|
33
|
+
end
|
34
|
+
|
35
|
+
def produce(resource, allocations=[])
|
36
|
+
unless free_capacity > 0
|
37
|
+
raise "The factory has no capacity for further production"
|
38
|
+
end
|
39
|
+
|
40
|
+
unless Factory.check_preconditions(resource, allocations)
|
41
|
+
raise "The preconditions for this production aren't met"
|
42
|
+
end
|
43
|
+
|
44
|
+
time = @production_times[resource]
|
45
|
+
unless time
|
46
|
+
raise "The resource #{resource.name} is not registered with this factory"
|
47
|
+
end
|
48
|
+
|
49
|
+
# in case of a sequential production the time must be extended
|
50
|
+
# find latest end time of a preceeding production
|
51
|
+
if sequential && latest = @productions.sort{|x,y| y.end_time <=> x.end_time}.first
|
52
|
+
time += (latest.end_time - Time.now).to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
allocations.each{ |a| a.get }
|
56
|
+
production = Production.new(resource, time)
|
57
|
+
@productions << production
|
58
|
+
production
|
59
|
+
end
|
60
|
+
|
61
|
+
def pick(production)
|
62
|
+
if production && production.finished?
|
63
|
+
@productions.delete(production)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
"Factory '#{@name}' with capacity of #{@capacity} is responsible for #{@resources.keys.join(", ")}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_hash
|
72
|
+
{
|
73
|
+
"name" => @name,
|
74
|
+
"capacity" => @capacity,
|
75
|
+
"sequential" => @sequential,
|
76
|
+
"resources" => @resources.values.map{|r| r.to_hash(@production_times[r])}
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def self.check_preconditions(resource, allocations)
|
83
|
+
allocated_resources = {}
|
84
|
+
allocations.each do |a|
|
85
|
+
if a.valid?
|
86
|
+
depot = allocated_resources[a.resource] || {amount: 0}
|
87
|
+
depot[:amount] += a.amount
|
88
|
+
allocated_resources[a.resource] = depot
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
resource.dependent_resources.each do |dr|
|
93
|
+
required_amount = resource.dependent_resource_amount(dr)
|
94
|
+
depot = allocated_resources[dr]
|
95
|
+
if depot && depot[:amount] >= required_amount
|
96
|
+
depot[:amount] -= required_amount
|
97
|
+
else
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
def natural_time(time)
|
2
|
+
unit = time
|
3
|
+
|
4
|
+
# given unit in seconds
|
5
|
+
hours = unit / 3600
|
6
|
+
unit = unit - (hours * 3600)
|
7
|
+
|
8
|
+
minutes = unit / 60
|
9
|
+
seconds = unit - (minutes * 60)
|
10
|
+
|
11
|
+
result = (hours > 0) ? "#{hours}h " : ""
|
12
|
+
result += (minutes > 0) ? "#{minutes}m " : ""
|
13
|
+
result += "#{seconds}s"
|
14
|
+
|
15
|
+
result.strip
|
16
|
+
end
|
data/lib/smcty/job.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Job
|
3
|
+
attr_reader :resource, :allocation, :production, :dependent_jobs
|
4
|
+
|
5
|
+
def initialize(resource)
|
6
|
+
@resource = resource
|
7
|
+
@used = false
|
8
|
+
@dependent_jobs = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def allocate(allocation)
|
12
|
+
@allocation = allocation
|
13
|
+
@production = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def produce(production)
|
17
|
+
@allocation = nil
|
18
|
+
@production = production
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_dependent(job)
|
22
|
+
@dependent_jobs << job
|
23
|
+
end
|
24
|
+
|
25
|
+
def reset_dependent_jobs
|
26
|
+
@dependent_jobs = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def dependencies?
|
30
|
+
@dependent_jobs.size > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def allocated?
|
34
|
+
@allocation != nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def in_production?
|
38
|
+
@production != nil && !@production.finished?
|
39
|
+
end
|
40
|
+
|
41
|
+
def ready?
|
42
|
+
@production != nil && @production.finished?
|
43
|
+
end
|
44
|
+
|
45
|
+
def new?
|
46
|
+
@allocation == nil && @production == nil && @dependent_jobs.size == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
def allocated_dependencies?
|
50
|
+
if dependencies?
|
51
|
+
@dependent_jobs.each do |job|
|
52
|
+
return false unless job.allocated?
|
53
|
+
end
|
54
|
+
return true
|
55
|
+
else
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_hash
|
61
|
+
core_hash = {
|
62
|
+
"id" => self.object_id,
|
63
|
+
"resource" => @resource.name,
|
64
|
+
}
|
65
|
+
core_hash["allocation"] = @allocation.to_hash if @allocation
|
66
|
+
core_hash["production"] = @production.to_hash if @production
|
67
|
+
core_hash["dependent_jobs"] = @dependent_jobs.map{|j| j.object_id} if @dependent_jobs.size > 0
|
68
|
+
core_hash
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
data/lib/smcty/output.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
def list_inventory(store)
|
2
|
+
puts "---------------------------------------------------------------------------------------"
|
3
|
+
puts store.to_s
|
4
|
+
puts "---------------------------------------------------------------------------------------"
|
5
|
+
puts "Current inventory:"
|
6
|
+
puts ""
|
7
|
+
store.inventory.each do |item|
|
8
|
+
puts "\t#{item.name}: #{store.available_stock(item)} items (allocated: #{store.allocated_stock(item)})"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def list_resources(resource_list)
|
13
|
+
puts "-------------------------------------------------------------------"
|
14
|
+
puts "Registered Resources:"
|
15
|
+
puts ""
|
16
|
+
resource_list.each do |r|
|
17
|
+
puts "\t#{r.to_s}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def list_project(project)
|
22
|
+
puts "-------------------------------------------------------------------"
|
23
|
+
puts "Project #{project.name}"
|
24
|
+
puts ""
|
25
|
+
project.resources.each do |r|
|
26
|
+
puts "\t#{r.name} - #{project.amount(r)}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def list_factories(factory_list)
|
31
|
+
puts "-------------------------------------------------------------------"
|
32
|
+
puts "Registered Factories:"
|
33
|
+
puts ""
|
34
|
+
factory_list.each do |f|
|
35
|
+
puts "\t#{f.to_s}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def list_factory(factory)
|
40
|
+
puts "-------------------------------------------------------------------"
|
41
|
+
puts factory.to_s
|
42
|
+
puts "sequential: #{factory.sequential}"
|
43
|
+
puts "-------------------------------------------------------------------"
|
44
|
+
puts "Available resources:"
|
45
|
+
puts ""
|
46
|
+
factory.resources.each do |resource|
|
47
|
+
puts "\t#{resource.to_s} (Time: #{natural_time(factory.production_time(resource))})"
|
48
|
+
end
|
49
|
+
puts ""
|
50
|
+
puts "Running production"
|
51
|
+
factory.productions.each do |production|
|
52
|
+
line = "\t#{production.object_id} -> #{production.resource.name}"
|
53
|
+
line += " / started at: #{production.start_time}"
|
54
|
+
line += " / duration: #{production.duration}"
|
55
|
+
line += " / delivery at: #{production.start_time + production.duration}"
|
56
|
+
line + " / finished: #{production.finished?}"
|
57
|
+
puts line
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def list_configuration(configuration)
|
62
|
+
puts "==================================================================="
|
63
|
+
puts "Configuration created at: #{configuration.created_at}"
|
64
|
+
puts "==================================================================="
|
65
|
+
puts ""
|
66
|
+
list_inventory(configuration.store)
|
67
|
+
configuration.factories.each do |factory_name|
|
68
|
+
puts ""
|
69
|
+
outline_factory(configuration.factory(factory_name))
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Production
|
3
|
+
attr_reader :start_time, :duration, :resource
|
4
|
+
|
5
|
+
def initialize(resource, duration, start_time=nil)
|
6
|
+
@start_time = start_time ? Time.at(start_time) : Time.now
|
7
|
+
@duration = duration
|
8
|
+
@resource = resource
|
9
|
+
end
|
10
|
+
|
11
|
+
def finished?
|
12
|
+
end_time <= Time.now
|
13
|
+
end
|
14
|
+
|
15
|
+
def end_time
|
16
|
+
@start_time + duration
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"Production of #{resource.name} since #{@start_time} takes #{natural_time(@duration)} (Finished: #{finished?})"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
{
|
25
|
+
"start_time" => @start_time.to_i,
|
26
|
+
"duration" => @duration,
|
27
|
+
"resource" => @resource.name
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Project
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
@requirements = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def resources
|
11
|
+
@requirements.keys
|
12
|
+
end
|
13
|
+
|
14
|
+
def amount(resource)
|
15
|
+
@requirements[resource]
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_requirement(resource, amount)
|
19
|
+
@requirements[resource] = amount
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Smcty
|
2
|
+
class Resource
|
3
|
+
attr_reader :name, :description
|
4
|
+
|
5
|
+
def initialize(name, description)
|
6
|
+
@name = name
|
7
|
+
@description = description
|
8
|
+
@dependencies = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def register_dependency(resource, amount)
|
12
|
+
@dependencies[resource] = amount
|
13
|
+
end
|
14
|
+
|
15
|
+
def dependent_resources
|
16
|
+
@dependencies.keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def dependent_resource_amount(resource)
|
20
|
+
@dependencies[resource]
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"Resource '#{@name}' (#{@description}) with these dependencies: #{dependency_list}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash(time)
|
28
|
+
{
|
29
|
+
"name" => @name,
|
30
|
+
"description" => @description,
|
31
|
+
"time" => time,
|
32
|
+
"deps" => @dependencies.keys.map{|r| {"name" => r.name, "amount" => @dependencies[r]}}
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def dependency_list
|
39
|
+
items = []
|
40
|
+
@dependencies.keys.each do |resource|
|
41
|
+
items << "#{resource.name}: #{@dependencies[resource]}"
|
42
|
+
end
|
43
|
+
items.join(", ")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'smcty/job'
|
2
|
+
|
3
|
+
module Smcty
|
4
|
+
class Scheduling
|
5
|
+
|
6
|
+
def initialize(configuration)
|
7
|
+
@configuration = configuration
|
8
|
+
@projects = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def plan_project(project)
|
12
|
+
jobs = []
|
13
|
+
@projects[project] = jobs
|
14
|
+
|
15
|
+
# 1. split the requirements into jobs
|
16
|
+
project.resources.each do |r|
|
17
|
+
project.amount(r).times do
|
18
|
+
jobs << Job.new(r)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# 2. work through initial job list and either
|
23
|
+
# - allocate available resources
|
24
|
+
# - add jobs to produce dependent jobs
|
25
|
+
# and iterate until the list does not change any more.
|
26
|
+
#
|
27
|
+
prev_size = 0
|
28
|
+
while (jobs.size != prev_size) do
|
29
|
+
prev_size = jobs.size
|
30
|
+
jobs.select{|j| j.new? }.each do |job|
|
31
|
+
if store.available_stock(job.resource) >= 1
|
32
|
+
job.allocate(store.allocate(job.resource, 1))
|
33
|
+
else
|
34
|
+
job.resource.dependent_resources.each do |dr|
|
35
|
+
job.resource.dependent_resource_amount(dr).times do
|
36
|
+
djob = Job.new(dr)
|
37
|
+
job.add_dependent(djob)
|
38
|
+
jobs << djob
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def next
|
47
|
+
puts "determine the next action"
|
48
|
+
# any project finished?
|
49
|
+
action = project_ready
|
50
|
+
return action if action
|
51
|
+
# any pure request?
|
52
|
+
action = something_dependent_to_produce
|
53
|
+
return action if action
|
54
|
+
# anything to pickup?
|
55
|
+
action = something_to_pick
|
56
|
+
return action if action
|
57
|
+
# anything that can be produced?
|
58
|
+
action = something_pure_to_produce
|
59
|
+
if action
|
60
|
+
return action
|
61
|
+
else
|
62
|
+
return "wait"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def project_ready
|
67
|
+
puts " -> is there a project ready for delivery?"
|
68
|
+
@projects.keys.each do |project|
|
69
|
+
ready = true
|
70
|
+
@projects[project].each do |job|
|
71
|
+
unless job.allocated?
|
72
|
+
ready = false
|
73
|
+
break
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if ready
|
78
|
+
finish_project(project)
|
79
|
+
# we are done and the project can be finished.
|
80
|
+
return "finish #{project.name}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def finish_project(project)
|
87
|
+
# get all the allocated resources
|
88
|
+
@projects[project].each do |job|
|
89
|
+
job.allocation.get
|
90
|
+
end
|
91
|
+
# remove the project from scheduling
|
92
|
+
@projects.delete(project)
|
93
|
+
end
|
94
|
+
|
95
|
+
def something_pure_to_produce
|
96
|
+
puts " -> is there something without dependencies to produce?"
|
97
|
+
@projects.keys.each do |project|
|
98
|
+
@projects[project].each do |job|
|
99
|
+
if job.new?
|
100
|
+
factory = @configuration.factory_for(job.resource)
|
101
|
+
if factory.free_capacity > 0
|
102
|
+
job.produce(factory.produce(job.resource))
|
103
|
+
return "produce #{job.resource.name}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def something_to_pick
|
112
|
+
puts " -> is there something to pick?"
|
113
|
+
@projects.keys.each do |project|
|
114
|
+
@projects[project].each do |job|
|
115
|
+
if job.ready? && store.free_capacity > 0
|
116
|
+
@configuration.factory_for(job.resource).pick(job.production)
|
117
|
+
store.put(job.resource, 1)
|
118
|
+
job.allocate(store.allocate(job.resource, 1))
|
119
|
+
return "pick #{job.resource.name}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def something_dependent_to_produce
|
127
|
+
puts " -> is there something with dependencies ready for production?"
|
128
|
+
@projects.keys.each do |project|
|
129
|
+
@projects[project].each do |job|
|
130
|
+
if job.allocated_dependencies?
|
131
|
+
|
132
|
+
# extract the requirements
|
133
|
+
requirements = job.dependent_jobs.map{|j| j.allocation }
|
134
|
+
# produce the resource
|
135
|
+
|
136
|
+
factory = @configuration.factory_for(job.resource)
|
137
|
+
|
138
|
+
if factory.free_capacity > 0
|
139
|
+
puts "go for a complex production: #{job.resource}"
|
140
|
+
job.produce(factory.produce(job.resource, requirements))
|
141
|
+
|
142
|
+
# remove the dependent jobs
|
143
|
+
job.dependent_jobs.each{|j| @projects[project].delete(j)}
|
144
|
+
# reset the job dependency
|
145
|
+
job.reset_dependent_jobs
|
146
|
+
|
147
|
+
return "produce #{job.resource.name}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
# debugging
|
156
|
+
def print_job_lists
|
157
|
+
@projects.keys.each do |project|
|
158
|
+
puts
|
159
|
+
puts "Project: #{project.name}"
|
160
|
+
puts ""
|
161
|
+
@projects[project].each_with_index do |job, idx|
|
162
|
+
puts "\t Step-#{idx} - #{job_line(job)}"
|
163
|
+
end
|
164
|
+
puts
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def job_line(job)
|
169
|
+
"#{job.resource.name}: allocation: #{job.allocation} production: #{job.production} dependent: #{job.dependent_jobs.map{|j| j.resource.name}} new: #{job.new?} ready: #{job.ready?} allocated dependencies: #{job.allocated_dependencies?}"
|
170
|
+
end
|
171
|
+
# debugging
|
172
|
+
|
173
|
+
def load_project(project, job_list)
|
174
|
+
@projects[project] = job_list
|
175
|
+
end
|
176
|
+
|
177
|
+
def to_hash
|
178
|
+
{
|
179
|
+
projects: @projects.keys.map{|p| project_hash(p, @projects[p])}
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
def project_hash(project, job_list)
|
184
|
+
{
|
185
|
+
"name" => project.name,
|
186
|
+
"requirements" => project.resources.map{|r| {"resource" => r.name, "amount" => project.amount(r)}},
|
187
|
+
"jobs" => job_list.map{|j| j.to_hash}
|
188
|
+
}
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def store
|
194
|
+
@configuration.store
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
data/lib/smcty/store.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'smcty/allocation'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Smcty
|
5
|
+
class Store
|
6
|
+
attr_reader :name, :capacity
|
7
|
+
|
8
|
+
def initialize(name, capacity)
|
9
|
+
@name = name
|
10
|
+
@capacity = capacity
|
11
|
+
@storage = {}
|
12
|
+
@allocations = Set.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def free_capacity
|
16
|
+
@capacity - total_available_stock - total_allocated_stock
|
17
|
+
end
|
18
|
+
|
19
|
+
def total_available_stock
|
20
|
+
result = 0
|
21
|
+
@storage.values.each do |value|
|
22
|
+
result += value
|
23
|
+
end
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
def total_allocated_stock
|
28
|
+
result = 0
|
29
|
+
@allocations.each do |alloc|
|
30
|
+
result += alloc.amount
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def available_stock(resource)
|
36
|
+
@storage[resource] || 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def allocated_stock(resource)
|
40
|
+
result = 0
|
41
|
+
@allocations.each do |alloc|
|
42
|
+
result += alloc.amount if resource == alloc.resource
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
def total_stock(resource)
|
48
|
+
available_stock(resource) + allocated_stock(resource)
|
49
|
+
end
|
50
|
+
|
51
|
+
def put(resource, _amount=1)
|
52
|
+
# at least one item of the resource
|
53
|
+
amount = [1, _amount].max
|
54
|
+
|
55
|
+
unless free_capacity >= amount
|
56
|
+
raise "The store is full - Cannot put #{amount} items of #{resource.name}"
|
57
|
+
end
|
58
|
+
|
59
|
+
in_stock = available_stock(resource)
|
60
|
+
@storage[resource] = in_stock + amount
|
61
|
+
amount
|
62
|
+
end
|
63
|
+
|
64
|
+
def allocate(resource, _amount)
|
65
|
+
# at least one item of the resource
|
66
|
+
amount = [1, _amount].max
|
67
|
+
|
68
|
+
in_stock = available_stock(resource)
|
69
|
+
unless in_stock >= amount
|
70
|
+
raise "#{in_stock} is not enough of #{resource.name} to allocate #{amount} items"
|
71
|
+
end
|
72
|
+
|
73
|
+
@storage[resource] = in_stock - amount
|
74
|
+
allocation = Allocation.new(self, resource, amount)
|
75
|
+
@allocations.add(allocation)
|
76
|
+
allocation
|
77
|
+
end
|
78
|
+
|
79
|
+
def get(allocation)
|
80
|
+
@allocations.delete(allocation)
|
81
|
+
end
|
82
|
+
|
83
|
+
def free(allocation)
|
84
|
+
put(allocation.resource, allocation.amount)
|
85
|
+
@allocations.delete(allocation)
|
86
|
+
end
|
87
|
+
|
88
|
+
def valid?(allocation)
|
89
|
+
@allocations.include?(allocation)
|
90
|
+
end
|
91
|
+
|
92
|
+
def inventory
|
93
|
+
@storage.keys
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
"Store #{@name} has a total capacity of #{@capacity} whereas the free capacity is #{free_capacity}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def to_hash
|
101
|
+
{
|
102
|
+
"name" => @name,
|
103
|
+
"capacity" => @capacity,
|
104
|
+
"stock" => @storage.keys.map{|k| {"name" => k.name, "amount" => total_stock(k)}}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def enforce(lower, amount, upper)
|
111
|
+
[[lower, amount].max, upper].min
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: smcty
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Markus Kuhnt
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Plan your production based on scheduled plans to optimize throughput.
|
14
|
+
email:
|
15
|
+
- markus.kuhnt@gmail.com
|
16
|
+
executables:
|
17
|
+
- smcty
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- bin/smcty
|
22
|
+
- lib/smcty.rb
|
23
|
+
- lib/smcty/allocation.rb
|
24
|
+
- lib/smcty/configuration.rb
|
25
|
+
- lib/smcty/configurator.rb
|
26
|
+
- lib/smcty/console.rb
|
27
|
+
- lib/smcty/factory.rb
|
28
|
+
- lib/smcty/helpers.rb
|
29
|
+
- lib/smcty/job.rb
|
30
|
+
- lib/smcty/output.rb
|
31
|
+
- lib/smcty/planner.rb
|
32
|
+
- lib/smcty/production.rb
|
33
|
+
- lib/smcty/project.rb
|
34
|
+
- lib/smcty/resource.rb
|
35
|
+
- lib/smcty/scheduling.rb
|
36
|
+
- lib/smcty/store.rb
|
37
|
+
- lib/smcty/version.rb
|
38
|
+
homepage:
|
39
|
+
licenses:
|
40
|
+
- MIT
|
41
|
+
metadata: {}
|
42
|
+
post_install_message:
|
43
|
+
rdoc_options: []
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 1.3.6
|
56
|
+
requirements: []
|
57
|
+
rubyforge_project:
|
58
|
+
rubygems_version: 2.4.5.1
|
59
|
+
signing_key:
|
60
|
+
specification_version: 4
|
61
|
+
summary: A production planning software for simcity(tm)
|
62
|
+
test_files: []
|