Skip to content
Updated Oct 5, 2026 by Pavel Coufal · Owner: analysisactiveentityconfluence-migration Edit on GitHub

Entity: reading ​

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

Description: Stores a raw, as-received measurement value for a gauge at a specific point in time. Represented as a flat row per combination of measurement dimensions (type, direction, tariff, source, format) rather than a header-plus-values structure — a reading with multiple simultaneous values (e.g. high/low tariff) is stored as multiple rows sharing the same gauge and timestamp. Owned by the Ingestion domain; consumed downstream by Processing (Consumption), which derives the normalised time series from these raw readings.

Resolved — tenantId included. Tenant isolation for this entity is primarily handled at the schema level (schema-per-tenant), but a tenantId column is included regardless, for consistency with the rest of the domain model (defence-in-depth). Confirmed no technical constraint (e.g. hypertable partitioning) prevents this.

unit stored on each row. The unit a value was recorded in is kept on the row, taken at write time from the gaugeSourceSetting of the row's gaugeId and source valid at readAt — not from the gauge, whose unit is the canonical output unit. A manual and a remote row of the same gauge can therefore carry different units (gas: m³ by hand, impulses remotely). A unit change is a new source setting valid from a date (a meter replacement writes one — see Meter Replacement Flow); rows written before keep the unit they were written in, so a stored value never changes meaning. A write without a source setting valid at readAt is refused (ERR_SOURCE_SETTING_MISSING). Consumers convert to the gauge's canonical unit, applying the setting's coefficient, when they combine rows.

format is derived, not chosen. The row's format follows from the measured parameter's type and the source, never from a gauge attribute (gauge.readingMode is retired):

typesource = manualsource = remotesource = invoice
energy, volume (meter registers — consumption, feed-in, production, battery; water, gas volume, fuel, PHM)cumulative — the meter state read from the displaycumulative for a register state; delta for an interval quantity or an impulse count per interval, as the endpoint's register declaresdelta — the billed quantity of the period
power— (not read by hand)average — the interval's mean power—
temperature, humidity, co2 (KVP)— (not read by hand)average—

A virtual gauge of the Ruční zadání subtype enters period quantities by hand: its rows are source = manual, format = delta. importId is a real column — see the table and Audited fields below.

Data Attributes Table ​

Attribute NameDescriptionData TypeDefault ValueRequired (= Nullable)UniqueFormatValidationsIndexExample
idSurrogate primary key. Combined with readAt into a composite PK, required because readAt is the hypertable partition column.UUIDGenerated in codeYesPart of composite PKUUID v7-Primary Key (composite with readAt)018f6e2a-1234-7abc-9def-0123456789ab
tenantIdTenant this record belongs to. Included for consistency with the rest of the domain model, alongside schema-per-tenant isolation.UUID-YesNoUUID v7Foreign Key → tenant; must existname: idx_reading_tenantId, type: btree018fa51f-fda1-79f4-8461-2cb8f1cabc10
readAtPoint in time the value was measured. Hypertable partition column.Timestamp with time zone-YesPart of composite uniqueISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ-Hypertable partition key; part of composite unique index2026-08-07T11:22:00Z
gaugeIdGauge this reading was recorded for. Loose reference only — no FK constraint, since Gauge belongs to a different bounded context (assets) and the boundary between them is facade-only.UUID-YesPart of composite uniqueUUID v7No DB-level FK — existence enforced at application/facade level.Part of composite unique index018f6e2a-...
typeMeasurement dimension: category of what is measured.Enum-YesPart of composite uniqueenum-Part of composite unique indexenergy
directionMeasurement dimension: flow direction (DLMS/COSEM).Enum-NoPart of composite uniqueenum-Part of composite unique indeximport
tariffMeasurement dimension: tariff band.Enum-NoPart of composite uniqueenum-Part of composite unique indexhigh
sourceOrigin of the value.Enum-YesPart of composite uniqueenum-Part of composite unique indexmanual
formatWhether the value is a running total, a discrete increment, or a rate to be multiplied by interval duration. Derived at write time per the matrix above: average for type = power and the KVP types; cumulative for a meter register read by hand or delivered as a state; delta for an interval quantity (remote interval, invoice, manual period entry).Enum-YesNoenum — GaugeReadingMode--cumulative
lifecycleStateMarks whether this reading is the initial (newly installed gauge), final (before replacement) or regular reading in between. Set exclusively by the Meter Replacement Flow (for initial/final) — never set directly through the normal write path.Enum'reading'YesNo (not part of composite unique)enum--reading
valueThe raw recorded value, as read or delivered; the source coefficient is not applied.Double precision-YesNo---1234.56
unitUnit the value was recorded in, from the source setting of (gaugeId, source) valid at readAt. Never updated when a later setting changes the unit.Enum-YesNoEnum - UnitEqual to gaugeSourceSetting.unit valid at readAt.-kWh
importIdThe bulk-import batch that produced this reading, if any. Loose reference — no FOREIGN KEY (a constraint on this hypertable overflows past ~46 chunks); pre-existing rows stay null permanently, with no backfill.UUIDnullNoNoUUID v7Foreign Key → dataImport; no DB-level constraint.-018fa51f-fda1-79f4-8461-2cb8f1cabc10
createdAtTimestamp of record creation.Timestamp with time zonenow() — set in codeYesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZCannot be null.-2026-08-07T12:33:00Z
updatedAtTimestamp of last update.Timestamp with time zonenow() — set in codeYesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZCannot be null.-2026-08-07T12:33:00Z
createdByActor who created the record.String-YesNotype:actor — e.g. user:uuid or system:migrationNon-empty.-user:018f6e2a-...
updatedByActor who last updated the record.String-YesNotype:actor — e.g. user:uuid or system:migrationNon-empty.-user:018f6e2a-...

Primary key: composite (id, readAt) — required because readAt is the hypertable partition column.

Business-identity dedup constraint (separate from the PK): UNIQUE (gaugeId, readAt, type, direction, tariff, source), treating NULLs as not distinct.

Note. This entity has no deletedAt column, confirmed intentional: reading uses hard delete exclusively (DeleteReadingUseCase → deleteByKey), and the audit-log deleted entry — carrying the row's full state as it stood immediately before removal — is the durable record of a deleted reading's last state.

Audited fields ​

Recorded on created (in full), updated (changed only, value or lifecycleState only — no other field is ever touched post-creation; a reading entry moved to another moment is a deleted and a created) and deleted (in full): readAt, gaugeId, type, direction, tariff, source, format, value, unit, lifecycleState, importId.

Excluded: none.

Every entry carries subjectEntityType = gauge / subjectEntityId = gaugeId — reading has no human-readable key of its own, and gaugeId is the realistic lookup key for "what happened to this meter's readings."

Not registered for entityName resolution — a hypertable row identified by its gaugeId/readAt/dimension scope, not a named record.