Files
MeterVault/tests/Integration.Tests/Reconciliation/OilReconciliationTests.cs
T
schmidt.florian 5977c81002 M2: German CSV importer + reconciliation of all 4 fixtures
- German-dialect scalar parsers in Core (GermanNumber/Money/Date, ValueCell):
  decimal comma, thousands dot, unit suffixes, € currency, both date shapes.
- Declarative MappingProfile + RowClassifier (skip summary/blank/all-zero rows) +
  CsvImporter (CsvHelper) staging readings/events/manual-costs, with auto swap
  detection on register decreases and month-end anchoring for interleaved oil dates.
- Four built-in ReferenceProfiles (Strom/Wasser/Heizöl/Kosten).
- ImportService: commit as revertible import_batch + wholesale consumption recompute
  per affected meter (NormalizationService/MeterConfigFactory), revert by batch.
- Reconciliation tests: all 4 CSVs match the sheet's own columns within tolerance
  (electricity 5 meters + Netz Einsparung, water swap→12, oil tank incl. deliveries +
  burner hours, cost category totals). Commit/revert round-trip verified on Timescale.

69 tests green (53 Core + 16 integration).

Known follow-up (polish): historical imports can contend with the 30-day compression
policy's background job; tests pause it. Consider retry-on-deadlock or deferred
compression for large historical imports in production.

Claude-Session: https://claude.ai/code/session_01WujdMtMJPbxDpDnMeK22rr
2026-07-13 11:35:54 +02:00

57 lines
2.2 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 MeterVault.Infrastructure.Import;
using static MeterVault.Integration.Tests.Reconciliation.ReconciliationSupport;
namespace MeterVault.Integration.Tests.Reconciliation;
/// <summary>
/// Reconciles the heating-oil tank against the Heizöl sheet (SDD §2.4). The sheet's
/// <c>Differenz Tank</c> (col 9) is the negated actual consumption, already netting deliveries —
/// exactly what the consumable-balance normalizer computes (prevLevel + deliveries currLevel).
/// The burner runtime meter's Δhours is reconciled against <c>Differenz Betrieb</c> (col 2).
/// </summary>
public sealed class OilReconciliationTests
{
[Fact]
public void Tank_consumption_matches_differenz_tank_including_deliveries()
{
var staged = Stage(ReferenceProfiles.HeatingOil(), Oil);
var config = new MeterConfig
{
MeterId = ReferenceProfiles.OilTank,
Mode = MeterMode.ConsumableBalance,
Unit = "L",
Tank = new TankConfig
{
Capacity = 7000,
Calibration = new CalibrationCurve(ReferenceProfiles.OilLitresPerCm),
},
};
var computed = ByDate(Normalize(staged, config));
// Differenz Tank is negative consumption → negate to compare with positive draw.
var oracle = OracleByDate(ReadRows(Oil), dateColumn: 0, valueColumn: 9, firstDataRow: 4, v => -v);
// ±2 L covers cm→litre rounding on both ends of each interval.
AssertReconciles(computed, oracle, tolerance: 2.0, "oil tank", minMatches: 30);
}
[Fact]
public void Burner_delta_hours_match_differenz_betrieb()
{
var staged = Stage(ReferenceProfiles.HeatingOil(), Oil);
var config = new MeterConfig
{
MeterId = ReferenceProfiles.Burner,
Mode = MeterMode.RuntimeCounter,
Unit = "h",
};
var computed = ByDate(Normalize(staged, config));
var oracle = OracleByDate(ReadRows(Oil), dateColumn: 0, valueColumn: 2, firstDataRow: 4);
AssertReconciles(computed, oracle, tolerance: 0.5, "burner hours", minMatches: 30);
}
}