Skip to content
Updated Sep 26, 2026 by Barča Dvořáková · Owner: analysisactiveentity Edit on GitHub

Entity: consumption ​

Entity Type: Database table (TimescaleDB hypertable, partitioned by slotAt)

Description: One calculated value for one gauge, one 15-minute interval and one data source. Consumption is derived data: it is computed from reading values and from invoice amounts, and can be rebuilt from them at any time. Values from different sources are stored side by side as parallel series and never overwrite one another — a gauge read both remotely and manually carries two series over the same intervals, and the series a consumer sees is chosen at read time (see Consumption Aggregation & Effective Consumption).

The same table carries money: a kind of costWithVat or costWithoutVat stores an amount spread from an invoice over the intervals that invoice covers, in the same shape as a quantity, so that summaries and comparisons read both from one place.

Business rule. For one gauge, one interval can hold exactly one value of the same kind, measured quantity, flow direction, tariff band, source and — for values derived from an invoice — the same invoice. An invoice and the credit note that corrects it cover the same period and are stored side by side, so the invoice is part of the row's identity; for values from readings and from virtual gauges it is null.

Data Attributes Table ​

Attribute NameDescriptionData TypeDefault ValueRequired (= Nullable)UniqueFormatValidationsIndexExample
idSurrogate primary key. Combined with slotAt into a composite primary key, because slotAt is the hypertable partition column.UUIDGenerated in code (app layer)YesPart of composite PKUUID v7-Primary Key (composite with slotAt)018f6e2a-1234-7abc-9def-0123456789ab
tenantIdTenant this record belongs to. Carried for consistency with the rest of the domain model; on a hypertable it is also the predicate the application-level tenant guard applies, since row-level security is not available here.UUID-YesNoUUID v7Foreign Key → tenant; must existname: idx_consumption_tenantId, type: btree018fa51f-fda1-79f4-8461-2cb8f1cabc10
gaugeIdGauge the value belongs to. Loose reference only — no foreign key, matching reading, because the hypertable is written at ingestion volume and the gauge lives in another bounded context.UUID-YesPart of composite uniqueUUID v7No database-level foreign key — existence enforced at application level.name: idx_consumption_gauge_slot_at, type: btree018f6e2a-…
slotAtStart of the 15-minute interval the value belongs to, in UTC. Hypertable partition column. A value always describes the whole interval, never an instant.Timestamp with time zone-YesPart of composite uniqueISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZMust be aligned to a quarter hour (00, 15, 30, 45 minutes, zero seconds).Hypertable partition key; part of composite unique index2026-01-15T09:15:00Z
kindWhether the value is a quantity or an amount of money.Enum-YesPart of composite uniqueEnum - ConsumptionKindAn entry from the enum.Part of composite unique indexconsumption
typeMeasured quantity. Required when kind is consumption; null for money, which is not a measured quantity.Enum-NoPart of composite uniqueEnum - ReadingTypeMust be null when kind is costWithVat or costWithoutVat.Part of composite unique indexenergy
directionFlow direction, from the building's point of view. null where the quantity has no direction.Enum-NoPart of composite uniqueEnum - GaugeDirection-Part of composite unique indeximport
tariffTariff band the value falls in. null where the gauge has no tariff split.Enum-NoPart of composite uniqueEnum - ReadingTariff-Part of composite unique indexhigh
sourceWhere the value came from: a remote series, a manual reading, an invoice, or a virtual-gauge calculation. Never mixed within one series.Enum-YesPart of composite uniqueEnum - ReadingSourceAn entry from the enum. calculated is reserved for virtual gauges.Part of composite unique indexremote
valueThe calculated value for the interval, in the unit named by unit. Exact decimal rather than floating point, so that a money series sums back to its invoice to the last hundredth.Decimal-YesNonumeric(18,6)Money rows carry at most two decimal places. Quantities may be negative (a correction, or an export series); money may be negative on a credit note.-12.480000
unitUnit the value is stored in — the canonical unit for the quantity (kWh for energy, m³ for volume, kW for power) or the currency for money. Not part of the row's identity: a value is stored once and converted for display.Enum-YesNoEnum - UnitMust belong to the unit family implied by kind and type.-kWh
invoiceIdInvoice the value was derived from. Set on every row whose source is invoice and on every money row; null otherwise. Loose reference, as for gaugeId. Part of the row's identity, so that an invoice and a credit note over the same period do not overwrite each other.UUIDnullNoPart of composite uniqueUUID v7Must be set when source is invoice or kind is a money kind; must be null otherwise — see invoice.name: idx_consumption_invoice_id, type: btree018f7b31-…
createdAtTimestamp of record creation.Timestamp with time zonenow() — set in codeYesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZCannot be null.-2026-01-16T02:11:00Z
updatedAtTimestamp of the last recalculation that changed this value.Timestamp with time zonenow() — set in codeYesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZCannot be null.-2026-03-02T08:40:00Z
createdByActor that wrote the value. Always a system actor — consumption is never entered by hand.String-YesNotype:actor — e.g. system:consumption-calculationNon-empty.-system:consumption-calculation
updatedByActor that last recalculated the value.String-YesNotype:actorNon-empty.-system:consumption-recalculation

Indexes ​

NameColumnsTypeWhy
uq_consumption_slotgaugeId, slotAt, kind, type, direction, tariff, source, invoiceId — nulls not distinctUniqueEnforces the business rule above and makes recalculation an upsert rather than a delete-and-insert.
idx_consumption_gauge_slot_atgaugeId, slotAt DESCbtreeThe read path of every chart, report and summary.
idx_consumption_invoice_idinvoiceIdbtreeRecalculating or removing the series of one corrected invoice.
idx_consumption_tenantIdtenantIdbtreeSupports the application-level tenant predicate.

Soft deletion ​

This entity has no deletedAt. A value is either recomputed in place or, where its input disappeared, removed by the recalculation that owns that gauge and period — there is no user-facing delete, and an interval with no input carries no row at all. An absent row means "not measured", which is not the same as a stored zero.

Audited fields ​

Not carried in an audit trail. Every row is machine-written and reproducible from its inputs, and the trail that matters is the one on those inputs: readings are audited by the reading action log, invoices by their own trail. createdBy and updatedBy name the calculation that wrote the row, which is what an investigation of a suspicious figure needs.

Not registered for entityName resolution — not an audited entity; a machine-written time-series row identified by its gaugeId/interval scope.

Row-level security ​

None at the database level. Like reading, this table is a TimescaleDB hypertable with compression, and row-level security is not usable together with it; tenant isolation is enforced by the same application-level hypertable guard, which requires the tenantId predicate on every statement.

Retention ​

None. Retention applies to the raw inputs only; the 15-minute series and their aggregates are kept indefinitely, so historical consumption stays reconstructable after the readings behind it have been thinned.