green-button-data 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/Gemfile +11 -0
- data/Guardfile +50 -0
- data/LICENSE.txt +26 -0
- data/README.md +72 -0
- data/Rakefile +5 -0
- data/green-button-data.gemspec +24 -0
- data/lib/green-button-data/core_ext/date.rb +9 -0
- data/lib/green-button-data/core_ext/fixnum.rb +10 -0
- data/lib/green-button-data/core_ext.rb +2 -0
- data/lib/green-button-data/dst.rb +49 -0
- data/lib/green-button-data/enumerations.rb +523 -0
- data/lib/green-button-data/feed.rb +7 -0
- data/lib/green-button-data/parser/application_information.rb +180 -0
- data/lib/green-button-data/parser/authorization.rb +45 -0
- data/lib/green-button-data/parser/content.rb +36 -0
- data/lib/green-button-data/parser/entry.rb +47 -0
- data/lib/green-button-data/parser/feed.rb +47 -0
- data/lib/green-button-data/parser/interval.rb +32 -0
- data/lib/green-button-data/parser/interval_block.rb +32 -0
- data/lib/green-button-data/parser/interval_reading.rb +30 -0
- data/lib/green-button-data/parser/local_time_parameters.rb +105 -0
- data/lib/green-button-data/parser/rational_number.rb +22 -0
- data/lib/green-button-data/parser/reading_type.rb +134 -0
- data/lib/green-button-data/parser/service_category.rb +17 -0
- data/lib/green-button-data/parser/usage_point.rb +20 -0
- data/lib/green-button-data/parser.rb +2 -0
- data/lib/green-button-data/utilities.rb +106 -0
- data/lib/green-button-data/version.rb +3 -0
- data/lib/green-button-data.rb +24 -0
- data/spec/fixtures/ESPIApplicationInformation.xml +49 -0
- data/spec/fixtures/ESPIAuthorization.xml +25 -0
- data/spec/fixtures/ESPIIntervalBlock.xml +3814 -0
- data/spec/fixtures/ESPILocalTimeParameters.xml +16 -0
- data/spec/fixtures/ESPIReadingType.xml +535 -0
- data/spec/fixtures/ESPIUsagePoint.xml +18 -0
- data/spec/fixtures.rb +22 -0
- data/spec/green-button-data/core_ext/date_spec.rb +18 -0
- data/spec/green-button-data/core_ext/fixnum_spec.rb +13 -0
- data/spec/green-button-data/parser/application_information_spec.rb +154 -0
- data/spec/green-button-data/parser/authorization_spec.rb +52 -0
- data/spec/green-button-data/parser/interval_block_spec.rb +39 -0
- data/spec/green-button-data/parser/local_time_parameter_spec.rb +56 -0
- data/spec/green-button-data/parser/reading_type_spec.rb +76 -0
- data/spec/green-button-data/parser/usage_point_spec.rb +22 -0
- data/spec/green-button-data/utilities_spec.rb +63 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/custom_expectations/warn_expectation.rb +31 -0
- metadata +148 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class Authorization
|
4
|
+
include SAXMachine
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
element :authorizedPeriod, class: Interval, as: :authorized_period
|
8
|
+
element :publishedPeriod, class: Interval, as: :published_period
|
9
|
+
|
10
|
+
element :expires_at, class: Integer do |epoch|
|
11
|
+
Time.at(normalize_epoch(epoch)).utc.to_datetime
|
12
|
+
end
|
13
|
+
|
14
|
+
element :status, class: Integer
|
15
|
+
|
16
|
+
def active?
|
17
|
+
@status > 0
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO: Add scope parser
|
21
|
+
element :scope
|
22
|
+
|
23
|
+
element :resourceURI, as: :resource_uri
|
24
|
+
element :authorizationURI, as: :authorization_uri
|
25
|
+
|
26
|
+
# ESPI Namespacing
|
27
|
+
element :'espi:authorizedPeriod', class: Interval, as: :authorized_period
|
28
|
+
element :'espi:publishedPeriod', class: Interval, as: :published_period
|
29
|
+
element :'espi:expires_at', class: Integer, as: :expires_at
|
30
|
+
element :'espi:status', class: Integer, as: :status
|
31
|
+
element :'espi:scope', as: :scope
|
32
|
+
element :'espi:resourceURI', as: :resource_uri
|
33
|
+
element :'espi:authorizationURI', as: :authorization_uri
|
34
|
+
|
35
|
+
# Special case for PG&E which uses generic namespacing
|
36
|
+
element :'ns0:authorizedPeriod', class: Interval, as: :authorized_period
|
37
|
+
element :'ns0:publishedPeriod', class: Interval, as: :published_period
|
38
|
+
element :'ns0:expires_at', class: Integer, as: :expires_at
|
39
|
+
element :'ns0:status', class: Integer, as: :status
|
40
|
+
element :'ns0:scope', as: :scope
|
41
|
+
element :'ns0:resourceURI', as: :resource_uri
|
42
|
+
element :'ns0:authorizationURI', as: :authorization_uri
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class Content
|
4
|
+
include SAXMachine
|
5
|
+
|
6
|
+
element :ApplicationInformation, class: ApplicationInformation,
|
7
|
+
as: :application_information
|
8
|
+
element :Authorization, class: Authorization, as: :authorization
|
9
|
+
element :IntervalBlock, class: IntervalBlock, as: :interval_block
|
10
|
+
element :LocalTimeParameters, class: LocalTimeParameters,
|
11
|
+
as: :local_time_parameters
|
12
|
+
element :ReadingType, class: ReadingType, as: :reading_type
|
13
|
+
element :UsagePoint, class: UsagePoint, as: :usage_point
|
14
|
+
|
15
|
+
# ESPI Namespacing
|
16
|
+
element :'espi:ApplicationInformation', class: ApplicationInformation,
|
17
|
+
as: :application_information
|
18
|
+
element :'espi:Authorization', class: Authorization, as: :authorization
|
19
|
+
element :'espi:IntervalBlock', class: IntervalBlock, as: :interval_block
|
20
|
+
element :'espi:LocalTimeParameters', class: LocalTimeParameters,
|
21
|
+
as: :local_time_parameters
|
22
|
+
element :'espi:ReadingType', class: ReadingType, as: :reading_type
|
23
|
+
element :'espi:UsagePoint', class: UsagePoint, as: :usage_point
|
24
|
+
|
25
|
+
# Special case for PG&E generic namespaces
|
26
|
+
element :'ns0:ApplicationInformation', class: ApplicationInformation,
|
27
|
+
as: :application_information
|
28
|
+
element :'ns0:Authorization', class: Authorization, as: :authorization
|
29
|
+
element :'ns0:IntervalBlock', class: IntervalBlock, as: :interval_block
|
30
|
+
element :'ns0:LocalTimeParameters', class: LocalTimeParameters,
|
31
|
+
as: :local_time_parameters
|
32
|
+
element :'ns0:ReadingType', class: ReadingType, as: :reading_type
|
33
|
+
element :'ns0:UsagePoint', class: UsagePoint, as: :usage_point
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class Entry
|
4
|
+
include SAXMachine
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
element :id, as: :entry_id
|
8
|
+
|
9
|
+
def id
|
10
|
+
@entry_id ||= @self
|
11
|
+
end
|
12
|
+
|
13
|
+
element :link, as: :up, value: :href, with: { rel: 'up' }
|
14
|
+
element :link, as: :self, value: :href, with: { rel: 'self' }
|
15
|
+
element :link, as: :related, value: :href, with: { rel: 'related' }
|
16
|
+
|
17
|
+
element :content, class: Content, as: :content
|
18
|
+
|
19
|
+
# Published Date
|
20
|
+
element :published
|
21
|
+
|
22
|
+
def published
|
23
|
+
@published ||= @updated
|
24
|
+
end
|
25
|
+
|
26
|
+
def published=(val)
|
27
|
+
@published = parse_datetime val
|
28
|
+
end
|
29
|
+
|
30
|
+
# Updated Date
|
31
|
+
element :updated
|
32
|
+
|
33
|
+
def updated=(val)
|
34
|
+
@updated = parse_datetime val
|
35
|
+
end
|
36
|
+
|
37
|
+
# Handle PG&E namespacing
|
38
|
+
element :'ns1:id', as: :entry_id
|
39
|
+
element :'ns1:link', as: :up, value: :href, with: { rel: 'up' }
|
40
|
+
element :'ns1:link', as: :self, value: :href, with: { rel: 'self' }
|
41
|
+
element :'ns1:link', as: :related, value: :href, with: { rel: 'related' }
|
42
|
+
element :'ns1:content', class: Content, as: :content
|
43
|
+
element :'ns1:published', as: :published
|
44
|
+
element :'ns1:updated', as: :updated
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class Feed
|
4
|
+
include SAXMachine
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
element :id, as: :feed_id
|
8
|
+
|
9
|
+
def id
|
10
|
+
@feed_id ||= @feed_url
|
11
|
+
end
|
12
|
+
|
13
|
+
element :title
|
14
|
+
element :subtitle, as: :description
|
15
|
+
|
16
|
+
element :link, as: :url, value: :href, with: { type: 'text/html' }
|
17
|
+
element :link, as: :feed_url, value: :href, with: { rel: 'self' }
|
18
|
+
element :link, as: :links, value: :href
|
19
|
+
|
20
|
+
def url
|
21
|
+
@url || (links - [feed_url]).last || links.last
|
22
|
+
end
|
23
|
+
|
24
|
+
def feed_url
|
25
|
+
@feed_url ||= links.first
|
26
|
+
end
|
27
|
+
|
28
|
+
elements :entry, class: Entry, as: :entries
|
29
|
+
|
30
|
+
element :updated
|
31
|
+
|
32
|
+
def updated=(val)
|
33
|
+
@updated = parse_datetime val
|
34
|
+
end
|
35
|
+
|
36
|
+
# PG&E's generic namespace
|
37
|
+
element :'ns1:id', as: :feed_id
|
38
|
+
element :'ns1:title', as: :title
|
39
|
+
element :'ns1:subtitle', as: :description
|
40
|
+
element :'ns1:link', as: :url, value: :href, with: { type: 'text/html' }
|
41
|
+
element :'ns1:link', as: :feed_url, value: :href, with: { rel: 'self' }
|
42
|
+
element :'ns1:links', as: :links, value: :href
|
43
|
+
element :'ns1:entry', class: Entry, as: :entries
|
44
|
+
element :'ns1:updated', as: :updated
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class Interval
|
4
|
+
include SAXMachine
|
5
|
+
include Utilities
|
6
|
+
|
7
|
+
element :duration, class: Integer do |t|
|
8
|
+
normalize_epoch t
|
9
|
+
end
|
10
|
+
|
11
|
+
element :start, class: Integer do |t|
|
12
|
+
normalize_epoch t
|
13
|
+
end
|
14
|
+
|
15
|
+
def starts_at
|
16
|
+
Time.at(normalize_epoch(@start)).utc.to_datetime
|
17
|
+
end
|
18
|
+
|
19
|
+
def ends_at
|
20
|
+
Time.at(normalize_epoch(@start + @duration)).utc.to_datetime
|
21
|
+
end
|
22
|
+
|
23
|
+
# Standard ESPI namespacing
|
24
|
+
element :'espi:duration', class: Integer, as: :duration
|
25
|
+
element :'espi:start', class: Integer, as: :start
|
26
|
+
|
27
|
+
# Special case for PG&E which uses generic namespacing
|
28
|
+
element :'ns0:duration', class: Integer, as: :duration
|
29
|
+
element :'ns0:start', class: Integer, as: :start
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class IntervalBlock
|
4
|
+
include SAXMachine
|
5
|
+
|
6
|
+
element :interval, class: Interval
|
7
|
+
elements :IntervalReading, class: IntervalReading, as: :interval_readings
|
8
|
+
|
9
|
+
def duration
|
10
|
+
@interval.duration
|
11
|
+
end
|
12
|
+
|
13
|
+
def starts_at
|
14
|
+
@interval.starts_at
|
15
|
+
end
|
16
|
+
|
17
|
+
def ends_at
|
18
|
+
@interval.ends_at
|
19
|
+
end
|
20
|
+
|
21
|
+
# Standard ESPI namespacing
|
22
|
+
element :'espi:interval', class: Interval, as: :interval
|
23
|
+
elements :'espi:IntervalReading', class: IntervalReading,
|
24
|
+
as: :interval_readings
|
25
|
+
|
26
|
+
# Special case for PG&E which uses generic namespacing
|
27
|
+
element :'ns0:interval', class: Interval, as: :interval
|
28
|
+
elements :'ns0:IntervalReading', class: IntervalReading,
|
29
|
+
as: :interval_readings
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class IntervalReading
|
4
|
+
include SAXMachine
|
5
|
+
|
6
|
+
element :cost, class: Integer
|
7
|
+
element :timePeriod, class: Interval, as: :time_period
|
8
|
+
element :value, class: Integer
|
9
|
+
element :consumptionTier, class: Integer, as: :consumption_tier
|
10
|
+
element :tou, class: Integer
|
11
|
+
element :cpp, class: Integer
|
12
|
+
|
13
|
+
# Standard ESPI namespacing
|
14
|
+
element :'espi:cost', class: Integer, as: :cost
|
15
|
+
element :'espi:timePeriod', class: Interval, as: :time_period
|
16
|
+
element :'espi:value', class: Integer, as: :value
|
17
|
+
element :'espi:consumptionTier', class: Integer, as: :consumption_tier
|
18
|
+
element :'espi:tou', class: Integer, as: :tou
|
19
|
+
element :'espi:cpp', class: Integer, as: :cpp
|
20
|
+
|
21
|
+
# Special case for PG&E which uses generic namespacing
|
22
|
+
element :'ns0:cost', class: Integer, as: :cost
|
23
|
+
element :'ns0:timePeriod', class: Interval, as: :time_period
|
24
|
+
element :'ns0:value', class: Integer, as: :value
|
25
|
+
element :'ns0:consumptionTier', class: Integer, as: :consumption_tier
|
26
|
+
element :'ns0:tou', class: Integer, as: :tou
|
27
|
+
element :'ns0:cpp', class: Integer, as: :cpp
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class LocalTimeParameters
|
4
|
+
include SAXMachine
|
5
|
+
include Dst
|
6
|
+
include Utilities
|
7
|
+
|
8
|
+
element :dstStartRule, as: :dst_start_rule do |string|
|
9
|
+
string.to_i 16
|
10
|
+
end
|
11
|
+
|
12
|
+
element :dstEndRule, as: :dst_end_rule do |string|
|
13
|
+
string.to_i 16
|
14
|
+
end
|
15
|
+
|
16
|
+
def dst_starts_at(year = Time.now.year)
|
17
|
+
byte_to_dst_datetime @dst_start_rule, year
|
18
|
+
end
|
19
|
+
|
20
|
+
def dst_ends_at(year = Time.now.year)
|
21
|
+
byte_to_dst_datetime @dst_end_rule, year
|
22
|
+
end
|
23
|
+
|
24
|
+
element :dstOffset, class: Integer, as: :dst_offset
|
25
|
+
element :tzOffset, class: Integer, as: :tz_offset
|
26
|
+
|
27
|
+
def total_offset
|
28
|
+
@dst_offset + @tz_offset
|
29
|
+
end
|
30
|
+
|
31
|
+
def total_offset_hours
|
32
|
+
total_offset / 3600
|
33
|
+
end
|
34
|
+
|
35
|
+
# ESPI Namespacing
|
36
|
+
element :'espi:dstStartRule', as: :dst_start_rule
|
37
|
+
element :'espi:dstEndRule', as: :dst_end_rule
|
38
|
+
element :'espi:dstOffset', class: Integer, as: :dst_offset
|
39
|
+
element :'espi:tzOffset', class: Integer, as: :tz_offset
|
40
|
+
|
41
|
+
# Special case for PG&E which uses generic namespacing
|
42
|
+
element :'ns0:dstStartRule', as: :dst_start_rule
|
43
|
+
element :'ns0:dstEndRule', as: :dst_end_rule
|
44
|
+
element :'ns0:dstOffset', class: Integer, as: :dst_offset
|
45
|
+
element :'ns0:tzOffset', class: Integer, as: :tz_offset
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def byte_to_dst_datetime(byte, year = Time.now.year)
|
50
|
+
# Bits 0 - 11: seconds 0 - 3599
|
51
|
+
seconds = byte & BITMASK_SECOND
|
52
|
+
|
53
|
+
# Bits 12 - 16: hours 0 - 23
|
54
|
+
hour = (byte & BITMASK_HOUR) >> BITSHIFT_HOUR
|
55
|
+
|
56
|
+
# Bits 17 - 19: day of the week; 0 = NA, 1 - 7 (Monday = 1)
|
57
|
+
weekday = (byte & BITMASK_DAY_OF_WEEK) >> BITSHIFT_DAY_OF_WEEK
|
58
|
+
|
59
|
+
# Bits 20 - 24: day of the month; 0 = NA, 1 - 31
|
60
|
+
day = (byte & BITMASK_DAY_OF_MONTH) >> BITSHIFT_DAY_OF_MONTH
|
61
|
+
|
62
|
+
# Bits 25 - 27: DST rule 0 - 7
|
63
|
+
dst_rule = (byte & BITMASK_DST_RULE) >> BITSHIFT_DST_RULE
|
64
|
+
|
65
|
+
# Bits 28 - 31: month 1 - 12
|
66
|
+
month = (byte & BITMASK_MONTH) >> BITSHIFT_MONTH
|
67
|
+
|
68
|
+
# Raise an error unless all the values are in valid range
|
69
|
+
seconds.between?(0, 3599) and hour.between?(0, 23) and
|
70
|
+
weekday.between?(1, 7) and day.between?(0, 31) and
|
71
|
+
dst_rule.between?(0, 7) and month.between?(1, 12) or
|
72
|
+
raise RangeError, 'Invalid value range'
|
73
|
+
|
74
|
+
# In Ruby, Sunday = 0 not 7
|
75
|
+
weekday = weekday == 7 ? 0 : weekday
|
76
|
+
|
77
|
+
# Check the DST rule
|
78
|
+
dst_day = if dst_rule == 1
|
79
|
+
# Rule 1: DST starts/ends on Day of Week on or after the Day of Month
|
80
|
+
day_of_month = DateTime.new year, month, day
|
81
|
+
day_offset = if weekday >= day_of_month.wday
|
82
|
+
weekday - day_of_month.wday
|
83
|
+
else
|
84
|
+
7 + weekday - day_of_month.wday
|
85
|
+
end
|
86
|
+
|
87
|
+
day_of_month + day_offset
|
88
|
+
elsif dst_rule.between?(2, 6)
|
89
|
+
# Rule 2 - 6: DST starts/ends on Nth Day of Week in given month
|
90
|
+
# Nth Day of Week (e.g. third Friday of July)
|
91
|
+
nth_weekday_of year, month, weekday, dst_rule - 1
|
92
|
+
elsif dst_rule == 7
|
93
|
+
# Rule 7: DST starts/ends on last Day of Week in given month
|
94
|
+
last_weekday_of year, month, weekday
|
95
|
+
else
|
96
|
+
# Rule 0: DST starts/ends on the Day of Month
|
97
|
+
DateTime.new year, month, day
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add the hour and seconds component to the day
|
101
|
+
dst_day + Rational(hour * 3600 + seconds, 86400)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class RationalNumber
|
4
|
+
include SAXMachine
|
5
|
+
|
6
|
+
element :numerator, class: Integer
|
7
|
+
element :denominator, class: Integer
|
8
|
+
|
9
|
+
def to_f
|
10
|
+
@numerator / @denominator
|
11
|
+
end
|
12
|
+
|
13
|
+
# ESPI Namespacing
|
14
|
+
element :'espi:numerator', class: Integer, as: :numerator
|
15
|
+
element :'espi:denominator', class: Integer, as: :denominator
|
16
|
+
|
17
|
+
# Special case for PG&E which uses generic namespacing
|
18
|
+
element :'ns0:numerator', class: Integer, as: :numerator
|
19
|
+
element :'ns0:denominator', class: Integer, as: :denominator
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class ReadingType
|
4
|
+
include SAXMachine
|
5
|
+
include Enumerations
|
6
|
+
|
7
|
+
element :accumulationBehaviour, class: Integer,
|
8
|
+
as: :accumulation_behaviour
|
9
|
+
element :commodity, class: Integer
|
10
|
+
element :consumptionTier, class: Integer, as: :consumption_tier
|
11
|
+
element :currency, class: Integer
|
12
|
+
element :dataQualifier, class: Integer, as: :data_qualifier
|
13
|
+
element :defaultQuality, class: Integer, as: :default_quality
|
14
|
+
element :flowDirection, class: Integer, as: :flow_direction
|
15
|
+
element :intervalLength, class: Integer, as: :interval_length
|
16
|
+
element :kind, class: Integer
|
17
|
+
element :phase, class: Integer
|
18
|
+
element :powerOfTenMultiplier, class: Integer,
|
19
|
+
as: :power_of_ten_multiplier
|
20
|
+
element :timeAttribute, class: Integer, as: :time_attribute
|
21
|
+
element :tou, class: Integer
|
22
|
+
element :uom, class: Integer
|
23
|
+
element :cpp, class: Integer
|
24
|
+
element :interharmonic, class: RationalNumber
|
25
|
+
element :measuringPeriod, class: Integer, as: :measuring_period
|
26
|
+
element :argument, class: RationalNumber
|
27
|
+
|
28
|
+
def accumulation_behaviour
|
29
|
+
ACCUMULATION[@accumulation_behaviour]
|
30
|
+
end
|
31
|
+
|
32
|
+
def argument
|
33
|
+
if @argument && @argument.denominator != 0
|
34
|
+
@argument.numerator / @argument.denominator
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def commodity
|
39
|
+
COMMODITY[@commodity]
|
40
|
+
end
|
41
|
+
|
42
|
+
def currency
|
43
|
+
CURRENCY[@currency]
|
44
|
+
end
|
45
|
+
|
46
|
+
def data_qualifier
|
47
|
+
DATA_QUALIFIER[@data_qualifier]
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_quality
|
51
|
+
QUALITY_OF_READING[@default_quality]
|
52
|
+
end
|
53
|
+
|
54
|
+
def flow_direction
|
55
|
+
FLOW_DIRECTION[@flow_direction]
|
56
|
+
end
|
57
|
+
|
58
|
+
def interharmonic
|
59
|
+
# Prevent blackholes from forming
|
60
|
+
if @interharmonic && @interharmonic.denominator != 0
|
61
|
+
@interharmonic.numerator / @interharmonic.denominator
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def kind
|
66
|
+
MEASUREMENT[@kind]
|
67
|
+
end
|
68
|
+
|
69
|
+
def measuring_period
|
70
|
+
TIME_ATTRIBUTE[@measuring_period]
|
71
|
+
end
|
72
|
+
|
73
|
+
def phase
|
74
|
+
PHASE_CODE[@phase]
|
75
|
+
end
|
76
|
+
|
77
|
+
def power_of_ten_multiplier
|
78
|
+
10.0 ** @power_of_ten_multiplier
|
79
|
+
end
|
80
|
+
|
81
|
+
def time_attribute
|
82
|
+
TIME_PERIOD_OF_INTEREST[@time_attribute]
|
83
|
+
end
|
84
|
+
|
85
|
+
def uom
|
86
|
+
UNIT_SYMBOL[@uom]
|
87
|
+
end
|
88
|
+
|
89
|
+
# ESPI Namespacing
|
90
|
+
element :'espi:accumulationBehaviour', class: Integer,
|
91
|
+
as: :accumulation_behaviour
|
92
|
+
element :'espi:commodity', class: Integer, as: :commodity
|
93
|
+
element :'espi:consumptionTier', class: Integer, as: :consumption_tier
|
94
|
+
element :'espi:currency', class: Integer, as: :currency
|
95
|
+
element :'espi:dataQualifier', class: Integer, as: :data_qualifier
|
96
|
+
element :'espi:defaultQuality', class: Integer, as: :default_quality
|
97
|
+
element :'espi:flowDirection', class: Integer, as: :flow_direction
|
98
|
+
element :'espi:intervalLength', class: Integer, as: :interval_length
|
99
|
+
element :'espi:kind', class: Integer, as: :kind
|
100
|
+
element :'espi:phase', class: Integer, as: :phase
|
101
|
+
element :'espi:powerOfTenMultiplier', class: Integer,
|
102
|
+
as: :power_of_ten_multiplier
|
103
|
+
element :'espi:timeAttribute', class: Integer, as: :time_attribute
|
104
|
+
element :'espi:tou', class: Integer, as: :tou
|
105
|
+
element :'espi:uom', class: Integer, as: :uom
|
106
|
+
element :'espi:cpp', class: Integer, as: :cpp
|
107
|
+
element :'espi:interharmonic', class: RationalNumber, as: :interharmonic
|
108
|
+
element :'espi:measuringPeriod', class: Integer, as: :measuring_period
|
109
|
+
element :'espi:argument', class: RationalNumber, as: :argument
|
110
|
+
|
111
|
+
# Special case for PG&E which uses generic namespacing
|
112
|
+
element :'ns0:accumulationBehaviour', class: Integer,
|
113
|
+
as: :accumulation_behaviour
|
114
|
+
element :'ns0:commodity', class: Integer, as: :commodity
|
115
|
+
element :'ns0:consumptionTier', class: Integer, as: :consumption_tier
|
116
|
+
element :'ns0:currency', class: Integer, as: :currency
|
117
|
+
element :'ns0:dataQualifier', class: Integer, as: :data_qualifier
|
118
|
+
element :'ns0:defaultQuality', class: Integer, as: :default_quality
|
119
|
+
element :'ns0:flowDirection', class: Integer, as: :flow_direction
|
120
|
+
element :'ns0:intervalLength', class: Integer, as: :interval_length
|
121
|
+
element :'ns0:kind', class: Integer, as: :kind
|
122
|
+
element :'ns0:phase', class: Integer, as: :phase
|
123
|
+
element :'ns0:powerOfTenMultiplier', class: Integer,
|
124
|
+
as: :power_of_ten_multiplier
|
125
|
+
element :'ns0:timeAttribute', class: Integer, as: :time_attribute
|
126
|
+
element :'ns0:tou', class: Integer, as: :tou
|
127
|
+
element :'ns0:uom', class: Integer, as: :uom
|
128
|
+
element :'ns0:cpp', class: Integer, as: :cpp
|
129
|
+
element :'ns0:interharmonic', class: RationalNumber, as: :interharmonic
|
130
|
+
element :'ns0:measuringPeriod', class: Integer, as: :measuring_period
|
131
|
+
element :'ns0:argument', class: RationalNumber, as: :argument
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class ServiceCategory
|
4
|
+
include SAXMachine
|
5
|
+
|
6
|
+
element :kind, class: Integer do |kind|
|
7
|
+
GreenButtonData::Enumerations::SERVICE[kind]
|
8
|
+
end
|
9
|
+
|
10
|
+
# ESPI Namespacing
|
11
|
+
element :'espi:kind', class: Integer, as: :kind
|
12
|
+
|
13
|
+
# Special case for PG&E which uses generic namespacing
|
14
|
+
element :'ns0:kind', class: Integer, as: :kind
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Parser
|
3
|
+
class UsagePoint
|
4
|
+
include SAXMachine
|
5
|
+
include Enumerations
|
6
|
+
|
7
|
+
element :kind, class: Integer
|
8
|
+
|
9
|
+
def service_category
|
10
|
+
SERVICE[@kind]
|
11
|
+
end
|
12
|
+
|
13
|
+
# ESPI Namespacing
|
14
|
+
element :'espi:kind', class: Integer, as: :kind
|
15
|
+
|
16
|
+
# Special case for PG&E which uses generic namespacing
|
17
|
+
element :'ns0:kind', class: Integer, as: :kind
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module GreenButtonData
|
2
|
+
module Utilities
|
3
|
+
|
4
|
+
def parse_datetime(string)
|
5
|
+
begin
|
6
|
+
DateTime.parse(string).utc
|
7
|
+
rescue
|
8
|
+
warn "Parsing failed for string: #{string.inspect}"
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Normalizes UNIX +epoch+ ticks to seconds.
|
15
|
+
#
|
16
|
+
# If the number of digits for +epoch+ are greater than or equal to 15
|
17
|
+
# decimal digits, it assumes that the +epoch+ is in microseconds.
|
18
|
+
#
|
19
|
+
# If the number of digits for +epoch+ are less than 15 but greater than or
|
20
|
+
# equal to 13 decimal digits, it assumes that +epoch+ is in milliseconds.
|
21
|
+
#
|
22
|
+
# Less than 13 digits is assumed to be in seconds.
|
23
|
+
#
|
24
|
+
# ==== Arguments
|
25
|
+
#
|
26
|
+
# * +epoch+ - Amount of seconds/milliseconds/microseconds since 1970-01-01
|
27
|
+
def normalize_epoch(epoch)
|
28
|
+
if epoch.digits >= 15
|
29
|
+
epoch / 100000
|
30
|
+
elsif epoch.digits >= 13
|
31
|
+
epoch / 1000
|
32
|
+
else
|
33
|
+
epoch
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Retrieves the first Sunday of the month
|
39
|
+
#
|
40
|
+
# ==== Arguments
|
41
|
+
#
|
42
|
+
# * +year+ - year
|
43
|
+
# * +month+ - month
|
44
|
+
def first_sunday_of(year = Time.now.year, month = Time.now.month)
|
45
|
+
first_day = DateTime.new(year, month, 1)
|
46
|
+
first_weekday = first_day.wday
|
47
|
+
|
48
|
+
# If today is Sunday, no offset, otherwise, calculate number of days that
|
49
|
+
# need to be added before hitting the first Sunday of month
|
50
|
+
day_offset = first_weekday == 0 ? 0 : 7 - first_weekday
|
51
|
+
|
52
|
+
# Return first Monday of the month
|
53
|
+
first_day + day_offset
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Returns the Nth weekday in the given month
|
58
|
+
#
|
59
|
+
# ==== Arguments
|
60
|
+
#
|
61
|
+
# * +year+ - year
|
62
|
+
# * +month+ - month
|
63
|
+
# * +weekday+ - day of week; 0 = Sunday, 6 = Saturday
|
64
|
+
# * +week+ - Nth week of month
|
65
|
+
#
|
66
|
+
# ==== Examples
|
67
|
+
#
|
68
|
+
# To retrieve third Friday of July 2015,
|
69
|
+
# nth_weekday_of 2015, 7, 5, 3
|
70
|
+
def nth_weekday_of(year = Time.now.year, month = Time.now.month,
|
71
|
+
weekday = 0, week = 1)
|
72
|
+
|
73
|
+
# Day offset needed for Nth day of the week
|
74
|
+
first_sunday_of(year, month) + weekday + (week - 1) * 7
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Returns the last weekday in the given month
|
79
|
+
#
|
80
|
+
# ==== Arguments
|
81
|
+
#
|
82
|
+
# * +year+ - year
|
83
|
+
# * +month+ - month
|
84
|
+
# * +weekday+ - day of week; 0 = Sunday, 6 = Saturday
|
85
|
+
#
|
86
|
+
# ==== Examples
|
87
|
+
#
|
88
|
+
# To retrieve the last Wednesday of September 2015,
|
89
|
+
# last_weekday_of 2015, 9, 3
|
90
|
+
def last_weekday_of(year = Time.now.year, month = Time.now.month,
|
91
|
+
weekday = 0)
|
92
|
+
|
93
|
+
# Get the last day of month
|
94
|
+
last_day = DateTime.new year, month, -1
|
95
|
+
last_weekday = last_day.wday
|
96
|
+
|
97
|
+
day_offset = if last_weekday >= weekday
|
98
|
+
last_weekday - weekday
|
99
|
+
else
|
100
|
+
7 + last_weekday - weekday
|
101
|
+
end
|
102
|
+
|
103
|
+
last_day - day_offset
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|