M1: pure normalization engine + default seed
Infrastructure-free normalization engine in Core dispatching on MeterMode: - Cumulative/Generation counters: register deltas, first-reading baseline (Haus 411, Auto 3755), meter swaps (water …861→2 reconciles to 12 via boundary registers OR an explicit amount override), counter resets, anomaly-guarded decreases. - RuntimeCounter: Δhours × rate (fixed/empirical). - ConsumableBalance: tank level-Δ + deliveries → consumption, cm→litre calibration; delivery-only rows before the first dipstick emit nothing. - DirectDelta, and Virtual meters via a small safe arithmetic evaluator (Netz Einsparung = Haus − Netz, Eigenverbrauch = Erzeugung − Einsparung) — data-driven, not hardcoded. - DatabaseSeeder: default energy types, cost categories, base settings (idempotent). 24 Core unit tests + 4 integration tests green. Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
using MeterVault.Core.Domain;
|
||||
using MeterVault.Core.Normalization;
|
||||
using static MeterVault.Core.Tests.TestData;
|
||||
|
||||
namespace MeterVault.Core.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// The water meter register swaps mid-series (…861 → 2 → 15). The swap month's consumption (12)
|
||||
/// cannot be derived from the two visible registers alone — an explicit swap event carries the
|
||||
/// boundary, and continuity is preserved across it (SDD §2.3, §7.1).
|
||||
/// </summary>
|
||||
public sealed class WaterSwapTests
|
||||
{
|
||||
private readonly INormalizationEngine _engine = NormalizationEngine.CreateDefault();
|
||||
|
||||
private static NormalizationContext WaterContext(MeterEvent swap) => new()
|
||||
{
|
||||
Meter = new MeterConfig
|
||||
{
|
||||
MeterId = 10,
|
||||
Mode = MeterMode.CumulativeCounter,
|
||||
Unit = "m3",
|
||||
InitialBaseline = 820, // Nov 2022 register, so Dez books 14.
|
||||
},
|
||||
Readings =
|
||||
[
|
||||
Reading(10, Month(2022, 12), 834),
|
||||
Reading(10, Month(2023, 1), 848),
|
||||
Reading(10, Month(2023, 2), 861),
|
||||
Reading(10, Month(2023, 3), 2), // new meter
|
||||
Reading(10, Month(2023, 4), 15),
|
||||
],
|
||||
Events = [swap],
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Swap_with_boundary_registers_reconciles_to_twelve()
|
||||
{
|
||||
// Old meter ran 861 → 873 before removal; new meter installed reading 2.
|
||||
var swap = Swap(10, Month(2023, 3), prevValue: 873, newValue: 2);
|
||||
|
||||
var result = _engine.Normalize(WaterContext(swap));
|
||||
|
||||
Assert.Equal([14d, 14d, 13d, 12d, 13d], result.Select(c => c.Amount));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Swap_with_explicit_amount_override_reconciles_to_twelve()
|
||||
{
|
||||
// The importer can seed the sheet's own März consumption (12) as an override.
|
||||
var swap = Swap(10, Month(2023, 3), amount: 12);
|
||||
|
||||
var result = _engine.Normalize(WaterContext(swap));
|
||||
|
||||
Assert.Equal([14d, 14d, 13d, 12d, 13d], result.Select(c => c.Amount));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user