"""Index the cloud daily-billing ingest watermark. Adds ``ix_billing_daily_fetched_at`` on ``cmdb_cloud_billing_daily(fetched_at)``, which backs the External API's incremental-pull signal (``GET /open/cloud/billing/changes`` and ``?updated_since=``): "which (subscription, day) cells were re-ingested since T". Why ``fetched_at`` and not ``updated_at`` — measured, not assumed (2026-08-15, on the real upsert path): fetched_at 17:45:21 → 17:45:23 advanced updated_at 17:45:21 → 17:45:21 frozen The daily-billing ingest writes through ``INSERT … ON DUPLICATE KEY UPDATE`` with ``fetched_at`` in the SET list, while ``updated_at`` only carries SQLAlchemy's client-side ``onupdate`` (never applied on that path) and the DDL has no ``ON UPDATE CURRENT_TIMESTAMP``. Indexing ``updated_at`` would have produced an incremental feed that silently returns nothing. Note the sibling table is the mirror image: ``cmdb_cost_facts`` puts ``updated_at=func.now()`` in its SET list and leaves ``fetched_at`` alone, so the facts-side signal reads ``updated_at``. That query is already bounded by ``(scope_id, date)`` (``ix_facts_scope_date``), so it needs no new index — deliberately, since an index on a multi-million-row table is not something to add for a read path that is already covered. Revision ID: 471 Revises: 470 """ from alembic import op revision = "471" down_revision = "470" branch_labels = None depends_on = None INDEX_NAME = "ix_billing_daily_fetched_at" TABLE = "cmdb_cloud_billing_daily" def upgrade() -> None: op.create_index(INDEX_NAME, TABLE, ["fetched_at"]) def downgrade() -> None: op.drop_index(INDEX_NAME, table_name=TABLE)