amee-analytics 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|