amee-analytics 1.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/.rvmrc +1 -0
- data/CHANGELOG.txt +4 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +27 -0
- data/README.txt +94 -0
- data/Rakefile +99 -0
- data/VERSION +1 -0
- data/amee-analytics.gemspec +80 -0
- data/init.rb +4 -0
- data/lib/amee/data_abstraction/calculation_collection_analytics_support.rb +236 -0
- data/lib/amee/data_abstraction/result.rb +16 -0
- data/lib/amee/data_abstraction/term_analytics_support.rb +28 -0
- data/lib/amee/data_abstraction/terms_list_analytics_support.rb +347 -0
- data/lib/amee-analytics.rb +8 -0
- data/rails/init.rb +9 -0
- data/spec/amee/analytics/calculation_collection_spec.rb +234 -0
- data/spec/amee/analytics/term_spec.rb +11 -0
- data/spec/amee/analytics/terms_list_spec.rb +428 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +132 -0
- metadata +213 -0
@@ -0,0 +1,347 @@
|
|
1
|
+
|
2
|
+
# Copyright (C) 2011 AMEE UK Ltd. - http://www.amee.com
|
3
|
+
# Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details.
|
4
|
+
#
|
5
|
+
# :title: Module: AMEE::DataAbstraction::TermsListAnalyticsSupport
|
6
|
+
|
7
|
+
module AMEE
|
8
|
+
module DataAbstraction
|
9
|
+
|
10
|
+
# Mixin module for the <i>AMEE::DataAbstraction::Term</i> class, providing
|
11
|
+
# methods for handling collections of calculations.
|
12
|
+
#
|
13
|
+
module TermsListAnalyticsSupport
|
14
|
+
|
15
|
+
def name
|
16
|
+
first.name if analogous?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns <tt>true</tt> if all terms within the list have the same label.
|
20
|
+
# Otherwise, returns <tt>false</tt>.
|
21
|
+
#
|
22
|
+
# This enables a check as to whether all terms represent the same thing,
|
23
|
+
# i.e. same calculation component (i.e. the same drill choice, or profile
|
24
|
+
# item value, or return value, or metadata type).
|
25
|
+
#
|
26
|
+
def analogous?
|
27
|
+
labels.uniq.size == (1 or nil)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns <tt>true</tt> if all terms within the list have the same label
|
31
|
+
# AND contain consistent units. Otherwise, returns <tt>false</tt>.
|
32
|
+
#
|
33
|
+
# This enables a term list to be manipulated numerically, for example, by
|
34
|
+
# producing a sum or a mean across all terms.
|
35
|
+
#
|
36
|
+
def homogeneous?
|
37
|
+
analogous? and homogeneous_units? and homogeneous_per_units?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns <tt>true</tt> if TermsList is NOT homogeneous, i.e. it does NOT
|
41
|
+
# contain all analogous terms with corresponding units. Otherwise, returns
|
42
|
+
# <tt>false</tt>.
|
43
|
+
#
|
44
|
+
def heterogeneous?
|
45
|
+
!homogeneous?
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns <tt>true</tt> if all terms within the list are represented by the
|
49
|
+
# same unit or are all <tt>nil</tt>. Otherwise, returns <tt>false</tt>.
|
50
|
+
#
|
51
|
+
def homogeneous_units?
|
52
|
+
return true if all? { |term| term.unit.nil? } or
|
53
|
+
( all? { |term| term.unit.is_a? Quantity::Unit::Base } and
|
54
|
+
map { |term| term.unit.label }.uniq.size == 1 )
|
55
|
+
return false
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns <tt>true</tt> if all terms within the list are represented by the
|
59
|
+
# same PER unit or are all <tt>nil</tt>. Otherwise, returns <tt>false</tt>.
|
60
|
+
#
|
61
|
+
def homogeneous_per_units?
|
62
|
+
return true if all? { |term| term.per_unit.nil? } or
|
63
|
+
( all? { |term| term.per_unit.is_a? Quantity::Unit::Base } and
|
64
|
+
map { |term| term.per_unit.label }.uniq.size == 1 )
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the label which defines all terms in contained within <tt>self</tt>,
|
69
|
+
# if they are all the same. Otherwise, returns <tt>nil</tt>.
|
70
|
+
#
|
71
|
+
def label
|
72
|
+
first.label if analogous?
|
73
|
+
end
|
74
|
+
|
75
|
+
def +(other_list)
|
76
|
+
self.class.new(self.to_a + other_list.to_a)
|
77
|
+
end
|
78
|
+
|
79
|
+
def -(other_list)
|
80
|
+
other_list = [other_list].flatten
|
81
|
+
self.delete_if { |term| other_list.include?(term) }
|
82
|
+
end
|
83
|
+
|
84
|
+
def first_of_each_type
|
85
|
+
labels = self.labels.uniq
|
86
|
+
terms = labels.map {|label| find { |term| term.label == label } }
|
87
|
+
TermsList.new(terms)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns the label of the unit which is predominantly used across all terms
|
91
|
+
# in the list, e.g.
|
92
|
+
#
|
93
|
+
# list.predominant_unit #=> kg
|
94
|
+
#
|
95
|
+
# list.predominant_unit #=> kWh
|
96
|
+
#
|
97
|
+
# Returns nil if all units are blank
|
98
|
+
#
|
99
|
+
def predominant_unit
|
100
|
+
terms = reject { |term| term.unit.nil? }
|
101
|
+
unit = terms.group_by { |term| term.unit.label }.
|
102
|
+
max {|a,b| a.last.size <=> b.last.size }.first unless terms.blank?
|
103
|
+
return unit
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the label of the per unit which is predominantly used across all terms
|
107
|
+
# in the list, e.g.
|
108
|
+
#
|
109
|
+
# list.predominant_per_unit #=> h
|
110
|
+
#
|
111
|
+
# list.predominant_per_unit #=> kWh
|
112
|
+
#
|
113
|
+
# Returns nil if all per units are blank
|
114
|
+
#
|
115
|
+
def predominant_per_unit
|
116
|
+
terms = reject { |term| term.per_unit.nil? }
|
117
|
+
unit = terms.group_by { |term| term.per_unit.label }.
|
118
|
+
max {|a,b| a.last.size <=> b.last.size }.first unless terms.blank?
|
119
|
+
return unit
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns <tt>true</tt> if all terms in the list have numeric values.
|
123
|
+
# Otherwise, returns <tt>false</tt>.
|
124
|
+
#
|
125
|
+
def all_numeric?
|
126
|
+
all? { |term| term.has_numeric_value? }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns a new instance of <i>TermsList</i> comprising only those terms
|
130
|
+
# belongong to <tt>self</tt> which have numeric values.
|
131
|
+
#
|
132
|
+
# This is useful for establishing which terms in a list to perform numerical
|
133
|
+
# operations on
|
134
|
+
#
|
135
|
+
def numeric_terms
|
136
|
+
TermsList.new select { |term| term.has_numeric_value? }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns a new instance of <i>TermsList</i> with all units standardized and
|
140
|
+
# the respective term values adjusted accordingly.
|
141
|
+
#
|
142
|
+
# The unit and per units to be standardized to can be specified as the first
|
143
|
+
# and second arguments respectively. Either the unit name, symbol or label
|
144
|
+
# (as defined in the <i>Quantify</i> gem) can be used. If no arguments are
|
145
|
+
# specified, the standardized units represent those which are predominant
|
146
|
+
# in the list, e.g.
|
147
|
+
#
|
148
|
+
# list.standardize_units #=> <TermsList>
|
149
|
+
#
|
150
|
+
# list.standardize_units(:t,:kWh) #=> <TermsList>
|
151
|
+
#
|
152
|
+
# list.standardize_units('pound') #=> <TermsList>
|
153
|
+
#
|
154
|
+
# list.standardize_units(nil, 'BTU') #=> <TermsList>
|
155
|
+
#
|
156
|
+
def standardize_units(unit=nil,per_unit=nil)
|
157
|
+
return self if homogeneous? and ((unit.nil? or (first.unit and first.unit.label == unit)) and
|
158
|
+
(per_unit.nil? or (first.per_unit and first.per_unit.label == per_unit)))
|
159
|
+
unit = predominant_unit if unit.nil?
|
160
|
+
per_unit = predominant_per_unit if per_unit.nil?
|
161
|
+
new_terms = map { |term| term.convert_unit(:unit => unit, :per_unit => per_unit) }
|
162
|
+
TermsList.new new_terms
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns a new instance of <i>Result</i> which represents the sum of all
|
166
|
+
# term values within the list.
|
167
|
+
#
|
168
|
+
# Any terms within self which contain non-numeric values are ignored.
|
169
|
+
#
|
170
|
+
# If the terms within <tt>self</tt> do not contain consistent units, they
|
171
|
+
# are standardized by default to the unit (and per unit) which predominate
|
172
|
+
# in the list. Alternatively, the required unit and per units can be
|
173
|
+
# specified as arguments using the same conventions as the
|
174
|
+
# <tt>#standardize_units</tt> method.
|
175
|
+
#
|
176
|
+
def sum(unit=nil,per_unit=nil)
|
177
|
+
unit = predominant_unit if unit.nil?
|
178
|
+
per_unit = predominant_per_unit if per_unit.nil?
|
179
|
+
value = numeric_terms.standardize_units(unit,per_unit).inject(0.0) do |sum,term|
|
180
|
+
sum + term.value
|
181
|
+
end
|
182
|
+
template = self
|
183
|
+
Result.new { label template.label; value value; unit unit; per_unit per_unit; name template.name }
|
184
|
+
end
|
185
|
+
|
186
|
+
# Returns a new instance of <i>Result</i> which represents the mean of all
|
187
|
+
# term values within the list.
|
188
|
+
#
|
189
|
+
# Any terms within self which contain non-numeric values are ignored.
|
190
|
+
#
|
191
|
+
# If the terms within <tt>self</tt> do not contain consistent units, they
|
192
|
+
# are standardized by default to the unit (and per unit) which predominate
|
193
|
+
# in the list. Alternatively, the required unit and per units can be
|
194
|
+
# specified as arguments using the same conventions as the
|
195
|
+
# <tt>#standardize_units</tt> method.
|
196
|
+
#
|
197
|
+
def mean(unit=nil,per_unit=nil)
|
198
|
+
list = numeric_terms
|
199
|
+
sum = list.sum(unit,per_unit)
|
200
|
+
Result.new { label sum.label; value (sum.value/list.size); unit sum.unit; per_unit sum.per_unit; name sum.name }
|
201
|
+
end
|
202
|
+
|
203
|
+
# Returns a representation of the term with most prevalent value in
|
204
|
+
# <tt>self</tt>, i.e. the modal value. This method considers both numerical
|
205
|
+
# and text values.
|
206
|
+
#
|
207
|
+
# If only a single modal value is discovered an instance of the class
|
208
|
+
# <i>Result</i> is returning representing the modal value. Where multiple
|
209
|
+
# modal values occur a new instance of <i>TermsList</i> is returned
|
210
|
+
# containing <i>Result</i> representations of each modal value.
|
211
|
+
#
|
212
|
+
def mode
|
213
|
+
groups = standardize_units.reject { |term| term.value.nil? }.
|
214
|
+
group_by { |term| term.value }.map(&:last)
|
215
|
+
max_group_size = groups.max {|a,b| a.size <=> b.size }.size
|
216
|
+
max_groups = groups.select {|a| a.size == max_group_size}
|
217
|
+
if max_groups.size == 1
|
218
|
+
max_groups.first.first.to_result
|
219
|
+
else
|
220
|
+
TermsList.new max_groups.map { |group| group.first.to_result }
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Returns a representation of the term with median value in <tt>self</tt>.
|
225
|
+
# This method considers both numerical and text values.
|
226
|
+
#
|
227
|
+
# If <tt>self</tt> has an even-numbered size, the median is caluclated as
|
228
|
+
# the mean of the values of the two centrally placed terms (having been
|
229
|
+
# sorted according to their value attributes).
|
230
|
+
#
|
231
|
+
def median
|
232
|
+
new_list = standardize_units
|
233
|
+
midpoint = new_list.size/2
|
234
|
+
if new_list.size % 2.0 == 1
|
235
|
+
median_term = new_list.sort_by_value[midpoint]
|
236
|
+
elsif new_list.size % 2.0 == 0
|
237
|
+
median_term = new_list.sort_by_value[midpoint-1, 2].mean
|
238
|
+
else
|
239
|
+
raise
|
240
|
+
end
|
241
|
+
median_term.to_result
|
242
|
+
end
|
243
|
+
|
244
|
+
# Convenience method for initializing instances of the <i>Result</i> class.
|
245
|
+
# Intialize the new object with the attributes described by <tt>label</tt>,
|
246
|
+
# <tt>value</tt>, <tt>unit</tt> and <tt>per_unit</tt>. The unit and per_unit
|
247
|
+
# attributes default to <tt>nil</tt> if left unspecified.
|
248
|
+
#
|
249
|
+
def initialize_result(label,value,unit=nil,per_unit=nil)
|
250
|
+
Result.new { label label; value value; unit unit; per_unit per_unit }
|
251
|
+
end
|
252
|
+
|
253
|
+
# Sorts the terms list in place according to the term attribute indiated by
|
254
|
+
# <tt>attr</tt>, returning <tt>self</tt>.
|
255
|
+
#
|
256
|
+
# my_terms_list.sort_by! :value
|
257
|
+
#
|
258
|
+
# #=> <AMEE::DataAbstraction::TermsList ... >
|
259
|
+
#
|
260
|
+
def sort_by!(attr)
|
261
|
+
replace(sort_by(attr))
|
262
|
+
end
|
263
|
+
|
264
|
+
# Similar to <tt>#sort_by!</tt> but returns a new instance of
|
265
|
+
# <i>TermsList</i> arranged according to the values on the
|
266
|
+
# attribute <tt>attr</tt>. E.g.
|
267
|
+
#
|
268
|
+
# my_terms_list.sort_by :value
|
269
|
+
#
|
270
|
+
# #=> <AMEE::DataAbstraction::TermsList ... >
|
271
|
+
#
|
272
|
+
def sort_by(attr)
|
273
|
+
# Remove unset terms before sort and append at end
|
274
|
+
unset_terms = select { |term| term.unset? }
|
275
|
+
set_terms = select { |term| term.set? }
|
276
|
+
set_terms.sort! { |term,other_term| term.send(attr) <=> other_term.send(attr) }
|
277
|
+
TermsList.new(set_terms + unset_terms)
|
278
|
+
end
|
279
|
+
|
280
|
+
# Return an instance of <i>TermsList</i> containing only terms labelled
|
281
|
+
# :type.
|
282
|
+
#
|
283
|
+
# This method overrides the standard #type method (which is deprecated) and
|
284
|
+
# mimics the functionality provied by the first #method_missing method in
|
285
|
+
# dynamically retrieving a subset of terms according their labels.
|
286
|
+
#
|
287
|
+
def type
|
288
|
+
TermsList.new select{ |x| x.label == :type }
|
289
|
+
end
|
290
|
+
|
291
|
+
def respond_to?(method)
|
292
|
+
if labels.include? method.to_sym
|
293
|
+
return true
|
294
|
+
elsif method.to_s =~ /sort_by_(.*)!/ and self.class::TermProperties.include? $1.to_sym
|
295
|
+
return true
|
296
|
+
elsif method.to_s =~ /sort_by_(.*)/ and self.class::TermProperties.include? $1.to_sym
|
297
|
+
return true
|
298
|
+
else
|
299
|
+
super
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Syntactic sugar for several instance methods.
|
304
|
+
#
|
305
|
+
# ---
|
306
|
+
#
|
307
|
+
# Call a method on <tt>self</tt> which named after a specific term label
|
308
|
+
# contained within <tt>self</tt> and return a new instance of the
|
309
|
+
# <tt>TermsList</tt> class containing each of those terms. E.g.,
|
310
|
+
#
|
311
|
+
# my_terms = my_terms_list.type #=> <AMEE::DataAbstraction::TermsList>
|
312
|
+
# my_terms.label #=> :type
|
313
|
+
#
|
314
|
+
# my_terms = my_terms_list.mass #=> <AMEE::DataAbstraction::TermsList>
|
315
|
+
# my_terms.label #=> :mass
|
316
|
+
#
|
317
|
+
# my_terms = my_terms_list.co2 #=> <AMEE::DataAbstraction::TermsList>
|
318
|
+
# my_terms.label #=> :co2
|
319
|
+
#
|
320
|
+
# ---
|
321
|
+
#
|
322
|
+
# Call either the <tt>#sort_by</tt> or <tt>#sort_by!</tt> methods including
|
323
|
+
# the argument term as part of the method name, e.g.,
|
324
|
+
#
|
325
|
+
# my_calculation_collection.sort_by_value
|
326
|
+
#
|
327
|
+
# #=> <AMEE::DataAbstraction::TermsList ... >
|
328
|
+
#
|
329
|
+
# my_calculation_collection.sort_by_name!
|
330
|
+
#
|
331
|
+
# #=> <AMEE::DataAbstraction::TermsList ... >
|
332
|
+
#
|
333
|
+
def method_missing(method, *args, &block)
|
334
|
+
if labels.include? method
|
335
|
+
TermsList.new select{ |x| x.label == method }
|
336
|
+
elsif method.to_s =~ /sort_by_(.*)!/ and self.class::TermProperties.include? $1.to_sym
|
337
|
+
sort_by! $1.to_sym
|
338
|
+
elsif method.to_s =~ /sort_by_(.*)/ and self.class::TermProperties.include? $1.to_sym
|
339
|
+
sort_by $1.to_sym
|
340
|
+
else
|
341
|
+
super
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# Copyright (C) 2011 AMEE UK Ltd. - http://www.amee.com
|
2
|
+
# Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details.
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
gem 'amee-data-abstraction'
|
6
|
+
require 'amee-data-abstraction'
|
7
|
+
|
8
|
+
require 'amee/data_abstraction/result'
|
data/rails/init.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
|
2
|
+
# Copyright (C) 2011 AMEE UK Ltd. - http://www.amee.com
|
3
|
+
# Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details.
|
4
|
+
|
5
|
+
require 'amee-data-abstraction'
|
6
|
+
|
7
|
+
::AMEE::DataAbstraction::CalculationCollection.class_eval { include AMEE::DataAbstraction::CalculationCollectionAnalyticsSupport }
|
8
|
+
::AMEE::DataAbstraction::TermsList.class_eval { include AMEE::DataAbstraction::TermsListAnalyticsSupport }
|
9
|
+
::AMEE::DataAbstraction::Term.class_eval { include AMEE::DataAbstraction::TermAnalyticsSupport }
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
include AMEE::DataAbstraction
|
4
|
+
|
5
|
+
describe CalculationCollection do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
initialize_calculation_set
|
9
|
+
@calcs = []
|
10
|
+
@calcs << add_elec_calc(500,240)
|
11
|
+
@calcs << add_elec_calc(1000,480)
|
12
|
+
@calcs << add_elec_calc(1234,600)
|
13
|
+
@coll = CalculationCollection.new @calcs
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should add calcs to collection" do
|
17
|
+
@coll.should be_a CalculationCollection
|
18
|
+
@coll.first.should be_a AMEE::DataAbstraction::OngoingCalculation
|
19
|
+
@coll.size.should eql 3
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be homogeneous" do
|
23
|
+
@coll.should be_homogeneous
|
24
|
+
@coll.should_not be_heterogeneous
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should be heterogeneous" do
|
28
|
+
@coll << add_transport_calc(1000,231)
|
29
|
+
@coll.should_not be_homogeneous
|
30
|
+
@coll.should be_heterogeneous
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should hold all calculation terms" do
|
34
|
+
terms = @coll.terms
|
35
|
+
terms.should be_a AMEE::DataAbstraction::TermsList
|
36
|
+
terms.size.should eql 9
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return all like terms dynamically" do
|
40
|
+
terms = @coll.co2
|
41
|
+
terms.should be_a AMEE::DataAbstraction::TermsList
|
42
|
+
terms.size.should eql 3
|
43
|
+
terms.labels.uniq.should eql [:co2]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should delegate selector methods" do
|
47
|
+
terms = @coll.outputs
|
48
|
+
terms.all? {|term| term.is_a? AMEE::DataAbstraction::Output }.should be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should sum term values with homegeneous calcs" do
|
52
|
+
@coll.co2.sum.to_s.should eql "1320.0 t"
|
53
|
+
@coll.co2.mean.to_s.should eql "440.0 t"
|
54
|
+
@coll.usage.sum.to_s.should eql "2734.0 kWh"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should sum term values with heterogeneous calcs" do
|
58
|
+
@coll << add_transport_calc(1000,231)
|
59
|
+
@coll.co2.sum.to_s.should eql "1551.0 t"
|
60
|
+
@coll.distance.sum.to_s.should eql "1000.0 km"
|
61
|
+
@coll.usage.sum.to_s.should eql "2734.0 kWh"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should sort self by specified term" do
|
65
|
+
# check order
|
66
|
+
@coll.first['co2'].value.should eql 240
|
67
|
+
@coll[1]['co2'].value.should eql 480
|
68
|
+
@coll.last['co2'].value.should eql 600
|
69
|
+
|
70
|
+
# reverse and check order
|
71
|
+
@coll.reverse!
|
72
|
+
@coll.first['co2'].value.should eql 600
|
73
|
+
@coll[1]['co2'].value.should eql 480
|
74
|
+
@coll.last['co2'].value.should eql 240
|
75
|
+
|
76
|
+
# sort by co2 and check order
|
77
|
+
@coll.sort_by_co2!
|
78
|
+
@coll.first['co2'].value.should eql 240
|
79
|
+
@coll[1]['co2'].value.should eql 480
|
80
|
+
@coll.last['co2'].value.should eql 600
|
81
|
+
|
82
|
+
# reverse and check order
|
83
|
+
@coll.reverse!
|
84
|
+
@coll.first['co2'].value.should eql 600
|
85
|
+
@coll[1]['co2'].value.should eql 480
|
86
|
+
@coll.last['co2'].value.should eql 240
|
87
|
+
|
88
|
+
# sort by usage and check order
|
89
|
+
@coll.sort_by_usage!
|
90
|
+
@coll.first['co2'].value.should eql 240
|
91
|
+
@coll[1]['co2'].value.should eql 480
|
92
|
+
@coll.last['co2'].value.should eql 600
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should sort by specified term and return new" do
|
96
|
+
# check order
|
97
|
+
@coll.first['co2'].value.should eql 240
|
98
|
+
@coll[1]['co2'].value.should eql 480
|
99
|
+
@coll.last['co2'].value.should eql 600
|
100
|
+
|
101
|
+
# reverse and check order
|
102
|
+
@coll.reverse!
|
103
|
+
@coll.first['co2'].value.should eql 600
|
104
|
+
@coll[1]['co2'].value.should eql 480
|
105
|
+
@coll.last['co2'].value.should eql 240
|
106
|
+
|
107
|
+
# instantiate new based on co2 and check order
|
108
|
+
coll = @coll.sort_by_co2
|
109
|
+
coll.first['co2'].value.should eql 240
|
110
|
+
coll[1]['co2'].value.should eql 480
|
111
|
+
coll.last['co2'].value.should eql 600
|
112
|
+
|
113
|
+
# check reversed order of original
|
114
|
+
@coll.first['co2'].value.should eql 600
|
115
|
+
@coll[1]['co2'].value.should eql 480
|
116
|
+
@coll.last['co2'].value.should eql 240
|
117
|
+
|
118
|
+
# instantiate new based on usage and check order
|
119
|
+
coll = @coll.sort_by_usage
|
120
|
+
coll.first['co2'].value.should eql 240
|
121
|
+
coll[1]['co2'].value.should eql 480
|
122
|
+
coll.last['co2'].value.should eql 600
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should standardize units in place" do
|
126
|
+
@coll.first['usage'].unit 'J'
|
127
|
+
@coll.first['usage'].value.should eql 500
|
128
|
+
@coll.first['usage'].unit.label.should eql 'J'
|
129
|
+
@coll.standardize_units!(:usage,:kWh)
|
130
|
+
@coll.first['usage'].unit 'kWh'
|
131
|
+
@coll.first['usage'].value.should be_close 0.000138888888888889,0.000001
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should standardize units returning new collection" do
|
135
|
+
@coll.first['co2'].value.should eql 240
|
136
|
+
@coll.first['co2'].unit.label.should eql 't'
|
137
|
+
coll = @coll.standardize_units(:co2,:lb)
|
138
|
+
coll.first['co2'].unit 'lb'
|
139
|
+
coll.first['co2'].value.should be_close 529109.429243706,0.01
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should handle 'type' as a terms filter" do
|
143
|
+
calcs = []
|
144
|
+
calcs << add_transport_calc(500,240)
|
145
|
+
calcs << add_transport_calc(1000,480)
|
146
|
+
calcs << add_transport_calc(1234,600)
|
147
|
+
@coll = CalculationCollection.new calcs
|
148
|
+
@coll.each { |calc| calc['type'].value "car" }
|
149
|
+
terms = @coll.type
|
150
|
+
terms.should be_a TermsList
|
151
|
+
terms.values.all? {|val| val == "car"}.should be_true
|
152
|
+
end
|
153
|
+
|
154
|
+
it "should add calculation collections" do
|
155
|
+
calcs1 = []
|
156
|
+
calcs1 << add_transport_calc(500,240)
|
157
|
+
calcs1 << add_transport_calc(1000,480)
|
158
|
+
calcs1 << add_transport_calc(1234,600)
|
159
|
+
@coll1 = CalculationCollection.new(calcs1)
|
160
|
+
calcs2 = []
|
161
|
+
calcs2 << add_transport_calc(700,350)
|
162
|
+
calcs2 << add_transport_calc(5,11)
|
163
|
+
calcs2 << add_transport_calc(123,234)
|
164
|
+
@coll2 = CalculationCollection.new(calcs2)
|
165
|
+
@coll3 = @coll1 + @coll2
|
166
|
+
@coll3.should be_a CalculationCollection
|
167
|
+
@coll3.size.should eql 6
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should subtract calculation collections" do
|
171
|
+
calcs1 = []
|
172
|
+
calcs1 << add_transport_calc(500,240)
|
173
|
+
calcs1 << add_transport_calc(1000,480)
|
174
|
+
calcs1 << add_transport_calc(1234,600)
|
175
|
+
@coll1 = CalculationCollection.new(calcs1)
|
176
|
+
calcs2 = []
|
177
|
+
calcs2 << add_transport_calc(500,240)
|
178
|
+
calcs2 << add_transport_calc(1000,480)
|
179
|
+
@coll2 = CalculationCollection.new(calcs2)
|
180
|
+
@coll3 = @coll1 - @coll2
|
181
|
+
@coll3.should be_a CalculationCollection
|
182
|
+
@coll3.size.should eql 1
|
183
|
+
@coll3.first['distance'].value.should eql 1234
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should add to calculation collection using += syntax" do
|
187
|
+
@coll = CalculationCollection.new
|
188
|
+
@coll += add_transport_calc(500,240)
|
189
|
+
@coll.should be_a CalculationCollection
|
190
|
+
@coll += add_transport_calc(1000,480)
|
191
|
+
@coll.should be_a CalculationCollection
|
192
|
+
@coll += add_transport_calc(1234,600)
|
193
|
+
@coll.should be_a CalculationCollection
|
194
|
+
@coll.size.should eql 3
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should subtract from calculation collection using -= syntax" do
|
198
|
+
calcs1 = []
|
199
|
+
calcs1 << add_transport_calc(500,240)
|
200
|
+
calcs1 << add_transport_calc(1000,480)
|
201
|
+
calcs1 << add_transport_calc(1234,600)
|
202
|
+
@coll1 = CalculationCollection.new(calcs1)
|
203
|
+
@coll1 -= add_transport_calc(500,240)
|
204
|
+
@coll1 -= add_transport_calc(1000,480)
|
205
|
+
@coll1.should be_a CalculationCollection
|
206
|
+
@coll1.size.should eql 1
|
207
|
+
@coll1.first['distance'].value.should eql 1234
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should add all outputs" do
|
211
|
+
res = @coll.sum_all_outputs
|
212
|
+
res.instance_of?(TermsList).should be_true
|
213
|
+
res.first.value.should eql 1320.0
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should respond to dynamic term methods" do
|
217
|
+
@coll.respond_to?(:co2).should be_true
|
218
|
+
@coll.respond_to?(:usage).should be_true
|
219
|
+
@coll.respond_to?(:distance).should be_false
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should respond to dynamic sort methods" do
|
223
|
+
@coll.respond_to?(:sort_by_co2).should be_true
|
224
|
+
@coll.respond_to?(:sort_by_usage!).should be_true
|
225
|
+
@coll.respond_to?(:sort_by_distance).should be_false
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should return co2 outputs" do
|
229
|
+
terms = @coll.co2_or_co2e_outputs
|
230
|
+
terms.size.should eql 3
|
231
|
+
terms.first.label.should eql :co2
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|