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

Entity: readingActionLog ​

Entity Type: Database table

Description: A domain-specific action log recording every value-changing action taken on a reading row — who changed it, when, and the value before and after. Entries outlive the reading they describe: readingId, readAt and gaugeId are loose references (plain columns, no FOREIGN KEY), since an enforced FK to the reading hypertable would make compress_chunk() fail and would block the very delete this log has to record. importId, by contrast, is a real, enforced Foreign Key to dataImport.id — dataImport is an ordinary table, not a hypertable, so it carries none of that risk. The row is create-only — no update or delete method exists on its repository port — and it carries no soft-delete column: once written, it is permanent.

Data Attributes Table ​

Attribute NameDescriptionData TypeDefault ValueRequired (= Nullable)UniqueFormatValidationsIndexExample
idPrimary key of the entity. Application-supplied — no DB-level default.UUID-YesYesUUID v7-Primary Key018fa51f-fda1-79f4-8461-2cb8f1cabc10
tenantIdTenant this record belongs to.UUID-YesNoUUID v7Foreign Key → tenant; must exist-018fa51f-fda3-7c63-b5a9-3fa33dc989de
readingIdThe reading this action concerns. Loose reference — no FOREIGN KEY, deliberately, so the log survives the reading's own deletion or hypertable compression.UUID-YesNoUUID v7-name: idx_reading_action_log_reading (reading_id, created_at DESC, id DESC), type: btree018fa51f-fda9-...
readAtThe reading's own timestamp, carried alongside readingId so the log stays meaningful after the reading row is gone. Loose reference, same reasoning as readingId.Timestamp with time zone-YesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ--2026-03-01T00:00:00Z
gaugeIdThe gauge the reading belongs to. Loose reference, same reasoning as readingId.UUID-YesNoUUID v7--018fa51f-...
actionWhat happened to the reading. create is defined by the CHECK constraint and the enum but has no producer in code today.String (enum)-YesNoenum: create, edit, delete, clearFinalMust be one of the listed values-edit
valueBeforeThe reading's value before this action.Double precision, nullablenullconditionalNo-reading_action_log_values_per_action CHECK — must be NULL for create/clearFinal, NOT NULL for edit/delete-1024.5
valueAfterThe reading's value after this action.Double precision, nullablenullconditionalNo-Same CHECK as valueBefore, mirrored — must be NULL for delete/clearFinal, NOT NULL for create/edit-1080.2
noteFree-text note about the action.String, nullablenullNoNo---Corrected per customer complaint
importIdThe import batch that produced the original reading, if any. Real, enforced Foreign Key — see Description.UUID, nullablenullNoNoUUID v7Foreign Key → dataImport.idname: idx_reading_action_log_import, type: btree018fa51f-...
createdAtTimestamp this log row was written. Immutable — the row is never updated.Timestamp with time zonenow()YesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ--2026-03-01T09:00:00Z
updatedAtPresent for schema consistency (auditFields mixin); never meaningfully changed, since the row is create-only.Timestamp with time zonenow()YesNoISO 8601 — YYYY-MM-DDTHH:mm:ss.SSSZ--2026-03-01T09:00:00Z
createdByActor who performed the action.String-YesNotype:actor--user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2
updatedByPresent for schema consistency; equals createdBy in practice, since the row is never updated.String-YesNotype:actor--user:018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2

No deletedAt column — this table uses the plain auditFields mixin, not auditFieldsWithSoftDelete; unlike most other EM3 entities, it has no soft-delete state at all.

Audited fields ​

Recorded on created only — no update or delete method exists on ReadingActionLogRepositoryPort (insert, findPageByReading only): readingId, readAt, gaugeId, action, valueBefore, valueAfter, note, importId.

Excluded: none — no secret, no blob; note is a plain field a user entered deliberately, audited not redacted.

Every entry carries subjectEntityType = reading / subjectEntityId = readingId — readingActionLog does not self-subject, even though it is itself a domain-specific version store: the generic concept's own rule is that already having a history of its own is not a reason to exempt an entity from audit adoption. This makes "everything that happened to reading X" — its own audit trail plus every action logged about it — one subject-pinned read. Not registered for entityName resolution, and has no independent human-readable attribute to register.