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:
2026-07-13 11:12:03 +02:00
parent 48a7f5a825
commit d972f67bad
21 changed files with 1121 additions and 1 deletions
+47
View File
@@ -0,0 +1,47 @@
using MeterVault.Core.Domain;
namespace MeterVault.Core.Normalization;
/// <summary>
/// The immutable, infrastructure-free view of a meter that the normalization engine needs.
/// Infrastructure projects a <see cref="Meter"/> (+ optional <see cref="Tank"/>) into this.
/// </summary>
public sealed record MeterConfig
{
public required int MeterId { get; init; }
public required MeterMode Mode { get; init; }
public required string Unit { get; init; }
/// <summary>Register baseline for a newly installed meter (SDD §7.1). Default 0.</summary>
public double InitialBaseline { get; init; }
/// <summary>Tank/runtime configuration for consumable_balance and runtime_counter meters.</summary>
public TankConfig? Tank { get; init; }
/// <summary>Expression + referenced meters for virtual meters.</summary>
public VirtualSpec? Virtual { get; init; }
}
/// <summary>Consumable/runtime parameters (SDD §5.3 <c>tank</c>).</summary>
public sealed record TankConfig
{
public double Capacity { get; init; }
public TankRateMode RateMode { get; init; } = TankRateMode.Empirical;
/// <summary>Litres per runtime-hour when <see cref="RateMode"/> is Fixed.</summary>
public double? FixedRate { get; init; }
/// <summary>Physical-level (cm) → volume curve. Present when dipstick readings are recorded.</summary>
public CalibrationCurve? Calibration { get; init; }
}
/// <summary>A virtual meter's expression over other meters' series (SDD §7.4).</summary>
public sealed record VirtualSpec
{
public required string Expression { get; init; }
public IReadOnlyList<int> ReferencedMeterIds { get; init; } = [];
}