Ingestion: derive consumption on ingest, and poll HA in minutes not seconds
ci / build-test (push) Successful in 1m13s
ci / build-test (push) Successful in 1m13s
Live ingestion wrote the raw reading and stopped there. Import, the REST push endpoint and the meter editor all recompute afterwards; the MQTT/Tasmota/HA path was the one that did not, so a polled reading landed in `reading` and every derived figure stayed frozen at the last import. Observed on a GenerationCounter: 45 readings, 44 consumption rows, generation pinned to the register value of the last imported reading. Recompute inline rather than behind a debounce. Normalizing a whole meter is cheap at metering cadence and a background dirty-set worker is machinery this does not yet need; the remark on RenormalizeAsync records when it would. Fixes a latent bug this surfaced in NormalizationService: ExecuteDelete drops the consumption rows in the database but leaves them in the change tracker, so a second recompute on the same context threw an identity conflict on (meter, time, kind). One worker scope ingesting two readings was enough to hit it. Detach the stale entries after the delete. Poll interval is now minutes, default 60, replacing seconds/60. A meter answers "how much this month, what will it cost" — an hourly sample answers that exactly as well as a per-second one, with far less raw volume (SDD §5.5). The `pollSeconds` key no longer binds, so existing sources fall back to the 60 default and move from every-60-seconds to hourly, which is the intent. A source that had deliberately set e.g. 300 seconds also lands on 60 minutes. Two test cleanups now delete consumption before the meter: live ingestion never produced any before, so the FK had nothing to trip on. Claude-Session: https://claude.ai/code/session_01V6joyergfvVLFEizH1hJLd
This commit is contained in:
@@ -92,6 +92,7 @@ public sealed class HomeAssistantWebSocketWorkerTests(TimescaleFixture fx)
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable(TokenEnvVar, null);
|
||||
await db.Consumption.ExecuteDeleteAsync();
|
||||
await db.Readings.ExecuteDeleteAsync();
|
||||
await db.MeterSources.ExecuteDeleteAsync();
|
||||
await db.IngestionEndpoints.ExecuteDeleteAsync();
|
||||
@@ -109,6 +110,10 @@ public sealed class HomeAssistantWebSocketWorkerTests(TimescaleFixture fx)
|
||||
.UseSnakeCaseNamingConvention());
|
||||
services.AddScoped<MeterVaultDbContext>(sp => sp.GetRequiredService<IDbContextFactory<MeterVaultDbContext>>().CreateDbContext());
|
||||
services.AddScoped<IngestionService>();
|
||||
// Ingestion derives consumption inline, so the normalizer has to be resolvable here too.
|
||||
services.AddSingleton<MeterVault.Core.Normalization.INormalizationEngine>(
|
||||
_ => MeterVault.Core.Normalization.NormalizationEngine.CreateDefault());
|
||||
services.AddScoped<MeterVault.Infrastructure.Normalization.NormalizationService>();
|
||||
// Ephemeral keys: this test's token comes from an env var, so nothing needs to outlive the run.
|
||||
services.AddSingleton<Microsoft.AspNetCore.DataProtection.IDataProtectionProvider>(
|
||||
new Microsoft.AspNetCore.DataProtection.EphemeralDataProtectionProvider());
|
||||
|
||||
Reference in New Issue
Block a user