# 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