5977c81002
- German-dialect scalar parsers in Core (GermanNumber/Money/Date, ValueCell): decimal comma, thousands dot, unit suffixes, € currency, both date shapes. - Declarative MappingProfile + RowClassifier (skip summary/blank/all-zero rows) + CsvImporter (CsvHelper) staging readings/events/manual-costs, with auto swap detection on register decreases and month-end anchoring for interleaved oil dates. - Four built-in ReferenceProfiles (Strom/Wasser/Heizöl/Kosten). - ImportService: commit as revertible import_batch + wholesale consumption recompute per affected meter (NormalizationService/MeterConfigFactory), revert by batch. - Reconciliation tests: all 4 CSVs match the sheet's own columns within tolerance (electricity 5 meters + Netz Einsparung, water swap→12, oil tank incl. deliveries + burner hours, cost category totals). Commit/revert round-trip verified on Timescale. 69 tests green (53 Core + 16 integration). Known follow-up (polish): historical imports can contend with the 30-day compression policy's background job; tests pause it. Consider retry-on-deadlock or deferred compression for large historical imports in production. Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
73 lines
3.0 KiB
C#
73 lines
3.0 KiB
C#
using MeterVault.Core.Domain;
|
|
using MeterVault.Core.Normalization;
|
|
using MeterVault.Infrastructure.Import;
|
|
using static MeterVault.Integration.Tests.Reconciliation.ReconciliationSupport;
|
|
|
|
namespace MeterVault.Integration.Tests.Reconciliation;
|
|
|
|
/// <summary>
|
|
/// Reconciles the five electricity meters and a derived virtual meter against the Strom sheet's
|
|
/// own Verbrauch / Netz Einsparung columns (SDD §2.2). Runs without a database.
|
|
/// </summary>
|
|
public sealed class ElectricityReconciliationTests
|
|
{
|
|
private static MeterConfig Cumulative(int id) =>
|
|
new() { MeterId = id, Mode = MeterMode.CumulativeCounter, Unit = "kWh" };
|
|
|
|
private static MeterConfig Generation(int id) =>
|
|
new() { MeterId = id, Mode = MeterMode.GenerationCounter, Unit = "kWh" };
|
|
|
|
[Theory]
|
|
[InlineData(ReferenceProfiles.Haus, 7)] // Haus Verbrauch
|
|
[InlineData(ReferenceProfiles.Netz, 8)] // Netz Verbrauch
|
|
[InlineData(ReferenceProfiles.Auto, 9)] // Auto Verbrauch
|
|
[InlineData(ReferenceProfiles.Solar1, 10)] // Solar Erzeugung 1
|
|
[InlineData(ReferenceProfiles.Solar2, 11)] // Solar Erzeugung 2
|
|
public void Meter_consumption_matches_the_sheet(int meterId, int oracleColumn)
|
|
{
|
|
var staged = Stage(ReferenceProfiles.Electricity(), Electricity);
|
|
var config = meterId is ReferenceProfiles.Solar1 or ReferenceProfiles.Solar2
|
|
? Generation(meterId)
|
|
: Cumulative(meterId);
|
|
|
|
var computed = ByMonth(Normalize(staged, config));
|
|
var oracle = OracleByMonth(ReadRows(Electricity), dateColumn: 0, valueColumn: oracleColumn, firstDataRow: 1);
|
|
|
|
AssertReconciles(computed, oracle, tolerance: 1.0, $"meter {meterId}", minMatches: 20);
|
|
}
|
|
|
|
[Fact]
|
|
public void Netz_einsparung_virtual_matches_the_sheet()
|
|
{
|
|
var staged = Stage(ReferenceProfiles.Electricity(), Electricity);
|
|
var haus = Normalize(staged, Cumulative(ReferenceProfiles.Haus));
|
|
var netz = Normalize(staged, Cumulative(ReferenceProfiles.Netz));
|
|
|
|
var engine = NormalizationEngine.CreateDefault();
|
|
var virtualContext = new NormalizationContext
|
|
{
|
|
Meter = new MeterConfig
|
|
{
|
|
MeterId = 100,
|
|
Mode = MeterMode.Virtual,
|
|
Unit = "kWh",
|
|
Virtual = new VirtualSpec
|
|
{
|
|
Expression = $"m{ReferenceProfiles.Haus} - m{ReferenceProfiles.Netz}",
|
|
ReferencedMeterIds = [ReferenceProfiles.Haus, ReferenceProfiles.Netz],
|
|
},
|
|
},
|
|
ReferencedSeries = new Dictionary<int, IReadOnlyList<Consumption>>
|
|
{
|
|
[ReferenceProfiles.Haus] = haus,
|
|
[ReferenceProfiles.Netz] = netz,
|
|
},
|
|
};
|
|
|
|
var computed = ByMonth(engine.Normalize(virtualContext));
|
|
var oracle = OracleByMonth(ReadRows(Electricity), dateColumn: 0, valueColumn: 13, firstDataRow: 1); // Netz Einsparung
|
|
|
|
AssertReconciles(computed, oracle, tolerance: 1.0, "Netz Einsparung", minMatches: 20);
|
|
}
|
|
}
|