aemo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|