Skip to content

Commit e6340cf

Browse files
authored
feat: add NEM12 reader and header, 200, 300, and end records (#6)
1 parent f0dc578 commit e6340cf

File tree

6 files changed

+183
-0
lines changed

6 files changed

+183
-0
lines changed

src/AEMO.MDFF/EndRecord.cs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using AEMO.MDFF.Abstractions;
2+
3+
namespace AEMO.MDFF;
4+
5+
public sealed class EndRecord : IMdffRecord
6+
{
7+
public string RecordIndicator => "900";
8+
}

src/AEMO.MDFF/HeaderRecord.cs

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using AEMO.MDFF.Abstractions;
2+
3+
namespace AEMO.MDFF;
4+
5+
public sealed class HeaderRecord : IMdffRecord
6+
{
7+
public string RecordIndicator => "100";
8+
public string VersionHeader { get; set; }
9+
public DateTime DateTime { get; set; }
10+
public string FromParticipant { get; set; }
11+
public string ToParticipant { get; set; }
12+
}
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using AEMO.MDFF.Abstractions;
2+
3+
namespace AEMO.MDFF.NEM12;
4+
5+
public sealed class IntervalDataRecord : IMdffRecord
6+
{
7+
public string RecordIndicator => "300";
8+
public DateOnly IntervalDate { get; set; }
9+
public IReadOnlyCollection<decimal> IntervalValues { get; set; }
10+
public string QualityMethod { get; set; }
11+
public string ReasonCode { get; set; }
12+
public string ReasonDescription { get; set; }
13+
public string UpdateDateTime { get; set; }
14+
public DateTime MSATSLoadDateTime { get; set; }
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using AEMO.MDFF.Abstractions;
2+
3+
namespace AEMO.MDFF.NEM12;
4+
5+
public sealed class NMIDataDetailsRecord : IMdffRecord
6+
{
7+
public string RecordIndicator => "200";
8+
public required string NMI { get; set; }
9+
public string NMIConfiguration { get; set; }
10+
public string RegisterId { get; set; }
11+
public string NMISuffix { get; set; }
12+
public string MDMDataStreamIdentifier { get; set; }
13+
public string MeterSerialNumber { get; set; }
14+
public string UOM { get; set; }
15+
public int IntervalLength { get; set; }
16+
public DateOnly NextScheduledReadDate { get; set; }
17+
}

src/AEMO.MDFF/NEM12/Nem12Reader.cs

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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+
}

src/AEMO.MDFF/NEM13/Nem13Reader.cs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using AEMO.MDFF.Abstractions;
2+
3+
namespace AEMO.MDFF.NEM13;
4+
5+
public class Nem13Reader : IMdffReader
6+
{
7+
public IAsyncEnumerable<IMdffRecord> ReadAsync(Stream stream, CancellationToken ct)
8+
{
9+
throw new NotImplementedException();
10+
}
11+
}

0 commit comments

Comments
 (0)