aemo 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/Gemfile +19 -0
- data/Gemfile.lock +7 -0
- data/README.md +0 -0
- data/aemo.gemspec +27 -0
- data/lib/aemo.rb +9 -0
- data/lib/aemo/dispatchable.rb +7 -0
- data/lib/aemo/market.rb +40 -0
- data/lib/aemo/market/interval.rb +73 -0
- data/lib/aemo/nem12.rb +487 -0
- data/lib/aemo/nem13.rb +5 -0
- data/lib/aemo/region.rb +61 -0
- data/spec/aemo/market_spec.rb +16 -0
- data/spec/aemo/nem12_spec.rb +58 -0
- data/spec/aemo/region_spec.rb +9 -0
- data/spec/aemo_spec.rb +9 -0
- data/spec/fixtures/GRAPH_30NSW1.csv +122 -0
- data/spec/fixtures/GRAPH_5NSW1.csv +289 -0
- data/spec/fixtures/NEM12-Errors/NEM12#000000000000021#CNRGYMDP#NEMMCO +3 -0
- data/spec/fixtures/NEM12-Errors/NEM12#000000000000022#CNRGYMDP#NEMMCO +4 -0
- data/spec/fixtures/NEM12-Errors/NEM12#000000000000023#CNRGYMDP#NEMMCO +6 -0
- data/spec/fixtures/NEM12-Errors/NEM12#000000000000024#CNRGYMDP#NEMMCO +6 -0
- data/spec/fixtures/NEM12-Errors/NEM12#000000000000025#CNRGYMDP#NEMMCO +3 -0
- data/spec/fixtures/NEM12/NEM12#000000000000001#CNRGYMDP#NEMMCO.csv +18 -0
- data/spec/fixtures/NEM12/NEM12#000000000000002#CNRGYMDP#NEMMCO.csv +34 -0
- data/spec/fixtures/NEM12/NEM12#000000000000003#CNRGYMDP#NEMMCO.csv +42 -0
- data/spec/fixtures/NEM12/NEM12#000000000000004#CNRGYMDP#NEMMCO.csv +10 -0
- data/spec/fixtures/NEM12/NEM12#000000000000005#CNRGYMDP#NEMMCO.csv +10 -0
- data/spec/fixtures/NEM12/NEM12#000000000000006#CNRGYMDP#NEMMCO.csv +18 -0
- data/spec/fixtures/NEM12/NEM12#000000000000007#CNRGYMDP#NEMMCO.csv +18 -0
- data/spec/fixtures/NEM12/NEM12#000000000000008#CNRGYMDP#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/NEM12#000000000000009#CNRGYMDP#NEMMCO.csv +25 -0
- data/spec/fixtures/NEM12/NEM12#000000000000010#CNRGYMDP#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/NEM12#01010_05030502#WBAYM#NEMMCO.V01 +12 -0
- data/spec/fixtures/NEM12/NEM12#02030_05030501#WBAYM#NEMMCO.V01 +22 -0
- data/spec/fixtures/NEM12/NEM12#03050_05031001#WBAYM#NEMMCO.V01 +12 -0
- data/spec/fixtures/NEM12/NEM12#05050200001000000#GLOBALM#NEMMCO +18 -0
- data/spec/fixtures/NEM12/NEM12#05050200002000000#GLOBALM#NEMMCO +34 -0
- data/spec/fixtures/NEM12/NEM12#05050200003000000#GLOBALM#NEMMCO +18 -0
- data/spec/fixtures/NEM12/NEM12#05050200004000000#GLOBALM#NEMMCO +18 -0
- data/spec/fixtures/NEM12/NEM12#05050200005000000#GLOBALM#NEMMCO +18 -0
- data/spec/fixtures/NEM12/NEM12#05050200008000000#GLOBALM#NEMMCO +19 -0
- data/spec/fixtures/NEM12/NEM12#05051100001000000#GLOBALM#NEMMCO +7 -0
- data/spec/fixtures/NEM12/NEM12#05051100002000000#GLOBALM#NEMMCO +5 -0
- data/spec/fixtures/NEM12/NEM12#05051100004000000#GLOBALM#NEMMCO +12 -0
- data/spec/fixtures/NEM12/NEM12#05051200001000000#GLOBALM#NEMMCO +13 -0
- data/spec/fixtures/NEM12/NEM12#05062000001000000#GLOBALM#EASTENGY +20 -0
- data/spec/fixtures/NEM12/NEM12#05090_05031401#WBAYM#NEMMCO.V01 +8 -0
- data/spec/fixtures/NEM12/NEM12#06110_05021206#WBAYM#NEMMCO.V01 +12 -0
- data/spec/fixtures/NEM12/NEM12#07130_05021202#WBAYM#NEMMCO.V01 +12 -0
- data/spec/fixtures/NEM12/NEM12#08150_05031502#WBAYM#NEMMCO.V01 +10 -0
- data/spec/fixtures/NEM12/NEM12#10190_05031401#WBAYM#NEMMCO.V01 +10 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO1#UNITEDDP#NEMMCO.csv +14 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO10#UNITEDDP#NEMMCO.csv +23 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO1005032705#ENERGEXM#NEMMCO.V05 +21 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO105032701#ENERGEXM#NEMMCO.V01 +12 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO2#UNITEDDP#NEMMCO.csv +22 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO205032701#ENERGEXM#NEMMCO.V01 +22 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO3#UNITEDDP#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO305032701#ENERGEXM#NEMMCO.V01 +12 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO4#UNITEDDP#NEMMCO.csv +9 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO5#UNITEDDP#NEMMCO.csv +9 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO505033001#ENERGEXM#NEMMCO.V01 +8 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO6#UNITEDDP#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO605033001#ENERGEXM#NEMMCO.V01 +12 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO7#UNITEDDP#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO705033001#ENERGEXM#NEMMCO.V01 +12 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO8#UNITEDDP#NEMMCO.csv +9 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO805040401#ENERGEXM#NEMMCO.V01 +11 -0
- data/spec/fixtures/NEM12/NEM12#SCENARIO9#UNITEDDP#NEMMCO.csv +13 -0
- data/spec/fixtures/NEM12/NEM12#Scenario01#ETSAMDP#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/NEM12#Scenario01#POWERMDP#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/NEM12#Scenario04#ETSAMDP#NEMMCO.csv +9 -0
- data/spec/fixtures/NEM12/NEM12#Scenario04#POWERMDP#NEMMCO.csv +9 -0
- data/spec/fixtures/NEM12/NEM12#Scenario05#ETSAMDP#NEMMCO.csv +9 -0
- data/spec/fixtures/NEM12/NEM12#Scenario05#POWERMDP#NEMMCO.csv +9 -0
- data/spec/fixtures/NEM12/NEM12#Scenario06#ETSAMDP#NEMMCO.csv +18 -0
- data/spec/fixtures/NEM12/NEM12#Scenario06#POWERMDP#NEMMCO.csv +18 -0
- data/spec/fixtures/NEM12/NEM12#Scenario07#ETSAMDP#NEMMCO.csv +18 -0
- data/spec/fixtures/NEM12/NEM12#Scenario07#POWERMDP#NEMMCO.csv +18 -0
- data/spec/fixtures/NEM12/NEM12#Scenario08#ETSAMDP#NEMMCO.csv +10 -0
- data/spec/fixtures/NEM12/NEM12#Scenario08#POWERMDP#NEMMCO.csv +10 -0
- data/spec/fixtures/NEM12/NEM12#Scenario09#ETSAMDP#NEMMCO.csv +13 -0
- data/spec/fixtures/NEM12/NEM12#Scenario09#POWERMDP#NEMMCO.csv +13 -0
- data/spec/fixtures/NEM12/NEM12#Scenario10#ETSAMDP#NEMMCO.csv +31 -0
- data/spec/fixtures/NEM12/NEM12#Scenario10#POWERMDP#NEMMCO.csv +31 -0
- data/spec/fixtures/NEM12/NEM12#mdffl0000000001#ACTEWM#NEMMCO.mdff +12 -0
- data/spec/fixtures/NEM12/NEM12#mdffl0000000004#ACTEWM#NEMMCO.txt +8 -0
- data/spec/fixtures/NEM12/NEM12#mdffl0000000008#ACTEWM#NEMMCO.txt +7 -0
- data/spec/fixtures/NEM12/nem12#S01#INTEGM#NEMMCO +14 -0
- data/spec/fixtures/NEM12/nem12#S02#INTEGM#NEMMCO +22 -0
- data/spec/fixtures/NEM12/nem12#S03#INTEGM#NEMMCO +12 -0
- data/spec/fixtures/NEM12/nem12#S04#INTEGM#NEMMCO +8 -0
- data/spec/fixtures/NEM12/nem12#S05#INTEGM#NEMMCO +8 -0
- data/spec/fixtures/NEM12/nem12#S06#INTEGM#NEMMCO +12 -0
- data/spec/fixtures/NEM12/nem12#S07#INTEGM#NEMMCO +12 -0
- data/spec/fixtures/NEM12/nem12#S08#INTEGM#NEMMCO +10 -0
- data/spec/fixtures/NEM12/nem12#S09#INTEGM#NEMMCO +13 -0
- data/spec/fixtures/NEM12/nem12#S10#INTEGM#NEMMCO +11 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO01#TCAUSTM#NEMMCO.csv +14 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO01NEM1201003#ELECTDSM#NEMMCO +14 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO02#TCAUSTM#NEMMCO.csv +22 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO02NEM1202023#ELECTDSM#NEMMCO +22 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO03#TCAUSTM#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO03NEM1203043#ELECTDSM#NEMMCO +12 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO04#TCAUSTM#NEMMCO.csv +9 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO05#TCAUSTM#NEMMCO.csv +8 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO05NEM1205083#ELECTDSM#NEMMCO +8 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO06#TCAUSTM#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO06NEM1206103#ELECTDSM#NEMMCO +14 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO07#TCAUSTM#NEMMCO.csv +12 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO07NEM1206103#ELECTDSM#NEMMCO +14 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO08#TCAUSTM#NEMMCO.csv +8 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO08NEM1208143#ELECTDSM#NEMMCO +12 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO09#TCAUSTM#NEMMCO.csv +13 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO10#TCAUSTM#NEMMCO.csv +20 -0
- data/spec/fixtures/NEM12/nem12#SCENARIO10NEM1210183#ELECTDSM#NEMMCO +10 -0
- data/spec/fixtures/nmi_checksum.json +32 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +4 -0
- metadata +326 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a6c51782a64249e4194c67303c84c6c934cdfb79
|
4
|
+
data.tar.gz: b87f5bfb7d5a38deede4a0751e46f257bf6840f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f3106500593f93c92b30faf2fb5df869cedea03bcafb3c14037c6eb994c1ba4af1489d674b511aef97f1732394985f2f36d7aeaa9e355c7f12c31fbf1dd32d82
|
7
|
+
data.tar.gz: eca1a6b0db4795e6d9776da382237c9ccc011356d4b2e31a4e526877caa7174dfe329c753fd8accca728425a4250c3233c57100c0b4635948991f51215773864
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem 'httparty', '~0.13'
|
7
|
+
gem 'json', '~1.8'
|
8
|
+
gem 'multi_xml', '>= 0.5.2'
|
9
|
+
gem 'coveralls', :require => false
|
10
|
+
|
11
|
+
# Add dependencies to develop your gem here.
|
12
|
+
# Include everything needed to run rake, tests, features, etc.
|
13
|
+
# group :development do
|
14
|
+
# gem "shoulda", ">= 0"
|
15
|
+
# gem "rdoc", "~> 3.12"
|
16
|
+
# gem "bundler", ">= 1.0.0"
|
17
|
+
# gem "jeweler", ">= 1.8.4"
|
18
|
+
# gem "simplecov", ">= 0"
|
19
|
+
# end
|
data/Gemfile.lock
ADDED
data/README.md
ADDED
File without changes
|
data/aemo.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'aemo'
|
6
|
+
s.version = '0.1.0'
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.date = '2014-08-19'
|
9
|
+
s.summary = 'AEMO Gem'
|
10
|
+
s.description = 'Gem providing functionality for the Australian Energy Market Operator data'
|
11
|
+
s.authors = ['Joel Courtney']
|
12
|
+
s.email = ['jcourtney@cozero.com.au']
|
13
|
+
s.homepage =
|
14
|
+
'https://bitbucket.org/jufemaiz/aemo-gem'
|
15
|
+
s.license = 'MIT'
|
16
|
+
|
17
|
+
s.required_ruby_version = '>= 1.9.3'
|
18
|
+
|
19
|
+
s.add_dependency 'json', '~> 1.8'
|
20
|
+
s.add_runtime_dependency 'multi_xml', '~> 0.5', '>= 0.5.2'
|
21
|
+
s.add_runtime_dependency 'httparty', '~> 0.13', '>= 0.13.1'
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ['lib']
|
27
|
+
end
|
data/lib/aemo.rb
ADDED
data/lib/aemo/market.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
module AEMO
|
2
|
+
module Market
|
3
|
+
include HTTParty
|
4
|
+
base_uri 'www.nemweb.com.au'
|
5
|
+
|
6
|
+
def self.regions
|
7
|
+
AEMO::Region::REGIONS
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.current_dispatch(region)
|
11
|
+
response = AEMO::Market.get "/mms.GRAPHS/GRAPHS/GRAPH_5#{region}1.csv"
|
12
|
+
values = AEMO::Market.parse_response(response)
|
13
|
+
values
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.current_trading(region)
|
17
|
+
response = AEMO::Market.get "/mms.GRAPHS/GRAPHS/GRAPH_30#{region}1.csv"
|
18
|
+
values = AEMO::Market.parse_response(response)
|
19
|
+
values
|
20
|
+
end
|
21
|
+
|
22
|
+
# ######### #
|
23
|
+
protected
|
24
|
+
# ######### #
|
25
|
+
|
26
|
+
def self.parse_response(response)
|
27
|
+
values = []
|
28
|
+
if response.response.code == '200'
|
29
|
+
CSV.parse(response.body, :headers => true, :converters => :numeric) do |row|
|
30
|
+
row = row.to_h
|
31
|
+
values.push AEMO::Market::Interval.new(row['SETTLEMENTDATE'],row)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
values
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module AEMO
|
2
|
+
module Market
|
3
|
+
class Interval
|
4
|
+
INTERVALS = {
|
5
|
+
:trading => 'Trading',
|
6
|
+
:dispatch => 'Dispatch'
|
7
|
+
}
|
8
|
+
|
9
|
+
attr_accessor :datetime, :region, :total_demand, :rrp, :period_type
|
10
|
+
|
11
|
+
# @param datetime [Time]
|
12
|
+
# @param options [Hash] Hash of optional data values
|
13
|
+
# @return [AEMO::Market::Interval]
|
14
|
+
def initialize(datetime,options={})
|
15
|
+
@datetime = Time.parse("#{datetime} +1000")
|
16
|
+
@region = options['REGION']
|
17
|
+
@total_demand = options['TOTALDEMAND']
|
18
|
+
@rrp = options['RRP']
|
19
|
+
@period_type = options['PERIODTYPE']
|
20
|
+
end
|
21
|
+
|
22
|
+
# Instance Variable Getters
|
23
|
+
|
24
|
+
# All AEMO Data operates in Australian Eastern Standard Time
|
25
|
+
# All AEMO Data aggregates to the trailing edge of the period (this makes it difficult to do daily aggregations :( )
|
26
|
+
# @param trailing_edge [Boolean] selection of either the trailing edge of the period or the rising edge of the period for the date time
|
27
|
+
# @return [Time] a time object of the trailing edge of the interval
|
28
|
+
def datetime(trailing_edge = true)
|
29
|
+
t = @datetime
|
30
|
+
# If the datetime requested is the trailing edge, offset as per interval requirement
|
31
|
+
unless trailing_edge
|
32
|
+
# This is for dispatch intervals of five minutes
|
33
|
+
if self.is_dispatch?
|
34
|
+
t -= 5 * 60
|
35
|
+
elsif self.is_trading?
|
36
|
+
t -= 30 * 60
|
37
|
+
end
|
38
|
+
end
|
39
|
+
t
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Time] the time of the
|
43
|
+
def interval_length
|
44
|
+
Time.at(300)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Symbol] :dispatch or :trading
|
48
|
+
def interval_type
|
49
|
+
self.is_dispatch? ? :dispatch : :trading
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Boolean] returns true if the interval type is dispatch
|
53
|
+
def is_dispatch?
|
54
|
+
@period_type.nil? || @period_type.empty?
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Boolean] returns true if the interval type is trading
|
58
|
+
def is_trading?
|
59
|
+
!(self.is_dispatch?)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [Float] the value of the interval in Australian Dollars
|
63
|
+
def value
|
64
|
+
@value ||= Float::NAN
|
65
|
+
if @total_demand.class == Float && @rrp.class == Float
|
66
|
+
@value = (@total_demand * @rrp).round(2)
|
67
|
+
end
|
68
|
+
@value
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/aemo/nem12.rb
ADDED
@@ -0,0 +1,487 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'time'
|
3
|
+
module AEMO
|
4
|
+
class NEM12
|
5
|
+
# As per AEMO NEM12 Specification
|
6
|
+
# http://www.aemo.com.au/Consultations/National-Electricity-Market/Open/~/media/Files/Other/consultations/nem/Meter%20Data%20File%20Format%20Specification%20NEM12_NEM13/MDFF_Specification_NEM12_NEM13_Final_v102_clean.ashx
|
7
|
+
RECORD_INDICATORS = {
|
8
|
+
100 => 'Header',
|
9
|
+
200 => 'NMI Data Details',
|
10
|
+
300 => 'Interval Data',
|
11
|
+
400 => 'Interval Event',
|
12
|
+
500 => 'B2B Details',
|
13
|
+
900 => 'End'
|
14
|
+
}
|
15
|
+
|
16
|
+
TRANSACTION_CODE_FLAGS = {
|
17
|
+
'A' => 'Alteration',
|
18
|
+
'C' => 'Meter Reconfiguration',
|
19
|
+
'G' => 'Re-energisation',
|
20
|
+
'D' => 'De-energisation',
|
21
|
+
'E' => 'Forward Estimate',
|
22
|
+
'N' => 'Normal Read',
|
23
|
+
'O' => 'Other',
|
24
|
+
'S' => 'Special Read',
|
25
|
+
'R' => 'Removal of Meter'
|
26
|
+
}
|
27
|
+
|
28
|
+
UOM = {
|
29
|
+
'MWh' => { :name => 'Megawatt Hour', :multiplier => 1e6 },
|
30
|
+
'kWh' => { :name => 'Kilowatt Hour', :multiplier => 1e3 },
|
31
|
+
'Wh' => { :name => 'Watt Hour', :multiplier => 1 },
|
32
|
+
'MW' => { :name => 'Megawatt', :multiplier => 1e6 },
|
33
|
+
'kW' => { :name => 'Kilowatt', :multiplier => 1e3 },
|
34
|
+
'W' => { :name => 'Watt', :multiplier => 1 },
|
35
|
+
'MVArh' => { :name => 'Megavolt Ampere Reactive Hour', :multiplier => 1e6 },
|
36
|
+
'kVArh' => { :name => 'Kilovolt Ampere Reactive Hour', :multiplier => 1e3 },
|
37
|
+
'VArh' => { :name => 'Volt Ampere Reactive Hour', :multiplier => 1 },
|
38
|
+
'MVAr' => { :name => 'Megavolt Ampere Reactive', :multiplier => 1e6 },
|
39
|
+
'kVAr' => { :name => 'Kilovolt Ampere Reactive', :multiplier => 1e3 },
|
40
|
+
'VAr' => { :name => 'Volt Ampere Reactive', :multiplier => 1 },
|
41
|
+
'MVAh' => { :name => 'Megavolt Ampere Hour', :multiplier => 1e6 },
|
42
|
+
'kVAh' => { :name => 'Kilovolt Ampere Hour', :multiplier => 1e3 },
|
43
|
+
'VAh' => { :name => 'Volt Ampere Hour', :multiplier => 1 },
|
44
|
+
'MVA' => { :name => 'Megavolt Ampere', :multiplier => 1e6 },
|
45
|
+
'kVA' => { :name => 'Kilovolt Ampere', :multiplier => 1e3 },
|
46
|
+
'VA' => { :name => 'Volt Ampere', :multiplier => 1 },
|
47
|
+
'kV' => { :name => 'Kilovolt', :multiplier => 1e3 },
|
48
|
+
'V' => { :name => 'Volt', :multiplier => 1 },
|
49
|
+
'kA' => { :name => 'Kiloampere', :multiplier => 1e3 },
|
50
|
+
'A' => { :name => 'Ampere', :multiplier => 1 },
|
51
|
+
'pf' => { :name => 'Power Factor', :multiplier => 1 }
|
52
|
+
}
|
53
|
+
|
54
|
+
QUALITY_FLAGS = {
|
55
|
+
'A' => 'Actual Data',
|
56
|
+
'E' => 'Forward Estimated Data',
|
57
|
+
'F' => 'Final Substituted Data',
|
58
|
+
'N' => 'Null Data',
|
59
|
+
'S' => 'Substituted Data',
|
60
|
+
'V' => 'Variable Data',
|
61
|
+
}
|
62
|
+
|
63
|
+
METHOD_FLAGS = {
|
64
|
+
"11" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Check", description: "" },
|
65
|
+
"12" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Calculated", description: "" },
|
66
|
+
"13" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "SCADA", description: "" },
|
67
|
+
"14" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Like Day", description: "" },
|
68
|
+
"15" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Average Like Day", description: "" },
|
69
|
+
"16" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Agreed", description: "" },
|
70
|
+
"17" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Linear", description: "" },
|
71
|
+
"18" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Alternate", description: "" },
|
72
|
+
"19" => { type: ["SUB"], installation_type: [1,2,3,4], short_descriptor: "Zero", description: "" },
|
73
|
+
"51" => { type: ["EST","SUB"], installation_type: 5, short_descriptor: "Previous Year", description: "" },
|
74
|
+
"52" => { type: ["EST","SUB"], installation_type: 5, short_descriptor: "Previous Read", description: "" },
|
75
|
+
"53" => { type: ["SUB"], installation_type: 5, short_descriptor: "Revision", description: "" },
|
76
|
+
"54" => { type: ["SUB"], installation_type: 5, short_descriptor: "Linear", description: "" },
|
77
|
+
"55" => { type: ["SUB"], installation_type: 5, short_descriptor: "Agreed", description: "" },
|
78
|
+
"56" => { type: ["EST","SUB"], installation_type: 5, short_descriptor: "Prior to First Read – Agreed", description: "" },
|
79
|
+
"57" => { type: ["EST","SUB"], installation_type: 5, short_descriptor: "Customer Class", description: "" },
|
80
|
+
"58" => { type: ["EST","SUB"], installation_type: 5, short_descriptor: "Zero", description: "" },
|
81
|
+
"61" => { type: ["EST","SUB"], installation_type: 6, short_descriptor: "Previous Year", description: "" },
|
82
|
+
"62" => { type: ["EST","SUB"], installation_type: 6, short_descriptor: "Previous Read", description: "" },
|
83
|
+
"63" => { type: ["EST","SUB"], installation_type: 6, short_descriptor: "Customer Class", description: "" },
|
84
|
+
"64" => { type: ["SUB"], installation_type: 6, short_descriptor: "Agreed", description: "" },
|
85
|
+
"65" => { type: ["EST"], installation_type: 6, short_descriptor: "ADL", description: "" },
|
86
|
+
"66" => { type: ["SUB"], installation_type: 6, short_descriptor: "Revision", description: "" },
|
87
|
+
"67" => { type: ["SUB"], installation_type: 6, short_descriptor: "Customer Read", description: "" },
|
88
|
+
"68" => { type: ["EST","SUB"], installation_type: 6, short_descriptor: "Zero", description: "" },
|
89
|
+
"71" => { type: ["SUB"], installation_type: 7, short_descriptor: "Recalculation", description: "" },
|
90
|
+
"72" => { type: ["SUB"], installation_type: 7, short_descriptor: "Revised Table", description: "" },
|
91
|
+
"73" => { type: ["SUB"], installation_type: 7, short_descriptor: "Revised Algorithm", description: "" },
|
92
|
+
"74" => { type: ["SUB"], installation_type: 7, short_descriptor: "Agreed", description: "" },
|
93
|
+
"75" => { type: ["EST"], installation_type: 7, short_descriptor: "Existing Table", description: "" }
|
94
|
+
}
|
95
|
+
|
96
|
+
REASON_CODES = {
|
97
|
+
0 => 'Free Text Description',
|
98
|
+
1 => 'Meter/Equipment Changed',
|
99
|
+
2 => 'Extreme Weather/Wet',
|
100
|
+
3 => 'Quarantine',
|
101
|
+
4 => 'Savage Dog',
|
102
|
+
5 => 'Meter/Equipment Changed',
|
103
|
+
6 => 'Extreme Weather/Wet',
|
104
|
+
7 => 'Unable To Locate Meter',
|
105
|
+
8 => 'Vacant Premise',
|
106
|
+
9 => 'Meter/Equipment Changed',
|
107
|
+
10 => 'Lock Damaged/Seized',
|
108
|
+
11 => 'In Wrong Walk',
|
109
|
+
12 => 'Locked Premises',
|
110
|
+
13 => 'Locked Gate',
|
111
|
+
14 => 'Locked Meter Box',
|
112
|
+
15 => 'Access - Overgrown',
|
113
|
+
16 => 'Noxious Weeds',
|
114
|
+
17 => 'Unsafe Equipment/Location',
|
115
|
+
18 => 'Read Below Previous',
|
116
|
+
19 => 'Consumer Wanted',
|
117
|
+
20 => 'Damaged Equipment/Panel',
|
118
|
+
21 => 'Switched Off',
|
119
|
+
22 => 'Meter/Equipment Seals Missing',
|
120
|
+
23 => 'Meter/Equipment Seals Missing',
|
121
|
+
24 => 'Meter/Equipment Seals Missing',
|
122
|
+
25 => 'Meter/Equipment Seals Missing',
|
123
|
+
26 => 'Meter/Equipment Seals Missing',
|
124
|
+
27 => 'Meter/Equipment Seals Missing',
|
125
|
+
28 => 'Damaged Equipment/Panel',
|
126
|
+
29 => 'Relay Faulty/Damaged',
|
127
|
+
30 => 'Meter Stop Switch On',
|
128
|
+
31 => 'Meter/Equipment Seals Missing',
|
129
|
+
32 => 'Damaged Equipment/Panel',
|
130
|
+
33 => 'Relay Faulty/Damaged',
|
131
|
+
34 => 'Meter Not In Handheld',
|
132
|
+
35 => 'Timeswitch Faulty/Reset Required',
|
133
|
+
36 => 'Meter High/Ladder Required',
|
134
|
+
37 => 'Meter High/Ladder Required',
|
135
|
+
38 => 'Unsafe Equipment/Location',
|
136
|
+
39 => 'Reverse Energy Observed',
|
137
|
+
40 => 'Timeswitch Faulty/Reset Required',
|
138
|
+
41 => 'Faulty Equipment Display/Dials',
|
139
|
+
42 => 'Faulty Equipment Display/Dials',
|
140
|
+
43 => 'Power Outage',
|
141
|
+
44 => 'Unsafe Equipment/Location',
|
142
|
+
45 => 'Readings Failed To Validate',
|
143
|
+
46 => 'Extreme Weather/Hot',
|
144
|
+
47 => 'Refused Access',
|
145
|
+
48 => 'Timeswitch Faulty/Reset Required',
|
146
|
+
49 => 'Wet Paint',
|
147
|
+
50 => 'Wrong Tariff',
|
148
|
+
51 => 'Installation Demolished',
|
149
|
+
52 => 'Access - Blocked',
|
150
|
+
53 => 'Bees/Wasp In Meter Box',
|
151
|
+
54 => 'Meter Box Damaged/Faulty',
|
152
|
+
55 => 'Faulty Equipment Display/Dials',
|
153
|
+
56 => 'Meter Box Damaged/Faulty',
|
154
|
+
57 => 'Timeswitch Faulty/Reset Required',
|
155
|
+
58 => 'Meter Ok - Supply Failure',
|
156
|
+
59 => 'Faulty Equipment Display/Dials',
|
157
|
+
60 => 'Illegal Connection/Equipment Tampered',
|
158
|
+
61 => 'Meter Box Damaged/Faulty',
|
159
|
+
62 => 'Damaged Equipment/Panel',
|
160
|
+
63 => 'Illegal Connection/Equipment Tampered',
|
161
|
+
64 => 'Key Required',
|
162
|
+
65 => 'Wrong Key Provided',
|
163
|
+
66 => 'Lock Damaged/Seized',
|
164
|
+
67 => 'Extreme Weather/Wet',
|
165
|
+
68 => 'Zero Consumption',
|
166
|
+
69 => 'Reading Exceeds Estimate',
|
167
|
+
70 => 'Probe Reports Tampering',
|
168
|
+
71 => 'Probe Read Error',
|
169
|
+
72 => 'Meter/Equipment Changed',
|
170
|
+
73 => 'Low Consumption',
|
171
|
+
74 => 'High Consumption',
|
172
|
+
75 => 'Customer Read',
|
173
|
+
76 => 'Communications Fault',
|
174
|
+
77 => 'Estimation Forecast',
|
175
|
+
78 => 'Null Data',
|
176
|
+
79 => 'Power Outage Alarm',
|
177
|
+
80 => 'Short Interval Alarm',
|
178
|
+
81 => 'Long Interval Alarm',
|
179
|
+
82 => 'CRC Error',
|
180
|
+
83 => 'RAM Checksum Error',
|
181
|
+
84 => 'ROM Checksum Error',
|
182
|
+
85 => 'Data Missing Alarm',
|
183
|
+
86 => 'Clock Error Alarm',
|
184
|
+
87 => 'Reset Occurred',
|
185
|
+
88 => 'Watchdog Timeout Alarm',
|
186
|
+
89 => 'Time Reset Occurred',
|
187
|
+
90 => 'Test Mode',
|
188
|
+
91 => 'Load Control',
|
189
|
+
92 => 'Added Interval (Data Correction)',
|
190
|
+
93 => 'Replaced Interval (Data Correction)',
|
191
|
+
94 => 'Estimated Interval (Data Correction)',
|
192
|
+
95 => 'Pulse Overflow Alarm',
|
193
|
+
96 => 'Data Out Of Limits',
|
194
|
+
97 => 'Excluded Data',
|
195
|
+
98 => 'Parity Error',
|
196
|
+
99 => 'Energy Type (Register Changed)'
|
197
|
+
}
|
198
|
+
|
199
|
+
DATA_STREAM_SUFFIX = {
|
200
|
+
# Averaged Data Streams
|
201
|
+
'A' => { :stream => 'Average', :description => 'Import', :units => 'kWh' },
|
202
|
+
'D' => { :stream => 'Average', :description => 'Export', :units => 'kWh' },
|
203
|
+
'J' => { :stream => 'Average', :description => 'Import', :units => 'kVArh' },
|
204
|
+
'P' => { :stream => 'Average', :description => 'Export', :units => 'kVArh' },
|
205
|
+
'S' => { :stream => 'Average', :description => '', :units => 'kVAh' },
|
206
|
+
# Master Data Streams
|
207
|
+
'B' => { :stream => 'Master', :description => 'Import', :units => 'kWh' },
|
208
|
+
'E' => { :stream => 'Master', :description => 'Export', :units => 'kWh' },
|
209
|
+
'K' => { :stream => 'Master', :description => 'Import', :units => 'kVArh' },
|
210
|
+
'Q' => { :stream => 'Master', :description => 'Export', :units => 'kVArh' },
|
211
|
+
'T' => { :stream => 'Master', :description => '', :units => 'kVAh' },
|
212
|
+
'G' => { :stream => 'Master', :description => 'Power Factor', :units => 'PF' },
|
213
|
+
'H' => { :stream => 'Master', :description => 'Q Metering', :units => 'Qh' },
|
214
|
+
'M' => { :stream => 'Master', :description => 'Par Metering', :units => 'parh' },
|
215
|
+
'V' => { :stream => 'Master', :description => 'Volts or V2h or Amps or A2h', :units => '' },
|
216
|
+
# Check Meter Streams
|
217
|
+
'C' => { :stream => 'Check', :description => 'Import', :units => 'kWh' },
|
218
|
+
'F' => { :stream => 'Check', :description => 'Export', :units => 'kWh' },
|
219
|
+
'L' => { :stream => 'Check', :description => 'Import', :units => 'kVArh' },
|
220
|
+
'R' => { :stream => 'Check', :description => 'Export', :units => 'kVArh' },
|
221
|
+
'U' => { :stream => 'Check', :description => '', :units => 'kVAh' },
|
222
|
+
'Y' => { :stream => 'Check', :description => 'Q Metering', :units => 'Qh' },
|
223
|
+
'W' => { :stream => 'Check', :description => 'Par Metering Path', :units => '' },
|
224
|
+
'Z' => { :stream => 'Check', :description => 'Volts or V2h or Amps or A2h', :units => '' },
|
225
|
+
# Net Meter Streams
|
226
|
+
'D' => { :stream => 'Net', :description => 'Net', :units => 'kWh' },
|
227
|
+
'J' => { :stream => 'Net', :description => 'Net', :units => 'kVArh' }
|
228
|
+
}
|
229
|
+
|
230
|
+
@nmi = nil
|
231
|
+
@data_details = []
|
232
|
+
@interval_data = []
|
233
|
+
@interval_events = []
|
234
|
+
|
235
|
+
attr_accessor :nmi, :file_contents
|
236
|
+
attr_reader :data_details, :interval_data, :interval_events
|
237
|
+
|
238
|
+
# Initialize a NEM12 file
|
239
|
+
def initialize(nmi,options={})
|
240
|
+
@nmi = nmi
|
241
|
+
@data_details = []
|
242
|
+
@interval_data = []
|
243
|
+
@interval_events = []
|
244
|
+
options.keys.each do |key|
|
245
|
+
eval "self.#{key} = #{options[key]}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# @return [Integer] checksum of the NMI
|
250
|
+
def nmi_checksum
|
251
|
+
summation = 0
|
252
|
+
@nmi.reverse.split(//).each_index do |i|
|
253
|
+
value = nmi[nmi.length - i - 1].ord
|
254
|
+
if(i % 2 == 0)
|
255
|
+
value = value * 2
|
256
|
+
end
|
257
|
+
value = value.to_s.split(//).map{|i| i.to_i}.reduce(:+)
|
258
|
+
summation += value
|
259
|
+
end
|
260
|
+
checksum = (10 - (summation % 10)) % 10
|
261
|
+
checksum
|
262
|
+
end
|
263
|
+
|
264
|
+
# Parses the header record
|
265
|
+
# @param line [String] A single line in string format
|
266
|
+
# @return [Hash] the line parsed into a hash of information
|
267
|
+
def self.parse_nem12_100(line,options={})
|
268
|
+
csv = line.parse_csv
|
269
|
+
|
270
|
+
raise ArgumentError, 'RecordIndicator is not 100' if csv[0] != '100'
|
271
|
+
raise ArgumentError, 'VersionHeader is not NEM12' if csv[1] != 'NEM12'
|
272
|
+
if options[:strict]
|
273
|
+
raise ArgumentError, 'DateTime is not valid' if csv[2].match(/\d{12}/).nil? || csv[2] != Time.parse("#{csv[2]}00").strftime('%Y%m%d%H%M')
|
274
|
+
end
|
275
|
+
raise ArgumentError, 'FromParticispant is not valid' if csv[3].match(/.{1,10}/).nil?
|
276
|
+
raise ArgumentError, 'ToParticispant is not valid' if csv[4].match(/.{1,10}/).nil?
|
277
|
+
|
278
|
+
nem12_100 = {
|
279
|
+
:record_indicator => csv[0].to_i,
|
280
|
+
:version_header => csv[1],
|
281
|
+
:datetime => Time.parse("#{csv[2]}+1000"),
|
282
|
+
:from_participant => csv[3],
|
283
|
+
:to_participant => csv[4]
|
284
|
+
}
|
285
|
+
end
|
286
|
+
|
287
|
+
# Parses the NMI Data Details
|
288
|
+
# @param line [String] A single line in string format
|
289
|
+
# @return [Hash] the line parsed into a hash of information
|
290
|
+
def parse_nem12_200(line,options={})
|
291
|
+
csv = line.parse_csv
|
292
|
+
|
293
|
+
raise ArgumentError, 'RecordIndicator is not 200' if csv[0] != '200'
|
294
|
+
raise ArgumentError, 'NMI is not valid' if csv[1].match(/[A-Z0-9]{10}/).nil?
|
295
|
+
raise ArgumentError, 'NMIConfiguration is not valid' if csv[2].match(/.{1,240}/).nil?
|
296
|
+
unless csv[3].nil?
|
297
|
+
raise ArgumentError, 'RegisterID is not valid' if csv[3].match(/.{1,10}/).nil?
|
298
|
+
end
|
299
|
+
raise ArgumentError, 'NMISuffix is not valid' if csv[4].match(/[A-HJ-NP-Z][1-9A-HJ-NP-Z]/).nil?
|
300
|
+
if !csv[5].nil? && !csv[5].empty? && !csv[5].match(/^\s*$/)
|
301
|
+
raise ArgumentError, 'MDMDataStreamIdentifier is not valid' if csv[5].match(/[A-Z0-9]{2}/).nil?
|
302
|
+
end
|
303
|
+
if !csv[6].nil? && !csv[6].empty? && !csv[6].match(/^\s*$/)
|
304
|
+
raise ArgumentError, 'MeterSerialNumber is not valid' if csv[6].match(/[A-Z0-9]{2}/).nil?
|
305
|
+
end
|
306
|
+
raise ArgumentError, 'UOM is not valid' if csv[7].upcase.match(/[A-Z0-9]{2}/).nil?
|
307
|
+
raise ArgumentError, 'UOM is not valid' unless UOM.keys.map{|k| k.upcase}.include?(csv[7].upcase)
|
308
|
+
raise ArgumentError, 'IntervalLength is not valid' unless %w(1 5 10 15 30).include?(csv[8])
|
309
|
+
# raise ArgumentError, 'NextScheduledReadDate is not valid' if csv[9].match(/\d{8}/).nil? || csv[9] != Time.parse("#{csv[9]}").strftime('%Y%m%d')
|
310
|
+
|
311
|
+
@nmi = csv[1]
|
312
|
+
|
313
|
+
# Push onto the stack
|
314
|
+
@data_details << {
|
315
|
+
:record_indicator => csv[0].to_i,
|
316
|
+
:nmi => csv[1],
|
317
|
+
:nmi_configuration => csv[2],
|
318
|
+
:register_id => csv[3],
|
319
|
+
:nmi_suffix => csv[4],
|
320
|
+
:mdm_data_streaming_identifier => csv[5],
|
321
|
+
:meter_serial_nubmer => csv[6],
|
322
|
+
:uom => csv[7].upcase,
|
323
|
+
:interval_length => csv[8].to_i,
|
324
|
+
:next_scheduled_read_date => csv[9],
|
325
|
+
}
|
326
|
+
end
|
327
|
+
|
328
|
+
# @param line [String] A single line in string format
|
329
|
+
# @return [Array of hashes] the line parsed into a hash of information
|
330
|
+
def parse_nem12_300(line,options={})
|
331
|
+
csv = line.parse_csv
|
332
|
+
|
333
|
+
raise TypeError, 'Expected NMI Data Details to exist with IntervalLength specified' if @data_details.last.nil? || @data_details.last[:interval_length].nil?
|
334
|
+
number_of_intervals = 1440 / @data_details.last[:interval_length]
|
335
|
+
intervals_offset = number_of_intervals + 2
|
336
|
+
|
337
|
+
raise ArgumentError, 'RecordIndicator is not 300' if csv[0] != '300'
|
338
|
+
raise ArgumentError, 'IntervalDate is not valid' if csv[1].match(/\d{8}/).nil? || csv[1] != Time.parse("#{csv[1]}").strftime('%Y%m%d')
|
339
|
+
(2..(number_of_intervals+1)).each do |i|
|
340
|
+
raise ArgumentError, "Interval number #{i-1} is not valid" if csv[i].match(/\d+(\.\d+)?/).nil?
|
341
|
+
end
|
342
|
+
raise ArgumentError, 'QualityMethod is not valid' unless csv[intervals_offset + 0].class == String
|
343
|
+
raise ArgumentError, 'QualityMethod does not have valid length' unless [1,3].include?(csv[intervals_offset + 0].length)
|
344
|
+
raise ArgumentError, 'QualityMethod does not have valid QualityFlag' unless QUALITY_FLAGS.keys.include?(csv[intervals_offset + 0][0])
|
345
|
+
unless %w(A N V).include?(csv[intervals_offset + 0][0])
|
346
|
+
raise ArgumentError, 'QualityMethod does not have valid length' unless csv[intervals_offset + 0].length == 3
|
347
|
+
raise ArgumentError, 'QualityMethod does not have valid MethodFlag' unless METHOD_FLAGS.keys.include?(csv[intervals_offset + 0][1..2])
|
348
|
+
end
|
349
|
+
unless %w(A N E).include?(csv[intervals_offset + 0][0])
|
350
|
+
raise ArgumentError, 'ReasonCode is not valid' unless REASON_CODES.keys.include?(csv[intervals_offset + 1].to_i)
|
351
|
+
end
|
352
|
+
if !csv[intervals_offset + 1].nil? && csv[intervals_offset + 1].to_i == 0
|
353
|
+
raise ArgumentError, 'ReasonDescription is not valid' unless csv[intervals_offset + 2].class == String && csv[intervals_offset + 2].length > 0
|
354
|
+
end
|
355
|
+
if options[:strict]
|
356
|
+
raise ArgumentError, 'UpdateDateTime is not valid' if csv[intervals_offset + 3].match(/\d{14}/).nil? || csv[intervals_offset + 3] != Time.parse("#{csv[intervals_offset + 3]}").strftime('%Y%m%d%H%M%S')
|
357
|
+
unless csv[intervals_offset + 4].nil?
|
358
|
+
raise ArgumentError, 'MSATSLoadDateTime is not valid' if csv[intervals_offset + 4].match(/\d{14}/).nil? || csv[intervals_offset + 4] != Time.parse("#{csv[intervals_offset + 4]}").strftime('%Y%m%d%H%M%S')
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
base_interval = { :data_details => @data_details.last, :datetime => Time.parse("#{csv[1]}000000+1000"), :value => nil, :flag => nil}
|
363
|
+
intervals = []
|
364
|
+
(2..(number_of_intervals+1)).each do |i|
|
365
|
+
interval = base_interval.dup
|
366
|
+
interval[:datetime] += (i-1) * interval[:data_details][:interval_length] * 60
|
367
|
+
interval[:value] = csv[i].to_f
|
368
|
+
intervals << interval
|
369
|
+
end
|
370
|
+
@interval_data += intervals
|
371
|
+
intervals
|
372
|
+
end
|
373
|
+
|
374
|
+
# @param line [String] A single line in string format
|
375
|
+
# @return [Hash] the line parsed into a hash of information
|
376
|
+
def parse_nem12_400(line)
|
377
|
+
csv = line.parse_csv
|
378
|
+
raise ArgumentError, 'RecordIndicator is not 400' if csv[0] != '400'
|
379
|
+
raise ArgumentError, 'StartInterval is not valid' if csv[1].match(/^\d+$/).nil?
|
380
|
+
raise ArgumentError, 'EndInterval is not valid' if csv[2].match(/^\d+$/).nil?
|
381
|
+
raise ArgumentError, 'QualityMethod is not valid' if csv[3].match(/^([AN]|([AEFNSV]\d{2}))$/).nil?
|
382
|
+
# raise ArgumentError, 'ReasonCode is not valid' if (csv[4].nil? && csv[3].match(/^ANE/)) || csv[4].match(/^\d{3}?$/) || csv[3].match(/^ANE/)
|
383
|
+
# raise ArgumentError, 'ReasonDescription is not valid' if (csv[4].nil? && csv[3].match(/^ANE/)) || ( csv[5].match(/^$/) && csv[4].match(/^0$/) )
|
384
|
+
|
385
|
+
interval_events = []
|
386
|
+
|
387
|
+
# Only need to update flags for EFSV
|
388
|
+
unless %w(A N).include?csv[3]
|
389
|
+
number_of_intervals = 1440 / @data_details.last[:interval_length]
|
390
|
+
interval_start_point = @interval_data.length - number_of_intervals
|
391
|
+
|
392
|
+
# For each of these
|
393
|
+
base_interval_event = { datetime: nil, quality_method: csv[3], reason_code: csv[4], reason_description: csv[5] }
|
394
|
+
|
395
|
+
# Interval Numbers are 1-indexed
|
396
|
+
((csv[1].to_i)..(csv[2].to_i)).each do |i|
|
397
|
+
interval_event = base_interval_event.dup
|
398
|
+
interval_event[:datetime] = @interval_data[interval_start_point + (i-1)][:datetime]
|
399
|
+
interval_events << interval_event
|
400
|
+
|
401
|
+
method_flag = nil
|
402
|
+
unless (quality_method = interval_event[:quality_method].match(/(\d+)/)[1]).nil?
|
403
|
+
method_flag = METHOD_FLAGS[quality_method][:short_descriptor]
|
404
|
+
end
|
405
|
+
reason_code = nil
|
406
|
+
unless (reason_code = interval_event[:reason_code]).nil?
|
407
|
+
reason_code = REASON_CODES[reason_code.to_i]
|
408
|
+
end
|
409
|
+
|
410
|
+
case csv[3][0]
|
411
|
+
when 'E'
|
412
|
+
@interval_data[interval_start_point + (i-1)][:flag] = ['Estimate',method_flag,reason_code].compact.join(' - ')
|
413
|
+
when 'F'
|
414
|
+
@interval_data[interval_start_point + (i-1)][:flag] = nil
|
415
|
+
when 'S'
|
416
|
+
@interval_data[interval_start_point + (i-1)][:flag] = ['Substitute',method_flag,reason_code].compact.join(' - ')
|
417
|
+
end
|
418
|
+
end
|
419
|
+
@interval_events += interval_events
|
420
|
+
end
|
421
|
+
interval_events
|
422
|
+
end
|
423
|
+
|
424
|
+
# @param line [String] A single line in string format
|
425
|
+
# @return [Hash] the line parsed into a hash of information
|
426
|
+
def parse_nem12_500(line,options={})
|
427
|
+
end
|
428
|
+
|
429
|
+
# @param line [String] A single line in string format
|
430
|
+
# @return [Hash] the line parsed into a hash of information
|
431
|
+
def parse_nem12_900(line,options={})
|
432
|
+
end
|
433
|
+
|
434
|
+
# @param nmi [String] a NMI that is to be checked for validity
|
435
|
+
# @return [Boolean] determines if the NMI is valid
|
436
|
+
def self.valid_nmi?(nmi)
|
437
|
+
(nmi.class == String && nmi.length == 10 && !nmi.match(/^[A-Z1-9][A-Z0-9]{9}$/).nil?)
|
438
|
+
end
|
439
|
+
|
440
|
+
# @param path_to_file [String] the path to a file
|
441
|
+
# @return [] NEM12 object
|
442
|
+
def self.parse_nem12_file(path_to_file, strict = false)
|
443
|
+
parse_nem12(File.read(path_to_file),strict)
|
444
|
+
end
|
445
|
+
|
446
|
+
# @return [Array] array of a NEM12 file a given Meter + Data Stream for easy reading
|
447
|
+
def to_a
|
448
|
+
values = @interval_data.map{|d| [d[:data_details][:nmi],d[:data_details][:nmi_suffix].upcase,d[:data_details][:uom],d[:datetime],d[:value]]}
|
449
|
+
values
|
450
|
+
end
|
451
|
+
|
452
|
+
# @return [Array] CSV of a NEM12 file a given Meter + Data Stream for easy reading
|
453
|
+
def to_csv
|
454
|
+
headers = ['nmi','suffix','units','datetime','value']
|
455
|
+
([headers]+self.to_a.map{|row| row[3]=row[3].strftime("%Y%m%d%H%M%S%z"); row}).map{|row| row.join(',')}.join("\n")
|
456
|
+
end
|
457
|
+
|
458
|
+
# @param contents [String] the path to a file
|
459
|
+
# @return [Array[AEMO::NEM12]] An array of NEM12 objects
|
460
|
+
def self.parse_nem12(contents, strict=false)
|
461
|
+
file_contents = contents.gsub(/\r/,"\n").gsub(/\n\n/,"\n").split("\n").delete_if{|line| line.empty? }
|
462
|
+
raise ArgumentError, 'First row should be have a RecordIndicator of 100 and be of type Header Record' unless file_contents.first.parse_csv[0] == '100'
|
463
|
+
|
464
|
+
nem12s = []
|
465
|
+
nem12_100 = AEMO::NEM12.parse_nem12_100(file_contents.first,:strict => strict)
|
466
|
+
nem12 = nil
|
467
|
+
file_contents.each do |line|
|
468
|
+
case line[0..2].to_i
|
469
|
+
when 200
|
470
|
+
nem12s << AEMO::NEM12.new('')
|
471
|
+
nem12s.last.parse_nem12_200(line)
|
472
|
+
when 300
|
473
|
+
nem12s.last.parse_nem12_300(line)
|
474
|
+
when 400
|
475
|
+
nem12s.last.parse_nem12_400(line)
|
476
|
+
# when 500
|
477
|
+
# nem12s.last.parse_nem12_500(line)
|
478
|
+
# when 900
|
479
|
+
# nem12s.last.parse_nem12_900(line)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
# Return the array of NEM12 groups
|
483
|
+
nem12s
|
484
|
+
end
|
485
|
+
|
486
|
+
end
|
487
|
+
end
|