Flow: virtual "sum" meters that aggregate upstreams + demo chain
ci / build-test (push) Successful in 1m14s
ci / build-test (push) Successful in 1m14s
Adds the "sum meter" the user asked for: a meter that doesn't physically exist but represents the sum of other meters in the flow view. - FlowService: a Virtual-mode meter has no readings of its own; its flow value is the sum of its upstream meters, resolved in topological order (so "Summe Solar" = Solar 1 + Solar 2, and Grid + Summe Solar → House with the remainder as "Other" = export / battery / inverter losses). - Meters editor: a hint when Mode = Virtual explaining the sum-meter behaviour. - Reference data seeds the full demo chain: Solar 1 + Solar 2 → Summe Solar; Netz + Summe Solar → Haus → Auto + Other — so /energy/1 shows a multi-level Sankey out of the box. - Fix: SankeyChart Unit was passed as the literal string "_graph.Unit" (missing @) so node labels read "_graph.Unit" instead of "kWh". Caught by screenshotting the live page. Test: Virtual_sum_meter_aggregates_its_upstreams. 69 Core + 50 Integration = 119 green. Live-verified with a browser screenshot of the multi-level flow. Claude-Session: https://claude.ai/code/session_01Lz2RqAsnQhetqWNoCDfexK
This commit is contained in:
@@ -104,6 +104,45 @@ public sealed class FlowServiceTests(TimescaleFixture fx)
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Virtual_sum_meter_aggregates_its_upstreams()
|
||||
{
|
||||
await using var db = fx.CreateContext();
|
||||
try
|
||||
{
|
||||
var type = await SeedTypeAsync(db, "flow_elec_d");
|
||||
var solar1 = await AddMeterAsync(db, "Solar 1", type);
|
||||
var solar2 = await AddMeterAsync(db, "Solar 2", type);
|
||||
var sumSolar = await AddMeterAsync(db, "Sum Solar", type, MeterMode.Virtual);
|
||||
var grid = await AddMeterAsync(db, "Grid", type);
|
||||
var house = await AddMeterAsync(db, "House", type);
|
||||
// Solar1 + Solar2 → Sum Solar ; Grid + Sum Solar → House.
|
||||
db.MeterLinks.Add(new MeterLink { FromMeterId = solar1.Id, ToMeterId = sumSolar.Id });
|
||||
db.MeterLinks.Add(new MeterLink { FromMeterId = solar2.Id, ToMeterId = sumSolar.Id });
|
||||
db.MeterLinks.Add(new MeterLink { FromMeterId = grid.Id, ToMeterId = house.Id });
|
||||
db.MeterLinks.Add(new MeterLink { FromMeterId = sumSolar.Id, ToMeterId = house.Id });
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await AddConsumptionAsync(db, solar1.Id, 15, ConsumptionKind.Generation);
|
||||
await AddConsumptionAsync(db, solar2.Id, 15, ConsumptionKind.Generation);
|
||||
await AddConsumptionAsync(db, grid.Id, 75);
|
||||
await AddConsumptionAsync(db, house.Id, 40);
|
||||
|
||||
var graph = await new FlowService(fx).GetFlowAsync(type, new DateOnly(2024, 1, 1), new DateOnly(2024, 12, 31));
|
||||
|
||||
// Sum Solar has no readings but equals Solar 1 + Solar 2 = 30.
|
||||
Assert.Equal(30, graph.Nodes.Single(n => n.MeterId == sumSolar.Id).Value, 1);
|
||||
// House (40) splits across Grid (75) and Sum Solar (30) → 40*30/105 from solar.
|
||||
Assert.Equal(40.0 * 30 / 105, graph.Links.Single(l => l.From == $"m{sumSolar.Id}" && l.To == $"m{house.Id}").Value, 1);
|
||||
// No spurious remainder under Solar 1/2 (their whole output flows into Sum Solar).
|
||||
Assert.DoesNotContain(graph.Nodes, n => n.IsOther && n.Id == $"other{solar1.Id}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ClearAsync(db);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<short> SeedTypeAsync(MeterVaultDbContext db, string key)
|
||||
{
|
||||
var type = new EnergyType { Key = key, DisplayName = key, BaseUnit = "kWh", DefaultMode = MeterMode.CumulativeCounter };
|
||||
@@ -112,9 +151,9 @@ public sealed class FlowServiceTests(TimescaleFixture fx)
|
||||
return type.Id;
|
||||
}
|
||||
|
||||
private static async Task<Meter> AddMeterAsync(MeterVaultDbContext db, string name, short type)
|
||||
private static async Task<Meter> AddMeterAsync(MeterVaultDbContext db, string name, short type, MeterMode mode = MeterMode.DirectDelta)
|
||||
{
|
||||
var meter = new Meter { Name = name, EnergyTypeId = type, Mode = MeterMode.DirectDelta, Unit = "kWh" };
|
||||
var meter = new Meter { Name = name, EnergyTypeId = type, Mode = mode, Unit = "kWh" };
|
||||
db.Meters.Add(meter);
|
||||
await db.SaveChangesAsync();
|
||||
return meter;
|
||||
|
||||
Reference in New Issue
Block a user