# Copyright (C) 2011 AMEE UK Ltd. - http://www.amee.com # Released as Open Source Software under the BSD 3-Clause license. See LICENSE.txt for details. # :title: Class: AMEE::DataAbstraction::PrototypeCalculation module AMEE module DataAbstraction # The PrototypeCalculation class represents a template for a potential # calculation within the AMEE platfom. # # The class inherits from the Calculation class and is therefore primarly # characterised by the label, name, and path attributes, # as well as an associated instance of the TermsList class which represents # each of the values (input, outputs, metdata) involved in the calculation. Unlike # the OngoingCalculation, the terms associated with an instance of # PrototypeCalculation will typically contains blank (nil) values. # # Objects of the class PrototypeCalculation are typically instantiated # using block ('DSL') syntax, within which each of the attributes and associated # terms are defined. Thus, # # calculation = PrototypeCalculation.new { # # label :electricity # name "Domestic electricity consumption" # path "some/path/in/amee" # drill { ... } # profile { ... } # ... # # } # class PrototypeCalculation < Calculation public # Initialize a new instance of PrototypeCalculation. # # The calculation can be configured in place by passing a block (evaluated # in the context of the new instance) which defines the calculation properties # using the macro-style instance helper methods. # # calculation = PrototypeCalculation.new { # # label :transport # path "some/other/path/in/amee" # terms_from_amee # metadatum { ... } # start_and_end_dates # ... # # } # def initialize(options={},&block) super() instance_eval(&block) if block end # Associate a new instance of the Profile class (subclass of the # Term class) with self, for representing an AMEE profile item input # # The newly instantiated Term object is configured according to the # ('DSL') block passed in. # # my_protptype.profile { # label :energy_used # path 'energyUsed' # default_unit :kWh # } # def profile(options={},&block) construct(Profile,options,&block) end # Associate a new instance of the Drill class (subclass of the # Term class) with self, for representing an AMEE drill down choice # # The newly instantiated Term object is configured according to the # ('DSL') block passed in. # # my_protptype.drill { # label :fuel_type # path 'fuelType' # fixed 'diesel' # } # def drill(options={},&block) construct(Drill,options,&block) end # Associate a new instance of the Output class (subclass of the # Term class) with self, for representing an AMEE return value # # The newly instantiated Term object is configured according to the # ('DSL') block passed in. # # my_protptype.output { # label :co2 # path 'CO2' # } # def output(options={},&block) construct(Output,options,&block) end # Associate a new instance of the Metadatum class (subclass of the # Term class) with self, for representing arbitrary metadata which # is to be associated with each calculation. These may represent unique # references to location, equipment (vehicles, furnaces), reporting periods, # for example. # # The newly instantiated Term object is configured according to the # ('DSL') block passed in. # # my_protptype.metadatum { # label :reporting_period # value "July 2010" # } # def metadatum(options={},&block) construct(Metadatum,options,&block) end # Helper method for automatically instantiating Drill class term # objects representing all drill down choices based on those associated with # the AMEE platform category with which self corresponds. # def all_drills # Need to use #drill_downs rather than simply finding drills # directly from #amee_ivds in order to establish drill order amee_item_definition.drill_downs.each do |apath| amee_ivds.each do |ivd| next unless ivd.path == apath drill { path ivd.path name ivd.name } end end end # Helper method for automatically instantiating Profile class term # objects representing all profile item values based on those associated with # the AMEE platform category with which self corresponds. # # Each term is instantiated with path, name, choices, default_unit and # default_per_unit attributes corresponding to those defined in the AMEE # platform. # def all_profiles amee_ivds.each do |ivd| next unless ivd.profile? construct_from_ivd(Profile,ivd) end end # Helper method for automatically instantiating Output class term # objects representing all return values based on those associated with # the AMEE platform category with which self corresponds. # # Each term is instantiated with path, default_unit and default_per_unit # attributes corresponding to those defined in the AMEE platform. # def all_outputs amee_return_values.each do |rvd| output { path rvd.name default_unit rvd.unit default_per_unit rvd.perunit } end end # Helper method for automatically instantiating Profile class term # objects representing only the profile item values associated with a # particular usage (specified by usage) for the AMEE platform # category with which self corresponds. # # This method does not permit dynamic usage switching during run-time. # # Each term is instantiated with path, name, choices, default_unit and # default_per_unit attributes corresponding to those defined in the AMEE # platform. # def profiles_from_usage(usage) self.fixed_usage usage amee_ivds.each do |ivd| next unless ivd.profile? construct_from_ivd(Profile,ivd) if ivd.compulsory?(usage) || ivd.optional?(usage) end end # Helper method for automatically instantiating Profile, Drill # and Output class term objects representing all profile item values, # drill choices and return values associated with the AMEE platform category # with which self corresponds. # # Optionally, instantiate only those profile terms corresponding to a # particular usage by passing the path of the required usage as an argument. # The latter case does not allow dynamic usage switching at run-time. # # Each term is instantiated with path, name, choices, default_unit and # default_per_unit attributes corresponding to those defined in the AMEE # platform. # def terms_from_amee(usage=nil) all_drills if usage profiles_from_usage(usage) else all_profiles end all_outputs end # Helper method for automatically instantiating Profile, Drill # and Output class term objects representing all profile item values, # drill choices and return values associated with the AMEE platform category # with which self corresponds. # # Also automatically defines a usage term for the usage represented by # ausage to enable dynamic usage switching. The profile terms # associated with the specified usage are automatically activated and # deactivated as appropriate, but this can be switched at run-time by # changing the value of the instantiated usage term. # # Each term is instantiated with path, name, choices, default_unit and # default_per_unit attributes (where appropriate) corresponding to those # defined in the AMEE platform. # def terms_from_amee_dynamic_usage(ausage) all_drills usage{ value ausage} all_outputs end # Helper method for automatically instantiating Profile class term # objects representing all profile item values associated with the AMEE # platform category represented by self, and instantiating a new instance # of the Usage term class which can be used for dynamically switching # usages at run-time. # # Each term is instantiated with path, name, choices, default_unit and # default_per_unit attributes corresponding to those defined in the AMEE # platform. # # The newly instantiated Usage object can be configured in place # according to the ('DSL') block passed in, e.g., # # my_protptype.usage { # inactive :disabled # value nil # } # def usage(options={},&block) all_profiles construct(Usage,options.merge(:first=>true),&block) end # Helper method for automatically instantiating Metadatum class term # objects explicitly configured for storing start and end dates for an AMEE # platform profile item. # def start_and_end_dates metadatum { path 'start_date' name 'Start date' interface :date type :datetime validation lambda{|v| v.is_a?(Time) || v.is_a?(DateTime) || (v.is_a?(String) && Date.parse(v) rescue false)} } metadatum { path 'end_date' name 'End date' interface :date type :datetime validation lambda{|v| v.is_a?(Time) || v.is_a?(DateTime) || (v.is_a?(String) && Date.parse(v) rescue false)} } end # Helper method for reopening and modifying the definition of the term with # the label attribute matching label. Modification is specified in # the passed block, which is evaluated in the context of the respective term # instance. # # This is typically used to override (customize) the attributes and behaviour # of term autoloaded from the AMEE platform using one of the instance helper # methods of self. # def correcting(label,&block) return unless contents[label] contents[label].instance_eval(&block) end #Instantiate an OngoingCalculation based on this prototype, ready for #communication with AMEE. def begin_calculation result=OngoingCalculation.new contents.each do |k,v| result.contents[k]=v.clone result.contents[k].parent=result end result.path path result.name name result.label label result.fixed_usage fixed_usage result.save_amee saved_amee result end private def construct_from_ivd(klass,ivd) # Initialize boolean values as ruby boolean objects if (ivd.valuetype == "BOOLEAN") && (ivd.default == ("true"||"TRUE")) default_value = true elsif (ivd.valuetype == "BOOLEAN") && (ivd.default == ("false"||"FALSE")) default_value = false else default_value = ivd.default end construct(klass) { path ivd.path name ivd.name note ivd.meta.wikidoc type ivd.valuetype.downcase value default_value choices ivd.choices default_unit ivd.unit default_per_unit ivd.perunit } end # Construct a term of class klass, and evaluate a DSL block in its context. def construct(klass,options={},&block) new_content=klass.new(options.merge(:parent=>self),&block) raise Exceptions::DSL.new( "Attempt to create #{klass} without a label") unless new_content.label if options[:first] @contents.insert_at_start(new_content.label,new_content) else @contents[new_content.label]=new_content end end end end end