amee-data-abstraction 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.rvmrc +1 -0
  2. data/CHANGELOG.txt +4 -0
  3. data/Gemfile +16 -0
  4. data/Gemfile.lock +41 -0
  5. data/LICENSE.txt +27 -0
  6. data/README.txt +188 -0
  7. data/Rakefile +102 -0
  8. data/VERSION +1 -0
  9. data/amee-data-abstraction.gemspec +115 -0
  10. data/examples/_calculator_form.erb +27 -0
  11. data/examples/calculation_controller.rb +16 -0
  12. data/init.rb +4 -0
  13. data/lib/amee-data-abstraction.rb +30 -0
  14. data/lib/amee-data-abstraction/calculation.rb +236 -0
  15. data/lib/amee-data-abstraction/calculation_set.rb +101 -0
  16. data/lib/amee-data-abstraction/drill.rb +63 -0
  17. data/lib/amee-data-abstraction/exceptions.rb +47 -0
  18. data/lib/amee-data-abstraction/input.rb +197 -0
  19. data/lib/amee-data-abstraction/metadatum.rb +58 -0
  20. data/lib/amee-data-abstraction/ongoing_calculation.rb +545 -0
  21. data/lib/amee-data-abstraction/output.rb +16 -0
  22. data/lib/amee-data-abstraction/profile.rb +108 -0
  23. data/lib/amee-data-abstraction/prototype_calculation.rb +350 -0
  24. data/lib/amee-data-abstraction/term.rb +506 -0
  25. data/lib/amee-data-abstraction/terms_list.rb +150 -0
  26. data/lib/amee-data-abstraction/usage.rb +90 -0
  27. data/lib/config/amee_units.rb +129 -0
  28. data/lib/core-extensions/class.rb +27 -0
  29. data/lib/core-extensions/hash.rb +43 -0
  30. data/lib/core-extensions/ordered_hash.rb +21 -0
  31. data/lib/core-extensions/proc.rb +15 -0
  32. data/rails/init.rb +32 -0
  33. data/spec/amee-data-abstraction/calculation_set_spec.rb +54 -0
  34. data/spec/amee-data-abstraction/calculation_spec.rb +75 -0
  35. data/spec/amee-data-abstraction/drill_spec.rb +38 -0
  36. data/spec/amee-data-abstraction/input_spec.rb +77 -0
  37. data/spec/amee-data-abstraction/metadatum_spec.rb +17 -0
  38. data/spec/amee-data-abstraction/ongoing_calculation_spec.rb +494 -0
  39. data/spec/amee-data-abstraction/profile_spec.rb +39 -0
  40. data/spec/amee-data-abstraction/prototype_calculation_spec.rb +256 -0
  41. data/spec/amee-data-abstraction/term_spec.rb +385 -0
  42. data/spec/amee-data-abstraction/terms_list_spec.rb +53 -0
  43. data/spec/config/amee_units_spec.rb +71 -0
  44. data/spec/core-extensions/class_spec.rb +25 -0
  45. data/spec/core-extensions/hash_spec.rb +44 -0
  46. data/spec/core-extensions/ordered_hash_spec.rb +12 -0
  47. data/spec/core-extensions/proc_spec.rb +12 -0
  48. data/spec/fixtures/electricity.rb +35 -0
  49. data/spec/fixtures/electricity_and_transport.rb +55 -0
  50. data/spec/fixtures/transport.rb +26 -0
  51. data/spec/spec.opts +2 -0
  52. data/spec/spec_helper.rb +244 -0
  53. metadata +262 -0
@@ -0,0 +1,16 @@
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
+ # :title: Class: AMEE::DataAbstraction::Output
5
+
6
+ module AMEE
7
+ module DataAbstraction
8
+
9
+ # Subclass of <tt>Term</tt> providing methods and attributes appropriate for
10
+ # representing AMEE return values specifically
11
+ #
12
+ class Output < Term
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,108 @@
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
+ # :title: Class: AMEE::DataAbstraction::Profile
5
+
6
+ module AMEE
7
+ module DataAbstraction
8
+
9
+ # Subclass of <tt>Input</tt> providing methods and attributes appropriate for
10
+ # representing AMEE profile item values specifically
11
+ #
12
+ class Profile < Input
13
+
14
+ # Represents a list of acceptable choices for the value of <tt>self</tt>.
15
+ # Set the list of choices by passing an argument. Retrieve the choices by
16
+ # calling without an argument, e.g.,
17
+ #
18
+ # my_term.choices 'industrial', 'commercial', 'residential'
19
+ #
20
+ # my_term.choices #=> [ 'industrial',
21
+ # 'commercial',
22
+ # 'residential' ]
23
+ #
24
+ # A single value of <tt>nil</tt> represents an unrestricted value
25
+ # .
26
+ attr_property :choices
27
+
28
+ # Initialization of <i>Input</i> objects follows that of the parent
29
+ # <i>Term</i> class. The <tt>interface</tt> attribute of <tt>self</tt> is
30
+ # set to <tt>:drop_down</tt> by default if a list of choices is defined
31
+ # using the <tt>choices</tt> attribute. Otherwise the <tt>interface</tt>
32
+ # attribute is set to <tt>:test_box</tt>, but can be manually configured if
33
+ # required.
34
+ #
35
+ def initialize(options={},&block)
36
+ super
37
+ interface :drop_down unless choices.blank?
38
+ choice_validation_message unless choices.blank?
39
+ interface :text_box unless interface
40
+ end
41
+
42
+ # Return <tt>true</tt> if the value of <tt>self</tt> is NOT required before
43
+ # the parent calculation can be calculated. Otherwise, return <tt>false</tt>.
44
+ #
45
+ # If no argument is provided, optional status is determined according to the
46
+ # current usage of the parent calculation. Optionality can be determined for
47
+ # a specific usage by passing in the usage path as an argument
48
+ #
49
+ def optional?(usage=nil)
50
+ usage||=parent.current_usage
51
+ usage ? amee_ivd.optional?(usage) : super()
52
+ end
53
+
54
+ # Return <tt>true</tt> if the value of <tt>self</tt> is required before
55
+ # the parent calculation can be calculated. Otherwise, return <tt>false</tt>.
56
+ #
57
+ # If no argument is provided, compulsory status is determined according to
58
+ # the current usage of the parent calculation. Compulsory status can be
59
+ # determined for a specific usage by passing in the usage path as an argument
60
+ #
61
+ def compulsory?(usage=nil)
62
+ usage||=parent.current_usage
63
+ usage ? amee_ivd.compulsory?(usage) : super()
64
+ end
65
+
66
+ # Return <tt>true</tt> if the value of <tt>self</tt> is either compulsory
67
+ # OR optional in the owning calculation, i.e. is NOT forbidden.
68
+ #
69
+ # If no argument is provided, the optional/compulsory status is defined
70
+ # according to the current usage of the parent caluclation. Otherwise,
71
+ # optional/compulsory status is determined on the basis of the usage whose
72
+ # AMEE platform path matches <tt>usage</tt>
73
+ #
74
+ def in_use?(usage)
75
+ compulsory?(usage)||optional?(usage)
76
+ end
77
+
78
+ # Return <tt>true</tt> if the value of <tt>self</tt> is neither compulsory
79
+ # OR optional in the owning calculation, i.e. is forbidden.
80
+ #
81
+ # If no argument is provided, forbbiden status is defined according to the
82
+ # current usage of the parent caluclation. Otherwise, it is determined on
83
+ # the basis of the usage whose AMEE platform path matches <tt>usage</tt>
84
+ #
85
+ def out_of_use?(usage)
86
+ !in_use?(usage)
87
+ end
88
+
89
+ # Return the <i>AMEE::Admin::ItemValueDefinition</i> object associated
90
+ # with <tt>self</tt>.
91
+ #
92
+ def amee_ivd
93
+ parent.amee_ivds.detect{|x|x.path==path}
94
+ end
95
+
96
+ # Returns <tt>true</tt> if the value set for <tt>self</tt> is valid. If
97
+ # <tt>self</tt> contains neither a custom validation pattern nor any
98
+ # defined choices, <tt>true</tt> is returned. Otherwise, validity depends
99
+ # on the custom validation being successful (if present) and the the value
100
+ # of <tt>self</tt> matching one of the entries in the <tt>choices</tt>
101
+ # attribute (if defined). Otherwise, returns <tt>false</tt>.
102
+ #
103
+ def valid?
104
+ super && (choices.blank? || choices.include?(value))
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,350 @@
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
+ # :title: Class: AMEE::DataAbstraction::PrototypeCalculation
5
+
6
+ module AMEE
7
+ module DataAbstraction
8
+
9
+ # The <i>PrototypeCalculation</i> class represents a template for a potential
10
+ # calculation within the AMEE platfom.
11
+ #
12
+ # The class inherits from the <i>Calculation</i> class and is therefore primarly
13
+ # characterised by the <tt>label</tt>, <tt>name</tt>, and <tt>path</tt> attributes,
14
+ # as well as an associated instance of the <tt>TermsList</tt> class which represents
15
+ # each of the values (input, outputs, metdata) involved in the calculation. Unlike
16
+ # the <i>OngoingCalculation</i>, the terms associated with an instance of
17
+ # <i>PrototypeCalculation</i> will typically contains blank (nil) values.
18
+ #
19
+ # Objects of the class <i>PrototypeCalculation</tt> are typically instantiated
20
+ # using block ('DSL') syntax, within which each of the attributes and associated
21
+ # terms are defined. Thus,
22
+ #
23
+ # calculation = PrototypeCalculation.new {
24
+ #
25
+ # label :electricity
26
+ # name "Domestic electricity consumption"
27
+ # path "some/path/in/amee"
28
+ # drill { ... }
29
+ # profile { ... }
30
+ # ...
31
+ #
32
+ # }
33
+ #
34
+ class PrototypeCalculation < Calculation
35
+
36
+ public
37
+
38
+ # Initialize a new instance of <i>PrototypeCalculation</i>.
39
+ #
40
+ # The calculation can be configured in place by passing a block (evaluated
41
+ # in the context of the new instance) which defines the calculation properties
42
+ # using the macro-style instance helper methods.
43
+ #
44
+ # calculation = PrototypeCalculation.new {
45
+ #
46
+ # label :transport
47
+ # path "some/other/path/in/amee"
48
+ # terms_from_amee
49
+ # metadatum { ... }
50
+ # start_and_end_dates
51
+ # ...
52
+ #
53
+ # }
54
+ #
55
+ def initialize(options={},&block)
56
+ super()
57
+ instance_eval(&block) if block
58
+ end
59
+
60
+ # Associate a new instance of the <i>Profile</i> class (subclass of the
61
+ # <i>Term</i> class) with <tt>self</tt>, for representing an AMEE profile item input
62
+ #
63
+ # The newly instantiated <i>Term</i> object is configured according to the
64
+ # ('DSL') block passed in.
65
+ #
66
+ # my_protptype.profile {
67
+ # label :energy_used
68
+ # path 'energyUsed'
69
+ # default_unit :kWh
70
+ # }
71
+ #
72
+ def profile(options={},&block)
73
+ construct(Profile,options,&block)
74
+ end
75
+
76
+ # Associate a new instance of the <i>Drill</i> class (subclass of the
77
+ # <i>Term</i> class) with <tt>self</tt>, for representing an AMEE drill down choice
78
+ #
79
+ # The newly instantiated <i>Term</i> object is configured according to the
80
+ # ('DSL') block passed in.
81
+ #
82
+ # my_protptype.drill {
83
+ # label :fuel_type
84
+ # path 'fuelType'
85
+ # fixed 'diesel'
86
+ # }
87
+ #
88
+ def drill(options={},&block)
89
+ construct(Drill,options,&block)
90
+ end
91
+
92
+ # Associate a new instance of the <i>Output</i> class (subclass of the
93
+ # <i>Term</i> class) with <tt>self</tt>, for representing an AMEE return value
94
+ #
95
+ # The newly instantiated <i>Term</i> object is configured according to the
96
+ # ('DSL') block passed in.
97
+ #
98
+ # my_protptype.output {
99
+ # label :co2
100
+ # path 'CO2'
101
+ # }
102
+ #
103
+ def output(options={},&block)
104
+ construct(Output,options,&block)
105
+ end
106
+
107
+ # Associate a new instance of the <i>Metadatum</i> class (subclass of the
108
+ # <i>Term</i> class) with <tt>self</tt>, for representing arbitrary metadata which
109
+ # is to be associated with each calculation. These may represent unique
110
+ # references to location, equipment (vehicles, furnaces), reporting periods,
111
+ # for example.
112
+ #
113
+ # The newly instantiated <i>Term</i> object is configured according to the
114
+ # ('DSL') block passed in.
115
+ #
116
+ # my_protptype.metadatum {
117
+ # label :reporting_period
118
+ # value "July 2010"
119
+ # }
120
+ #
121
+ def metadatum(options={},&block)
122
+ construct(Metadatum,options,&block)
123
+ end
124
+
125
+ # Helper method for automatically instantiating <i>Drill</i> class term
126
+ # objects representing all drill down choices based on those associated with
127
+ # the AMEE platform category with which <tt>self</tt> corresponds.
128
+ #
129
+ def all_drills
130
+ # Need to use #drill_downs rather than simply finding drills
131
+ # directly from #amee_ivds in order to establish drill order
132
+ amee_item_definition.drill_downs.each do |apath|
133
+ amee_ivds.each do |ivd|
134
+ next unless ivd.path == apath
135
+ drill {
136
+ path ivd.path
137
+ name ivd.name
138
+ }
139
+ end
140
+ end
141
+ end
142
+
143
+ # Helper method for automatically instantiating <i>Profile</i> class term
144
+ # objects representing all profile item values based on those associated with
145
+ # the AMEE platform category with which <tt>self</tt> corresponds.
146
+ #
147
+ # Each term is instantiated with path, name, choices, default_unit and
148
+ # default_per_unit attributes corresponding to those defined in the AMEE
149
+ # platform.
150
+ #
151
+ def all_profiles
152
+ amee_ivds.each do |ivd|
153
+ next unless ivd.profile?
154
+ construct_from_ivd(Profile,ivd)
155
+ end
156
+ end
157
+
158
+ # Helper method for automatically instantiating <i>Output</i> class term
159
+ # objects representing all return values based on those associated with
160
+ # the AMEE platform category with which <tt>self</tt> corresponds.
161
+ #
162
+ # Each term is instantiated with path, default_unit and default_per_unit
163
+ # attributes corresponding to those defined in the AMEE platform.
164
+ #
165
+ def all_outputs
166
+ amee_return_values.each do |rvd|
167
+ output {
168
+ path rvd.name
169
+ default_unit rvd.unit
170
+ default_per_unit rvd.perunit
171
+ }
172
+ end
173
+ end
174
+
175
+ # Helper method for automatically instantiating <i>Profile</i> class term
176
+ # objects representing only the profile item values associated with a
177
+ # particular usage (specified by <tt>usage</tt>) for the AMEE platform
178
+ # category with which <tt>self</tt> corresponds.
179
+ #
180
+ # This method does not permit dynamic usage switching during run-time.
181
+ #
182
+ # Each term is instantiated with path, name, choices, default_unit and
183
+ # default_per_unit attributes corresponding to those defined in the AMEE
184
+ # platform.
185
+ #
186
+ def profiles_from_usage(usage)
187
+ self.fixed_usage usage
188
+ amee_ivds.each do |ivd|
189
+ next unless ivd.profile?
190
+ construct_from_ivd(Profile,ivd) if ivd.compulsory?(usage) || ivd.optional?(usage)
191
+ end
192
+ end
193
+
194
+ # Helper method for automatically instantiating <i>Profile</i>, <i>Drill</i>
195
+ # and <i>Output</i> class term objects representing all profile item values,
196
+ # drill choices and return values associated with the AMEE platform category
197
+ # with which <tt>self</tt> corresponds.
198
+ #
199
+ # Optionally, instantiate only those profile terms corresponding to a
200
+ # particular usage by passing the path of the required usage as an argument.
201
+ # The latter case does not allow dynamic usage switching at run-time.
202
+ #
203
+ # Each term is instantiated with path, name, choices, default_unit and
204
+ # default_per_unit attributes corresponding to those defined in the AMEE
205
+ # platform.
206
+ #
207
+ def terms_from_amee(usage=nil)
208
+ all_drills
209
+ if usage
210
+ profiles_from_usage(usage)
211
+ else
212
+ all_profiles
213
+ end
214
+ all_outputs
215
+ end
216
+
217
+ # Helper method for automatically instantiating <i>Profile</i>, <i>Drill</i>
218
+ # and <i>Output</i> class term objects representing all profile item values,
219
+ # drill choices and return values associated with the AMEE platform category
220
+ # with which <tt>self</tt> corresponds.
221
+ #
222
+ # Also automatically defines a usage term for the usage represented by
223
+ # <tt>ausage</tt> to enable dynamic usage switching. The profile terms
224
+ # associated with the specified usage are automatically activated and
225
+ # deactivated as appropriate, but this can be switched at run-time by
226
+ # changing the value of the instantiated usage term.
227
+ #
228
+ # Each term is instantiated with path, name, choices, default_unit and
229
+ # default_per_unit attributes (where appropriate) corresponding to those
230
+ # defined in the AMEE platform.
231
+ #
232
+ def terms_from_amee_dynamic_usage(ausage)
233
+ all_drills
234
+ usage{ value ausage}
235
+ all_outputs
236
+ end
237
+
238
+ # Helper method for automatically instantiating <i>Profile</i> class term
239
+ # objects representing all profile item values associated with the AMEE
240
+ # platform category represented by <tt>self</tt>, and instantiating a new instance
241
+ # of the <i>Usage</i> term class which can be used for dynamically switching
242
+ # usages at run-time.
243
+ #
244
+ # Each term is instantiated with path, name, choices, default_unit and
245
+ # default_per_unit attributes corresponding to those defined in the AMEE
246
+ # platform.
247
+ #
248
+ # The newly instantiated <i>Usage</i> object can be configured in place
249
+ # according to the ('DSL') block passed in, e.g.,
250
+ #
251
+ # my_protptype.usage {
252
+ # inactive :disabled
253
+ # value nil
254
+ # }
255
+ #
256
+ def usage(options={},&block)
257
+ all_profiles
258
+ construct(Usage,options.merge(:first=>true),&block)
259
+ end
260
+
261
+ # Helper method for automatically instantiating <i>Metadatum</i> class term
262
+ # objects explicitly configured for storing start and end dates for an AMEE
263
+ # platform profile item.
264
+ #
265
+ def start_and_end_dates
266
+ metadatum {
267
+ path 'start_date'
268
+ name 'Start date'
269
+ interface :date
270
+ type :datetime
271
+ validation lambda{|v| v.is_a?(Time) || v.is_a?(DateTime) || (v.is_a?(String) && Date.parse(v) rescue false)}
272
+ }
273
+ metadatum {
274
+ path 'end_date'
275
+ name 'End date'
276
+ interface :date
277
+ type :datetime
278
+ validation lambda{|v| v.is_a?(Time) || v.is_a?(DateTime) || (v.is_a?(String) && Date.parse(v) rescue false)}
279
+ }
280
+ end
281
+
282
+ # Helper method for reopening and modifying the definition of the term with
283
+ # the label attribute matching <tt>label</tt>. Modification is specified in
284
+ # the passed block, which is evaluated in the context of the respective term
285
+ # instance.
286
+ #
287
+ # This is typically used to override (customize) the attributes and behaviour
288
+ # of term autoloaded from the AMEE platform using one of the instance helper
289
+ # methods of <tt>self</tt>.
290
+ #
291
+ def correcting(label,&block)
292
+ return unless contents[label]
293
+ contents[label].instance_eval(&block)
294
+ end
295
+
296
+ #Instantiate an OngoingCalculation based on this prototype, ready for
297
+ #communication with AMEE.
298
+ def begin_calculation
299
+ result=OngoingCalculation.new
300
+ contents.each do |k,v|
301
+ result.contents[k]=v.clone
302
+ result.contents[k].parent=result
303
+ end
304
+ result.path path
305
+ result.name name
306
+ result.label label
307
+ result.fixed_usage fixed_usage
308
+ result.save_amee saved_amee
309
+ result
310
+ end
311
+
312
+ private
313
+
314
+ def construct_from_ivd(klass,ivd)
315
+ # Initialize boolean values as ruby boolean objects
316
+ if (ivd.valuetype == "BOOLEAN") && (ivd.default == ("true"||"TRUE"))
317
+ default_value = true
318
+ elsif (ivd.valuetype == "BOOLEAN") && (ivd.default == ("false"||"FALSE"))
319
+ default_value = false
320
+ else
321
+ default_value = ivd.default
322
+ end
323
+
324
+ construct(klass) {
325
+ path ivd.path
326
+ name ivd.name
327
+ note ivd.meta.wikidoc
328
+ type ivd.valuetype.downcase
329
+ value default_value
330
+ choices ivd.choices
331
+ default_unit ivd.unit
332
+ default_per_unit ivd.perunit
333
+ }
334
+ end
335
+
336
+ # Construct a term of class klass, and evaluate a DSL block in its context.
337
+ def construct(klass,options={},&block)
338
+ new_content=klass.new(options.merge(:parent=>self),&block)
339
+ raise Exceptions::DSL.new(
340
+ "Attempt to create #{klass} without a label") unless new_content.label
341
+ if options[:first]
342
+ @contents.insert_at_start(new_content.label,new_content)
343
+ else
344
+ @contents[new_content.label]=new_content
345
+ end
346
+ end
347
+
348
+ end
349
+ end
350
+ end