1
+ using System . Globalization ;
2
+ using System . Runtime . CompilerServices ;
3
+ using AEMO . MDFF . Abstractions ;
4
+ using Sylvan . Data . Csv ;
5
+
6
+ namespace AEMO . MDFF . NEM12 ;
7
+
8
+ public class Nem12Reader ( ) : IMdffReader
9
+ {
10
+ private readonly Dictionary < string , int > _nmiIntervalLengths = new ( ) ;
11
+
12
+ public async IAsyncEnumerable < IMdffRecord > ReadAsync ( Stream stream , [ EnumeratorCancellation ] CancellationToken ct )
13
+ {
14
+ using var sr = new StreamReader ( stream ) ;
15
+ await using var csv = await CsvDataReader . CreateAsync ( sr ,
16
+ new CsvDataReaderOptions ( )
17
+ {
18
+ HasHeaders = false ,
19
+ } , ct ) ;
20
+
21
+ bool headerFound = false ;
22
+ bool endFound = false ;
23
+ string currentNMI = null ;
24
+
25
+ while ( await csv . ReadAsync ( ct ) && ! endFound )
26
+ {
27
+ var recordIndicator = csv . GetString ( 0 ) ;
28
+ switch ( recordIndicator )
29
+ {
30
+ case "100" :
31
+ if ( headerFound )
32
+ throw new InvalidDataException ( "Multiple header records found" ) ;
33
+ var hr = ParseHeaderRecord ( csv ) ;
34
+ headerFound = true ;
35
+ yield return hr ;
36
+ break ;
37
+ case "200" :
38
+ if ( ! headerFound )
39
+ throw new InvalidDataException ( "Data record found before header" ) ;
40
+ var ddr = ParseNMIDataDetailsRecord ( csv ) ;
41
+ currentNMI = ddr . NMI ;
42
+ _nmiIntervalLengths [ currentNMI ] = ddr . IntervalLength ;
43
+ yield return ddr ;
44
+ break ;
45
+ case "300" :
46
+ if ( ! headerFound )
47
+ throw new InvalidDataException ( "Data record found before header" ) ;
48
+ var idr = ParseIntervalDataRecord ( csv , currentNMI ) ;
49
+ yield return idr ;
50
+ break ;
51
+ case "400" :
52
+ if ( ! headerFound )
53
+ throw new InvalidDataException ( "Data record found before header" ) ;
54
+ break ;
55
+ case "500" :
56
+ if ( ! headerFound )
57
+ throw new InvalidDataException ( "Data record found before header" ) ;
58
+ break ;
59
+ case "900" :
60
+ var er = new EndRecord ( ) ;
61
+ endFound = true ;
62
+ yield return er ;
63
+ break ;
64
+ default :
65
+ throw new InvalidDataException ( $ "Unsupported record indicator: { recordIndicator } ") ;
66
+ }
67
+ }
68
+ if ( ! headerFound )
69
+ throw new InvalidDataException ( "No header record found" ) ;
70
+ if ( ! endFound )
71
+ throw new InvalidDataException ( "No end record found" ) ;
72
+ }
73
+
74
+ private HeaderRecord ParseHeaderRecord ( CsvDataReader csv )
75
+ {
76
+ var dateTimeString = csv . GetString ( 2 ) ;
77
+ var dateTime = DateTime . ParseExact ( dateTimeString , "yyyyMMddHHmm" , CultureInfo . InvariantCulture ) ;
78
+
79
+ return new HeaderRecord ( )
80
+ {
81
+ VersionHeader = csv . GetString ( 1 ) ,
82
+ DateTime = dateTime ,
83
+ FromParticipant = csv . GetString ( 3 ) ,
84
+ ToParticipant = csv . GetString ( 4 )
85
+ } ;
86
+ }
87
+ private NMIDataDetailsRecord ParseNMIDataDetailsRecord ( CsvDataReader csv )
88
+ {
89
+ var dateString = csv . GetString ( 9 ) ;
90
+ var date = DateOnly . ParseExact ( dateString , "yyyyMMdd" , CultureInfo . InvariantCulture ) ;
91
+
92
+ return new NMIDataDetailsRecord
93
+ {
94
+ NMI = csv . GetString ( 1 ) ,
95
+ NMIConfiguration = csv . GetString ( 2 ) ,
96
+ RegisterId = csv . GetString ( 3 ) ,
97
+ NMISuffix = csv . GetString ( 4 ) ,
98
+ MDMDataStreamIdentifier = csv . GetString ( 5 ) ,
99
+ MeterSerialNumber = csv . GetString ( 6 ) ,
100
+ UOM = csv . GetString ( 7 ) ,
101
+ IntervalLength = csv . GetInt32 ( 8 ) ,
102
+ NextScheduledReadDate = date
103
+ } ;
104
+ }
105
+
106
+ private IntervalDataRecord ParseIntervalDataRecord ( CsvDataReader csv , string currentNMI )
107
+ {
108
+ var dateString = csv . GetString ( 1 ) ;
109
+ var date = DateOnly . ParseExact ( dateString , "yyyyMMdd" , CultureInfo . InvariantCulture ) ;
110
+ int intervalLength = _nmiIntervalLengths [ currentNMI ] ;
111
+ int expectedIntervals = 1440 / intervalLength ; // 1440 minutes in a day
112
+
113
+ return new IntervalDataRecord
114
+ {
115
+ IntervalDate = date
116
+ // TODO: parse interval values
117
+ } ;
118
+ }
119
+
120
+ }
0 commit comments