7e34aeccc8
ci / build-test (push) Successful in 1m24s
Adds a meter hierarchy and a flow view: a downstream meter is a *subsection*
of an upstream one (not an addition), so you can see where a main meter's flow
divides — e.g. official water → garden, pool, other; grid/battery → all → car.
- MeterLink (schema + migration AddMeterLinks): a directed from→to flow edge.
Multi-parent allowed (a merge, e.g. grid + solar → house); multi-child is a
split. Cascade-deletes with either endpoint; unique + distinct-endpoint checks.
- FlowService: per energy type + period, builds a Sankey graph — nodes = meters
sized by consumption; link value = downstream meter's consumption, split
proportionally across multiple upstreams; unaccounted remainder under a meter
becomes a synthetic "Other" node; depth via topological longest-path.
- SankeyChart.razor: hand-rolled inline-SVG Sankey (ApexCharts has no Sankey
type) — columns by depth, nodes stacked by value, bezier ribbons sized by flow,
left→right, theme-aware, HTML-encoded labels, tooltips. Built as a MarkupString
to sidestep Razor's <text> element clash.
- /energy/{id} page (one per energy type): KPIs (consumption + cost), the flow
Sankey, and the meter list. NavMenu now lists a link per energy type
(Electricity, Water, Gas, …) loaded from the DB.
- Meters admin: cycle-safe "Sub-meter of (upstream meters)" multi-select
(descendants excluded to prevent cycles); reconciles meter_link rows on save.
- Reference data seeds a demo chain (Haus → Auto) so electricity flow shows
Haus dividing into Auto + Other.
Tests: FlowServiceTests (single-parent remainder; two-parent proportional
split); render test now asserts the flow chain + covers /energy/{id}. 69 Core +
47 Integration = 116 green. Live-verified: Haus 95,450 kWh → Auto 51,909 +
Other 43,541 (flow conserved), all 5 energy-type pages render.
Claude-Session: https://claude.ai/code/session_01Lz2RqAsnQhetqWNoCDfexK
63 lines
2.9 KiB
C#
63 lines
2.9 KiB
C#
using MeterVault.Core.Normalization;
|
|
using MeterVault.Infrastructure.Import;
|
|
using MeterVault.Infrastructure.Ingestion;
|
|
using MeterVault.Infrastructure.Normalization;
|
|
using MeterVault.Infrastructure.Persistence;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace MeterVault.Infrastructure;
|
|
|
|
/// <summary>Composition root for the infrastructure layer (persistence, ingestion, import).</summary>
|
|
public static class DependencyInjection
|
|
{
|
|
public static IServiceCollection AddMeterVaultInfrastructure(
|
|
this IServiceCollection services,
|
|
string connectionString)
|
|
{
|
|
// A factory (for per-operation contexts in Blazor components/read services — a shared
|
|
// circuit-scoped DbContext is not thread-safe) plus a scoped context (from the factory)
|
|
// for request/tick-scoped services (API, workers, importers) that inject it directly.
|
|
services.AddDbContextFactory<MeterVaultDbContext>(options =>
|
|
options
|
|
.UseNpgsql(connectionString, npgsql =>
|
|
npgsql.MigrationsAssembly(typeof(MeterVaultDbContext).Assembly.FullName))
|
|
.UseSnakeCaseNamingConvention());
|
|
services.AddScoped<MeterVaultDbContext>(sp =>
|
|
sp.GetRequiredService<IDbContextFactory<MeterVaultDbContext>>().CreateDbContext());
|
|
|
|
services.AddSingleton<INormalizationEngine>(_ => NormalizationEngine.CreateDefault());
|
|
services.AddScoped<NormalizationService>();
|
|
services.AddScoped<CsvImporter>();
|
|
services.AddScoped<ImportService>();
|
|
services.AddScoped<ReferenceDataImporter>();
|
|
services.AddScoped<IngestionService>();
|
|
services.AddScoped<MqttMessageRouter>();
|
|
// HttpClient + HA tester are available even with live ingestion off, so the admin
|
|
// "Test connection" works without the background workers running.
|
|
services.AddHttpClient();
|
|
services.AddScoped<HaConnectionTester>();
|
|
services.AddScoped<Costing.CostService>();
|
|
services.AddScoped<Dashboard.DashboardService>();
|
|
services.AddScoped<Dashboard.SolarService>();
|
|
services.AddScoped<Dashboard.ConsumableService>();
|
|
services.AddScoped<Dashboard.MeterDetailService>();
|
|
services.AddScoped<Dashboard.FlowService>();
|
|
services.AddScoped<Backup.ExportService>();
|
|
|
|
return services;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers the live-ingestion background workers (MQTT/Tasmota + Home Assistant). Kept
|
|
/// separate from <see cref="AddMeterVaultInfrastructure"/> so tests can opt out of brokers.
|
|
/// </summary>
|
|
public static IServiceCollection AddMeterVaultIngestion(this IServiceCollection services)
|
|
{
|
|
services.AddHttpClient();
|
|
services.AddHostedService<MqttIngestionWorker>();
|
|
services.AddHostedService<HomeAssistantWorker>();
|
|
return services;
|
|
}
|
|
}
|