Files
MeterVault/src/Infrastructure/Persistence/DatabaseSeeder.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

62 lines
2.7 KiB
C#

using MeterVault.Core.Domain;
using Microsoft.EntityFrameworkCore;
namespace MeterVault.Infrastructure.Persistence;
/// <summary>
/// Seeds sensible defaults (energy types, cost categories, base settings) on first run.
/// Idempotent — safe to call after every migration. Nothing here is hardcoded into the domain;
/// these are just starter rows the user can edit or delete (SDD FR-1).
/// </summary>
public static class DatabaseSeeder
{
public static async Task SeedAsync(MeterVaultDbContext db, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(db);
if (!await db.EnergyTypes.AnyAsync(cancellationToken).ConfigureAwait(false))
{
db.EnergyTypes.AddRange(DefaultEnergyTypes());
}
if (!await db.CostCategories.AnyAsync(cancellationToken).ConfigureAwait(false))
{
db.CostCategories.AddRange(DefaultCostCategories());
}
foreach (var (key, value) in DefaultSettings())
{
if (!await db.AppSettings.AnyAsync(s => s.Key == key, cancellationToken).ConfigureAwait(false))
{
db.AppSettings.Add(new AppSetting { Key = key, Value = value });
}
}
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
private static IEnumerable<EnergyType> DefaultEnergyTypes() =>
[
new() { Key = "electricity", DisplayName = "Strom", BaseUnit = "kWh", DefaultMode = MeterMode.CumulativeCounter, Icon = "bolt", ColorHex = "#F6C445" },
new() { Key = "water", DisplayName = "Wasser", BaseUnit = "m3", DefaultMode = MeterMode.CumulativeCounter, Icon = "water_drop", ColorHex = "#3B82F6" },
new() { Key = "heating_oil", DisplayName = "Heizöl", BaseUnit = "L", DefaultMode = MeterMode.ConsumableBalance, Icon = "local_gas_station", ColorHex = "#B45309" },
new() { Key = "gas", DisplayName = "Gas", BaseUnit = "kWh", DefaultMode = MeterMode.CumulativeCounter, Icon = "gas_meter", ColorHex = "#EF4444" },
new() { Key = "district_heat", DisplayName = "Fernwärme", BaseUnit = "kWh", DefaultMode = MeterMode.CumulativeCounter, Icon = "thermostat", ColorHex = "#F97316" },
];
private static IEnumerable<CostCategory> DefaultCostCategories() =>
[
new() { Name = "Heizung", ColorHex = "#B45309", Sort = 0 },
new() { Name = "Strom", ColorHex = "#F6C445", Sort = 1 },
new() { Name = "Wasser", ColorHex = "#3B82F6", Sort = 2 },
new() { Name = "Pool Betrieb", ColorHex = "#14B8A6", Sort = 3 },
];
private static IEnumerable<(string Key, string Value)> DefaultSettings() =>
[
("currency", "\"EUR\""),
("locale", "\"en\""),
("timezone", "\"Europe/Berlin\""),
];
}