using MeterVault.Core.Domain; using MeterVault.Core.Normalization; using static MeterVault.Core.Tests.TestData; namespace MeterVault.Core.Tests; public sealed class CumulativeCounterNormalizerTests { private readonly INormalizationEngine _engine = NormalizationEngine.CreateDefault(); [Fact] public void Straight_deltas_match_the_electricity_house_meter() { // Zähler Haus: Sept 0 → Okt 411 → Nov 1153 → Dez 1968 (SDD reference data). var ctx = new NormalizationContext { Meter = new MeterConfig { MeterId = 1, Mode = MeterMode.CumulativeCounter, Unit = "kWh" }, Readings = [ Reading(1, Month(2022, 9), 0), Reading(1, Month(2022, 10), 411), Reading(1, Month(2022, 11), 1153), Reading(1, Month(2022, 12), 1968), ], }; var result = _engine.Normalize(ctx); Assert.Equal([0d, 411d, 742d, 815d], result.Select(c => c.Amount)); Assert.All(result, c => Assert.Equal(ConsumptionKind.Consumption, c.Kind)); } [Fact] public void First_reading_books_the_full_register_against_a_zero_baseline() { // Zähler Auto appears mid-series: first reading 3755 → month-one consumption 3755. var ctx = new NormalizationContext { Meter = new MeterConfig { MeterId = 3, Mode = MeterMode.CumulativeCounter, Unit = "kWh" }, Readings = [Reading(3, Month(2023, 5), 3755), Reading(3, Month(2023, 6), 3960)], }; var result = _engine.Normalize(ctx); Assert.Equal([3755d, 205d], result.Select(c => c.Amount)); } [Fact] public void Counter_reset_rebaselines_at_new_value() { var ctx = new NormalizationContext { Meter = new MeterConfig { MeterId = 1, Mode = MeterMode.CumulativeCounter, Unit = "kWh", InitialBaseline = 90, }, Readings = [ Reading(1, Month(2023, 1), 100), Reading(1, Month(2023, 2), 150), Reading(1, Month(2023, 3), 30), Reading(1, Month(2023, 4), 80), ], Events = [Reset(1, Month(2023, 3), newValue: 0)], }; var result = _engine.Normalize(ctx); Assert.Equal([10d, 50d, 30d, 50d], result.Select(c => c.Amount)); } [Fact] public void Counter_reset_with_a_final_register_books_the_stretch_before_the_reset() { // The register climbed 150 → 170 after the February reading, then reset to 0 and reached 30 // by March. Knowing the 170 turns the reset from "lose 20" into the swap formula: 20 + 30. var ctx = new NormalizationContext { Meter = new MeterConfig { MeterId = 1, Mode = MeterMode.CumulativeCounter, Unit = "kWh", InitialBaseline = 90 }, Readings = [ Reading(1, Month(2023, 1), 100), Reading(1, Month(2023, 2), 150), Reading(1, Month(2023, 3), 30), Reading(1, Month(2023, 4), 80), ], Events = [Reset(1, Month(2023, 3), newValue: 0, prevValue: 170)], }; var result = _engine.Normalize(ctx); Assert.Equal([10d, 50d, 50d, 50d], result.Select(c => c.Amount)); } [Fact] public void Swap_recorded_with_the_new_meters_start_reading_at_the_same_instant_books_the_tail_there() { // What the meter page records for "the meter was swapped today": the swap event and a reading of // the new register's start value, both at the swap instant. The old meter's tail (873 − 861) // lands at the swap, and the next reading counts from the new register's start. var swapAt = Month(2023, 3).AddDays(16).AddHours(9); var ctx = new NormalizationContext { Meter = new MeterConfig { MeterId = 10, Mode = MeterMode.CumulativeCounter, Unit = "m3", InitialBaseline = 848 }, Readings = [ DayReading(10, Month(2023, 3), 861), Reading(10, swapAt, 2), Reading(10, swapAt.AddDays(1), 2.5), ], Events = [Swap(10, swapAt, prevValue: 873, newValue: 2)], }; var result = _engine.Normalize(ctx); Assert.Equal([13d, 12d, 0.5d], result.Select(c => c.Amount)); Assert.Equal(swapAt, result[1].Time); } [Fact] public void Unexplained_decrease_yields_zero_and_marks_quality() { var ctx = new NormalizationContext { Meter = new MeterConfig { MeterId = 1, Mode = MeterMode.CumulativeCounter, Unit = "kWh" }, Readings = [Reading(1, Month(2023, 1), 100), Reading(1, Month(2023, 2), 60)], }; var result = _engine.Normalize(ctx); Assert.Equal(0d, result[1].Amount); Assert.Equal(ReadingQuality.Estimated, result[1].Quality); } [Fact] public void Generation_meter_emits_generation_kind() { // Solar 2: Okt 0 → Nov 0 → ... → first real reading 7 in Feb. var ctx = new NormalizationContext { Meter = new MeterConfig { MeterId = 5, Mode = MeterMode.GenerationCounter, Unit = "kWh" }, Readings = [Reading(5, Month(2023, 1), 0), Reading(5, Month(2023, 2), 7)], }; var result = _engine.Normalize(ctx); Assert.Equal([0d, 7d], result.Select(c => c.Amount)); Assert.All(result, c => Assert.Equal(ConsumptionKind.Generation, c.Kind)); } }