Files
MeterVault/tests/Core.Tests/RuntimeAndTankTests.cs
T
schmidt.florian d972f67bad 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
2026-07-13 11:12:03 +02:00

91 lines
3.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using MeterVault.Core.Domain;
using MeterVault.Core.Normalization;
using static MeterVault.Core.Tests.TestData;
namespace MeterVault.Core.Tests;
public sealed class RuntimeAndTankTests
{
private readonly INormalizationEngine _engine = NormalizationEngine.CreateDefault();
[Fact]
public void Runtime_counter_multiplies_delta_hours_by_fixed_rate()
{
var ctx = new NormalizationContext
{
Meter = new MeterConfig
{
MeterId = 20,
Mode = MeterMode.RuntimeCounter,
Unit = "L",
InitialBaseline = 7785,
Tank = new TankConfig { Capacity = 7000, RateMode = TankRateMode.Fixed, FixedRate = 2.0 },
},
Readings = [Reading(20, Month(2023, 1), 7785), Reading(20, Month(2023, 2), 7952)],
};
var result = _engine.Normalize(ctx);
// Δhours: 0, then 167 × 2.0 L/h = 334 L.
Assert.Equal([0d, 334d], result.Select(c => c.Amount));
}
[Fact]
public void Tank_consumption_is_level_delta_between_dipsticks()
{
// Linear calibration 7000 L / 150 cm ≈ 46.667 L/cm (SDD §2.4).
var calibration = new CalibrationCurve(7000d / 150d);
var ctx = new NormalizationContext
{
Meter = new MeterConfig
{
MeterId = 30,
Mode = MeterMode.ConsumableBalance,
Unit = "L",
Tank = new TankConfig { Capacity = 7000, Calibration = calibration },
},
Events =
[
Delivery(30, Month(2020, 9), 3500), // pre-first-level delivery: absorbed
TankLevelCm(30, Month(2022, 9), 35), // first level: no consumption emitted
TankLevelCm(30, Month(2022, 10), 34), // ~46.7 L drawn
TankLevelCm(30, Month(2022, 11), 27), // ~326.7 L drawn
],
};
var result = _engine.Normalize(ctx);
Assert.Equal(2, result.Count);
Assert.Equal(46.7, result[0].Amount, 1);
Assert.Equal(326.7, result[1].Amount, 1);
}
[Fact]
public void Delivery_between_dipsticks_reconciles_into_the_balance()
{
var calibration = new CalibrationCurve(7000d / 150d);
var ctx = new NormalizationContext
{
Meter = new MeterConfig
{
MeterId = 30,
Mode = MeterMode.ConsumableBalance,
Unit = "L",
Tank = new TankConfig { Capacity = 7000, Calibration = calibration },
},
Events =
[
TankLevelCm(30, Month(2022, 9), 20), // 933.3 L
Delivery(30, Month(2022, 10), 2000),
TankLevelCm(30, Month(2022, 11), 50), // 2333.3 L
],
};
var result = _engine.Normalize(ctx);
// consumption = 933.3 + 2000 2333.3 = 600.
Assert.Single(result);
Assert.Equal(600d, result[0].Amount, 1);
}
}