"""Realized cost-optimization savings ledger. Revision ID: 423 Revises: 422 Create Date: 2026-07-20 Why this exists --------------- The cloud efficiency feature reported only ESTIMATED savings ("if you acted, you'd save $X"). Leadership asked the obvious question — did we ACTUALLY save money, how much, and how. This ledger closes the loop: an audit post-log hook (``cmdb.savings_tracker``) records a row here when a server that was flagged idle/oversized is actually destroyed or stopped, snapshotting the pre-action monthly cost baseline. The realized amount is derived at read time (``savings_service``) as baseline − actual post-action cost from ``cmdb_cost_facts`` — so a destroy shows the full drop and a restarted box self-corrects to ~0 (no overcount, no cron). ``server_id`` is a plain string (NOT an FK): the server is typically soft-deleted / terminated right after the action, and the outcome must survive that. One row per server (``uq_savings_event_server``). Idempotency: ``CREATE TABLE IF NOT EXISTS`` via ``_table_exists``. """ from __future__ import annotations import sqlalchemy as sa from alembic import op from app.core.migration_helpers import id_type_for, table_kw_for revision = "423" down_revision = "422" branch_labels = None depends_on = None TABLE = "cmdb_efficiency_savings_events" def _table_exists(bind, name: str) -> bool: return bind.execute( sa.text( "SELECT 1 FROM information_schema.TABLES " "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :n" ), {"n": name}, ).scalar() is not None def upgrade() -> None: bind = op.get_bind() id_t = id_type_for("cmdb_servers") kw = table_kw_for("cmdb_servers") if _table_exists(bind, TABLE): return op.create_table( TABLE, sa.Column("id", id_t, primary_key=True), sa.Column("server_id", sa.String(36), nullable=False), sa.Column("instance_id", sa.String(512), nullable=True), sa.Column("integration_id", sa.String(36), nullable=True), sa.Column("scope_id", sa.String(36), nullable=True), sa.Column("method", sa.String(16), nullable=False), sa.Column("verdict_at_action", sa.String(32), nullable=True), sa.Column("monthly_cost_baseline_usd", sa.Numeric(14, 2), nullable=True), sa.Column("cost_currency", sa.String(8), nullable=True), sa.Column("owner_person_id", sa.String(36), nullable=True), sa.Column("owner_department", sa.String(256), nullable=True), sa.Column("acted_by_person_id", sa.String(36), nullable=True), sa.Column( "acted_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column( "updated_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.UniqueConstraint("server_id", name="uq_savings_event_server"), **kw, ) op.create_index("ix_savings_event_acted", TABLE, ["acted_at"]) op.create_index("ix_savings_event_integration", TABLE, ["integration_id"]) def downgrade() -> None: bind = op.get_bind() if _table_exists(bind, TABLE): op.drop_table(TABLE)