diff --git a/app/db.py b/app/db.py index d951c76..0298be3 100644 --- a/app/db.py +++ b/app/db.py @@ -1,5 +1,5 @@ from collections.abc import Generator -from sqlalchemy import create_engine +from sqlalchemy import create_engine, inspect, text from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from app.core import get_settings @@ -25,3 +25,36 @@ def init_db() -> None: from app import models # noqa: F401 Base.metadata.create_all(bind=engine) + apply_safe_schema_patches() + + +def apply_safe_schema_patches() -> None: + """Tiny compatibility layer for the early MVP. + + SQLAlchemy create_all creates missing tables but does not alter existing ones. + During rapid MVP iterations this keeps old server databases from breaking + when a new column is added. + """ + inspector = inspect(engine) + if 'bot_state' in inspector.get_table_names(): + existing = {column['name'] for column in inspector.get_columns('bot_state')} + patches = [] + if 'live_acknowledged' not in existing: + patches.append("ALTER TABLE bot_state ADD COLUMN live_acknowledged BOOLEAN NOT NULL DEFAULT FALSE") + if 'emergency_stop' not in existing: + patches.append("ALTER TABLE bot_state ADD COLUMN emergency_stop BOOLEAN NOT NULL DEFAULT FALSE") + if 'trade_mode' not in existing: + patches.append("ALTER TABLE bot_state ADD COLUMN trade_mode VARCHAR(16) NOT NULL DEFAULT 'demo'") + if 'enabled' not in existing: + patches.append("ALTER TABLE bot_state ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT FALSE") + if 'trade_style_mode' not in existing: + patches.append("ALTER TABLE bot_state ADD COLUMN trade_style_mode VARCHAR(32) NOT NULL DEFAULT 'balanced'") + if 'min_signal_score' not in existing: + patches.append("ALTER TABLE bot_state ADD COLUMN min_signal_score FLOAT NOT NULL DEFAULT 65.0") + if 'max_open_positions' not in existing: + patches.append("ALTER TABLE bot_state ADD COLUMN max_open_positions INTEGER NOT NULL DEFAULT 3") + if 'max_quote_per_trade' not in existing: + patches.append("ALTER TABLE bot_state ADD COLUMN max_quote_per_trade FLOAT NOT NULL DEFAULT 100.0") + with engine.begin() as conn: + for patch in patches: + conn.execute(text(patch))