Meter detail: lead with periods and change, not register totals
ci / build-test (push) Successful in 1m23s
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
This commit is contained in:
@@ -8,6 +8,47 @@ public sealed record ReadingRow(DateTimeOffset Time, double Value, ReadingQualit
|
||||
/// <summary>A normalized consumption row for the meter-detail table.</summary>
|
||||
public sealed record ConsumptionDetailRow(DateTimeOffset Time, double Amount, ConsumptionKind Kind, ReadingQuality Quality);
|
||||
|
||||
/// <summary>One calendar month of a meter's normalized history, bucketed in the instance timezone.</summary>
|
||||
public sealed record MeterMonthPoint(DateOnly Month, double Amount, double Cost);
|
||||
|
||||
/// <summary>
|
||||
/// A meter framed the way it is actually read: what it used this period, how that compares with the
|
||||
/// last one, and where the year is heading. Amounts are generation for a generation counter and
|
||||
/// consumption otherwise, so <see cref="Label"/> says which.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Month- and year-to-date are compared against a <em>projection</em> of the current period rather
|
||||
/// than its raw running total: three days into a month, "12 kWh vs 340 kWh last month" reads as a
|
||||
/// collapse in usage when nothing has changed. Projections are flagged so the UI can mark them.
|
||||
/// </remarks>
|
||||
public sealed record MeterPeriodView(
|
||||
string Label,
|
||||
string Unit,
|
||||
string Currency,
|
||||
double MonthToDate,
|
||||
double MonthProjected,
|
||||
double LastMonth,
|
||||
double YearToDate,
|
||||
double YearProjected,
|
||||
double LastYear,
|
||||
double YearToDateCost,
|
||||
double YearProjectedCost,
|
||||
double LastYearCost,
|
||||
bool MonthIsPartial,
|
||||
IReadOnlyList<MeterMonthPoint> Last12Months)
|
||||
{
|
||||
/// <summary>Projected month against last month, as a fraction (+0.12 = 12% more). Null if no basis.</summary>
|
||||
public double? MonthChange => Ratio(MonthProjected, LastMonth);
|
||||
|
||||
/// <summary>Projected year against last year, as a fraction. Null if no basis.</summary>
|
||||
public double? YearChange => Ratio(YearProjected, LastYear);
|
||||
|
||||
public bool HasHistory => Last12Months.Count > 0;
|
||||
|
||||
private static double? Ratio(double current, double previous) =>
|
||||
Math.Abs(previous) < 1e-9 ? null : (current - previous) / previous;
|
||||
}
|
||||
|
||||
/// <summary>A meter lifecycle/correction event row.</summary>
|
||||
public sealed record EventRow(DateTimeOffset Time, MeterEventType Type, double? Amount, double? PrevValue, double? NewValue, string? Unit, string? Notes);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user