aemo 0.1.27 → 0.1.28
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 +4 -4
- data/lib/aemo/dispatchable.rb +4 -4
- data/lib/aemo/market.rb +30 -27
- data/lib/aemo/market/interval.rb +27 -23
- data/lib/aemo/msats.rb +218 -210
- data/lib/aemo/nem12.rb +165 -160
- data/lib/aemo/nem13.rb +1 -2
- data/lib/aemo/nmi.rb +91 -85
- data/lib/aemo/region.rb +12 -11
- data/lib/aemo/version.rb +3 -3
- data/lib/data/xml_to_json.rb +62 -0
- data/spec/aemo_spec.rb +1 -2
- data/spec/lib/aemo/market/interval_spec.rb +13 -13
- data/spec/lib/aemo/market_spec.rb +8 -6
- data/spec/lib/aemo/msats_spec.rb +14 -15
- data/spec/lib/aemo/nem12_spec.rb +8 -10
- data/spec/lib/aemo/nmi_spec.rb +79 -71
- data/spec/lib/aemo/region_spec.rb +5 -6
- data/spec/spec_helper.rb +38 -35
- metadata +16 -2
- data/lib/data/xml-to-json.rb +0 -61
data/lib/aemo/nem13.rb
CHANGED
data/lib/aemo/nmi.rb
CHANGED
@@ -15,9 +15,10 @@ module AEMO
|
|
15
15
|
'VIC' => 'Victoria',
|
16
16
|
'WA' => 'Western Australia',
|
17
17
|
'NT' => 'Northern Territory'
|
18
|
-
}
|
19
|
-
|
20
|
-
#
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
# NMI_ALLOCATIONS as per AEMO Documentation at http://aemo.com.au/Electricity/Policies-and-Procedures/Retail-and-Metering/~/media/Files/Other/Retail% 20and% 20Metering/NMI_Allocation_List_v7_June_2012.ashx
|
21
|
+
# Last accessed 2016-05-15
|
21
22
|
NMI_ALLOCATIONS = {
|
22
23
|
'ACTEWP' => {
|
23
24
|
title: 'Actew Distribution Ltd and Jemena Networks (ACT) Pty Ltd',
|
@@ -192,7 +193,7 @@ module AEMO
|
|
192
193
|
state: 'TAS',
|
193
194
|
type: 'electricity',
|
194
195
|
includes: [
|
195
|
-
/^(T[A-HJ-NP-Z\d]{3}W[A-HJ-NP-Z\d]{5})
|
196
|
+
/^(T[A-HJ-NP-Z\d]{3}W[A-HJ-NP-Z\d]{5})$/
|
196
197
|
],
|
197
198
|
excludes: [
|
198
199
|
]
|
@@ -395,17 +396,17 @@ module AEMO
|
|
395
396
|
excludes: [
|
396
397
|
]
|
397
398
|
}
|
398
|
-
}
|
399
|
+
}.freeze
|
399
400
|
# Transmission Node Identifier Codes are loaded from a json file
|
400
401
|
# Obtained from http://www.nemweb.com.au/
|
401
402
|
#
|
402
403
|
# See /lib/data for further data manipulation required
|
403
|
-
TNI_CODES = JSON.parse(File.read(File.join(File.dirname(__FILE__),'..','data','aemo-tni.json')))
|
404
|
+
TNI_CODES = JSON.parse(File.read(File.join(File.dirname(__FILE__), '..', 'data', 'aemo-tni.json'))).freeze
|
404
405
|
# Distribution Loss Factor Codes are loaded from a json file
|
405
|
-
# Obtained from MSATS, matching to DNSP from file http://www.aemo.com.au/Electricity/Market-Operations/Loss-Factors-and-Regional-Boundaries/~/media/Files/Other/loss%20factors/DLF_FINAL_V2_2014_2015.ashx
|
406
|
+
# Obtained from MSATS, matching to DNSP from file http://www.aemo.com.au/Electricity/Market-Operations/Loss-Factors-and-Regional-Boundaries/~/media/Files/Other/loss% 20factors/DLF_FINAL_V2_2014_2015.ashx
|
406
407
|
# Last accessed 2015-02-06
|
407
408
|
# See /lib/data for further data manipulation required
|
408
|
-
DLF_CODES = JSON.parse(File.read(File.join(File.dirname(__FILE__),'..','data','aemo-dlf.json')))
|
409
|
+
DLF_CODES = JSON.parse(File.read(File.join(File.dirname(__FILE__), '..', 'data', 'aemo-dlf.json'))).freeze
|
409
410
|
|
410
411
|
# [String] National Meter Identifier
|
411
412
|
@nmi = nil
|
@@ -424,20 +425,24 @@ module AEMO
|
|
424
425
|
|
425
426
|
attr_accessor :nmi, :msats_detail, :tni, :dlf, :customer_classification_code, :customer_threshold_code, :jurisdiction_code, :classification_code, :status, :address, :meters, :roles, :data_streams
|
426
427
|
|
427
|
-
# Initialize a
|
428
|
+
# Initialize a NMI file
|
428
429
|
#
|
429
430
|
# @param nmi [String] the National Meter Identifier (NMI)
|
430
431
|
# @param options [Hash] a hash of options
|
432
|
+
# @option options [Hash] :msats_detail MSATS details as per #parse_msats_detail requirements
|
431
433
|
# @return [AEMO::NMI] an instance of AEMO::NMI is returned
|
432
|
-
def initialize(nmi,options={})
|
433
|
-
raise ArgumentError
|
434
|
-
raise ArgumentError
|
435
|
-
raise ArgumentError
|
434
|
+
def initialize(nmi, options = {})
|
435
|
+
raise ArgumentError, 'NMI is not a string' unless nmi.is_a?(String)
|
436
|
+
raise ArgumentError, 'NMI is not 10 characters' unless nmi.length == 10
|
437
|
+
raise ArgumentError, 'NMI is not constructed with valid characters' unless AEMO::NMI.valid_nmi?(nmi)
|
438
|
+
|
439
|
+
@nmi = nmi
|
440
|
+
@meters = []
|
441
|
+
@roles = {}
|
442
|
+
@data_streams = []
|
443
|
+
@msats_detail = options[:msats_detail]
|
436
444
|
|
437
|
-
@
|
438
|
-
@meters = []
|
439
|
-
@roles = {}
|
440
|
-
@data_streams = []
|
445
|
+
parse_msats_detail unless @msats_detail.nil?
|
441
446
|
end
|
442
447
|
|
443
448
|
# A function to validate the instance's nmi value
|
@@ -459,7 +464,7 @@ module AEMO
|
|
459
464
|
# @param checksum_value [Integer] the checksum value to check against the current National Meter Identifier's checksum value
|
460
465
|
# @return [Boolean] whether or not the checksum is valid
|
461
466
|
def valid_checksum?(checksum_value)
|
462
|
-
checksum_value ==
|
467
|
+
checksum_value == checksum
|
463
468
|
end
|
464
469
|
|
465
470
|
# Checksum is a function to calculate the checksum value for a given National Meter Identifier
|
@@ -469,10 +474,8 @@ module AEMO
|
|
469
474
|
summation = 0
|
470
475
|
@nmi.reverse.split(//).each_index do |i|
|
471
476
|
value = nmi[nmi.length - i - 1].ord
|
472
|
-
|
473
|
-
|
474
|
-
end
|
475
|
-
value = value.to_s.split(//).map{|i| i.to_i}.reduce(:+)
|
477
|
+
value *= 2 if i.even?
|
478
|
+
value = value.to_s.split(//).map(&:to_i).reduce(:+)
|
476
479
|
summation += value
|
477
480
|
end
|
478
481
|
checksum = (10 - (summation % 10)) % 10
|
@@ -482,18 +485,26 @@ module AEMO
|
|
482
485
|
# Provided MSATS is configured, gets the MSATS data for the NMI
|
483
486
|
#
|
484
487
|
# @return [Hash] MSATS NMI Detail data
|
485
|
-
def raw_msats_nmi_detail(options={})
|
486
|
-
raise ArgumentError,
|
487
|
-
|
488
|
-
AEMO::MSATS.nmi_detail(@nmi,options)
|
488
|
+
def raw_msats_nmi_detail(options = {})
|
489
|
+
raise ArgumentError,
|
490
|
+
'MSATS has no authentication credentials' unless AEMO::MSATS.can_authenticate?
|
491
|
+
AEMO::MSATS.nmi_detail(@nmi, options)
|
489
492
|
end
|
490
493
|
|
491
494
|
# Provided MSATS is configured, uses the raw MSATS data to augment NMI information
|
492
495
|
#
|
493
496
|
# @return [self] returns self
|
494
|
-
def update_from_msats!(options={})
|
497
|
+
def update_from_msats!(options = {})
|
495
498
|
# Update local cache
|
496
499
|
@msats_detail = raw_msats_nmi_detail(options)
|
500
|
+
parse_msats_detail
|
501
|
+
self
|
502
|
+
end
|
503
|
+
|
504
|
+
# Turns raw MSATS junk into useful things
|
505
|
+
#
|
506
|
+
# @return [self] returns self
|
507
|
+
def parse_msats_detail
|
497
508
|
# Set the details if there are any
|
498
509
|
unless @msats_detail['MasterData'].nil?
|
499
510
|
@tni = @msats_detail['MasterData']['TransmissionNodeIdentifier']
|
@@ -512,7 +523,7 @@ module AEMO
|
|
512
523
|
unless @msats_detail['MeterRegister'].nil?
|
513
524
|
meters = @msats_detail['MeterRegister']['Meter']
|
514
525
|
meters = [meters] if meters.is_a?(Hash)
|
515
|
-
meters.select{|x| !x['Status'].nil? }.each do |meter|
|
526
|
+
meters.select { |x| !x['Status'].nil? }.each do |meter|
|
516
527
|
@meters << OpenStruct.new(
|
517
528
|
status: meter['Status'],
|
518
529
|
installation_type_code: meter['InstallationTypeCode'],
|
@@ -522,9 +533,9 @@ module AEMO
|
|
522
533
|
serial_number: meter['SerialNumber']
|
523
534
|
)
|
524
535
|
end
|
525
|
-
meters.select{|x| x['Status'].nil? }.each do |registers|
|
526
|
-
m = @meters.find{|x| x.serial_number == registers['SerialNumber']}
|
527
|
-
m.registers <<
|
536
|
+
meters.select { |x| x['Status'].nil? }.each do |registers|
|
537
|
+
m = @meters.find { |x| x.serial_number == registers['SerialNumber'] }
|
538
|
+
m.registers << OpenStruct.new(
|
528
539
|
controlled_load: (registers['RegisterConfiguration']['Register']['ControlledLoad'] == 'Y'),
|
529
540
|
dial_format: registers['RegisterConfiguration']['Register']['DialFormat'],
|
530
541
|
multiplier: registers['RegisterConfiguration']['Register']['Multiplier'],
|
@@ -549,7 +560,13 @@ module AEMO
|
|
549
560
|
data_streams = @msats_detail['DataStreams']['DataStream']
|
550
561
|
data_streams = [data_streams] if data_streams.is_a?(Hash) # Deal with issue of only one existing
|
551
562
|
data_streams.each do |stream|
|
552
|
-
@data_streams << OpenStruct.new(
|
563
|
+
@data_streams << OpenStruct.new(
|
564
|
+
suffix: stream['Suffix'],
|
565
|
+
profile_name: stream['ProfileName'],
|
566
|
+
averaged_daily_load: stream['AveragedDailyLoad'],
|
567
|
+
data_stream_type: stream['DataStreamType'],
|
568
|
+
status: stream['Status']
|
569
|
+
)
|
553
570
|
end
|
554
571
|
end
|
555
572
|
self
|
@@ -561,7 +578,12 @@ module AEMO
|
|
561
578
|
def friendly_address
|
562
579
|
friendly_address = ''
|
563
580
|
if @address.is_a?(Hash)
|
564
|
-
friendly_address = @address.values.map
|
581
|
+
friendly_address = @address.values.map do |x|
|
582
|
+
if x.is_a?(Hash)
|
583
|
+
x = x.values.map { |y| y.is_a?(Hash) ? y.values.join(' ') : y }.join(' ')
|
584
|
+
end
|
585
|
+
x
|
586
|
+
end.join(', ')
|
565
587
|
end
|
566
588
|
friendly_address
|
567
589
|
end
|
@@ -571,7 +593,7 @@ module AEMO
|
|
571
593
|
# @param status [String] the stateus [C|R]
|
572
594
|
# @return [Array<OpenStruct>] Returns an array of OpenStructs for Meters with the status provided
|
573
595
|
def meters_by_status(status = 'C')
|
574
|
-
@meters.select{|x| x.status ==
|
596
|
+
@meters.select { |x| x.status == status.to_s }
|
575
597
|
end
|
576
598
|
|
577
599
|
# Returns the data_stream OpenStructs for the requested status (A/I)
|
@@ -579,14 +601,14 @@ module AEMO
|
|
579
601
|
# @param status [String] the stateus [A|I]
|
580
602
|
# @return [Array<OpenStruct>] Returns an array of OpenStructs for the current Meters
|
581
603
|
def data_streams_by_status(status = 'A')
|
582
|
-
@data_streams.select{|x| x.status ==
|
604
|
+
@data_streams.select { |x| x.status == status.to_s }
|
583
605
|
end
|
584
606
|
|
585
607
|
# The current daily load
|
586
608
|
#
|
587
609
|
# @return [Integer] the current daily load for the meter
|
588
610
|
def current_daily_load
|
589
|
-
data_streams_by_status
|
611
|
+
data_streams_by_status.map { |x| x.averaged_daily_load.to_i }.inject(0, :+)
|
590
612
|
end
|
591
613
|
|
592
614
|
# A function to validate the NMI provided
|
@@ -602,7 +624,7 @@ module AEMO
|
|
602
624
|
# @param nmi [String] the NMI to check the checksum against
|
603
625
|
# @param checksum_value [Integer] the checksum value to check against the current National Meter Identifier's checksum value
|
604
626
|
# @return [Boolean] whether or not the checksum is valid
|
605
|
-
def self.valid_checksum?(nmi,checksum_value)
|
627
|
+
def self.valid_checksum?(nmi, checksum_value)
|
606
628
|
nmi = AEMO::NMI.new(nmi)
|
607
629
|
nmi.valid_checksum?(checksum_value)
|
608
630
|
end
|
@@ -626,14 +648,14 @@ module AEMO
|
|
626
648
|
|
627
649
|
# A function to return the distribution loss factor value for a given date
|
628
650
|
#
|
629
|
-
# @param [DateTime,Time] datetime the date for the distribution loss factor value
|
630
|
-
# @return [nil,float] the distribution loss factor value
|
651
|
+
# @param [DateTime, Time] datetime the date for the distribution loss factor value
|
652
|
+
# @return [nil, float] the distribution loss factor value
|
631
653
|
def dlfc_value(datetime = DateTime.now)
|
632
654
|
raise 'No DLF set, ensure that you have set the value either via the update_from_msats! function or manually' if @dlf.nil?
|
633
655
|
raise 'DLF is invalid' unless DLF_CODES.keys.include?(@dlf)
|
634
|
-
raise 'Invalid date' unless [DateTime,Time].include?(datetime.class)
|
635
|
-
possible_values = DLF_CODES[@dlf].select{|x| DateTime.parse(x['FromDate']) <= datetime && datetime <= DateTime.parse(x['ToDate']) }
|
636
|
-
if possible_values.
|
656
|
+
raise 'Invalid date' unless [DateTime, Time].include?(datetime.class)
|
657
|
+
possible_values = DLF_CODES[@dlf].select { |x| DateTime.parse(x['FromDate']) <= datetime && datetime <= DateTime.parse(x['ToDate']) }
|
658
|
+
if possible_values.empty?
|
637
659
|
nil
|
638
660
|
else
|
639
661
|
possible_values.first['Value'].to_f
|
@@ -642,66 +664,50 @@ module AEMO
|
|
642
664
|
|
643
665
|
# A function to return the distribution loss factor value for a given date
|
644
666
|
#
|
645
|
-
# @param [DateTime,Time] start the date for the distribution loss factor value
|
646
|
-
# @param [DateTime,Time] finish the date for the distribution loss factor value
|
647
|
-
# @return [Array(Hash)] array of hashes of start,finish and value
|
648
|
-
def dlfc_values(start = DateTime.now, finish=DateTime.now)
|
667
|
+
# @param [DateTime, Time] start the date for the distribution loss factor value
|
668
|
+
# @param [DateTime, Time] finish the date for the distribution loss factor value
|
669
|
+
# @return [Array(Hash)] array of hashes of start, finish and value
|
670
|
+
def dlfc_values(start = DateTime.now, finish = DateTime.now)
|
649
671
|
raise 'No DLF set, ensure that you have set the value either via the update_from_msats! function or manually' if @dlf.nil?
|
650
672
|
raise 'DLF is invalid' unless DLF_CODES.keys.include?(@dlf)
|
651
|
-
raise 'Invalid start' unless [DateTime,Time].include?(start.class)
|
652
|
-
raise 'Invalid finish' unless [DateTime,Time].include?(finish.class)
|
673
|
+
raise 'Invalid start' unless [DateTime, Time].include?(start.class)
|
674
|
+
raise 'Invalid finish' unless [DateTime, Time].include?(finish.class)
|
653
675
|
raise 'start cannot be after finish' if start > finish
|
654
|
-
DLF_CODES[@dlf].reject{|x| start > DateTime.parse(x['ToDate']) || finish < DateTime.parse(x['FromDate']) }
|
676
|
+
DLF_CODES[@dlf].reject { |x| start > DateTime.parse(x['ToDate']) || finish < DateTime.parse(x['FromDate']) }
|
677
|
+
.map { |x| { 'start' => x['FromDate'], 'finish' => x['ToDate'], 'value' => x['Value'].to_f } }
|
655
678
|
end
|
656
679
|
|
657
680
|
# A function to return the transmission node identifier loss factor value for a given date
|
658
681
|
#
|
659
|
-
# @param [DateTime,Time] datetime the date for the distribution loss factor value
|
660
|
-
# @return [nil,float] the transmission node identifier loss factor value
|
682
|
+
# @param [DateTime, Time] datetime the date for the distribution loss factor value
|
683
|
+
# @return [nil, float] the transmission node identifier loss factor value
|
661
684
|
def tni_value(datetime = DateTime.now)
|
662
685
|
raise 'No TNI set, ensure that you have set the value either via the update_from_msats! function or manually' if @tni.nil?
|
663
686
|
raise 'TNI is invalid' unless TNI_CODES.keys.include?(@tni)
|
664
|
-
raise 'Invalid date' unless [DateTime,Time].include?(datetime.class)
|
665
|
-
possible_values = TNI_CODES[@tni].select{|x| DateTime.parse(x['FromDate']) <= datetime && datetime <= DateTime.parse(x['ToDate']) }
|
666
|
-
if possible_values.
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
if possible_values.length == 0
|
671
|
-
nil
|
672
|
-
else
|
673
|
-
possible_values.first['value'].to_f
|
674
|
-
end
|
675
|
-
end
|
687
|
+
raise 'Invalid date' unless [DateTime, Time].include?(datetime.class)
|
688
|
+
possible_values = TNI_CODES[@tni].select { |x| DateTime.parse(x['FromDate']) <= datetime && datetime <= DateTime.parse(x['ToDate']) }
|
689
|
+
return nil if possible_values.empty?
|
690
|
+
possible_values = possible_values.first['mlf_data']['loss_factors'].select { |x| DateTime.parse(x['start']) <= datetime && datetime <= DateTime.parse(x['finish']) }
|
691
|
+
return nil if possible_values.empty?
|
692
|
+
possible_values.first['value'].to_f
|
676
693
|
end
|
677
694
|
|
678
695
|
# A function to return the transmission node identifier loss factor value for a given date
|
679
696
|
#
|
680
|
-
# @param [DateTime,Time] start the date for the distribution loss factor value
|
681
|
-
# @param [DateTime,Time] finish the date for the distribution loss factor value
|
682
|
-
# @return [Array(Hash)] array of hashes of start,finish and value
|
683
|
-
def tni_values(start=DateTime.now,finish=DateTime.now)
|
697
|
+
# @param [DateTime, Time] start the date for the distribution loss factor value
|
698
|
+
# @param [DateTime, Time] finish the date for the distribution loss factor value
|
699
|
+
# @return [Array(Hash)] array of hashes of start, finish and value
|
700
|
+
def tni_values(start = DateTime.now, finish = DateTime.now)
|
684
701
|
raise 'No TNI set, ensure that you have set the value either via the update_from_msats! function or manually' if @tni.nil?
|
685
702
|
raise 'TNI is invalid' unless TNI_CODES.keys.include?(@tni)
|
686
|
-
raise 'Invalid start' unless [DateTime,Time].include?(start.class)
|
687
|
-
raise 'Invalid finish' unless [DateTime,Time].include?(finish.class)
|
703
|
+
raise 'Invalid start' unless [DateTime, Time].include?(start.class)
|
704
|
+
raise 'Invalid finish' unless [DateTime, Time].include?(finish.class)
|
688
705
|
raise 'start cannot be after finish' if start > finish
|
689
|
-
possible_values = TNI_CODES[@tni]
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
possible_values.each do |possible_value|
|
695
|
-
tni_values += possible_value['mlf_data']['loss_factors'].reject{|x| start > DateTime.parse(x['finish']) || finish < DateTime.parse(x['start']) }
|
696
|
-
end
|
697
|
-
end
|
698
|
-
tni_values
|
706
|
+
possible_values = TNI_CODES[@tni]
|
707
|
+
.reject { |x| start > DateTime.parse(x['ToDate']) || finish < DateTime.parse(x['FromDate']) }
|
708
|
+
.reject { |x| start > DateTime.parse(x['finish']) || finish < DateTime.parse(x['start']) }
|
709
|
+
return nil if possible_values.empty?
|
710
|
+
possible_values.map { |x| x['mlf_data']['loss_factors'] }
|
699
711
|
end
|
700
|
-
|
701
|
-
# ######### #
|
702
|
-
protected
|
703
|
-
# ######### #
|
704
|
-
|
705
712
|
end
|
706
|
-
|
707
713
|
end
|
data/lib/aemo/region.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
module AEMO
|
2
|
+
# AEMO::Region
|
3
|
+
#
|
4
|
+
# @author Joel Courtney
|
5
|
+
# @abstract
|
6
|
+
# @since 0.1.0
|
2
7
|
class Region
|
3
|
-
|
4
8
|
# Regions under juristiction
|
5
9
|
REGIONS = {
|
6
10
|
'ACT' => 'Australian Capital Territory',
|
@@ -11,12 +15,12 @@ module AEMO
|
|
11
15
|
'VIC' => 'Victoria',
|
12
16
|
'NT' => 'Northern Territory',
|
13
17
|
'WA' => 'Western Australia'
|
14
|
-
}
|
18
|
+
}.freeze
|
15
19
|
|
16
20
|
attr_accessor :region
|
17
21
|
|
18
22
|
def initialize(region)
|
19
|
-
raise ArgumentError
|
23
|
+
raise ArgumentError, "Region '#{region}' is not valid." unless valid_region?(region)
|
20
24
|
@region = region
|
21
25
|
@current_trading = []
|
22
26
|
@current_dispatch = []
|
@@ -35,30 +39,27 @@ module AEMO
|
|
35
39
|
end
|
36
40
|
|
37
41
|
def current_dispatch
|
38
|
-
if @current_dispatch.
|
42
|
+
if @current_dispatch.empty? || @current_dispatch.last.datetime != (Time.now - Time.now.to_i % 300)
|
39
43
|
@current_dispatch = AEMO::Market.current_dispatch(@region)
|
40
44
|
end
|
41
45
|
@current_dispatch
|
42
46
|
end
|
43
47
|
|
44
48
|
def current_trading
|
45
|
-
if @current_trading.
|
49
|
+
if @current_trading.empty? || @current_trading.reject { |i| i.period_type != 'TRADE' }.last.datetime != (Time.now - Time.now.to_i % 300)
|
46
50
|
@current_trading = AEMO::Market.current_trading(@region)
|
47
51
|
end
|
48
52
|
@current_trading
|
49
53
|
end
|
50
54
|
|
51
55
|
def self.all
|
52
|
-
REGIONS.keys.map{|k| AEMO::Region.new(k)}
|
56
|
+
REGIONS.keys.map { |k| AEMO::Region.new(k) }
|
53
57
|
end
|
54
58
|
|
55
|
-
|
56
|
-
protected
|
57
|
-
# ######### #
|
59
|
+
protected
|
58
60
|
|
59
|
-
def
|
61
|
+
def valid_region?(region)
|
60
62
|
REGIONS.keys.include?(region)
|
61
63
|
end
|
62
|
-
|
63
64
|
end
|
64
65
|
end
|
data/lib/aemo/version.rb
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
#
|
3
3
|
# Copyright 2014 Joel Courtney
|
4
4
|
#
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the 'License');
|
6
6
|
# you may not use this file except in compliance with the License.
|
7
7
|
# You may obtain a copy of the License at
|
8
8
|
#
|
9
9
|
# http://www.apache.org/licenses/LICENSE-2.0
|
10
10
|
#
|
11
11
|
# Unless required by applicable law or agreed to in writing, software
|
12
|
-
# distributed under the License is distributed on an
|
12
|
+
# distributed under the License is distributed on an 'AS IS' BASIS,
|
13
13
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
@@ -22,7 +22,7 @@
|
|
22
22
|
# @author Joel Courtney <euphemize@gmail.com>
|
23
23
|
module AEMO
|
24
24
|
# aemo version
|
25
|
-
VERSION = '0.1.
|
25
|
+
VERSION = '0.1.28'.freeze
|
26
26
|
|
27
27
|
# aemo version split amongst different revisions
|
28
28
|
MAJOR_VERSION, MINOR_VERSION, REVISION = VERSION.split('.').map(&:to_i)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'json'
|
3
|
+
require 'csv'
|
4
|
+
require 'active_support/all'
|
5
|
+
|
6
|
+
@path = Dir.pwd
|
7
|
+
@files = Dir.entries(@path).reject { |f| %w(. ..).include?(f) }
|
8
|
+
|
9
|
+
@mlf_data = {}
|
10
|
+
@dlf_data = {}
|
11
|
+
|
12
|
+
# Let's get the CSV Data first
|
13
|
+
|
14
|
+
# TNI to MLF
|
15
|
+
CSV.open(File.join(@path, 'tni-mlf-codes.csv'), headers: true, converters: :numeric).each do |row|
|
16
|
+
@mlf_data[row['TNI']] ||= { location: row['Location'], voltage: row['Voltage'], loss_factors: [] }
|
17
|
+
@mlf_data[row['TNI']][:loss_factors] << { start: DateTime.parse('2015-07-01T00:00:00+1000'), finish: DateTime.parse('2016-07-01T00:00:00+1000'), value: row['FY16'] }
|
18
|
+
@mlf_data[row['TNI']][:loss_factors] << { start: DateTime.parse('2014-07-01T00:00:00+1000'), finish: DateTime.parse('2015-07-01T00:00:00+1000'), value: row['FY15'] }
|
19
|
+
@mlf_data[row['TNI']][:loss_factors] << { start: DateTime.parse('2013-07-01T00:00:00+1000'), finish: DateTime.parse('2014-07-01T00:00:00+1000'), value: row['FY14'] }
|
20
|
+
end
|
21
|
+
|
22
|
+
# TNI to MLF
|
23
|
+
CSV.open(File.join(@path, 'aemo-dlf-dnsp.csv'), headers: true, converters: :numeric).each do |row|
|
24
|
+
@dlf_data[row['dlf_code']] ||= row['nsp_code']
|
25
|
+
end
|
26
|
+
|
27
|
+
# Now to create the DLF and TNI output JSON files for use
|
28
|
+
@files.select { |x| ['aemo-tni.xml', 'aemo-dlf.xml'].include?(x) }.each do |file|
|
29
|
+
output_file = file.gsub('.xml', '.json')
|
30
|
+
output_data = {}
|
31
|
+
open_file = File.open(File.join(@path, file))
|
32
|
+
xml = Nokogiri::XML(open_file) { |c| c.options = Nokogiri::XML::ParseOptions::NOBLANKS }
|
33
|
+
open_file.close
|
34
|
+
|
35
|
+
xml.xpath('//Row').each do |row|
|
36
|
+
row_children = row.children
|
37
|
+
code = row_children.find { |x| x.name == 'Code' }.children.first.text
|
38
|
+
output_data[code] ||= []
|
39
|
+
output_data_instance = {}
|
40
|
+
row_children.each do |row_child|
|
41
|
+
output_data_instance[row_child.name] = row_child.children.first.text
|
42
|
+
end
|
43
|
+
if file =~ /tni/
|
44
|
+
puts "output_data_instance: #{output_data_instance.inspect}"
|
45
|
+
output_data_instance[:mlf_data] = {}
|
46
|
+
unless @mlf_data[code].nil?
|
47
|
+
output_data_instance[:mlf_data] = @mlf_data[code].deep_dup
|
48
|
+
output_data_instance[:mlf_data][:loss_factors] = output_data_instance[:mlf_data][:loss_factors].reject do |x|
|
49
|
+
DateTime.parse(output_data_instance['ToDate']) < x[:start] || DateTime.parse(output_data_instance['FromDate']) >= x[:finish]
|
50
|
+
end
|
51
|
+
puts "output_data_instance[:mlf_data][:loss_factors]: #{output_data_instance[:mlf_data][:loss_factors].inspect}"
|
52
|
+
end
|
53
|
+
elsif file =~ /dlf/
|
54
|
+
output_data_instance[:nsp_code] = @dlf_data[code]
|
55
|
+
end
|
56
|
+
output_data[code] << output_data_instance
|
57
|
+
end
|
58
|
+
|
59
|
+
File.open(File.join(@path, output_file), 'w') do |write_file|
|
60
|
+
write_file.write(output_data.to_json)
|
61
|
+
end
|
62
|
+
end
|