Appearance
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):
type | source = manual | source = remote | source = invoice |
|---|---|---|---|
energy, volume (meter registers — consumption, feed-in, production, battery; water, gas volume, fuel, PHM) | cumulative — the meter state read from the display | cumulative for a register state; delta for an interval quantity or an impulse count per interval, as the endpoint's register declares | delta — 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 Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
| id | Surrogate primary key. Combined with readAt into a composite PK, required because readAt is the hypertable partition column. | UUID | Generated in code | Yes | Part of composite PK | UUID v7 | - | Primary Key (composite with readAt) | 018f6e2a-1234-7abc-9def-0123456789ab |
| tenantId | Tenant this record belongs to. Included for consistency with the rest of the domain model, alongside schema-per-tenant isolation. | UUID | - | Yes | No | UUID v7 | Foreign Key → tenant; must exist | name: idx_reading_tenantId, type: btree | 018fa51f-fda1-79f4-8461-2cb8f1cabc10 |
| readAt | Point in time the value was measured. Hypertable partition column. | Timestamp with time zone | - | Yes | Part of composite unique | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | - | Hypertable partition key; part of composite unique index | 2026-08-07T11:22:00Z |
| gaugeId | Gauge 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 | - | Yes | Part of composite unique | UUID v7 | No DB-level FK — existence enforced at application/facade level. | Part of composite unique index | 018f6e2a-... |
| type | Measurement dimension: category of what is measured. | Enum | - | Yes | Part of composite unique | enum | - | Part of composite unique index | energy |
| direction | Measurement dimension: flow direction (DLMS/COSEM). | Enum | - | No | Part of composite unique | enum | - | Part of composite unique index | import |
| tariff | Measurement dimension: tariff band. | Enum | - | No | Part of composite unique | enum | - | Part of composite unique index | high |
| source | Origin of the value. | Enum | - | Yes | Part of composite unique | enum | - | Part of composite unique index | manual |
| format | Whether 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 | - | Yes | No | enum — GaugeReadingMode | - | - | cumulative |
| lifecycleState | Marks 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' | Yes | No (not part of composite unique) | enum | - | - | reading |
| value | The raw recorded value, as read or delivered; the source coefficient is not applied. | Double precision | - | Yes | No | - | - | - | 1234.56 |
| unit | Unit 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 | - | Yes | No | Enum - Unit | Equal to gaugeSourceSetting.unit valid at readAt. | - | kWh |
| importId | The 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. | UUID | null | No | No | UUID v7 | Foreign Key → dataImport; no DB-level constraint. | - | 018fa51f-fda1-79f4-8461-2cb8f1cabc10 |
| createdAt | Timestamp of record creation. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Cannot be null. | - | 2026-08-07T12:33:00Z |
| updatedAt | Timestamp of last update. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Cannot be null. | - | 2026-08-07T12:33:00Z |
| createdBy | Actor who created the record. | String | - | Yes | No | type:actor — e.g. user:uuid or system:migration | Non-empty. | - | user:018f6e2a-... |
| updatedBy | Actor who last updated the record. | String | - | Yes | No | type:actor — e.g. user:uuid or system:migration | Non-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.