Skip to content

Commit

Permalink
feat: add NEM12 reader and header, 200, 300, and end records (#6
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ahanoff authored Sep 13, 2024
1 parent f0dc578 commit e6340cf
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/AEMO.MDFF/EndRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using AEMO.MDFF.Abstractions;

namespace AEMO.MDFF;

public sealed class EndRecord : IMdffRecord
{
public string RecordIndicator => "900";
}
12 changes: 12 additions & 0 deletions src/AEMO.MDFF/HeaderRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using AEMO.MDFF.Abstractions;

namespace AEMO.MDFF;

public sealed class HeaderRecord : IMdffRecord
{
public string RecordIndicator => "100";
public string VersionHeader { get; set; }

Check warning on line 8 in src/AEMO.MDFF/HeaderRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'VersionHeader' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public DateTime DateTime { get; set; }
public string FromParticipant { get; set; }

Check warning on line 10 in src/AEMO.MDFF/HeaderRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'FromParticipant' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string ToParticipant { get; set; }

Check warning on line 11 in src/AEMO.MDFF/HeaderRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'ToParticipant' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
}
15 changes: 15 additions & 0 deletions src/AEMO.MDFF/NEM12/IntervalDataRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using AEMO.MDFF.Abstractions;

namespace AEMO.MDFF.NEM12;

public sealed class IntervalDataRecord : IMdffRecord
{
public string RecordIndicator => "300";
public DateOnly IntervalDate { get; set; }
public IReadOnlyCollection<decimal> IntervalValues { get; set; }

Check warning on line 9 in src/AEMO.MDFF/NEM12/IntervalDataRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'IntervalValues' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string QualityMethod { get; set; }
public string ReasonCode { get; set; }
public string ReasonDescription { get; set; }
public string UpdateDateTime { get; set; }
public DateTime MSATSLoadDateTime { get; set; }
}
17 changes: 17 additions & 0 deletions src/AEMO.MDFF/NEM12/NMIDataDetailsRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using AEMO.MDFF.Abstractions;

namespace AEMO.MDFF.NEM12;

public sealed class NMIDataDetailsRecord : IMdffRecord
{
public string RecordIndicator => "200";
public required string NMI { get; set; }
public string NMIConfiguration { get; set; }

Check warning on line 9 in src/AEMO.MDFF/NEM12/NMIDataDetailsRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'NMIConfiguration' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string RegisterId { get; set; }

Check warning on line 10 in src/AEMO.MDFF/NEM12/NMIDataDetailsRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'RegisterId' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string NMISuffix { get; set; }

Check warning on line 11 in src/AEMO.MDFF/NEM12/NMIDataDetailsRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'NMISuffix' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string MDMDataStreamIdentifier { get; set; }

Check warning on line 12 in src/AEMO.MDFF/NEM12/NMIDataDetailsRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'MDMDataStreamIdentifier' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string MeterSerialNumber { get; set; }

Check warning on line 13 in src/AEMO.MDFF/NEM12/NMIDataDetailsRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'MeterSerialNumber' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string UOM { get; set; }

Check warning on line 14 in src/AEMO.MDFF/NEM12/NMIDataDetailsRecord.cs

View workflow job for this annotation

GitHub Actions / build (8.x)

Non-nullable property 'UOM' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public int IntervalLength { get; set; }
public DateOnly NextScheduledReadDate { get; set; }
}
120 changes: 120 additions & 0 deletions src/AEMO.MDFF/NEM12/Nem12Reader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using AEMO.MDFF.Abstractions;
using Sylvan.Data.Csv;

namespace AEMO.MDFF.NEM12;

public class Nem12Reader() : IMdffReader
{
private readonly Dictionary<string, int> _nmiIntervalLengths = new();

public async IAsyncEnumerable<IMdffRecord> ReadAsync(Stream stream, [EnumeratorCancellation] CancellationToken ct)
{
using var sr = new StreamReader(stream);
await using var csv = await CsvDataReader.CreateAsync(sr,
new CsvDataReaderOptions()
{
HasHeaders = false,
}, ct);

bool headerFound = false;
bool endFound = false;
string currentNMI = null;

while (await csv.ReadAsync(ct) && !endFound)
{
var recordIndicator = csv.GetString(0);
switch (recordIndicator)
{
case "100":
if (headerFound)
throw new InvalidDataException("Multiple header records found");
var hr = ParseHeaderRecord(csv);
headerFound = true;
yield return hr;
break;
case "200":
if (!headerFound)
throw new InvalidDataException("Data record found before header");
var ddr = ParseNMIDataDetailsRecord(csv);
currentNMI = ddr.NMI;
_nmiIntervalLengths[currentNMI] = ddr.IntervalLength;
yield return ddr;
break;
case "300":
if (!headerFound)
throw new InvalidDataException("Data record found before header");
var idr = ParseIntervalDataRecord(csv, currentNMI);
yield return idr;
break;
case "400":
if (!headerFound)
throw new InvalidDataException("Data record found before header");
break;
case "500":
if (!headerFound)
throw new InvalidDataException("Data record found before header");
break;
case "900":
var er = new EndRecord();
endFound = true;
yield return er;
break;
default:
throw new InvalidDataException($"Unsupported record indicator: {recordIndicator}");
}
}
if (!headerFound)
throw new InvalidDataException("No header record found");
if (!endFound)
throw new InvalidDataException("No end record found");
}

private HeaderRecord ParseHeaderRecord(CsvDataReader csv)
{
var dateTimeString = csv.GetString(2);
var dateTime = DateTime.ParseExact(dateTimeString, "yyyyMMddHHmm", CultureInfo.InvariantCulture);

return new HeaderRecord()
{
VersionHeader = csv.GetString(1),
DateTime = dateTime,
FromParticipant = csv.GetString(3),
ToParticipant = csv.GetString(4)
};
}
private NMIDataDetailsRecord ParseNMIDataDetailsRecord(CsvDataReader csv)
{
var dateString = csv.GetString(9);
var date = DateOnly.ParseExact(dateString, "yyyyMMdd", CultureInfo.InvariantCulture);

return new NMIDataDetailsRecord
{
NMI = csv.GetString(1),
NMIConfiguration = csv.GetString(2),
RegisterId = csv.GetString(3),
NMISuffix = csv.GetString(4),
MDMDataStreamIdentifier = csv.GetString(5),
MeterSerialNumber = csv.GetString(6),
UOM = csv.GetString(7),
IntervalLength = csv.GetInt32(8),
NextScheduledReadDate = date
};
}

private IntervalDataRecord ParseIntervalDataRecord(CsvDataReader csv, string currentNMI)
{
var dateString = csv.GetString(1);
var date = DateOnly.ParseExact(dateString, "yyyyMMdd", CultureInfo.InvariantCulture);
int intervalLength = _nmiIntervalLengths[currentNMI];
int expectedIntervals = 1440 / intervalLength; // 1440 minutes in a day

return new IntervalDataRecord
{
IntervalDate = date
// TODO: parse interval values
};
}

}
11 changes: 11 additions & 0 deletions src/AEMO.MDFF/NEM13/Nem13Reader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using AEMO.MDFF.Abstractions;

namespace AEMO.MDFF.NEM13;

public class Nem13Reader : IMdffReader
{
public IAsyncEnumerable<IMdffRecord> ReadAsync(Stream stream, CancellationToken ct)
{
throw new NotImplementedException();
}
}

0 comments on commit e6340cf

Please sign in to comment.