Audit fixes: batch recompute, negative-baseline percentages, key-ring persistence
ci / build-test (push) Successful in 1m17s
ci / build-test (push) Successful in 1m17s
Three defects found reviewing the last few commits. Deriving consumption on ingest made the batch reading endpoint quadratic. A recompute rewrites a meter's entire consumption series, and POST /api/v1/readings ran one per reading -- 500 readings for one meter meant 500 full rewrites. IngestByMeterAsync takes renormalize:false and the endpoint normalizes each touched meter once after the batch. Percentage change divided by a possibly negative baseline. A net-export meter going from -100 to -150 exported half again as much and would have been reported as "+50%", reading as more consumption. A non-positive baseline now reports no basis rather than a confident lie. The data-protection key ring had no persistent home outside Docker Compose. The LXC installer now creates /var/lib/metervault/keys at 0700 -- the app would otherwise create it under the default umask, leaving a key ring world-readable -- and the Unraid template maps it, since without that every UI-entered secret was lost whenever the container was recreated. README documents the variable and the trust boundary: keys on disk protect against leaked database content, not against an attacker who already has the host. Claude-Session: https://claude.ai/code/session_01V6joyergfvVLFEizH1hJLd
This commit is contained in:
@@ -36,17 +36,25 @@ public static class ApiEndpoints
|
||||
}
|
||||
|
||||
int written = 0, updated = 0, rejected = 0, ignored = 0;
|
||||
var touched = new HashSet<int>();
|
||||
foreach (var r in readings)
|
||||
{
|
||||
switch (await ingestion.IngestByMeterAsync(r.MeterId, r.Time, r.Value, ct))
|
||||
// Normalize once per meter after the batch, not per reading: a recompute rewrites the
|
||||
// meter's whole consumption series, so doing it inside the loop is quadratic.
|
||||
switch (await ingestion.IngestByMeterAsync(r.MeterId, r.Time, r.Value, renormalize: false, ct))
|
||||
{
|
||||
case IngestionOutcome.Written: written++; break;
|
||||
case IngestionOutcome.Updated: updated++; break;
|
||||
case IngestionOutcome.Written: written++; touched.Add(r.MeterId); break;
|
||||
case IngestionOutcome.Updated: updated++; touched.Add(r.MeterId); break;
|
||||
case IngestionOutcome.RejectedDecrease: rejected++; break;
|
||||
default: ignored++; break; // unknown meter
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var meterId in touched)
|
||||
{
|
||||
await ingestion.RenormalizeMeterAsync(meterId, ct);
|
||||
}
|
||||
|
||||
return Results.Ok(new IngestResult(written, updated, rejected, ignored));
|
||||
}).WithSummary("Ingest one or more readings (idempotent). Lets Home Assistant push.");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user