Files
MeterVault/src/Infrastructure/DependencyInjection.cs
T
schmidt.florian 95c51842e8
ci / build-test (push) Successful in 1m23s
Meter detail: lead with periods and change, not register totals
The headline tiles were lifetime consumption, a raw reading count and the
register span. None of those answer why someone opens a meter: how much this
month, more or less than last, where the year lands, what it costs. A
cumulative counter's register value is an accident of when the meter was
installed.

MeterPeriodService buckets consumption by calendar month in the instance
timezone -- via date_trunc(... AT TIME ZONE) rather than EF grouping, because a
reading at 00:30 local on 1 January is 23:30 on 31 December in UTC and would be
booked to the wrong month (SDD §10). It reports generation for a generation
counter and consumption otherwise, so a PV meter stops claiming it consumed
0 kWh.

Month- and year-to-date are compared against a projection of the current period
rather than its running total. Three days into a month, "12 kWh vs 340 kWh last
month" reads as a collapse in usage when nothing has changed. The projection is
straight-line on elapsed days -- wrong for anything seasonal, but the honest
reading of "at this rate" -- and the UI marks it with a leading ~.

A 12-month bar strip gives the shape at a glance. A meter with nothing
normalized yet returns an empty history rather than a flat line, which would
look like a meter reading zero.

Register span, reading count and lifetime total move into a collapsed panel.
Still there when needed for an audit, no longer the first thing you see.

Claude-Session: https://claude.ai/code/session_01V6joyergfvVLFEizH1hJLd
2026-07-18 19:10:32 +02:00

68 lines
3.2 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>();
// Singleton: wraps one IDataProtector, and the ingestion workers (themselves singletons)
// resolve connector secrets on every reconnect.
services.AddSingleton<Security.SecretProtector>();
services.AddScoped<Costing.CostService>();
services.AddScoped<Dashboard.DashboardService>();
services.AddScoped<Dashboard.SolarService>();
services.AddScoped<Dashboard.MeterPeriodService>();
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>();
services.AddHostedService<HomeAssistantWebSocketWorker>();
return services;
}
}