greenbutton 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 52012aa618542579f16c93ee0ad2c7a14509423a
4
+ data.tar.gz: 5c66932af049ff6ed7bb5b01bf8c9c149bce6065
5
+ SHA512:
6
+ metadata.gz: 1ee88c5b6cf336011e92f7a175c2a7ee0903eb96076c0c098030d919bf7b8a5ccab9e43af69eb34f2307b0442644cfdffae1f0f5b4467e18bf6177cd00ac6c25
7
+ data.tar.gz: 230b9e587e05057f66689d57d1d9e3362dc1d82ba1afcd472fea5ea38e151b920b61bb64cae05cfcd65707adb1ac5506c987333b60329e996f4db8a492afbdda
@@ -0,0 +1,5 @@
1
+ notes.txt
2
+ test.rb
3
+ .project
4
+ .DS_Store
5
+ .bundle/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1 @@
1
+ greenbuttonparser
@@ -0,0 +1 @@
1
+ ruby-2.1.0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ greenbutton (0.0.1)
5
+ nokogiri (~> 1.6)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.2.5)
11
+ mini_portile (0.5.2)
12
+ nokogiri (1.6.1)
13
+ mini_portile (~> 0.5.0)
14
+ rspec (2.14.1)
15
+ rspec-core (~> 2.14.0)
16
+ rspec-expectations (~> 2.14.0)
17
+ rspec-mocks (~> 2.14.0)
18
+ rspec-core (2.14.7)
19
+ rspec-expectations (2.14.4)
20
+ diff-lcs (>= 1.1.3, < 2.0)
21
+ rspec-mocks (2.14.4)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ bundler (~> 1.5)
28
+ greenbutton!
29
+ rspec (~> 2.14)
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,10 @@
1
+
2
+ ## Sample Green Button Parser Implemented in Ruby ##
3
+
4
+ [![Code Climate](https://codeclimate.com/github/cew821/greenbutton.png)](https://codeclimate.com/github/cew821/greenbutton)
5
+
6
+ This is a sample implementation of a parser for [Green Button Data](http://services.greenbuttondata.org/), written in Ruby.
7
+
8
+ This parser parsers Green Button XML into ruby objects.
9
+
10
+ This software is free, and is released as open source under the MIT license. See license.txt for complete details.
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'greenbutton'
3
+ s.version = '0.0.1'
4
+ s.date = '2014-03-17'
5
+ s.summary = "Ruby parser for the GreenButton data standard."
6
+ s.description = "This parser programmatically creates a Ruby object from a GreenButton data file. See https://collaborate.nist.gov/twiki-sggrid/bin/view/SmartGrid/GreenButtonSDK for more information on GreenButton. It is under active development and participation is encouraged. It is not yet stable."
7
+ s.authors = ["Charles Worthington", "Eric Hulburd"]
8
+ s.email = ['c.e.worthington@gmail.com','eric@arbol.org']
9
+ s.files = `git ls-files`.split("\n")
10
+ s.homepage = 'https://github.com/cew821/greenbutton'
11
+ s.license = 'MIT'
12
+
13
+ s.add_dependency "nokogiri", "~>1.6"
14
+ s.add_development_dependency "bundler", "~>1.5"
15
+ s.add_development_dependency "rspec", "~>2.14"
16
+
17
+ s.test_files = Dir.glob('spec/*_spec.rb')
18
+ end
@@ -0,0 +1,60 @@
1
+ module GreenButton
2
+ require 'nokogiri'
3
+ require 'greenbutton/gb_classes.rb'
4
+
5
+ UsagePoint = GreenButtonClasses::UsagePoint
6
+
7
+ # could also load this from the data custodian:feed
8
+ # url = "http://services.greenbuttondata.org:80/DataCustodian/espi/1_1/resource/RetailCustomer/1/DownloadMyData"
9
+
10
+ def self.load_xml_from_web(url)
11
+ xml_file = Nokogiri.XML(open(url))
12
+ xml_file.remove_namespaces!
13
+ Parser.new(xml_file)
14
+ end
15
+
16
+ def self.load_xml_from_file(path)
17
+ xml_file = Nokogiri.XML(File.open(path, 'rb'))
18
+ xml_file.remove_namespaces!
19
+ Parser.new(xml_file)
20
+ end
21
+
22
+
23
+ class Parser
24
+ attr_accessor :doc, :usage_points
25
+
26
+ def initialize(doc)
27
+ @doc = doc
28
+ @usage_points = []
29
+ doc.xpath('//UsagePoint').each do |usage_point|
30
+ @usage_points << UsagePoint.new(usage_point.parent.parent, self)
31
+ end
32
+ end
33
+
34
+ def filter_usage_points(params)
35
+ # customer_id, service_kind, title, id, href
36
+ filtered = []
37
+ @usage_points.each do |usage_point|
38
+ params.each_pair do |key, value|
39
+ filtered << usage_point if usage_point.send(key) == value
40
+ end
41
+ end
42
+ filtered
43
+ end
44
+
45
+ def get_unique(attr)
46
+ #customer_id, service_kind, title
47
+ unique = []
48
+ @usage_points.each do |usage_point|
49
+ val = usage_point.send(attr)
50
+ if !unique.include?(val)
51
+ unique << val
52
+ end
53
+ end
54
+ unique
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+
@@ -0,0 +1,361 @@
1
+ module GreenButtonClasses
2
+ require 'greenbutton/helpers.rb'
3
+ require 'nokogiri'
4
+
5
+ Rule = Helper::Rule
6
+ RULES = [
7
+ Rule.new(:href, "./link[@rel='self']/@href", :string),
8
+ Rule.new(:parent_href, "./link[@rel='up']/@href", :string),
9
+ Rule.new(:id, "./id", :string),
10
+ Rule.new(:title, "./title", :string),
11
+ Rule.new(:date_published, "./published", :datetime),
12
+ Rule.new(:date_updated, "./updated", :datetime)
13
+ ]
14
+
15
+
16
+ class GreenButtonEntry
17
+ attr_accessor :id, :title, :href, :published, :updated, :parent_href, :related_hrefs, :other_related
18
+
19
+ def initialize(entry_xml, parent)
20
+ if !entry_xml.nil?
21
+ @entry_xml = entry_xml
22
+ self.related_hrefs = []
23
+ self.other_related = []
24
+ pre_rule_assignment(parent)
25
+ assign_rules
26
+ find_related_entries
27
+ end
28
+ end
29
+
30
+ def pre_rule_assignment(parent)
31
+ raise self.class + 'failed to implement pre_rule_assignment'
32
+ end
33
+
34
+ def additional_rules
35
+ []
36
+ end
37
+
38
+ def doc
39
+ self.usage_point.doc
40
+ end
41
+
42
+ def find_by_href(href)
43
+ doc.xpath("//link[@rel='self' and @href='#{href}']/..")[0]
44
+ end
45
+
46
+ def assign_rules
47
+ (RULES + additional_rules).each do |rule|
48
+ create_attr(rule.attr_name)
49
+ rule_xml = @entry_xml.xpath(rule.xpath)
50
+ value = rule_xml.empty? ? nil : rule_xml.text
51
+ translated_value = value.nil? ? nil : Helper.translate(rule.type, value)
52
+ self.send(rule.attr_name.to_s+"=", translated_value)
53
+ end
54
+ end
55
+
56
+ def find_related_entries
57
+ self.related_hrefs = []
58
+ @entry_xml.xpath("./link[@rel='related']/@href").each do |href|
59
+ if /\/\d+$/i.match(href.text)
60
+ related_entry = find_by_href(href.text)
61
+ if related_entry
62
+ parse_related_entry(related_entry)
63
+ self.related_hrefs << href.text
64
+ else
65
+ warn 'no link found for href: ' + href.text
66
+ end
67
+ else
68
+ doc.xpath("//link[@rel='up' and @href='#{href.text}']").each do |link|
69
+ self.related_hrefs << link.attr('href')
70
+ parse_related_entry(link.parent)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def parse_related_entry(entry_xml)
77
+ name = get_related_name(entry_xml)
78
+ classParser = GreenButtonClasses.const_get(name)
79
+ if !classParser.nil?
80
+ self.add_related(Helper.underscore(name), classParser.new(entry_xml, self))
81
+ else
82
+ other_related.push(xml)
83
+ end
84
+ end
85
+
86
+ def add_related(type, parser)
87
+ raise self.class + ' does not have any recognized relations.'
88
+ end
89
+
90
+ private
91
+
92
+ def get_related_name(xml)
93
+ name = nil
94
+ xml.xpath('./content').children.each do |elem|
95
+ if elem.name != 'text'
96
+ name = elem.name
97
+ break
98
+ end
99
+ end
100
+ name
101
+ end
102
+
103
+ def alt_link(href)
104
+ # SDGE links map as .../MeterReading to .../MeterReading/\d+
105
+ regex = Regexp.new(href + '\/\d+$')
106
+ related_link = doc.xpath("//link[@rel='self']").select do |e|
107
+ if e['href'] =~ regex
108
+ e.parent
109
+ end
110
+ end
111
+ related_link[0]
112
+ end
113
+
114
+ def create_method( name, &block )
115
+ self.class.send( :define_method, name, &block )
116
+ end
117
+
118
+ def create_attr( name )
119
+ create_method( "#{name.to_s}=".to_sym ) { |val|
120
+ instance_variable_set( "@" + name.to_s, val)
121
+ }
122
+ create_method( name.to_sym ) {
123
+ instance_variable_get( "@" + name.to_s )
124
+ }
125
+ end
126
+
127
+ end
128
+
129
+ class UsagePoint < GreenButtonEntry
130
+ attr_accessor :local_time_parameters, :meter_readings, :electric_power_usage_summaries,
131
+ :electric_power_quality_summaries, :green_button
132
+
133
+ def pre_rule_assignment(parent)
134
+ self.green_button = parent
135
+ self.meter_readings = []
136
+ self.electric_power_quality_summaries = []
137
+ self.electric_power_usage_summaries = []
138
+ end
139
+
140
+ def doc
141
+ self.green_button.doc
142
+ end
143
+
144
+ def add_related(type, parser)
145
+ case type
146
+ when 'local_time_parameters'
147
+ self.local_time_parameters = parser
148
+ when 'meter_reading', 'electric_power_usage_summary', 'electric_power_quality_summary'
149
+ self.send(Helper.pluralize(type)) << parser
150
+ else
151
+ raise 'Not a recognized relation for UsagePoint: ' + type
152
+ end
153
+ end
154
+
155
+ def additional_rules
156
+ [ Rule.new(:service_kind, "//ServiceCategory/kind", :ServiceKind) ]
157
+ end
158
+
159
+ def customer_id
160
+ if @customer_id.nil?
161
+ match = /\/([^\/]+)\/UsagePoint/i.match(self.href)
162
+ @customer_id = match.nil? ? nil : match[1]
163
+ end
164
+ @customer_id
165
+ end
166
+
167
+ end
168
+
169
+ class MeterReading < GreenButtonEntry
170
+ attr_accessor :reading_type, :interval_blocks, :usage_point
171
+
172
+ def pre_rule_assignment(parent)
173
+ self.usage_point = parent
174
+ self.interval_blocks = []
175
+ end
176
+
177
+ def add_related(type, parser)
178
+ case type
179
+ when 'reading_type'
180
+ self.reading_type = parser
181
+ when 'interval_block'
182
+ self.interval_blocks << parser
183
+ else
184
+ raise 'Not a recognized relation for MeterReading'
185
+ end
186
+ end
187
+ end
188
+
189
+ class ReadingType < GreenButtonEntry
190
+ attr_accessor :meter_reading
191
+ ATTRS = ['accumulationBehaviour', 'commodity', 'currency', 'dataQualifier', 'flowDirection', 'intervalLength',
192
+ 'kind', 'phase', 'powerOfTenMultiplier', 'timeAttribute', 'uom']
193
+
194
+ def pre_rule_assignment(parent)
195
+ self.meter_reading = parent
196
+ end
197
+
198
+ def doc
199
+ self.meter_reading.doc
200
+ end
201
+
202
+ def additional_rules
203
+ rules = []
204
+ ATTRS.each do |attr|
205
+ rules << Rule.new( Helper.underscore(attr).to_sym, './/'+attr, :integer )
206
+ end
207
+ rules
208
+ end
209
+ end
210
+
211
+ class ElectricPowerUsageSummary < GreenButtonEntry
212
+ attr_accessor :usage_point
213
+ ATTRS = ['billLastPeriod', 'billToDate', 'costAdditionalLastPeriod', 'currency',
214
+ 'qualityOfReading', 'statusTimeStamp']
215
+
216
+ def pre_rule_assignment(parent)
217
+ self.usage_point = parent
218
+ end
219
+
220
+ def additional_rules
221
+ rules = [
222
+ Rule.new(:bill_duration, ".//duration", :integer),
223
+ Rule.new(:bill_start, ".//start", :unix_time),
224
+ Rule.new(:last_power_ten, ".//overallConsumptionLastPeriod/powerOfTenMultiplier", :integer),
225
+ Rule.new(:last_uom, ".//overallConsumptionLastPeriod/uom", :integer),
226
+ Rule.new(:last_value, ".//overallConsumptionLastPeriod/value", :integer),
227
+ Rule.new(:current_power_ten, ".//currentBillingPeriodOverAllConsumption/powerOfTenMultiplier", :integer),
228
+ Rule.new(:current_uom, ".//currentBillingPeriodOverAllConsumption/uom", :integer),
229
+ Rule.new(:current_value, ".//currentBillingPeriodOverAllConsumption/value", :integer),
230
+ Rule.new(:current_timestamp, ".//currentBillingPeriodOverAllConsumption/timeStamp", :unix_time)
231
+ ]
232
+ ATTRS.each do |attr|
233
+ rules << Rule.new( Helper.underscore(attr).to_sym, '//'+attr, :integer )
234
+ end
235
+ rules
236
+ end
237
+ end
238
+
239
+ class ElectricPowerQualitySummary < GreenButtonEntry
240
+ attr_accessor :usage_point
241
+ def pre_rule_assignment(parent)
242
+ self.usage_point = parent
243
+ end
244
+ end
245
+
246
+ class LocalTimeParameters < GreenButtonEntry
247
+ attr_accessor :usage_point
248
+
249
+ def pre_rule_assignment(parent)
250
+ self.usage_point = parent
251
+ end
252
+
253
+ def additional_rules
254
+ [
255
+ Rule.new(:dst_end_rule, ".//dstEndRule", :string),
256
+ Rule.new(:dst_offset, ".//dstOffset", :integer),
257
+ Rule.new(:dst_start_rule, ".//dstStartRule", :string),
258
+ Rule.new(:tz_offset, ".//tzOffset", :integer)
259
+ ]
260
+ end
261
+ end
262
+
263
+ class IntervalBlock < GreenButtonEntry
264
+ attr_accessor :meter_reading
265
+
266
+ def pre_rule_assignment(parent)
267
+ self.meter_reading = parent
268
+ end
269
+
270
+ def additional_rules
271
+ [
272
+ Rule.new(:start_time, './/interval/start', :unix_time),
273
+ Rule.new(:duration, './/interval/duration', :integer)
274
+ ]
275
+ end
276
+
277
+ def doc
278
+ self.meter_reading.doc
279
+ end
280
+
281
+ def end_time
282
+ self.start_time + self.duration
283
+ end
284
+
285
+ def power_of_ten_multiplier
286
+ self.meter_reading.reading_type.power_of_ten_multiplier
287
+ end
288
+
289
+ def reading_at_time(time)
290
+ if (time >= self.start_time) && (time < end_time)
291
+ @entry_xml.xpath('.//IntervalReading').each do |interval_reading|
292
+ intervalReading = IntervalReading.new(interval_reading)
293
+ if (intervalReading.start_time <= time) && (intervalReading.end_time > time)
294
+ return intervalReading
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ def value_at_time(time)
301
+ reading_at_time(time).value*10**power_of_ten_multiplier
302
+ end
303
+
304
+ def total
305
+ if @total.nil?
306
+ @total = sum
307
+ end
308
+ @total
309
+ end
310
+
311
+ def average_interval_value
312
+ total/n_readings
313
+ end
314
+
315
+ def n_readings
316
+ @entry_xml.xpath('.//IntervalReading').length
317
+ end
318
+
319
+ def sum(starttime=nil, endtime=nil)
320
+ starttime = starttime.nil? ? self.start_time : starttime.utc
321
+ endtime = endtime.nil? ? self.end_time : endtime.utc
322
+ sum = 0
323
+ @entry_xml.xpath('.//IntervalReading').each do |interval_reading|
324
+ intervalReading = IntervalReading.new(interval_reading)
325
+ if intervalReading.start_time >= starttime && intervalReading.start_time < endtime
326
+ if intervalReading.end_time <= endtime
327
+ sum += intervalReading.value
328
+ else
329
+ ratio = (intervalReading.end_time.to_i - endtime.to_i)/intervalReading.duration
330
+ sum += ratio*intervalReading.value
331
+ break
332
+ end
333
+ end
334
+ end
335
+ sum*10**power_of_ten_multiplier
336
+ end
337
+ end
338
+
339
+ class IntervalReading
340
+ def initialize(reading_xml)
341
+ @reading_xml = reading_xml
342
+ end
343
+
344
+ def value
345
+ @reading_xml.xpath('./value').text.to_f
346
+ end
347
+
348
+ def start_time
349
+ Time.at(@reading_xml.xpath('./timePeriod/start').text.to_i).utc
350
+ end
351
+
352
+ def duration
353
+ @reading_xml.xpath('./timePeriod/duration').text.to_i
354
+ end
355
+
356
+ def end_time
357
+ start_time + duration
358
+ end
359
+ end
360
+
361
+ end