"""Widen ai_spending_summaries.language from VARCHAR(8) to VARCHAR(16). The default value the service writes is the 9-char literal ``'bilingual'``. The original column was VARCHAR(8); MySQL strict-mode rejects the INSERT with ``Data too long for column 'language' at row 1`` after the LLM call has already spent 30–60s of compute, so every "Regenerate AI summary" click produces a wasted Bailian API call and a generic 500 with no usable feedback to the admin (verified 2026-04-29 against the local AliCloud International tenant). The fix is structural — bump the column width — not a runtime truncation, because storing a truncated key would silently mismatch the unique constraint ``uq_ai_spending_summaries_period_lang`` and the read path in ``AiSpendingService.get_summary`` which both look up the literal value ``'bilingual'``. """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "083" down_revision = "082" branch_labels = None depends_on = None def upgrade() -> None: op.alter_column( "ai_spending_summaries", "language", existing_type=sa.String(length=8), type_=sa.String(length=16), existing_nullable=False, ) def downgrade() -> None: # Downgrade is destructive in principle (any row whose language # value exceeds 8 chars would be truncated), but the only non-en / # non-zh value in production is "bilingual" — and that's exactly # the value the upgrade was added to support. We refuse the # downgrade rather than silently corrupt data. raise RuntimeError( "Refusing to narrow ai_spending_summaries.language back to " "VARCHAR(8): would truncate existing 'bilingual' rows. Drop " "those rows first if you really need to roll back." )