95c51842e8
ci / build-test (push) Successful in 1m23s
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
68 lines
3.2 KiB
C#
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;
|
|
}
|
|
}
|