Fix estimate silently dropping the gap between last reading and today

remainingDays was computed as days-from-today to period-end, while
actual-to-date consumption only ever reflects data up to the meter's
last real reading. With sparse readings the last reading sits days
before today, so that gap was counted in neither the actual nor the
projected total. Project from the last reading's date instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-28 14:04:49 +00:00
parent 0b9ac16449
commit bed0b134dc

View file

@ -350,10 +350,17 @@ export async function getMeterEstimateForPeriod(meterId, periodStart, periodEnd,
const today = toISODate(new Date())
const asOfDate = today < periodEnd ? today : periodEnd
const daysInPeriod = daysInclusive(periodStart, periodEnd)
const remainingDays = Math.max(daysBetween(asOfDate, periodEnd), 0)
const { daily_rate } = await getTrailingDailyRate(meterId, windowDays)
const { consumption: actualToDate, has_data } = await getPeriodConsumption(meterId, periodStart, asOfDate)
const { consumption: actualToDate, has_data, last: lastReading } = await getPeriodConsumption(meterId, periodStart, asOfDate)
// Project from the meter's last actual reading, not from "today" — a meter
// read only every couple of weeks will almost always have its last reading
// sitting days before today, and projecting only from today would silently
// drop that gap from both the actual and projected totals.
const lastKnownDate = lastReading ? toISODate(lastReading.reading_date) : periodStart
const projectionFrom = lastKnownDate > periodStart ? lastKnownDate : periodStart
const remainingDays = Math.max(daysBetween(projectionFrom, periodEnd), 0)
let projectedConsumption = null
if (daily_rate != null) {