195 lines
9.1 KiB
Python
195 lines
9.1 KiB
Python
|
|
"""add business_contacts and routing_events tables
|
|||
|
|
|
|||
|
|
新建 business_contacts 表(业务联系人)和 routing_events 表(路由命中统计),
|
|||
|
|
并预置初始联系人数据(行政/HR/财务/法务/物业 各1-2人)。
|
|||
|
|
|
|||
|
|
关联设计文档:docs/03-技术架构/业务路由推荐-架构设计.md
|
|||
|
|
|
|||
|
|
Revision ID: 047_business_contacts
|
|||
|
|
Revises: 046_add_login_logs
|
|||
|
|
Create Date: 2026-07-15
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
from typing import Sequence, Union
|
|||
|
|
|
|||
|
|
from alembic import op
|
|||
|
|
import sqlalchemy as sa
|
|||
|
|
|
|||
|
|
|
|||
|
|
# revision identifiers, used by Alembic.
|
|||
|
|
revision: str = '047_business_contacts'
|
|||
|
|
down_revision: Union[str, None] = '046_add_login_logs'
|
|||
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|||
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|||
|
|
|
|||
|
|
|
|||
|
|
def upgrade() -> None:
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
# 新建 business_contacts 表
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
op.create_table(
|
|||
|
|
'business_contacts',
|
|||
|
|
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True,
|
|||
|
|
comment='主键'),
|
|||
|
|
sa.Column('name', sa.String(50), nullable=False, comment='联系人姓名'),
|
|||
|
|
sa.Column('gender', sa.String(10), nullable=False, server_default='male',
|
|||
|
|
comment='性别(male/female)'),
|
|||
|
|
sa.Column('department', sa.String(100), nullable=False, comment='部门名称'),
|
|||
|
|
sa.Column('position', sa.String(100), nullable=False, comment='岗位'),
|
|||
|
|
sa.Column('responsibility', sa.String(500), nullable=False, comment='负责业务描述'),
|
|||
|
|
sa.Column('extension', sa.String(20), nullable=True, comment='分机号'),
|
|||
|
|
sa.Column('service_area', sa.String(200), nullable=True, comment='服务区域/办公地点'),
|
|||
|
|
sa.Column('wecom_userid', sa.String(100), nullable=False, comment='企微用户ID'),
|
|||
|
|
sa.Column('avatar_url', sa.String(500), nullable=True, comment='头像URL'),
|
|||
|
|
sa.Column('business_category', sa.String(50), nullable=False,
|
|||
|
|
comment='业务类别(行政/人力资源/财务/法务/行政-物业)'),
|
|||
|
|
sa.Column('is_active', sa.Boolean(), nullable=False, server_default=sa.text('true'),
|
|||
|
|
comment='是否启用'),
|
|||
|
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False,
|
|||
|
|
server_default=sa.func.now(), comment='创建时间'),
|
|||
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False,
|
|||
|
|
server_default=sa.func.now(), comment='更新时间'),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 索引:按业务类别 + 是否启用查询
|
|||
|
|
op.create_index(
|
|||
|
|
'idx_business_contacts_category',
|
|||
|
|
'business_contacts',
|
|||
|
|
['business_category', 'is_active'],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
# 新建 routing_events 表(P1)
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
op.create_table(
|
|||
|
|
'routing_events',
|
|||
|
|
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True,
|
|||
|
|
comment='主键'),
|
|||
|
|
sa.Column('conversation_id', sa.String(36),
|
|||
|
|
sa.ForeignKey('conversations.id', ondelete='CASCADE'),
|
|||
|
|
nullable=False, comment='会话ID'),
|
|||
|
|
sa.Column('employee_id', sa.String(64), nullable=False, comment='员工ID'),
|
|||
|
|
sa.Column('message_content', sa.String(500), nullable=False,
|
|||
|
|
comment='触发路由的员工消息(截断)'),
|
|||
|
|
sa.Column('business_category', sa.String(50), nullable=False,
|
|||
|
|
comment='识别的业务类别'),
|
|||
|
|
sa.Column('routing_confidence', sa.Float(), nullable=False, comment='路由置信度'),
|
|||
|
|
sa.Column('contact_id', sa.Integer(),
|
|||
|
|
sa.ForeignKey('business_contacts.id', ondelete='SET NULL'),
|
|||
|
|
nullable=True, comment='推荐的联系人ID'),
|
|||
|
|
sa.Column('contact_name', sa.String(50), nullable=False,
|
|||
|
|
comment='联系人姓名(冗余)'),
|
|||
|
|
sa.Column('is_clicked', sa.Boolean(), nullable=False, server_default=sa.text('false'),
|
|||
|
|
comment='是否点击了联系TA'),
|
|||
|
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False,
|
|||
|
|
server_default=sa.func.now(), comment='创建时间'),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 索引
|
|||
|
|
op.create_index('idx_routing_events_category', 'routing_events', ['business_category'])
|
|||
|
|
op.create_index('idx_routing_events_conv', 'routing_events', ['conversation_id'])
|
|||
|
|
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
# 预置初始联系人数据
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
# 说明:wecom_userid 使用占位符,上线前需替换为真实的企微通讯录 userid
|
|||
|
|
# --------------------------------------------------------------------------
|
|||
|
|
contacts_data = [
|
|||
|
|
# 行政(2人)
|
|||
|
|
{
|
|||
|
|
'name': '王芳', 'gender': 'female', 'department': '行政部',
|
|||
|
|
'position': '设备管理岗', 'responsibility': '打印机/复印机/扫描仪',
|
|||
|
|
'extension': '8002', 'service_area': '滨江园区 3-5楼',
|
|||
|
|
'wecom_userid': 'WangFang', 'avatar_url': '',
|
|||
|
|
'business_category': '行政', 'is_active': True,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
'name': '陈伟', 'gender': 'male', 'department': '行政部',
|
|||
|
|
'position': '行政事务岗', 'responsibility': '办公用品/名片印刷/保洁服务',
|
|||
|
|
'extension': '8003', 'service_area': '滨江园区 1-2楼',
|
|||
|
|
'wecom_userid': 'ChenWei', 'avatar_url': '',
|
|||
|
|
'business_category': '行政', 'is_active': True,
|
|||
|
|
},
|
|||
|
|
# 人力资源(2人)
|
|||
|
|
{
|
|||
|
|
'name': '李娜', 'gender': 'female', 'department': '人力资源部',
|
|||
|
|
'position': '员工服务岗', 'responsibility': '工牌补办/考勤异常/入职手续',
|
|||
|
|
'extension': '8005', 'service_area': '滨江园区 A栋3楼',
|
|||
|
|
'wecom_userid': 'LiNa', 'avatar_url': '',
|
|||
|
|
'business_category': '人力资源', 'is_active': True,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
'name': '张磊', 'gender': 'male', 'department': '人力资源部',
|
|||
|
|
'position': '薪酬福利岗', 'responsibility': '社保/公积金/离职手续',
|
|||
|
|
'extension': '8006', 'service_area': '滨江园区 A栋3楼',
|
|||
|
|
'wecom_userid': 'ZhangLei', 'avatar_url': '',
|
|||
|
|
'business_category': '人力资源', 'is_active': True,
|
|||
|
|
},
|
|||
|
|
# 财务(1人)
|
|||
|
|
{
|
|||
|
|
'name': '刘洋', 'gender': 'male', 'department': '财务部',
|
|||
|
|
'position': '费用报销岗', 'responsibility': '报销/发票/借款/工资条',
|
|||
|
|
'extension': '8010', 'service_area': '滨江园区 B栋4楼',
|
|||
|
|
'wecom_userid': 'LiuYang', 'avatar_url': '',
|
|||
|
|
'business_category': '财务', 'is_active': True,
|
|||
|
|
},
|
|||
|
|
# 法务(1人)
|
|||
|
|
{
|
|||
|
|
'name': '赵敏', 'gender': 'female', 'department': '法务部',
|
|||
|
|
'position': '法务顾问岗', 'responsibility': '合同/法律咨询/知识产权',
|
|||
|
|
'extension': '8015', 'service_area': '滨江园区 C栋5楼',
|
|||
|
|
'wecom_userid': 'ZhaoMin', 'avatar_url': '',
|
|||
|
|
'business_category': '法务', 'is_active': True,
|
|||
|
|
},
|
|||
|
|
# 行政-物业(1人)
|
|||
|
|
{
|
|||
|
|
'name': '孙强', 'gender': 'male', 'department': '行政部',
|
|||
|
|
'position': '物业管理岗', 'responsibility': '空调/电梯/门禁/停车',
|
|||
|
|
'extension': '8008', 'service_area': '滨江园区 全园区',
|
|||
|
|
'wecom_userid': 'SunQiang', 'avatar_url': '',
|
|||
|
|
'business_category': '行政-物业', 'is_active': True,
|
|||
|
|
},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# 批量插入初始数据
|
|||
|
|
op.bulk_insert(
|
|||
|
|
sa.table(
|
|||
|
|
'business_contacts',
|
|||
|
|
sa.column('name', sa.String),
|
|||
|
|
sa.column('gender', sa.String),
|
|||
|
|
sa.column('department', sa.String),
|
|||
|
|
sa.column('position', sa.String),
|
|||
|
|
sa.column('responsibility', sa.String),
|
|||
|
|
sa.column('extension', sa.String),
|
|||
|
|
sa.column('service_area', sa.String),
|
|||
|
|
sa.column('wecom_userid', sa.String),
|
|||
|
|
sa.column('avatar_url', sa.String),
|
|||
|
|
sa.column('business_category', sa.String),
|
|||
|
|
sa.column('is_active', sa.Boolean),
|
|||
|
|
sa.column('created_at', sa.DateTime),
|
|||
|
|
sa.column('updated_at', sa.DateTime),
|
|||
|
|
),
|
|||
|
|
[
|
|||
|
|
{
|
|||
|
|
**c,
|
|||
|
|
'avatar_url': c['avatar_url'] or None,
|
|||
|
|
'extension': c.get('extension') or None,
|
|||
|
|
'service_area': c.get('service_area') or None,
|
|||
|
|
'created_at': sa.func.now(),
|
|||
|
|
'updated_at': sa.func.now(),
|
|||
|
|
}
|
|||
|
|
for c in contacts_data
|
|||
|
|
],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def downgrade() -> None:
|
|||
|
|
# 删除索引
|
|||
|
|
op.drop_index('idx_routing_events_conv', table_name='routing_events')
|
|||
|
|
op.drop_index('idx_routing_events_category', table_name='routing_events')
|
|||
|
|
op.drop_index('idx_business_contacts_category', table_name='business_contacts')
|
|||
|
|
|
|||
|
|
# 删除表
|
|||
|
|
op.drop_table('routing_events')
|
|||
|
|
op.drop_table('business_contacts')
|