"""decouple dsmr_reading from the telegram id The DSMR Reader's own ``id`` field is auto-incrementing but is known to overflow and require a manual reset to zero (a long-standing DSMR firmware quirk). If we keep a UNIQUE constraint on ``source_id`` (the telegram id) and use it for idempotency, an overflow/reset would make legitimately-new telegrams collide with old ids and be silently dropped as "duplicates" — data loss. This migration removes that coupling: - Drops the UNIQUE constraint on ``source_id`` (the column is kept as a plain, nullable reference value; it is no longer relied upon for uniqueness/dedup). - Makes ``recorded_at`` (the telegram timestamp) UNIQUE instead — a single P1 meter emits one telegram per timestamp, so this is a robust, telegram-id- independent idempotency key. The table's own autoincrement ``id`` PK remains the stable internal identity. Revision ID: 20260624_12_dsmr_decouple_telegram_id Revises: 20260623_11_energy_tables Create Date: 2026-06-24 00:00:00.000000 """ from typing import Sequence, Union from alembic import op revision: str = "20260624_12_dsmr_decouple_telegram_id" down_revision: Union[str, None] = "20260623_11_energy_tables" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # SQLite cannot ALTER away a constraint in place, so recreate the table via # Alembic's batch mode. The table is recreated and rows are copied; existing # data (if any) is preserved. recorded_at must be unique for this to succeed # — a single meter never emits two telegrams at the exact same timestamp. with op.batch_alter_table("dsmr_reading", schema=None) as batch_op: # Old non-unique index on recorded_at is replaced by the unique constraint. batch_op.drop_index("ix_dsmr_reading_recorded_at") # Telegram id is no longer a uniqueness/idempotency key. batch_op.drop_constraint("uq_dsmr_reading_source_id", type_="unique") # Timestamp becomes the telegram-id-independent dedup key. batch_op.create_unique_constraint( "uq_dsmr_reading_recorded_at", ["recorded_at"] ) def downgrade() -> None: with op.batch_alter_table("dsmr_reading", schema=None) as batch_op: batch_op.drop_constraint("uq_dsmr_reading_recorded_at", type_="unique") batch_op.create_unique_constraint( "uq_dsmr_reading_source_id", ["source_id"] ) batch_op.create_index( "ix_dsmr_reading_recorded_at", ["recorded_at"], unique=False )