import type { GroupedRow, TimeGrain } from '../db/repositories/usage-repository.js'; /** * Filling in the buckets that had no activity. * * `daily --days 30` returned only the days that HAD data -- ten rows for a * thirty-day window, with nothing to say the other twenty existed. Reading a * trend off that is not merely harder, it is actively misleading: the gaps are * invisible, so an ordinary day renders immediately beside one three weeks * earlier and looks like a spike next to it. * * A zero row is not a fabricated number. It asserts that no usage was recorded * in that bucket, which is exactly what the absence of rows meant -- it just * says it where a reader can see it. Every filled row carries `zeroFilled: true` * so a consumer can tell an observed zero from a constructed one. */ /** A bucket that was constructed rather than observed. */ export interface TimeBucket extends GroupedRow { zeroFilled?: boolean; } /** * Zero-filling an hourly range over all time would generate tens of thousands * of rows nobody asked for, so it is bounded and the caller is told when the * bound bit, rather than quietly returning a partial series. */ export declare const MAX_ZERO_FILLED_BUCKETS = 5000; export interface ZeroFillResult { rows: TimeBucket[]; /** Set when the range was too large to fill, explaining why it was not. */ note?: string; } /** * Returns the observed rows with every empty bucket in range filled in. * * @param bounds The window to fill. When the caller gave no explicit period, * pass the first and last activity instead -- filling from the epoch would * invent thousands of rows describing time before the data existed. */ export declare function zeroFill(rows: GroupedRow[], grain: TimeGrain, bounds: { since?: string; until?: string; }): ZeroFillResult;