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:
@@ -77,6 +77,27 @@ public sealed class MeterPeriodServiceTests(TimescaleFixture fx)
|
||||
await CleanupAsync(db, meterId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_negative_previous_period_reports_no_basis_rather_than_an_inverted_percentage()
|
||||
{
|
||||
// Net export: -100 -> -150 is half again as much exported, but dividing by a negative
|
||||
// baseline would render it "+50%", which reads as more consumption.
|
||||
await using var db = fx.CreateContext();
|
||||
var meterId = await SetupAsync(db, MeterMode.CumulativeCounter);
|
||||
var today = DateOnly.FromDateTime(DateTime.UtcNow);
|
||||
var thisMonth = new DateOnly(today.Year, today.Month, 1);
|
||||
|
||||
await AddConsumptionAsync(db, meterId, ConsumptionKind.Consumption, thisMonth.AddDays(1), -150);
|
||||
await AddConsumptionAsync(db, meterId, ConsumptionKind.Consumption, thisMonth.AddMonths(-1).AddDays(3), -100);
|
||||
|
||||
var view = await NewService().GetAsync(meterId);
|
||||
|
||||
Assert.NotNull(view);
|
||||
Assert.Null(view!.MonthChange);
|
||||
|
||||
await CleanupAsync(db, meterId);
|
||||
}
|
||||
|
||||
private MeterPeriodService NewService()
|
||||
{
|
||||
var options = Microsoft.Extensions.Options.Options.Create(
|
||||
|
||||
Reference in New Issue
Block a user