plantwatchdog 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.
- data/History.txt +4 -0
- data/License.txt +674 -0
- data/Manifest.txt +42 -0
- data/README.txt +91 -0
- data/Rakefile +22 -0
- data/bin/plantwatchdog +8 -0
- data/bin/upload_measurements +2 -0
- data/config.ru +35 -0
- data/config/app_config.yaml +10 -0
- data/lib/plantwatchdog/aggregation.rb +220 -0
- data/lib/plantwatchdog/aggregation_methods.rb +90 -0
- data/lib/plantwatchdog/data.rb +126 -0
- data/lib/plantwatchdog/db.rb +37 -0
- data/lib/plantwatchdog/gems.rb +5 -0
- data/lib/plantwatchdog/main.rb +76 -0
- data/lib/plantwatchdog/model.rb +442 -0
- data/lib/plantwatchdog/sinatra.rb +206 -0
- data/public/images/arrow-down.gif +0 -0
- data/public/images/arrow-left.gif +0 -0
- data/public/images/arrow-right.gif +0 -0
- data/public/images/arrow-up.gif +0 -0
- data/public/images/spinner.gif +0 -0
- data/public/images/tabs.png +0 -0
- data/public/js/customflot.js +120 -0
- data/public/js/jquery-1.3.2.min.js +19 -0
- data/public/js/jquery.flot.crosshair.js +157 -0
- data/public/js/jquery.flot.js +2119 -0
- data/public/js/jquery.flot.navigate.js +272 -0
- data/public/js/jquery.flot.selection.js +299 -0
- data/public/js/select-chain.js +71 -0
- data/public/js/tools.tabs-1.0.4.js +285 -0
- data/public/tabs.css +87 -0
- data/sample/solar/create_solar.rb +31 -0
- data/sample/solar/measurements/client.sqlite3 +0 -0
- data/sample/solar/static/devices.yml +17 -0
- data/sample/solar/static/metadata.yml +30 -0
- data/sample/solar/static/plants.yml +3 -0
- data/sample/solar/static/users.yml +4 -0
- data/sample/solar/upload_measurements +26 -0
- data/templates/graph.erb +134 -0
- data/templates/index.erb +24 -0
- data/templates/monthly_graph.erb +41 -0
- data/test/test_aggregation.rb +161 -0
- data/test/test_aggregation_methods.rb +50 -0
- data/test/test_base.rb +83 -0
- data/test/test_data.rb +118 -0
- data/test/test_model.rb +142 -0
- data/test/test_sync.rb +71 -0
- data/test/test_web.rb +87 -0
- metadata +167 -0
data/Manifest.txt
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
config.ru
|
2
|
+
History.txt
|
3
|
+
License.txt
|
4
|
+
Manifest.txt
|
5
|
+
README.txt
|
6
|
+
Rakefile
|
7
|
+
bin/plantwatchdog
|
8
|
+
bin/upload_measurements
|
9
|
+
config/app_config.yaml
|
10
|
+
lib/plantwatchdog/aggregation.rb
|
11
|
+
lib/plantwatchdog/aggregation_methods.rb
|
12
|
+
lib/plantwatchdog/data.rb
|
13
|
+
lib/plantwatchdog/db.rb
|
14
|
+
lib/plantwatchdog/gems.rb
|
15
|
+
lib/plantwatchdog/main.rb
|
16
|
+
lib/plantwatchdog/model.rb
|
17
|
+
lib/plantwatchdog/sinatra.rb
|
18
|
+
public/images/arrow-down.gif
|
19
|
+
public/images/arrow-left.gif
|
20
|
+
public/images/arrow-right.gif
|
21
|
+
public/images/arrow-up.gif
|
22
|
+
public/images/spinner.gif
|
23
|
+
public/images/tabs.png
|
24
|
+
public/js/customflot.js
|
25
|
+
public/js/jquery-1.3.2.min.js
|
26
|
+
public/js/jquery.flot.crosshair.js
|
27
|
+
public/js/jquery.flot.js
|
28
|
+
public/js/jquery.flot.navigate.js
|
29
|
+
public/js/jquery.flot.selection.js
|
30
|
+
public/js/select-chain.js
|
31
|
+
public/js/tools.tabs-1.0.4.js
|
32
|
+
public/tabs.css
|
33
|
+
sample/solar/create_solar.rb
|
34
|
+
sample/solar/measurements/client.sqlite3
|
35
|
+
sample/solar/static/devices.yml
|
36
|
+
sample/solar/static/metadata.yml
|
37
|
+
sample/solar/static/plants.yml
|
38
|
+
sample/solar/static/users.yml
|
39
|
+
sample/solar/upload_measurements
|
40
|
+
templates/graph.erb
|
41
|
+
templates/index.erb
|
42
|
+
templates/monthly_graph.erb
|
data/README.txt
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
= Plant Watchdog
|
2
|
+
|
3
|
+
A watchdog for your technical plant, e.g. a photovoltaic generator. The plant
|
4
|
+
watchdog listens to data loggers, which continuously upload measurements of
|
5
|
+
plant parameters. Based on that data the watchdog creates reports about the
|
6
|
+
operational status.
|
7
|
+
|
8
|
+
|
9
|
+
== DESCRIPTION:
|
10
|
+
|
11
|
+
This software is being developed to monitor a photovoltaic generator. We found
|
12
|
+
that it would make sense to keep the domain specific knowledge out of the code
|
13
|
+
and instead provide a DSL to allow users to define their rules. Therefore this
|
14
|
+
software should be useful to monitor any kind of technical plant.
|
15
|
+
|
16
|
+
At the time being the focus of development is to
|
17
|
+
- make it reasonably stable
|
18
|
+
- run per-day aggregations
|
19
|
+
- show intraday and monthly diagrams
|
20
|
+
|
21
|
+
With more data being available the task of finding deviations from regular
|
22
|
+
operation is becoming feasible. Therefore expected values of parameters must
|
23
|
+
be calculated and compared with actual values: the users must be allowed to
|
24
|
+
define a model of the plant.
|
25
|
+
|
26
|
+
== FEATURES/PROBLEMS:
|
27
|
+
|
28
|
+
The vision of this software is to
|
29
|
+
- allow data logger devices to continuously upload time series measurements
|
30
|
+
- backup and distribute measurement data
|
31
|
+
- run user defined aggregations and analyses on the data
|
32
|
+
- provide customizable HTML reports including visual diagrams
|
33
|
+
- find deviations from regular operation and send alarms if necessary
|
34
|
+
|
35
|
+
== REQUIREMENTS:
|
36
|
+
|
37
|
+
* Ruby 1.8.7 and a database supported by active record, e.g. MySQL or SQLite
|
38
|
+
|
39
|
+
== INSTALL:
|
40
|
+
|
41
|
+
1. Install Ruby 1.8
|
42
|
+
For ubuntu
|
43
|
+
$ apt-get install ruby1.8-dev libopenssl-ruby1.8
|
44
|
+
|
45
|
+
2. Install a database
|
46
|
+
Any database supported from active record should do, so far the server has
|
47
|
+
been tested with MySql 5.1.14 and SQLite 3.6.21.
|
48
|
+
|
49
|
+
In order to install sqlite3 and ruby bindings on ubuntu run
|
50
|
+
$ apt-get install sqlite3-dev
|
51
|
+
$ gem install sqlite3-ruby
|
52
|
+
|
53
|
+
3. Install Plant Watchdog and required gems
|
54
|
+
There are two options, either install the gem or download the sources from
|
55
|
+
github.
|
56
|
+
A) gem
|
57
|
+
$ gem install plantwatchdog
|
58
|
+
|
59
|
+
B) github
|
60
|
+
$ mkdir plant
|
61
|
+
$ cd plant
|
62
|
+
$ git clone git@github.com:mbarchfe/plantwatchdog.git
|
63
|
+
$ rake check_extra_deps // Get the required ruby gems
|
64
|
+
|
65
|
+
4. Install sample database and run
|
66
|
+
If your gem's bin directory is not on the PATH, add it
|
67
|
+
$ export PATH=$PATH:/var/lib/gems/1.8/bin
|
68
|
+
|
69
|
+
The default database connection uses sqlite3 and a database at
|
70
|
+
/tmp/solarsample.sqlite3. You can change the default by editing
|
71
|
+
the config file
|
72
|
+
$ vi config/app-config.yaml
|
73
|
+
|
74
|
+
Install the sample
|
75
|
+
$ plantwatch --create_sample
|
76
|
+
Upload data (needs curl and sqlite3 command line)
|
77
|
+
|
78
|
+
Start the plantwatchdog web server (an alternative way is to start via rackup)
|
79
|
+
$ plantwatchdog
|
80
|
+
|
81
|
+
On another shell upload sample measurements. The script needs curl and the
|
82
|
+
sqlite3 command line
|
83
|
+
$ apt-get install curl
|
84
|
+
$ upload_measurements
|
85
|
+
|
86
|
+
Run the daily aggregation
|
87
|
+
$ plantwatchdog -a
|
88
|
+
|
89
|
+
== LICENSE:
|
90
|
+
|
91
|
+
GPL v3
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__),"lib")
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
require 'plantwatchdog/main'
|
6
|
+
|
7
|
+
Hoe.spec 'plantwatchdog' do |p|
|
8
|
+
developer('plantwatchdogteam', 'mbarchfe@rubyforge.org')
|
9
|
+
p.version=PlantWatchdog::Version::STRING
|
10
|
+
p.rubyforge_name = 'pwd'
|
11
|
+
p.author = "Plant Watchdog Team"
|
12
|
+
p.email = "mbarchfe@rubyforge.org"
|
13
|
+
p.summary = 'Plant Watchdog'
|
14
|
+
p.description = p.paragraphs_of('README.txt', 1..5).join("\n\n")
|
15
|
+
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
16
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
17
|
+
p.extra_deps<<['sinatra','0.9.4']
|
18
|
+
p.extra_deps<<['activerecord', '2.3.5']
|
19
|
+
p.extra_deps<<['patir', '0.6.4']
|
20
|
+
p.spec_extras={:executables=>["plantwatchdog","upload_measurements"],
|
21
|
+
:default_executable=>"plantwatchdog"}
|
22
|
+
end
|
data/bin/plantwatchdog
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# an alternative way of starting the server is via rackup:
|
3
|
+
# 1. bin$ gem install rack
|
4
|
+
# 2. bin$ cd ..
|
5
|
+
# 3. $ rackup
|
6
|
+
#d=`dirname $0`
|
7
|
+
#ruby -I${d:-.}/../lib -rubygems ${d:-.}/../lib/plantwatchdog/main.rb $*
|
8
|
+
require 'plantwatchdog/main'
|
data/config.ru
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# this is Rackup file. It is necessary for applications started from a container
|
2
|
+
# e.g. passenger, or via the rackup command.
|
3
|
+
require 'rubygems'
|
4
|
+
#this locks on the versions we need
|
5
|
+
require 'lib/plantwatchdog/gems'
|
6
|
+
require 'lib/plantwatchdog/sinatra.rb'
|
7
|
+
require 'lib/plantwatchdog/db.rb'
|
8
|
+
require 'yaml'
|
9
|
+
path = ''
|
10
|
+
|
11
|
+
set :root, path
|
12
|
+
set :views, path + '/views'
|
13
|
+
set :public, path + '/public'
|
14
|
+
set :run, false
|
15
|
+
set :raise_errors, true
|
16
|
+
|
17
|
+
log = File.new("sinatra.log", "a")
|
18
|
+
STDOUT.reopen(log)
|
19
|
+
STDERR.reopen(log)
|
20
|
+
|
21
|
+
logger=Logger.new(STDOUT)
|
22
|
+
logger.level = Logger::INFO
|
23
|
+
|
24
|
+
config_file="config/app_config.yaml"
|
25
|
+
if File.exists?(config_file)
|
26
|
+
config=YAML.load(File.read(config_file))
|
27
|
+
config[:logger]=logger
|
28
|
+
else
|
29
|
+
logger.fatal("Cannot find #{config_file}")
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
|
33
|
+
extend PlantWatchdog::ActiveRecordConnections
|
34
|
+
self.connect_to_active_record(config[:database_configuration],logger)
|
35
|
+
run PlantWatchdog::UI::SinatraApp
|
@@ -0,0 +1,220 @@
|
|
1
|
+
# Copyright (C) 2010 Markus Barchfeld, Vassilis Rizopoulos
|
2
|
+
#
|
3
|
+
# This program is free software; you can redistribute it and/or modify it under
|
4
|
+
# the terms of the GNU General Public License as published by the Free Software
|
5
|
+
# Foundation; either version 3 of the License, or (at your option) any later
|
6
|
+
# version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License along with
|
13
|
+
# this program; if not, see <http://www.gnu.org/licenses/>.
|
14
|
+
|
15
|
+
$:.unshift File.join(File.dirname(__FILE__),"..")
|
16
|
+
require 'plantwatchdog/model'
|
17
|
+
require 'plantwatchdog/aggregation_methods'
|
18
|
+
|
19
|
+
module PlantWatchdog
|
20
|
+
# define the Aggregation Model,
|
21
|
+
module Aggregation
|
22
|
+
# the global environment to which the aggregation blocks have access
|
23
|
+
class AggregationEnv
|
24
|
+
logger = ActiveRecord::Base.logger
|
25
|
+
attr_accessor :year, :day_of_year, :plant, :devices
|
26
|
+
def initialize(year, day_of_year)
|
27
|
+
@year = year
|
28
|
+
@day_of_year = day_of_year
|
29
|
+
@devices = []
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class Data
|
35
|
+
# return the time series data for symbol
|
36
|
+
def [] symbol
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
# return the list of seconds of the day for which there is data available
|
41
|
+
def times
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module RuleEvaluation
|
46
|
+
# data is an array of dictionaries, e.g. retrieved via Measurements.from_csv
|
47
|
+
def eval_rule rule_array, data
|
48
|
+
return 0 if rule_array.empty?
|
49
|
+
method_name = rule_array.first
|
50
|
+
arg_desc = rule_array[1, rule_array.size] # the column names for which we need to create time series
|
51
|
+
args = arg_desc.collect { |arg_desc|
|
52
|
+
if arg_desc.is_a? Numeric then
|
53
|
+
arg_desc
|
54
|
+
elsif arg_desc.is_a? Array
|
55
|
+
eval_rule(arg_desc, data)
|
56
|
+
else
|
57
|
+
data.collect { |d| d[arg_desc] }
|
58
|
+
end
|
59
|
+
}
|
60
|
+
return Methods.call(method_name, *args)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Device
|
65
|
+
include RuleEvaluation
|
66
|
+
def Device.create(model_device, year, day)
|
67
|
+
# select data
|
68
|
+
data = Model::MeasurementChunk.find(:first, :conditions => ["device_id=? and time_year=? and time_day_of_year=?", model_device.id, year, day])
|
69
|
+
Device.new(model_device, data)
|
70
|
+
end
|
71
|
+
attr_accessor :model_device
|
72
|
+
|
73
|
+
def initialize(model_device, data)
|
74
|
+
@model_device = model_device
|
75
|
+
@aggregates = {}
|
76
|
+
@data = data
|
77
|
+
end
|
78
|
+
|
79
|
+
# generic access to the fields of the underlying model_device
|
80
|
+
def not_understand
|
81
|
+
# model_device
|
82
|
+
end
|
83
|
+
|
84
|
+
def measurements
|
85
|
+
@data ? @data.measurements : []
|
86
|
+
end
|
87
|
+
|
88
|
+
def meta
|
89
|
+
model_device.meta
|
90
|
+
end
|
91
|
+
|
92
|
+
# return the dict with aggregated values
|
93
|
+
def aggregates
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
# execute the aggregation rules of the device
|
98
|
+
def aggregate
|
99
|
+
result = {}
|
100
|
+
logger.debug "Aggregating device #{model_device.id}, aggrules: #{model_device.aggrules}"
|
101
|
+
model_device.aggrules.each_pair do
|
102
|
+
|agg_key, rule_array|
|
103
|
+
result[agg_key] = eval_rule(rule_array, measurements)
|
104
|
+
end
|
105
|
+
logger.debug "Aggregation results: " + result.to_s
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
def persist
|
110
|
+
dm = Model::DailyMeasurement.new()
|
111
|
+
dm.description = JSON(aggregates)
|
112
|
+
return dm
|
113
|
+
end
|
114
|
+
|
115
|
+
# TODO: better way to access logger
|
116
|
+
def logger
|
117
|
+
return ActiveRecord::Base.logger
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class Plant
|
122
|
+
include RuleEvaluation
|
123
|
+
def initialize model_plant, device_aggregates
|
124
|
+
@model_plant = model_plant
|
125
|
+
@device_aggregates = device_aggregates
|
126
|
+
end
|
127
|
+
|
128
|
+
def aggregate
|
129
|
+
result = {}
|
130
|
+
logger.debug "Aggregating plant, aggrules: #{@model_plant.aggrules}"
|
131
|
+
@model_plant.aggrules.each_pair do
|
132
|
+
|agg_key, rule_array|
|
133
|
+
result[agg_key] = eval_rule(rule_array, @device_aggregates)
|
134
|
+
end
|
135
|
+
logger.debug "Aggregation results: " + result.to_s
|
136
|
+
result
|
137
|
+
end
|
138
|
+
|
139
|
+
# TODO: better way to access logger
|
140
|
+
def logger
|
141
|
+
return ActiveRecord::Base.logger
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
class Runner
|
147
|
+
def find_missing_aggregates
|
148
|
+
sql = <<EOF
|
149
|
+
select time_year, time_day_of_year, plant_id from
|
150
|
+
(select CHUNK.time_year, CHUNK.time_day_of_year, CHUNK.device_id AS device_id, AGG.device_id AS agg_device_id from
|
151
|
+
((select time_year, time_day_of_year, device_id from measurement_chunks where type="MeasurementChunk" order by time_year, time_day_of_year, device_id) AS CHUNK
|
152
|
+
LEFT OUTER JOIN
|
153
|
+
(select time_year, time_day_of_year, device_id from measurement_chunks where type="MeasurementAggregate") AS AGG
|
154
|
+
ON CHUNK.time_year = AGG.time_year AND CHUNK.time_day_of_year=AGG.time_day_of_year AND CHUNK.device_id = AGG.device_id)
|
155
|
+
where AGG.device_id IS NULL) AS MISSING
|
156
|
+
INNER JOIN
|
157
|
+
devices
|
158
|
+
ON MISSING.device_id = devices.id
|
159
|
+
GROUP BY MISSING.time_year, MISSING.time_day_of_year;
|
160
|
+
EOF
|
161
|
+
rows = ActiveRecord::Base.connection.select_all(sql)
|
162
|
+
result = []
|
163
|
+
rows.collect {
|
164
|
+
|r|
|
165
|
+
time_year = r["time_year"].to_i
|
166
|
+
time_day_of_year = r["time_day_of_year"].to_i
|
167
|
+
plant_id = r["plant_id"].to_i
|
168
|
+
[time_year, time_day_of_year, plant_id]
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def run
|
173
|
+
find_missing_aggregates.each {
|
174
|
+
|m|
|
175
|
+
time_year, time_day_of_year, plant_id = m
|
176
|
+
aggregate(Model::Plant.find_by_id(plant_id), time_year, time_day_of_year)
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def aggregate(model_plant, year, day_of_year)
|
181
|
+
env = AggregationEnv.new(year, day_of_year)
|
182
|
+
model_plant.devices.each {
|
183
|
+
|model_device|
|
184
|
+
logger.debug("Adding device " + model_device.to_s)
|
185
|
+
env.devices << Device.create(model_device, year, day_of_year)
|
186
|
+
}
|
187
|
+
# build the aggregates for the devices first ...
|
188
|
+
aggregates = env.devices.collect do
|
189
|
+
|device|
|
190
|
+
daily = Model::MeasurementAggregate.new
|
191
|
+
daily.device = device.model_device
|
192
|
+
daily.time_year = year
|
193
|
+
daily.time_day_of_year = day_of_year
|
194
|
+
daily.data = device.aggregate
|
195
|
+
daily
|
196
|
+
end
|
197
|
+
|
198
|
+
# ... and then aggregate the plant
|
199
|
+
gen_aggregates = Plant.new(model_plant, aggregates.collect{|ma| ma.data}).aggregate
|
200
|
+
|
201
|
+
gen_aggregate = Model::MeasurementAggregate.new
|
202
|
+
gen_aggregate.data = gen_aggregates
|
203
|
+
gen_aggregate.time_year = year
|
204
|
+
gen_aggregate.time_day_of_year = day_of_year
|
205
|
+
gen_aggregate.plant = model_plant
|
206
|
+
|
207
|
+
# save when everything has been calculated
|
208
|
+
aggregates << gen_aggregate
|
209
|
+
aggregates.each {|a| a.save}
|
210
|
+
aggregates
|
211
|
+
end
|
212
|
+
|
213
|
+
# TODO: better way to access logger
|
214
|
+
def logger
|
215
|
+
return ActiveRecord::Base.logger
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Copyright (C) 2010 Markus Barchfeld, Vassilis Rizopoulos
|
2
|
+
#
|
3
|
+
# This program is free software; you can redistribute it and/or modify it under
|
4
|
+
# the terms of the GNU General Public License as published by the Free Software
|
5
|
+
# Foundation; either version 3 of the License, or (at your option) any later
|
6
|
+
# version.
|
7
|
+
#
|
8
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
9
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
10
|
+
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
11
|
+
#
|
12
|
+
# You should have received a copy of the GNU General Public License along with
|
13
|
+
# this program; if not, see <http://www.gnu.org/licenses/>.
|
14
|
+
|
15
|
+
$:.unshift File.join(File.dirname(__FILE__),"..")
|
16
|
+
require 'plantwatchdog/model'
|
17
|
+
|
18
|
+
class Array
|
19
|
+
def each_prior()
|
20
|
+
i=0
|
21
|
+
result=[]
|
22
|
+
while (i < self.size-1) do
|
23
|
+
result << yield(self[i],self[i+1])
|
24
|
+
i+=1
|
25
|
+
end
|
26
|
+
return result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module PlantWatchdog
|
31
|
+
module Aggregation
|
32
|
+
module Methods
|
33
|
+
class << self
|
34
|
+
def growth timeseries
|
35
|
+
timeseries.last - timeseries.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def avg timeseries
|
39
|
+
result = sum(timeseries)
|
40
|
+
result.to_f / timeseries.size
|
41
|
+
end
|
42
|
+
|
43
|
+
def integrate times, values
|
44
|
+
return [times.each_prior {|x,y| y-x}, values.each_prior {|x,y| (y+x)/2.0}].transpose.inject(0) {|i,a| i + a.first * a.last }
|
45
|
+
end
|
46
|
+
|
47
|
+
def sum timeseries
|
48
|
+
result = 0
|
49
|
+
timeseries.each { |v| result += v if v}
|
50
|
+
return result
|
51
|
+
end
|
52
|
+
|
53
|
+
def mult a,b
|
54
|
+
a*b
|
55
|
+
end
|
56
|
+
|
57
|
+
def div a,b
|
58
|
+
a/b
|
59
|
+
end
|
60
|
+
|
61
|
+
def subtract a,b
|
62
|
+
a - b
|
63
|
+
end
|
64
|
+
|
65
|
+
def add
|
66
|
+
a + b
|
67
|
+
end
|
68
|
+
|
69
|
+
def pick n,a
|
70
|
+
a[n]
|
71
|
+
end
|
72
|
+
|
73
|
+
def call(method, *args)
|
74
|
+
begin
|
75
|
+
m = Methods.method method
|
76
|
+
m.call *args
|
77
|
+
rescue
|
78
|
+
logger.debug("Error calling method '#{method}': " + $!.to_s)
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def logger
|
84
|
+
return ActiveRecord::Base.logger
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|