先更新1版记录下
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
urbanLifeline
|
||||
Tik
|
||||
|
||||
schoolNewsServ
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
|
||||
74980
docs/1/postgres - k12study - achievement.svg
Normal file
|
After Width: | Height: | Size: 5.4 MiB |
178
docs/1/postgres - k12study - ai.graphml
Normal file
@@ -0,0 +1,178 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||
<key for="node" id="nodegraph" yfiles.type="nodegraphics"/>
|
||||
<key for="edge" id="edgegraph" yfiles.type="edgegraphics"/>
|
||||
<graph edgedefault="directed" id="G">
|
||||
<node id="entity0">
|
||||
<data key="nodegraph">
|
||||
<y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
|
||||
<y:Geometry height="370" width="421.48" x="440" y="40"/>
|
||||
<y:Fill color="#FFFFE1" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.name" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="internal" modelPosition="t" backgroundColor="#FFFFE1" textColor="#FFFFFF" visible="true" horizontalTextPosition="center" iconTextGap="4" height="22" width="357" x="0" y="4">tb_ai_graph_entity</y:NodeLabel>
|
||||
<y:NodeLabel alignment="left" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.attributes" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="custom" modelPosition="t" backgroundColor="#FFFFFF" textColor="#000000" visible="true" horizontalTextPosition="center" iconTextGap="4" height="304" width="357" x="2" y="31.66796875">entity_id: varchar(64) NOT NULL - 实体ID
|
||||
file_id: varchar(64) - 来源知识文件ID
|
||||
entity_type: varchar(64) NOT NULL - 实体类型
|
||||
entity_name: varchar(255) NOT NULL - 实体名称
|
||||
normalized_name: varchar(255) - 标准化实体名称
|
||||
graph_vertex_id: varchar(128) - 图数据库顶点ID
|
||||
entity_status: varchar(32) NOT NULL - 实体状态
|
||||
sync_status: varchar(32) NOT NULL - 同步状态
|
||||
properties_json: jsonb NOT NULL - 实体属性JSON
|
||||
adcode: varchar(12) - 行政区划编码
|
||||
tenant_id: varchar(64) - 租户ID
|
||||
tenant_path: varchar(255) - 租户路径
|
||||
dept_id: varchar(64) - 部门ID
|
||||
dept_path: varchar(255) - 部门路径
|
||||
created_at: timestamp NOT NULL - 创建时间
|
||||
updated_at: timestamp NOT NULL - 更新时间 <y:LabelModel>
|
||||
<y:ErdAttributesNodeLabelModel/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:ErdAttributesNodeLabelModelParameter/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="entity1">
|
||||
<data key="nodegraph">
|
||||
<y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
|
||||
<y:Geometry height="390" width="411.24" x="40" y="40"/>
|
||||
<y:Fill color="#FFFFE1" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.name" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="internal" modelPosition="t" backgroundColor="#FFFFE1" textColor="#FFFFFF" visible="true" horizontalTextPosition="center" iconTextGap="4" height="22" width="341" x="0" y="4">tb_ai_graph_relation</y:NodeLabel>
|
||||
<y:NodeLabel alignment="left" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.attributes" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="custom" modelPosition="t" backgroundColor="#FFFFFF" textColor="#000000" visible="true" horizontalTextPosition="center" iconTextGap="4" height="324" width="341" x="2" y="31.66796875">relation_id: varchar(64) NOT NULL - 关系ID
|
||||
src_entity_id: varchar(64) NOT NULL - 源实体ID
|
||||
dst_entity_id: varchar(64) NOT NULL - 目标实体ID
|
||||
relation_type: varchar(64) NOT NULL - 关系类型
|
||||
relation_name: varchar(128) - 关系名称
|
||||
graph_edge_id: varchar(128) - 图数据库边ID
|
||||
relation_weight: numeric(6, 4) NOT NULL - 关系权重
|
||||
relation_status: varchar(32) NOT NULL - 关系状态
|
||||
sync_status: varchar(32) NOT NULL - 同步状态
|
||||
properties_json: jsonb NOT NULL - 关系属性JSON
|
||||
adcode: varchar(12) - 行政区划编码
|
||||
tenant_id: varchar(64) - 租户ID
|
||||
tenant_path: varchar(255) - 租户路径
|
||||
dept_id: varchar(64) - 部门ID
|
||||
dept_path: varchar(255) - 部门路径
|
||||
created_at: timestamp NOT NULL - 创建时间
|
||||
updated_at: timestamp NOT NULL - 更新时间 <y:LabelModel>
|
||||
<y:ErdAttributesNodeLabelModel/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:ErdAttributesNodeLabelModelParameter/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="entity2">
|
||||
<data key="nodegraph">
|
||||
<y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
|
||||
<y:Geometry height="430" width="545.28" x="940" y="220"/>
|
||||
<y:Fill color="#FFFFE1" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.name" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="internal" modelPosition="t" backgroundColor="#FFFFE1" textColor="#FFFFFF" visible="true" horizontalTextPosition="center" iconTextGap="4" height="22" width="452" x="0" y="4">tb_ai_knowledge_file</y:NodeLabel>
|
||||
<y:NodeLabel alignment="left" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.attributes" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="custom" modelPosition="t" backgroundColor="#FFFFFF" textColor="#000000" visible="true" horizontalTextPosition="center" iconTextGap="4" height="364" width="452" x="2" y="31.66796875">file_id: varchar(64) NOT NULL - 知识文件ID
|
||||
file_code: varchar(64) NOT NULL - 知识文件编码
|
||||
file_name: varchar(255) NOT NULL - 知识文件名称
|
||||
source_file_id: varchar(64) - 来源文件ID(upms.tb_sys_file.file_id)
|
||||
file_type: varchar(32) NOT NULL - 文件类型
|
||||
file_status: varchar(32) NOT NULL - 文件状态
|
||||
graph_sync_status: varchar(32) NOT NULL - 图谱同步状态
|
||||
vector_sync_status: varchar(32) NOT NULL - 向量库同步状态
|
||||
graph_doc_ref: varchar(128) - 图谱侧文档/对象引用
|
||||
vector_doc_ref: varchar(128) - 向量库侧文档/对象引用
|
||||
content_checksum: varchar(128) - 内容校验值
|
||||
metadata_json: jsonb NOT NULL - 文件元数据JSON
|
||||
adcode: varchar(12) - 行政区划编码
|
||||
tenant_id: varchar(64) - 租户ID
|
||||
tenant_path: varchar(255) - 租户路径
|
||||
dept_id: varchar(64) - 部门ID
|
||||
dept_path: varchar(255) - 部门路径
|
||||
created_at: timestamp NOT NULL - 创建时间
|
||||
updated_at: timestamp NOT NULL - 更新时间 <y:LabelModel>
|
||||
<y:ErdAttributesNodeLabelModel/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:ErdAttributesNodeLabelModelParameter/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<node id="entity3">
|
||||
<data key="nodegraph">
|
||||
<y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
|
||||
<y:Geometry height="350" width="520.84" x="440" y="480"/>
|
||||
<y:Fill color="#FFFFE1" transparent="false"/>
|
||||
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||
<y:NodeLabel alignment="center" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.name" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="internal" modelPosition="t" backgroundColor="#FFFFE1" textColor="#FFFFFF" visible="true" horizontalTextPosition="center" iconTextGap="4" height="22" width="429" x="0" y="4">tb_ai_knowledge_sync_task</y:NodeLabel>
|
||||
<y:NodeLabel alignment="left" autoSizePolicy="content" configuration="com.yworks.entityRelationship.label.attributes" fontFamily="Courier" fontSize="12" fontStyle="plain" hasLineColor="false" modelName="custom" modelPosition="t" backgroundColor="#FFFFFF" textColor="#000000" visible="true" horizontalTextPosition="center" iconTextGap="4" height="284" width="429" x="2" y="31.66796875">task_id: varchar(64) NOT NULL - 同步任务ID
|
||||
file_id: varchar(64) NOT NULL - 知识文件ID
|
||||
target_store: varchar(16) NOT NULL - 目标存储(GRAPH/VECTOR)
|
||||
task_type: varchar(16) NOT NULL - 任务类型(UPSERT/DELETE/REBUILD)
|
||||
task_status: varchar(32) NOT NULL - 任务状态
|
||||
retry_count: int4 NOT NULL - 重试次数
|
||||
error_message: text - 错误信息
|
||||
payload_json: jsonb NOT NULL - 任务载荷JSON
|
||||
adcode: varchar(12) - 行政区划编码
|
||||
tenant_id: varchar(64) - 租户ID
|
||||
tenant_path: varchar(255) - 租户路径
|
||||
dept_id: varchar(64) - 部门ID
|
||||
dept_path: varchar(255) - 部门路径
|
||||
created_at: timestamp NOT NULL - 创建时间
|
||||
updated_at: timestamp NOT NULL - 更新时间 <y:LabelModel>
|
||||
<y:ErdAttributesNodeLabelModel/>
|
||||
</y:LabelModel>
|
||||
<y:ModelParameter>
|
||||
<y:ErdAttributesNodeLabelModelParameter/>
|
||||
</y:ModelParameter>
|
||||
</y:NodeLabel>
|
||||
</y:GenericNode>
|
||||
</data>
|
||||
</node>
|
||||
<edge id="edge0-0" source="entity0" target="entity2">
|
||||
<data key="edgegraph">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="white_diamond" target="circle"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="edge1-0" source="entity1" target="entity0">
|
||||
<data key="edgegraph">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="white_diamond" target="circle"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="edge1-1" source="entity1" target="entity0">
|
||||
<data key="edgegraph">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="white_diamond" target="circle"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
<edge id="edge3-0" source="entity3" target="entity2">
|
||||
<data key="edgegraph">
|
||||
<y:PolyLineEdge>
|
||||
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||
<y:Arrows source="white_diamond" target="circle"/>
|
||||
<y:BendStyle smoothed="false"/>
|
||||
</y:PolyLineEdge>
|
||||
</data>
|
||||
</edge>
|
||||
</graph>
|
||||
</graphml>
|
||||
12945
docs/1/postgres - k12study - auth.svg
Normal file
|
After Width: | Height: | Size: 978 KiB |
90910
docs/1/postgres - k12study - course.svg
Normal file
|
After Width: | Height: | Size: 6.6 MiB |
128021
docs/1/postgres - k12study - question.svg
Normal file
|
After Width: | Height: | Size: 9.3 MiB |
79021
docs/1/postgres - k12study - recommendation.svg
Normal file
|
After Width: | Height: | Size: 5.7 MiB |
122
docs/AI智能学习系统功能清单.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# AI智能学习系统功能清单(结构化版)
|
||||
|
||||
## 一、学生端(微信小程序)
|
||||
|
||||
| 序号 | 模块 | 功能点 | 详细描述 |
|
||||
|---|---|---|---|
|
||||
| 1 | 账号与登录 | 多方式登录 | 微信一键登录/手机号+验证码登录;年级/班级信息绑定;登录态校验与提示 |
|
||||
| 2 | 资料与作业上传 | 机构作业接收 | 查看教师发布的班级作业/学习资料;按学科、年级分类展示 |
|
||||
| 3 | 资料与作业上传 | 多形式上传 | 拍照上传(裁剪/旋转/增强);文件上传(PDF/Word/图片);手动录入答题 |
|
||||
| 4 | 资料与作业上传 | 双模式批改 | 绑定机构作业批改;自主任意作业批改 |
|
||||
| 5 | 作业处理 | 批改结果查看 | 客观题+主观题批改结果展示;生成知识点/薄弱项/学习趋势报告 |
|
||||
| 6 | 作业处理 | 作业评分与等级展示 | 展示作业得分、超越同学比例、答题用时、质量评级(A/B/C) |
|
||||
| 7 | 错题本 | 错题归集管理 | 机构作业/自主练习错题自动收录;按学科/知识点/错因分类;筛选/搜索/收藏 |
|
||||
| 8 | 错题本 | 错题关联学习 | 点击错题自动关联知识点讲解课程 |
|
||||
| 9 | 错题本 | 错题状态管理 | 错题按待学习/复习中/已掌握状态分类;支持按错因类型筛选 |
|
||||
| 10 | 错题本 | 错题复习次数统计 | 记录每道错题复习次数,展示复习轨迹 |
|
||||
| 11 | 错因分析 | 错因可视化 | 个人错因分布饼图/柱状图 |
|
||||
| 12 | 错因分析 | 细维度错因拆解 | 展示审题不清、计算失误、概念模糊等具体错因及优化建议 |
|
||||
| 13 | 举一反三训练 | AI变式题生成 | 基于单题错题生成同类变式题;梯度难度练习 |
|
||||
| 14 | 举一反三训练 | 在线答题批改 | 提交后自动批改;实时错因提示 |
|
||||
| 15 | 费曼学习法 | 错题重做 | 变式题后进入错题重做;错误时自动关联知识库课程 |
|
||||
| 16 | 费曼学习法 | 语音讲解 | 1-3分钟讲解录制;预览/重录/提交;提供讲解规范 |
|
||||
| 17 | 费曼学习法 | 讲解评估 | 知识/逻辑/表达/迁移多维度评分;通过/未通过+改进建议 |
|
||||
| 18 | 复习计划 | 艾宾浩斯提醒 | 复习日历+节点提醒;个性化复习任务推送 |
|
||||
| 19 | 复习计划 | 复习内容匹配 | 自动关联复习要点课程 |
|
||||
| 20 | 个性化推荐 | 学习内容推荐 | 首页推送薄弱知识点微课/专项训练/同类错题 |
|
||||
| 21 | 学习激励 | 成长轨迹展示 | 知识点掌握度曲线/错因减少趋势;学习勋章/打卡/时长统计 |
|
||||
| 22 | 学习激励 | 成长指标量化 | 展示努力指数、进步率、学习韧性等量化指标 |
|
||||
| 23 | 学习激励 | 连续打卡统计 | 记录连续学习天数,展示打卡 streak |
|
||||
| 24 | 消息通知 | 学习提醒 | 作业/复习/批改/推荐内容提醒;消息列表已读/删除 |
|
||||
## 二、教师端(Web)
|
||||
| 序号 | 模块 | 功能点 | 详细描述 |
|
||||
|---|---|---|---|
|
||||
| 1 | 账号鉴权 | 教师登录校验 | 账号密码/验证码登录;生成 Token;登录日志记录 |
|
||||
| 2 | 班级与学员管理 | 班级管理 | 班级创建/编辑/解散;批量导入/导出学员 |
|
||||
| 3 | 班级与学员管理 | 学员管理 | 学员信息增删改;班级绑定/解绑;状态管理 |
|
||||
| 4 | 作业与资料发布 | 资料发布 | 上传课件/习题/文档;按班级推送 |
|
||||
| 5 | 作业管理 | 作业批改 | AI自动批改+教师复核;批改结果存储/查询/导出 |
|
||||
| 6 | 学情分析 | 学员档案 | 聚合作业/错题/讲解/复习数据;生成个人学习档案 |
|
||||
| 7 | 学情分析 | 班级学情 | 成绩/知识点/错因统计;进度排名/热力图 |
|
||||
| 8 | 学情分析 | 班级能力层级可视化 | 展示班级知识点掌握度、认知能力层级分布 |
|
||||
| 9 | 教学干预 | 讲解点评 | 查看学生语音讲解;文字/语音点评与评分 |
|
||||
| 10 | 教学干预 | 教学建议 | AI生成教学建议;周/月报表生成导出 |
|
||||
## 三、机构端(Web)
|
||||
| 序号 | 模块 | 功能点 | 详细描述 |
|
||||
|---|---|---|---|
|
||||
| 1 | 管理员鉴权 | 高权限登录 | 管理员账号校验;生成高权限 Token;操作日志审计 |
|
||||
| 2 | 用户管理 | 教师管理 | 账号创建/编辑/禁用;批量导入/导出 |
|
||||
| 3 | 用户管理 | 学员管理 | 全机构学员统一管理;跨班级/跨校区调配 |
|
||||
| 4 | 用户管理 | 权限配置 | 校长/教务/教师角色权限自定义;分级管控 |
|
||||
| 5 | 数据可视化 | 全机构数据 | 校区/年级/学科汇总;运营仪表盘展示 |
|
||||
| 6 | 标准化管理 | 教学规范 | 批改/错因/复习计划标准统一 |
|
||||
| 7 | 标准化管理 | 知识库规范 | 分类/标签/内容格式统一 |
|
||||
| 8 | 质量监控 | 教学监控 | 教师/学员数据异常预警 |
|
||||
| 9 | 质量监控 | 内容监控 | 课程合规性审核;违规预警处理 |
|
||||
| 10 | 质量监控 | 质量报表 | 教学质量/学习效果报表导出 |
|
||||
| 11 | 决策支持 | 数据分析 | 趋势分析/瓶颈识别 |
|
||||
## 四、公共支撑层:知识库
|
||||
| 序号 | 模块 | 功能点 | 详细描述 |
|
||||
|---|---|---|---|
|
||||
| 1 | 课程基础管理 | 课程存储 | 图文/PDF/视频多格式存储;学科→年级→知识点分类 |
|
||||
| 2 | 课程基础管理 | 课程标签 | 自动/手动打标签;全机构标签统一 |
|
||||
| 3 | 内容检索 | 智能匹配 | 错题→知识点课程关联;关键词/错因检索 |
|
||||
| 4 | 内容生成 | 题目生成 | 基础/变式/拓展题生成;按难度/题量定制 |
|
||||
## 五、公共支撑层:个性化推荐算法
|
||||
| 序号 | 模块 | 功能点 | 详细描述 |
|
||||
|---|---|---|---|
|
||||
| 1 | 学员画像 | 多维度画像 | 基础:年级/学科/班级;学习:错题/错因/通过率;行为:学习偏好/习惯 |
|
||||
| 2 | 推荐策略 | 内容推荐 | 错题→知识点+变式题;错因→专项训练;遗忘节点→复习内容 |
|
||||
| 3 | 效果优化 | 算法优化 | 收集学习反馈;动态调整权重;统计推荐效果 |
|
||||
## 六、与 `init` 表结构 + `docs/architecture` 架构流转对齐分析
|
||||
### 6.1 模块级覆盖评估
|
||||
| 功能域 | 架构流转对应 | 当前数据落点(init/pg) | 覆盖结论 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| 组织与鉴权 | `modules/00-组织与权限上下文/*`,系统架构「认证与用户中心」 | `upms.tb_sys_*`、`auth.tb_auth_*` | 已覆盖 | 租户/组织/RBAC/登录审计链路完整 |
|
||||
| 课程学习与知识点 | `modules/01-课程学习/*`,系统架构「课程与内容服务」 | `course.cl_*` | 已覆盖 | 课程-章节-课时-知识点-学习会话/进度已落表 |
|
||||
| 习题与作业 | `modules/02-习题与作业/*`,多角色流转 P2 | `homework.hw_*` + `upms.tb_sys_file` | 已覆盖 | 出题、组卷、发布、提交、附件引用链路完整 |
|
||||
| 批改与反馈 | `modules/03-批改与反馈/*`,多角色流转 P3/P7 | `grading.gd_*` | 部分覆盖(有实现差异) | 核心能力已落地,但与架构 ER 命名/拆表不完全一致 |
|
||||
| 错题本与复习计划 | 多角色流转 P4 | `grading.gd_wrong_question`、`grading.gd_review_plan` | 已覆盖 | 错题沉淀、复习计划、复习次数有落点 |
|
||||
| 站内信提醒 | 多角色流转 D7,03 模块站内信链路 | `upms.tb_sys_message`、`upms.tb_sys_message_recipient` | 已覆盖 | 支持 `content_object_id` + `web_jump_url` 跳转链路 |
|
||||
| 学习激励/成就 | 系统架构「学习激励」能力 | `achievement.ac_*` | 已扩展 | 超出原架构图细粒度,具备成就定义/发放/进度 |
|
||||
| AI知识库与检索 | 系统架构「Python AI处理层」「知识库/题库」 | `ai.tb_ai_knowledge_graph`、`ai.tb_ai_vector_*`、`ai.tb_ai_retrieval_log` | 已扩展 | 图谱+向量检索能力具备,支持 RAG 基础设施 |
|
||||
| 个性化推荐 | 多角色流转 P5,系统架构「画像与推荐策略」 | 仅间接依赖 `grading/course/ai`,缺独立推荐域表 | 部分覆盖 | 缺推荐结果、曝光点击、反馈回流等闭环数据 |
|
||||
| 教学运营监控 | 系统架构「质量监控/运营分析」 | 主要依赖 upms 消息与业务表;缺独立质控事实表 | 部分覆盖 | 可查询但难做稳定报表与规则预警闭环 |
|
||||
### 6.2 当前缺失项(功能清单有诉求,但表结构/流转未完全落地)
|
||||
| 缺失能力 | 当前现状 | 建议补充 |
|
||||
|---|---|---|
|
||||
| 班级实体与班级成员关系 | 目前主要依赖 `dept`/`target_ref_id` 间接表达 | 新增班级主表、班级成员表、班级-课程关联表 |
|
||||
| 费曼语音讲解全链路 | 仅有教师点评和文件能力,缺“讲解提交/评估”专表 | 新增讲解记录、讲解评估、讲解维度评分明细表 |
|
||||
| AI变式题生成可追踪 | 无“变式题生成任务/来源错题/生成结果”结构 | 新增变式题任务表、变式题结果表、题源映射表 |
|
||||
| 推荐反馈回流闭环 | 缺推荐结果曝光/点击/完成反馈事实表 | 新增推荐任务、推荐结果、用户反馈与效果统计表 |
|
||||
| 学习激励行为明细 | 有成就域,但缺打卡 streak/努力指数原始行为表 | 新增学习打卡流水、学习时长明细、成长指标快照表 |
|
||||
| 机构质量监控与内容合规 | 清单有诉求,缺独立事件与处置记录 | 新增质量监控事件表、内容审核表、处置流程表 |
|
||||
| 周/月报表可追溯快照 | 现可临时聚合,缺报表固化快照 | 新增学情报表快照表、报表任务执行日志表 |
|
||||
### 6.3 需修正项(`docs/architecture` 与 `init` 不一致)
|
||||
| 修正点 | 架构图当前表达 | `init` 实际实现 | 建议 |
|
||||
|---|---|---|---|
|
||||
| 批改结果拆表方式 | 03 ER 使用 `gd_objective_score` + `gd_subjective_review` | 采用统一表 `grading.gd_answer_grade`(含 `grade_mode`/`grade_status`) | 更新 ER 图为统一批改结果模型,减少实现偏差 |
|
||||
| 批改规则集实体 | 03 ER 有 `gd_grading_rule_set` | 当前无对应表,策略主要在 `hw_paper.grading_policy_json` 与服务层 | 二选一:补规则集表,或在 ER 中改为“策略JSON+策略版本” |
|
||||
| 教师点评关联键 | 03 ER 中 `gd_teacher_comment` 关联 `review_id` | 实际关联 `answer_grade_id` | 统一为 `answer_grade_id`,避免主观/客观分支分叉 |
|
||||
| 作答附件模型 | 02 ER 体现 `hw_submission_attachment` 独立表 | 实际为 `hw_submission_answer.file_id/file_type` + `upms.tb_sys_file` | ER 图改为“答案行内引用文件”模型 |
|
||||
| 题目版本模型 | 02 ER 含 `hw_question_version` | 当前为 `hw_question_item` 单表(含 `answer_payload`/`scoring_rule_json`) | 若需版本回溯,补版本表;否则 ER 去掉版本实体 |
|
||||
### 6.4 可扩展项(建议)
|
||||
| 扩展方向 | 建议能力 | 目标收益 |
|
||||
|---|---|---|
|
||||
| 推荐域独立化 | 新建 `recommendation` schema(策略、召回、排序、结果、反馈) | 打通“推荐-点击-学习效果”闭环优化 |
|
||||
| 画像域标准化 | 新增学员画像快照与特征版本表 | 支撑可解释推荐与分层教学 |
|
||||
| AI任务细分 | 在 `ai.tb_ai_task_log` 之上增加 OCR/ASR/讲解评测明细 | 提升 AI 调用可观测性与成本分析能力 |
|
||||
| 教学运营报表化 | 固化班级/机构日报周报月报事实快照 | 降低临时聚合成本,提高口径一致性 |
|
||||
| 复习策略强化 | 复习计划增加策略来源与命中规则 | 支撑艾宾浩斯与个性化策略并行迭代 |
|
||||
### 6.5 可融合项(跨域打通)
|
||||
| 融合目标 | 融合对象 | 融合方式 |
|
||||
|---|---|---|
|
||||
| 统一学员画像 | `course.cl_learning_*` + `homework.hw_submission*` + `grading.gd_*` + `achievement.ac_*` | 形成统一画像特征层,服务推荐/学情/激励 |
|
||||
| 错题-推荐-复习闭环 | `gd_wrong_question` + `gd_review_plan` + 推荐结果域 | 用复习结果反哺推荐权重,形成闭环 |
|
||||
| 主观题待审阅闭环 | `gd_answer_grade` + `upms.tb_sys_message*` + 教师复核页面 | 统一消息跳转、回执、处理状态追踪 |
|
||||
| 知识点统一索引 | `course.cl_knowledge_point` + `homework.hw_question_kp_rel` + `ai.tb_ai_kg_*` | 建立知识点主索引,统一课程/题目/图谱引用 |
|
||||
| 内容资产统一管理 | `upms.tb_sys_file` + `course.cl_course_resource` + 讲解音频能力 | 统一文件引用规范与生命周期管理 |
|
||||
## 七、优先级建议(落地顺序)
|
||||
1. **P0(先修正)**:先统一 `docs/architecture` 与 `init` 的 ER 表达差异(批改模型、附件模型、题目版本)。
|
||||
2. **P1(补缺口)**:优先补齐“班级实体”“推荐闭环”“语音讲解评估”三条主链路。
|
||||
3. **P2(做融合)**:建立统一学员画像层,打通错题-复习-推荐-激励数据闭环。
|
||||
26
docs/architecture.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# K12Study 项目架构文档(建设中)
|
||||
|
||||
## 1. 当前已确认技术选型
|
||||
|
||||
- 图数据库:NebulaGraph(主选),Neo4j Community(备选)
|
||||
- 向量库:Milvus
|
||||
- 关系库:PostgreSQL(存储原始业务表)
|
||||
|
||||
## 2. 分层职责(先行约定)
|
||||
|
||||
- PostgreSQL:业务主数据与原始表数据(唯一事实源)
|
||||
- NebulaGraph / Neo4j:知识点、题目、学生作答、知识关系等图结构查询
|
||||
- Milvus:教材/题解/知识片段向量检索与语义召回
|
||||
|
||||
## 3. 三库协同原则(首版)
|
||||
|
||||
- 关系库到图库:通过同步任务进行实体与关系映射
|
||||
- 关系库到向量库:通过切片与 embedding 任务进行索引
|
||||
- 图谱节点保留源表标识(如 `source_table`、`source_pk`),支持点击节点回查原始表数据
|
||||
|
||||
## 4. 架构图产物(待补充)
|
||||
|
||||
- 系统流程图:建设中
|
||||
- ER 图:建设中
|
||||
- 数据流图:建设中
|
||||
|
||||
@@ -1,74 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-04-14T10:41:00.000Z" agent="Oz" version="24.7.17">
|
||||
<diagram id="ctx-er" name="ER图">
|
||||
<mxGraphModel dx="1800" dy="1200" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="ctx-er-v2" name="ER图">
|
||||
<mxGraphModel dx="1312" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="00 组织与权限上下文 - ER图" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="450" height="30" as="geometry"/>
|
||||
<mxCell id="2" value="00 组织与权限上下文 - ER图(含班级模型)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="620" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="统一隔离键:adcode / tenant_id / tenant_path / dept_id / dept_path" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="720" height="24" as="geometry"/>
|
||||
<mxCell id="3" value="全局技术栈对齐:业务主数据落 PostgreSQL;AI知识检索能力由 Milvus + NebulaGraph/Neo4j 承担;同步调度统一采用 REST/JAR(本模块仅维护组织权限)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="1400" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="tb_sys_tenant<br>PK tenant_id<br>FK parent_tenant_id -> tb_sys_tenant.tenant_id<br>tenant_name, tenant_type, status<br>adcode, tenant_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="120" width="280" height="140" as="geometry"/>
|
||||
<mxCell id="10" value="upms.tb_sys_tenant<br>PK tenant_id<br>parent_tenant_id, tenant_name, tenant_type<br>adcode, tenant_path, status" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="90" width="280" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="tb_sys_dept<br>PK dept_id<br>FK parent_dept_id -> tb_sys_dept.dept_id<br>FK tenant_id -> tb_sys_tenant.tenant_id<br>dept_name, dept_type<br>adcode, tenant_path, dept_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="120" width="300" height="150" as="geometry"/>
|
||||
<mxCell id="11" value="upms.tb_sys_dept<br>PK dept_id<br>FK tenant_id -> tb_sys_tenant.tenant_id<br>parent_dept_id, dept_name, dept_type<br>adcode, tenant_path, dept_path" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="90" width="320" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="tb_sys_user<br>PK user_id, UK username<br>FK tenant_id -> tb_sys_tenant.tenant_id<br>FK dept_id -> tb_sys_dept.dept_id<br>display_name, password_hash, status<br>adcode, tenant_path, dept_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="700" y="120" width="320" height="150" as="geometry"/>
|
||||
<mxCell id="12" value="upms.tb_sys_user<br>PK user_id, UK username<br>FK tenant_id -> tb_sys_tenant.tenant_id<br>FK dept_id -> tb_sys_dept.dept_id<br>display_name, status, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="730" y="90" width="340" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="tb_sys_role<br>PK role_id, UK role_code<br>FK tenant_id -> tb_sys_tenant.tenant_id<br>role_name<br>dept_id, dept_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="700" y="320" width="290" height="130" as="geometry"/>
|
||||
<mxCell id="13" value="upms.tb_sys_role<br>PK role_id, UK role_code<br>tenant_id, role_name, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
|
||||
<mxGeometry x="1110" y="90" width="280" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" value="tb_sys_permission<br>PK permission_id, UK permission_code<br>FK tenant_id -> tb_sys_tenant.tenant_id<br>permission_name<br>dept_id, dept_path, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1030" y="320" width="320" height="130" as="geometry"/>
|
||||
<mxCell id="14" value="upms.tb_sys_menu<br>PK route_id<br>parent_route_id, route_path, component_key<br>permission_code, hidden, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
|
||||
<mxGeometry x="1430" y="90" width="320" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="rel_user_role<br>PK (user_id, role_id)<br>FK user_id -> tb_sys_user.user_id<br>FK role_id -> tb_sys_role.role_id<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1030" y="120" width="290" height="130" as="geometry"/>
|
||||
<mxCell id="15" value="upms.tb_sys_role_menu<br>PK (role_id, route_id)<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="1430" y="260" width="300" height="100" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="16" value="rel_role_permission<br>PK (role_id, permission_id)<br>FK role_id -> tb_sys_role.role_id<br>FK permission_id -> tb_sys_permission.permission_id<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1360" y="120" width="320" height="130" as="geometry"/>
|
||||
<mxCell id="16" value="auth.tb_auth_refresh_token<br>PK token_id<br>FK user_id -> tb_sys_user.user_id<br>refresh_token, expire_at, revoked" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="290" width="320" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" value="tb_auth_refresh_token<br>PK token_id<br>FK user_id -> tb_sys_user.user_id<br>refresh_token, expire_at, revoked<br>tenant_id, dept_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="330" width="300" height="130" as="geometry"/>
|
||||
<mxCell id="17" value="auth.tb_auth_login_audit<br>PK audit_id<br>FK user_id -> tb_sys_user.user_id<br>username, login_ip, login_status" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="400" y="290" width="320" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" value="tb_auth_login_audit<br>PK audit_id<br>FK user_id -> tb_sys_user.user_id<br>username, login_ip, login_status<br>tenant_id, dept_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="360" y="330" width="300" height="130" as="geometry"/>
|
||||
<mxCell id="18" value="upms.tb_school_class<br>PK class_id<br>FK dept_id -> tb_sys_dept.dept_id<br>class_name, grade_code, status<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="760" y="290" width="340" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="设计方法与原因<br>1) DDD上下文先行:权限域作为业务域前置依赖<br>2) RBAC最小闭环:用户-角色-权限拆分降低耦合<br>3) 多租户统一隔离键:保证后续业务表可水平扩展<br>4) 认证审计分离:令牌高频写与审计查询分流" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1360" y="320" width="320" height="170" as="geometry"/>
|
||||
<mxCell id="19" value="upms.tb_school_class_member<br>PK (class_id, user_id)<br>FK class_id -> tb_school_class.class_id<br>FK user_id -> tb_sys_user.user_id<br>member_role, member_status, joined_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="1140" y="450" width="350" height="150" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="11" target="10" edge="1">
|
||||
<mxCell id="20" value="upms.tb_school_class_course_rel<br>PK (class_id, course_id)<br>FK class_id -> tb_school_class.class_id<br>relation_status, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="1530" y="390" width="320" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=1;exitY=0.35;entryX=0;entryY=0.35;" parent="1" source="10" target="11" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="101" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="10" edge="1">
|
||||
<mxCell id="31" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=1;exitY=0;entryX=0;entryY=0;" parent="1" source="10" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="700" y="70"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="32" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=0.6;exitY=1;entryX=0.4;entryY=0;" parent="1" source="13" target="15" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="102" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" parent="1" source="12" target="11" edge="1">
|
||||
<mxCell id="33" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=0.4;exitY=1;entryX=0.6;entryY=0;" parent="1" source="14" target="15" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="103" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#9673a6;" parent="1" source="13" target="10" edge="1">
|
||||
<mxCell id="34" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=0;exitY=0.7;entryX=1;entryY=0.35;" parent="1" source="12" target="16" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="104" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="15" target="12" edge="1">
|
||||
<mxCell id="35" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=0;exitY=0.85;entryX=1;entryY=0.45;" parent="1" source="12" target="17" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="105" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="15" target="13" edge="1">
|
||||
<mxCell id="36" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=1;exitY=0.8;entryX=0;entryY=0.4;" parent="1" source="11" target="18" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="106" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="16" target="13" edge="1">
|
||||
<mxCell id="37" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=1;exitY=0.6;entryX=0;entryY=0.4;" parent="1" source="18" target="19" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="107" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" parent="1" source="16" target="14" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="108" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="17" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="109" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" parent="1" source="18" target="12" edge="1">
|
||||
<mxCell id="38" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;exitX=1;exitY=0.85;entryX=0;entryY=0.2;" parent="1" source="18" target="20" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<mxCell id="2" value="00 组织与权限上下文 - 对象类数据流图" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="520" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="本模块边界说明:仅承载组织/权限与认证;AI 图谱与向量同步任务由 ai 模块统一调度(REST/JAR),目标存储为 Milvus + NebulaGraph/Neo4j" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="1200" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="Actor<br>学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="120" width="120" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
@@ -38,7 +41,7 @@
|
||||
<mxCell id="41" value="Entity<br>TenantDeptAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="820" y="210" width="200" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="42" value="Entity<br>RolePermissionAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxCell id="42" value="Entity<br>RoleMenuAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="820" y="310" width="200" height="70" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="43" value="Entity<br>AuthTokenStore" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||
@@ -50,7 +53,7 @@
|
||||
<mxCell id="51" value="Redis(Session/Token)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="1090" y="320" width="220" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="60" value="设计方法与原因<br>1) BCE分层明确交互职责<br>2) 鉴权与查询控制器分离,便于独立扩展<br>3) 权限判定集中到Policy服务,降低重复逻辑<br>4) Token热数据落Redis,用户组织主数据落PG" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxCell id="60" value="设计方法与原因<br>1) BCE分层明确交互职责<br>2) 鉴权与查询控制器分离,便于独立扩展<br>3) 菜单授权判定集中到Policy服务,降低重复逻辑<br>4) Token热数据落Redis,用户组织主数据落PG<br>5) AI同步调度配置通过权限体系统一管控" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1360" y="190" width="360" height="180" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="登录/路由请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
@@ -61,7 +64,7 @@
|
||||
<mxCell id="105" value="权限判定" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="31" target="32" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="106" value="用户读取" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="30" target="40" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="107" value="组织读取" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="31" target="41" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="108" value="角色权限读取" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="42" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="108" value="角色菜单读取" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="42" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="109" value="Token签发/撤销" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="30" target="43" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="110" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="40" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="111" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="41" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
|
||||
@@ -1,63 +1,111 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-04-14T10:42:00.000Z" agent="Oz" version="24.7.17">
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="course-er" name="ER图">
|
||||
<mxGraphModel dx="1800" dy="1200" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||
<mxGraphModel dx="1312" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="01 课程学习 - ER图" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxCell id="2" value="01 课程学习 - ER图(含知识点掌握与图谱/向量同步关联)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="360" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="课程主数据 + 学习过程数据分层;所有业务主表均携带 tenant_id/adcode/dept_path" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="860" height="24" as="geometry"/>
|
||||
<mxCell id="3" value="课程主数据 + 学习过程数据分层;业务主库为 PostgreSQL,知识检索能力由 Milvus + NebulaGraph/Neo4j 承担" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="1100" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="cl_course<br>PK course_id<br>title, subject_code, grade_code<br>difficulty_level, status<br>tenant_id, adcode, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="120" width="250" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="cl_course_chapter<br>PK chapter_id<br>FK course_id -> cl_course.course_id<br>chapter_no, chapter_title<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="330" y="120" width="250" height="130" as="geometry"/>
|
||||
<mxGeometry x="50" y="130" width="250" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="cl_course_lesson<br>PK lesson_id<br>FK chapter_id -> cl_course_chapter.chapter_id<br>lesson_no, lesson_title, duration_sec<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="620" y="120" width="270" height="130" as="geometry"/>
|
||||
<mxGeometry x="650" y="300" width="270" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="cl_knowledge_point<br>PK kp_id<br>kp_code, kp_name, subject_code, grade_code<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
|
||||
<mxGeometry x="930" y="120" width="260" height="120" as="geometry"/>
|
||||
<mxGeometry x="1030" y="10" width="260" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" value="cl_course_knowledge_rel<br>PK (course_id, kp_id)<br>FK course_id -> cl_course.course_id<br>FK kp_id -> cl_knowledge_point.kp_id<br>weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="1230" y="120" width="280" height="130" as="geometry"/>
|
||||
<mxGeometry x="1680" y="190" width="280" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="cl_course_resource<br>PK resource_id<br>FK lesson_id -> cl_course_lesson.lesson_id<br>resource_type(pdf/video/doc), resource_url<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="620" y="290" width="290" height="130" as="geometry"/>
|
||||
<mxGeometry x="640" y="560" width="290" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="16" value="cl_course_tag<br>PK tag_id<br>tag_name, tag_type<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="300" width="230" height="110" as="geometry"/>
|
||||
<mxGeometry x="50" y="420" width="230" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" value="cl_course_tag_rel<br>PK (course_id, tag_id)<br>FK course_id -> cl_course.course_id<br>FK tag_id -> cl_course_tag.tag_id<br>tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="300" y="300" width="270" height="120" as="geometry"/>
|
||||
<mxGeometry x="330" y="410" width="270" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" value="cl_learning_session<br>PK session_id<br>FK user_id -> tb_sys_user.user_id<br>FK course_id -> cl_course.course_id<br>status(STARTED/PAUSED/COMPLETED)<br>started_at, ended_at, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="930" y="290" width="300" height="140" as="geometry"/>
|
||||
<mxGeometry x="950" y="450" width="300" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="cl_learning_progress<br>PK progress_id<br>FK session_id -> cl_learning_session.session_id<br>FK lesson_id -> cl_course_lesson.lesson_id<br>progress_pct, last_position_sec<br>mastery_level, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="1260" y="300" width="320" height="140" as="geometry"/>
|
||||
<mxGeometry x="1880" y="295" width="320" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="cl_learning_event<br>PK event_id<br>FK session_id -> cl_learning_session.session_id<br>event_type(start/pause/seek/finish)<br>event_time, payload_json, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="930" y="460" width="300" height="130" as="geometry"/>
|
||||
<mxGeometry x="950" y="750" width="300" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="设计方法与原因<br>1) 聚合根:Course / LearningSession<br>2) 状态机:Session状态驱动进度写入<br>3) 标签与知识点解耦,支持多维检索<br>4) 事件明细单表追加,兼顾审计与回放" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="470" width="840" height="120" as="geometry"/>
|
||||
<mxCell id="21" value="设计方法与原因<br>1) 聚合根:Course / LearningSession<br>2) 状态机:Session状态驱动进度写入<br>3) 标签与知识点解耦,支持多维检索<br>4) 事件明细单表追加,兼顾审计与回放<br>5) 知识点/学习画像变化可通过 ai.tb_ai_knowledge_sync_task 异步同步" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="10" y="850" width="840" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" value="外部同步依赖(ai)<br>ai.tb_ai_knowledge_file<br>ai.tb_ai_knowledge_sync_task<br>ai.tb_ai_graph_entity / ai.tb_ai_graph_relation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
|
||||
<mxGeometry x="1320" y="760" width="330" height="150" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" value="cl_kp_prerequisite_rel<br>PK (kp_id, pre_kp_id)<br>FK kp_id -> cl_knowledge_point.kp_id<br>FK pre_kp_id -> cl_knowledge_point.kp_id<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="1320" y="150" width="340" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" value="cl_kp_material_rel<br>PK (kp_id, resource_id)<br>FK kp_id -> cl_knowledge_point.kp_id<br>FK resource_id -> cl_course_resource.resource_id<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="1350" y="565" width="360" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="cl_student_kp_mastery<br>PK mastery_id<br>FK kp_id -> cl_knowledge_point.kp_id<br>student_id, mastery_level, mastery_score<br>last_practiced_at, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="1320" y="420" width="360" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="11" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="101" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="102" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="12" target="15" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="103" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="14" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="104" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="14" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="105" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="17" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="106" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="16" target="17" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="107" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="18" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="108" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="18" target="19" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="109" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="12" target="19" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="110" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="18" target="20" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="112" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="23" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="113" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="24" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="114" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="15" target="24" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="115" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="25" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="116" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="19" target="25" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="cl_course_chapter<br>PK chapter_id<br>FK course_id -> cl_course.course_id<br>chapter_no, chapter_title<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="340" y="240" width="250" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="101" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="102" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="15" target="12" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="103" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="14" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="104" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="14" target="13" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="105" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="17" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="106" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="17" target="16" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="107" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="18" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="108" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="109" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="12" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="110" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="20" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<mxCell id="2" value="01 课程学习 - 对象类数据流图" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="460" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="知识点/学习进度相关事件可入 ai 同步任务表,由调度器以 REST/JAR 模式驱动向 Milvus + NebulaGraph/Neo4j 同步" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="1300" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="Actor<br>学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="40" y="130" width="120" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="11" value="Actor<br>教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="240" width="120" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="12" value="Actor<br>机构管理员" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="40" y="350" width="120" height="70" as="geometry"/></mxCell>
|
||||
@@ -20,9 +23,12 @@
|
||||
<mxCell id="41" value="Entity<br>KnowledgeIndexAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="800" y="210" width="220" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="42" value="Entity<br>LearningSessionAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="800" y="310" width="230" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="43" value="Entity<br>LearningProgressAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="800" y="410" width="240" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="44" value="Entity<br>AiSyncTaskAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="800" y="510" width="240" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="50" value="PostgreSQL(course/learning)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="170" width="240" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="51" value="Redis(学习进度热点缓存)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="320" width="240" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="60" value="设计方法与原因<br>1) CQRS:课程目录读取与学习进度写入分离<br>2) 会话聚合驱动事件化进度更新<br>3) 热点进度走Redis,减轻主库读压<br>4) 课程实体与知识点索引分离,支持后续多策略检索" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxCell id="52" value="PostgreSQL(ai sync task/config)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="470" width="260" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="53" value="Milvus + NebulaGraph/Neo4j" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="620" width="260" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="60" value="设计方法与原因<br>1) CQRS:课程目录读取与学习进度写入分离<br>2) 会话聚合驱动事件化进度更新<br>3) 热点进度走Redis,减轻主库读压<br>4) 课程实体与知识点索引分离,支持后续多策略检索<br>5) 同步调度支持 JAR 本地执行与 REST 远程触发" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1360" y="170" width="360" height="190" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="浏览课程/课时" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
@@ -40,6 +46,9 @@
|
||||
<mxCell id="112" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="42" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="113" value="持久化/缓存" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="43" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="114" value="热点缓存" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="43" target="51" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="115" value="同步任务入队" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=1;" parent="1" source="32" target="44" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="116" value="落库" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="44" target="52" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="117" value="REST/JAR调度执行" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=1;" parent="1" source="44" target="53" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
||||
@@ -1,38 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-04-14T10:43:00.000Z" agent="Oz" version="24.7.17">
|
||||
<diagram id="homework-er" name="ER图">
|
||||
<mxfile host="app.diagrams.net" modified="2026-04-15T07:43:00.000Z" agent="Oz" version="24.7.17">
|
||||
<diagram id="homework-er-v2" name="ER图">
|
||||
<mxGraphModel dx="1800" dy="1200" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="02 习题与作业 - ER图" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="400" height="30" as="geometry"/>
|
||||
<mxCell id="2" value="02 习题与作业 - ER图(question单schema:10_create + 20_init)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="520" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="hw_question_bank<br>PK bank_id<br>bank_name, subject_code, grade_code<br>status, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="110" width="240" height="120" as="geometry"/></mxCell>
|
||||
<mxCell id="11" value="hw_question_item<br>PK question_id<br>FK bank_id -> hw_question_bank.bank_id<br>question_type, stem, difficulty<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="310" y="110" width="270" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="12" value="hw_question_version<br>PK version_id<br>FK question_id -> hw_question_item.question_id<br>version_no, answer_key, analysis<br>is_current, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="610" y="110" width="280" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="13" value="hw_question_kp_rel<br>PK (question_id, kp_id)<br>FK question_id -> hw_question_item.question_id<br>FK kp_id -> cl_knowledge_point.kp_id<br>weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="920" y="110" width="280" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="14" value="hw_paper<br>PK paper_id<br>paper_name, subject_code, total_score<br>tenant_id, created_by, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="40" y="290" width="240" height="120" as="geometry"/></mxCell>
|
||||
<mxCell id="15" value="hw_paper_question<br>PK (paper_id, question_id)<br>FK paper_id -> hw_paper.paper_id<br>FK question_id -> hw_question_item.question_id<br>question_order, score, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="310" y="290" width="300" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="16" value="hw_assignment<br>PK assignment_id<br>FK paper_id -> hw_paper.paper_id<br>title, publish_time, deadline<br>status(DRAFT/PUBLISHED/CLOSED)<br>tenant_id, created_by" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="640" y="290" width="300" height="150" as="geometry"/></mxCell>
|
||||
<mxCell id="17" value="hw_assignment_target<br>PK target_id<br>FK assignment_id -> hw_assignment.assignment_id<br>target_type(class/student)<br>target_ref_id, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="970" y="290" width="280" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="18" value="hw_submission<br>PK submission_id<br>FK assignment_id -> hw_assignment.assignment_id<br>FK student_id -> tb_sys_user.user_id<br>submit_time, used_seconds<br>status(SUBMITTED/RESUBMITTED)<br>tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="1280" y="290" width="330" height="150" as="geometry"/></mxCell>
|
||||
<mxCell id="19" value="hw_submission_answer<br>PK answer_id<br>FK submission_id -> hw_submission.submission_id<br>FK question_id -> hw_question_item.question_id<br>answer_content, answer_type, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="1280" y="470" width="330" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="20" value="hw_submission_attachment<br>PK attachment_id<br>FK submission_id -> hw_submission.submission_id<br>file_type, object_key, file_hash<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="920" y="470" width="330" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="21" value="设计方法与原因<br>1) 题目版本化:保障历史作业可追溯<br>2) 试卷与作业分离:一份试卷可多次发布<br>3) 发布对象独立表:支持按班级/学员精细投放<br>4) 提交主表+答案明细:便于后续批改并行处理" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="470" width="840" height="130" as="geometry"/>
|
||||
<mxCell id="3" value="说明:question 目录仅保留 2 个 SQL 文件(10_create_question_tables.sql / 20_init_question_seed.sql)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="1100" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="101" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="102" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="13" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="103" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="15" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="104" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="15" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="105" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="16" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="106" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="17" target="16" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="107" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="18" target="16" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="108" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="109" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="110" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="20" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="10" value="question.hw_question_bank<br>PK bank_id<br>bank_name, subject_code, grade_code, status<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="90" width="270" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="question.hw_question_item<br>PK question_id<br>FK bank_id -> hw_question_bank.bank_id<br>question_type, stem, stem_json, difficulty<br>answer_payload, analysis, scoring_rule_json<br>question_status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="350" y="80" width="380" height="170" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="question.hw_question_kp_rel<br>PK (question_id, kp_id)<br>FK question_id -> hw_question_item.question_id<br>FK kp_id -> cl_knowledge_point.kp_id<br>relation_type, confidence, graph_relation_id, source_table/source_pk<br>tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="780" y="90" width="320" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="外部同步依赖(ai)<br>ai.tb_ai_knowledge_file<br>ai.tb_ai_knowledge_sync_task<br>ai.tb_ai_graph_entity / ai.tb_ai_graph_relation<br>Target: Milvus + NebulaGraph/Neo4j" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="890" width="370" height="150" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="question.hw_paper<br>PK paper_id<br>paper_name, subject_code, total_score<br>grading_policy_json<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="300" width="290" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" value="question.hw_paper_question<br>PK (paper_id, question_id)<br>question_order, score, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="380" y="320" width="310" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="question.hw_assignment<br>PK assignment_id<br>FK paper_id -> hw_paper.paper_id<br>title, publish_time, deadline, status<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="740" y="300" width="340" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="16" value="question.hw_assignment_target<br>PK target_id<br>FK assignment_id -> hw_assignment.assignment_id<br>target_type, target_ref_id, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="1130" y="300" width="320" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" value="question.hw_submission<br>PK submission_id<br>FK assignment_id -> hw_assignment.assignment_id<br>FK student_id -> tb_sys_user.user_id<br>submit_time, used_seconds, status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="740" y="500" width="360" height="150" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" value="question.hw_submission_answer<br>PK answer_id<br>FK submission_id -> hw_submission.submission_id<br>FK question_id -> hw_question_item.question_id<br>answer_type, answer_payload<br>file_id, file_type, tenant_id<br>UK (submission_id, question_id)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="1140" y="500" width="390" height="170" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="upms.tb_sys_file<br>PK file_id<br>media_type, object_key, file_hash<br>uploaded_by, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="1140" y="730" width="320" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="31" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="12" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="32" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="33" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="34" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="15" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="35" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="15" target="16" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="36" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="15" target="17" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="37" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="17" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="38" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="19" target="18" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
||||
@@ -5,24 +5,30 @@
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="02 习题与作业 - 对象类数据流图" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxCell id="2" value="02 习题与作业 - 对象类数据流图(question单schema)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="500" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="question 仅 10/20 两个 SQL;提交成功后除 MQ 事件外,同步写入 ai 任务表,通过 REST/JAR 调度同步到 Milvus + NebulaGraph/Neo4j" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="1400" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="Actor<br>教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="150" width="120" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="11" value="Actor<br>学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="40" y="300" width="120" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="20" value="Boundary<br>TeacherHomeworkBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="130" width="220" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="21" value="Boundary<br>StudentHomeworkBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="280" width="220" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="20" value="Boundary<br>TeacherQuestionBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="130" width="220" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="21" value="Boundary<br>StudentQuestionBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="280" width="220" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="30" value="Control<br>QuestionBankService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="90" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="31" value="Control<br>AssignmentPublishService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="200" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="32" value="Control<br>HomeworkQueryService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="310" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="32" value="Control<br>QuestionQueryService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="310" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="33" value="Control<br>SubmissionService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="420" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="40" value="Entity<br>QuestionBankAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="90" width="220" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="41" value="Entity<br>AssignmentAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="200" width="220" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="42" value="Entity<br>AssignmentTargetAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="300" width="240" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="43" value="Entity<br>SubmissionAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="410" width="220" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="50" value="PostgreSQL(question/homework)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="170" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="44" value="Entity<br>AiSyncTaskAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="810" y="520" width="240" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="50" value="PostgreSQL(question)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="170" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="51" value="ObjectStorage(附件/图片/PDF)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="320" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="52" value="MQ(SubmissionCreatedEvent)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="450" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="53" value="PostgreSQL(ai sync task/config)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="580" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="54" value="Milvus + NebulaGraph/Neo4j" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="710" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="60" value="设计方法与原因<br>1) 发布链路与作答链路拆分,降低接口复杂度<br>2) 提交成功后发事件,解耦后续批改流程<br>3) 附件文件与答案结构分存,便于扩展多题型<br>4) 题库聚合与作业聚合分离,支持独立演进" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1380" y="200" width="330" height="190" as="geometry"/>
|
||||
</mxCell>
|
||||
@@ -41,6 +47,9 @@
|
||||
<mxCell id="112" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="43" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="113" value="附件存储" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="43" target="51" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="114" value="异步事件" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="43" target="52" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="115" value="同步任务入队" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=1;" parent="1" source="33" target="44" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="116" value="落库" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="44" target="53" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="117" value="REST/JAR调度执行" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=1;" parent="1" source="44" target="54" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
||||
@@ -1,34 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-04-14T10:44:00.000Z" agent="Oz" version="24.7.17">
|
||||
<diagram id="grading-er" name="ER图">
|
||||
<mxGraphModel dx="1800" dy="1200" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="grading-er-v2" name="ER图">
|
||||
<mxGraphModel dx="1312" dy="773" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="03 批改与反馈 - ER图(不含AI实现细节)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxCell id="2" value="03 批改与反馈 - ER图(question单schema,含知识点分析与AI同步)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="560" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="gd_grading_task<br>PK grading_task_id<br>FK submission_id -> hw_submission.submission_id<br>status(PENDING/RUNNING/WAIT_REVIEW/DONE)<br>trigger_source, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="120" width="330" height="140" as="geometry"/></mxCell>
|
||||
<mxCell id="11" value="gd_grading_rule_set<br>PK rule_set_id<br>rule_version, subject_code, objective_policy<br>subjective_policy(manual_first)<br>tenant_id, enabled" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="400" y="120" width="320" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="12" value="gd_objective_score<br>PK objective_score_id<br>FK grading_task_id -> gd_grading_task.grading_task_id<br>FK answer_id -> hw_submission_answer.answer_id<br>score, matched_rule, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="750" y="120" width="340" height="140" as="geometry"/></mxCell>
|
||||
<mxCell id="13" value="gd_subjective_review<br>PK review_id<br>FK grading_task_id -> gd_grading_task.grading_task_id<br>FK answer_id -> hw_submission_answer.answer_id<br>reviewer_id, review_score, status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="1120" y="120" width="350" height="140" as="geometry"/></mxCell>
|
||||
<mxCell id="14" value="gd_score_summary<br>PK summary_id<br>FK grading_task_id -> gd_grading_task.grading_task_id<br>total_score, grade_level(A/B/C)<br>surpass_ratio, used_seconds, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="40" y="300" width="330" height="140" as="geometry"/></mxCell>
|
||||
<mxCell id="15" value="gd_error_tag<br>PK error_tag_id<br>tag_code, tag_name<br>category(审题/计算/概念)<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="400" y="300" width="300" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="16" value="gd_answer_error_rel<br>PK (answer_id, error_tag_id)<br>FK answer_id -> hw_submission_answer.answer_id<br>FK error_tag_id -> gd_error_tag.error_tag_id<br>confidence, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="730" y="300" width="330" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="17" value="gd_wrong_question<br>PK wrong_question_id<br>FK student_id -> tb_sys_user.user_id<br>FK question_id -> hw_question_item.question_id<br>source_submission_id, mastery_status<br>review_count, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="1090" y="300" width="350" height="150" as="geometry"/></mxCell>
|
||||
<mxCell id="18" value="gd_review_plan<br>PK review_plan_id<br>FK wrong_question_id -> gd_wrong_question.wrong_question_id<br>plan_date, plan_stage(E1/E2/E3)<br>status, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="40" y="490" width="330" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="19" value="gd_teacher_comment<br>PK comment_id<br>FK review_id -> gd_subjective_review.review_id<br>comment_type(text/voice), content_ref<br>reviewer_id, tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1"><mxGeometry x="400" y="490" width="340" height="130" as="geometry"/></mxCell>
|
||||
<mxCell id="20" value="设计方法与原因<br>1) 批改任务聚合承接状态机,保证流程一致性<br>2) 客观分与主观复核拆表,支持并行与补录<br>3) 错因标签与错题沉淀解耦,便于策略演进<br>4) 仅保留 GradingEnginePort 抽象,不绑定AI实现" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="780" y="500" width="660" height="130" as="geometry"/>
|
||||
<mxCell id="3" value="说明:批改与反馈表统一在 question/10_create_question_tables.sql 中定义" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="900" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="question.gd_grading_task<br>PK grading_task_id<br>FK submission_id -> hw_submission.submission_id<br>paper_id, task_status, trigger_source<br>tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="25" y="110" width="330" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="question.gd_answer_grade<br>PK answer_grade_id<br>FK grading_task_id -> gd_grading_task.grading_task_id<br>FK answer_id -> hw_submission_answer.answer_id<br>grade_mode, grade_status, score, grader_id<br>evidence_json, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="90" width="360" height="150" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="question.gd_score_summary<br>PK summary_id<br>UK grading_task_id -> gd_grading_task.grading_task_id<br>total_score, grade_level, surpass_ratio, used_seconds<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
|
||||
<mxGeometry x="830" y="90" width="340" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="question.gd_error_tag<br>PK error_tag_id<br>tag_code, tag_name, category<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="300" width="300" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="14" value="question.gd_answer_error_rel<br>PK (answer_id, error_tag_id)<br>FK answer_id -> hw_submission_answer.answer_id<br>FK error_tag_id -> gd_error_tag.error_tag_id<br>confidence, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="390" y="300" width="350" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="15" value="question.gd_wrong_question<br>PK wrong_question_id<br>student_id, question_id, source_submission_id<br>mastery_status, review_count<br>tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="790" y="290" width="350" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="16" value="question.gd_review_plan<br>PK review_plan_id<br>FK wrong_question_id -> gd_wrong_question.wrong_question_id<br>plan_date, plan_stage, plan_status<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1">
|
||||
<mxGeometry x="1190" y="290" width="340" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="17" value="question.gd_teacher_comment<br>PK comment_id<br>FK answer_grade_id -> gd_answer_grade.answer_grade_id<br>reviewer_id, comment_type, content_ref<br>tenant_id, created_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||
<mxGeometry x="420" y="480" width="350" height="130" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="18" value="question.gd_explanation_submission<br>PK explanation_id<br>student_id, wrong_question_id, source_answer_id<br>audio_file_id, transcript_text, submission_status<br>tenant_id, created_at, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="500" width="340" height="150" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="19" value="question.gd_explanation_assessment<br>PK assessment_id<br>UK explanation_id -> gd_explanation_submission.explanation_id<br>evaluator_type, total_score, pass_status<br>improvement_suggestion, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="830" y="500" width="360" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="question.gd_explanation_dimension_score<br>PK dimension_score_id<br>FK assessment_id -> gd_explanation_assessment.assessment_id<br>dimension_code, dimension_name<br>score, weight, tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="1240" y="500" width="340" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" value="question.gd_answer_kp_analysis<br>PK analysis_id<br>FK answer_id -> hw_submission_answer.answer_id<br>FK kp_id -> cl_knowledge_point.kp_id<br>correctness, mastery_score, confidence<br>evidence_json, tenant_id, updated_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||
<mxGeometry x="1240" y="90" width="360" height="160" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="upms.tb_sys_message<br>PK message_id<br>message_type, biz_type, content_object_id<br>web_jump_url, tenant_id, send_at" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="40" y="710" width="330" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" value="upms.tb_sys_message_recipient<br>PK (message_id, recipient_user_id)<br>FK message_id -> tb_sys_message.message_id<br>read_status, read_at, clicked_at<br>tenant_id" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1">
|
||||
<mxGeometry x="430" y="710" width="360" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" value="外部同步依赖(ai)<br>ai.tb_ai_knowledge_file<br>ai.tb_ai_knowledge_sync_task<br>ai.tb_ai_graph_entity / ai.tb_ai_graph_relation<br>Target: Milvus + NebulaGraph/Neo4j" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" parent="1" vertex="1">
|
||||
<mxGeometry x="820" y="710" width="360" height="150" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="11" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="31" value="1:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="10" target="12" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="32" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="13" target="14" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="33" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="12" target="15" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="34" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="15" target="16" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="35" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="17" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="36" value="1:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="18" target="19" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="37" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="19" target="20" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="38" value="触发通知" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="21" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="39" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="21" target="22" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="40" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;exitPerimeter=1;entryPerimeter=1;" parent="1" source="11" target="23" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="N:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="11" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="101" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="12" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="102" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="13" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="103" value="1:1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="14" target="10" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="104" value="M:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="16" target="15" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="105" value="衍生" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="17" target="14" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="106" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="18" target="17" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="107" value="1:N" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="19" target="13" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
||||
@@ -5,27 +5,36 @@
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="03 批改与反馈 - 对象类数据流图(规则批改 + 教师复核)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxCell id="2" value="03 批改与反馈 - 对象类数据流图(question单schema)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=22;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="20" width="700" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="批改结果会触发知识点分析同步任务;调度器支持 REST/JAR 两种模式(JAR 本地执行 + REST 远程触发),目标为 Milvus + NebulaGraph/Neo4j" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=12;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="20" y="52" width="1400" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="Actor<br>学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" parent="1" vertex="1"><mxGeometry x="40" y="150" width="120" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="11" value="Actor<br>教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1"><mxGeometry x="40" y="300" width="120" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="20" value="Boundary<br>StudentResultBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="140" width="220" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="21" value="Boundary<br>TeacherReviewBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="290" width="220" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="22" value="Boundary<br>MessageInboxBoundary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="230" y="640" width="220" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="30" value="Control<br>GradingDispatcher" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="80" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="31" value="Control<br>ObjectiveScoringService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="190" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="32" value="Control<br>ReviewWorkflowService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="300" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="33" value="Control<br>WrongBookService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="410" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="34" value="Control<br>ReviewPlannerService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="520" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="36" value="Control<br>SiteMessageService" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" parent="1" vertex="1"><mxGeometry x="520" y="640" width="220" height="80" as="geometry"/></mxCell>
|
||||
<mxCell id="35" value="Port<br>GradingEnginePort<br>(当前规则实现 RuleBasedEngine)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="80" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="40" value="Entity<br>GradingTaskAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="210" width="230" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="41" value="Entity<br>ObjectiveScoreAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="310" width="230" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="42" value="Entity<br>SubjectiveReviewAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="410" width="250" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="43" value="Entity<br>WrongQuestionAggregate" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="510" width="240" height="70" as="geometry"/></mxCell>
|
||||
<mxCell id="50" value="PostgreSQL(grading/wrongbook)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="230" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="44" value="Entity<br>SiteMessageAggregate<br>message_id, biz_type<br>content_object_id, web_jump_url" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;" parent="1" vertex="1"><mxGeometry x="780" y="640" width="270" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="50" value="PostgreSQL(question + ai sync task)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="230" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="51" value="Redis(结果缓存/待复核队列)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="370" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="52" value="MQ(GradingCompletedEvent)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="510" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="60" value="设计方法与原因<br>1) 批改调度、判分、复核、错题沉淀分控制器,职责清晰<br>2) 通过 Port 抽象引擎实现,后续可无缝接入AI或外部服务<br>3) 结果完成后事件化通知,支撑复习计划与消息触达<br>4) 主流程先保障规则可用,再逐步增强智能能力" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxCell id="53" value="PostgreSQL(upms.site_message / recipient)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="650" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="54" value="Redis(未读计数缓存)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="770" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="55" value="Milvus + NebulaGraph/Neo4j" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;" parent="1" vertex="1"><mxGeometry x="1080" y="890" width="250" height="90" as="geometry"/></mxCell>
|
||||
<mxCell id="60" value="设计方法与原因<br>1) 批改调度、判分、复核、错题沉淀分控制器,职责清晰<br>2) 通过 Port 抽象引擎实现,后续可无缝接入AI或外部服务<br>3) 主观题待审阅由站内信服务落库(含content_object_id和web_jump_url)<br>4) 教师通过收件箱点击跳转到对应审阅页,链路可追踪可回执" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1380" y="230" width="330" height="220" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="查看批改结果" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="10" target="20" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
@@ -46,6 +55,16 @@
|
||||
<mxCell id="115" value="持久化" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="43" target="50" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="116" value="待复核缓存" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="42" target="51" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="117" value="完成事件" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="34" target="52" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="118" value="查看站内信" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="11" target="22" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="119" value="待审阅通知触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="36" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="120" value="复习提醒通知触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="34" target="36" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="121" value="生成站内信" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="36" target="44" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="122" value="消息落库" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="44" target="53" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="123" value="未读计数缓存" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="44" target="54" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="124" value="已读/点击回执" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="22" target="36" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="125" value="点击消息跳转审阅页" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="22" target="21" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="126" value="返回跳转参数(messageId/objectId/url)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;endArrow=block;endFill=1;" parent="1" source="36" target="22" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="127" value="同步任务执行" style="edgeStyle=orthogonalEdgeStyle;rounded=0;jettySize=auto;html=1;dashed=1;endArrow=block;endFill=1;" parent="1" source="34" target="55" edge="1"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
||||
259
docs/architecture/完整业务流程图.drawio
Normal file
@@ -0,0 +1,259 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="full-business-flow-v3" name="完整业务流程图">
|
||||
<mxGraphModel dx="2300" dy="1500" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="5600" pageHeight="2300" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="AI智能学习系统完整业务流程图(教学业务主线 + 学习闭环 + 管理审计)
版本:模块上色 + 数据实体标注(schema.table)+ 图谱/向量同步链路(Milvus + NebulaGraph/Neo4j)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=20;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="20" width="1500" height="46" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="90" value="模块色卡(与 SQL schema 对齐)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="2550" y="20" width="300" height="34" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="91" value="course" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=11;" parent="1" vertex="1">
|
||||
<mxGeometry x="2550" y="62" width="92" height="28" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="92" value="homework" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=11;" parent="1" vertex="1">
|
||||
<mxGeometry x="2650" y="62" width="92" height="28" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="93" value="grading" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=11;" parent="1" vertex="1">
|
||||
<mxGeometry x="2750" y="62" width="92" height="28" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="94" value="ai" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=11;" parent="1" vertex="1">
|
||||
<mxGeometry x="2850" y="62" width="92" height="28" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="95" value="recommendation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=11;" parent="1" vertex="1">
|
||||
<mxGeometry x="2950" y="62" width="120" height="28" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="96" value="achievement" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=11;" parent="1" vertex="1">
|
||||
<mxGeometry x="3080" y="62" width="110" height="28" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="97" value="upms/运营" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=11;" parent="1" vertex="1">
|
||||
<mxGeometry x="3200" y="62" width="110" height="28" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="教师主线" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=13;fontStyle=1;align=left;spacingLeft=10;" vertex="1" parent="1">
|
||||
<mxGeometry x="30" y="110" width="5400" height="220" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="学生主线" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=13;fontStyle=1;align=left;spacingLeft=10;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="360" width="5400" height="260" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="AI/系统主线" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=13;fontStyle=1;align=left;spacingLeft=10;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="660" width="5400" height="620" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="13" value="管理运营主线" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=13;fontStyle=1;align=left;spacingLeft=10;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="1320" width="5400" height="300" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="开始" style="ellipse;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="70" y="470" width="110" height="64" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" value="教师创建课程
course.cl_course" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="200" y="170" width="220" height="76" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="31" value="课程绑定知识点
course.cl_course_knowledge_rel
course.cl_knowledge_point" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="450" y="160" width="250" height="96" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="32" value="组卷生成 Paper
question.hw_paper
question.hw_paper_question" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="730" y="160" width="250" height="96" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="33" value="发布作业到班级
question.hw_assignment
question.hw_assignment_target" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1010" y="160" width="250" height="96" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="40" value="学生接收作业
upms.tb_school_class_member
question.hw_assignment_target" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1180" y="440" width="250" height="92" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="41" value="学生答题并提交(文本/图片/音频)
question.hw_submission
question.hw_submission_answer / upms.tb_sys_file" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1460" y="430" width="320" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="50" value="创建批改任务
question.gd_grading_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="1780" y="760" width="240" height="76" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="51" value="AI-OCR 解析
ai.tb_ai_knowledge_file / ai.tb_ai_knowledge_sync_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="2050" y="760" width="210" height="76" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="52" value="AI-LLM 批改与错因标签
question.gd_answer_grade
question.gd_answer_error_rel / question.gd_error_tag
ai.tb_ai_knowledge_sync_task + ai.tb_ai_graph_entity + ai.tb_ai_graph_relation" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="2290" y="744" width="330" height="108" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="53" value="主观题待复核?
question.gd_answer_grade.grade_mode" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="2660" y="736" width="220" height="122" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="54" value="教师复核/点评
question.gd_teacher_comment" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="2860" y="170" width="220" height="76" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="55" value="成绩汇总(总分/等级)
question.gd_score_summary" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="2920" y="760" width="240" height="76" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="56" value="错题沉淀入错题本
question.gd_wrong_question" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3190" y="760" width="240" height="76" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="57" value="错题状态流转(待学习/复习中/已掌握)
question.gd_wrong_question
question.gd_review_plan + ai.tb_ai_knowledge_sync_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3460" y="744" width="280" height="108" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="60" value="AI生成举一反三变式题
ai.tb_ai_knowledge_sync_task
recommendation.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3190" y="430" width="280" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="61" value="学生变式题练习
question.hw_submission
question.hw_submission_answer" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3500" y="440" width="250" height="92" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="62" value="是否掌握?
question.gd_wrong_question.mastery_status" style="rhombus;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3780" y="428" width="200" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="63" value="生成艾宾浩斯复习计划
question.gd_review_plan" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="4010" y="432" width="240" height="92" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="64" value="到点提醒与复习反馈
upms.tb_sys_message
recommendation.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="4250" y="900" width="260" height="92" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="65" value="推荐召回与排序
recommendation.rc_recommendation_task
recommendation.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="4250" y="1000" width="260" height="92" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="66" value="推送微课/专项训练/同类错题
recommendation.rc_recommendation_item" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="4250" y="1110" width="260" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="67" value="学生反馈(曝光/点击/完成)
recommendation.rc_recommendation_feedback" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="4250" y="1210" width="260" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="68" value="费曼讲解任务触发
recommendation.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="2920" y="430" width="240" height="92" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="69" value="学生语音讲解提交
question.gd_explanation_submission
upms.tb_sys_file" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="2920" y="540" width="240" height="92" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="70" value="AI语音评测(ASR/表达评分/建议)
question.gd_explanation_assessment
question.gd_explanation_dimension_score
ai.tb_ai_knowledge_sync_task" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3190" y="540" width="300" height="110" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="71" value="画像与闭环引擎
recommendation.rc_student_profile_snapshot
recommendation.rc_student_profile_feature
recommendation.rc_learning_loop_case / rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3400" y="980" width="330" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="72" value="规则模板/事件字典/阶段映射
achievement.ac_achievement_event_dict
achievement.ac_achievement_metric_def
achievement.ac_achievement_rule_template
recommendation.rc_loop_stage_achievement_map / rc_loop_metric_map" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=11;" parent="1" vertex="1">
|
||||
<mxGeometry x="3400" y="1120" width="330" height="140" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="73" value="成就计算与发放(激励数据=成就数据)
achievement.ac_user_achievement
achievement.ac_user_achievement_progress" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3770" y="980" width="310" height="104" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="74" value="成长轨迹/指标更新
achievement.ac_user_achievement_daily_fact
recommendation.rc_learning_loop_effect_daily" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3770" y="1100" width="310" height="104" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="80" value="机构学情与运营看板
question.gd_score_summary
recommendation.rc_recommendation_effect_daily
achievement.ac_user_achievement_daily_fact" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="2860" y="1390" width="300" height="116" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="81" value="质量监控与异常预警
question.gd_grading_task
recommendation.rc_learning_loop_effect_daily" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3200" y="1400" width="300" height="100" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="82" value="审计追溯与报表导出
ai.tb_ai_knowledge_file / ai.tb_ai_knowledge_sync_task
upms.tb_sys_message
recommendation.rc_learning_loop_event" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3540" y="1390" width="320" height="116" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="83" value="结束
(进入下一轮学习循环)" style="ellipse;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;fontSize=12;" parent="1" vertex="1">
|
||||
<mxGeometry x="3890" y="1400" width="280" height="96" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="20" target="30" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="101" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="30" target="31" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="102" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="31" target="32" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="103" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="32" target="33" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="104" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="33" target="40" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="105" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="40" target="41" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="106" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="41" target="50" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="107" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="50" target="51" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="108" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="51" target="52" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="109" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="52" target="53" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="110" value="是" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="53" target="54" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="111" value="否" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="53" target="55" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="112" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="54" target="55" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="113" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="55" target="56" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="114" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="56" target="57" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="115" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="56" target="60" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="116" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="60" target="61" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="117" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="61" target="62" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="118" value="否,继续练" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;dashed=1;" parent="1" source="62" target="60" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="119" value="是" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="62" target="63" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="120" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="63" target="64" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="121" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="64" target="65" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="122" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="65" target="66" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="123" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="66" target="67" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="124" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="57" target="68" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="125" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="68" target="69" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="126" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="69" target="70" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="127" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="67" target="71" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="128" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="70" target="71" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="129" value="配置读取" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="72" target="71" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="130" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="71" target="73" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="131" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="73" target="74" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="132" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="74" target="80" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="133" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="80" target="81" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="134" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="81" target="82" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="135" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;" parent="1" source="82" target="83" edge="1">
|
||||
<mxGeometry relative="1" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1,157 +1,129 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<mxfile host="app.diagrams.net" modified="2026-04-14T09:05:00.000Z" agent="Codex GPT-5" version="24.7.17">
|
||||
<diagram id="multi-role-flow" name="数据流图(多角色)">
|
||||
<mxGraphModel dx="1800" dy="1200" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1800" pageHeight="1200" math="0" shadow="0">
|
||||
<mxfile host="app.diagrams.net" modified="2026-04-15T07:55:00.000Z" agent="Oz" version="24.7.17">
|
||||
<diagram id="multi-role-flow-ai" name="数据流图(多角色)">
|
||||
<mxGraphModel dx="1800" dy="1200" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="2000" pageHeight="1300" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="2" value="多角色数据流图" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="30" y="20" width="220" height="30" as="geometry" />
|
||||
<mxCell id="2" value="多角色数据流图(AI逻辑完整版)" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="入口:微信小程序 / React 后台,处理链路:Java 分布式服务 -> Python AI 服务 -> Redis / PostgreSQL" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" vertex="1" parent="1">
|
||||
<mxGeometry x="30" y="52" width="860" height="22" as="geometry" />
|
||||
<mxCell id="3" value="入口:微信小程序/React 后台;主链路:Java服务编排 -> Python AI子服务(OCR/LLM/ASR) -> PostgreSQL/Redis/对象存储/Milvus/NebulaGraph(Neo4j);同步调度支持 REST/JAR" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" vertex="1" parent="1">
|
||||
<mxGeometry x="30" y="52" width="1250" height="24" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="10" value="学生" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="70" y="140" width="160" height="70" as="geometry" />
|
||||
<mxGeometry x="60" y="120" width="150" height="65" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="11" value="教师" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="70" y="350" width="160" height="70" as="geometry" />
|
||||
<mxGeometry x="60" y="250" width="150" height="65" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="12" value="机构管理员" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontSize=15;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="70" y="560" width="160" height="70" as="geometry" />
|
||||
<mxGeometry x="60" y="380" width="150" height="65" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="20" value="P1 接入与身份认证<br>微信小程序 / React -> Java Gateway" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="80" width="290" height="80" as="geometry" />
|
||||
|
||||
<mxCell id="20" value="P1 接入与鉴权
Gateway + Auth + RBAC" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="90" width="260" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="21" value="P2 作业 / 资料管理<br>Java 作业服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="205" width="290" height="80" as="geometry" />
|
||||
<mxCell id="21" value="P2 作业/资料管理
布置、上传、提交、附件引用" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="200" width="260" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="22" value="P3 AI批改与错因分析<br>Java 编排服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="330" width="290" height="80" as="geometry" />
|
||||
<mxCell id="22" value="P3 批改编排服务
触发AI子流程、汇总得分/错因" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="310" width="260" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="23" value="P4 错题本 / 复习计划<br>Java 复习服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="455" width="290" height="80" as="geometry" />
|
||||
<mxCell id="23" value="P4 错题本与复习计划
错题沉淀、复习节点评估" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="420" width="260" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="24" value="P5 推荐与消息推送<br>Java 推荐服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="340" y="580" width="290" height="80" as="geometry" />
|
||||
<mxCell id="24" value="P5 推荐与消息触达
推荐任务、反馈回流、站内信" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="530" width="260" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="25" value="P6 Python AI处理服务<br>OCR / 批改 / 语音评测" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="250" width="300" height="80" as="geometry" />
|
||||
<mxCell id="25" value="P6 讲解评估与教学干预
语音讲解评估、教师点评、学情回传" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="640" width="260" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="26" value="P7 讲解评估 / 教学干预 / 运营监控<br>Java 教学与运营服务" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="470" width="330" height="90" as="geometry" />
|
||||
<mxCell id="26" value="P7 画像与学习闭环引擎
画像快照、阶段事件、成就触发映射" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="280" y="750" width="260" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="30" value="D1 PostgreSQL 用户 / 班级库<br>支持分库分表 / 分区" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1180" y="70" width="260" height="75" as="geometry" />
|
||||
|
||||
<mxCell id="40" value="AI-1 OCR解析服务
图片增强 / 版面解析 / 文本结构化" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="610" y="220" width="300" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="31" value="D2 PostgreSQL 作业 / 批改库<br>支持分库分表 / 分区" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1180" y="190" width="260" height="75" as="geometry" />
|
||||
<mxCell id="41" value="AI-2 LLM批改服务
客观判分 / 主观评估 / 错因标签" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="610" y="320" width="300" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="32" value="D3 PostgreSQL 错题 / 复习库<br>支持分区归档" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1180" y="310" width="260" height="75" as="geometry" />
|
||||
<mxCell id="42" value="AI-3 讲解评测服务
ASR转写 / 表达评分 / 改进建议" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="610" y="640" width="300" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="33" value="D4 知识库 / 题库 / 对象存储" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1180" y="430" width="260" height="75" as="geometry" />
|
||||
<mxCell id="43" value="AI-4 推荐策略服务
召回 / 排序 / 反馈学习" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="610" y="540" width="300" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="34" value="D5 Redis Cluster<br>会话、缓存、热数据、推荐结果" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1180" y="550" width="260" height="75" as="geometry" />
|
||||
<mxCell id="44" value="AI-5 模型与任务管理
model_config / task_log / retrieval_log
sync_scheduler_config / sync_dispatch_log" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="610" y="760" width="300" height="78" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="35" value="D6 日志 / 审计 / BI" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" vertex="1" parent="1">
|
||||
<mxGeometry x="1180" y="670" width="260" height="75" as="geometry" />
|
||||
|
||||
<mxCell id="60" value="D1 PostgreSQL 用户/组织/班级库
upms/auth" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="90" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="40" value="登录 / 绑定" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="10" target="20">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="61" value="D2 PostgreSQL question题目/作业库
question.hw_*" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="200" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="41" value="登录 / 班级关系" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="11" target="20">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="62" value="D3 PostgreSQL question批改/错题/讲解库
question.gd_*" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="310" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="42" value="高权限登录" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="12" target="20">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="63" value="D4 PostgreSQL 推荐/画像/闭环库
recommendation" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="420" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="43" value="用户 / 班级 / 权限" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="20" target="30">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="64" value="D5 PostgreSQL 成就库
achievement(激励=成就)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="530" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="44" value="会话 / Token" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="20" target="34">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="65" value="D6 Redis缓存层
会话/推荐缓存/未读计数" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="640" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="45" value="发布作业 / 资料" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="11" target="21">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="66" value="D7 对象存储
文件/课件/音频/附件" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="750" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="46" value="上传作业 / 答题" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="10" target="21">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="67" value="D8 Milvus + NebulaGraph/Neo4j
向量检索 / 知识图谱 / 同步任务结果" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="860" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="47" value="作业 / 资料 / 提交" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="21" target="31">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="48" value="文件 / 课件" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="21" target="33">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="49" value="批改请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="21" target="22">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="50" value="调用 Python AI" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="22" target="25">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="51" value="解析结果 / 错因 / 评分" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="25" target="22">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="52" value="知识点 / 题库匹配" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="25" target="33">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="53" value="批改结果" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="22" target="31">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="54" value="错题 / 薄弱项" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="22" target="23">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="55" value="批改结果 / 错因" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="22" target="10">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="56" value="复核任务 / 学情" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="22" target="11">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="57" value="错题 / 复习节点" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="23" target="32">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="58" value="画像 / 复习信号" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="23" target="24">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="59" value="错题本 / 复习计划" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="23" target="10">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="60" value="微课 / 变式题召回" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="24" target="33">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="61" value="缓存推荐结果" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="24" target="34">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="62" value="推荐内容 / 提醒" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="24" target="10">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="63" value="语音讲解提交" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="10" target="26">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="64" value="点评 / 评分 / 学情查询" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="11" target="26">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="65" value="机构监控 / 审核" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="12" target="26">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="66" value="讲解评估调用" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="26" target="25">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="67" value="讲解记录 / 教学数据" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="26" target="31">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="68" value="报表 / 审计" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="26" target="35">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="69" value="班级学情 / 教学建议" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="26" target="11">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="70" value="机构看板 / 质量报表" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="26" target="12">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="68" value="D9 站内信与审计BI
message / logs / reports" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#f8f9fa;strokeColor=#666666;fontSize=12;" vertex="1" parent="1">
|
||||
<mxGeometry x="980" y="970" width="280" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
|
||||
<mxCell id="80" value="登录/绑定" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="10" target="20"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="81" value="教学管理" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="11" target="20"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="82" value="运营管控" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="12" target="20"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
|
||||
<mxCell id="83" value="作业/资料请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="20" target="21"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="84" value="批改触发" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="21" target="22"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="85" value="错题沉淀" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="22" target="23"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="86" value="推荐触达" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="23" target="24"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="87" value="讲解评估" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="24" target="25"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="88" value="画像闭环" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="25" target="26"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
|
||||
<mxCell id="89" value="OCR请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="22" target="40"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="90" value="LLM批改请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="22" target="41"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="91" value="语音评测请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="25" target="42"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="92" value="推荐排序请求" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="24" target="43"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="93" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="40" target="44"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="94" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="41" target="44"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="95" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="42" target="44"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="96" value="任务记录/模型路由" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#d6b656;" edge="1" parent="1" source="43" target="44"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
|
||||
<mxCell id="100" value="用户/权限" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="20" target="60"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="101" value="作业/提交" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="21" target="61"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="102" value="批改/错题/讲解" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="22" target="62"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="103" value="错题/复习计划" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="23" target="62"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="104" value="推荐任务/反馈/画像" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="24" target="63"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="105" value="成就进度/发放" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="26" target="64"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="106" value="缓存读写" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="24" target="65"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="107" value="文件/知识资产" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="40" target="66"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="108" value="检索/图谱" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="43" target="66"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="109" value="消息/审计报表" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="24" target="68"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="110" value="AI日志/任务" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="44" target="68"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="114" value="同步调度(REST/JAR)" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;dashed=1;endArrow=block;endFill=1;strokeColor=#666666;" edge="1" parent="1" source="44" target="67"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
|
||||
<mxCell id="111" value="结果回传" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#82b366;" edge="1" parent="1" source="22" target="10"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="112" value="待复核/教学建议" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#6c8ebf;" edge="1" parent="1" source="25" target="11"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
<mxCell id="113" value="质量看板" style="edgeStyle=orthogonalEdgeStyle;rounded=0;html=1;endArrow=block;endFill=1;strokeColor=#b85450;" edge="1" parent="1" source="26" target="12"><mxGeometry relative="1" as="geometry"/></mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<mxCell id="2" value="AI智能学习系统系统架构" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="技术栈:微信小程序 + React + Java + Python(AI处理供 Java 调用) + Redis + PostgreSQL" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxCell id="3" value="技术栈:微信小程序 + React + Java + Python(AI处理供 Java 调用) + Redis + PostgreSQL + Milvus + NebulaGraph/Neo4j" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="52" width="760" height="22" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="角色与前端接入层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||
@@ -40,7 +40,7 @@
|
||||
<mxCell id="34" value="错题 / 复习 / 推荐服务<br>错题本、艾宾浩斯计划、推荐触达" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;fontSize=14;" parent="1" vertex="1">
|
||||
<mxGeometry x="690" y="280" width="220" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="35" value="知识库与机构运营服务<br>知识库审核、运营看板、质量监控、报表导出" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
|
||||
<mxCell id="35" value="知识库与机构运营服务<br>知识图谱/向量库管理、运营看板、质量监控、报表导出" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;fontSize=14;" parent="1" vertex="1">
|
||||
<mxGeometry x="940" y="280" width="220" height="80" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="36" value="分布式治理能力<br>服务注册发现 / 配置中心 / 链路追踪 / 灰度发布" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;fontSize=14;" parent="1" vertex="1">
|
||||
@@ -79,10 +79,10 @@
|
||||
<mxCell id="63" value="对象存储<br>图片 / PDF / 音频 / 课件 / 讲解录音" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||
<mxGeometry x="1100" y="650" width="200" height="85" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="64" value="MQ / 任务调度<br>异步批改、推荐推送、报表任务" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||
<mxCell id="64" value="MQ / 任务调度<br>异步批改、推荐推送、图谱/向量同步(REST/JAR)" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||
<mxGeometry x="1340" y="650" width="200" height="85" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="65" value="日志 / 审计 / BI<br>操作日志、链路日志、运营分析" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||
<mxCell id="65" value="Milvus + NebulaGraph/Neo4j<br>向量检索、知识图谱、混合检索日志" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;fillColor=#fff2cc;strokeColor=#d6b656;fontSize=13;" parent="1" vertex="1">
|
||||
<mxGeometry x="1580" y="650" width="160" height="85" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="70" value="外部能力" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||
@@ -210,7 +210,7 @@
|
||||
<mxCell id="2" value="AI智能学习系统代码架构" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=24;fontStyle=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="20" width="420" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="3" value="前端:微信小程序 + React,后端:Java 分布式服务,AI:Python 服务,数据:Redis + PostgreSQL" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxCell id="3" value="前端:微信小程序 + React,后端:Java 分布式服务,AI:Python 服务,数据:Redis + PostgreSQL + Milvus + NebulaGraph/Neo4j" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;fontSize=13;fontColor=#666666;" parent="1" vertex="1">
|
||||
<mxGeometry x="30" y="52" width="780" height="22" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="前端应用层" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f8f9fa;strokeColor=#b7c3d0;fontSize=16;fontStyle=1;align=left;spacingLeft=12;verticalAlign=top;" parent="1" vertex="1">
|
||||
|
||||
BIN
docs/flow_student.jpeg
Normal file
|
After Width: | Height: | Size: 392 KiB |
BIN
docs/flow_teacher.jpeg
Normal file
|
After Width: | Height: | Size: 58 KiB |
@@ -5,7 +5,7 @@
|
||||
- [x] 根目录骨架已创建:`backend`、`frontend`、`app`、`init/pg`
|
||||
- [x] `backend` Maven 多模块目录与基础 POM 已落地
|
||||
- [x] `gateway`、`auth`、`upms`、`boot-dev`、`python-ai` 首版占位代码已创建
|
||||
- [x] `init/pg` 已按模块拆分,并接入根目录 `sys_area.sql`
|
||||
- [x] `init/pg` 已按模块拆分,并接入根目录 `tb_sys_area.sql`
|
||||
- [x] 学校租户表命名已修正为 `tb_sys_tenant`
|
||||
- [x] 跨包 DTO、Enums 已收口到对应 `api-*` 模块
|
||||
- [x] Web 端已从 workspace 收敛为单一 React 项目
|
||||
@@ -40,7 +40,7 @@
|
||||
- `backend/apis`:`api-auth`、`api-upms`、`api-ai`
|
||||
- `backend/gateway`:统一入口、鉴权、路由、跨域、trace 透传
|
||||
- `backend/auth`:登录、token、当前用户
|
||||
- `backend/upms`:用户、角色、权限、菜单、动态路由元数据、组织与区域基座
|
||||
- `backend/upms`:用户、角色、菜单授权、菜单与动态路由元数据、组织与区域基座
|
||||
- `backend/ai-client`:Java 调 Python 的适配层
|
||||
- `backend/boot-dev`:本地聚合启动模块
|
||||
- `backend/python-ai`:独立 Python AI 服务占位
|
||||
@@ -76,9 +76,9 @@
|
||||
- 区域是分库分表的核心路由维度:
|
||||
- 以“省份区域”作为首要分片依据
|
||||
- 业务表设计时必须显式保留区域路由字段
|
||||
- `sys_area.sql` 视为区域基础数据来源约束:
|
||||
- 首版必须预留 `sys_area` 基础表和初始化脚本接入位
|
||||
- 区域编码、层级、父子关系以 `sys_area.sql` 为准
|
||||
- `tb_sys_area.sql` 视为区域基础数据来源约束:
|
||||
- 首版必须预留 `tb_sys_area` 基础表和初始化脚本接入位
|
||||
- 区域编码、层级、父子关系以 `tb_sys_area.sql` 为准
|
||||
- 首版数据模型明确区分两棵树:
|
||||
- 区域树:省 / 市 / 区县
|
||||
- 组织树:总校 / 分校 / 校区 / 部门
|
||||
@@ -88,7 +88,6 @@
|
||||
- `SysDept`
|
||||
- `SysUser`
|
||||
- `SysRole`
|
||||
- `SysPermission`
|
||||
- 所有租户级业务主表统一预留字段:
|
||||
- `adcode`
|
||||
- `tenant_id` 或 `school_id`
|
||||
@@ -106,7 +105,7 @@
|
||||
- 目录结构固定为类似:
|
||||
- `init/pg/00_create_db.sql`
|
||||
- `init/pg/01_create_schema.sql`
|
||||
- `init/pg/sys/sys_area.sql`
|
||||
- `init/pg/sys/tb_sys_area.sql`
|
||||
- `init/pg/auth/*.sql`
|
||||
- `init/pg/upms/*.sql`
|
||||
- `init/pg/ai/*.sql`
|
||||
@@ -114,12 +113,12 @@
|
||||
- 每个模块维护自己的建表 SQL、索引 SQL、初始化数据 SQL
|
||||
- 不把所有表混在一个超大 SQL 文件中
|
||||
- 公共基础表单独归 `sys` 或 `common` 目录
|
||||
- `sys_area.sql` 固定归属:
|
||||
- `tb_sys_area.sql` 固定归属:
|
||||
- 放在 `init/pg/sys/`
|
||||
- 作为区域基础数据的首批初始化脚本
|
||||
- 模块 SQL 的职责边界固定:
|
||||
- `auth`:登录、token、认证相关表
|
||||
- `upms`:用户、角色、权限、菜单、学校租户、部门、区域引用关系
|
||||
- `upms`:用户、角色、菜单、角色菜单授权、学校租户、部门、区域引用关系
|
||||
- `ai`:AI 调用记录、任务记录、模型配置占位
|
||||
- 初始化脚本执行规则固定:
|
||||
- 先执行库 / Schema 基础脚本
|
||||
@@ -150,7 +149,7 @@
|
||||
- `traceId`
|
||||
- `upms` 首版接口能力必须覆盖:
|
||||
- 当前用户信息
|
||||
- 用户 / 角色 / 权限基座
|
||||
- 用户 / 角色 / 菜单授权基座
|
||||
- 区域树查询
|
||||
- 学校租户树查询
|
||||
- 部门树查询
|
||||
@@ -189,7 +188,7 @@
|
||||
- PostgreSQL 与 Redis 本地联调配置可跑通
|
||||
- SQL 初始化验证必须覆盖:
|
||||
- `init/pg` 下脚本可按顺序执行
|
||||
- `sys_area.sql` 可独立导入
|
||||
- `tb_sys_area.sql` 可独立导入
|
||||
- `auth`、`upms` 模块脚本可独立维护且组合执行无冲突
|
||||
- 区域与租户模型最小验证必须覆盖:
|
||||
- 区域树可查询
|
||||
@@ -202,5 +201,5 @@
|
||||
- 不使用 `Dubbo`
|
||||
- 首版不做真实业务页面、不做学生端真实业务、不做真实 AI 推理
|
||||
- `upms` 继续承担首版系统管理中心职责
|
||||
- `sys_area.sql` 是必须接入的区域基础数据脚本,且归档在 `init/pg/sys/`
|
||||
- `tb_sys_area.sql` 是必须接入的区域基础数据脚本,且归档在 `init/pg/sys/`
|
||||
- 部门当前先按“年级、学科等组织维度”建模,不在首版细化更复杂教学组织规则
|
||||
|
||||
267
docs/系统.md
Normal file
@@ -0,0 +1,267 @@
|
||||
---
|
||||
|
||||
# 产品名称:优知源·AI错题费曼复习系统
|
||||
**版本号**:V1.1
|
||||
**文档状态**:初稿
|
||||
**最后更新日期**:2026-03-24
|
||||
|
||||
## 1. 项目概述
|
||||
### 1.1 项目背景
|
||||
旨在利用AI技术解决批改作业时间占用率问题,缩短作业批改时长和学生使用费曼学习点评时长,同一班级学生作业和点评可同时批改,比如之前一个学生5分钟*40学生,老师需要占用200分钟批批改作业,费曼评分10分钟*10学生,需占用老师100分钟,系统上线后可实现AI自动批改和点评,批改+费曼评分时间可忽略不计,还可以帮助老师统计学生作业完成率,正确率,可以帮助老师用同样的时间管理更多学生
|
||||
|
||||
### 1.2 核心价值
|
||||
+ **学生**:错题自动成册,费曼讲解结合AI分析,提升知识吸收,缩短作业批改等待时长。
|
||||
+ **教师**:快速了解班级学情,智能发布作业,基于真实错题数据的堂前精准测验,减少批改作业和费曼点评占用工作时长。
|
||||
|
||||
---
|
||||
|
||||
## 2. 用户角色与功能
|
||||
| 角色 | 终端 | 核心功能 |
|
||||
| --- | --- | --- |
|
||||
| **学生** | 微信小程序 | 1. 班级作业(包含查看作业,下载作业、上传作业图片等功能)<br/>2. AI批改(包含学科作业校验防止学生乱传、作业题目批改、优知源题库识别)<br/>3. AI错题本复习(包含错题与作业匹配、历史错题记录)<br/>4. 艾宾浩斯遗忘曲线和莱特纳盒子算法推送错题<br/>5. AI费曼讲解评分(包含费曼模板、图片音频识别、评分体系) |
|
||||
| **教师** | Web管理后台 | 1. AI班级看板<br/>2. AI学情跟踪<br/>3. AI深度学情分析<br/>4. AI作业计划日历管理<br/>5. AI温故知新 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3. 业务流程图 (逻辑描述)
|
||||
### 3.1 学生端核心流程
|
||||

|
||||
|
||||
|
||||
|
||||
1. **登录**:手机号一键登录/验证码登录 -> 识别身份。
|
||||
2. **学生作业页面**:展示已报学科班级列表 + 小红点(待办计数,作业/错题)。
|
||||
3. **班级页**:
|
||||
- **<font style="color:#000000;">学科作业:</font>**<font style="color:#000000;">下载当日作业 -> 上传 -></font>**AI判断是否为已报学科作业**<font style="color:#000000;">-> </font>**AI批改**<font style="color:#000000;"> -> 生成错题本并标记错题待办</font><font style="color:rgb(6, 10, 38);"> -> </font>**<font style="color:rgb(6, 10, 38);">学生针对错题进行重做</font>**<font style="color:#000000;"> -> 上传 </font><font style="color:rgb(6, 10, 38);"> -> AI二次批改</font><font style="color:#000000;">。</font>
|
||||
- **<font style="color:#000000;">温故知新</font>**<font style="color:#000000;">:</font>班级页独立入口,根据艾宾浩斯遗忘曲线和莱特纳盒子算法推送历史错题复习任务<font style="color:rgb(6, 10, 38);">。</font>
|
||||
- **返回逻辑**:若有未完成待办(当日作业/错题),返回首页时保留小红点提示。
|
||||
- **判断逻辑**<font style="color:rgb(6, 10, 38);">:</font>若仍有错误:循环“重做”步骤,直到该题正确。
|
||||
若**本次作业所有错题均重做正确** -> 触发**费曼讲解环节**(必须全部做对才能开始讲)。
|
||||
4. **错题复习(费曼模式)**:
|
||||
- 进入错题 -> 展示费曼模版 -> 语音录入。
|
||||
- **AI评分**:
|
||||
* < 85%:提示错误原因,需要重录,错题待办(按钮:重新录入 / 先做下一题)
|
||||
如选择先做下一题,则将本题置于错题本最后一题,重新推送。
|
||||
* ≥85%:正向鼓励,提示如何更优秀 -> 自动进入下一题。
|
||||
- **循环结束**:所有错题待办完成 -> 弹出鼓励语 -> 返回首页 -> **消除该学科小红点**。
|
||||
|
||||
### 3.2 教师端核心流程
|
||||
|
||||

|
||||
|
||||
1. **登录**:手机号登录Web端。
|
||||
2. **首页**:展示分配班级/学科。
|
||||
3. **班级详情页**:
|
||||
- **仪表盘**:昨日/今日完成率、正确率。
|
||||
- **深度学情分析**:点击数据 -> 全班列表 -> 点击学生 -> 查看具体作业(图片+费曼语音)。
|
||||
4. **训练计划(日历)**:
|
||||
- 颜色标识:绿(已预设)。
|
||||
- 操作:新建(默认未来一周,支持文档AI识别填充)、查看、修改(删除)。
|
||||
5. **温故计划**
|
||||
- 默认展示T+1日期的学生筛选结果
|
||||
- 操作:对学生维度<font style="color:rgb(6, 10, 38);">温故知新推送错题</font>查看、修改(删除)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 详细功能需求说明
|
||||
### 4.1 学生小程序端
|
||||
#### 4.1.1 登录与首页
|
||||
+ **功能描述**:
|
||||
- 支持手机号获取验证码登录,自动关联后台用户信息。
|
||||
- **首页布局**:卡片式展示“已报名学科班级”。
|
||||
- **消息提示**:每个班级卡片右上角显示红色圆点及数字,代表“待处理作业数”或“待复习错题数”。
|
||||
+ **交互逻辑**:
|
||||
- 若用户有未完成的“待办作业或待办错题”并点击返回上一页,系统需拦截或标记,确保小红点不消失,直到任务状态变更。
|
||||
|
||||
#### 4.1.2 班级作业模块
|
||||
+ **待办作业列表**:
|
||||
- 列表一:展示老师布置的作业标题、截止日期。
|
||||
- 列表二:温故知新系统根据艾宾浩斯遗忘曲线和莱特纳盒子算法,自动展示需要复习的历史错题并展示需要复习题数小红点。
|
||||
- **开始作业**
|
||||
* **下载作业**:点击按钮下载老师上传/温故知新的作业文件(图片/PDF/word)。
|
||||
* **上传作业**:调用摄像头拍照,支持多图上传,支持上传wore,PDF,
|
||||
拍照支持重拍,图片和文件支持添加和删除,提交后触发AI批改。
|
||||
* 如从已上传作业且批改完成,则进入批改结果页面。
|
||||
|
||||
#### 4.1.3 AI批改与错题本
|
||||
+ **后端逻辑**:
|
||||
- 接收图片 -> 大模型识别->本次作业校验->MCP优知源题库 -> 题目匹配->解题步骤分析 -> 判定对错。
|
||||
- 模型优化:通过意图识别优化、skill沉淀、MCP分层等手段持续优化模型识别准确性和性能
|
||||
上传作业先校验是否为优智源发布作业,如非学生当日学科作业阻塞流程,提醒:“<font style="color:rgb(51, 51, 51);">该作业并非学生在优知所学习学科作业,请核对后重新上传。”</font>
|
||||
若答案为空,则判断为错误。
|
||||
- 若错误,自动存入“错题本”,标记知识点,标记待办,并统计所有错题待办数。
|
||||
- <font style="color:#DF2A3F;">莱特盒子标记逻辑:
|
||||
</font><font style="color:#DF2A3F;">预设box1、box2、box3、box4、box5
|
||||
</font><font style="color:#DF2A3F;">新增错题,记录错题时间,进入box1,
|
||||
</font><font style="color:#DF2A3F;">复习时正确则题目盒子+1(比如box1->box2)
|
||||
</font><font style="color:#DF2A3F;">复习时错误则题目盒子重置为box1
|
||||
</font><font style="color:#DF2A3F;">复习时如题目在box5且回答正确则从错题本移除</font>
|
||||
- 异常场景:处理中时,中途断网、中断操作(退出小程序,来点/短信),默认为未上传成功
|
||||
+ **前端展示**:
|
||||
- 批改结果页:显示原题、学生作答、正确答案、**错因分析**。
|
||||
- 按钮:“开始重做”“返回”:
|
||||
[开始重做],点击后进入重做页,支持再次拍照上传解答过程。
|
||||
[返回],返回待办作业列表,当前学科待办继续展示,待办数读取后端存值。
|
||||
+ **二次批改**:
|
||||
- AI对比原题与新上传的答案。
|
||||
- 前端展示:批改结果页:显示原题、学生作答、正确答案、**错因分析**。
|
||||
若正确:标记该题为“已订正”。
|
||||
- 若全部正确,页面提示:“已全部答对,我们开始复习吧!”并触发费曼解锁
|
||||
+ **费曼解锁条件**:
|
||||
- 只有当本次作业下所有错题状态均为“**已订正**”时,页面底部[开始复习]按钮才变为可点击状态(高亮)[开始重做]同时置灰。
|
||||
若有点错未订正,点击按钮提示:“请先完成所有错题的订正”。
|
||||
- [开始复习],进入费曼讲解
|
||||
[返回],返回待办作业列表,当前学科待办继续展示,待办数读取后端存值。
|
||||
|
||||
|
||||
|
||||
#### 4.1.4 费曼讲解核心模块(重点)
|
||||
+ **页面布局**:
|
||||
- 顶部:当前错题题目简述。
|
||||
- 中部:**费曼五步法填空模版**(支持语音转文字实时上屏,也支持纯语音提交):
|
||||
1. 我要讲的概念是:`__________`
|
||||
2. 它最核心的作用是:`__________`(一句话说清价值)
|
||||
3. 举个学习中的例子:`__________`(用熟悉题目类比)
|
||||
4. 常见的误解是:`__________`(指出易错点)
|
||||
5. 我可以这样简化它:`__________`(用口诀/比喻总结)
|
||||
- 底部:大尺寸“按住说话”按钮;小喇叭“试听”按钮,“提交评分”按钮。
|
||||
+ **AI评分逻辑**:
|
||||
- **输入**:学生语音 + 原题知识点。
|
||||
* **知识点讲清 (30%)**:是否准确描述了题目涉及的核心概念。
|
||||
* **原理说明 (30%)**:能否一句话概括该知识点的作用。
|
||||
* **逻辑思路 (30%)**:举例是否恰当,是否能用旧知解释新知。
|
||||
* **语言表达 (10%)**:回答语言是否清晰,语速是否流畅。
|
||||
* **总分计算**:加权求和,映射到0-100分。
|
||||
- **处理**:分析讲解的逻辑性、准确性、通俗性。
|
||||
- **异常场景**:处理中时,中途断网、中断操作(退出小程序,来点/短信),默认为未上传成功,需重新录入
|
||||
- **输出与分支**:
|
||||
* **评分 < 85%**:
|
||||
+ 提示:“亲爱的伙伴讲解不够清晰,未抓住核心,请重试。”
|
||||
AI总结根据评分标准显示对应维度的得分。
|
||||
+ 按钮组:[重新录入] (高亮), [先做下一题] (次要)。
|
||||
+ _注:选择“先做下一题”则该题状态标记为“待重讲”,小红点不消除。_
|
||||
* **评分 ≥ 85%**:
|
||||
+ 动效:撒花/勋章动画,文案:“太棒了!你已经掌握了!”
|
||||
+ 动作:自动跳转至下一道错题。
|
||||
+ **结算页**:
|
||||
- 条件:当前学科下所有“待复习”错题状态均为“通过”。
|
||||
- 展示:全屏鼓励语海报。
|
||||
- 操作:[返回主页] -> 触发该学科作业或温故知新的小红点消除逻辑。
|
||||
|
||||
---
|
||||
|
||||
#### 4.1.5 温故知新复习模式
|
||||
+ **推送逻辑**:
|
||||
- 后台根据每道错题的“最后掌握时间”和“错误次数”计算下次复习日期。
|
||||
- **莱特纳盒子机制**:
|
||||
* 盒子1(T+1日):新错题或常错题。
|
||||
* 盒子2(3天后):掌握度一般的题。
|
||||
* 盒子3(7天后)、盒子4(16天后)、盒子5(35天后):掌握度高的题。
|
||||
- **艾宾浩斯节点**:强制在遗忘临界点(如第1、3、7、16、35天)推送。
|
||||
+ **复习流程**:
|
||||
- 学生点击“温故知新”待办 →→ 上传作业 →→ AI批改 →→ 正确/错误 →→ 系统更新该题的“盒子等级”和“下次复习时间”。
|
||||
|
||||
### 4.2 教师Web端
|
||||
#### 4.2.1 登录与概览
|
||||
+ **登录**:手机号 + 验证码/密码。
|
||||
+ **主页**:网格布局展示所带班级及对应学科。
|
||||
|
||||
#### 4.2.2 班级数据仪表盘
|
||||
+ **核心指标卡**:
|
||||
- 筛选条件,默认当天,日历可选择对应日期
|
||||
作业情况:完成率 % | 正确率 %
|
||||
(根据条件展示效果,完成率完成率=筛选日期区间内提交作业的学生人数/筛选日期区间内应提交作业的学生总人数×100%,正确率= 筛选日期区间内作业批改正确的总题数/筛选日期区间内作业批改的总题数×100%)
|
||||
+ **详细内容**:
|
||||
- **表格数据**:学生名称,取学生名称;完成度,筛选当天提交作业题数/筛选当天应提交题数*100%;第一次提交正确率,筛选当天第一次提交正确题数/筛选当天题数*100%;费曼学习平均评分,筛选当天费曼评分之和/次数;最后一次提交时间,筛选当天最后一次提交时间。
|
||||
- **查看详情:**左侧展示作业原图/答题图,右侧/下方播放“费曼讲解”录音波形及转写文本。
|
||||
- 导航:每层页面均有明确的 [返回] 按钮。
|
||||
+ **深度学情仪表盘(新增核心模块)**
|
||||
- **模块一:错题类型与概率分布(可按日,周,月时间筛选)**
|
||||
* **图表**:柱状图展示错题类型(计算错误、概念混淆、审题不清、逻辑漏洞)。
|
||||
* **数据**:各类型占比及出现概率。
|
||||
* **<font style="color:rgb(6, 10, 38);">目的</font>**<font style="color:rgb(6, 10, 38);">:帮助老师判断是全班共性问题还是个别习惯问题。
|
||||
</font><font style="color:rgb(6, 10, 38);">如计算错误 -< 题目列表 -< 学生列表-< 查看-< 具体单体分析报告</font>
|
||||
- **模块二:知识点错题率(可按日,周,月时间筛选)**
|
||||
* **图标**:柱状图按知识点罗列,显示错误概率大的前五个知识点。
|
||||
* **交互**:点击某知识点,打开新页面查看具体是哪些题目错了。
|
||||
* **目的**:帮助老师抓住高错误知识点
|
||||
如知识点1 <font style="color:rgb(6, 10, 38);">-< 题目列表 -< 学生列表-< 查看-< 具体单体分析报告</font>
|
||||
- **模块三:错题高频学生排名(可日,周,月按时间筛选)**
|
||||
* **列表**:错题总数最多学生排名
|
||||
展示默认展示10个学生,可翻页,
|
||||
* **目的**:快速定位需要重点辅导的“困难户”。
|
||||
如学生张三<font style="color:rgb(6, 10, 38);">-< 题目列表 -< 查看-< 具体单体分析报告</font>
|
||||
- 从上述模块一二图表点跳转学生列表
|
||||
+ 展示题目原图。
|
||||
+ 展示全班学生列表。
|
||||
- 对错,费曼评分,根据费曼评分最低倒序排列。
|
||||
- **位置**:简易仪表盘上方按钮,独立Tab页**[深度学情分析]**。
|
||||
|
||||
#### 4.2.3 训练计划(日历视图)
|
||||
+ **界面元素**:
|
||||
- **日历组件**:
|
||||
* 🟢 绿色:已预设作业且已完成批改。
|
||||
- **作业日志列表**:位于日历下方,按创建时间倒序排列。
|
||||
+ **操作功能**:
|
||||
- **新建计划**:
|
||||
* 默认时间范围:当前日期下周一周7天。
|
||||
* 表单:每一天为一行,支持上传文档。
|
||||
* **AI辅助**:上传文档后,点击“AI识别”,自动提取题目填入作业内容区域。
|
||||
文档上传提取准确率为100%,行预览按钮,可预览转换后数据
|
||||
* 校验:保存时检查天数 ≥ 1。
|
||||
- **修改**:
|
||||
* 点击某日期的“修改”按钮,可修改**该日期后一天**的作业内容和截止日期等内容(防止误改已发布当天的紧急数据)。
|
||||
* 按钮【确认】点击保存修改内容
|
||||
- **返回**:各层级均有返回按钮,返回不做保存逻辑。
|
||||
|
||||
|
||||
|
||||
#### 4.2.4 温故计划
|
||||
+ **界面元素**:
|
||||
- **筛选组件**:默认筛选T+1日期。
|
||||
- **学生列表**:位于日历下方,按入学录入系统时间排列。
|
||||
+ **操作功能**:
|
||||
- **修改**:
|
||||
* 点击某学生的“修改”按钮,进入修改作业页面,可更换推送内容,整体修改。
|
||||
* 按钮【确认】点击保存修改内容
|
||||
* 替换掉的错题盒子等级继续保留
|
||||
- **返回**:各层级均有返回按钮,返回不做保存逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 5.原型图链接
|
||||
### 5.1 小程序原型图链接
|
||||
[https://modao.cc/proto/BEv3KNantcaveaK0xcJ28A/sharing?view_mode=read_only&screen=rbpVEb8VfOdcd8i85](https://modao.cc/proto/BEv3KNantcaveaK0xcJ28A/sharing?view_mode=read_only&screen=rbpVEb8VfOdcd8i85) #小程序-分享
|
||||
|
||||
### 5.2wep原型图链接
|
||||
[https://modao.cc/proto/aDopc1kVtcaxlzyaS8cXEN/sharing?view_mode=read_only&screen=rbpVEbJqRCrED8gVy](https://modao.cc/proto/aDopc1kVtcaxlzyaS8cXEN/sharing?view_mode=read_only&screen=rbpVEbJqRCrED8gVy) #训练计划-分享
|
||||
|
||||
## 6. 待讨论项
|
||||
1. 老师设置作业的形式需要讨论
|
||||
1. 逐题录入?上传word?上传图片?作业量级如何? 录入形式影响C端批改作业、生成错题集的准确度
|
||||
3/23会议讨论:word和PDF格式文件
|
||||
2. 是否要录入标准答案,什么形式需要讨论
|
||||
3/23会议讨论:word和PDF格式文件作业内包含标准答案
|
||||
2. 学生端UI风格待确定,优知源哪位老师来设计?确定后九维用AI生成设计稿
|
||||
3/23会议讨论:优智源有专业设计,3/24进入项目组
|
||||
3. 费曼学习法学生上传 的是图片?语言?视频? 是一批题目的讲解还是一个题目? 视频费曼学习法,业界还没有这样的产品,比较费token,准确度也不高,探索难度极大,需要持续优化
|
||||
3/23会议讨论:可以是语音,如果是视频更好,可以技术探索一下
|
||||
4. 教师端作业相关的功能与静哥现在做的功能是什么关系?静哥是基础数据,需要给我们开放学生、班级、课程、作业等基础数据,我们做应用层开发
|
||||
3/23会议讨论:无太大影响,除了班级学科信息有关联,其他暂不用处理
|
||||
5. 现在服务器数据库资源放在腾讯云上的,大模型等AI产品都用阿里云千问的,后续其他资源譬如文件服务器OSS、redis等资源用腾讯的还是阿里的?需要维护2个平台,需要大家决策
|
||||
|
||||
6. 重做的审批机制是不是和批改完全一致,是不是需要更侧重一些对比数据——比如字体,格式,步骤,防止学生潦草应对
|
||||
7. 遗忘曲线模块,是否增加错题日限制——单日学生单科最大推送量,如果单日大量推送,学生能否做完,大量推送学生会不会产生厌学情绪
|
||||
8. 仪表盘一的统计数据需要预置是否满足业务,如有不足可讨论增加,
|
||||
9. 已超过截止时间,作业是否可以继续解答,如果做闭环,
|
||||
建议方案1:不可作答,过了12点自动更新掉不做累计,老师端核心指标增加仪表提示未做作业学生,人工介入处理,次日线下处理并上传ai批改。
|
||||
好处,不给学生太大压力,及时跟进学情,督促学生,缺点,人工需要做的多一些
|
||||
建议方案2:可作答,不做完作业待办小红点累计,老师端深度学情分析增加仪表统计晚于截止时间作答学生
|
||||
好处,无需人工介入,全程线上,缺点,不及时督促学生待办作业累积会有心理压力,且长期超过最晚时间作答可能影响孩子晚上睡眠,不利于好的作息习惯养成
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
81
docs/系统与功能清单差异分析.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 《功能清单》与《系统.md》差异分析
|
||||
## 文档定位
|
||||
- `AI智能学习系统功能清单.md`:已签约/已约定的合同范围基线(Scope Baseline)。
|
||||
- `系统.md`:甲方最新发出的需求说明,属于新增需求输入与流程细化输入(Change Request Source)。
|
||||
|
||||
## 核心区分点
|
||||
1. 功能清单用于界定合同交付边界。
|
||||
2. 系统文档用于表达甲方最新业务想法,其中包含细化项、增项、以及与合同冲突项。
|
||||
3. 后续评审必须以“合同基线 + 变更确认”双轨执行,避免范围漂移。
|
||||
|
||||
## 一、系统.md 相对合同的新增/细化点
|
||||
### 1) 学生端流程门禁增强
|
||||
- 增加“先订正完全部错题,才可解锁费曼复习”的强约束。
|
||||
- 增加费曼评分阈值(85分)及未通过回流机制(重录/后移)。
|
||||
- 增加小红点保留与消除的精细规则(与任务状态强绑定)。
|
||||
|
||||
### 2) AI策略与状态规则更明确
|
||||
- 新增莱特纳 box1~box5 的明确升降级逻辑与移除规则。
|
||||
- 新增费曼评分维度与权重(知识点/原理/逻辑/表达)。
|
||||
- 新增异常场景处理口径(断网、退出、来电等统一按失败处理)。
|
||||
|
||||
### 3) 教师端分析细节提升
|
||||
- 新增深度学情三大模块:错因分布、知识点错题率、高频错题学生。
|
||||
- 新增图表下钻链路(类型/知识点 -> 题目 -> 学生 -> 详情)。
|
||||
|
||||
### 4) 计划管理规则更具体
|
||||
- 训练计划默认未来7天。
|
||||
- 作业文档AI识别填充流程。
|
||||
- 计划修改边界限制(防误改发布内容)。
|
||||
|
||||
## 二、合同功能清单中存在、但系统.md 未覆盖或弱化的内容
|
||||
### 1) 机构端(Web)整体能力缺失
|
||||
- 管理员高权限、教师/学员统一管理、权限配置、质量监控、机构报表等未展开。
|
||||
|
||||
### 2) 学生端扩展能力弱化
|
||||
- AI变式题(举一反三)未成为主链路。
|
||||
- 个性化推荐未形成明确功能闭环。
|
||||
- 学习激励体系(勋章、打卡、韧性指标)未明确。
|
||||
- 消息中心(已读/删除等)未明确。
|
||||
|
||||
### 3) 教师端管理与干预能力弱化
|
||||
- 班级/学员管理未完整体现。
|
||||
- 教师复核批改与讲解点评能力弱化。
|
||||
- 教学建议与报表导出未作为明确交付项表达。
|
||||
|
||||
### 4) 支撑层能力表达不足
|
||||
- 知识库标准化管理、推荐算法闭环在系统文档中不完整。
|
||||
|
||||
## 三、两文档冲突点(需优先决策)
|
||||
1. **作业模式冲突**
|
||||
合同含“机构作业 + 自主作业双模式”;系统文档强调“非本学科作业阻塞”,存在边界冲突。
|
||||
|
||||
2. **人工复核与纯AI闭环冲突**
|
||||
合同包含教师复核/点评;系统文档更偏全自动闭环,需明确教师介入职责。
|
||||
|
||||
3. **交付范围冲突**
|
||||
合同覆盖“学生+教师+机构+支撑层”;系统文档聚焦“学生+教师流程”,存在范围收缩/转移风险。
|
||||
|
||||
## 四、变更分类建议(用于评审)
|
||||
### A. 可视为“细化不增项”
|
||||
- 已有功能的页面流程细化、状态文案细化、评分展示细化(不新增系统能力)。
|
||||
|
||||
### B. 可能构成“新增范围”
|
||||
- 深度学情下钻链路与分析维度扩展。
|
||||
- 严格状态机与门禁规则落地(对前后端改造影响较大)。
|
||||
- 费曼评分机制细化到权重与回流路径。
|
||||
|
||||
### C. 必须“变更单确认”
|
||||
- 与合同冲突的作业模式口径。
|
||||
- 是否保留教师复核机制。
|
||||
- 是否把机构端/推荐/激励等合同项后置或拆期。
|
||||
|
||||
## 五、建议的会议确认清单
|
||||
1. 本次交付是否继续以《功能清单》为合同唯一基线。
|
||||
2. 《系统.md》哪些条目作为本期新增,哪些进入二期。
|
||||
3. 冲突条目采用哪一版口径(含验收标准与责任边界)。
|
||||
4. 新增条目对应的工期、费用、里程碑是否同步调整。
|
||||
|
||||
## 结论
|
||||
`系统.md` 不是合同替代文本,而是新增需求输入文档。
|
||||
建议以《功能清单》作为验收主依据,对《系统.md》逐条走“细化/增项/冲突”分类并形成书面变更结论。
|
||||
@@ -1,4 +1,7 @@
|
||||
CREATE SCHEMA IF NOT EXISTS sys;
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
CREATE SCHEMA IF NOT EXISTS upms;
|
||||
CREATE SCHEMA IF NOT EXISTS ai;
|
||||
CREATE SCHEMA IF NOT EXISTS course;
|
||||
CREATE SCHEMA IF NOT EXISTS question;
|
||||
CREATE SCHEMA IF NOT EXISTS achievement;
|
||||
CREATE SCHEMA IF NOT EXISTS recommendation;
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
\i /docker-entrypoint-initdb.d/upms/10_create_upms_tables.sql
|
||||
\i /docker-entrypoint-initdb.d/auth/10_create_auth_tables.sql
|
||||
\i /docker-entrypoint-initdb.d/ai/10_create_ai_tables.sql
|
||||
\i /docker-entrypoint-initdb.d/course/10_create_course_tables.sql
|
||||
\i /docker-entrypoint-initdb.d/question/10_create_question_tables.sql
|
||||
\i /docker-entrypoint-initdb.d/achievement/10_create_achievement_tables.sql
|
||||
\i /docker-entrypoint-initdb.d/recommendation/10_create_recommendation_tables.sql
|
||||
\i /docker-entrypoint-initdb.d/sys/sys_area.sql
|
||||
\i /docker-entrypoint-initdb.d/upms/20_init_upms_seed.sql
|
||||
\i /docker-entrypoint-initdb.d/auth/20_init_auth_seed.sql
|
||||
\i /docker-entrypoint-initdb.d/ai/20_init_ai_seed.sql
|
||||
\i /docker-entrypoint-initdb.d/course/20_init_course_seed.sql
|
||||
\i /docker-entrypoint-initdb.d/question/20_init_question_seed.sql
|
||||
\i /docker-entrypoint-initdb.d/achievement/20_init_achievement_seed.sql
|
||||
|
||||
407
init/pg/achievement/10_create_achievement_tables.sql
Normal file
@@ -0,0 +1,407 @@
|
||||
DROP SCHEMA IF EXISTS achievement CASCADE;
|
||||
CREATE SCHEMA IF NOT EXISTS achievement;
|
||||
|
||||
DROP TABLE IF EXISTS achievement.ac_achievement CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS achievement.ac_achievement (
|
||||
achievement_id VARCHAR(64) PRIMARY KEY,
|
||||
achievement_code VARCHAR(64) UNIQUE NOT NULL,
|
||||
achievement_name VARCHAR(128) NOT NULL,
|
||||
achievement_desc TEXT,
|
||||
achievement_category VARCHAR(32) NOT NULL DEFAULT 'SCORE',
|
||||
trigger_event VARCHAR(32) NOT NULL DEFAULT 'GRADE_PUBLISHED',
|
||||
trigger_rule_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
badge_level VARCHAR(16) NOT NULL DEFAULT 'BRONZE',
|
||||
badge_icon_url VARCHAR(512),
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_by VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_ac_achievement_category
|
||||
CHECK (achievement_category IN ('SCORE', 'PROGRESS', 'TEACHING', 'ENGAGEMENT', 'MANUAL')),
|
||||
CONSTRAINT chk_ac_achievement_trigger_event
|
||||
CHECK (trigger_event IN ('GRADE_PUBLISHED', 'WRONG_QUESTION_MASTERED', 'TASK_COMPLETED', 'REVIEW_COMPLETED', 'MANUAL')),
|
||||
CONSTRAINT chk_ac_achievement_badge_level
|
||||
CHECK (badge_level IN ('BRONZE', 'SILVER', 'GOLD', 'PLATINUM')),
|
||||
CONSTRAINT chk_ac_achievement_status
|
||||
CHECK (status IN ('ACTIVE', 'DISABLED')),
|
||||
CONSTRAINT chk_ac_achievement_trigger_rule_json
|
||||
CHECK (jsonb_typeof(trigger_rule_json) = 'object'),
|
||||
CONSTRAINT fk_ac_achievement_created_by
|
||||
FOREIGN KEY (created_by) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE achievement.ac_achievement IS '成就定义表';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.achievement_id IS '成就ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.achievement_code IS '成就编码';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.achievement_name IS '成就名称';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.achievement_desc IS '成就描述';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.achievement_category IS '成就类别(SCORE/PROGRESS/TEACHING/ENGAGEMENT/MANUAL)';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.trigger_event IS '触发事件(GRADE_PUBLISHED/WRONG_QUESTION_MASTERED/TASK_COMPLETED/REVIEW_COMPLETED/MANUAL)';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.trigger_rule_json IS '触发规则JSON(可配置成绩阈值、次数阈值等)';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.badge_level IS '徽章等级(BRONZE/SILVER/GOLD/PLATINUM)';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.badge_icon_url IS '徽章图标URL';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.status IS '状态(ACTIVE/DISABLED)';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.created_by IS '创建人ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN achievement.ac_achievement.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS achievement.ac_achievement_role_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS achievement.ac_achievement_role_rel (
|
||||
achievement_id VARCHAR(64) NOT NULL,
|
||||
role_id VARCHAR(64) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (achievement_id, role_id),
|
||||
CONSTRAINT fk_ac_achievement_role_rel_achievement
|
||||
FOREIGN KEY (achievement_id) REFERENCES achievement.ac_achievement(achievement_id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_ac_achievement_role_rel_role
|
||||
FOREIGN KEY (role_id) REFERENCES upms.tb_sys_role(role_id)
|
||||
);
|
||||
COMMENT ON TABLE achievement.ac_achievement_role_rel IS '成就-角色绑定表';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_role_rel.achievement_id IS '成就ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_role_rel.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_role_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_role_rel.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS achievement.ac_user_achievement CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS achievement.ac_user_achievement (
|
||||
user_achievement_id VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
achievement_id VARCHAR(64) NOT NULL,
|
||||
role_id VARCHAR(64) NOT NULL,
|
||||
summary_id VARCHAR(64),
|
||||
grading_task_id VARCHAR(64),
|
||||
score_snapshot NUMERIC(8,2),
|
||||
grade_level VARCHAR(8),
|
||||
surpass_ratio NUMERIC(5,2),
|
||||
used_seconds INTEGER,
|
||||
award_source VARCHAR(32) NOT NULL DEFAULT 'RULE_ENGINE',
|
||||
award_reason VARCHAR(256),
|
||||
evidence_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
achieved_count INTEGER NOT NULL DEFAULT 1,
|
||||
first_achieved_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_achieved_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_ac_user_achievement_user_achievement_role
|
||||
UNIQUE (user_id, achievement_id, role_id),
|
||||
CONSTRAINT chk_ac_user_achievement_award_source
|
||||
CHECK (award_source IN ('RULE_ENGINE', 'MANUAL', 'SYSTEM')),
|
||||
CONSTRAINT chk_ac_user_achievement_evidence_json
|
||||
CHECK (jsonb_typeof(evidence_json) = 'object'),
|
||||
CONSTRAINT chk_ac_user_achievement_achieved_count
|
||||
CHECK (achieved_count > 0),
|
||||
CONSTRAINT fk_ac_user_achievement_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_ac_user_achievement_role_binding
|
||||
FOREIGN KEY (achievement_id, role_id) REFERENCES achievement.ac_achievement_role_rel(achievement_id, role_id),
|
||||
CONSTRAINT fk_ac_user_achievement_summary
|
||||
FOREIGN KEY (summary_id) REFERENCES question.gd_score_summary(summary_id),
|
||||
CONSTRAINT fk_ac_user_achievement_task
|
||||
FOREIGN KEY (grading_task_id) REFERENCES question.gd_grading_task(grading_task_id)
|
||||
);
|
||||
COMMENT ON TABLE achievement.ac_user_achievement IS '用户成就记录表';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.user_achievement_id IS '用户成就记录ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.achievement_id IS '成就ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.role_id IS '获奖时角色ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.summary_id IS '成绩汇总ID(来源于question.gd_score_summary)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.grading_task_id IS '批改任务ID(来源于question.gd_grading_task)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.score_snapshot IS '成绩快照分数';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.grade_level IS '成绩快照等级';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.surpass_ratio IS '成绩快照超越比例';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.used_seconds IS '成绩快照耗时(秒)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.award_source IS '发放来源(RULE_ENGINE/MANUAL/SYSTEM)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.award_reason IS '发放原因';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.evidence_json IS '发放证据JSON(可存规则命中明细)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.achieved_count IS '累计达成次数';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.first_achieved_at IS '首次达成时间';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.last_achieved_at IS '最近达成时间';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement.updated_at IS '更新时间';
|
||||
DROP TABLE IF EXISTS achievement.ac_user_achievement_progress CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS achievement.ac_user_achievement_progress (
|
||||
progress_id VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
achievement_id VARCHAR(64) NOT NULL,
|
||||
role_id VARCHAR(64) NOT NULL,
|
||||
current_value NUMERIC(12,2) NOT NULL DEFAULT 0,
|
||||
target_value NUMERIC(12,2) NOT NULL,
|
||||
progress_rate NUMERIC(5,2) NOT NULL DEFAULT 0,
|
||||
effort_index NUMERIC(6,2) NOT NULL DEFAULT 0,
|
||||
improvement_rate NUMERIC(6,2) NOT NULL DEFAULT 0,
|
||||
resilience_index NUMERIC(6,2) NOT NULL DEFAULT 0,
|
||||
streak_days INTEGER NOT NULL DEFAULT 0,
|
||||
last_checkin_date DATE,
|
||||
progress_status VARCHAR(16) NOT NULL DEFAULT 'IN_PROGRESS',
|
||||
progress_snapshot_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
last_evaluated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
achieved_at TIMESTAMP,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_ac_user_achievement_progress_user_achievement_role
|
||||
UNIQUE (user_id, achievement_id, role_id),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_current
|
||||
CHECK (current_value >= 0),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_target
|
||||
CHECK (target_value > 0),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_rate
|
||||
CHECK (progress_rate >= 0 AND progress_rate <= 100),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_effort_index
|
||||
CHECK (effort_index >= 0 AND effort_index <= 100),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_improvement_rate
|
||||
CHECK (improvement_rate >= -100 AND improvement_rate <= 1000),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_resilience_index
|
||||
CHECK (resilience_index >= 0 AND resilience_index <= 100),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_streak_days
|
||||
CHECK (streak_days >= 0),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_status
|
||||
CHECK (progress_status IN ('IN_PROGRESS', 'ACHIEVED', 'EXPIRED')),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_snapshot_json
|
||||
CHECK (jsonb_typeof(progress_snapshot_json) = 'object'),
|
||||
CONSTRAINT chk_ac_user_achievement_progress_achieved_at
|
||||
CHECK (
|
||||
(progress_status = 'ACHIEVED' AND achieved_at IS NOT NULL)
|
||||
OR (progress_status IN ('IN_PROGRESS', 'EXPIRED') AND achieved_at IS NULL)
|
||||
),
|
||||
CONSTRAINT fk_ac_user_achievement_progress_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_ac_user_achievement_progress_role_binding
|
||||
FOREIGN KEY (achievement_id, role_id) REFERENCES achievement.ac_achievement_role_rel(achievement_id, role_id)
|
||||
);
|
||||
COMMENT ON TABLE achievement.ac_user_achievement_progress IS '用户成就进度表(含未达成记录)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.progress_id IS '成就进度ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.achievement_id IS '成就ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.role_id IS '角色ID(学生/教师)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.current_value IS '当前进度值';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.target_value IS '目标值';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.progress_rate IS '完成率(0-100)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.effort_index IS '努力指数(0-100)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.improvement_rate IS '进步率(可为负)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.resilience_index IS '学习韧性指数(0-100)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.streak_days IS '连续打卡天数';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.last_checkin_date IS '最近打卡日期';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.progress_status IS '进度状态(IN_PROGRESS/ACHIEVED/EXPIRED)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.progress_snapshot_json IS '进度快照JSON(可记录最近一次评估明细)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.last_evaluated_at IS '最近评估时间';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.achieved_at IS '达成时间';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_progress.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS achievement.ac_user_achievement_daily_fact CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS achievement.ac_user_achievement_daily_fact (
|
||||
stat_date DATE NOT NULL,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
role_id VARCHAR(64) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
gained_achievement_count INTEGER NOT NULL DEFAULT 0,
|
||||
active_achievement_count INTEGER NOT NULL DEFAULT 0,
|
||||
progress_delta NUMERIC(10,4) NOT NULL DEFAULT 0,
|
||||
effort_index NUMERIC(6,2) NOT NULL DEFAULT 0,
|
||||
improvement_rate NUMERIC(6,2) NOT NULL DEFAULT 0,
|
||||
resilience_index NUMERIC(6,2) NOT NULL DEFAULT 0,
|
||||
streak_days INTEGER NOT NULL DEFAULT 0,
|
||||
learning_minutes INTEGER NOT NULL DEFAULT 0,
|
||||
checkin_status VARCHAR(16) NOT NULL DEFAULT 'NONE',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (stat_date, user_id, role_id),
|
||||
CONSTRAINT chk_ac_user_achievement_daily_fact_gained
|
||||
CHECK (gained_achievement_count >= 0),
|
||||
CONSTRAINT chk_ac_user_achievement_daily_fact_active
|
||||
CHECK (active_achievement_count >= 0),
|
||||
CONSTRAINT chk_ac_user_achievement_daily_fact_effort
|
||||
CHECK (effort_index >= 0 AND effort_index <= 100),
|
||||
CONSTRAINT chk_ac_user_achievement_daily_fact_improvement
|
||||
CHECK (improvement_rate >= -100 AND improvement_rate <= 1000),
|
||||
CONSTRAINT chk_ac_user_achievement_daily_fact_resilience
|
||||
CHECK (resilience_index >= 0 AND resilience_index <= 100),
|
||||
CONSTRAINT chk_ac_user_achievement_daily_fact_streak
|
||||
CHECK (streak_days >= 0),
|
||||
CONSTRAINT chk_ac_user_achievement_daily_fact_learning_minutes
|
||||
CHECK (learning_minutes >= 0),
|
||||
CONSTRAINT chk_ac_user_achievement_daily_fact_checkin
|
||||
CHECK (checkin_status IN ('NONE', 'CHECKED_IN', 'BROKEN')),
|
||||
CONSTRAINT fk_ac_user_achievement_daily_fact_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_ac_user_achievement_daily_fact_role
|
||||
FOREIGN KEY (role_id) REFERENCES upms.tb_sys_role(role_id)
|
||||
);
|
||||
COMMENT ON TABLE achievement.ac_user_achievement_daily_fact IS '用户成就激励日事实表(激励数据即成就数据)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.stat_date IS '统计日期';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.gained_achievement_count IS '当日新增成就数';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.active_achievement_count IS '当日活跃成就数';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.progress_delta IS '当日进度变化量';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.effort_index IS '当日努力指数(0-100)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.improvement_rate IS '当日进步率(可为负)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.resilience_index IS '当日学习韧性指数(0-100)';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.streak_days IS '当日连续打卡天数';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.learning_minutes IS '当日学习分钟数';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.checkin_status IS '打卡状态';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS achievement.ac_achievement_rule_template CASCADE;
|
||||
DROP TABLE IF EXISTS achievement.ac_achievement_metric_def CASCADE;
|
||||
DROP TABLE IF EXISTS achievement.ac_achievement_event_dict CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS achievement.ac_achievement_event_dict (
|
||||
event_code VARCHAR(64) PRIMARY KEY,
|
||||
event_name VARCHAR(128) NOT NULL,
|
||||
event_domain VARCHAR(32) NOT NULL DEFAULT 'LEARNING_LOOP',
|
||||
event_desc TEXT,
|
||||
payload_schema_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_ac_achievement_event_dict_domain
|
||||
CHECK (event_domain IN ('LEARNING_LOOP', 'GRADING', 'REVIEW', 'CHECKIN', 'SYSTEM')),
|
||||
CONSTRAINT chk_ac_achievement_event_dict_payload_json
|
||||
CHECK (jsonb_typeof(payload_schema_json) = 'object')
|
||||
);
|
||||
COMMENT ON TABLE achievement.ac_achievement_event_dict IS '成就事件字典表(通用触发事件定义)';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.event_code IS '事件编码';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.event_name IS '事件名称';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.event_domain IS '事件域';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.event_desc IS '事件描述';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.payload_schema_json IS '事件载荷结构定义JSON';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.enabled IS '是否启用';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_event_dict.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS achievement.ac_achievement_metric_def (
|
||||
metric_id VARCHAR(64) PRIMARY KEY,
|
||||
metric_code VARCHAR(64) NOT NULL,
|
||||
metric_name VARCHAR(128) NOT NULL,
|
||||
metric_type VARCHAR(16) NOT NULL DEFAULT 'NUM',
|
||||
metric_unit VARCHAR(32),
|
||||
source_domain VARCHAR(32) NOT NULL DEFAULT 'SYSTEM',
|
||||
agg_method VARCHAR(16) NOT NULL DEFAULT 'LATEST',
|
||||
default_value_num NUMERIC(18,6),
|
||||
default_value_text VARCHAR(255),
|
||||
expr_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_ac_achievement_metric_def_tenant_code
|
||||
UNIQUE (tenant_id, metric_code),
|
||||
CONSTRAINT chk_ac_achievement_metric_def_type
|
||||
CHECK (metric_type IN ('NUM', 'BOOL', 'TEXT', 'RATE', 'COUNT')),
|
||||
CONSTRAINT chk_ac_achievement_metric_def_source_domain
|
||||
CHECK (source_domain IN ('COURSE', 'HOMEWORK', 'GRADING', 'REVIEW', 'RECOMMENDATION', 'ACHIEVEMENT', 'SYSTEM')),
|
||||
CONSTRAINT chk_ac_achievement_metric_def_agg_method
|
||||
CHECK (agg_method IN ('LATEST', 'SUM', 'AVG', 'MAX', 'MIN', 'COUNT')),
|
||||
CONSTRAINT chk_ac_achievement_metric_def_expr_json
|
||||
CHECK (jsonb_typeof(expr_json) = 'object')
|
||||
);
|
||||
COMMENT ON TABLE achievement.ac_achievement_metric_def IS '成就指标定义表(通用指标注册)';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_id IS '指标ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_code IS '指标编码';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_name IS '指标名称';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_type IS '指标类型';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_unit IS '指标单位';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.source_domain IS '指标来源域';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.agg_method IS '聚合方式';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.default_value_num IS '默认数值';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.default_value_text IS '默认文本值';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.expr_json IS '计算表达式JSON';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.enabled IS '是否启用';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_metric_def.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS achievement.ac_achievement_rule_template (
|
||||
template_id VARCHAR(64) PRIMARY KEY,
|
||||
template_code VARCHAR(64) NOT NULL,
|
||||
template_name VARCHAR(128) NOT NULL,
|
||||
event_code VARCHAR(64) NOT NULL,
|
||||
role_id VARCHAR(64),
|
||||
achievement_category VARCHAR(32) NOT NULL DEFAULT 'PROGRESS',
|
||||
rule_condition_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
reward_action_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
priority INTEGER NOT NULL DEFAULT 100,
|
||||
template_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
valid_from TIMESTAMP,
|
||||
valid_to TIMESTAMP,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_by VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_ac_achievement_rule_template_tenant_code
|
||||
UNIQUE (tenant_id, template_code),
|
||||
CONSTRAINT chk_ac_achievement_rule_template_category
|
||||
CHECK (achievement_category IN ('SCORE', 'PROGRESS', 'TEACHING', 'ENGAGEMENT', 'MANUAL')),
|
||||
CONSTRAINT chk_ac_achievement_rule_template_status
|
||||
CHECK (template_status IN ('ACTIVE', 'INACTIVE', 'ARCHIVED')),
|
||||
CONSTRAINT chk_ac_achievement_rule_template_condition_json
|
||||
CHECK (jsonb_typeof(rule_condition_json) = 'object'),
|
||||
CONSTRAINT chk_ac_achievement_rule_template_reward_json
|
||||
CHECK (jsonb_typeof(reward_action_json) = 'object'),
|
||||
CONSTRAINT chk_ac_achievement_rule_template_valid
|
||||
CHECK (valid_to IS NULL OR valid_from IS NULL OR valid_to >= valid_from),
|
||||
CONSTRAINT fk_ac_achievement_rule_template_event
|
||||
FOREIGN KEY (event_code) REFERENCES achievement.ac_achievement_event_dict(event_code),
|
||||
CONSTRAINT fk_ac_achievement_rule_template_role
|
||||
FOREIGN KEY (role_id) REFERENCES upms.tb_sys_role(role_id),
|
||||
CONSTRAINT fk_ac_achievement_rule_template_created_by
|
||||
FOREIGN KEY (created_by) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE achievement.ac_achievement_rule_template IS '成就规则模板表(通用配置驱动)';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.template_id IS '模板ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.template_code IS '模板编码';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.template_name IS '模板名称';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.event_code IS '触发事件编码';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.role_id IS '适用角色ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.achievement_category IS '成就类别';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.rule_condition_json IS '规则条件JSON';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.reward_action_json IS '奖励动作JSON';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.priority IS '优先级';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.template_status IS '模板状态';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.valid_from IS '生效开始时间';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.valid_to IS '生效结束时间';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.created_by IS '创建人';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN achievement.ac_achievement_rule_template.updated_at IS '更新时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_achievement_tenant_status
|
||||
ON achievement.ac_achievement(tenant_id, status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_achievement_role_rel_role
|
||||
ON achievement.ac_achievement_role_rel(role_id, tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_user_role
|
||||
ON achievement.ac_user_achievement(user_id, role_id, last_achieved_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_tenant_time
|
||||
ON achievement.ac_user_achievement(tenant_id, last_achieved_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_summary
|
||||
ON achievement.ac_user_achievement(summary_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_task
|
||||
ON achievement.ac_user_achievement(grading_task_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_progress_user_status
|
||||
ON achievement.ac_user_achievement_progress(user_id, role_id, progress_status, last_evaluated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_progress_tenant_status
|
||||
ON achievement.ac_user_achievement_progress(tenant_id, progress_status, last_evaluated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_progress_achievement
|
||||
ON achievement.ac_user_achievement_progress(achievement_id, role_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_progress_streak
|
||||
ON achievement.ac_user_achievement_progress(user_id, role_id, streak_days DESC, last_evaluated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_daily_fact_tenant_date
|
||||
ON achievement.ac_user_achievement_daily_fact(tenant_id, stat_date DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_daily_fact_user_date
|
||||
ON achievement.ac_user_achievement_daily_fact(user_id, role_id, stat_date DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_achievement_event_dict_tenant_enabled
|
||||
ON achievement.ac_achievement_event_dict(tenant_id, enabled, event_domain);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_achievement_metric_def_tenant_enabled
|
||||
ON achievement.ac_achievement_metric_def(tenant_id, enabled, source_domain);
|
||||
CREATE INDEX IF NOT EXISTS idx_ac_achievement_rule_template_tenant_event
|
||||
ON achievement.ac_achievement_rule_template(tenant_id, event_code, template_status, priority);
|
||||
2
init/pg/achievement/20_init_achievement_seed.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- 成就模块初始化数据占位
|
||||
-- 按需补充学生成就、教师成就及角色绑定关系种子数据
|
||||
@@ -1,47 +1,215 @@
|
||||
CREATE TABLE IF NOT EXISTS ai.tb_ai_model_config (
|
||||
model_id VARCHAR(64) PRIMARY KEY,
|
||||
model_name VARCHAR(128) NOT NULL,
|
||||
provider VARCHAR(64) NOT NULL,
|
||||
endpoint VARCHAR(255),
|
||||
adcode VARCHAR(12),
|
||||
tenant_id VARCHAR(64),
|
||||
tenant_path VARCHAR(255),
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE ai.tb_ai_model_config IS 'AI模型配置表';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.model_id IS '模型ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.model_name IS '模型名称';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.provider IS '模型提供商';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.endpoint IS '模型服务地址';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_model_config.created_at IS '创建时间';
|
||||
DROP SCHEMA IF EXISTS ai CASCADE;
|
||||
CREATE SCHEMA IF NOT EXISTS ai;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ai.tb_ai_task_log (
|
||||
task_id VARCHAR(64) PRIMARY KEY,
|
||||
task_type VARCHAR(64) NOT NULL,
|
||||
task_status VARCHAR(32) NOT NULL,
|
||||
DROP TABLE IF EXISTS ai.tb_ai_knowledge_file CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS ai.tb_ai_knowledge_file (
|
||||
file_id VARCHAR(64) PRIMARY KEY,
|
||||
file_code VARCHAR(64) UNIQUE NOT NULL,
|
||||
file_name VARCHAR(255) NOT NULL,
|
||||
source_file_id VARCHAR(64),
|
||||
file_type VARCHAR(32) NOT NULL DEFAULT 'DOCUMENT',
|
||||
file_status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
|
||||
graph_sync_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
vector_sync_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
graph_doc_ref VARCHAR(128),
|
||||
vector_doc_ref VARCHAR(128),
|
||||
content_checksum VARCHAR(128),
|
||||
metadata_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
adcode VARCHAR(12),
|
||||
tenant_id VARCHAR(64),
|
||||
tenant_path VARCHAR(255),
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
payload_json TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_ai_knowledge_file_type
|
||||
CHECK (file_type IN ('DOCUMENT', 'PDF', 'IMAGE', 'OTHER')),
|
||||
CONSTRAINT chk_ai_knowledge_file_status
|
||||
CHECK (file_status IN ('ACTIVE', 'DISABLED')),
|
||||
CONSTRAINT chk_ai_knowledge_file_graph_status
|
||||
CHECK (graph_sync_status IN ('PENDING', 'SYNCED', 'FAILED')),
|
||||
CONSTRAINT chk_ai_knowledge_file_vector_status
|
||||
CHECK (vector_sync_status IN ('PENDING', 'SYNCED', 'FAILED')),
|
||||
CONSTRAINT chk_ai_knowledge_file_metadata_json
|
||||
CHECK (jsonb_typeof(metadata_json) = 'object'),
|
||||
CONSTRAINT fk_ai_knowledge_file_source_file
|
||||
FOREIGN KEY (source_file_id) REFERENCES upms.tb_sys_file(file_id)
|
||||
);
|
||||
COMMENT ON TABLE ai.tb_ai_task_log IS 'AI任务日志表';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.task_id IS '任务ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.task_type IS '任务类型';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.task_status IS '任务状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.payload_json IS '任务载荷JSON';
|
||||
COMMENT ON COLUMN ai.tb_ai_task_log.created_at IS '创建时间';
|
||||
COMMENT ON TABLE ai.tb_ai_knowledge_file IS '知识文件主表(统一管理图谱和向量知识库入库对象)';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_id IS '知识文件ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_code IS '知识文件编码';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_name IS '知识文件名称';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.source_file_id IS '来源文件ID(upms.tb_sys_file.file_id)';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_type IS '文件类型';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_status IS '文件状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.graph_sync_status IS '图谱同步状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.vector_sync_status IS '向量库同步状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.graph_doc_ref IS '图谱侧文档/对象引用';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.vector_doc_ref IS '向量库侧文档/对象引用';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.content_checksum IS '内容校验值';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.metadata_json IS '文件元数据JSON';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_file.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS ai.tb_ai_knowledge_sync_task CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS ai.tb_ai_knowledge_sync_task (
|
||||
task_id VARCHAR(64) PRIMARY KEY,
|
||||
file_id VARCHAR(64) NOT NULL,
|
||||
target_store VARCHAR(16) NOT NULL,
|
||||
task_type VARCHAR(16) NOT NULL DEFAULT 'UPSERT',
|
||||
task_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
error_message TEXT,
|
||||
payload_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
adcode VARCHAR(12),
|
||||
tenant_id VARCHAR(64),
|
||||
tenant_path VARCHAR(255),
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_ai_knowledge_sync_task_store
|
||||
CHECK (target_store IN ('GRAPH', 'VECTOR')),
|
||||
CONSTRAINT chk_ai_knowledge_sync_task_type
|
||||
CHECK (task_type IN ('UPSERT', 'DELETE', 'REBUILD')),
|
||||
CONSTRAINT chk_ai_knowledge_sync_task_status
|
||||
CHECK (task_status IN ('PENDING', 'RUNNING', 'SUCCESS', 'FAILED')),
|
||||
CONSTRAINT chk_ai_knowledge_sync_task_retry
|
||||
CHECK (retry_count >= 0),
|
||||
CONSTRAINT chk_ai_knowledge_sync_task_payload_json
|
||||
CHECK (jsonb_typeof(payload_json) = 'object'),
|
||||
CONSTRAINT fk_ai_knowledge_sync_task_file
|
||||
FOREIGN KEY (file_id) REFERENCES ai.tb_ai_knowledge_file(file_id) ON DELETE CASCADE
|
||||
);
|
||||
COMMENT ON TABLE ai.tb_ai_knowledge_sync_task IS '知识文件同步任务表(同步到图谱或向量知识库)';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.task_id IS '同步任务ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.file_id IS '知识文件ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.target_store IS '目标存储(GRAPH/VECTOR)';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.task_type IS '任务类型(UPSERT/DELETE/REBUILD)';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.task_status IS '任务状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.retry_count IS '重试次数';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.error_message IS '错误信息';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.payload_json IS '任务载荷JSON';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS ai.tb_ai_graph_entity CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS ai.tb_ai_graph_entity (
|
||||
entity_id VARCHAR(64) PRIMARY KEY,
|
||||
file_id VARCHAR(64),
|
||||
entity_type VARCHAR(64) NOT NULL,
|
||||
entity_name VARCHAR(255) NOT NULL,
|
||||
normalized_name VARCHAR(255),
|
||||
graph_vertex_id VARCHAR(128),
|
||||
entity_status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
|
||||
sync_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
properties_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
adcode VARCHAR(12),
|
||||
tenant_id VARCHAR(64),
|
||||
tenant_path VARCHAR(255),
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uk_ai_graph_entity_file_name_type
|
||||
UNIQUE (file_id, entity_name, entity_type),
|
||||
CONSTRAINT chk_ai_graph_entity_status
|
||||
CHECK (entity_status IN ('ACTIVE', 'DISABLED')),
|
||||
CONSTRAINT chk_ai_graph_entity_sync_status
|
||||
CHECK (sync_status IN ('PENDING', 'SYNCED', 'FAILED')),
|
||||
CONSTRAINT chk_ai_graph_entity_properties_json
|
||||
CHECK (jsonb_typeof(properties_json) = 'object'),
|
||||
CONSTRAINT fk_ai_graph_entity_file
|
||||
FOREIGN KEY (file_id) REFERENCES ai.tb_ai_knowledge_file(file_id) ON DELETE SET NULL
|
||||
);
|
||||
COMMENT ON TABLE ai.tb_ai_graph_entity IS '图谱实体表(用于实体绑定)';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.entity_id IS '实体ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.file_id IS '来源知识文件ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.entity_type IS '实体类型';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.entity_name IS '实体名称';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.normalized_name IS '标准化实体名称';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.graph_vertex_id IS '图数据库顶点ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.entity_status IS '实体状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.sync_status IS '同步状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.properties_json IS '实体属性JSON';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_entity.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS ai.tb_ai_graph_relation CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS ai.tb_ai_graph_relation (
|
||||
relation_id VARCHAR(64) PRIMARY KEY,
|
||||
src_entity_id VARCHAR(64) NOT NULL,
|
||||
dst_entity_id VARCHAR(64) NOT NULL,
|
||||
relation_type VARCHAR(64) NOT NULL,
|
||||
relation_name VARCHAR(128),
|
||||
graph_edge_id VARCHAR(128),
|
||||
relation_weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
relation_status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
|
||||
sync_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
properties_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
adcode VARCHAR(12),
|
||||
tenant_id VARCHAR(64),
|
||||
tenant_path VARCHAR(255),
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uk_ai_graph_relation_src_dst_type
|
||||
UNIQUE (src_entity_id, dst_entity_id, relation_type),
|
||||
CONSTRAINT chk_ai_graph_relation_src_dst
|
||||
CHECK (src_entity_id <> dst_entity_id),
|
||||
CONSTRAINT chk_ai_graph_relation_weight
|
||||
CHECK (relation_weight > 0 AND relation_weight <= 1),
|
||||
CONSTRAINT chk_ai_graph_relation_status
|
||||
CHECK (relation_status IN ('ACTIVE', 'DISABLED')),
|
||||
CONSTRAINT chk_ai_graph_relation_sync_status
|
||||
CHECK (sync_status IN ('PENDING', 'SYNCED', 'FAILED')),
|
||||
CONSTRAINT chk_ai_graph_relation_properties_json
|
||||
CHECK (jsonb_typeof(properties_json) = 'object'),
|
||||
CONSTRAINT fk_ai_graph_relation_src
|
||||
FOREIGN KEY (src_entity_id) REFERENCES ai.tb_ai_graph_entity(entity_id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_ai_graph_relation_dst
|
||||
FOREIGN KEY (dst_entity_id) REFERENCES ai.tb_ai_graph_entity(entity_id) ON DELETE CASCADE
|
||||
);
|
||||
COMMENT ON TABLE ai.tb_ai_graph_relation IS '图谱实体关联表(用于实体关系绑定)';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_id IS '关系ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.src_entity_id IS '源实体ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.dst_entity_id IS '目标实体ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_type IS '关系类型';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_name IS '关系名称';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.graph_edge_id IS '图数据库边ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_weight IS '关系权重';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_status IS '关系状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.sync_status IS '同步状态';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.properties_json IS '关系属性JSON';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN ai.tb_ai_graph_relation.updated_at IS '更新时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_knowledge_file_sync_status
|
||||
ON ai.tb_ai_knowledge_file(graph_sync_status, vector_sync_status, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_knowledge_sync_task_status
|
||||
ON ai.tb_ai_knowledge_sync_task(task_status, target_store, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_graph_entity_name
|
||||
ON ai.tb_ai_graph_entity(entity_type, entity_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_graph_relation_src_dst
|
||||
ON ai.tb_ai_graph_relation(src_entity_id, dst_entity_id, relation_type);
|
||||
|
||||
@@ -1,6 +1,60 @@
|
||||
INSERT INTO ai.tb_ai_model_config (
|
||||
model_id, model_name, provider, endpoint, adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
INSERT INTO ai.tb_ai_knowledge_file (
|
||||
file_id, file_code, file_name, file_type, file_status,
|
||||
graph_sync_status, vector_sync_status, metadata_json,
|
||||
adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
) VALUES (
|
||||
'MODEL-MOCK-001', 'mock-python-ai', 'local', 'http://localhost:9000', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
|
||||
'KF-DEMO-001', 'K12_DEMO_KNOWLEDGE_FILE', 'K12示例知识文件', 'DOCUMENT', 'ACTIVE',
|
||||
'PENDING', 'PENDING', '{"source":"seed","scenario":"knowledge_file_management"}'::JSONB,
|
||||
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
|
||||
)
|
||||
ON CONFLICT (model_id) DO NOTHING;
|
||||
ON CONFLICT (file_id) DO NOTHING;
|
||||
|
||||
INSERT INTO ai.tb_ai_graph_entity (
|
||||
entity_id, file_id, entity_type, entity_name, normalized_name, sync_status, properties_json,
|
||||
adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
) VALUES (
|
||||
'GE-KP-001', 'KF-DEMO-001', 'KNOWLEDGE_POINT', '一次函数', '一次函数', 'PENDING',
|
||||
'{"subject":"math","grade":"7"}'::JSONB,
|
||||
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
|
||||
)
|
||||
ON CONFLICT (entity_id) DO NOTHING;
|
||||
|
||||
INSERT INTO ai.tb_ai_graph_entity (
|
||||
entity_id, file_id, entity_type, entity_name, normalized_name, sync_status, properties_json,
|
||||
adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
) VALUES (
|
||||
'GE-QUESTION-001', 'KF-DEMO-001', 'QUESTION', '一次函数应用题', '一次函数应用题', 'PENDING',
|
||||
'{"difficulty":"MEDIUM"}'::JSONB,
|
||||
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
|
||||
)
|
||||
ON CONFLICT (entity_id) DO NOTHING;
|
||||
|
||||
INSERT INTO ai.tb_ai_graph_relation (
|
||||
relation_id, src_entity_id, dst_entity_id, relation_type, relation_name, relation_weight, sync_status, properties_json,
|
||||
adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
) VALUES (
|
||||
'GR-001', 'GE-QUESTION-001', 'GE-KP-001', 'BELONGS_TO', '题目关联知识点', 1.0000, 'PENDING',
|
||||
'{"source":"manual_binding"}'::JSONB,
|
||||
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
|
||||
)
|
||||
ON CONFLICT (relation_id) DO NOTHING;
|
||||
|
||||
INSERT INTO ai.tb_ai_knowledge_sync_task (
|
||||
task_id, file_id, target_store, task_type, task_status, payload_json,
|
||||
adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
) VALUES (
|
||||
'KST-GRAPH-001', 'KF-DEMO-001', 'GRAPH', 'UPSERT', 'PENDING',
|
||||
'{"trigger":"seed","description":"sync to graph store"}'::JSONB,
|
||||
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
|
||||
)
|
||||
ON CONFLICT (task_id) DO NOTHING;
|
||||
|
||||
INSERT INTO ai.tb_ai_knowledge_sync_task (
|
||||
task_id, file_id, target_store, task_type, task_status, payload_json,
|
||||
adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
) VALUES (
|
||||
'KST-VECTOR-001', 'KF-DEMO-001', 'VECTOR', 'UPSERT', 'PENDING',
|
||||
'{"trigger":"seed","description":"sync to vector store"}'::JSONB,
|
||||
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
|
||||
)
|
||||
ON CONFLICT (task_id) DO NOTHING;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
DROP SCHEMA IF EXISTS auth CASCADE;
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
|
||||
DROP TABLE IF EXISTS auth.tb_auth_refresh_token CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS auth.tb_auth_refresh_token (
|
||||
token_id VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
@@ -26,6 +30,7 @@ COMMENT ON COLUMN auth.tb_auth_refresh_token.expire_at IS '过期时间';
|
||||
COMMENT ON COLUMN auth.tb_auth_refresh_token.revoked IS '是否撤销';
|
||||
COMMENT ON COLUMN auth.tb_auth_refresh_token.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS auth.tb_auth_login_audit CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS auth.tb_auth_login_audit (
|
||||
audit_id VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(64),
|
||||
|
||||
512
init/pg/course/10_create_course_tables.sql
Normal file
@@ -0,0 +1,512 @@
|
||||
DROP SCHEMA IF EXISTS course CASCADE;
|
||||
CREATE SCHEMA IF NOT EXISTS course;
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_course CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_course (
|
||||
course_id VARCHAR(64) PRIMARY KEY,
|
||||
title VARCHAR(256) NOT NULL,
|
||||
subject_code VARCHAR(32) NOT NULL,
|
||||
grade_code VARCHAR(32) NOT NULL,
|
||||
difficulty_level VARCHAR(16),
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'DRAFT',
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_by VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE course.cl_course IS '课程主表';
|
||||
COMMENT ON COLUMN course.cl_course.course_id IS '课程ID';
|
||||
COMMENT ON COLUMN course.cl_course.title IS '课程标题';
|
||||
COMMENT ON COLUMN course.cl_course.subject_code IS '学科编码';
|
||||
COMMENT ON COLUMN course.cl_course.grade_code IS '年级编码';
|
||||
COMMENT ON COLUMN course.cl_course.difficulty_level IS '难度等级';
|
||||
COMMENT ON COLUMN course.cl_course.status IS '状态(DRAFT/PUBLISHED/ARCHIVED)';
|
||||
COMMENT ON COLUMN course.cl_course.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN course.cl_course.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_course.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN course.cl_course.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN course.cl_course.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN course.cl_course.created_by IS '创建人ID';
|
||||
COMMENT ON COLUMN course.cl_course.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_course_chapter CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_course_chapter (
|
||||
chapter_id VARCHAR(64) PRIMARY KEY,
|
||||
course_id VARCHAR(64) NOT NULL,
|
||||
chapter_no INTEGER NOT NULL,
|
||||
chapter_title VARCHAR(256) NOT NULL,
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_cl_course_chapter_course
|
||||
FOREIGN KEY (course_id) REFERENCES course.cl_course(course_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_course_chapter IS '课程章节表';
|
||||
COMMENT ON COLUMN course.cl_course_chapter.chapter_id IS '章节ID';
|
||||
COMMENT ON COLUMN course.cl_course_chapter.course_id IS '课程ID';
|
||||
COMMENT ON COLUMN course.cl_course_chapter.chapter_no IS '章节序号';
|
||||
COMMENT ON COLUMN course.cl_course_chapter.chapter_title IS '章节标题';
|
||||
COMMENT ON COLUMN course.cl_course_chapter.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN course.cl_course_chapter.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_course_chapter.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN course.cl_course_chapter.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_course_node CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_course_node (
|
||||
node_id VARCHAR(64) PRIMARY KEY,
|
||||
chapter_id VARCHAR(64) NOT NULL,
|
||||
node_no INTEGER NOT NULL,
|
||||
node_title VARCHAR(256) NOT NULL,
|
||||
node_type VARCHAR(32) NOT NULL DEFAULT 'LESSON',
|
||||
class_type VARCHAR(32),
|
||||
duration_sec INTEGER,
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_cl_course_node_type
|
||||
CHECK (node_type IN ('LESSON', 'IN_CLASS_ACTIVITY', 'AFTER_CLASS_TASK', 'MATERIAL', 'OTHER')),
|
||||
CONSTRAINT fk_cl_course_node_chapter
|
||||
FOREIGN KEY (chapter_id) REFERENCES course.cl_course_chapter(chapter_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_course_node IS '课程学习节点表';
|
||||
COMMENT ON COLUMN course.cl_course_node.node_id IS '学习节点ID';
|
||||
COMMENT ON COLUMN course.cl_course_node.chapter_id IS '章节ID';
|
||||
COMMENT ON COLUMN course.cl_course_node.node_no IS '节点序号';
|
||||
COMMENT ON COLUMN course.cl_course_node.node_title IS '节点标题';
|
||||
COMMENT ON COLUMN course.cl_course_node.node_type IS '节点类型(LESSON/IN_CLASS_ACTIVITY/AFTER_CLASS_TASK/MATERIAL/OTHER)';
|
||||
COMMENT ON COLUMN course.cl_course_node.class_type IS '课堂类型(如讲授/讨论/测验)';
|
||||
COMMENT ON COLUMN course.cl_course_node.duration_sec IS '时长(秒)';
|
||||
COMMENT ON COLUMN course.cl_course_node.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN course.cl_course_node.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_course_node.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN course.cl_course_node.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_knowledge_point CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_knowledge_point (
|
||||
kp_id VARCHAR(64) PRIMARY KEY,
|
||||
kp_code VARCHAR(64) NOT NULL,
|
||||
kp_name VARCHAR(256) NOT NULL,
|
||||
kp_alias VARCHAR(256),
|
||||
kp_type VARCHAR(32) NOT NULL DEFAULT 'ATOMIC',
|
||||
parent_kp_id VARCHAR(64),
|
||||
kp_path VARCHAR(512),
|
||||
kp_desc TEXT,
|
||||
subject_code VARCHAR(32) NOT NULL,
|
||||
grade_code VARCHAR(32) NOT NULL,
|
||||
difficulty_level VARCHAR(16),
|
||||
importance_level NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
graph_entity_id VARCHAR(64),
|
||||
source_db VARCHAR(64) NOT NULL DEFAULT 'postgresql',
|
||||
source_table VARCHAR(128) NOT NULL DEFAULT 'course.cl_knowledge_point',
|
||||
source_pk VARCHAR(128),
|
||||
metadata_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_cl_knowledge_point_tenant_code
|
||||
UNIQUE (tenant_id, kp_code),
|
||||
CONSTRAINT uq_cl_knowledge_point_tenant_graph_entity
|
||||
UNIQUE (tenant_id, graph_entity_id),
|
||||
CONSTRAINT chk_cl_knowledge_point_type
|
||||
CHECK (kp_type IN ('DOMAIN', 'TOPIC', 'ATOMIC')),
|
||||
CONSTRAINT chk_cl_knowledge_point_importance
|
||||
CHECK (importance_level > 0 AND importance_level <= 1),
|
||||
CONSTRAINT chk_cl_knowledge_point_metadata_json
|
||||
CHECK (jsonb_typeof(metadata_json) = 'object'),
|
||||
CONSTRAINT fk_cl_knowledge_point_parent
|
||||
FOREIGN KEY (parent_kp_id) REFERENCES course.cl_knowledge_point(kp_id) ON DELETE SET NULL
|
||||
);
|
||||
COMMENT ON TABLE course.cl_knowledge_point IS '知识点表';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.kp_id IS '知识点ID';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.kp_code IS '知识点编码';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.kp_name IS '知识点名称';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.kp_alias IS '知识点别名';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.kp_type IS '知识点类型(DOMAIN/TOPIC/ATOMIC)';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.parent_kp_id IS '父知识点ID';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.kp_path IS '知识点树路径';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.kp_desc IS '知识点描述';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.subject_code IS '学科编码';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.grade_code IS '年级编码';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.difficulty_level IS '难度等级';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.importance_level IS '重要性权重(0,1]';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.graph_entity_id IS '图谱实体ID(Nebula/Neo4j 映射)';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.source_db IS '来源数据库';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.source_table IS '来源表';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.source_pk IS '来源主键';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.metadata_json IS '扩展元数据JSON';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.status IS '状态(ACTIVE/DISABLED)';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN course.cl_knowledge_point.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_chapter_kp_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_chapter_kp_rel (
|
||||
chapter_id VARCHAR(64) NOT NULL,
|
||||
kp_id VARCHAR(64) NOT NULL,
|
||||
relation_type VARCHAR(32) NOT NULL DEFAULT 'TEACHES',
|
||||
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
is_core BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (chapter_id, kp_id),
|
||||
CONSTRAINT chk_cl_chapter_kp_rel_type
|
||||
CHECK (relation_type IN ('TEACHES', 'REVIEWS', 'EXTENDS')),
|
||||
CONSTRAINT chk_cl_chapter_kp_rel_weight
|
||||
CHECK (weight > 0 AND weight <= 1),
|
||||
CONSTRAINT fk_cl_chapter_kp_rel_chapter
|
||||
FOREIGN KEY (chapter_id) REFERENCES course.cl_course_chapter(chapter_id),
|
||||
CONSTRAINT fk_cl_chapter_kp_rel_kp
|
||||
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_chapter_kp_rel IS '章节-知识点关联表';
|
||||
COMMENT ON COLUMN course.cl_chapter_kp_rel.chapter_id IS '章节ID';
|
||||
COMMENT ON COLUMN course.cl_chapter_kp_rel.kp_id IS '知识点ID';
|
||||
COMMENT ON COLUMN course.cl_chapter_kp_rel.relation_type IS '关系类型(TEACHES/REVIEWS/EXTENDS)';
|
||||
COMMENT ON COLUMN course.cl_chapter_kp_rel.weight IS '关联权重(0,1]';
|
||||
COMMENT ON COLUMN course.cl_chapter_kp_rel.is_core IS '是否核心知识点';
|
||||
COMMENT ON COLUMN course.cl_chapter_kp_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_chapter_kp_rel.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN course.cl_chapter_kp_rel.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_student_kp_mastery CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_student_kp_mastery (
|
||||
mastery_id VARCHAR(64) PRIMARY KEY,
|
||||
student_id VARCHAR(64) NOT NULL,
|
||||
kp_id VARCHAR(64) NOT NULL,
|
||||
mastery_score NUMERIC(6,4) NOT NULL DEFAULT 0,
|
||||
mastery_level VARCHAR(16) NOT NULL DEFAULT 'UNKNOWN',
|
||||
mastery_status VARCHAR(16) NOT NULL DEFAULT 'LEARNING',
|
||||
evidence_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
last_source_type VARCHAR(32),
|
||||
last_source_id VARCHAR(64),
|
||||
last_assessed_at TIMESTAMP,
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_cl_student_kp_mastery_student_kp
|
||||
UNIQUE (student_id, kp_id),
|
||||
CONSTRAINT chk_cl_student_kp_mastery_score
|
||||
CHECK (mastery_score >= 0 AND mastery_score <= 1),
|
||||
CONSTRAINT chk_cl_student_kp_mastery_level
|
||||
CHECK (mastery_level IN ('UNKNOWN', 'LOW', 'MEDIUM', 'HIGH', 'MASTERED')),
|
||||
CONSTRAINT chk_cl_student_kp_mastery_status
|
||||
CHECK (mastery_status IN ('LEARNING', 'STABLE', 'FORGOTTEN')),
|
||||
CONSTRAINT chk_cl_student_kp_mastery_evidence_json
|
||||
CHECK (jsonb_typeof(evidence_json) = 'object'),
|
||||
CONSTRAINT fk_cl_student_kp_mastery_student
|
||||
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_cl_student_kp_mastery_kp
|
||||
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_student_kp_mastery IS '学生知识点掌握度表';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.mastery_id IS '掌握度记录ID';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.student_id IS '学生ID';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.kp_id IS '知识点ID';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.mastery_score IS '掌握度分值(0-1)';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.mastery_level IS '掌握等级';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.mastery_status IS '掌握状态(LEARNING/STABLE/FORGOTTEN)';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.evidence_json IS '证据数据JSON(错题、批改、学习行为等)';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.last_source_type IS '最近更新来源类型';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.last_source_id IS '最近更新来源ID';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.last_assessed_at IS '最近评估时间';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN course.cl_student_kp_mastery.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_kp_material_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_kp_material_rel (
|
||||
rel_id VARCHAR(64) PRIMARY KEY,
|
||||
kp_id VARCHAR(64) NOT NULL,
|
||||
material_type VARCHAR(32) NOT NULL,
|
||||
material_ref_id VARCHAR(64) NOT NULL,
|
||||
material_uri VARCHAR(512),
|
||||
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
source_table VARCHAR(128),
|
||||
source_pk VARCHAR(128),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_cl_kp_material_rel_type
|
||||
CHECK (material_type IN ('VECTOR_DOCUMENT', 'VECTOR_CHUNK', 'COURSE_RESOURCE', 'EXTERNAL_URI')),
|
||||
CONSTRAINT chk_cl_kp_material_rel_weight
|
||||
CHECK (weight > 0 AND weight <= 1),
|
||||
CONSTRAINT fk_cl_kp_material_rel_kp
|
||||
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_kp_material_rel IS '知识点-学习资料关联表';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.rel_id IS '关联ID';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.kp_id IS '知识点ID';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.material_type IS '资料类型(VECTOR_DOCUMENT/VECTOR_CHUNK/COURSE_RESOURCE/EXTERNAL_URI)';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.material_ref_id IS '资料对象ID';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.material_uri IS '资料URI';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.weight IS '关联权重(0,1]';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.source_table IS '来源表';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.source_pk IS '来源主键';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN course.cl_kp_material_rel.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_course_knowledge_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_course_knowledge_rel (
|
||||
course_id VARCHAR(64) NOT NULL,
|
||||
kp_id VARCHAR(64) NOT NULL,
|
||||
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (course_id, kp_id),
|
||||
CONSTRAINT chk_cl_course_knowledge_rel_weight
|
||||
CHECK (weight > 0 AND weight <= 1),
|
||||
CONSTRAINT fk_cl_course_knowledge_rel_course
|
||||
FOREIGN KEY (course_id) REFERENCES course.cl_course(course_id),
|
||||
CONSTRAINT fk_cl_course_knowledge_rel_kp
|
||||
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_course_knowledge_rel IS '课程-知识点关联表';
|
||||
COMMENT ON COLUMN course.cl_course_knowledge_rel.course_id IS '课程ID';
|
||||
COMMENT ON COLUMN course.cl_course_knowledge_rel.kp_id IS '知识点ID';
|
||||
COMMENT ON COLUMN course.cl_course_knowledge_rel.weight IS '关联权重(0,1])';
|
||||
COMMENT ON COLUMN course.cl_course_knowledge_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_course_knowledge_rel.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_course_tag CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_course_tag (
|
||||
tag_id VARCHAR(64) PRIMARY KEY,
|
||||
tag_name VARCHAR(128) NOT NULL,
|
||||
tag_type VARCHAR(32) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE course.cl_course_tag IS '课程标签表';
|
||||
COMMENT ON COLUMN course.cl_course_tag.tag_id IS '标签ID';
|
||||
COMMENT ON COLUMN course.cl_course_tag.tag_name IS '标签名称';
|
||||
COMMENT ON COLUMN course.cl_course_tag.tag_type IS '标签类型';
|
||||
COMMENT ON COLUMN course.cl_course_tag.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_course_tag.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_course_tag_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_course_tag_rel (
|
||||
course_id VARCHAR(64) NOT NULL,
|
||||
tag_id VARCHAR(64) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (course_id, tag_id),
|
||||
CONSTRAINT fk_cl_course_tag_rel_course
|
||||
FOREIGN KEY (course_id) REFERENCES course.cl_course(course_id),
|
||||
CONSTRAINT fk_cl_course_tag_rel_tag
|
||||
FOREIGN KEY (tag_id) REFERENCES course.cl_course_tag(tag_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_course_tag_rel IS '课程-标签关联表';
|
||||
COMMENT ON COLUMN course.cl_course_tag_rel.course_id IS '课程ID';
|
||||
COMMENT ON COLUMN course.cl_course_tag_rel.tag_id IS '标签ID';
|
||||
COMMENT ON COLUMN course.cl_course_tag_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_course_tag_rel.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_node_resource CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_node_resource (
|
||||
resource_id VARCHAR(64) PRIMARY KEY,
|
||||
node_id VARCHAR(64) NOT NULL,
|
||||
resource_type VARCHAR(32) NOT NULL,
|
||||
file_id VARCHAR(64),
|
||||
resource_url VARCHAR(512),
|
||||
resource_title VARCHAR(256),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_cl_node_resource_type
|
||||
CHECK (resource_type IN ('PDF', 'VIDEO', 'DOC', 'IMAGE', 'LINK', 'OTHER')),
|
||||
CONSTRAINT chk_cl_node_resource_ref
|
||||
CHECK (file_id IS NOT NULL OR resource_url IS NOT NULL),
|
||||
CONSTRAINT fk_cl_node_resource_node
|
||||
FOREIGN KEY (node_id) REFERENCES course.cl_course_node(node_id),
|
||||
CONSTRAINT fk_cl_node_resource_file
|
||||
FOREIGN KEY (file_id) REFERENCES upms.tb_sys_file(file_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_node_resource IS '学习节点资源表';
|
||||
COMMENT ON COLUMN course.cl_node_resource.resource_id IS '资源ID';
|
||||
COMMENT ON COLUMN course.cl_node_resource.node_id IS '学习节点ID';
|
||||
COMMENT ON COLUMN course.cl_node_resource.resource_type IS '资源类型(PDF/VIDEO/DOC/IMAGE/LINK/OTHER)';
|
||||
COMMENT ON COLUMN course.cl_node_resource.file_id IS '资源文件ID';
|
||||
COMMENT ON COLUMN course.cl_node_resource.resource_url IS '资源地址';
|
||||
COMMENT ON COLUMN course.cl_node_resource.resource_title IS '资源标题';
|
||||
COMMENT ON COLUMN course.cl_node_resource.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_node_resource.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_node_kp_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_node_kp_rel (
|
||||
node_id VARCHAR(64) NOT NULL,
|
||||
kp_id VARCHAR(64) NOT NULL,
|
||||
relation_type VARCHAR(32) NOT NULL DEFAULT 'TEACHES',
|
||||
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (node_id, kp_id),
|
||||
CONSTRAINT chk_cl_node_kp_rel_type
|
||||
CHECK (relation_type IN ('TEACHES', 'PRACTICES', 'REVIEWS')),
|
||||
CONSTRAINT chk_cl_node_kp_rel_weight
|
||||
CHECK (weight > 0 AND weight <= 1),
|
||||
CONSTRAINT fk_cl_node_kp_rel_node
|
||||
FOREIGN KEY (node_id) REFERENCES course.cl_course_node(node_id),
|
||||
CONSTRAINT fk_cl_node_kp_rel_kp
|
||||
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_node_kp_rel IS '学习节点-知识点关联表';
|
||||
COMMENT ON COLUMN course.cl_node_kp_rel.node_id IS '学习节点ID';
|
||||
COMMENT ON COLUMN course.cl_node_kp_rel.kp_id IS '知识点ID';
|
||||
COMMENT ON COLUMN course.cl_node_kp_rel.relation_type IS '关系类型(TEACHES/PRACTICES/REVIEWS)';
|
||||
COMMENT ON COLUMN course.cl_node_kp_rel.weight IS '关联权重(0,1]';
|
||||
COMMENT ON COLUMN course.cl_node_kp_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_node_kp_rel.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_node_homework_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_node_homework_rel (
|
||||
node_id VARCHAR(64) NOT NULL,
|
||||
assignment_id VARCHAR(64) NOT NULL,
|
||||
relation_type VARCHAR(32) NOT NULL DEFAULT 'AFTER_CLASS',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (node_id, assignment_id),
|
||||
CONSTRAINT chk_cl_node_homework_rel_type
|
||||
CHECK (relation_type IN ('IN_CLASS', 'AFTER_CLASS')),
|
||||
CONSTRAINT fk_cl_node_homework_rel_node
|
||||
FOREIGN KEY (node_id) REFERENCES course.cl_course_node(node_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_node_homework_rel IS '学习节点-作业关联表';
|
||||
COMMENT ON COLUMN course.cl_node_homework_rel.node_id IS '学习节点ID';
|
||||
COMMENT ON COLUMN course.cl_node_homework_rel.assignment_id IS '作业ID(question.hw_assignment.assignment_id)';
|
||||
COMMENT ON COLUMN course.cl_node_homework_rel.relation_type IS '关系类型(IN_CLASS/AFTER_CLASS)';
|
||||
COMMENT ON COLUMN course.cl_node_homework_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_node_homework_rel.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_learning_session CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_learning_session (
|
||||
session_id VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
course_id VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'STARTED',
|
||||
started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
ended_at TIMESTAMP,
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
CONSTRAINT fk_cl_learning_session_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_cl_learning_session_course
|
||||
FOREIGN KEY (course_id) REFERENCES course.cl_course(course_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_learning_session IS '学习会话表';
|
||||
COMMENT ON COLUMN course.cl_learning_session.session_id IS '会话ID';
|
||||
COMMENT ON COLUMN course.cl_learning_session.user_id IS '学员ID';
|
||||
COMMENT ON COLUMN course.cl_learning_session.course_id IS '课程ID';
|
||||
COMMENT ON COLUMN course.cl_learning_session.status IS '会话状态(STARTED/PAUSED/COMPLETED)';
|
||||
COMMENT ON COLUMN course.cl_learning_session.started_at IS '开始时间';
|
||||
COMMENT ON COLUMN course.cl_learning_session.ended_at IS '结束时间';
|
||||
COMMENT ON COLUMN course.cl_learning_session.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN course.cl_learning_session.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_learning_session.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN course.cl_learning_session.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN course.cl_learning_session.dept_path IS '部门路径';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_learning_progress CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_learning_progress (
|
||||
progress_id VARCHAR(64) PRIMARY KEY,
|
||||
session_id VARCHAR(64) NOT NULL,
|
||||
node_id VARCHAR(64) NOT NULL,
|
||||
progress_pct NUMERIC(5,2) NOT NULL DEFAULT 0,
|
||||
last_position_sec INTEGER NOT NULL DEFAULT 0,
|
||||
mastery_level VARCHAR(16),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_cl_learning_progress_session
|
||||
FOREIGN KEY (session_id) REFERENCES course.cl_learning_session(session_id),
|
||||
CONSTRAINT fk_cl_learning_progress_node
|
||||
FOREIGN KEY (node_id) REFERENCES course.cl_course_node(node_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_learning_progress IS '学习进度表';
|
||||
COMMENT ON COLUMN course.cl_learning_progress.progress_id IS '进度ID';
|
||||
COMMENT ON COLUMN course.cl_learning_progress.session_id IS '学习会话ID';
|
||||
COMMENT ON COLUMN course.cl_learning_progress.node_id IS '学习节点ID';
|
||||
COMMENT ON COLUMN course.cl_learning_progress.progress_pct IS '进度百分比';
|
||||
COMMENT ON COLUMN course.cl_learning_progress.last_position_sec IS '最后学习位置(秒)';
|
||||
COMMENT ON COLUMN course.cl_learning_progress.mastery_level IS '掌握等级';
|
||||
COMMENT ON COLUMN course.cl_learning_progress.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN course.cl_learning_progress.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS course.cl_learning_event CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS course.cl_learning_event (
|
||||
event_id VARCHAR(64) PRIMARY KEY,
|
||||
session_id VARCHAR(64) NOT NULL,
|
||||
event_type VARCHAR(32) NOT NULL,
|
||||
event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
payload_json JSONB,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
CONSTRAINT fk_cl_learning_event_session
|
||||
FOREIGN KEY (session_id) REFERENCES course.cl_learning_session(session_id)
|
||||
);
|
||||
COMMENT ON TABLE course.cl_learning_event IS '学习行为事件表';
|
||||
COMMENT ON COLUMN course.cl_learning_event.event_id IS '事件ID';
|
||||
COMMENT ON COLUMN course.cl_learning_event.session_id IS '会话ID';
|
||||
COMMENT ON COLUMN course.cl_learning_event.event_type IS '事件类型(start/pause/seek/finish)';
|
||||
COMMENT ON COLUMN course.cl_learning_event.event_time IS '事件时间';
|
||||
COMMENT ON COLUMN course.cl_learning_event.payload_json IS '事件扩展信息';
|
||||
COMMENT ON COLUMN course.cl_learning_event.tenant_id IS '租户ID';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_course_tenant_subject_grade
|
||||
ON course.cl_course(tenant_id, subject_code, grade_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_course_chapter_course_no
|
||||
ON course.cl_course_chapter(course_id, chapter_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_course_node_chapter_no
|
||||
ON course.cl_course_node(chapter_id, node_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_knowledge_point_subject_grade
|
||||
ON course.cl_knowledge_point(tenant_id, subject_code, grade_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_knowledge_point_parent
|
||||
ON course.cl_knowledge_point(tenant_id, parent_kp_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_knowledge_point_graph_entity
|
||||
ON course.cl_knowledge_point(tenant_id, graph_entity_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_chapter_kp_rel_chapter
|
||||
ON course.cl_chapter_kp_rel(tenant_id, chapter_id, relation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_chapter_kp_rel_kp
|
||||
ON course.cl_chapter_kp_rel(tenant_id, kp_id, relation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_student_kp_mastery_student
|
||||
ON course.cl_student_kp_mastery(tenant_id, student_id, mastery_score DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_student_kp_mastery_kp
|
||||
ON course.cl_student_kp_mastery(tenant_id, kp_id, mastery_score DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_kp_material_rel_kp
|
||||
ON course.cl_kp_material_rel(tenant_id, kp_id, material_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_node_resource_node
|
||||
ON course.cl_node_resource(node_id, resource_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_node_kp_rel_node
|
||||
ON course.cl_node_kp_rel(tenant_id, node_id, relation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_node_kp_rel_kp
|
||||
ON course.cl_node_kp_rel(tenant_id, kp_id, relation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_node_homework_rel_node
|
||||
ON course.cl_node_homework_rel(tenant_id, node_id, relation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_learning_session_user_course
|
||||
ON course.cl_learning_session(tenant_id, user_id, course_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_learning_progress_session_node
|
||||
ON course.cl_learning_progress(session_id, node_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cl_learning_event_session_time
|
||||
ON course.cl_learning_event(session_id, event_time DESC);
|
||||
2
init/pg/course/20_init_course_seed.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- 课程学习模块初始化数据占位
|
||||
-- 按需补充课程、章节、课时、知识点、标签等种子数据
|
||||
729
init/pg/question/10_create_question_tables.sql
Normal file
@@ -0,0 +1,729 @@
|
||||
DROP SCHEMA IF EXISTS question CASCADE;
|
||||
CREATE SCHEMA IF NOT EXISTS question;
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_question_bank CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_question_bank (
|
||||
bank_id VARCHAR(64) PRIMARY KEY,
|
||||
bank_name VARCHAR(256) NOT NULL,
|
||||
subject_code VARCHAR(32) NOT NULL,
|
||||
grade_code VARCHAR(32) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_by VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE question.hw_question_bank IS '题库表';
|
||||
COMMENT ON COLUMN question.hw_question_bank.bank_id IS '题库ID';
|
||||
COMMENT ON COLUMN question.hw_question_bank.bank_name IS '题库名称';
|
||||
COMMENT ON COLUMN question.hw_question_bank.subject_code IS '学科编码';
|
||||
COMMENT ON COLUMN question.hw_question_bank.grade_code IS '年级编码';
|
||||
COMMENT ON COLUMN question.hw_question_bank.status IS '状态';
|
||||
COMMENT ON COLUMN question.hw_question_bank.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN question.hw_question_bank.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_question_bank.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN question.hw_question_bank.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN question.hw_question_bank.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN question.hw_question_bank.created_by IS '创建人ID';
|
||||
COMMENT ON COLUMN question.hw_question_bank.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_question_item CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_question_item (
|
||||
question_id VARCHAR(64) PRIMARY KEY,
|
||||
question_type VARCHAR(32) NOT NULL,
|
||||
stem TEXT NOT NULL,
|
||||
stem_json JSONB,
|
||||
difficulty VARCHAR(16),
|
||||
answer_payload JSONB,
|
||||
analysis TEXT,
|
||||
scoring_rule_json JSONB,
|
||||
question_status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_hw_question_item_type
|
||||
CHECK (question_type IN ('SINGLE_CHOICE', 'MULTIPLE_CHOICE', 'FILL_BLANK', 'JUDGE', 'SUBJECTIVE')),
|
||||
CONSTRAINT chk_hw_question_item_answer_payload
|
||||
CHECK (answer_payload IS NOT NULL AND jsonb_typeof(answer_payload) = 'object')
|
||||
);
|
||||
COMMENT ON TABLE question.hw_question_item IS '题目表(含标准答案)';
|
||||
COMMENT ON COLUMN question.hw_question_item.question_id IS '题目ID';
|
||||
COMMENT ON COLUMN question.hw_question_item.question_type IS '题型';
|
||||
COMMENT ON COLUMN question.hw_question_item.stem IS '题干';
|
||||
COMMENT ON COLUMN question.hw_question_item.stem_json IS '结构化题干JSON(富文本/图片公式等)';
|
||||
COMMENT ON COLUMN question.hw_question_item.difficulty IS '难度';
|
||||
COMMENT ON COLUMN question.hw_question_item.answer_payload IS '统一标准答案载荷JSONB(对应后端题型答案类型)';
|
||||
COMMENT ON COLUMN question.hw_question_item.analysis IS '题目解析';
|
||||
COMMENT ON COLUMN question.hw_question_item.scoring_rule_json IS '评分规则JSON(含主观题评分点)';
|
||||
COMMENT ON COLUMN question.hw_question_item.question_status IS '题目状态(ACTIVE/DISABLED)';
|
||||
COMMENT ON COLUMN question.hw_question_item.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_question_item.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.hw_question_item.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_bank_question_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_bank_question_rel (
|
||||
bank_id VARCHAR(64) NOT NULL,
|
||||
question_id VARCHAR(64) NOT NULL,
|
||||
question_order INTEGER,
|
||||
source_type VARCHAR(32) NOT NULL DEFAULT 'MANUAL',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (bank_id, question_id),
|
||||
CONSTRAINT chk_hw_bank_question_rel_source_type
|
||||
CHECK (source_type IN ('MANUAL', 'AI_GENERATED', 'IMPORTED', 'MIGRATED')),
|
||||
CONSTRAINT fk_hw_bank_question_rel_bank
|
||||
FOREIGN KEY (bank_id) REFERENCES question.hw_question_bank(bank_id),
|
||||
CONSTRAINT fk_hw_bank_question_rel_question
|
||||
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id)
|
||||
);
|
||||
COMMENT ON TABLE question.hw_bank_question_rel IS '题库-题目关联表';
|
||||
COMMENT ON COLUMN question.hw_bank_question_rel.bank_id IS '题库ID';
|
||||
COMMENT ON COLUMN question.hw_bank_question_rel.question_id IS '题目ID';
|
||||
COMMENT ON COLUMN question.hw_bank_question_rel.question_order IS '题库内题目顺序';
|
||||
COMMENT ON COLUMN question.hw_bank_question_rel.source_type IS '题目来源类型(MANUAL/AI_GENERATED/IMPORTED/MIGRATED)';
|
||||
COMMENT ON COLUMN question.hw_bank_question_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_bank_question_rel.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_question_kp_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_question_kp_rel (
|
||||
question_id VARCHAR(64) NOT NULL,
|
||||
kp_id VARCHAR(64) NOT NULL,
|
||||
relation_type VARCHAR(32) NOT NULL DEFAULT 'TESTS',
|
||||
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
confidence NUMERIC(5,4),
|
||||
graph_relation_id VARCHAR(64),
|
||||
source_table VARCHAR(128),
|
||||
source_pk VARCHAR(128),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (question_id, kp_id),
|
||||
CONSTRAINT chk_hw_question_kp_rel_type
|
||||
CHECK (relation_type IN ('TESTS', 'RELATED', 'SUPPORTS')),
|
||||
CONSTRAINT chk_hw_question_kp_rel_weight
|
||||
CHECK (weight > 0 AND weight <= 1),
|
||||
CONSTRAINT chk_hw_question_kp_rel_confidence
|
||||
CHECK (confidence IS NULL OR (confidence >= 0 AND confidence <= 1)),
|
||||
CONSTRAINT fk_hw_question_kp_rel_question
|
||||
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id),
|
||||
CONSTRAINT fk_hw_question_kp_rel_kp
|
||||
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
|
||||
);
|
||||
COMMENT ON TABLE question.hw_question_kp_rel IS '题目-知识点关联表';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.question_id IS '题目ID';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.kp_id IS '知识点ID';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.relation_type IS '关系类型(TESTS/RELATED/SUPPORTS)';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.weight IS '关联权重(0,1])';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.confidence IS '抽取置信度';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.graph_relation_id IS '图谱关系ID';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.source_table IS '来源表';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.source_pk IS '来源主键';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.hw_question_kp_rel.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_paper CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_paper (
|
||||
paper_id VARCHAR(64) PRIMARY KEY,
|
||||
paper_name VARCHAR(256) NOT NULL,
|
||||
paper_type VARCHAR(32) NOT NULL DEFAULT 'STANDARD',
|
||||
subject_code VARCHAR(32) NOT NULL,
|
||||
total_score NUMERIC(8,2) NOT NULL DEFAULT 100,
|
||||
source_file_id VARCHAR(64),
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
created_by VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
grading_policy_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
CONSTRAINT chk_hw_paper_type
|
||||
CHECK (paper_type IN ('STANDARD', 'EXTERNAL_UPLOAD')),
|
||||
CONSTRAINT chk_hw_paper_grading_policy_json
|
||||
CHECK (jsonb_typeof(grading_policy_json) = 'object'),
|
||||
CONSTRAINT fk_hw_paper_source_file
|
||||
FOREIGN KEY (source_file_id) REFERENCES upms.tb_sys_file(file_id)
|
||||
);
|
||||
COMMENT ON TABLE question.hw_paper IS '试卷表';
|
||||
COMMENT ON COLUMN question.hw_paper.paper_id IS '试卷ID';
|
||||
COMMENT ON COLUMN question.hw_paper.paper_name IS '试卷名称';
|
||||
COMMENT ON COLUMN question.hw_paper.paper_type IS '试卷类型(STANDARD/EXTERNAL_UPLOAD)';
|
||||
COMMENT ON COLUMN question.hw_paper.subject_code IS '学科编码';
|
||||
COMMENT ON COLUMN question.hw_paper.total_score IS '总分';
|
||||
COMMENT ON COLUMN question.hw_paper.source_file_id IS '来源文件ID(课外上传作业解析来源)';
|
||||
COMMENT ON COLUMN question.hw_paper.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN question.hw_paper.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_paper.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN question.hw_paper.created_by IS '创建人';
|
||||
COMMENT ON COLUMN question.hw_paper.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.hw_paper.grading_policy_json IS '试卷批改策略JSON(单选/判断完全匹配、多选得分策略、填空匹配策略、简答评估模式)';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_paper_question CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_paper_question (
|
||||
paper_id VARCHAR(64) NOT NULL,
|
||||
question_id VARCHAR(64) NOT NULL,
|
||||
question_order INTEGER NOT NULL,
|
||||
score NUMERIC(8,2) NOT NULL DEFAULT 0,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (paper_id, question_id),
|
||||
CONSTRAINT fk_hw_paper_question_paper
|
||||
FOREIGN KEY (paper_id) REFERENCES question.hw_paper(paper_id),
|
||||
CONSTRAINT fk_hw_paper_question_question
|
||||
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id)
|
||||
);
|
||||
COMMENT ON TABLE question.hw_paper_question IS '试卷-题目关联表';
|
||||
COMMENT ON COLUMN question.hw_paper_question.paper_id IS '试卷ID';
|
||||
COMMENT ON COLUMN question.hw_paper_question.question_id IS '题目ID';
|
||||
COMMENT ON COLUMN question.hw_paper_question.question_order IS '题目顺序';
|
||||
COMMENT ON COLUMN question.hw_paper_question.score IS '题目分值';
|
||||
COMMENT ON COLUMN question.hw_paper_question.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_paper_question.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_assignment CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_assignment (
|
||||
assignment_id VARCHAR(64) PRIMARY KEY,
|
||||
paper_id VARCHAR(64) NOT NULL,
|
||||
assignment_mode VARCHAR(32) NOT NULL DEFAULT 'PAPER',
|
||||
title VARCHAR(256) NOT NULL,
|
||||
publish_time TIMESTAMP,
|
||||
deadline TIMESTAMP,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'DRAFT',
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_by VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_hw_assignment_mode
|
||||
CHECK (assignment_mode IN ('PAPER', 'EXTERNAL_UPLOAD')),
|
||||
CONSTRAINT fk_hw_assignment_paper
|
||||
FOREIGN KEY (paper_id) REFERENCES question.hw_paper(paper_id)
|
||||
);
|
||||
COMMENT ON TABLE question.hw_assignment IS '作业表';
|
||||
COMMENT ON COLUMN question.hw_assignment.assignment_id IS '作业ID';
|
||||
COMMENT ON COLUMN question.hw_assignment.paper_id IS '试卷ID';
|
||||
COMMENT ON COLUMN question.hw_assignment.assignment_mode IS '作业模式(PAPER/EXTERNAL_UPLOAD)';
|
||||
COMMENT ON COLUMN question.hw_assignment.title IS '作业标题';
|
||||
COMMENT ON COLUMN question.hw_assignment.publish_time IS '发布时间';
|
||||
COMMENT ON COLUMN question.hw_assignment.deadline IS '截止时间';
|
||||
COMMENT ON COLUMN question.hw_assignment.status IS '状态(DRAFT/PUBLISHED/CLOSED)';
|
||||
COMMENT ON COLUMN question.hw_assignment.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN question.hw_assignment.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_assignment.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN question.hw_assignment.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN question.hw_assignment.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN question.hw_assignment.created_by IS '创建人';
|
||||
COMMENT ON COLUMN question.hw_assignment.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_assignment_target CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_assignment_target (
|
||||
target_id VARCHAR(64) PRIMARY KEY,
|
||||
assignment_id VARCHAR(64) NOT NULL,
|
||||
target_type VARCHAR(32) NOT NULL,
|
||||
target_ref_id VARCHAR(64) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_hw_assignment_target_assignment
|
||||
FOREIGN KEY (assignment_id) REFERENCES question.hw_assignment(assignment_id)
|
||||
);
|
||||
COMMENT ON TABLE question.hw_assignment_target IS '作业投放对象表';
|
||||
COMMENT ON COLUMN question.hw_assignment_target.target_id IS '投放记录ID';
|
||||
COMMENT ON COLUMN question.hw_assignment_target.assignment_id IS '作业ID';
|
||||
COMMENT ON COLUMN question.hw_assignment_target.target_type IS '投放类型(CLASS/STUDENT)';
|
||||
COMMENT ON COLUMN question.hw_assignment_target.target_ref_id IS '投放对象ID';
|
||||
COMMENT ON COLUMN question.hw_assignment_target.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_assignment_target.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_submission CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_submission (
|
||||
submission_id VARCHAR(64) PRIMARY KEY,
|
||||
assignment_id VARCHAR(64) NOT NULL,
|
||||
student_id VARCHAR(64) NOT NULL,
|
||||
submission_mode VARCHAR(32) NOT NULL DEFAULT 'ASSIGNMENT',
|
||||
origin_file_id VARCHAR(64),
|
||||
submit_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
used_seconds INTEGER,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'SUBMITTED',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_hw_submission_mode
|
||||
CHECK (submission_mode IN ('ASSIGNMENT', 'EXTERNAL_UPLOAD')),
|
||||
CONSTRAINT fk_hw_submission_assignment
|
||||
FOREIGN KEY (assignment_id) REFERENCES question.hw_assignment(assignment_id),
|
||||
CONSTRAINT fk_hw_submission_student
|
||||
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_hw_submission_origin_file
|
||||
FOREIGN KEY (origin_file_id) REFERENCES upms.tb_sys_file(file_id)
|
||||
);
|
||||
COMMENT ON TABLE question.hw_submission IS '作业提交表';
|
||||
COMMENT ON COLUMN question.hw_submission.submission_id IS '提交ID';
|
||||
COMMENT ON COLUMN question.hw_submission.assignment_id IS '作业ID';
|
||||
COMMENT ON COLUMN question.hw_submission.student_id IS '学员ID';
|
||||
COMMENT ON COLUMN question.hw_submission.submission_mode IS '提交模式(ASSIGNMENT/EXTERNAL_UPLOAD)';
|
||||
COMMENT ON COLUMN question.hw_submission.origin_file_id IS '原始上传文件ID(课外作业)';
|
||||
COMMENT ON COLUMN question.hw_submission.submit_time IS '提交时间';
|
||||
COMMENT ON COLUMN question.hw_submission.used_seconds IS '作答耗时(秒)';
|
||||
COMMENT ON COLUMN question.hw_submission.status IS '提交状态';
|
||||
COMMENT ON COLUMN question.hw_submission.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_submission.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.hw_submission_answer CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.hw_submission_answer (
|
||||
answer_id VARCHAR(64) PRIMARY KEY,
|
||||
submission_id VARCHAR(64) NOT NULL,
|
||||
question_id VARCHAR(64) NOT NULL,
|
||||
answer_type VARCHAR(32) NOT NULL,
|
||||
answer_payload JSONB,
|
||||
file_id VARCHAR(64),
|
||||
file_type VARCHAR(32),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_hw_submission_answer_type
|
||||
CHECK (answer_type IN ('SINGLE_CHOICE', 'MULTIPLE_CHOICE', 'FILL_BLANK', 'JUDGE', 'SUBJECTIVE')),
|
||||
CONSTRAINT chk_hw_submission_answer_payload
|
||||
CHECK (
|
||||
answer_payload IS NOT NULL
|
||||
OR file_id IS NOT NULL
|
||||
),
|
||||
CONSTRAINT chk_hw_submission_answer_payload_json
|
||||
CHECK (answer_payload IS NULL OR jsonb_typeof(answer_payload) = 'object'),
|
||||
CONSTRAINT chk_hw_submission_answer_file_type
|
||||
CHECK (file_type IS NULL OR file_type IN ('IMAGE', 'AUDIO', 'VIDEO', 'DOCUMENT', 'OTHER')),
|
||||
CONSTRAINT chk_hw_submission_answer_file_ref
|
||||
CHECK (
|
||||
(file_id IS NULL AND file_type IS NULL)
|
||||
OR (file_id IS NOT NULL AND file_type IS NOT NULL)
|
||||
),
|
||||
CONSTRAINT uq_hw_submission_answer_submission_question
|
||||
UNIQUE (submission_id, question_id),
|
||||
CONSTRAINT fk_hw_submission_answer_submission
|
||||
FOREIGN KEY (submission_id) REFERENCES question.hw_submission(submission_id),
|
||||
CONSTRAINT fk_hw_submission_answer_question
|
||||
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id),
|
||||
CONSTRAINT fk_hw_submission_answer_file
|
||||
FOREIGN KEY (file_id) REFERENCES upms.tb_sys_file(file_id)
|
||||
);
|
||||
COMMENT ON TABLE question.hw_submission_answer IS '提交答案明细表(一次提交中每题一行)';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.answer_id IS '答案ID';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.submission_id IS '提交ID';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.question_id IS '题目ID';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.answer_type IS '答案类型(与题型一致)';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.answer_payload IS '统一作答载荷JSONB(对应后端答案类型)';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.file_id IS '作答文件ID(图片/音频等)';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.file_type IS '作答文件类型';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.hw_submission_answer.updated_at IS '更新时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_question_bank_tenant_subject_grade
|
||||
ON question.hw_question_bank(tenant_id, subject_code, grade_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_question_item_type
|
||||
ON question.hw_question_item(question_type, difficulty);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_question_item_tenant
|
||||
ON question.hw_question_item(tenant_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_bank_question_rel_question
|
||||
ON question.hw_bank_question_rel(question_id, bank_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_bank_question_rel_tenant_bank
|
||||
ON question.hw_bank_question_rel(tenant_id, bank_id, question_order);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_question_kp_rel_tenant_question
|
||||
ON question.hw_question_kp_rel(tenant_id, question_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_question_kp_rel_tenant_kp
|
||||
ON question.hw_question_kp_rel(tenant_id, kp_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_question_kp_rel_graph_relation
|
||||
ON question.hw_question_kp_rel(graph_relation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_assignment_tenant_status_deadline
|
||||
ON question.hw_assignment(tenant_id, status, deadline);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_assignment_mode
|
||||
ON question.hw_assignment(assignment_mode, tenant_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_assignment_target_assignment
|
||||
ON question.hw_assignment_target(assignment_id, target_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_submission_assignment_student
|
||||
ON question.hw_submission(assignment_id, student_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_submission_mode
|
||||
ON question.hw_submission(submission_mode, tenant_id, submit_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_submission_origin_file
|
||||
ON question.hw_submission(origin_file_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_submission_answer_submission
|
||||
ON question.hw_submission_answer(submission_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_submission_answer_question
|
||||
ON question.hw_submission_answer(question_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_hw_submission_answer_file_id
|
||||
ON question.hw_submission_answer(file_id);
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_grading_task CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_grading_task (
|
||||
grading_task_id VARCHAR(64) PRIMARY KEY,
|
||||
submission_id VARCHAR(64) NOT NULL,
|
||||
paper_id VARCHAR(64) NOT NULL,
|
||||
task_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
trigger_source VARCHAR(32) NOT NULL DEFAULT 'SUBMISSION',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_gd_grading_task_submission
|
||||
FOREIGN KEY (submission_id) REFERENCES question.hw_submission(submission_id),
|
||||
CONSTRAINT fk_gd_grading_task_paper
|
||||
FOREIGN KEY (paper_id) REFERENCES question.hw_paper(paper_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_grading_task IS '批改任务表';
|
||||
COMMENT ON COLUMN question.gd_grading_task.grading_task_id IS '批改任务ID';
|
||||
COMMENT ON COLUMN question.gd_grading_task.submission_id IS '提交ID';
|
||||
COMMENT ON COLUMN question.gd_grading_task.paper_id IS '试卷ID';
|
||||
COMMENT ON COLUMN question.gd_grading_task.task_status IS '任务状态(PENDING/RUNNING/WAIT_REVIEW/DONE)';
|
||||
COMMENT ON COLUMN question.gd_grading_task.trigger_source IS '触发来源';
|
||||
COMMENT ON COLUMN question.gd_grading_task.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_grading_task.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.gd_grading_task.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_answer_grade CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_answer_grade (
|
||||
answer_grade_id VARCHAR(64) PRIMARY KEY,
|
||||
grading_task_id VARCHAR(64) NOT NULL,
|
||||
answer_id VARCHAR(64) NOT NULL,
|
||||
paper_id VARCHAR(64) NOT NULL,
|
||||
grade_mode VARCHAR(32) NOT NULL DEFAULT 'AUTO',
|
||||
grade_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
score NUMERIC(8,2),
|
||||
grader_id VARCHAR(64),
|
||||
grade_comment TEXT,
|
||||
evidence_json JSONB,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_gd_answer_grade_mode
|
||||
CHECK (grade_mode IN ('AUTO', 'MANUAL', 'AI', 'AI_REVIEW')),
|
||||
CONSTRAINT chk_gd_answer_grade_status
|
||||
CHECK (grade_status IN ('PENDING', 'DONE', 'REVIEWED')),
|
||||
CONSTRAINT uq_gd_answer_grade_task_answer
|
||||
UNIQUE (grading_task_id, answer_id),
|
||||
CONSTRAINT fk_gd_answer_grade_task
|
||||
FOREIGN KEY (grading_task_id) REFERENCES question.gd_grading_task(grading_task_id),
|
||||
CONSTRAINT fk_gd_answer_grade_answer
|
||||
FOREIGN KEY (answer_id) REFERENCES question.hw_submission_answer(answer_id),
|
||||
CONSTRAINT fk_gd_answer_grade_paper
|
||||
FOREIGN KEY (paper_id) REFERENCES question.hw_paper(paper_id),
|
||||
CONSTRAINT fk_gd_answer_grade_grader
|
||||
FOREIGN KEY (grader_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_answer_grade IS '答案批改结果表(统一客观/主观)';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.answer_grade_id IS '答案批改结果ID';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.grading_task_id IS '批改任务ID';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.answer_id IS '答案ID';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.paper_id IS '试卷ID';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.grade_mode IS '批改模式(AUTO/MANUAL/AI/AI_REVIEW)';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.grade_status IS '批改状态(PENDING/DONE/REVIEWED)';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.score IS '最终得分';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.grader_id IS '评阅人ID';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.grade_comment IS '批改评语';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.evidence_json IS '判定依据JSON';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.gd_answer_grade.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_score_summary CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_score_summary (
|
||||
summary_id VARCHAR(64) PRIMARY KEY,
|
||||
grading_task_id VARCHAR(64) UNIQUE NOT NULL,
|
||||
total_score NUMERIC(8,2) NOT NULL DEFAULT 0,
|
||||
grade_level VARCHAR(8),
|
||||
surpass_ratio NUMERIC(5,2),
|
||||
used_seconds INTEGER,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_gd_score_summary_task
|
||||
FOREIGN KEY (grading_task_id) REFERENCES question.gd_grading_task(grading_task_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_score_summary IS '批改结果汇总表';
|
||||
COMMENT ON COLUMN question.gd_score_summary.summary_id IS '汇总ID';
|
||||
COMMENT ON COLUMN question.gd_score_summary.grading_task_id IS '批改任务ID';
|
||||
COMMENT ON COLUMN question.gd_score_summary.total_score IS '总分';
|
||||
COMMENT ON COLUMN question.gd_score_summary.grade_level IS '等级(A/B/C)';
|
||||
COMMENT ON COLUMN question.gd_score_summary.surpass_ratio IS '超越比例';
|
||||
COMMENT ON COLUMN question.gd_score_summary.used_seconds IS '作答耗时';
|
||||
COMMENT ON COLUMN question.gd_score_summary.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_score_summary.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_error_tag CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_error_tag (
|
||||
error_tag_id VARCHAR(64) PRIMARY KEY,
|
||||
tag_code VARCHAR(64) UNIQUE NOT NULL,
|
||||
tag_name VARCHAR(128) NOT NULL,
|
||||
category VARCHAR(32) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE question.gd_error_tag IS '错因标签表';
|
||||
COMMENT ON COLUMN question.gd_error_tag.error_tag_id IS '标签ID';
|
||||
COMMENT ON COLUMN question.gd_error_tag.tag_code IS '标签编码';
|
||||
COMMENT ON COLUMN question.gd_error_tag.tag_name IS '标签名称';
|
||||
COMMENT ON COLUMN question.gd_error_tag.category IS '分类(审题/计算/概念等)';
|
||||
COMMENT ON COLUMN question.gd_error_tag.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_error_tag.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_answer_error_rel CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_answer_error_rel (
|
||||
answer_id VARCHAR(64) NOT NULL,
|
||||
error_tag_id VARCHAR(64) NOT NULL,
|
||||
confidence NUMERIC(5,4),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (answer_id, error_tag_id),
|
||||
CONSTRAINT fk_gd_answer_error_rel_answer
|
||||
FOREIGN KEY (answer_id) REFERENCES question.hw_submission_answer(answer_id),
|
||||
CONSTRAINT fk_gd_answer_error_rel_tag
|
||||
FOREIGN KEY (error_tag_id) REFERENCES question.gd_error_tag(error_tag_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_answer_error_rel IS '答案-错因标签关联表';
|
||||
COMMENT ON COLUMN question.gd_answer_error_rel.answer_id IS '答案ID';
|
||||
COMMENT ON COLUMN question.gd_answer_error_rel.error_tag_id IS '错因标签ID';
|
||||
COMMENT ON COLUMN question.gd_answer_error_rel.confidence IS '置信度';
|
||||
COMMENT ON COLUMN question.gd_answer_error_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_answer_error_rel.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_answer_kp_analysis CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_answer_kp_analysis (
|
||||
answer_id VARCHAR(64) NOT NULL,
|
||||
kp_id VARCHAR(64) NOT NULL,
|
||||
grading_task_id VARCHAR(64),
|
||||
correctness VARCHAR(16) NOT NULL DEFAULT 'UNKNOWN',
|
||||
mastery_score NUMERIC(6,4),
|
||||
confidence NUMERIC(5,4),
|
||||
evidence_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (answer_id, kp_id),
|
||||
CONSTRAINT chk_gd_answer_kp_analysis_correctness
|
||||
CHECK (correctness IN ('CORRECT', 'PARTIAL', 'WRONG', 'UNKNOWN')),
|
||||
CONSTRAINT chk_gd_answer_kp_analysis_mastery
|
||||
CHECK (mastery_score IS NULL OR (mastery_score >= 0 AND mastery_score <= 1)),
|
||||
CONSTRAINT chk_gd_answer_kp_analysis_confidence
|
||||
CHECK (confidence IS NULL OR (confidence >= 0 AND confidence <= 1)),
|
||||
CONSTRAINT chk_gd_answer_kp_analysis_evidence_json
|
||||
CHECK (jsonb_typeof(evidence_json) = 'object'),
|
||||
CONSTRAINT fk_gd_answer_kp_analysis_answer
|
||||
FOREIGN KEY (answer_id) REFERENCES question.hw_submission_answer(answer_id),
|
||||
CONSTRAINT fk_gd_answer_kp_analysis_kp
|
||||
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id),
|
||||
CONSTRAINT fk_gd_answer_kp_analysis_grading_task
|
||||
FOREIGN KEY (grading_task_id) REFERENCES question.gd_grading_task(grading_task_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_answer_kp_analysis IS '答案-知识点分析表';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.answer_id IS '答案ID';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.kp_id IS '知识点ID';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.grading_task_id IS '批改任务ID';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.correctness IS '正确性(CORRECT/PARTIAL/WRONG/UNKNOWN)';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.mastery_score IS '答案层知识点掌握度(0-1)';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.confidence IS '分析置信度';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.evidence_json IS '知识点分析依据JSON';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.gd_answer_kp_analysis.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_wrong_question CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_wrong_question (
|
||||
wrong_question_id VARCHAR(64) PRIMARY KEY,
|
||||
student_id VARCHAR(64) NOT NULL,
|
||||
question_id VARCHAR(64) NOT NULL,
|
||||
source_submission_id VARCHAR(64) NOT NULL,
|
||||
mastery_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
review_count INTEGER NOT NULL DEFAULT 0,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_gd_wrong_question_student
|
||||
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_gd_wrong_question_question
|
||||
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id),
|
||||
CONSTRAINT fk_gd_wrong_question_submission
|
||||
FOREIGN KEY (source_submission_id) REFERENCES question.hw_submission(submission_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_wrong_question IS '错题沉淀表';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.wrong_question_id IS '错题记录ID';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.student_id IS '学员ID';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.question_id IS '题目ID';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.source_submission_id IS '来源提交ID';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.mastery_status IS '掌握状态';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.review_count IS '复习次数';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.gd_wrong_question.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_review_plan CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_review_plan (
|
||||
review_plan_id VARCHAR(64) PRIMARY KEY,
|
||||
wrong_question_id VARCHAR(64) NOT NULL,
|
||||
plan_date DATE NOT NULL,
|
||||
plan_stage VARCHAR(16) NOT NULL,
|
||||
plan_status VARCHAR(32) NOT NULL DEFAULT 'TODO',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_gd_review_plan_wrong_question
|
||||
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_review_plan IS '复习计划表';
|
||||
COMMENT ON COLUMN question.gd_review_plan.review_plan_id IS '复习计划ID';
|
||||
COMMENT ON COLUMN question.gd_review_plan.wrong_question_id IS '错题记录ID';
|
||||
COMMENT ON COLUMN question.gd_review_plan.plan_date IS '计划日期';
|
||||
COMMENT ON COLUMN question.gd_review_plan.plan_stage IS '阶段(E1/E2/E3)';
|
||||
COMMENT ON COLUMN question.gd_review_plan.plan_status IS '计划状态';
|
||||
COMMENT ON COLUMN question.gd_review_plan.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_review_plan.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_teacher_comment CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_teacher_comment (
|
||||
comment_id VARCHAR(64) PRIMARY KEY,
|
||||
answer_grade_id VARCHAR(64) NOT NULL,
|
||||
reviewer_id VARCHAR(64) NOT NULL,
|
||||
comment_type VARCHAR(16) NOT NULL DEFAULT 'TEXT',
|
||||
content_ref TEXT,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_gd_teacher_comment_answer_grade
|
||||
FOREIGN KEY (answer_grade_id) REFERENCES question.gd_answer_grade(answer_grade_id),
|
||||
CONSTRAINT fk_gd_teacher_comment_reviewer
|
||||
FOREIGN KEY (reviewer_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_teacher_comment IS '教师点评表';
|
||||
COMMENT ON COLUMN question.gd_teacher_comment.comment_id IS '点评ID';
|
||||
COMMENT ON COLUMN question.gd_teacher_comment.answer_grade_id IS '答案批改结果ID';
|
||||
COMMENT ON COLUMN question.gd_teacher_comment.reviewer_id IS '教师ID';
|
||||
COMMENT ON COLUMN question.gd_teacher_comment.comment_type IS '点评类型(TEXT/VOICE)';
|
||||
COMMENT ON COLUMN question.gd_teacher_comment.content_ref IS '点评内容或资源引用';
|
||||
COMMENT ON COLUMN question.gd_teacher_comment.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_teacher_comment.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS question.gd_explanation_dimension_score CASCADE;
|
||||
DROP TABLE IF EXISTS question.gd_explanation_assessment CASCADE;
|
||||
DROP TABLE IF EXISTS question.gd_explanation_submission CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS question.gd_explanation_submission (
|
||||
explanation_id VARCHAR(64) PRIMARY KEY,
|
||||
student_id VARCHAR(64) NOT NULL,
|
||||
wrong_question_id VARCHAR(64),
|
||||
source_submission_id VARCHAR(64),
|
||||
source_answer_id VARCHAR(64),
|
||||
audio_file_id VARCHAR(64) NOT NULL,
|
||||
duration_seconds INTEGER,
|
||||
transcript_text TEXT,
|
||||
submission_status VARCHAR(16) NOT NULL DEFAULT 'SUBMITTED',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_gd_explanation_submission_status
|
||||
CHECK (submission_status IN ('SUBMITTED', 'EVALUATED', 'REJECTED')),
|
||||
CONSTRAINT fk_gd_explanation_submission_student
|
||||
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_gd_explanation_submission_wrong_question
|
||||
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id),
|
||||
CONSTRAINT fk_gd_explanation_submission_source_submission
|
||||
FOREIGN KEY (source_submission_id) REFERENCES question.hw_submission(submission_id),
|
||||
CONSTRAINT fk_gd_explanation_submission_source_answer
|
||||
FOREIGN KEY (source_answer_id) REFERENCES question.hw_submission_answer(answer_id),
|
||||
CONSTRAINT fk_gd_explanation_submission_audio_file
|
||||
FOREIGN KEY (audio_file_id) REFERENCES upms.tb_sys_file(file_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_explanation_submission IS '费曼讲解提交表';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.explanation_id IS '讲解提交ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.student_id IS '学员ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.wrong_question_id IS '关联错题ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.source_submission_id IS '来源作业提交ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.source_answer_id IS '来源答案ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.audio_file_id IS '讲解音频文件ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.duration_seconds IS '时长(秒)';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.transcript_text IS '转写文本';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.submission_status IS '提交状态';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.gd_explanation_submission.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS question.gd_explanation_assessment (
|
||||
assessment_id VARCHAR(64) PRIMARY KEY,
|
||||
explanation_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
evaluator_type VARCHAR(16) NOT NULL DEFAULT 'AI',
|
||||
evaluator_id VARCHAR(64),
|
||||
total_score NUMERIC(8,2),
|
||||
pass_status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
|
||||
improvement_suggestion TEXT,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_gd_explanation_assessment_evaluator_type
|
||||
CHECK (evaluator_type IN ('AI', 'TEACHER', 'HYBRID')),
|
||||
CONSTRAINT chk_gd_explanation_assessment_pass_status
|
||||
CHECK (pass_status IN ('PENDING', 'PASSED', 'FAILED')),
|
||||
CONSTRAINT fk_gd_explanation_assessment_explanation
|
||||
FOREIGN KEY (explanation_id) REFERENCES question.gd_explanation_submission(explanation_id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_gd_explanation_assessment_evaluator
|
||||
FOREIGN KEY (evaluator_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE question.gd_explanation_assessment IS '费曼讲解评估汇总表';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.assessment_id IS '评估ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.explanation_id IS '讲解提交ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.evaluator_type IS '评估类型';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.evaluator_id IS '评估人ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.total_score IS '总分';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.pass_status IS '通过状态';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.improvement_suggestion IS '改进建议';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN question.gd_explanation_assessment.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS question.gd_explanation_dimension_score (
|
||||
dimension_score_id VARCHAR(64) PRIMARY KEY,
|
||||
assessment_id VARCHAR(64) NOT NULL,
|
||||
dimension_code VARCHAR(32) NOT NULL,
|
||||
dimension_name VARCHAR(64) NOT NULL,
|
||||
score NUMERIC(8,2) NOT NULL DEFAULT 0,
|
||||
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
comment_text TEXT,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_gd_explanation_dimension_score_weight
|
||||
CHECK (weight > 0 AND weight <= 1),
|
||||
CONSTRAINT fk_gd_explanation_dimension_score_assessment
|
||||
FOREIGN KEY (assessment_id) REFERENCES question.gd_explanation_assessment(assessment_id) ON DELETE CASCADE
|
||||
);
|
||||
COMMENT ON TABLE question.gd_explanation_dimension_score IS '费曼讲解维度评分表';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.dimension_score_id IS '维度评分ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.assessment_id IS '评估ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.dimension_code IS '维度编码';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.dimension_name IS '维度名称';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.score IS '维度得分';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.weight IS '维度权重';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.comment_text IS '维度评语';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN question.gd_explanation_dimension_score.created_at IS '创建时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_grading_task_submission
|
||||
ON question.gd_grading_task(submission_id, task_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_grading_task_paper_status
|
||||
ON question.gd_grading_task(paper_id, task_status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_answer_grade_task_status
|
||||
ON question.gd_answer_grade(grading_task_id, grade_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_answer_grade_paper_status
|
||||
ON question.gd_answer_grade(paper_id, grade_status, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_answer_error_rel_answer
|
||||
ON question.gd_answer_error_rel(answer_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_answer_kp_analysis_kp
|
||||
ON question.gd_answer_kp_analysis(kp_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_answer_kp_analysis_task
|
||||
ON question.gd_answer_kp_analysis(grading_task_id, kp_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_wrong_question_student_status
|
||||
ON question.gd_wrong_question(student_id, mastery_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_review_plan_wrong_question_date
|
||||
ON question.gd_review_plan(wrong_question_id, plan_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_teacher_comment_answer_grade
|
||||
ON question.gd_teacher_comment(answer_grade_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_student
|
||||
ON question.gd_explanation_submission(student_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_wrong_question
|
||||
ON question.gd_explanation_submission(wrong_question_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_explanation_assessment_pass_status
|
||||
ON question.gd_explanation_assessment(pass_status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_gd_explanation_dimension_score_assessment
|
||||
ON question.gd_explanation_dimension_score(assessment_id, dimension_code);
|
||||
5
init/pg/question/20_init_question_seed.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- 习题与作业模块初始化数据占位
|
||||
-- 按需补充题库、题目、试卷、作业模板等种子数据
|
||||
|
||||
-- 批改与反馈模块初始化数据占位
|
||||
-- 按需补充错因标签、统一答案批改结果、复习计划策略等种子数据
|
||||
440
init/pg/recommendation/10_create_recommendation_tables.sql
Normal file
@@ -0,0 +1,440 @@
|
||||
DROP SCHEMA IF EXISTS recommendation CASCADE;
|
||||
CREATE SCHEMA IF NOT EXISTS recommendation;
|
||||
|
||||
DROP TABLE IF EXISTS recommendation.rc_recommendation_task CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_task (
|
||||
task_id VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
scene_code VARCHAR(32) NOT NULL,
|
||||
trigger_source VARCHAR(32) NOT NULL DEFAULT 'SCHEDULE',
|
||||
strategy_version VARCHAR(32),
|
||||
profile_snapshot_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
status VARCHAR(16) NOT NULL DEFAULT 'CREATED',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_rc_recommendation_task_status
|
||||
CHECK (status IN ('CREATED', 'RUNNING', 'DONE', 'FAILED')),
|
||||
CONSTRAINT chk_rc_recommendation_task_profile_json
|
||||
CHECK (jsonb_typeof(profile_snapshot_json) = 'object'),
|
||||
CONSTRAINT fk_rc_recommendation_task_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_recommendation_task IS '推荐任务表';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.task_id IS '推荐任务ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.user_id IS '推荐目标用户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.scene_code IS '推荐场景编码(HOME/WRONGBOOK/REVIEW等)';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.trigger_source IS '触发来源(SCHEDULE/EVENT/MANUAL)';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.strategy_version IS '推荐策略版本';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.profile_snapshot_json IS '画像快照JSON';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.status IS '任务状态';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_task.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS recommendation.rc_recommendation_item CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_item (
|
||||
item_id VARCHAR(64) PRIMARY KEY,
|
||||
task_id VARCHAR(64) NOT NULL,
|
||||
content_type VARCHAR(32) NOT NULL,
|
||||
content_object_id VARCHAR(64) NOT NULL,
|
||||
content_object_type VARCHAR(64) NOT NULL,
|
||||
rank_no INTEGER NOT NULL,
|
||||
score NUMERIC(10,6),
|
||||
reason_codes VARCHAR(255),
|
||||
metadata_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_rc_recommendation_item_content_type
|
||||
CHECK (content_type IN ('COURSE', 'QUESTION', 'PAPER', 'WRONG_QUESTION', 'REVIEW_PLAN', 'MIXED')),
|
||||
CONSTRAINT chk_rc_recommendation_item_metadata_json
|
||||
CHECK (jsonb_typeof(metadata_json) = 'object'),
|
||||
CONSTRAINT uq_rc_recommendation_item_task_rank
|
||||
UNIQUE (task_id, rank_no),
|
||||
CONSTRAINT fk_rc_recommendation_item_task
|
||||
FOREIGN KEY (task_id) REFERENCES recommendation.rc_recommendation_task(task_id) ON DELETE CASCADE
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_recommendation_item IS '推荐结果明细表';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.item_id IS '推荐项ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.task_id IS '推荐任务ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.content_type IS '内容类型';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.content_object_id IS '内容对象ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.content_object_type IS '内容对象类型';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.rank_no IS '推荐位次';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.score IS '推荐分';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.reason_codes IS '推荐原因编码集合';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.metadata_json IS '扩展元数据JSON';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_item.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS recommendation.rc_recommendation_feedback CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_feedback (
|
||||
feedback_id VARCHAR(64) PRIMARY KEY,
|
||||
item_id VARCHAR(64) NOT NULL,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
event_type VARCHAR(16) NOT NULL,
|
||||
event_value NUMERIC(10,4),
|
||||
event_detail_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_rc_recommendation_feedback_event_type
|
||||
CHECK (event_type IN ('EXPOSE', 'CLICK', 'START', 'COMPLETE', 'DISLIKE', 'SKIP')),
|
||||
CONSTRAINT chk_rc_recommendation_feedback_detail_json
|
||||
CHECK (jsonb_typeof(event_detail_json) = 'object'),
|
||||
CONSTRAINT fk_rc_recommendation_feedback_item
|
||||
FOREIGN KEY (item_id) REFERENCES recommendation.rc_recommendation_item(item_id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_rc_recommendation_feedback_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_recommendation_feedback IS '推荐反馈事件表';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.feedback_id IS '反馈事件ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.item_id IS '推荐项ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_type IS '事件类型';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_value IS '事件值(可选)';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_detail_json IS '事件详情JSON';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_time IS '事件发生时间';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS recommendation.rc_recommendation_effect_daily CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_effect_daily (
|
||||
stat_date DATE NOT NULL,
|
||||
scene_code VARCHAR(32) NOT NULL,
|
||||
strategy_version VARCHAR(32) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
expose_count BIGINT NOT NULL DEFAULT 0,
|
||||
click_count BIGINT NOT NULL DEFAULT 0,
|
||||
complete_count BIGINT NOT NULL DEFAULT 0,
|
||||
dislike_count BIGINT NOT NULL DEFAULT 0,
|
||||
ctr NUMERIC(10,6) NOT NULL DEFAULT 0,
|
||||
complete_rate NUMERIC(10,6) NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (stat_date, scene_code, strategy_version, tenant_id)
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_recommendation_effect_daily IS '推荐效果日统计表';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.stat_date IS '统计日期';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.scene_code IS '推荐场景编码';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.strategy_version IS '策略版本';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.expose_count IS '曝光次数';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.click_count IS '点击次数';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.complete_count IS '完成次数';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.dislike_count IS '不喜欢次数';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.ctr IS '点击率';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.complete_rate IS '完成率';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.updated_at IS '更新时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_user_scene
|
||||
ON recommendation.rc_recommendation_task(user_id, scene_code, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_tenant_status
|
||||
ON recommendation.rc_recommendation_task(tenant_id, status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_task
|
||||
ON recommendation.rc_recommendation_item(task_id, rank_no);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_content
|
||||
ON recommendation.rc_recommendation_item(content_object_type, content_object_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_item_event
|
||||
ON recommendation.rc_recommendation_feedback(item_id, event_type, event_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_user_event
|
||||
ON recommendation.rc_recommendation_feedback(user_id, event_type, event_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_effect_daily_tenant_date
|
||||
ON recommendation.rc_recommendation_effect_daily(tenant_id, stat_date DESC);
|
||||
|
||||
DROP TABLE IF EXISTS recommendation.rc_student_profile_feature CASCADE;
|
||||
DROP TABLE IF EXISTS recommendation.rc_student_profile_snapshot CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_student_profile_snapshot (
|
||||
profile_id VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
profile_version VARCHAR(32) NOT NULL,
|
||||
profile_date DATE NOT NULL,
|
||||
feature_source_version VARCHAR(64),
|
||||
profile_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
confidence_score NUMERIC(6,4),
|
||||
profile_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_rc_student_profile_snapshot_user_version
|
||||
UNIQUE (user_id, profile_version),
|
||||
CONSTRAINT chk_rc_student_profile_snapshot_json
|
||||
CHECK (jsonb_typeof(profile_json) = 'object'),
|
||||
CONSTRAINT chk_rc_student_profile_snapshot_status
|
||||
CHECK (profile_status IN ('ACTIVE', 'ARCHIVED')),
|
||||
CONSTRAINT fk_rc_student_profile_snapshot_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_student_profile_snapshot IS '学员画像快照表';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_id IS '画像ID';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.user_id IS '学员ID';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_version IS '画像版本';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_date IS '画像日期';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.feature_source_version IS '特征来源版本';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_json IS '画像JSON';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.confidence_score IS '画像置信度';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_status IS '画像状态';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_student_profile_feature (
|
||||
feature_id VARCHAR(64) PRIMARY KEY,
|
||||
profile_id VARCHAR(64) NOT NULL,
|
||||
feature_group VARCHAR(32) NOT NULL,
|
||||
feature_key VARCHAR(64) NOT NULL,
|
||||
feature_value_num NUMERIC(18,6),
|
||||
feature_value_text VARCHAR(512),
|
||||
feature_value_json JSONB,
|
||||
value_type VARCHAR(16) NOT NULL DEFAULT 'NUM',
|
||||
source_domain VARCHAR(32) NOT NULL,
|
||||
source_ref_id VARCHAR(64),
|
||||
sample_time TIMESTAMP,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_rc_student_profile_feature_value_type
|
||||
CHECK (value_type IN ('NUM', 'TEXT', 'JSON', 'BOOL')),
|
||||
CONSTRAINT chk_rc_student_profile_feature_source_domain
|
||||
CHECK (source_domain IN ('COURSE', 'HOMEWORK', 'GRADING', 'REVIEW', 'RECOMMENDATION', 'ACHIEVEMENT', 'SYSTEM')),
|
||||
CONSTRAINT chk_rc_student_profile_feature_json
|
||||
CHECK (feature_value_json IS NULL OR jsonb_typeof(feature_value_json) IN ('object', 'array', 'string', 'number', 'boolean')),
|
||||
CONSTRAINT fk_rc_student_profile_feature_profile
|
||||
FOREIGN KEY (profile_id) REFERENCES recommendation.rc_student_profile_snapshot(profile_id) ON DELETE CASCADE
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_student_profile_feature IS '学员画像特征明细表';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_id IS '特征ID';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.profile_id IS '画像ID';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_group IS '特征组';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_key IS '特征键';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_num IS '数值特征值';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_text IS '文本特征值';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_json IS 'JSON特征值';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.value_type IS '值类型';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.source_domain IS '来源域';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.source_ref_id IS '来源对象ID';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.sample_time IS '采样时间';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_student_profile_feature.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS recommendation.rc_learning_loop_event CASCADE;
|
||||
DROP TABLE IF EXISTS recommendation.rc_learning_loop_case CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_case (
|
||||
loop_case_id VARCHAR(64) PRIMARY KEY,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
wrong_question_id VARCHAR(64) NOT NULL,
|
||||
review_plan_id VARCHAR(64),
|
||||
profile_id VARCHAR(64),
|
||||
recommendation_task_id VARCHAR(64),
|
||||
recommendation_item_id VARCHAR(64),
|
||||
feedback_id VARCHAR(64),
|
||||
user_achievement_id VARCHAR(64),
|
||||
loop_status VARCHAR(16) NOT NULL DEFAULT 'OPEN',
|
||||
effect_score NUMERIC(10,4),
|
||||
opened_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
closed_at TIMESTAMP,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_rc_learning_loop_case_status
|
||||
CHECK (loop_status IN ('OPEN', 'IN_PROGRESS', 'CLOSED', 'FAILED')),
|
||||
CONSTRAINT chk_rc_learning_loop_case_closed_at
|
||||
CHECK (closed_at IS NULL OR closed_at >= opened_at),
|
||||
CONSTRAINT fk_rc_learning_loop_case_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
|
||||
CONSTRAINT fk_rc_learning_loop_case_wrong_question
|
||||
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id),
|
||||
CONSTRAINT fk_rc_learning_loop_case_review_plan
|
||||
FOREIGN KEY (review_plan_id) REFERENCES question.gd_review_plan(review_plan_id),
|
||||
CONSTRAINT fk_rc_learning_loop_case_profile
|
||||
FOREIGN KEY (profile_id) REFERENCES recommendation.rc_student_profile_snapshot(profile_id),
|
||||
CONSTRAINT fk_rc_learning_loop_case_recommendation_task
|
||||
FOREIGN KEY (recommendation_task_id) REFERENCES recommendation.rc_recommendation_task(task_id),
|
||||
CONSTRAINT fk_rc_learning_loop_case_recommendation_item
|
||||
FOREIGN KEY (recommendation_item_id) REFERENCES recommendation.rc_recommendation_item(item_id),
|
||||
CONSTRAINT fk_rc_learning_loop_case_feedback
|
||||
FOREIGN KEY (feedback_id) REFERENCES recommendation.rc_recommendation_feedback(feedback_id),
|
||||
CONSTRAINT fk_rc_learning_loop_case_user_achievement
|
||||
FOREIGN KEY (user_achievement_id) REFERENCES achievement.ac_user_achievement(user_achievement_id)
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_learning_loop_case IS '学习闭环主表(错题-复习-推荐-成就)';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.loop_case_id IS '闭环案例ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.wrong_question_id IS '错题ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.review_plan_id IS '复习计划ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.profile_id IS '画像ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.recommendation_task_id IS '推荐任务ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.recommendation_item_id IS '推荐项ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.feedback_id IS '反馈事件ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.user_achievement_id IS '成就记录ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.loop_status IS '闭环状态';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.effect_score IS '闭环效果分';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.opened_at IS '闭环开启时间';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.closed_at IS '闭环关闭时间';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_case.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_event (
|
||||
loop_event_id VARCHAR(64) PRIMARY KEY,
|
||||
loop_case_id VARCHAR(64) NOT NULL,
|
||||
stage_code VARCHAR(32) NOT NULL,
|
||||
stage_status VARCHAR(16) NOT NULL DEFAULT 'DONE',
|
||||
event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
event_score NUMERIC(10,4),
|
||||
event_detail_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_rc_learning_loop_event_stage_code
|
||||
CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')),
|
||||
CONSTRAINT chk_rc_learning_loop_event_stage_status
|
||||
CHECK (stage_status IN ('PENDING', 'DONE', 'FAILED', 'SKIPPED')),
|
||||
CONSTRAINT chk_rc_learning_loop_event_detail_json
|
||||
CHECK (jsonb_typeof(event_detail_json) = 'object'),
|
||||
CONSTRAINT fk_rc_learning_loop_event_case
|
||||
FOREIGN KEY (loop_case_id) REFERENCES recommendation.rc_learning_loop_case(loop_case_id) ON DELETE CASCADE
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_learning_loop_event IS '学习闭环阶段事件表';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.loop_event_id IS '闭环事件ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.loop_case_id IS '闭环案例ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.stage_code IS '阶段编码';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.stage_status IS '阶段状态';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_time IS '事件时间';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_score IS '阶段得分';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_detail_json IS '事件详情JSON';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_event.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS recommendation.rc_learning_loop_effect_daily CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_effect_daily (
|
||||
stat_date DATE NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
opened_case_count BIGINT NOT NULL DEFAULT 0,
|
||||
closed_case_count BIGINT NOT NULL DEFAULT 0,
|
||||
achieved_case_count BIGINT NOT NULL DEFAULT 0,
|
||||
avg_effect_score NUMERIC(10,4) NOT NULL DEFAULT 0,
|
||||
avg_close_hours NUMERIC(10,4) NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (stat_date, tenant_id)
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_learning_loop_effect_daily IS '学习闭环效果日统计表';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.stat_date IS '统计日期';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.opened_case_count IS '开启案例数';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.closed_case_count IS '关闭案例数';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.achieved_case_count IS '达成成就案例数';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.avg_effect_score IS '平均效果分';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.avg_close_hours IS '平均闭环时长(小时)';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.updated_at IS '更新时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_user_date
|
||||
ON recommendation.rc_student_profile_snapshot(user_id, profile_date DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_tenant_version
|
||||
ON recommendation.rc_student_profile_snapshot(tenant_id, profile_version, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_profile_group
|
||||
ON recommendation.rc_student_profile_feature(profile_id, feature_group, feature_key);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_source
|
||||
ON recommendation.rc_student_profile_feature(source_domain, source_ref_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_user_status
|
||||
ON recommendation.rc_learning_loop_case(user_id, loop_status, opened_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_wrong_question
|
||||
ON recommendation.rc_learning_loop_case(wrong_question_id, opened_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_tenant_status
|
||||
ON recommendation.rc_learning_loop_case(tenant_id, loop_status, opened_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_event_case_stage_time
|
||||
ON recommendation.rc_learning_loop_event(loop_case_id, stage_code, event_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_event_tenant_time
|
||||
ON recommendation.rc_learning_loop_event(tenant_id, event_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_effect_daily_tenant_date
|
||||
ON recommendation.rc_learning_loop_effect_daily(tenant_id, stat_date DESC);
|
||||
|
||||
DROP TABLE IF EXISTS recommendation.rc_loop_metric_map CASCADE;
|
||||
DROP TABLE IF EXISTS recommendation.rc_loop_stage_achievement_map CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_loop_stage_achievement_map (
|
||||
stage_map_id VARCHAR(64) PRIMARY KEY,
|
||||
stage_code VARCHAR(32) NOT NULL,
|
||||
event_code VARCHAR(64) NOT NULL,
|
||||
template_id VARCHAR(64),
|
||||
metric_id VARCHAR(64),
|
||||
award_source VARCHAR(32) NOT NULL DEFAULT 'RULE_ENGINE',
|
||||
priority INTEGER NOT NULL DEFAULT 100,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
ext_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_rc_loop_stage_achievement_map
|
||||
UNIQUE (tenant_id, stage_code, event_code, priority),
|
||||
CONSTRAINT chk_rc_loop_stage_achievement_map_stage_code
|
||||
CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')),
|
||||
CONSTRAINT chk_rc_loop_stage_achievement_map_award_source
|
||||
CHECK (award_source IN ('RULE_ENGINE', 'MANUAL', 'SYSTEM')),
|
||||
CONSTRAINT chk_rc_loop_stage_achievement_map_ext_json
|
||||
CHECK (jsonb_typeof(ext_json) = 'object'),
|
||||
CONSTRAINT fk_rc_loop_stage_achievement_map_event
|
||||
FOREIGN KEY (event_code) REFERENCES achievement.ac_achievement_event_dict(event_code),
|
||||
CONSTRAINT fk_rc_loop_stage_achievement_map_template
|
||||
FOREIGN KEY (template_id) REFERENCES achievement.ac_achievement_rule_template(template_id),
|
||||
CONSTRAINT fk_rc_loop_stage_achievement_map_metric
|
||||
FOREIGN KEY (metric_id) REFERENCES achievement.ac_achievement_metric_def(metric_id)
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_loop_stage_achievement_map IS '闭环阶段到成就事件映射表(通用配置)';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.stage_map_id IS '阶段映射ID';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.stage_code IS '闭环阶段编码';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.event_code IS '成就事件编码';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.template_id IS '成就规则模板ID';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.metric_id IS '成就指标ID';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.award_source IS '发放来源';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.priority IS '优先级';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.enabled IS '是否启用';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.ext_json IS '扩展配置JSON';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS recommendation.rc_loop_metric_map (
|
||||
metric_map_id VARCHAR(64) PRIMARY KEY,
|
||||
stage_code VARCHAR(32) NOT NULL,
|
||||
metric_id VARCHAR(64) NOT NULL,
|
||||
source_field VARCHAR(64) NOT NULL,
|
||||
agg_method VARCHAR(16) NOT NULL DEFAULT 'LATEST',
|
||||
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
ext_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_rc_loop_metric_map
|
||||
UNIQUE (tenant_id, stage_code, metric_id, source_field),
|
||||
CONSTRAINT chk_rc_loop_metric_map_stage_code
|
||||
CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')),
|
||||
CONSTRAINT chk_rc_loop_metric_map_agg_method
|
||||
CHECK (agg_method IN ('LATEST', 'SUM', 'AVG', 'MAX', 'MIN', 'COUNT')),
|
||||
CONSTRAINT chk_rc_loop_metric_map_weight
|
||||
CHECK (weight > 0 AND weight <= 1),
|
||||
CONSTRAINT chk_rc_loop_metric_map_ext_json
|
||||
CHECK (jsonb_typeof(ext_json) = 'object'),
|
||||
CONSTRAINT fk_rc_loop_metric_map_metric
|
||||
FOREIGN KEY (metric_id) REFERENCES achievement.ac_achievement_metric_def(metric_id)
|
||||
);
|
||||
COMMENT ON TABLE recommendation.rc_loop_metric_map IS '闭环指标映射表(通用配置)';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.metric_map_id IS '指标映射ID';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.stage_code IS '闭环阶段编码';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.metric_id IS '成就指标ID';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.source_field IS '来源字段名';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.agg_method IS '聚合方法';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.weight IS '权重';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.enabled IS '是否启用';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.ext_json IS '扩展配置JSON';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN recommendation.rc_loop_metric_map.updated_at IS '更新时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_tenant_stage
|
||||
ON recommendation.rc_loop_stage_achievement_map(tenant_id, stage_code, enabled, priority);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_event
|
||||
ON recommendation.rc_loop_stage_achievement_map(event_code, tenant_id, enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_rc_loop_metric_map_tenant_stage
|
||||
ON recommendation.rc_loop_metric_map(tenant_id, stage_code, enabled);
|
||||
@@ -1,4 +1,5 @@
|
||||
-- 学校租户表
|
||||
DROP TABLE IF EXISTS upms.tb_sys_tenant CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_tenant (
|
||||
tenant_id VARCHAR(64) PRIMARY KEY,
|
||||
parent_tenant_id VARCHAR(64),
|
||||
@@ -19,6 +20,7 @@ COMMENT ON COLUMN upms.tb_sys_tenant.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_tenant.status IS '租户状态';
|
||||
COMMENT ON COLUMN upms.tb_sys_tenant.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS upms.tb_sys_dept CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_dept (
|
||||
dept_id VARCHAR(64) PRIMARY KEY,
|
||||
parent_dept_id VARCHAR(64),
|
||||
@@ -41,6 +43,10 @@ COMMENT ON COLUMN upms.tb_sys_dept.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_dept.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_dept.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS upms.tb_school_class_course_rel CASCADE;
|
||||
DROP TABLE IF EXISTS upms.tb_school_class_member CASCADE;
|
||||
DROP TABLE IF EXISTS upms.tb_school_class CASCADE;
|
||||
DROP TABLE IF EXISTS upms.tb_sys_user CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_user (
|
||||
user_id VARCHAR(64) PRIMARY KEY,
|
||||
username VARCHAR(64) UNIQUE NOT NULL,
|
||||
@@ -66,7 +72,132 @@ COMMENT ON COLUMN upms.tb_sys_user.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_user.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_user.status IS '用户状态';
|
||||
COMMENT ON COLUMN upms.tb_sys_user.created_at IS '创建时间';
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_school_class (
|
||||
class_id VARCHAR(64) PRIMARY KEY,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
dept_id VARCHAR(64) NOT NULL,
|
||||
class_code VARCHAR(64),
|
||||
class_name VARCHAR(128) NOT NULL,
|
||||
grade_code VARCHAR(32),
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
|
||||
adcode VARCHAR(12),
|
||||
tenant_path VARCHAR(255),
|
||||
dept_path VARCHAR(255),
|
||||
created_by VARCHAR(64),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_school_class_status
|
||||
CHECK (status IN ('ACTIVE', 'INACTIVE', 'ARCHIVED')),
|
||||
CONSTRAINT fk_school_class_dept
|
||||
FOREIGN KEY (dept_id) REFERENCES upms.tb_sys_dept(dept_id),
|
||||
CONSTRAINT fk_school_class_created_by
|
||||
FOREIGN KEY (created_by) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_school_class IS '班级主表';
|
||||
COMMENT ON COLUMN upms.tb_school_class.class_id IS '班级ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class.dept_id IS '所属部门ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class.class_code IS '班级编码';
|
||||
COMMENT ON COLUMN upms.tb_school_class.class_name IS '班级名称';
|
||||
COMMENT ON COLUMN upms.tb_school_class.grade_code IS '年级编码';
|
||||
COMMENT ON COLUMN upms.tb_school_class.status IS '班级状态';
|
||||
COMMENT ON COLUMN upms.tb_school_class.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN upms.tb_school_class.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_school_class.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN upms.tb_school_class.created_by IS '创建人';
|
||||
COMMENT ON COLUMN upms.tb_school_class.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN upms.tb_school_class.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_school_class_member (
|
||||
class_id VARCHAR(64) NOT NULL,
|
||||
user_id VARCHAR(64) NOT NULL,
|
||||
member_role VARCHAR(16) NOT NULL DEFAULT 'STUDENT',
|
||||
member_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
joined_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
left_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (class_id, user_id),
|
||||
CONSTRAINT chk_school_class_member_role
|
||||
CHECK (member_role IN ('STUDENT', 'HEAD_TEACHER', 'TEACHER', 'ASSISTANT')),
|
||||
CONSTRAINT chk_school_class_member_status
|
||||
CHECK (member_status IN ('ACTIVE', 'LEFT', 'SUSPENDED')),
|
||||
CONSTRAINT chk_school_class_member_left_at
|
||||
CHECK (left_at IS NULL OR left_at >= joined_at),
|
||||
CONSTRAINT fk_school_class_member_class
|
||||
FOREIGN KEY (class_id) REFERENCES upms.tb_school_class(class_id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_school_class_member_user
|
||||
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_school_class_member IS '班级成员表';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.class_id IS '班级ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.user_id IS '成员用户ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.member_role IS '成员角色';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.member_status IS '成员状态';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.joined_at IS '加入时间';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.left_at IS '离开时间';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN upms.tb_school_class_member.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_school_class_course_rel (
|
||||
class_id VARCHAR(64) NOT NULL,
|
||||
course_id VARCHAR(64) NOT NULL,
|
||||
relation_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (class_id, course_id),
|
||||
CONSTRAINT chk_school_class_course_rel_status
|
||||
CHECK (relation_status IN ('ACTIVE', 'INACTIVE')),
|
||||
CONSTRAINT fk_school_class_course_rel_class
|
||||
FOREIGN KEY (class_id) REFERENCES upms.tb_school_class(class_id) ON DELETE CASCADE
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_school_class_course_rel IS '班级课程关联表';
|
||||
COMMENT ON COLUMN upms.tb_school_class_course_rel.class_id IS '班级ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class_course_rel.course_id IS '课程ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class_course_rel.relation_status IS '关联状态';
|
||||
COMMENT ON COLUMN upms.tb_school_class_course_rel.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN upms.tb_school_class_course_rel.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN upms.tb_school_class_course_rel.updated_at IS '更新时间';
|
||||
|
||||
DROP TABLE IF EXISTS upms.tb_sys_file CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_file (
|
||||
file_id VARCHAR(64) PRIMARY KEY,
|
||||
media_type VARCHAR(32) NOT NULL,
|
||||
object_key VARCHAR(512) NOT NULL,
|
||||
file_name VARCHAR(256),
|
||||
mime_type VARCHAR(128),
|
||||
file_size BIGINT,
|
||||
file_hash VARCHAR(128),
|
||||
duration_ms INTEGER,
|
||||
uploaded_by VARCHAR(64),
|
||||
adcode VARCHAR(12),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_sys_file_media_type
|
||||
CHECK (media_type IN ('IMAGE', 'AUDIO', 'VIDEO', 'DOCUMENT', 'OTHER')),
|
||||
CONSTRAINT fk_sys_file_uploaded_by
|
||||
FOREIGN KEY (uploaded_by) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_sys_file IS '系统文件资源表';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.file_id IS '文件ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.media_type IS '媒体类型';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.object_key IS '对象存储Key';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.file_name IS '文件名';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.mime_type IS 'MIME类型';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.file_size IS '文件大小(字节)';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.file_hash IS '文件哈希';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.duration_ms IS '音频/视频时长(毫秒)';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.uploaded_by IS '上传人ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_file.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS upms.tb_sys_role CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_role (
|
||||
role_id VARCHAR(64) PRIMARY KEY,
|
||||
role_code VARCHAR(64) UNIQUE NOT NULL,
|
||||
@@ -89,28 +220,8 @@ COMMENT ON COLUMN upms.tb_sys_role.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_role.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_role.created_at IS '创建时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_permission (
|
||||
permission_id VARCHAR(64) PRIMARY KEY,
|
||||
permission_code VARCHAR(128) UNIQUE NOT NULL,
|
||||
permission_name VARCHAR(128) NOT NULL,
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_sys_permission IS '权限表';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.permission_id IS '权限ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.permission_code IS '权限编码';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.permission_name IS '权限名称';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_permission.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS upms.tb_sys_menu CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_menu (
|
||||
route_id VARCHAR(64) PRIMARY KEY,
|
||||
parent_route_id VARCHAR(64),
|
||||
@@ -146,8 +257,181 @@ COMMENT ON COLUMN upms.tb_sys_menu.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_menu.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_menu.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_menu.created_at IS '创建时间';
|
||||
DROP TABLE IF EXISTS upms.tb_sys_role_menu CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_role_menu (
|
||||
role_id VARCHAR(64) NOT NULL,
|
||||
route_id VARCHAR(64) NOT NULL,
|
||||
adcode VARCHAR(12) NOT NULL,
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255) NOT NULL,
|
||||
dept_id VARCHAR(64),
|
||||
dept_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (role_id, route_id)
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_sys_role_menu IS '角色菜单授权表';
|
||||
COMMENT ON COLUMN upms.tb_sys_role_menu.role_id IS '角色ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_role_menu.route_id IS '路由ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_role_menu.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN upms.tb_sys_role_menu.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_role_menu.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_role_menu.dept_id IS '部门ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_role_menu.dept_path IS '部门路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_role_menu.created_at IS '创建时间';
|
||||
|
||||
DROP TABLE IF EXISTS upms.tb_sys_area CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_area (
|
||||
id BIGINT PRIMARY KEY,
|
||||
pid BIGINT NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
letter VARCHAR(32),
|
||||
adcode BIGINT NOT NULL,
|
||||
location VARCHAR(255),
|
||||
area_sort INTEGER,
|
||||
area_status VARCHAR(8) NOT NULL DEFAULT '1',
|
||||
area_type VARCHAR(8) NOT NULL DEFAULT '2',
|
||||
hot VARCHAR(8) NOT NULL DEFAULT '0',
|
||||
city_code VARCHAR(16),
|
||||
create_by VARCHAR(64),
|
||||
create_time TIMESTAMP,
|
||||
update_by VARCHAR(64),
|
||||
update_time TIMESTAMP,
|
||||
del_flag VARCHAR(8) NOT NULL DEFAULT '0'
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_sys_area IS '行政区划表';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.id IS '主键ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.pid IS '父级行政区划编码';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.name IS '名称';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.letter IS '首字母';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.location IS '经纬度';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.area_sort IS '排序';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.area_status IS '状态';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.area_type IS '层级类型';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.hot IS '是否热门';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.city_code IS '城市区号';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.create_by IS '创建人';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.create_time IS '创建时间';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.update_by IS '更新人';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.update_time IS '更新时间';
|
||||
COMMENT ON COLUMN upms.tb_sys_area.del_flag IS '删除标记';
|
||||
DROP TABLE IF EXISTS upms.tb_sys_message_recipient CASCADE;
|
||||
DROP TABLE IF EXISTS upms.tb_sys_message CASCADE;
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_message (
|
||||
message_id VARCHAR(64) PRIMARY KEY,
|
||||
message_type VARCHAR(32) NOT NULL DEFAULT 'INFO',
|
||||
biz_type VARCHAR(64) NOT NULL,
|
||||
title VARCHAR(256) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
content_object_type VARCHAR(64) NOT NULL,
|
||||
content_object_id VARCHAR(64) NOT NULL,
|
||||
web_jump_url VARCHAR(512) NOT NULL,
|
||||
send_channel VARCHAR(16) NOT NULL DEFAULT 'INBOX',
|
||||
sender_user_id VARCHAR(64),
|
||||
adcode VARCHAR(12),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255),
|
||||
message_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
|
||||
send_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
expire_at TIMESTAMP,
|
||||
ext_json JSONB NOT NULL DEFAULT '{}'::JSONB,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT chk_sys_message_type
|
||||
CHECK (message_type IN ('TODO', 'INFO', 'ALERT', 'SYSTEM')),
|
||||
CONSTRAINT chk_sys_message_channel
|
||||
CHECK (send_channel IN ('INBOX')),
|
||||
CONSTRAINT chk_sys_message_status
|
||||
CHECK (message_status IN ('ACTIVE', 'CANCELLED', 'EXPIRED')),
|
||||
CONSTRAINT chk_sys_message_url
|
||||
CHECK (web_jump_url LIKE '/%'),
|
||||
CONSTRAINT chk_sys_message_expire
|
||||
CHECK (expire_at IS NULL OR expire_at >= send_at),
|
||||
CONSTRAINT chk_sys_message_ext_json
|
||||
CHECK (jsonb_typeof(ext_json) = 'object'),
|
||||
CONSTRAINT fk_sys_message_sender
|
||||
FOREIGN KEY (sender_user_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_sys_message IS '站内信消息主表';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.message_id IS '消息ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.message_type IS '消息类型(TODO/INFO/ALERT/SYSTEM)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.biz_type IS '业务类型(如 SUBJECTIVE_REVIEW)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.title IS '消息标题';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.content IS '消息正文';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.content_object_type IS '内容对象类型(如 GRADING_TASK/TENANT)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.content_object_id IS '内容对象ID(用于业务定位)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.web_jump_url IS 'Web跳转URL(前端点击消息后跳转)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.send_channel IS '发送通道(当前仅INBOX)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.sender_user_id IS '发送人ID(系统消息可为空)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.adcode IS '行政区划编码';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.message_status IS '消息状态(ACTIVE/CANCELLED/EXPIRED)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.send_at IS '发送时间';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.expire_at IS '过期时间';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.ext_json IS '扩展字段JSON';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN upms.tb_sys_message.updated_at IS '更新时间';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS upms.tb_sys_message_recipient (
|
||||
message_id VARCHAR(64) NOT NULL,
|
||||
recipient_user_id VARCHAR(64) NOT NULL,
|
||||
delivery_status VARCHAR(16) NOT NULL DEFAULT 'DELIVERED',
|
||||
read_status VARCHAR(16) NOT NULL DEFAULT 'UNREAD',
|
||||
read_at TIMESTAMP,
|
||||
clicked_at TIMESTAMP,
|
||||
read_source VARCHAR(16),
|
||||
tenant_id VARCHAR(64) NOT NULL,
|
||||
tenant_path VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (message_id, recipient_user_id),
|
||||
CONSTRAINT chk_sys_message_recipient_delivery
|
||||
CHECK (delivery_status IN ('DELIVERED', 'FAILED', 'RECALLED')),
|
||||
CONSTRAINT chk_sys_message_recipient_read
|
||||
CHECK (read_status IN ('UNREAD', 'READ', 'ARCHIVED')),
|
||||
CONSTRAINT chk_sys_message_recipient_read_source
|
||||
CHECK (read_source IS NULL OR read_source IN ('WEB', 'APP', 'MINI_PROGRAM')),
|
||||
CONSTRAINT chk_sys_message_recipient_read_at
|
||||
CHECK (
|
||||
(read_status = 'UNREAD' AND read_at IS NULL)
|
||||
OR (read_status IN ('READ', 'ARCHIVED') AND read_at IS NOT NULL)
|
||||
),
|
||||
CONSTRAINT chk_sys_message_recipient_click_at
|
||||
CHECK (clicked_at IS NULL OR read_at IS NULL OR clicked_at >= read_at),
|
||||
CONSTRAINT fk_sys_message_recipient_message
|
||||
FOREIGN KEY (message_id) REFERENCES upms.tb_sys_message(message_id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_sys_message_recipient_user
|
||||
FOREIGN KEY (recipient_user_id) REFERENCES upms.tb_sys_user(user_id)
|
||||
);
|
||||
COMMENT ON TABLE upms.tb_sys_message_recipient IS '站内信收件人状态表';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.message_id IS '消息ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.recipient_user_id IS '收件人用户ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.delivery_status IS '投递状态(DELIVERED/FAILED/RECALLED)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.read_status IS '读取状态(UNREAD/READ/ARCHIVED)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.read_at IS '读取时间';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.clicked_at IS '点击跳转时间';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.read_source IS '读取来源(WEB/APP/MINI_PROGRAM)';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.tenant_id IS '租户ID';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.tenant_path IS '租户路径';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN upms.tb_sys_message_recipient.updated_at IS '更新时间';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_tenant_adcode ON upms.tb_sys_tenant(adcode);
|
||||
CREATE INDEX IF NOT EXISTS idx_dept_tenant ON upms.tb_sys_dept(tenant_id, dept_path);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_tenant ON upms.tb_sys_user(tenant_id, dept_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_school_class_tenant_dept ON upms.tb_school_class(tenant_id, dept_id, grade_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_school_class_member_tenant_user ON upms.tb_school_class_member(tenant_id, user_id, member_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_school_class_course_rel_tenant_course ON upms.tb_school_class_course_rel(tenant_id, course_id, relation_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_file_tenant_media ON upms.tb_sys_file(tenant_id, media_type, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_file_uploaded_by ON upms.tb_sys_file(uploaded_by, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_route_tenant ON upms.tb_sys_menu(tenant_id, route_path);
|
||||
CREATE INDEX IF NOT EXISTS idx_role_menu_tenant ON upms.tb_sys_role_menu(tenant_id, role_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_area_pid ON upms.tb_sys_area(pid);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_area_adcode ON upms.tb_sys_area(adcode);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_message_tenant_biz ON upms.tb_sys_message(tenant_id, biz_type, send_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_message_object ON upms.tb_sys_message(content_object_type, content_object_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_message_status_send_at ON upms.tb_sys_message(message_status, send_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_message_recipient_user_status ON upms.tb_sys_message_recipient(recipient_user_id, read_status, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_message_recipient_tenant_user ON upms.tb_sys_message_recipient(tenant_id, recipient_user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_sys_message_recipient_unread ON upms.tb_sys_message_recipient(recipient_user_id, created_at DESC) WHERE read_status = 'UNREAD';
|
||||
|
||||
@@ -25,12 +25,6 @@ INSERT INTO upms.tb_sys_role (
|
||||
('ROLE-ORG-ADMIN', 'ORG_ADMIN', '机构管理员', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/')
|
||||
ON CONFLICT (role_id) DO NOTHING;
|
||||
|
||||
INSERT INTO upms.tb_sys_permission (
|
||||
permission_id, permission_code, permission_name, adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
) VALUES
|
||||
('PERM-DASHBOARD', 'dashboard:view', '查看控制台', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'),
|
||||
('PERM-TENANT', 'tenant:view', '查看租户组织', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/')
|
||||
ON CONFLICT (permission_id) DO NOTHING;
|
||||
|
||||
INSERT INTO upms.tb_sys_menu (
|
||||
route_id, parent_route_id, route_path, route_name, component_key, layout_type, title, icon, permission_code,
|
||||
@@ -41,3 +35,84 @@ INSERT INTO upms.tb_sys_menu (
|
||||
('ROUTE-TENANT', NULL, '/tenant', 'tenant-management', 'tenant', 'SIDEBAR', '租户组织', 'building-2', 'tenant:view', FALSE,
|
||||
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/')
|
||||
ON CONFLICT (route_id) DO NOTHING;
|
||||
|
||||
INSERT INTO upms.tb_sys_role_menu (
|
||||
role_id, route_id, adcode, tenant_id, tenant_path, dept_id, dept_path
|
||||
) VALUES
|
||||
('ROLE-ORG-ADMIN', 'ROUTE-DASHBOARD', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'),
|
||||
('ROLE-ORG-ADMIN', 'ROUTE-TENANT', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/')
|
||||
ON CONFLICT (role_id, route_id) DO NOTHING;
|
||||
|
||||
INSERT INTO upms.tb_sys_message (
|
||||
message_id, message_type, biz_type, title, content, content_object_type, content_object_id, web_jump_url,
|
||||
send_channel, sender_user_id, adcode, tenant_id, tenant_path, message_status, send_at, ext_json
|
||||
) VALUES
|
||||
(
|
||||
'MSG-20260415001',
|
||||
'TODO',
|
||||
'SUBJECTIVE_REVIEW',
|
||||
'有 1 份主观题待审阅',
|
||||
'高一数学《函数综合训练》有主观题待你审阅,请尽快处理。',
|
||||
'GRADING_TASK',
|
||||
'GD-TASK-20260415001',
|
||||
'/grading/review?gradingTaskId=GD-TASK-20260415001&messageId=MSG-20260415001',
|
||||
'INBOX',
|
||||
NULL,
|
||||
'330100',
|
||||
'SCH-HQ',
|
||||
'/SCH-HQ/',
|
||||
'ACTIVE',
|
||||
CURRENT_TIMESTAMP,
|
||||
'{"priority":"high","source":"grading"}'::JSONB
|
||||
),
|
||||
(
|
||||
'MSG-20260415002',
|
||||
'INFO',
|
||||
'TENANT_NOTICE',
|
||||
'杭州分校租户信息有更新',
|
||||
'杭州分校基础资料已更新,可进入租户组织页查看详情。',
|
||||
'TENANT',
|
||||
'SCH-ZJ-HZ-01',
|
||||
'/tenant?tenantId=SCH-ZJ-HZ-01&messageId=MSG-20260415002',
|
||||
'INBOX',
|
||||
NULL,
|
||||
'330100',
|
||||
'SCH-HQ',
|
||||
'/SCH-HQ/',
|
||||
'ACTIVE',
|
||||
CURRENT_TIMESTAMP,
|
||||
'{"priority":"normal","source":"upms"}'::JSONB
|
||||
)
|
||||
ON CONFLICT (message_id) DO NOTHING;
|
||||
|
||||
INSERT INTO upms.tb_sys_message_recipient (
|
||||
message_id, recipient_user_id, delivery_status, read_status, read_at, clicked_at, read_source,
|
||||
tenant_id, tenant_path, created_at, updated_at
|
||||
) VALUES
|
||||
(
|
||||
'MSG-20260415001',
|
||||
'U10001',
|
||||
'DELIVERED',
|
||||
'UNREAD',
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
'SCH-HQ',
|
||||
'/SCH-HQ/',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
),
|
||||
(
|
||||
'MSG-20260415002',
|
||||
'U10001',
|
||||
'DELIVERED',
|
||||
'READ',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP,
|
||||
'WEB',
|
||||
'SCH-HQ',
|
||||
'/SCH-HQ/',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
ON CONFLICT (message_id, recipient_user_id) DO NOTHING;
|
||||
|
||||