数据库: - daily_topics表:每天每场景一条热点问题记录 - sample_clicks表:记录用户点击示例问题行为 后端: - DailyTopicStore: CRUD + 埋点 + 点击统计 - generate_daily_topics: AI生成4场景各25-30个问题 - 定时任务:每天凌晨3点自动生成(后台线程轮询) - GET /topics/daily: 获取当天热点(公开) - POST /topics/click: 点击埋点 - GET /admin/daily-topics: 管理端查看 - POST /admin/daily-topics: 管理员手动更新 - POST /admin/daily-topics/generate: 手动触发AI生成 - GET /admin/sample-clicks: 点击统计 前端: - ChatPanel挂载时取当天热点,无则用硬编码兜底 - 点击示例问题时记录埋点 - AdminDailyTopics管理页面:查看/编辑/手动生成 - 路由 /admin/daily-topics + 导航配置 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
482 lines
28 KiB
Python
482 lines
28 KiB
Python
"""BossAi 数据库初始化:建表 + 索引 + 种子数据。
|
|
|
|
运行:python3 db_init.py
|
|
幂等:重复执行不会报错,已存在的表跳过。
|
|
"""
|
|
import psycopg2
|
|
from datetime import datetime, timezone
|
|
|
|
SCHEMA = """
|
|
-- 法规知识库:法规/案例/模板,支持 RAG 检索
|
|
CREATE TABLE IF NOT EXISTS knowledge_docs (
|
|
id SERIAL PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
source TEXT NOT NULL DEFAULT 'local',
|
|
doc_type TEXT NOT NULL DEFAULT 'law', -- law / case / template
|
|
region TEXT, -- 适用地区,如 全国 / 上海
|
|
effective_date DATE, -- 生效日期(法规)
|
|
status TEXT NOT NULL DEFAULT 'active', -- active / superseded / repealed
|
|
keywords TEXT, -- 逗号分隔关键词
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_knowledge_docs_type ON knowledge_docs(doc_type);
|
|
CREATE INDEX IF NOT EXISTS idx_knowledge_docs_status ON knowledge_docs(status);
|
|
CREATE INDEX IF NOT EXISTS idx_knowledge_docs_keywords ON knowledge_docs USING gin(to_tsvector('simple', keywords));
|
|
|
|
-- 用户设置:单用户 MVP,key-value 存储
|
|
CREATE TABLE IF NOT EXISTS user_settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- 专家分配规则:风险等级 → 专家类型 + SLA 时长(小时)
|
|
CREATE TABLE IF NOT EXISTS expert_rules (
|
|
id SERIAL PRIMARY KEY,
|
|
risk_level TEXT NOT NULL UNIQUE, -- green / amber / orange / red
|
|
expert_type TEXT NOT NULL, -- 律师 / 资深律师 / 税务师 / 会计师
|
|
sla_hours INTEGER NOT NULL DEFAULT 24, -- SLA 响应时长(小时)
|
|
auto_assign TEXT, -- 自动分配的专家姓名(可空)
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- 人工复核工单
|
|
CREATE TABLE IF NOT EXISTS review_tickets (
|
|
id SERIAL PRIMARY KEY,
|
|
ticket_no TEXT NOT NULL UNIQUE, -- RT-YYYYMMDD-NNN
|
|
session_id TEXT, -- 来源会话(脱敏后可空)
|
|
question TEXT NOT NULL, -- 用户原始问题(脱敏)
|
|
risk_level TEXT NOT NULL, -- green / amber / orange / red
|
|
conclusion TEXT, -- AI 结论摘要
|
|
contact TEXT, -- 用户联系方式(脱敏)
|
|
status TEXT NOT NULL DEFAULT 'pending', -- pending / assigned / resolved / closed
|
|
priority TEXT NOT NULL DEFAULT 'normal', -- normal / urgent
|
|
assignee TEXT, -- 分配的律师/专家
|
|
expert_type TEXT, -- 专家类型(来自规则)
|
|
resolution TEXT, -- 处理结果
|
|
sla_deadline TIMESTAMPTZ, -- SLA 截止时间
|
|
sla_status TEXT NOT NULL DEFAULT 'normal', -- normal / warning / overdue
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_review_tickets_status ON review_tickets(status);
|
|
CREATE INDEX IF NOT EXISTS idx_review_tickets_priority ON review_tickets(priority);
|
|
CREATE INDEX IF NOT EXISTS idx_review_tickets_created ON review_tickets(created_at DESC);
|
|
|
|
-- 用户画像:按手机号累积用户特征(非对话内容),用于个性化回复
|
|
CREATE TABLE IF NOT EXISTS user_profiles (
|
|
phone TEXT PRIMARY KEY,
|
|
profile JSONB NOT NULL DEFAULT '{}', -- 画像数据(行业/规模/偏好/常用话题等)
|
|
interaction_count INTEGER NOT NULL DEFAULT 0, -- 交互次数
|
|
last_active TIMESTAMPTZ, -- 最后活跃时间
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- 用户画像历史版本:每次更新时写入快照,支持版本回溯
|
|
CREATE TABLE IF NOT EXISTS user_profile_history (
|
|
id SERIAL PRIMARY KEY,
|
|
phone TEXT NOT NULL,
|
|
version INTEGER NOT NULL, -- 版本号(递增)
|
|
profile JSONB NOT NULL DEFAULT '{}', -- 画像快照
|
|
source TEXT NOT NULL DEFAULT 'auto', -- auto(AI提取) / manual(用户确认)
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_profile_history_phone ON user_profile_history(phone);
|
|
CREATE INDEX IF NOT EXISTS idx_profile_history_phone_version ON user_profile_history(phone, version DESC);
|
|
|
|
-- 用户账号:手机号+密码注册登录
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id SERIAL PRIMARY KEY,
|
|
phone TEXT NOT NULL UNIQUE, -- 手机号(登录账号)
|
|
password TEXT NOT NULL, -- bcrypt 哈希
|
|
name TEXT, -- 昵称(可选)
|
|
status TEXT NOT NULL DEFAULT 'active', -- active/inactive(管理员终止)
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- 专家库:按分类上架的专家(法务/经营/情绪/生活)
|
|
CREATE TABLE IF NOT EXISTS experts (
|
|
id SERIAL PRIMARY KEY,
|
|
name TEXT NOT NULL, -- 专家姓名
|
|
phone TEXT NOT NULL UNIQUE, -- 手机号(专家登录账号)
|
|
password TEXT NOT NULL, -- bcrypt 哈希
|
|
category TEXT NOT NULL, -- 分类:legal/decision/emotion/life
|
|
title TEXT, -- 职称/头衔(如 高级律师/注册会计师)
|
|
bio TEXT, -- 简介
|
|
expertise TEXT, -- 擅长领域(逗号分隔)
|
|
avatar_url TEXT, -- 头像 URL(可空)
|
|
rate INTEGER NOT NULL DEFAULT 0, -- 服务评分(0-500,除以100为星级)
|
|
rating_count INTEGER NOT NULL DEFAULT 0, -- 评分人数
|
|
status TEXT NOT NULL DEFAULT 'active', -- active(上架) / inactive(下架)
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_experts_category ON experts(category);
|
|
CREATE INDEX IF NOT EXISTS idx_experts_status ON experts(status);
|
|
|
|
-- 专家服务关系:用户选定专家,专家同意后建立服务关系
|
|
CREATE TABLE IF NOT EXISTS expert_services (
|
|
id SERIAL PRIMARY KEY,
|
|
user_phone TEXT NOT NULL, -- 用户手机号
|
|
expert_id INTEGER NOT NULL REFERENCES experts(id),
|
|
category TEXT NOT NULL, -- 分类
|
|
status TEXT NOT NULL DEFAULT 'pending', -- pending(待确认) / active(服务中) / closed(已结束)
|
|
signed_at TIMESTAMPTZ, -- 专家确认时间
|
|
closed_at TIMESTAMPTZ, -- 结束时间
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE(user_phone, expert_id) -- 同一用户同一专家只能有一条服务关系
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_services_user ON expert_services(user_phone);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_services_expert ON expert_services(expert_id);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_services_status ON expert_services(status);
|
|
|
|
-- 专家咨询:用户将与 AI 的对话话题发给专家
|
|
CREATE TABLE IF NOT EXISTS expert_consultations (
|
|
id SERIAL PRIMARY KEY,
|
|
service_id INTEGER NOT NULL REFERENCES expert_services(id),
|
|
user_phone TEXT NOT NULL,
|
|
expert_id INTEGER NOT NULL REFERENCES experts(id),
|
|
category TEXT NOT NULL,
|
|
topic TEXT NOT NULL, -- 咨询话题(来自 AI 对话摘要)
|
|
context TEXT, -- 对话上下文(脱敏后的摘要)
|
|
question TEXT, -- 用户给专家的具体问题
|
|
reply TEXT, -- 专家回复
|
|
status TEXT NOT NULL DEFAULT 'pending', -- pending(待回复) / replied(已回复) / closed(已关闭)
|
|
replied_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_consultations_service ON expert_consultations(service_id);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_consultations_expert ON expert_consultations(expert_id);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_consultations_status ON expert_consultations(status);
|
|
|
|
-- 专家服务评价
|
|
CREATE TABLE IF NOT EXISTS expert_ratings (
|
|
id SERIAL PRIMARY KEY,
|
|
consultation_id INTEGER NOT NULL REFERENCES expert_consultations(id),
|
|
user_phone TEXT NOT NULL,
|
|
expert_id INTEGER NOT NULL REFERENCES experts(id),
|
|
rating INTEGER NOT NULL, -- 评分 1-5
|
|
comment TEXT, -- 评价内容
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_ratings_expert ON expert_ratings(expert_id);
|
|
|
|
-- 管理员表(独立于用户和专家)
|
|
CREATE TABLE IF NOT EXISTS admins (
|
|
id SERIAL PRIMARY KEY,
|
|
phone TEXT NOT NULL UNIQUE, -- 手机号(登录账号)
|
|
password TEXT NOT NULL, -- bcrypt 哈希
|
|
name TEXT, -- 姓名
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- 对话历史:普通模式明文存储,保密模式加密存储,阅后即焚不落库
|
|
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
id SERIAL PRIMARY KEY,
|
|
session_id TEXT NOT NULL, -- 会话 ID(内存会话标识)
|
|
phone TEXT NOT NULL, -- 用户手机号
|
|
mode TEXT NOT NULL, -- normal / confidential(ephemeral 不落库)
|
|
role TEXT NOT NULL, -- user / assistant
|
|
content TEXT NOT NULL, -- 消息正文(保密模式为密文)
|
|
risk_data JSONB NOT NULL DEFAULT '{}', -- 风险卡(level/conclusion/actions/evidence/escalation)
|
|
meta_data JSONB NOT NULL DEFAULT '{}', -- 元数据(scene/inference_location/model/fallback_used)
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_chat_messages_session ON chat_messages(session_id);
|
|
CREATE INDEX IF NOT EXISTS idx_chat_messages_phone ON chat_messages(phone);
|
|
CREATE INDEX IF NOT EXISTS idx_chat_messages_phone_mode ON chat_messages(phone, mode);
|
|
CREATE INDEX IF NOT EXISTS idx_chat_messages_created ON chat_messages(created_at DESC);
|
|
|
|
-- ===== BossAi 2.0 新增表 =====
|
|
|
|
-- 案卡:每次咨询绑定一张案卡,可视化追踪全生命周期
|
|
CREATE TABLE IF NOT EXISTS matters (
|
|
id SERIAL PRIMARY KEY,
|
|
code TEXT NOT NULL UNIQUE,
|
|
user_phone TEXT NOT NULL,
|
|
scene TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'new',
|
|
session_id TEXT,
|
|
risk_level TEXT,
|
|
summary TEXT,
|
|
closed_at TIMESTAMPTZ,
|
|
execution_result TEXT,
|
|
execution_note TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_matters_user_phone ON matters(user_phone);
|
|
CREATE INDEX IF NOT EXISTS idx_matters_status ON matters(status);
|
|
CREATE INDEX IF NOT EXISTS idx_matters_user_status ON matters(user_phone, status);
|
|
CREATE INDEX IF NOT EXISTS idx_matters_created ON matters(created_at DESC);
|
|
|
|
-- 专家关系:用户咨询过的专家自动沉淀为"我的团队"
|
|
CREATE TABLE IF NOT EXISTS expert_relations (
|
|
id SERIAL PRIMARY KEY,
|
|
user_phone TEXT NOT NULL,
|
|
expert_id INTEGER NOT NULL REFERENCES experts(id),
|
|
consultation_count INTEGER NOT NULL DEFAULT 0,
|
|
last_consulted_at TIMESTAMPTZ,
|
|
last_topic TEXT,
|
|
tags TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
UNIQUE(user_phone, expert_id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_relations_user ON expert_relations(user_phone);
|
|
CREATE INDEX IF NOT EXISTS idx_expert_relations_expert ON expert_relations(expert_id);
|
|
|
|
-- 风险推送:AI 主动提醒用户行业新规、合同风险等
|
|
CREATE TABLE IF NOT EXISTS risk_alerts (
|
|
id SERIAL PRIMARY KEY,
|
|
user_phone TEXT NOT NULL,
|
|
level TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
content TEXT NOT NULL,
|
|
source TEXT NOT NULL DEFAULT 'knowledge',
|
|
related_matter_id INTEGER,
|
|
read_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_risk_alerts_user ON risk_alerts(user_phone);
|
|
CREATE INDEX IF NOT EXISTS idx_risk_alerts_user_read ON risk_alerts(user_phone, read_at);
|
|
CREATE INDEX IF NOT EXISTS idx_risk_alerts_created ON risk_alerts(created_at DESC);
|
|
|
|
-- 每日热点问题库:每天凌晨 AI 生成,管理员可手动更新
|
|
CREATE TABLE IF NOT EXISTS daily_topics (
|
|
id SERIAL PRIMARY KEY,
|
|
date DATE NOT NULL, -- 生效日期
|
|
scene TEXT NOT NULL, -- legal/decision/life/emotion
|
|
questions JSONB NOT NULL, -- 问题数组 ["问题1","问题2",...]
|
|
source TEXT NOT NULL DEFAULT 'ai', -- ai(自动生成) / admin(管理员手动)
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- 唯一约束:同一天同一场景只有一条记录(更新时覆盖)
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_daily_topics_date_scene ON daily_topics(date, scene);
|
|
CREATE INDEX IF NOT EXISTS idx_daily_topics_date ON daily_topics(date DESC);
|
|
|
|
-- 示例问题点击埋点:记录用户点击哪些示例问题
|
|
CREATE TABLE IF NOT EXISTS sample_clicks (
|
|
id SERIAL PRIMARY KEY,
|
|
user_phone TEXT,
|
|
scene TEXT NOT NULL, -- legal/decision/life/emotion
|
|
question TEXT NOT NULL, -- 被点击的示例问题
|
|
clicked_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_sample_clicks_scene ON sample_clicks(scene);
|
|
CREATE INDEX IF NOT EXISTS idx_sample_clicks_clicked ON sample_clicks(clicked_at DESC);
|
|
"""
|
|
|
|
# 专家种子数据(密码统一为 expert123,生产环境应让专家自行注册)
|
|
SEED_EXPERTS = [
|
|
# 法务(legal)
|
|
("张明律师", "legal", "高级律师", "10年中小企业法务经验,擅长劳动纠纷、合同审查、股权纠纷", "劳动用工,合同审查,股权纠纷,债权债务"),
|
|
("李华律师", "legal", "执业律师", "专注商事诉讼,擅长公司治理、税务合规", "公司治理,税务合规,商事诉讼"),
|
|
# 经营(decision)
|
|
("王强顾问", "decision", "高级经营顾问", "15年企业管理咨询经验,擅长战略规划、组织架构、绩效管理", "战略规划,组织架构,绩效管理,扩张决策"),
|
|
("赵敏分析师", "decision", "财务分析师", "CPA持证,擅长财务诊断、融资规划、现金流管理", "财务诊断,融资规划,现金流管理"),
|
|
# 情绪(emotion)
|
|
("陈静咨询师", "emotion", "国家二级心理咨询师", "专注创业者心理健康,擅长焦虑疏导、压力管理、决策疲劳", "焦虑疏导,压力管理,决策疲劳,创业者心理"),
|
|
("刘阳教练", "emotion", "情绪教练", "10年情绪管理培训经验,擅长情绪识别、沟通改善", "情绪识别,沟通改善,团队氛围"),
|
|
# 生活(life)
|
|
("周婷顾问", "life", "生活规划师", "擅长时间管理、家庭关系、健康管理", "时间管理,家庭关系,健康管理"),
|
|
("吴昊教练", "life", "商务礼仪教练", "专注商务社交、出差规划、形象管理", "商务礼仪,出差规划,形象管理"),
|
|
]
|
|
|
|
# 管理员种子(密码 admin123)
|
|
SEED_ADMINS = [
|
|
("平台管理员", "13800000000"),
|
|
]
|
|
|
|
SEED_KNOWLEDGE = [
|
|
# 法规(law)
|
|
("劳动合同法-试用期解除", "试用期解除需证明不符合录用条件,需有明确考核标准。依据《劳动合同法》第三十九条第一项。", "law", "全国", "2013-07-01", "试用期,解除,录用条件"),
|
|
("劳动合同法-工伤不得解除", "职工因工负伤,用人单位不得依据第四十条、四十一条解除劳动合同。依据《工伤保险条例》第三十三条。", "law", "全国", "2011-01-01", "工伤,解除,保护"),
|
|
("民法典-违约金上限", "违约金过分高于造成损失的,当事人可请求法院或仲裁机构予以适当减少。一般以实际损失30%为参考。依据《民法典》第五百八十五条。", "law", "全国", "2021-01-01", "违约金,减少,实际损失"),
|
|
# 案例(case)
|
|
("劳动仲裁案例-旷工开除", "某公司以旷工3天为由开除员工,因考勤制度未经公示且未履行告知程序,被认定违法解除,赔偿2N。", "case", "上海", None, "旷工,开除,违法解除,赔偿"),
|
|
("股东出资瑕疵案例", "某股东未按期足额缴纳出资,公司及其他股东诉请补缴并赔偿违约损失,法院支持补缴并按章程追究违约责任。", "case", "全国", None, "股东,出资,违约,补缴"),
|
|
# 模板(template)
|
|
("催款函模板", "致XX公司:贵司于X年X月X日欠付货款XX元,已逾期XX天。请于收到本函X日内付清,否则将采取法律措施。", "template", "全国", None, "催款,欠款,逾期"),
|
|
("解除劳动合同通知书模板", "致XX员工:因XX原因,公司决定于X年X月X日解除与您的劳动合同。经济补偿按法律规定执行。请于X日前办理交接。", "template", "全国", None, "解除,劳动合同,通知"),
|
|
# 经营指南(guide)
|
|
("中小企业现金流管理要点", "现金流是中小企业生命线。建议:1.保持6个月运营资金储备;2.应收账款30天内催收;3.大额支出分批审批;4.每月做现金流预测。", "guide", "全国", None, "现金流,资金,应收账款,预测"),
|
|
("扩张前必须评估的5个指标", "扩张决策前评估:1.毛利率是否>30%;2.客户复购率;3.人均产出;4.现金流覆盖月数;5.团队管理带宽。任一不达标建议暂缓。", "guide", "全国", None, "扩张,评估,毛利率,现金流"),
|
|
("降价策略与利润平衡", "降价前评估:1.需求价格弹性(弹性>1降价有效);2.竞品定价区间;3.成本结构是否允许;4.是否引发价格战。降价不等于增收。", "guide", "全国", None, "降价,定价,利润,竞争"),
|
|
# 行业知识(industry)
|
|
("餐饮行业用工特点", "餐饮行业用工特点:1.员工流动率高(年均40-60%);2.兼职占比高;3.社保合规风险大;4.工伤风险高(烫伤/切伤)。建议完善考勤和安全培训。", "industry", "全国", None, "餐饮,用工,流动率,社保,工伤"),
|
|
("制造业常见法律风险", "制造业常见风险:1.环保处罚(废水废气);2.安全生产责任;3.供应链合同纠纷;4.知识产权(技术泄露);5.劳动密集型用工合规。", "industry", "全国", None, "制造,环保,安全,知识产权,用工"),
|
|
("科技企业股权激励要点", "科技企业股权激励:1.期权池建议10-15%;2.行权周期4年+1年悬崖;3.离职回购条款;4.税务规划(递延纳税);5.与业绩挂钩。", "industry", "全国", None, "科技,股权,期权,激励,税务"),
|
|
# 情绪疏导(emotion)
|
|
("创业者焦虑应对方法", '创业者焦虑常见且正常。应对:1.区分「能控制」和「不能控制」;2.每天15分钟正念呼吸;3.把大问题拆成今天能做的一步;4.找同行聊聊;5.严重时寻求专业帮助。', "emotion", "全国", None, "焦虑,创业,正念,应对"),
|
|
("决策疲劳的识别与恢复", "决策疲劳表现:拖延增多/易怒/回避选择。恢复:1.减少非必要决策(固定日程/穿搭);2.重要决策放在上午;3.充足睡眠;4.运动;5.委托授权。", "emotion", "全国", None, "决策疲劳,拖延,恢复,授权"),
|
|
("危机信号识别清单", '需立即关注的心理危机信号:1.持续失眠>2周;2.提到「没有意义」「结束一切」;3.社交完全退缩;4.食欲剧变;5.自伤念头。出现任一建议联系心理援助热线400-161-9995。', "emotion", "全国", None, "危机,失眠,自伤,援助热线"),
|
|
# 生活常识(life)
|
|
("管理者时间管理法则", "时间管理:1.用四象限法区分重要/紧急;2.每天聚焦3件重要不紧急的事;3.批量处理邮件(早晚各一次);4.会议<30分钟;5.留白时间应对突发。", "life", "全国", None, "时间管理,四象限,效率,会议"),
|
|
("商务出差打包清单", "出差必备:1.证件(身份证/护照/机票);2.充电器+移动电源;3.商务正装1套;4.常备药(胃药/感冒药);5.名片;6.合同/文件复印件;7.保温杯。", "life", "全国", None, "出差,打包,清单,商务"),
|
|
("家庭沟通技巧", "创业者的家庭沟通:1.每周固定家庭时间(不谈工作);2.重大决策提前告知家人;3.倾听>建议;4.表达感谢而非理所当然;5.冲突时先冷静24小时。", "life", "全国", None, "家庭,沟通,倾听,冲突"),
|
|
]
|
|
|
|
SEED_SETTINGS = [
|
|
("show_inference_location", "true"),
|
|
("ephemeral_disable_copy", "true"),
|
|
("auto_destroy_on_exit", "true"),
|
|
("long_term_memory", "false"),
|
|
("knowledge_base_write", "false"),
|
|
("training_data_collection", "false"),
|
|
("cloud_call_allowed", "true"),
|
|
("pii_redact_on_transfer", "true"),
|
|
("daily_reminder", "true"),
|
|
("risk_alert", "true"),
|
|
# 自动升级规则:检测到风险时是否自动创建复核工单
|
|
("auto_escalate_red", "true"),
|
|
("auto_escalate_orange", "false"),
|
|
]
|
|
|
|
# 专家分配规则:风险等级 → 专家类型 + SLA 时长 + 自动分配
|
|
SEED_EXPERT_RULES = [
|
|
("green", "法务顾问", 72, None),
|
|
("amber", "法务顾问", 48, None),
|
|
("orange", "执业律师", 24, None),
|
|
("red", "资深律师", 4, None),
|
|
]
|
|
|
|
|
|
def main():
|
|
conn = psycopg2.connect(host="localhost", port=5432, dbname="mcp", user="postgres")
|
|
conn.autocommit = True
|
|
cur = conn.cursor()
|
|
|
|
# 建表
|
|
cur.execute(SCHEMA)
|
|
|
|
# 升级已有表:补新列(IF NOT EXISTS 语义用 DO $$ 块)
|
|
cur.execute("""
|
|
DO $$
|
|
BEGIN
|
|
BEGIN ALTER TABLE review_tickets ADD COLUMN expert_type TEXT; EXCEPTION WHEN duplicate_column THEN NULL; END;
|
|
BEGIN ALTER TABLE review_tickets ADD COLUMN sla_deadline TIMESTAMPTZ; EXCEPTION WHEN duplicate_column THEN NULL; END;
|
|
BEGIN ALTER TABLE review_tickets ADD COLUMN sla_status TEXT NOT NULL DEFAULT 'normal'; EXCEPTION WHEN duplicate_column THEN NULL; END;
|
|
BEGIN ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'active'; EXCEPTION WHEN duplicate_column THEN NULL; END;
|
|
-- 2.0:chat_messages 关联案卡
|
|
BEGIN ALTER TABLE chat_messages ADD COLUMN matter_id INTEGER; EXCEPTION WHEN duplicate_column THEN NULL; END;
|
|
END $$;
|
|
""")
|
|
|
|
# 迁移 user_profiles:device_id → phone(旧表字段名变更)
|
|
cur.execute("""
|
|
DO $$
|
|
BEGIN
|
|
-- 若存在 device_id 列但不存在 phone 列,则重命名
|
|
IF EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_name = 'user_profiles' AND column_name = 'device_id'
|
|
) AND NOT EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_name = 'user_profiles' AND column_name = 'phone'
|
|
) THEN
|
|
ALTER TABLE user_profiles RENAME COLUMN device_id TO phone;
|
|
END IF;
|
|
END $$;
|
|
""")
|
|
print("schema created + upgraded")
|
|
|
|
# SLA 索引(列已确保存在后创建)
|
|
cur.execute("CREATE INDEX IF NOT EXISTS idx_review_tickets_sla ON review_tickets(sla_status)")
|
|
# 2.0:案卡关联索引
|
|
cur.execute("CREATE INDEX IF NOT EXISTS idx_chat_messages_matter ON chat_messages(matter_id)")
|
|
|
|
# 清除所有旧知识数据(测试数据已废弃,重新种子)
|
|
cur.execute("DELETE FROM knowledge_doc_versions")
|
|
cur.execute("DELETE FROM knowledge_docs")
|
|
print("cleared all old knowledge docs")
|
|
|
|
# 种子知识库(7 类分类,幂等:标题存在则跳过)
|
|
for title, content, doc_type, region, eff_date, keywords in SEED_KNOWLEDGE:
|
|
cur.execute(
|
|
"SELECT 1 FROM knowledge_docs WHERE title = %s", (title,)
|
|
)
|
|
if not cur.fetchone():
|
|
cur.execute(
|
|
"INSERT INTO knowledge_docs (title, content, source, doc_type, region, effective_date, keywords) "
|
|
"VALUES (%s, %s, 'seed', %s, %s, %s, %s)",
|
|
(title, content, doc_type, region, eff_date, keywords),
|
|
)
|
|
print(f"seeded {len(SEED_KNOWLEDGE)} knowledge docs")
|
|
|
|
# 种子设置(幂等)
|
|
for key, value in SEED_SETTINGS:
|
|
cur.execute(
|
|
"INSERT INTO user_settings (key, value) VALUES (%s, %s) "
|
|
"ON CONFLICT (key) DO NOTHING",
|
|
(key, value),
|
|
)
|
|
print(f"seeded {len(SEED_SETTINGS)} settings")
|
|
|
|
# 清理旧版本的角色权限设置项(已废弃,系统只有老板一个用户)
|
|
cur.execute(
|
|
"DELETE FROM user_settings WHERE key LIKE 'perm_%%'"
|
|
)
|
|
removed = cur.rowcount
|
|
if removed:
|
|
print(f"cleaned {removed} deprecated perm_ settings")
|
|
|
|
# 种子专家规则(幂等)
|
|
for risk_level, expert_type, sla_hours, auto_assign in SEED_EXPERT_RULES:
|
|
cur.execute(
|
|
"INSERT INTO expert_rules (risk_level, expert_type, sla_hours, auto_assign) "
|
|
"VALUES (%s, %s, %s, %s) ON CONFLICT (risk_level) DO NOTHING",
|
|
(risk_level, expert_type, sla_hours, auto_assign),
|
|
)
|
|
print(f"seeded {len(SEED_EXPERT_RULES)} expert rules")
|
|
|
|
# 种子专家(幂等:手机号存在则跳过)
|
|
import bcrypt
|
|
expert_password_hash = bcrypt.hashpw(b"expert123", bcrypt.gensalt()).decode()
|
|
for idx, (name, category, title, bio, expertise) in enumerate(SEED_EXPERTS):
|
|
# 生成唯一 11 位手机号:139 + 分类码(2位) + 序号(6位)
|
|
phone_suffix = {"legal": "10", "decision": "20", "emotion": "30", "life": "40"}
|
|
expert_phone = f"139{phone_suffix.get(category, '00')}{str(idx).zfill(6)}"
|
|
cur.execute("SELECT 1 FROM experts WHERE name = %s", (name,))
|
|
if not cur.fetchone():
|
|
cur.execute(
|
|
"INSERT INTO experts (name, phone, password, category, title, bio, expertise, status) "
|
|
"VALUES (%s, %s, %s, %s, %s, %s, %s, 'active')",
|
|
(name, expert_phone, expert_password_hash, category, title, bio, expertise),
|
|
)
|
|
print(f"seeded {len(SEED_EXPERTS)} experts")
|
|
|
|
# 种子管理员(幂等:手机号存在则跳过)
|
|
admin_password_hash = bcrypt.hashpw(b"admin123", bcrypt.gensalt()).decode()
|
|
for name, phone in SEED_ADMINS:
|
|
cur.execute("SELECT 1 FROM admins WHERE phone = %s", (phone,))
|
|
if not cur.fetchone():
|
|
cur.execute(
|
|
"INSERT INTO admins (name, phone, password) VALUES (%s, %s, %s)",
|
|
(name, phone, admin_password_hash),
|
|
)
|
|
print(f"seeded {len(SEED_ADMINS)} admins")
|
|
|
|
# 升级已有工单:补 sla_deadline(基于 risk_level 规则)
|
|
cur.execute(
|
|
"UPDATE review_tickets SET sla_deadline = created_at + interval '24 hours' "
|
|
"WHERE sla_deadline IS NULL"
|
|
)
|
|
|
|
cur.close()
|
|
conn.close()
|
|
print("db_init done")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|