"""add_external_auth_schema_with_task_isolation

Revision ID: 5e75a59fb763
Revises: 271dc036ea80
Create Date: 2025-11-14 16:01:51.940093

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision: str = '5e75a59fb763'
down_revision: Union[str, None] = '271dc036ea80'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    """Upgrade schema - only create new V2 tables, do not drop existing tables."""
    # ### commands auto generated by Alembic - please adjust! ###
    # Create new tool_ocr_* tables for V2 schema
    op.create_table('tool_ocr_users',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('email', sa.String(length=255), nullable=False, comment='Primary identifier from Azure AD'),
    sa.Column('display_name', sa.String(length=255), nullable=True, comment='Display name from API response'),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('last_login', sa.DateTime(), nullable=True),
    sa.Column('is_active', sa.Boolean(), nullable=False),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_tool_ocr_users_email'), 'tool_ocr_users', ['email'], unique=True)
    op.create_index(op.f('ix_tool_ocr_users_id'), 'tool_ocr_users', ['id'], unique=False)
    op.create_index(op.f('ix_tool_ocr_users_is_active'), 'tool_ocr_users', ['is_active'], unique=False)
    op.create_table('tool_ocr_sessions',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('user_id', sa.Integer(), nullable=False, comment='Foreign key to users table'),
    sa.Column('access_token', sa.Text(), nullable=True, comment='Encrypted JWT access token from external API'),
    sa.Column('id_token', sa.Text(), nullable=True, comment='Encrypted JWT ID token from external API'),
    sa.Column('refresh_token', sa.Text(), nullable=True, comment='Encrypted refresh token (if provided by API)'),
    sa.Column('token_type', sa.String(length=50), nullable=False, comment="Token type (typically 'Bearer')"),
    sa.Column('expires_at', sa.DateTime(), nullable=False, comment='Token expiration timestamp from API'),
    sa.Column('issued_at', sa.DateTime(), nullable=False, comment='Token issue timestamp from API'),
    sa.Column('ip_address', sa.String(length=45), nullable=True, comment='Client IP address (IPv4/IPv6)'),
    sa.Column('user_agent', sa.String(length=500), nullable=True, comment='Client user agent string'),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('last_accessed_at', sa.DateTime(), nullable=False, comment='Last time this session was used'),
    sa.ForeignKeyConstraint(['user_id'], ['tool_ocr_users.id'], ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_tool_ocr_sessions_created_at'), 'tool_ocr_sessions', ['created_at'], unique=False)
    op.create_index(op.f('ix_tool_ocr_sessions_expires_at'), 'tool_ocr_sessions', ['expires_at'], unique=False)
    op.create_index(op.f('ix_tool_ocr_sessions_id'), 'tool_ocr_sessions', ['id'], unique=False)
    op.create_index(op.f('ix_tool_ocr_sessions_user_id'), 'tool_ocr_sessions', ['user_id'], unique=False)
    op.create_table('tool_ocr_tasks',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('user_id', sa.Integer(), nullable=False, comment='Foreign key to users table'),
    sa.Column('task_id', sa.String(length=255), nullable=False, comment='Unique task identifier (UUID)'),
    sa.Column('filename', sa.String(length=255), nullable=True),
    sa.Column('file_type', sa.String(length=50), nullable=True),
    sa.Column('status', sa.Enum('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED', name='taskstatus'), nullable=False),
    sa.Column('result_json_path', sa.String(length=500), nullable=True, comment='Path to JSON result file'),
    sa.Column('result_markdown_path', sa.String(length=500), nullable=True, comment='Path to Markdown result file'),
    sa.Column('result_pdf_path', sa.String(length=500), nullable=True, comment='Path to searchable PDF file'),
    sa.Column('error_message', sa.Text(), nullable=True, comment='Error details if task failed'),
    sa.Column('processing_time_ms', sa.Integer(), nullable=True, comment='Processing time in milliseconds'),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.Column('updated_at', sa.DateTime(), nullable=False),
    sa.Column('completed_at', sa.DateTime(), nullable=True),
    sa.Column('file_deleted', sa.Boolean(), nullable=False, comment='Track if files were auto-deleted'),
    sa.ForeignKeyConstraint(['user_id'], ['tool_ocr_users.id'], ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_tool_ocr_tasks_created_at'), 'tool_ocr_tasks', ['created_at'], unique=False)
    op.create_index(op.f('ix_tool_ocr_tasks_filename'), 'tool_ocr_tasks', ['filename'], unique=False)
    op.create_index(op.f('ix_tool_ocr_tasks_id'), 'tool_ocr_tasks', ['id'], unique=False)
    op.create_index(op.f('ix_tool_ocr_tasks_status'), 'tool_ocr_tasks', ['status'], unique=False)
    op.create_index(op.f('ix_tool_ocr_tasks_task_id'), 'tool_ocr_tasks', ['task_id'], unique=True)
    op.create_index(op.f('ix_tool_ocr_tasks_user_id'), 'tool_ocr_tasks', ['user_id'], unique=False)
    op.create_table('tool_ocr_task_files',
    sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
    sa.Column('task_id', sa.Integer(), nullable=False, comment='Foreign key to tasks table'),
    sa.Column('original_name', sa.String(length=255), nullable=True),
    sa.Column('stored_path', sa.String(length=500), nullable=True, comment='Actual file path on server'),
    sa.Column('file_size', sa.Integer(), nullable=True, comment='File size in bytes'),
    sa.Column('mime_type', sa.String(length=100), nullable=True),
    sa.Column('file_hash', sa.String(length=64), nullable=True, comment='SHA256 hash for deduplication'),
    sa.Column('created_at', sa.DateTime(), nullable=False),
    sa.ForeignKeyConstraint(['task_id'], ['tool_ocr_tasks.id'], ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_tool_ocr_task_files_file_hash'), 'tool_ocr_task_files', ['file_hash'], unique=False)
    op.create_index(op.f('ix_tool_ocr_task_files_id'), 'tool_ocr_task_files', ['id'], unique=False)
    op.create_index(op.f('ix_tool_ocr_task_files_task_id'), 'tool_ocr_task_files', ['task_id'], unique=False)
    # Note: Legacy tables are not dropped to avoid foreign key constraint issues
    # They will coexist with new V2 tables until manual cleanup
    # op.drop_index(op.f('username'), table_name='tst_user')
    # op.drop_table('tst_user')
    # op.drop_index(op.f('idx_created_at'), table_name='ast_ai_interaction_logs')
    # op.drop_index(op.f('idx_model_name'), table_name='ast_ai_interaction_logs')
    # op.drop_index(op.f('idx_status'), table_name='ast_ai_interaction_logs')
    # op.drop_index(op.f('idx_timestamp'), table_name='ast_ai_interaction_logs')
    # op.drop_index(op.f('idx_type'), table_name='ast_ai_interaction_logs')
    # op.drop_index(op.f('idx_user_session'), table_name='ast_ai_interaction_logs')
    # op.drop_table('ast_ai_interaction_logs')
    # op.drop_table('todo_fire_email_log')
    # op.drop_table('BOM')
    # op.drop_index(op.f('email'), table_name='sys_user')
    # op.drop_index(op.f('username'), table_name='sys_user')
    # op.drop_table('sys_user')
    # op.drop_index(op.f('ix_dt_notifications_notification_uuid'), table_name='dt_notifications')
    # op.drop_table('dt_notifications')
    # op.drop_index(op.f('idx_created_at'), table_name='ast_system_logs')
    # op.drop_index(op.f('idx_event_type'), table_name='ast_system_logs')
    # op.drop_index(op.f('idx_timestamp'), table_name='ast_system_logs')
    # op.drop_index(op.f('idx_version'), table_name='ast_system_logs')
    # op.drop_table('ast_system_logs')
    # op.drop_table('todo_mail_log')
    # op.drop_index(op.f('username'), table_name='ms_users')
    # op.drop_table('ms_users')
    # op.drop_table('dt_job_files')
    # op.drop_table('todo_item')
    # op.drop_table('dt_api_usage_stats')
    # op.drop_table('dt_system_logs')
    # op.drop_index(op.f('idx_created_at'), table_name='ast_error_logs')
    # op.drop_index(op.f('idx_error_type'), table_name='ast_error_logs')
    # op.drop_index(op.f('idx_resolved'), table_name='ast_error_logs')
    # op.drop_index(op.f('idx_severity'), table_name='ast_error_logs')
    # op.drop_index(op.f('idx_timestamp'), table_name='ast_error_logs')
    # op.drop_index(op.f('idx_user_session'), table_name='ast_error_logs')
    # op.drop_table('ast_error_logs')
    # op.drop_index(op.f('ix_dt_users_email'), table_name='dt_users')
    # op.drop_index(op.f('ix_dt_users_username'), table_name='dt_users')
    # op.drop_table('dt_users')
    # op.drop_table('todo_user_pref')
    # op.drop_table('tst_spec_history')
    # op.drop_index(op.f('idx_component'), table_name='ast_health_checks')
    # op.drop_index(op.f('idx_component_status'), table_name='ast_health_checks')
    # op.drop_index(op.f('idx_created_at'), table_name='ast_health_checks')
    # op.drop_index(op.f('idx_status'), table_name='ast_health_checks')
    # op.drop_index(op.f('idx_timestamp'), table_name='ast_health_checks')
    # op.drop_table('ast_health_checks')
    # op.drop_index(op.f('ix_dt_translation_jobs_job_uuid'), table_name='dt_translation_jobs')
    # op.drop_table('dt_translation_jobs')
    # op.drop_table('fmcp_documents')
    # op.drop_index(op.f('unique_association'), table_name='fmcp_associations')
    # op.drop_table('fmcp_associations')
    # op.drop_index(op.f('ix_rt_translations_id'), table_name='rt_translations')
    # op.drop_table('rt_translations')
    # op.drop_table('todo_audit_log')
    # op.drop_index(op.f('idx_created_at'), table_name='ast_operation_logs')
    # op.drop_index(op.f('idx_level'), table_name='ast_operation_logs')
    # op.drop_index(op.f('idx_logger_name'), table_name='ast_operation_logs')
    # op.drop_index(op.f('idx_timestamp'), table_name='ast_operation_logs')
    # op.drop_index(op.f('idx_user_session'), table_name='ast_operation_logs')
    # op.drop_table('ast_operation_logs')
    # op.drop_table('fmcp_cp_items')
    # op.drop_table('ship_record_sqlitecopy')
    # op.drop_table('todo_import_job')
    # op.drop_table('fmcp_fmea_items')
    # op.drop_table('ms_meetings')
    # op.drop_table('tst_temp_spec')
    # op.drop_table('login_logs')
    # op.drop_table('todo_item_follower')
    # op.drop_index(op.f('ix_rt_users_id'), table_name='rt_users')
    # op.drop_index(op.f('ix_rt_users_username'), table_name='rt_users')
    # op.drop_table('rt_users')
    # op.drop_table('tst_upload')
    # op.drop_table('fmcp_fmea_header')
    # op.drop_table('todo_item_responsible')
    # op.drop_table('ms_action_items')
    # op.drop_index(op.f('ix_rt_rooms_id'), table_name='rt_rooms')
    # op.drop_index(op.f('ix_rt_rooms_name'), table_name='rt_rooms')
    # op.drop_table('rt_rooms')
    # op.drop_table('fmcp_fmea_fe_items')
    # op.drop_index(op.f('idx_languages'), table_name='dt_translation_cache')
    # op.drop_index(op.f('uk_cache'), table_name='dt_translation_cache')
    # op.drop_table('dt_translation_cache')
    # op.drop_table('rt_messages')
    # op.drop_index(op.f('username'), table_name='fmcp_users')
    # op.drop_table('fmcp_users')
    # ### end Alembic commands ###


def downgrade() -> None:
    """Downgrade schema."""
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('fmcp_users',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('username', mysql.VARCHAR(length=100), nullable=False),
    sa.Column('hashed_password', mysql.VARCHAR(length=255), nullable=False),
    sa.Column('role', mysql.VARCHAR(length=50), server_default=sa.text("'editor'"), nullable=True),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('username'), 'fmcp_users', ['username'], unique=True)
    op.create_table('rt_messages',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('message_type', mysql.VARCHAR(length=20), nullable=False),
    sa.Column('content', mysql.TEXT(), nullable=True),
    sa.Column('file_path', mysql.VARCHAR(length=512), nullable=True),
    sa.Column('file_name', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('timestamp', mysql.DATETIME(), server_default=sa.text('(now())'), nullable=True),
    sa.Column('source_lang', mysql.VARCHAR(length=20), nullable=True),
    sa.Column('user_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('room_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.ForeignKeyConstraint(['room_id'], ['rt_rooms.id'], name='rt_messages_ibfk_2'),
    sa.ForeignKeyConstraint(['user_id'], ['rt_users.id'], name='rt_messages_ibfk_1'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('dt_translation_cache',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('source_text_hash', mysql.VARCHAR(length=64), nullable=False, comment='來源文字hash'),
    sa.Column('source_language', mysql.VARCHAR(length=50), nullable=False, comment='來源語言'),
    sa.Column('target_language', mysql.VARCHAR(length=50), nullable=False, comment='目標語言'),
    sa.Column('source_text', mysql.TEXT(), nullable=False, comment='來源文字'),
    sa.Column('translated_text', mysql.TEXT(), nullable=False, comment='翻譯文字'),
    sa.Column('created_at', mysql.DATETIME(), nullable=True, comment='建立時間'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('uk_cache'), 'dt_translation_cache', ['source_text_hash', 'source_language', 'target_language'], unique=True)
    op.create_index(op.f('idx_languages'), 'dt_translation_cache', ['source_language', 'target_language'], unique=False)
    op.create_table('fmcp_fmea_fe_items',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('document_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('failure_effect', mysql.TEXT(), nullable=True),
    sa.Column('severity', mysql.TINYINT(), autoincrement=False, nullable=True),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
    sa.Column('updated_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True),
    sa.ForeignKeyConstraint(['document_id'], ['fmcp_documents.id'], name=op.f('fmcp_fmea_fe_items_ibfk_1'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('rt_rooms',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('name', mysql.VARCHAR(length=150), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('ix_rt_rooms_name'), 'rt_rooms', ['name'], unique=True)
    op.create_index(op.f('ix_rt_rooms_id'), 'rt_rooms', ['id'], unique=False)
    op.create_table('ms_action_items',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('meeting_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('item', mysql.TEXT(), nullable=True),
    sa.Column('action', mysql.TEXT(), nullable=False),
    sa.Column('owner_id', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('due_date', sa.DATE(), nullable=True),
    sa.Column('status', mysql.VARCHAR(length=50), nullable=False),
    sa.Column('created_at', mysql.DATETIME(), server_default=sa.text('(now())'), nullable=True),
    sa.Column('attachment_path', mysql.VARCHAR(length=255), nullable=True),
    sa.ForeignKeyConstraint(['meeting_id'], ['ms_meetings.id'], name=op.f('ms_action_items_ibfk_1')),
    sa.ForeignKeyConstraint(['owner_id'], ['ms_users.id'], name=op.f('ms_action_items_ibfk_2')),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('todo_item_responsible',
    sa.Column('todo_id', mysql.CHAR(length=36), nullable=False),
    sa.Column('ad_account', mysql.VARCHAR(length=128), nullable=False),
    sa.Column('added_by', mysql.VARCHAR(length=128), nullable=True),
    sa.Column('added_at', mysql.DATETIME(), nullable=False),
    sa.ForeignKeyConstraint(['todo_id'], ['todo_item.id'], name=op.f('todo_item_responsible_ibfk_1'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('todo_id', 'ad_account'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('fmcp_fmea_header',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('document_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('company_name', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('customer_name', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('model_year_platform', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('plant_location', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('subject', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('pfmea_start_date', sa.DATE(), nullable=True),
    sa.Column('pfmea_revision_date', sa.DATE(), nullable=True),
    sa.Column('pfmea_id', mysql.VARCHAR(length=50), nullable=True),
    sa.Column('process_responsibility', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('cross_functional_team', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('confidentiality_level', mysql.VARCHAR(length=100), nullable=True),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
    sa.Column('updated_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True),
    sa.Column('station_code', mysql.VARCHAR(length=10), nullable=True),
    sa.Column('station_name', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('station_display_name', mysql.VARCHAR(length=255), nullable=True),
    sa.ForeignKeyConstraint(['document_id'], ['fmcp_documents.id'], name=op.f('fmcp_fmea_header_ibfk_1'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('tst_upload',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('temp_spec_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('filename', mysql.VARCHAR(length=200), nullable=True),
    sa.Column('upload_time', mysql.DATETIME(), nullable=True),
    sa.ForeignKeyConstraint(['temp_spec_id'], ['tst_temp_spec.id'], name=op.f('tst_upload_ibfk_1'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('rt_users',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('username', mysql.VARCHAR(length=100), nullable=False),
    sa.Column('password_hash', mysql.VARCHAR(length=256), nullable=False),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('ix_rt_users_username'), 'rt_users', ['username'], unique=True)
    op.create_index(op.f('ix_rt_users_id'), 'rt_users', ['id'], unique=False)
    op.create_table('todo_item_follower',
    sa.Column('todo_id', mysql.CHAR(length=36), nullable=False),
    sa.Column('ad_account', mysql.VARCHAR(length=128), nullable=False),
    sa.Column('added_by', mysql.VARCHAR(length=128), nullable=True),
    sa.Column('added_at', mysql.DATETIME(), nullable=False),
    sa.ForeignKeyConstraint(['todo_id'], ['todo_item.id'], name=op.f('todo_item_follower_ibfk_1'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('todo_id', 'ad_account'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('login_logs',
    sa.Column('id', mysql.BIGINT(), autoincrement=True, nullable=False),
    sa.Column('username', mysql.VARCHAR(length=255), nullable=False, comment='登入帳號'),
    sa.Column('auth_method', mysql.ENUM('API', 'LDAP'), nullable=False, comment='認證方式'),
    sa.Column('login_success', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False, comment='是否成功'),
    sa.Column('error_message', mysql.TEXT(), nullable=True, comment='錯誤訊息（失敗時）'),
    sa.Column('ip_address', mysql.VARCHAR(length=45), nullable=True, comment='IP 地址'),
    sa.Column('user_agent', mysql.TEXT(), nullable=True, comment='瀏覽器資訊'),
    sa.Column('api_response_summary', mysql.JSON(), nullable=True, comment='API 回應摘要'),
    sa.Column('login_at', mysql.DATETIME(), nullable=True, comment='登入時間'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('tst_temp_spec',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('spec_code', mysql.VARCHAR(length=20), nullable=False),
    sa.Column('applicant', mysql.VARCHAR(length=50), nullable=True),
    sa.Column('title', mysql.VARCHAR(length=100), nullable=True),
    sa.Column('content', mysql.TEXT(), nullable=True),
    sa.Column('start_date', sa.DATE(), nullable=True),
    sa.Column('end_date', sa.DATE(), nullable=True),
    sa.Column('status', mysql.ENUM('pending_approval', 'active', 'expired', 'terminated'), nullable=False),
    sa.Column('created_at', mysql.DATETIME(), nullable=True),
    sa.Column('extension_count', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('termination_reason', mysql.TEXT(), nullable=True),
    sa.Column('notification_emails', mysql.TEXT(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('ms_meetings',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('topic', mysql.VARCHAR(length=255), nullable=False),
    sa.Column('meeting_date', mysql.DATETIME(), nullable=False),
    sa.Column('created_by_id', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('created_at', mysql.DATETIME(), server_default=sa.text('(now())'), nullable=True),
    sa.Column('status', mysql.VARCHAR(length=50), nullable=False),
    sa.Column('summary', mysql.TEXT(), nullable=True),
    sa.Column('transcript', mysql.TEXT(), nullable=True),
    sa.ForeignKeyConstraint(['created_by_id'], ['ms_users.id'], name=op.f('ms_meetings_ibfk_1')),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('fmcp_fmea_items',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('document_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('row_index', mysql.INTEGER(), autoincrement=False, nullable=True, comment='Original row number from the source file for reference'),
    sa.Column('process_step', mysql.TEXT(), nullable=True),
    sa.Column('failure_mode', mysql.TEXT(), nullable=True),
    sa.Column('failure_cause', mysql.TEXT(), nullable=True),
    sa.Column('detection_controls', mysql.TEXT(), nullable=True),
    sa.Column('severity', mysql.TINYINT(), autoincrement=False, nullable=True),
    sa.Column('occurrence', mysql.TINYINT(), autoincrement=False, nullable=True),
    sa.Column('detection', mysql.TINYINT(), autoincrement=False, nullable=True),
    sa.Column('ap', mysql.ENUM('H', 'M', 'L'), nullable=True, comment='Action Priority'),
    sa.Column('issue_no', mysql.TEXT(), nullable=True),
    sa.Column('history_change_authorization', mysql.TEXT(), nullable=True),
    sa.Column('process_item', mysql.TEXT(), nullable=True),
    sa.Column('process_work_element', mysql.TEXT(), nullable=True),
    sa.Column('function_of_process_item', mysql.TEXT(), nullable=True),
    sa.Column('function_of_process_step_and_product_characteristic', mysql.TEXT(), nullable=True),
    sa.Column('function_of_process_work_element_and_process_characteristic', mysql.TEXT(), nullable=True),
    sa.Column('failure_effects_description', mysql.TEXT(), nullable=True),
    sa.Column('prevention_controls_description', mysql.TEXT(), nullable=True),
    sa.Column('special_characteristics', mysql.TEXT(), nullable=True),
    sa.Column('filter_code', mysql.TEXT(), nullable=True),
    sa.Column('prevention_action', mysql.TEXT(), nullable=True),
    sa.Column('detection_action', mysql.TEXT(), nullable=True),
    sa.Column('responsible_person_name', mysql.TEXT(), nullable=True),
    sa.Column('target_completion_date', mysql.TEXT(), nullable=True),
    sa.Column('status', mysql.TEXT(), nullable=True),
    sa.Column('action_taken', mysql.TEXT(), nullable=True),
    sa.Column('completion_date', mysql.TEXT(), nullable=True),
    sa.Column('severity_opt', mysql.TINYINT(), autoincrement=False, nullable=True),
    sa.Column('occurrence_opt', mysql.TINYINT(), autoincrement=False, nullable=True),
    sa.Column('detection_opt', mysql.TINYINT(), autoincrement=False, nullable=True),
    sa.Column('ap_opt', mysql.ENUM('H', 'M', 'L'), nullable=True),
    sa.Column('remarks', mysql.TEXT(), nullable=True),
    sa.Column('special_characteristics_opt', mysql.TEXT(), nullable=True),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
    sa.Column('updated_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True),
    sa.ForeignKeyConstraint(['document_id'], ['fmcp_documents.id'], name='fmcp_fmea_items_ibfk_1', ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('todo_import_job',
    sa.Column('id', mysql.CHAR(length=36), nullable=False),
    sa.Column('actor_ad', mysql.VARCHAR(length=128), nullable=False),
    sa.Column('filename', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('total_rows', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('success_rows', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('failed_rows', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('status', mysql.ENUM('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED'), nullable=True),
    sa.Column('error_file_path', mysql.VARCHAR(length=500), nullable=True),
    sa.Column('error_details', mysql.JSON(), nullable=True),
    sa.Column('created_at', mysql.DATETIME(), nullable=False),
    sa.Column('completed_at', mysql.DATETIME(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('ship_record_sqlitecopy',
    sa.Column('出貨單狀態', mysql.TEXT(), nullable=True),
    sa.Column('TRIP號碼', mysql.TEXT(), nullable=True),
    sa.Column('交貨單號', mysql.TEXT(), nullable=True),
    sa.Column('移轉工單', mysql.TEXT(), nullable=True),
    sa.Column('order type', mysql.TEXT(), nullable=True),
    sa.Column('order line type', mysql.TEXT(), nullable=True),
    sa.Column('訂單單號', mysql.TEXT(), nullable=True),
    sa.Column('訂單項次', mysql.TEXT(), nullable=True),
    sa.Column('原始訂單單號(81 訂單)', mysql.TEXT(), nullable=True),
    sa.Column('原始訂單項次', mysql.TEXT(), nullable=True),
    sa.Column('客戶PO', mysql.TEXT(), nullable=True),
    sa.Column('關係人SO號碼', mysql.TEXT(), nullable=True),
    sa.Column('客戶名稱', mysql.TEXT(), nullable=True),
    sa.Column('SHIP TO LOCATION', mysql.TEXT(), nullable=True),
    sa.Column('終端客戶', mysql.TEXT(), nullable=True),
    sa.Column('幣別', mysql.TEXT(), nullable=True),
    sa.Column('客戶料號', mysql.TEXT(), nullable=True),
    sa.Column('強茂料號', mysql.TEXT(), nullable=True),
    sa.Column('出貨請求量', mysql.TEXT(), nullable=True),
    sa.Column('實際出貨量', mysql.TEXT(), nullable=True),
    sa.Column('出貨方式', mysql.TEXT(), nullable=True),
    sa.Column('訂單明細行備註', mysql.TEXT(), nullable=True),
    sa.Column('交貨地點', mysql.TEXT(), nullable=True),
    sa.Column('出貨組織', mysql.TEXT(), nullable=True),
    sa.Column('出貨倉庫', mysql.TEXT(), nullable=True),
    sa.Column('業務區域', mysql.TEXT(), nullable=True),
    sa.Column('業務代表', mysql.TEXT(), nullable=True),
    sa.Column('訂單狀態', mysql.TEXT(), nullable=True),
    sa.Column('訂單明細狀態', mysql.TEXT(), nullable=True),
    sa.Column('批號', mysql.TEXT(), nullable=True),
    sa.Column('幣別2', mysql.TEXT(), nullable=True),
    sa.Column('出貨確認日', mysql.TEXT(), nullable=True),
    sa.Column('Package', mysql.TEXT(), nullable=True),
    sa.Column('卸貨港', mysql.TEXT(), nullable=True),
    sa.Column('Type', mysql.TEXT(), nullable=True),
    sa.Column('Function', mysql.TEXT(), nullable=True),
    sa.Column('客戶簡稱', mysql.TEXT(), nullable=True),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('fmcp_cp_items',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('document_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('row_index', mysql.INTEGER(), autoincrement=False, nullable=True, comment='Original row number from the source file for reference'),
    sa.Column('process_name', mysql.TEXT(), nullable=True),
    sa.Column('product_characteristic', mysql.TEXT(), nullable=True),
    sa.Column('process_characteristic', mysql.TEXT(), nullable=True),
    sa.Column('evaluation_technique', mysql.TEXT(), nullable=True),
    sa.Column('control_method', mysql.TEXT(), nullable=True),
    sa.Column('spec_tolerance', mysql.TEXT(), nullable=True),
    sa.Column('sample_size', mysql.VARCHAR(length=100), nullable=True),
    sa.Column('sample_freq', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('special_character_class', mysql.VARCHAR(length=50), nullable=True),
    sa.Column('equipment', mysql.TEXT(), nullable=True),
    sa.Column('reaction_plan', mysql.TEXT(), nullable=True),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
    sa.Column('updated_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True),
    sa.ForeignKeyConstraint(['document_id'], ['fmcp_documents.id'], name='fmcp_cp_items_ibfk_1', ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('ast_operation_logs',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False, comment='主鍵ID'),
    sa.Column('timestamp', mysql.DATETIME(fsp=3), nullable=False, comment='日誌時間戳（毫秒精度）'),
    sa.Column('level', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=20), nullable=False, comment='日誌級別（DEBUG, INFO, WARNING, ERROR, CRITICAL）'),
    sa.Column('logger_name', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=False, comment='記錄器名稱'),
    sa.Column('module', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='模組名稱'),
    sa.Column('function_name', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='函數名稱'),
    sa.Column('message', mysql.TEXT(collation='utf8mb4_unicode_ci'), nullable=False, comment='日誌訊息'),
    sa.Column('extra_data', mysql.JSON(), nullable=True, comment='額外資料（JSON格式）'),
    sa.Column('user_id', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='用戶ID'),
    sa.Column('session_id', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='會話ID'),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='記錄創建時間'),
    sa.PrimaryKeyConstraint('id'),
    comment='系統操作日誌表',
    mysql_collate='utf8mb4_unicode_ci',
    mysql_comment='系統操作日誌表',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('idx_user_session'), 'ast_operation_logs', ['user_id', 'session_id'], unique=False)
    op.create_index(op.f('idx_timestamp'), 'ast_operation_logs', ['timestamp'], unique=False)
    op.create_index(op.f('idx_logger_name'), 'ast_operation_logs', ['logger_name'], unique=False)
    op.create_index(op.f('idx_level'), 'ast_operation_logs', ['level'], unique=False)
    op.create_index(op.f('idx_created_at'), 'ast_operation_logs', ['created_at'], unique=False)
    op.create_table('todo_audit_log',
    sa.Column('id', mysql.BIGINT(), autoincrement=True, nullable=False),
    sa.Column('actor_ad', mysql.VARCHAR(length=128), nullable=False),
    sa.Column('todo_id', mysql.CHAR(length=36), nullable=True),
    sa.Column('action', mysql.ENUM('CREATE', 'UPDATE', 'DELETE', 'COMPLETE', 'IMPORT', 'MAIL_SENT', 'MAIL_FAIL', 'FIRE_EMAIL', 'DIGEST_EMAIL', 'BULK_REMINDER', 'FOLLOW', 'UNFOLLOW'), nullable=False),
    sa.Column('detail', mysql.JSON(), nullable=True),
    sa.Column('created_at', mysql.DATETIME(), nullable=False),
    sa.ForeignKeyConstraint(['todo_id'], ['todo_item.id'], name=op.f('todo_audit_log_ibfk_1'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('rt_translations',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('target_lang', mysql.VARCHAR(length=20), nullable=False),
    sa.Column('translated_text', mysql.TEXT(), nullable=False),
    sa.Column('message_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.ForeignKeyConstraint(['message_id'], ['rt_messages.id'], name=op.f('rt_translations_ibfk_1')),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('ix_rt_translations_id'), 'rt_translations', ['id'], unique=False)
    op.create_table('fmcp_associations',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('fmea_item_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('cp_item_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('created_by', mysql.VARCHAR(length=100), nullable=False),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
    sa.ForeignKeyConstraint(['cp_item_id'], ['fmcp_cp_items.id'], name=op.f('fmcp_associations_ibfk_2'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['fmea_item_id'], ['fmcp_fmea_items.id'], name=op.f('fmcp_associations_ibfk_1'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('unique_association'), 'fmcp_associations', ['fmea_item_id', 'cp_item_id'], unique=True)
    op.create_table('fmcp_documents',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('file_name', mysql.VARCHAR(length=255), nullable=False),
    sa.Column('document_type', mysql.ENUM('FMEA', 'CP', 'OI'), nullable=False),
    sa.Column('version', mysql.VARCHAR(length=50), server_default=sa.text("'1.0'"), nullable=True),
    sa.Column('uploaded_by', mysql.VARCHAR(length=100), nullable=False),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
    sa.Column('updated_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('dt_translation_jobs',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('job_uuid', mysql.VARCHAR(length=36), nullable=False, comment='任務唯一識別碼'),
    sa.Column('user_id', mysql.INTEGER(), autoincrement=False, nullable=False, comment='使用者ID'),
    sa.Column('original_filename', mysql.VARCHAR(length=500), nullable=False, comment='原始檔名'),
    sa.Column('file_extension', mysql.VARCHAR(length=10), nullable=False, comment='檔案副檔名'),
    sa.Column('file_size', mysql.BIGINT(), autoincrement=False, nullable=False, comment='檔案大小(bytes)'),
    sa.Column('file_path', mysql.VARCHAR(length=1000), nullable=False, comment='檔案路徑'),
    sa.Column('source_language', mysql.VARCHAR(length=50), nullable=True, comment='來源語言'),
    sa.Column('target_languages', mysql.JSON(), nullable=False, comment='目標語言陣列'),
    sa.Column('status', mysql.ENUM('PENDING', 'PROCESSING', 'COMPLETED', 'FAILED', 'RETRY'), nullable=True, comment='任務狀態'),
    sa.Column('progress', mysql.DECIMAL(precision=5, scale=2), nullable=True, comment='處理進度(%)'),
    sa.Column('retry_count', mysql.INTEGER(), autoincrement=False, nullable=True, comment='重試次數'),
    sa.Column('error_message', mysql.TEXT(), nullable=True, comment='錯誤訊息'),
    sa.Column('total_tokens', mysql.INTEGER(), autoincrement=False, nullable=True, comment='總token數'),
    sa.Column('total_cost', mysql.DECIMAL(precision=10, scale=4), nullable=True, comment='總成本'),
    sa.Column('conversation_id', mysql.VARCHAR(length=100), nullable=True, comment='Dify對話ID，用於維持翻譯上下文'),
    sa.Column('processing_started_at', mysql.DATETIME(), nullable=True, comment='開始處理時間'),
    sa.Column('completed_at', mysql.DATETIME(), nullable=True, comment='完成時間'),
    sa.Column('created_at', mysql.DATETIME(), nullable=True, comment='建立時間'),
    sa.Column('updated_at', mysql.DATETIME(), nullable=True, comment='更新時間'),
    sa.Column('deleted_at', mysql.DATETIME(), nullable=True, comment='軟刪除時間'),
    sa.ForeignKeyConstraint(['user_id'], ['dt_users.id'], name='dt_translation_jobs_ibfk_1'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('ix_dt_translation_jobs_job_uuid'), 'dt_translation_jobs', ['job_uuid'], unique=True)
    op.create_table('ast_health_checks',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False, comment='主鍵ID'),
    sa.Column('timestamp', mysql.DATETIME(fsp=3), nullable=False, comment='檢查時間戳（毫秒精度）'),
    sa.Column('component', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=50), nullable=False, comment='組件名稱（database, dify_api, mcp_server, backend, frontend）'),
    sa.Column('status', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=20), nullable=False, comment='狀態（healthy, degraded, unhealthy, offline）'),
    sa.Column('response_time_ms', mysql.INTEGER(), autoincrement=False, nullable=True, comment='響應時間（毫秒）'),
    sa.Column('details', mysql.JSON(), nullable=True, comment='詳細資訊（JSON格式）'),
    sa.Column('error_message', mysql.TEXT(collation='utf8mb4_unicode_ci'), nullable=True, comment='錯誤訊息（如有）'),
    sa.Column('last_healthy_at', mysql.DATETIME(), nullable=True, comment='上次健康時間'),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='記錄創建時間'),
    sa.PrimaryKeyConstraint('id'),
    comment='健康檢查日誌表',
    mysql_collate='utf8mb4_unicode_ci',
    mysql_comment='健康檢查日誌表',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('idx_timestamp'), 'ast_health_checks', ['timestamp'], unique=False)
    op.create_index(op.f('idx_status'), 'ast_health_checks', ['status'], unique=False)
    op.create_index(op.f('idx_created_at'), 'ast_health_checks', ['created_at'], unique=False)
    op.create_index(op.f('idx_component_status'), 'ast_health_checks', ['component', 'status'], unique=False)
    op.create_index(op.f('idx_component'), 'ast_health_checks', ['component'], unique=False)
    op.create_table('tst_spec_history',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('spec_id', mysql.INTEGER(), autoincrement=False, nullable=False),
    sa.Column('user_id', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('action', mysql.VARCHAR(length=50), nullable=False),
    sa.Column('details', mysql.TEXT(), nullable=True),
    sa.Column('timestamp', mysql.DATETIME(), nullable=True),
    sa.ForeignKeyConstraint(['spec_id'], ['tst_temp_spec.id'], name=op.f('tst_spec_history_ibfk_1'), ondelete='CASCADE'),
    sa.ForeignKeyConstraint(['user_id'], ['tst_user.id'], name=op.f('tst_spec_history_ibfk_2'), ondelete='SET NULL'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('todo_user_pref',
    sa.Column('ad_account', mysql.VARCHAR(length=128), nullable=False),
    sa.Column('email', mysql.VARCHAR(length=256), nullable=True),
    sa.Column('display_name', mysql.VARCHAR(length=128), nullable=True),
    sa.Column('theme', mysql.ENUM('light', 'dark', 'auto'), nullable=True),
    sa.Column('language', mysql.VARCHAR(length=10), nullable=True),
    sa.Column('timezone', mysql.VARCHAR(length=50), nullable=True),
    sa.Column('notification_enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.Column('email_reminder_enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.Column('weekly_summary_enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.Column('monthly_summary_enabled', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.Column('reminder_days_before', mysql.JSON(), nullable=True),
    sa.Column('daily_summary_time', mysql.VARCHAR(length=5), nullable=True),
    sa.Column('weekly_summary_time', mysql.VARCHAR(length=5), nullable=True),
    sa.Column('monthly_summary_time', mysql.VARCHAR(length=5), nullable=True),
    sa.Column('weekly_summary_day', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('monthly_summary_day', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('fire_email_today_count', mysql.INTEGER(), autoincrement=False, nullable=True),
    sa.Column('fire_email_last_reset', sa.DATE(), nullable=True),
    sa.Column('created_at', mysql.DATETIME(), nullable=False),
    sa.Column('updated_at', mysql.DATETIME(), nullable=False),
    sa.PrimaryKeyConstraint('ad_account'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('dt_users',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('username', mysql.VARCHAR(length=100), nullable=False, comment='AD帳號'),
    sa.Column('display_name', mysql.VARCHAR(length=200), nullable=False, comment='顯示名稱'),
    sa.Column('email', mysql.VARCHAR(length=255), nullable=False, comment='電子郵件'),
    sa.Column('department', mysql.VARCHAR(length=100), nullable=True, comment='部門'),
    sa.Column('is_admin', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True, comment='是否為管理員'),
    sa.Column('last_login', mysql.DATETIME(), nullable=True, comment='最後登入時間'),
    sa.Column('created_at', mysql.DATETIME(), nullable=True, comment='建立時間'),
    sa.Column('updated_at', mysql.DATETIME(), nullable=True, comment='更新時間'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('ix_dt_users_username'), 'dt_users', ['username'], unique=True)
    op.create_index(op.f('ix_dt_users_email'), 'dt_users', ['email'], unique=False)
    op.create_table('ast_error_logs',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False, comment='主鍵ID'),
    sa.Column('timestamp', mysql.DATETIME(fsp=3), nullable=False, comment='錯誤時間戳（毫秒精度）'),
    sa.Column('error_type', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=False, comment='錯誤類型'),
    sa.Column('error_message', mysql.TEXT(collation='utf8mb4_unicode_ci'), nullable=False, comment='錯誤訊息'),
    sa.Column('stack_trace', mysql.LONGTEXT(collation='utf8mb4_unicode_ci'), nullable=True, comment='堆疊追蹤'),
    sa.Column('module', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='發生錯誤的模組'),
    sa.Column('function_name', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='發生錯誤的函數'),
    sa.Column('request_data', mysql.JSON(), nullable=True, comment='請求資料（JSON格式）'),
    sa.Column('user_id', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='用戶ID'),
    sa.Column('session_id', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='會話ID'),
    sa.Column('severity', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=20), nullable=True, comment='嚴重程度（low, medium, high, critical）'),
    sa.Column('resolved', mysql.TINYINT(display_width=1), server_default=sa.text("'0'"), autoincrement=False, nullable=True, comment='是否已解決'),
    sa.Column('resolved_at', mysql.DATETIME(), nullable=True, comment='解決時間'),
    sa.Column('resolved_by', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='解決人員'),
    sa.Column('resolution_notes', mysql.TEXT(collation='utf8mb4_unicode_ci'), nullable=True, comment='解決備註'),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='記錄創建時間'),
    sa.PrimaryKeyConstraint('id'),
    comment='錯誤日誌表，記錄所有系統錯誤',
    mysql_collate='utf8mb4_unicode_ci',
    mysql_comment='錯誤日誌表，記錄所有系統錯誤',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('idx_user_session'), 'ast_error_logs', ['user_id', 'session_id'], unique=False)
    op.create_index(op.f('idx_timestamp'), 'ast_error_logs', ['timestamp'], unique=False)
    op.create_index(op.f('idx_severity'), 'ast_error_logs', ['severity'], unique=False)
    op.create_index(op.f('idx_resolved'), 'ast_error_logs', ['resolved'], unique=False)
    op.create_index(op.f('idx_error_type'), 'ast_error_logs', ['error_type'], unique=False)
    op.create_index(op.f('idx_created_at'), 'ast_error_logs', ['created_at'], unique=False)
    op.create_table('dt_system_logs',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('level', mysql.ENUM('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'), nullable=False, comment='日誌等級'),
    sa.Column('module', mysql.VARCHAR(length=100), nullable=False, comment='模組名稱'),
    sa.Column('user_id', mysql.INTEGER(), autoincrement=False, nullable=True, comment='使用者ID'),
    sa.Column('job_id', mysql.INTEGER(), autoincrement=False, nullable=True, comment='任務ID'),
    sa.Column('message', mysql.TEXT(), nullable=False, comment='日誌訊息'),
    sa.Column('extra_data', mysql.JSON(), nullable=True, comment='額外資料'),
    sa.Column('created_at', mysql.DATETIME(), nullable=True, comment='建立時間'),
    sa.ForeignKeyConstraint(['job_id'], ['dt_translation_jobs.id'], name=op.f('dt_system_logs_ibfk_2')),
    sa.ForeignKeyConstraint(['user_id'], ['dt_users.id'], name=op.f('dt_system_logs_ibfk_1')),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('dt_api_usage_stats',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('user_id', mysql.INTEGER(), autoincrement=False, nullable=False, comment='使用者ID'),
    sa.Column('job_id', mysql.INTEGER(), autoincrement=False, nullable=True, comment='任務ID'),
    sa.Column('api_endpoint', mysql.VARCHAR(length=200), nullable=False, comment='API端點'),
    sa.Column('prompt_tokens', mysql.INTEGER(), autoincrement=False, nullable=True, comment='Prompt token數'),
    sa.Column('completion_tokens', mysql.INTEGER(), autoincrement=False, nullable=True, comment='Completion token數'),
    sa.Column('total_tokens', mysql.INTEGER(), autoincrement=False, nullable=True, comment='總token數'),
    sa.Column('prompt_unit_price', mysql.DECIMAL(precision=10, scale=8), nullable=True, comment='單價'),
    sa.Column('prompt_price_unit', mysql.VARCHAR(length=20), nullable=True, comment='價格單位'),
    sa.Column('cost', mysql.DECIMAL(precision=10, scale=4), nullable=True, comment='成本'),
    sa.Column('response_time_ms', mysql.INTEGER(), autoincrement=False, nullable=True, comment='回應時間(毫秒)'),
    sa.Column('success', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True, comment='是否成功'),
    sa.Column('error_message', mysql.TEXT(), nullable=True, comment='錯誤訊息'),
    sa.Column('created_at', mysql.DATETIME(), nullable=True, comment='建立時間'),
    sa.ForeignKeyConstraint(['job_id'], ['dt_translation_jobs.id'], name=op.f('dt_api_usage_stats_ibfk_2')),
    sa.ForeignKeyConstraint(['user_id'], ['dt_users.id'], name=op.f('dt_api_usage_stats_ibfk_1')),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('todo_item',
    sa.Column('id', mysql.CHAR(length=36), nullable=False),
    sa.Column('title', mysql.VARCHAR(length=200), nullable=False),
    sa.Column('description', mysql.TEXT(), nullable=True),
    sa.Column('status', mysql.ENUM('NEW', 'DOING', 'BLOCKED', 'DONE'), nullable=True),
    sa.Column('priority', mysql.ENUM('LOW', 'MEDIUM', 'HIGH', 'URGENT'), nullable=True),
    sa.Column('due_date', sa.DATE(), nullable=True),
    sa.Column('created_at', mysql.DATETIME(), nullable=False),
    sa.Column('completed_at', mysql.DATETIME(), nullable=True),
    sa.Column('creator_ad', mysql.VARCHAR(length=128), nullable=False),
    sa.Column('creator_display_name', mysql.VARCHAR(length=128), nullable=True),
    sa.Column('creator_email', mysql.VARCHAR(length=256), nullable=True),
    sa.Column('starred', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.Column('is_public', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True),
    sa.Column('tags', mysql.JSON(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('dt_job_files',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('job_id', mysql.INTEGER(), autoincrement=False, nullable=False, comment='任務ID'),
    sa.Column('file_type', mysql.ENUM('source', 'translated'), nullable=False, comment='檔案類型'),
    sa.Column('language_code', mysql.VARCHAR(length=50), nullable=True, comment='語言代碼(翻譯檔案)'),
    sa.Column('original_filename', mysql.VARCHAR(length=255), nullable=False, comment='原始檔名'),
    sa.Column('stored_filename', mysql.VARCHAR(length=255), nullable=False, comment='儲存檔名'),
    sa.Column('file_path', mysql.VARCHAR(length=500), nullable=False, comment='檔案路徑'),
    sa.Column('file_size', mysql.BIGINT(), autoincrement=False, nullable=True, comment='檔案大小'),
    sa.Column('mime_type', mysql.VARCHAR(length=100), nullable=True, comment='MIME 類型'),
    sa.Column('created_at', mysql.DATETIME(), nullable=True, comment='建立時間'),
    sa.ForeignKeyConstraint(['job_id'], ['dt_translation_jobs.id'], name=op.f('dt_job_files_ibfk_1')),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('ms_users',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('username', mysql.VARCHAR(length=80), nullable=False),
    sa.Column('password_hash', mysql.VARCHAR(length=128), nullable=False),
    sa.Column('role', mysql.VARCHAR(length=20), nullable=False),
    sa.Column('created_at', mysql.DATETIME(), server_default=sa.text('(now())'), nullable=True),
    sa.Column('display_name', mysql.VARCHAR(length=100), nullable=True),
    sa.Column('last_login', mysql.DATETIME(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('username'), 'ms_users', ['username'], unique=True)
    op.create_table('todo_mail_log',
    sa.Column('id', mysql.BIGINT(), autoincrement=True, nullable=False),
    sa.Column('todo_id', mysql.CHAR(length=36), nullable=True),
    sa.Column('type', mysql.ENUM('SCHEDULED', 'FIRE'), nullable=False),
    sa.Column('triggered_by_ad', mysql.VARCHAR(length=128), nullable=True),
    sa.Column('recipients', mysql.TEXT(), nullable=True),
    sa.Column('subject', mysql.VARCHAR(length=255), nullable=True),
    sa.Column('status', mysql.ENUM('QUEUED', 'SENT', 'FAILED'), nullable=True),
    sa.Column('provider_msg_id', mysql.VARCHAR(length=128), nullable=True),
    sa.Column('error_text', mysql.TEXT(), nullable=True),
    sa.Column('created_at', mysql.DATETIME(), nullable=False),
    sa.Column('sent_at', mysql.DATETIME(), nullable=True),
    sa.ForeignKeyConstraint(['todo_id'], ['todo_item.id'], name=op.f('todo_mail_log_ibfk_1'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('ast_system_logs',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False, comment='主鍵ID'),
    sa.Column('timestamp', mysql.DATETIME(fsp=3), nullable=False, comment='事件時間戳（毫秒精度）'),
    sa.Column('event_type', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=50), nullable=False, comment='事件類型（startup, shutdown, config_change, health_check）'),
    sa.Column('event_data', mysql.JSON(), nullable=True, comment='事件資料（JSON格式）'),
    sa.Column('cpu_usage', mysql.FLOAT(), nullable=True, comment='CPU使用率（%）'),
    sa.Column('memory_usage', mysql.FLOAT(), nullable=True, comment='記憶體使用率（%）'),
    sa.Column('disk_usage', mysql.FLOAT(), nullable=True, comment='磁碟使用率（%）'),
    sa.Column('network_status', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=50), nullable=True, comment='網路狀態'),
    sa.Column('version', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=20), nullable=True, comment='系統版本'),
    sa.Column('hostname', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='主機名稱'),
    sa.Column('ip_address', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=50), nullable=True, comment='IP地址'),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='記錄創建時間'),
    sa.PrimaryKeyConstraint('id'),
    comment='系統事件日誌表',
    mysql_collate='utf8mb4_unicode_ci',
    mysql_comment='系統事件日誌表',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('idx_version'), 'ast_system_logs', ['version'], unique=False)
    op.create_index(op.f('idx_timestamp'), 'ast_system_logs', ['timestamp'], unique=False)
    op.create_index(op.f('idx_event_type'), 'ast_system_logs', ['event_type'], unique=False)
    op.create_index(op.f('idx_created_at'), 'ast_system_logs', ['created_at'], unique=False)
    op.create_table('dt_notifications',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('notification_uuid', mysql.VARCHAR(length=36), nullable=False, comment='通知唯一識別碼'),
    sa.Column('user_id', mysql.INTEGER(), autoincrement=False, nullable=False, comment='使用者ID'),
    sa.Column('type', mysql.ENUM('INFO', 'SUCCESS', 'WARNING', 'ERROR'), nullable=False, comment='通知類型'),
    sa.Column('title', mysql.VARCHAR(length=255), nullable=False, comment='通知標題'),
    sa.Column('message', mysql.TEXT(), nullable=False, comment='通知內容'),
    sa.Column('job_uuid', mysql.VARCHAR(length=36), nullable=True, comment='關聯任務UUID'),
    sa.Column('link', mysql.VARCHAR(length=500), nullable=True, comment='相關連結'),
    sa.Column('is_read', mysql.TINYINT(display_width=1), autoincrement=False, nullable=False, comment='是否已讀'),
    sa.Column('read_at', mysql.DATETIME(), nullable=True, comment='閱讀時間'),
    sa.Column('created_at', mysql.DATETIME(), nullable=False, comment='建立時間'),
    sa.Column('expires_at', mysql.DATETIME(), nullable=True, comment='過期時間'),
    sa.Column('extra_data', mysql.JSON(), nullable=True, comment='額外數據'),
    sa.ForeignKeyConstraint(['user_id'], ['dt_users.id'], name=op.f('dt_notifications_ibfk_1')),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('ix_dt_notifications_notification_uuid'), 'dt_notifications', ['notification_uuid'], unique=True)
    op.create_table('sys_user',
    sa.Column('id', mysql.BIGINT(), autoincrement=True, nullable=False),
    sa.Column('username', mysql.VARCHAR(length=255), nullable=False, comment='登入帳號'),
    sa.Column('password_hash', mysql.VARCHAR(length=512), nullable=True, comment='密碼雜湊 (如果需要本地儲存)'),
    sa.Column('email', mysql.VARCHAR(length=255), nullable=False, comment='電子郵件'),
    sa.Column('display_name', mysql.VARCHAR(length=255), nullable=True, comment='顯示名稱'),
    sa.Column('api_user_id', mysql.VARCHAR(length=255), nullable=True, comment='API 回傳的使用者 ID'),
    sa.Column('api_access_token', mysql.TEXT(), nullable=True, comment='API 回傳的 access_token'),
    sa.Column('api_token_expires_at', mysql.DATETIME(), nullable=True, comment='API Token 過期時間'),
    sa.Column('auth_method', mysql.ENUM('API', 'LDAP'), nullable=True, comment='認證方式'),
    sa.Column('last_login_at', mysql.DATETIME(), nullable=True, comment='最後登入時間'),
    sa.Column('last_login_ip', mysql.VARCHAR(length=45), nullable=True, comment='最後登入 IP'),
    sa.Column('login_count', mysql.INTEGER(), autoincrement=False, nullable=True, comment='登入次數'),
    sa.Column('login_success_count', mysql.INTEGER(), autoincrement=False, nullable=True, comment='成功登入次數'),
    sa.Column('login_fail_count', mysql.INTEGER(), autoincrement=False, nullable=True, comment='失敗登入次數'),
    sa.Column('is_active', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True, comment='是否啟用'),
    sa.Column('is_locked', mysql.TINYINT(display_width=1), autoincrement=False, nullable=True, comment='是否鎖定'),
    sa.Column('locked_until', mysql.DATETIME(), nullable=True, comment='鎖定至何時'),
    sa.Column('created_at', mysql.DATETIME(), nullable=True, comment='建立時間'),
    sa.Column('updated_at', mysql.DATETIME(), nullable=True, comment='更新時間'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('username'), 'sys_user', ['username'], unique=True)
    op.create_index(op.f('email'), 'sys_user', ['email'], unique=True)
    op.create_table('BOM',
    sa.Column('Organization Code', mysql.TEXT(), nullable=True),
    sa.Column('Ass Item No', mysql.TEXT(), nullable=True),
    sa.Column('Description', mysql.TEXT(), nullable=True),
    sa.Column('Alt Bom Designator', mysql.TEXT(), nullable=True),
    sa.Column('Pj Type', mysql.TEXT(), nullable=True),
    sa.Column('Pj Package', mysql.TEXT(), nullable=True),
    sa.Column('Pj Function', mysql.TEXT(), nullable=True),
    sa.Column('Family', mysql.TEXT(), nullable=True),
    sa.Column('Logo', mysql.TEXT(), nullable=True),
    sa.Column('Marking Code', mysql.TEXT(), nullable=True),
    sa.Column('Pack', mysql.TEXT(), nullable=True),
    sa.Column('Packing Size', mysql.TEXT(), nullable=True),
    sa.Column('Routing', mysql.TEXT(), nullable=True),
    sa.Column('Source', mysql.TEXT(), nullable=True),
    sa.Column('Status Code', mysql.TEXT(), nullable=True),
    sa.Column('Doc No', mysql.TEXT(), nullable=True),
    sa.Column('Cust No', mysql.TEXT(), nullable=True),
    sa.Column('Item M Type', mysql.TEXT(), nullable=True),
    sa.Column('Creation Date', mysql.DATETIME(), nullable=True),
    sa.Column('Die Saw', mysql.TEXT(), nullable=True),
    sa.Column('Bop', mysql.TEXT(), nullable=True),
    sa.Column('Die Qty', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Wire Qty', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Process Spec', mysql.TEXT(), nullable=True),
    sa.Column('Bom Name', mysql.TEXT(), nullable=True),
    sa.Column('Operation Seq Num', mysql.BIGINT(), autoincrement=False, nullable=True),
    sa.Column('Com Item No', mysql.TEXT(), nullable=True),
    sa.Column('Vendor Name', mysql.TEXT(), nullable=True),
    sa.Column('Com Item B Type', mysql.TEXT(), nullable=True),
    sa.Column('Com Item M Type', mysql.TEXT(), nullable=True),
    sa.Column('Com Item Desc', mysql.TEXT(), nullable=True),
    sa.Column('Com Qty', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Wip Supply Type', mysql.TEXT(), nullable=True),
    sa.Column('Sub Com Item No', mysql.TEXT(), nullable=True),
    sa.Column('Sub Com Item B Type', mysql.TEXT(), nullable=True),
    sa.Column('Sub Com Item M Type', mysql.TEXT(), nullable=True),
    sa.Column('Sub Com Item Desc', mysql.TEXT(), nullable=True),
    sa.Column('Primary Unit Of Measure', mysql.TEXT(), nullable=True),
    sa.Column('Type', mysql.TEXT(), nullable=True),
    sa.Column('Sub Com Qty', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Sub Com Yield', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Wip Supply Type2', mysql.TEXT(), nullable=True),
    sa.Column('New Component Quantity', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Component Qty', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Sub Com Remarks', mysql.TEXT(), nullable=True),
    sa.Column('Die Seq', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Wafer Size', mysql.TEXT(), nullable=True),
    sa.Column('Wafer Type', mysql.TEXT(), nullable=True),
    sa.Column('Die Length', mysql.DOUBLE(asdecimal=True), nullable=True),
    sa.Column('Die Width', mysql.TEXT(), nullable=True),
    sa.Column('Thickness', mysql.TEXT(), nullable=True),
    sa.Column('Top Back Metal', mysql.TEXT(), nullable=True),
    sa.Column('Wire Material', mysql.TEXT(), nullable=True),
    sa.Column('Wire Mil', mysql.TEXT(), nullable=True),
    sa.Column('Wire Type', mysql.TEXT(), nullable=True),
    sa.Column('Options', mysql.TEXT(), nullable=True),
    sa.Column('Lef Material', mysql.TEXT(), nullable=True),
    sa.Column('Packing Min Qty', mysql.TEXT(), nullable=True),
    sa.Column('Pitch', mysql.TEXT(), nullable=True),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('todo_fire_email_log',
    sa.Column('id', mysql.BIGINT(), autoincrement=True, nullable=False),
    sa.Column('todo_id', mysql.CHAR(length=36), nullable=False),
    sa.Column('sender_ad', mysql.VARCHAR(length=128), nullable=False),
    sa.Column('sent_at', mysql.DATETIME(), nullable=False),
    sa.ForeignKeyConstraint(['todo_id'], ['todo_item.id'], name=op.f('todo_fire_email_log_ibfk_1'), ondelete='CASCADE'),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_table('ast_ai_interaction_logs',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False, comment='主鍵ID'),
    sa.Column('timestamp', mysql.DATETIME(fsp=3), nullable=False, comment='互動時間戳（毫秒精度）'),
    sa.Column('interaction_type', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=50), nullable=False, comment='互動類型（chat, translation, tone_conversion, file_processing, agent_execution）'),
    sa.Column('request_data', mysql.JSON(), nullable=True, comment='請求資料（JSON格式）'),
    sa.Column('response_data', mysql.JSON(), nullable=True, comment='響應資料（JSON格式）'),
    sa.Column('processing_time_ms', mysql.INTEGER(), autoincrement=False, nullable=True, comment='處理時間（毫秒）'),
    sa.Column('status', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=20), nullable=True, comment='狀態（success, error, timeout）'),
    sa.Column('error_message', mysql.TEXT(collation='utf8mb4_unicode_ci'), nullable=True, comment='錯誤訊息'),
    sa.Column('user_id', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='用戶ID'),
    sa.Column('session_id', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=100), nullable=True, comment='會話ID'),
    sa.Column('model_name', mysql.VARCHAR(collation='utf8mb4_unicode_ci', length=50), nullable=True, comment='AI模型名稱'),
    sa.Column('token_usage', mysql.JSON(), nullable=True, comment='Token使用量統計（JSON格式）'),
    sa.Column('created_at', mysql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='記錄創建時間'),
    sa.PrimaryKeyConstraint('id'),
    comment='AI互動日誌表，記錄所有AI服務互動',
    mysql_collate='utf8mb4_unicode_ci',
    mysql_comment='AI互動日誌表，記錄所有AI服務互動',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('idx_user_session'), 'ast_ai_interaction_logs', ['user_id', 'session_id'], unique=False)
    op.create_index(op.f('idx_type'), 'ast_ai_interaction_logs', ['interaction_type'], unique=False)
    op.create_index(op.f('idx_timestamp'), 'ast_ai_interaction_logs', ['timestamp'], unique=False)
    op.create_index(op.f('idx_status'), 'ast_ai_interaction_logs', ['status'], unique=False)
    op.create_index(op.f('idx_model_name'), 'ast_ai_interaction_logs', ['model_name'], unique=False)
    op.create_index(op.f('idx_created_at'), 'ast_ai_interaction_logs', ['created_at'], unique=False)
    op.create_table('tst_user',
    sa.Column('id', mysql.INTEGER(), autoincrement=True, nullable=False),
    sa.Column('username', mysql.VARCHAR(length=120), nullable=False),
    sa.Column('password_hash', mysql.VARCHAR(length=255), nullable=False),
    sa.Column('name', mysql.VARCHAR(length=100), nullable=False),
    sa.Column('role', mysql.ENUM('viewer', 'editor', 'admin'), nullable=False),
    sa.Column('last_login', mysql.DATETIME(), nullable=True),
    sa.PrimaryKeyConstraint('id'),
    mysql_collate='utf8mb4_0900_ai_ci',
    mysql_default_charset='utf8mb4',
    mysql_engine='InnoDB'
    )
    op.create_index(op.f('username'), 'tst_user', ['username'], unique=True)
    # op.drop_index(op.f('ix_tool_ocr_task_files_task_id'), table_name='tool_ocr_task_files')
    # op.drop_index(op.f('ix_tool_ocr_task_files_id'), table_name='tool_ocr_task_files')
    # op.drop_index(op.f('ix_tool_ocr_task_files_file_hash'), table_name='tool_ocr_task_files')
    # op.drop_table('tool_ocr_task_files')
    # op.drop_index(op.f('ix_tool_ocr_tasks_user_id'), table_name='tool_ocr_tasks')
    # op.drop_index(op.f('ix_tool_ocr_tasks_task_id'), table_name='tool_ocr_tasks')
    # op.drop_index(op.f('ix_tool_ocr_tasks_status'), table_name='tool_ocr_tasks')
    # op.drop_index(op.f('ix_tool_ocr_tasks_id'), table_name='tool_ocr_tasks')
    # op.drop_index(op.f('ix_tool_ocr_tasks_filename'), table_name='tool_ocr_tasks')
    # op.drop_index(op.f('ix_tool_ocr_tasks_created_at'), table_name='tool_ocr_tasks')
    # op.drop_table('tool_ocr_tasks')
    # op.drop_index(op.f('ix_tool_ocr_sessions_user_id'), table_name='tool_ocr_sessions')
    # op.drop_index(op.f('ix_tool_ocr_sessions_id'), table_name='tool_ocr_sessions')
    # op.drop_index(op.f('ix_tool_ocr_sessions_expires_at'), table_name='tool_ocr_sessions')
    # op.drop_index(op.f('ix_tool_ocr_sessions_created_at'), table_name='tool_ocr_sessions')
    # op.drop_table('tool_ocr_sessions')
    # op.drop_index(op.f('ix_tool_ocr_users_is_active'), table_name='tool_ocr_users')
    # op.drop_index(op.f('ix_tool_ocr_users_id'), table_name='tool_ocr_users')
    # op.drop_index(op.f('ix_tool_ocr_users_email'), table_name='tool_ocr_users')
    # op.drop_table('tool_ocr_users')
    # ### end Alembic commands ###
