Appearance
Entity: gaugeSerialHistory
Entity Type: Database table
Description: Append-only history of serial number changes for a gauge. Each physical meter replacement or serial number correction creates a new record with a validFrom timestamp; the previous record is never modified or deleted. The current serial number is the one with the highest validFrom that does not exceed the query time. This pattern is the source of truth for meter replacement history and enables Reading Management to detect counter resets when a new record appears with a validFrom that falls between two readings. gauge.serialNumber mirrors the most recent value for fast lookup and display — the two must be kept consistent by the application on every insert. This entity follows the versioned / append-only pattern (deviation #16): updatedAt, updatedBy, and deletedAt are intentionally omitted because records are written once and never modified or deleted in-place.
Data Attributes Table
| Attribute Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
| id | Primary key of the entity. | UUID | Generated in code (app layer) | Yes | Yes | UUID v7 | - | Primary Key | 018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| tenantId | Tenant that owns this record. Denormalised from gauge for uniform data-scoping. Must equal the parent gauge's tenantId; enforced by the application on insert. | UUID | - | Yes | No | UUID v7 | Foreign Key → tenant; must exist; must equal parent gauge.tenantId | name: idx_gaugeSerialHistory_tenantId, type: btree | e5f6a7b8-… |
| gaugeId | The gauge whose serial number is recorded. | UUID | - | Yes | No | UUID v7 | Foreign Key → gauge; must exist | name: idx_gaugeSerialHistory_gaugeId_validFrom, type: btree (composite with validFrom DESC) | b2c3d4e5-… |
| serialNumber | The serial number of the physical meter that was in place from validFrom until the next record's validFrom (exclusive). | String | - | Yes | No | - | Max 100 characters; non-empty. | - | 1EMH0004579834 |
| validFrom | Timestamp from which this serial number became the active one. Records are applied to any query time that falls on or after this timestamp, up to the next record's validFrom. On the initial gauge creation this equals gauge.createdAt. | Timestamp with time zone | - | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Must be unique per gaugeId (no two records for the same gauge at the same timestamp). | name: idx_gaugeSerialHistory_gaugeId_validFrom, type: btree (composite with gaugeId) | 2025-06-01T08:00:00Z |
| replacedBy | Optional reference to the new gauge record that replaced this physical device, if the replacement involved installing a fundamentally different meter (e.g. new generation DO-capable device) that required a separate gauge record. Null for same-gauge serial number corrections. | UUID | null | No | No | UUID v7 | Foreign Key → gauge; null when the replacement stays on the same gauge record. | - | f1a2b3c4-… |
| createdAt | Timestamp of when this history record was created. Immutable after insert. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ | Cannot be null; cannot be modified after creation. | - | 2025-06-01T08:00:00Z |
| createdBy | Identifier of the actor who created the record (user ID, migration script, or system). | String | - | Yes | No | type:actor — e.g. user:uuid or system:migration | Non-empty. | - | user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
Note. This entity is documented as intentionally omitting updatedAt, updatedBy and deletedAt (versioned/append-only pattern, deviation #16 in the source project) — carried over faithfully rather than padded out to the full audit quintet.
Audited fields
Recorded on created only — this entity is explicitly append-only (deviation #16: no updatedAt/updatedBy/deletedAt; a correction is a new row with a later validFrom, never a mutation of an existing one): gaugeId, serialNumber, validFrom, replacedBy.
Excluded: none.
Every entry would carry subjectEntityType = gauge / subjectEntityId = gaugeId — per the same rule applied to readingActionLog, this entity already being its own history mechanism for serial numbers is not a reason to exempt it from the generic trail.
Not registered for entityName resolution — renders id-only in the audit trail (architecture 61-audit-log.md §7.4 in the code repo: a valid, permanent state, not a gap). If registered, serialNumber is the natural candidate.