From d5c06eca2886b3284857a9ab5400aa949b592c19 Mon Sep 17 00:00:00 2001
From: wangys <3401275564@qq.com>
Date: Thu, 16 Apr 2026 15:46:29 +0800
Subject: [PATCH] =?UTF-8?q?sql=E6=9B=B4=E6=96=B0=E3=80=81=E6=9E=B6?=
=?UTF-8?q?=E6=9E=84=E6=9B=B4=E6=96=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.devcontainer/external/devcontainer.json | 2 +-
.devcontainer/external/docker-compose.yml | 7 +
.devcontainer/internal/devcontainer.json | 15 +-
.devcontainer/internal/docker-compose.yml | 116 +
.../docker-compose.public-services.yml | 96 +
docker/dev-common/docker-compose.yml | 109 +
docs/1/postgres - k12study - achievement.svg | 74980 ---------
docs/1/postgres - k12study - ai.graphml | 178 -
docs/1/postgres - k12study - auth.svg | 12945 --
docs/1/postgres - k12study - course.svg | 90910 ----------
docs/1/postgres - k12study - question.svg | 128021 ---------------
.../postgres - k12study - recommendation.svg | 79021 ---------
docs/architecture.md | 135 +-
docs/architecture/README.md | 30 +
docs/architecture/api-design.md | 82 +
docs/architecture/base-services.md | 59 +
docs/architecture/er-diagram.md | 77 +
docs/architecture/logical-view.md | 77 +
.../modules/01-课程学习/er图.drawio | 20 +-
.../modules/01-课程学习/数据流图.drawio | 2 +-
.../modules/02-习题与作业/er图.drawio | 16 +-
.../modules/03-批改与反馈/er图.drawio | 18 +-
.../modules/03-批改与反馈/数据流图.drawio | 16 +-
docs/architecture/代码架构.drawio | 136 +
docs/architecture/完整业务流程图.drawio | 97 +-
docs/architecture/数据流图(多角色).drawio | 330 +-
docs/architecture/系统架构.drawio | 205 +
docs/architecture/系统架构与代码架构.drawio | 450 -
docs/milestone.md | 81 +
docs/plan/architecture.md | 205 -
...学习-习题-批改ER与对象类数据流图实施计划.md | 44 -
init/pg/01_create_schema.sql | 1 -
init/pg/02_init_modules.sql | 1 -
.../pg/question/10_create_question_tables.sql | 545 +-
init/pg/question/20_init_question_seed.sql | 47 +
.../10_create_recommendation_tables.sql | 440 -
36 files changed, 2099 insertions(+), 387415 deletions(-)
delete mode 100644 docs/1/postgres - k12study - achievement.svg
delete mode 100644 docs/1/postgres - k12study - ai.graphml
delete mode 100644 docs/1/postgres - k12study - auth.svg
delete mode 100644 docs/1/postgres - k12study - course.svg
delete mode 100644 docs/1/postgres - k12study - question.svg
delete mode 100644 docs/1/postgres - k12study - recommendation.svg
create mode 100644 docs/architecture/README.md
create mode 100644 docs/architecture/api-design.md
create mode 100644 docs/architecture/base-services.md
create mode 100644 docs/architecture/er-diagram.md
create mode 100644 docs/architecture/logical-view.md
create mode 100644 docs/architecture/代码架构.drawio
create mode 100644 docs/architecture/系统架构.drawio
delete mode 100644 docs/architecture/系统架构与代码架构.drawio
create mode 100644 docs/milestone.md
delete mode 100644 docs/plan/architecture.md
delete mode 100644 docs/plan/课程学习-习题-批改ER与对象类数据流图实施计划.md
delete mode 100644 init/pg/recommendation/10_create_recommendation_tables.sql
diff --git a/.devcontainer/external/devcontainer.json b/.devcontainer/external/devcontainer.json
index 451c49a..b57984e 100644
--- a/.devcontainer/external/devcontainer.json
+++ b/.devcontainer/external/devcontainer.json
@@ -6,7 +6,7 @@
"service": "workspace",
"workspaceFolder": "/workspaces/K12study",
"shutdownAction": "stopCompose",
- "forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000],
+ "forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000, 8848, 9848, 9849, 9559, 9779, 9669, 19530, 9091],
"customizations": {
"vscode": {
"settings": {
diff --git a/.devcontainer/external/docker-compose.yml b/.devcontainer/external/docker-compose.yml
index 5dfd407..305bba1 100644
--- a/.devcontainer/external/docker-compose.yml
+++ b/.devcontainer/external/docker-compose.yml
@@ -18,6 +18,13 @@ services:
K12STUDY_REGISTRY_PORT: ${K12STUDY_REGISTRY_PORT:-8848}
K12STUDY_REGISTRY_GRPC_PORT: ${K12STUDY_REGISTRY_GRPC_PORT:-9848}
K12STUDY_REGISTRY_RAFT_PORT: ${K12STUDY_REGISTRY_RAFT_PORT:-9849}
+ K12STUDY_GRAPH_HOST: ${K12STUDY_GRAPH_HOST:-host.docker.internal}
+ K12STUDY_GRAPH_PORT: ${K12STUDY_GRAPH_PORT:-9669}
+ K12STUDY_GRAPH_USER: ${K12STUDY_GRAPH_USER:-root}
+ K12STUDY_GRAPH_PASSWORD: ${K12STUDY_GRAPH_PASSWORD:-nebula}
+ K12STUDY_VECTOR_HOST: ${K12STUDY_VECTOR_HOST:-host.docker.internal}
+ K12STUDY_VECTOR_PORT: ${K12STUDY_VECTOR_PORT:-19530}
+ K12STUDY_VECTOR_HTTP_PORT: ${K12STUDY_VECTOR_HTTP_PORT:-9091}
PYTHONUNBUFFERED: "1"
extra_hosts:
- "host.docker.internal:host-gateway"
diff --git a/.devcontainer/internal/devcontainer.json b/.devcontainer/internal/devcontainer.json
index 79c4989..61dad36 100644
--- a/.devcontainer/internal/devcontainer.json
+++ b/.devcontainer/internal/devcontainer.json
@@ -5,9 +5,20 @@
],
"service": "workspace",
"workspaceFolder": "/workspaces/K12study",
- "runServices": ["workspace", "postgres", "redis"],
+ "runServices": [
+ "workspace",
+ "postgres",
+ "redis",
+ "nacos",
+ "nebula-metad",
+ "nebula-storaged",
+ "nebula-graphd",
+ "milvus-etcd",
+ "milvus-minio",
+ "milvus"
+ ],
"shutdownAction": "stopCompose",
- "forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000, 5432, 6379],
+ "forwardPorts": [5173, 8080, 8081, 8082, 8088, 9000, 5432, 6379, 8848, 9848, 9849, 9559, 9779, 9669, 19530, 9091],
"customizations": {
"vscode": {
"settings": {
diff --git a/.devcontainer/internal/docker-compose.yml b/.devcontainer/internal/docker-compose.yml
index 15f172a..8de14a7 100644
--- a/.devcontainer/internal/docker-compose.yml
+++ b/.devcontainer/internal/docker-compose.yml
@@ -14,10 +14,24 @@ services:
K12STUDY_REDIS_HOST: redis
K12STUDY_REDIS_PORT: 6379
K12STUDY_REDIS_PASSWORD: ""
+ K12STUDY_REGISTRY_HOST: nacos
+ K12STUDY_REGISTRY_PORT: 8848
+ K12STUDY_REGISTRY_GRPC_PORT: 9848
+ K12STUDY_REGISTRY_RAFT_PORT: 9849
+ K12STUDY_GRAPH_HOST: nebula-graphd
+ K12STUDY_GRAPH_PORT: 9669
+ K12STUDY_GRAPH_USER: root
+ K12STUDY_GRAPH_PASSWORD: nebula
+ K12STUDY_VECTOR_HOST: milvus
+ K12STUDY_VECTOR_PORT: 19530
+ K12STUDY_VECTOR_HTTP_PORT: 9091
PYTHONUNBUFFERED: "1"
depends_on:
- postgres
- redis
+ - nacos
+ - nebula-graphd
+ - milvus
postgres:
image: postgres:16
@@ -40,7 +54,109 @@ services:
command: redis-server --appendonly yes
volumes:
- redis-dev-data:/data
+ nacos:
+ image: nacos/nacos-server:v3.2.1-2026.04.03
+ restart: unless-stopped
+ environment:
+ MODE: standalone
+ NACOS_AUTH_ENABLE: "false"
+ TZ: Asia/Shanghai
+ JVM_XMS: 256m
+ JVM_XMX: 512m
+ JVM_XMN: 128m
+ ports:
+ - "8848:8848"
+ - "9848:9848"
+ - "9849:9849"
+ volumes:
+ - nacos-dev-logs:/home/nacos/logs
+ - nacos-dev-data:/home/nacos/data
+ nebula-metad:
+ image: vesoft/nebula-metad:v3.8.0
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-metad
+ - --ws_ip=0.0.0.0
+ - --port=9559
+ - --data_path=/data/meta
+ ports:
+ - "9559:9559"
+ volumes:
+ - nebula-meta-dev-data:/data/meta
+ nebula-storaged:
+ image: vesoft/nebula-storaged:v3.8.0
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-storaged
+ - --ws_ip=0.0.0.0
+ - --port=9779
+ - --data_path=/data/storage
+ depends_on:
+ - nebula-metad
+ ports:
+ - "9779:9779"
+ volumes:
+ - nebula-storage-dev-data:/data/storage
+ nebula-graphd:
+ image: vesoft/nebula-graphd:v3.8.0
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-graphd
+ - --ws_ip=0.0.0.0
+ - --port=9669
+ depends_on:
+ - nebula-metad
+ - nebula-storaged
+ ports:
+ - "9669:9669"
+ milvus-etcd:
+ image: quay.io/coreos/etcd:v3.5.5
+ restart: unless-stopped
+ environment:
+ ETCD_AUTO_COMPACTION_MODE: revision
+ ETCD_AUTO_COMPACTION_RETENTION: "1000"
+ ETCD_QUOTA_BACKEND_BYTES: "4294967296"
+ ETCD_SNAPSHOT_COUNT: "50000"
+ command: etcd -advertise-client-urls=http://milvus-etcd:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd
+ volumes:
+ - milvus-etcd-dev-data:/etcd
+ milvus-minio:
+ image: minio/minio:RELEASE.2024-11-07T00-52-20Z
+ restart: unless-stopped
+ environment:
+ MINIO_ACCESS_KEY: minioadmin
+ MINIO_SECRET_KEY: minioadmin
+ command: minio server /data
+ ports:
+ - "9000:9000"
+ volumes:
+ - milvus-minio-dev-data:/data
+ milvus:
+ image: milvusdb/milvus:v2.4.8
+ restart: unless-stopped
+ command: ["milvus", "run", "standalone"]
+ depends_on:
+ - milvus-etcd
+ - milvus-minio
+ environment:
+ ETCD_ENDPOINTS: milvus-etcd:2379
+ MINIO_ADDRESS: milvus-minio:9000
+ ports:
+ - "19530:19530"
+ - "9091:9091"
+ volumes:
+ - milvus-dev-data:/var/lib/milvus
volumes:
postgres-dev-data:
redis-dev-data:
+ nacos-dev-logs:
+ nacos-dev-data:
+ nebula-meta-dev-data:
+ nebula-storage-dev-data:
+ milvus-etcd-dev-data:
+ milvus-minio-dev-data:
+ milvus-dev-data:
diff --git a/docker/dev-common/docker-compose.public-services.yml b/docker/dev-common/docker-compose.public-services.yml
index b096290..d6890cb 100644
--- a/docker/dev-common/docker-compose.public-services.yml
+++ b/docker/dev-common/docker-compose.public-services.yml
@@ -61,6 +61,102 @@ services:
- ${K12STUDY_RUN_DIR:-./runtime}/nacos/data:/home/nacos/data
networks:
- k12study-dev-net
+ nebula-metad:
+ image: vesoft/nebula-metad:v3.8.0
+ container_name: k12study-dev-nebula-metad
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-metad
+ - --ws_ip=0.0.0.0
+ - --port=9559
+ - --data_path=/data/meta
+ ports:
+ - "${K12STUDY_NEBULA_META_PORT:-9559}:9559"
+ volumes:
+ - ${K12STUDY_RUN_DIR:-./runtime}/nebula/meta:/data/meta
+ networks:
+ - k12study-dev-net
+ nebula-storaged:
+ image: vesoft/nebula-storaged:v3.8.0
+ container_name: k12study-dev-nebula-storaged
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-storaged
+ - --ws_ip=0.0.0.0
+ - --port=9779
+ - --data_path=/data/storage
+ depends_on:
+ - nebula-metad
+ ports:
+ - "${K12STUDY_NEBULA_STORAGE_PORT:-9779}:9779"
+ volumes:
+ - ${K12STUDY_RUN_DIR:-./runtime}/nebula/storage:/data/storage
+ networks:
+ - k12study-dev-net
+ nebula-graphd:
+ image: vesoft/nebula-graphd:v3.8.0
+ container_name: k12study-dev-nebula-graphd
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-graphd
+ - --ws_ip=0.0.0.0
+ - --port=9669
+ depends_on:
+ - nebula-metad
+ - nebula-storaged
+ ports:
+ - "${K12STUDY_GRAPH_PORT:-9669}:9669"
+ networks:
+ - k12study-dev-net
+ milvus-etcd:
+ image: quay.io/coreos/etcd:v3.5.5
+ container_name: k12study-dev-milvus-etcd
+ restart: unless-stopped
+ environment:
+ ETCD_AUTO_COMPACTION_MODE: revision
+ ETCD_AUTO_COMPACTION_RETENTION: "1000"
+ ETCD_QUOTA_BACKEND_BYTES: "4294967296"
+ ETCD_SNAPSHOT_COUNT: "50000"
+ command: etcd -advertise-client-urls=http://milvus-etcd:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd
+ volumes:
+ - ${K12STUDY_RUN_DIR:-./runtime}/milvus/etcd:/etcd
+ networks:
+ - k12study-dev-net
+ milvus-minio:
+ image: minio/minio:RELEASE.2024-11-07T00-52-20Z
+ container_name: k12study-dev-milvus-minio
+ restart: unless-stopped
+ environment:
+ MINIO_ACCESS_KEY: ${K12STUDY_MINIO_ACCESS_KEY:-minioadmin}
+ MINIO_SECRET_KEY: ${K12STUDY_MINIO_SECRET_KEY:-minioadmin}
+ command: minio server /data
+ ports:
+ - "${K12STUDY_MINIO_PORT:-9000}:9000"
+ volumes:
+ - ${K12STUDY_RUN_DIR:-./runtime}/milvus/minio:/data
+ networks:
+ - k12study-dev-net
+ milvus:
+ image: milvusdb/milvus:v2.4.8
+ container_name: k12study-dev-milvus
+ restart: unless-stopped
+ command: ["milvus", "run", "standalone"]
+ depends_on:
+ - milvus-etcd
+ - milvus-minio
+ environment:
+ ETCD_ENDPOINTS: milvus-etcd:2379
+ MINIO_ADDRESS: milvus-minio:9000
+ ports:
+ - "${K12STUDY_VECTOR_PORT:-19530}:19530"
+ - "${K12STUDY_VECTOR_HTTP_PORT:-9091}:9091"
+ volumes:
+ - ${K12STUDY_RUN_DIR:-./runtime}/milvus/data:/var/lib/milvus
+ networks:
+ - k12study-dev-net
networks:
k12study-dev-net:
diff --git a/docker/dev-common/docker-compose.yml b/docker/dev-common/docker-compose.yml
index ba83eb2..0b71263 100644
--- a/docker/dev-common/docker-compose.yml
+++ b/docker/dev-common/docker-compose.yml
@@ -23,7 +23,116 @@ services:
command: redis-server --appendonly yes
volumes:
- redis-data:/data
+ nacos:
+ image: nacos/nacos-server:v3.2.1-2026.04.03
+ container_name: k12study-nacos
+ restart: unless-stopped
+ environment:
+ MODE: standalone
+ NACOS_AUTH_ENABLE: "false"
+ TZ: Asia/Shanghai
+ JVM_XMS: 256m
+ JVM_XMX: 512m
+ JVM_XMN: 128m
+ ports:
+ - "8848:8848"
+ - "9848:9848"
+ - "9849:9849"
+ volumes:
+ - nacos-logs:/home/nacos/logs
+ - nacos-data:/home/nacos/data
+ nebula-metad:
+ image: vesoft/nebula-metad:v3.8.0
+ container_name: k12study-nebula-metad
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-metad
+ - --ws_ip=0.0.0.0
+ - --port=9559
+ - --data_path=/data/meta
+ ports:
+ - "9559:9559"
+ volumes:
+ - nebula-meta-data:/data/meta
+ nebula-storaged:
+ image: vesoft/nebula-storaged:v3.8.0
+ container_name: k12study-nebula-storaged
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-storaged
+ - --ws_ip=0.0.0.0
+ - --port=9779
+ - --data_path=/data/storage
+ depends_on:
+ - nebula-metad
+ ports:
+ - "9779:9779"
+ volumes:
+ - nebula-storage-data:/data/storage
+ nebula-graphd:
+ image: vesoft/nebula-graphd:v3.8.0
+ container_name: k12study-nebula-graphd
+ restart: unless-stopped
+ command:
+ - --meta_server_addrs=nebula-metad:9559
+ - --local_ip=nebula-graphd
+ - --ws_ip=0.0.0.0
+ - --port=9669
+ depends_on:
+ - nebula-metad
+ - nebula-storaged
+ ports:
+ - "9669:9669"
+ milvus-etcd:
+ image: quay.io/coreos/etcd:v3.5.5
+ container_name: k12study-milvus-etcd
+ restart: unless-stopped
+ environment:
+ ETCD_AUTO_COMPACTION_MODE: revision
+ ETCD_AUTO_COMPACTION_RETENTION: "1000"
+ ETCD_QUOTA_BACKEND_BYTES: "4294967296"
+ ETCD_SNAPSHOT_COUNT: "50000"
+ command: etcd -advertise-client-urls=http://milvus-etcd:2379 -listen-client-urls=http://0.0.0.0:2379 --data-dir=/etcd
+ volumes:
+ - milvus-etcd-data:/etcd
+ milvus-minio:
+ image: minio/minio:RELEASE.2024-11-07T00-52-20Z
+ container_name: k12study-milvus-minio
+ restart: unless-stopped
+ environment:
+ MINIO_ACCESS_KEY: minioadmin
+ MINIO_SECRET_KEY: minioadmin
+ command: minio server /data
+ ports:
+ - "9000:9000"
+ volumes:
+ - milvus-minio-data:/data
+ milvus:
+ image: milvusdb/milvus:v2.4.8
+ container_name: k12study-milvus
+ restart: unless-stopped
+ command: ["milvus", "run", "standalone"]
+ depends_on:
+ - milvus-etcd
+ - milvus-minio
+ environment:
+ ETCD_ENDPOINTS: milvus-etcd:2379
+ MINIO_ADDRESS: milvus-minio:9000
+ ports:
+ - "19530:19530"
+ - "9091:9091"
+ volumes:
+ - milvus-data:/var/lib/milvus
volumes:
postgres-data:
redis-data:
+ nacos-logs:
+ nacos-data:
+ nebula-meta-data:
+ nebula-storage-data:
+ milvus-etcd-data:
+ milvus-minio-data:
+ milvus-data:
diff --git a/docs/1/postgres - k12study - achievement.svg b/docs/1/postgres - k12study - achievement.svg
deleted file mode 100644
index 7c9b859..0000000
--- a/docs/1/postgres - k12study - achievement.svg
+++ /dev/null
@@ -1,74980 +0,0 @@
-
-
-
diff --git a/docs/1/postgres - k12study - ai.graphml b/docs/1/postgres - k12study - ai.graphml
deleted file mode 100644
index 8380499..0000000
--- a/docs/1/postgres - k12study - ai.graphml
+++ /dev/null
@@ -1,178 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- tb_ai_graph_entity
- 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 - 更新时间
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- tb_ai_graph_relation
- 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 - 更新时间
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- tb_ai_knowledge_file
- 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 - 更新时间
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- tb_ai_knowledge_sync_task
- 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 - 更新时间
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/1/postgres - k12study - auth.svg b/docs/1/postgres - k12study - auth.svg
deleted file mode 100644
index a8f3cf8..0000000
--- a/docs/1/postgres - k12study - auth.svg
+++ /dev/null
@@ -1,12945 +0,0 @@
-
-
-
diff --git a/docs/1/postgres - k12study - course.svg b/docs/1/postgres - k12study - course.svg
deleted file mode 100644
index 4c54667..0000000
--- a/docs/1/postgres - k12study - course.svg
+++ /dev/null
@@ -1,90910 +0,0 @@
-
-
-
diff --git a/docs/1/postgres - k12study - question.svg b/docs/1/postgres - k12study - question.svg
deleted file mode 100644
index 1381635..0000000
--- a/docs/1/postgres - k12study - question.svg
+++ /dev/null
@@ -1,128021 +0,0 @@
-
-
-
diff --git a/docs/1/postgres - k12study - recommendation.svg b/docs/1/postgres - k12study - recommendation.svg
deleted file mode 100644
index 71fdaba..0000000
--- a/docs/1/postgres - k12study - recommendation.svg
+++ /dev/null
@@ -1,79021 +0,0 @@
-
-
-
diff --git a/docs/architecture.md b/docs/architecture.md
index a68c0aa..2808dbc 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -1,26 +1,127 @@
-# K12Study 项目架构文档(建设中)
+# K12Study 项目模块化里程碑(2026-04-13 ~ 2026-07-01)
+## 1. 目标与范围
+- 在 7.1 前完成从基础架构到核心业务链路的可联调版本,并进入整体测试封板。
+- 里程碑覆盖全流程:逻辑视图、ER/SQL、基础服务、基础架构 API、基础架构前后端、核心课程/学习业务、模块测试、项目整体测试。
+- 当前项目基线:`gateway + auth + upms + boot-dev + python-ai + frontend + app + init/pg`。
-## 1. 当前已确认技术选型
+## 2. 技术路线(当前版本)
+- 图数据库:NebulaGraph(主选),Neo4j(开发备选)。
+- 向量库:Milvus(主选)。
+- 关系库:PostgreSQL(业务主数据与事实源)。
+- 缓存:Redis(token、权限、热点缓存)。
-- 图数据库:NebulaGraph(主选),Neo4j Community(备选)
-- 向量库:Milvus
-- 关系库:PostgreSQL(存储原始业务表)
+## 3. 模块化里程碑分解
+### M0 逻辑视图梳理(04.13-04.15)
+- 目标
+ - 固化系统分层、边界与主链路,避免后续并行开发职责交叉。
+- 关键任务
+ - 统一 BFF/API 网关边界、认证边界、业务域边界、AI 适配边界。
+ - 明确 Web / 小程序 / 后端服务 / 数据与中间件的调用方向。
+- 验收产物
+ - `docs/architecture/logical-view.md`
-## 2. 分层职责(先行约定)
+### M1 ER 图与 SQL 设计(04.16-04.21)
+- 目标
+ - 完成核心业务域数据模型定版,确保接口与代码开发有稳定数据契约。
+- 关键任务
+ - 梳理多租户基础域(tenant/dept/user/role/menu)。
+ - 梳理课程域、题目与批改域、成就与推荐域、AI 知识域的数据关系。
+ - 定义 SQL 执行顺序、索引策略、跨 schema 依赖约束。
+- 验收产物
+ - `docs/architecture/er-diagram.md`
+ - `docs/architecture/schema-baseline.sql`
-- PostgreSQL:业务主数据与原始表数据(唯一事实源)
-- NebulaGraph / Neo4j:知识点、题目、学生作答、知识关系等图结构查询
-- Milvus:教材/题解/知识片段向量检索与语义召回
+### M2 基础服务配置(04.22-04.26)
+- 目标
+ - 打通基础运行底座:PostgreSQL、Redis、注册中心、知识图谱、向量库。
+- 关键任务
+ - 统一本地开发环境变量命名与连接方式。
+ - 约定 NebulaGraph(主)/Neo4j(备)与 Milvus 接入规范。
+ - 明确健康检查、持久化卷、网络隔离策略。
+- 验收产物
+ - `docs/architecture/base-services.md`
+ - `docs/architecture/service-baseline.compose.yaml`
-## 3. 三库协同原则(首版)
+### M3 基础架构 API 定义(04.27-05.03)
+- 目标
+ - 完成基础能力 API(认证、权限、租户、组织、路由、基础文件)契约冻结。
+- 关键任务
+ - 统一响应结构、鉴权方式、错误码与 traceId 规范。
+ - 固化 auth/upms 的 API 清单与扩展位。
+- 验收产物
+ - `docs/architecture/api-design.md`(基础架构 API 段)
-- 关系库到图库:通过同步任务进行实体与关系映射
-- 关系库到向量库:通过切片与 embedding 任务进行索引
-- 图谱节点保留源表标识(如 `source_table`、`source_pk`),支持点击节点回查原始表数据
+### M4 基础架构前后端代码构建(05.04-05.10)
+- 目标
+ - 实现并联调基础架构链路(登录、当前用户、动态路由、区域树、租户树、部门树)。
+- 关键任务
+ - 后端:auth/upms 与网关链路稳定化。
+ - 前端:React 管理端基础框架与动态路由消费。
+ - 小程序:基础请求层与最小页面壳可访问基础接口。
+- 验收标准
+ - `/api/auth/*`、`/api/upms/*` 在本地模式和网关模式都可联调。
-## 4. 架构图产物(待补充)
+### M5 核心课程/学习业务 API 与数据模型冻结(05.11-05.24)
+- 目标
+ - 将剩余周期聚焦课程与学习核心内容,完成核心域契约冻结。
+- 关键任务
+ - 完成课程、学习路径、学习记录、学习效果等核心 API 与数据模型定义。
+ - 固化核心流程输入输出 DTO 与事件流。
+- 验收产物
+ - `docs/architecture/api-design.md`(课程/学习核心段)
-- 系统流程图:建设中
-- ER 图:建设中
-- 数据流图:建设中
+### M6 核心课程/学习业务前后端构建(05.25-06.14)
+- 目标
+ - 完成课程与学习核心功能前后端落地并联调。
+- 关键任务
+ - 后端:课程与学习域核心能力实现。
+ - 前端/小程序:课程学习主流程页面与 API 接入。
+- 验收标准
+ - 课程发布 -> 学习执行 -> 学习记录回写的核心闭环可演示。
+
+### M7 核心内容专项测试与修复(06.15-06.24)
+- 目标
+ - 核心课程/学习模块通过功能、接口、回归测试。
+- 关键任务
+ - 覆盖课程、学习、基础架构联动场景。
+ - 修复阻塞级问题并完成回归。
+- 验收产物
+ - `docs/architecture/test-plan.md`(模块测试段)
+
+### M8 整体回归与发布评估(06.25-07.01)
+- 目标
+ - 完成全链路回归、性能基线与发布准入评估。
+- 关键任务
+ - 核心课程/学习主流程端到端回归与异常链路验证。
+ - 输出上线前 go/no-go 结论。
+- 验收产物
+ - `docs/architecture/test-plan.md`(整体测试段)
+
+## 4. 三库协同原则(首版)
+- 关系库到图库:通过同步任务进行实体与关系映射。
+- 关系库到向量库:通过切片与 embedding 任务进行索引。
+- 图谱节点保留源表标识(如 `source_table`、`source_pk`),支持点击节点回查原始表数据。
+
+## 5. 执行原则
+- 所有模块按“先契约、后实现、再联调、最后回归”的顺序推进。
+- API 契约冻结后,跨端协作不随意破坏兼容性;新增字段走向后兼容策略。
+- 数据模型以 `init/pg` 既有结构为基础,新增结构优先走增量脚本。
+
+## 6. 风险与缓冲策略
+- 风险一:业务域并行开发导致接口反复变更。
+ - 策略:M3/M5 设置 API 冻结点,冻结后变更必须评审。
+- 风险二:AI 相关链路依赖图谱/向量服务稳定性不足。
+ - 策略:先完成本地 mock + 异步任务队列,再切换真实存储。
+- 风险三:最后阶段联调问题集中爆发。
+ - 策略:M6 结束前必须打通最小闭环,M7 提前做模块回归。
+
+## 7. 分册文档
+- `docs/architecture/README.md`
+- `docs/architecture/logical-view.md`
+- `docs/architecture/er-diagram.md`
+- `docs/architecture/schema-baseline.sql`
+- `docs/architecture/base-services.md`
+- `docs/architecture/service-baseline.compose.yaml`
+- `docs/architecture/api-design.md`
+- `docs/architecture/test-plan.md`
diff --git a/docs/architecture/README.md b/docs/architecture/README.md
new file mode 100644
index 0000000..86c284c
--- /dev/null
+++ b/docs/architecture/README.md
@@ -0,0 +1,30 @@
+# K12Study 架构设计分册
+## 文档目标
+本目录用于承载 4.13-7.1 里程碑执行所需的架构设计产物,作为 `docs/architecture.md` 的细化文档集合。
+
+## 文档清单
+- `logical-view.md`
+ - 系统逻辑视图、模块边界、主链路。
+- `er-diagram.md`
+ - 核心业务 ER 关系与跨域数据依赖说明。
+- `schema-baseline.sql`
+ - 数据与 AI 存储侧的增量 SQL 草案(设计基线)。
+- `base-services.md`
+ - PG/Redis/知识图谱/向量库基础服务配置方案。
+- `service-baseline.compose.yaml`
+ - 基础服务编排样例(开发环境基线)。
+- `api-design.md`
+ - 基础架构 API 与业务功能 API 设计。
+- `test-plan.md`
+ - 模块测试和项目整体测试计划。
+- `系统架构.drawio`
+ - 系统分层、角色入口、Java/Python/数据层关系图。
+- `代码架构.drawio`
+ - 前后端与服务模块、共享契约、基础设施代码架构图。
+
+## 使用顺序
+1. 先阅读 `logical-view.md` 明确边界。
+2. 再阅读 `er-diagram.md` 与 `schema-baseline.sql` 对齐数据模型。
+3. 按 `base-services.md` 与 `service-baseline.compose.yaml` 准备基础服务。
+4. 依据 `api-design.md` 先冻结契约,再推进前后端实现。
+5. 按 `test-plan.md` 执行模块测试与整体测试。
diff --git a/docs/architecture/api-design.md b/docs/architecture/api-design.md
new file mode 100644
index 0000000..1a139fb
--- /dev/null
+++ b/docs/architecture/api-design.md
@@ -0,0 +1,82 @@
+# API 设计(基础架构 + 业务功能)
+## 1. API 设计原则
+- 对外统一前缀:`/api/*`。
+- 统一响应结构:`code/message/data/traceId`。
+- 认证策略:JWT + RBAC,网关做统一鉴权透传。
+- API 冻结点:
+ - 基础架构 API 在 M3 冻结。
+ - 业务 API 在 M5 冻结。
+
+## 2. 基础架构 API(M3)
+### 2.1 认证域(auth)
+- `POST /api/auth/login`
+- `POST /api/auth/refresh`
+- `GET /api/auth/current-user`
+
+### 2.2 权限与组织域(upms)
+- `GET /api/upms/routes`
+- `GET /api/upms/current-user`
+- `GET /api/upms/areas/tree`
+- `GET /api/upms/tenants/tree`
+- `GET /api/upms/depts/tree`
+
+### 2.3 基础扩展 API(建议补充)
+- 文件域
+ - `POST /api/upms/files/upload`
+ - `GET /api/upms/files/{fileId}`
+- 站内消息域
+ - `GET /api/upms/messages/inbox`
+ - `POST /api/upms/messages/{messageId}/read`
+
+## 3. 业务功能 API(M5)
+### 3.1 课程域(course)
+- `POST /api/course/courses`
+- `GET /api/course/courses/{courseId}`
+- `POST /api/course/courses/{courseId}/chapters`
+- `POST /api/course/knowledge-points`
+- `POST /api/course/chapters/{chapterId}/knowledge-relations`
+- `GET /api/course/students/{studentId}/mastery`
+
+### 3.2 习题/作业/批改域(question)
+- 题库与题目
+ - `POST /api/question/banks`
+ - `POST /api/question/questions`
+ - `POST /api/question/banks/{bankId}/questions/{questionId}`
+- 试卷与作业
+ - `POST /api/question/papers`
+ - `POST /api/question/assignments`
+ - `POST /api/question/assignments/{assignmentId}/targets`
+- 提交与批改
+ - `POST /api/question/submissions`
+ - `POST /api/question/submissions/{submissionId}/answers`
+ - `POST /api/question/grading/tasks`
+ - `GET /api/question/grading/tasks/{taskId}`
+- 错题与复习
+ - `GET /api/question/wrong-questions`
+ - `POST /api/question/review/plans/generate`
+ - `POST /api/question/review/executions`
+
+### 3.3 成就域(achievement)
+- `POST /api/achievement/definitions`
+- `POST /api/achievement/rules/templates`
+- `GET /api/achievement/users/{userId}/records`
+- `GET /api/achievement/users/{userId}/progress`
+- `POST /api/achievement/events/trigger`
+
+### 3.4 推荐域(recommendation)
+- `POST /api/recommendation/tasks`
+- `GET /api/recommendation/tasks/{taskId}/items`
+- `POST /api/recommendation/items/{itemId}/feedback`
+- `GET /api/recommendation/effects/daily`
+
+### 3.5 AI 知识域(ai)
+- `POST /api/ai/knowledge/files`
+- `POST /api/ai/knowledge/files/{fileId}/sync`
+- `GET /api/ai/knowledge/sync-tasks/{taskId}`
+- `POST /api/ai/retrieval/query`
+- `GET /api/ai/health`
+
+## 4. API 阶段交付映射
+- M3:`auth + upms + file/message 基础扩展`
+- M5:`course + question + achievement + recommendation + ai`
+- M6:前后端按冻结 API 完成联调,不新增破坏性变更。
diff --git a/docs/architecture/base-services.md b/docs/architecture/base-services.md
new file mode 100644
index 0000000..f2bc2f1
--- /dev/null
+++ b/docs/architecture/base-services.md
@@ -0,0 +1,59 @@
+# 基础服务配置方案(PG / Redis / 知识图谱 / 向量库)
+## 1. 目标
+- 为 4.13-7.1 研发阶段提供统一、可重复的基础服务底座。
+- 与现有 `docker/dev-common` 与 `.devcontainer` 方案兼容,减少环境差异。
+
+## 2. 服务基线
+- 必选
+ - PostgreSQL:主业务数据存储(多 schema)。
+ - Redis:缓存、会话、热点数据。
+ - Nacos:配置中心与注册中心(服务化阶段)。
+ - NebulaGraph:知识图谱主方案(Neo4j 作为开发备选)。
+ - Milvus:向量检索主方案。
+- 可选
+ - Python AI 服务:统一模型调用、图谱同步、向量写入。
+
+## 3. 环境变量命名建议
+- PostgreSQL
+ - `K12STUDY_DB_HOST`
+ - `K12STUDY_DB_PORT`
+ - `K12STUDY_DB_NAME`
+ - `K12STUDY_DB_USER`
+ - `K12STUDY_DB_PASSWORD`
+- Redis
+ - `K12STUDY_REDIS_HOST`
+ - `K12STUDY_REDIS_PORT`
+ - `K12STUDY_REDIS_PASSWORD`
+- Nacos
+ - `K12STUDY_REGISTRY_HOST`
+ - `K12STUDY_REGISTRY_PORT`
+ - `K12STUDY_REGISTRY_GRPC_PORT`
+ - `K12STUDY_REGISTRY_RAFT_PORT`
+- 知识图谱(Nebula)
+ - `K12STUDY_GRAPH_HOST`
+ - `K12STUDY_GRAPH_PORT`
+ - `K12STUDY_GRAPH_USER`
+ - `K12STUDY_GRAPH_PASSWORD`
+- 向量库(Milvus)
+ - `K12STUDY_VECTOR_HOST`
+ - `K12STUDY_VECTOR_PORT`
+ - `K12STUDY_VECTOR_GRPC_PORT`
+
+## 4. 配置落地建议
+- 阶段一(M2)
+ - 先以本地单机编排启动,保证联调可用。
+- 阶段二(M5-M6)
+ - 将图谱/向量配置接入 `ai-client` 与 `python-ai`。
+- 阶段三(M7-M8)
+ - 增加连通性与降级测试(图谱不可用、向量库超时、Redis 抖动)。
+
+## 5. 健康检查建议
+- PostgreSQL:`pg_isready`
+- Redis:`redis-cli ping`
+- NebulaGraph:`graphd` 端口连接可用
+- Milvus:HTTP `/healthz`
+- Python AI:`GET /health`
+
+## 6. 与现有目录关系
+- 现有公共服务编排:`docker/dev-common/docker-compose.public-services.yml`
+- 本文新增服务编排样例:`docs/architecture/service-baseline.compose.yaml`
diff --git a/docs/architecture/er-diagram.md b/docs/architecture/er-diagram.md
new file mode 100644
index 0000000..2e774bc
--- /dev/null
+++ b/docs/architecture/er-diagram.md
@@ -0,0 +1,77 @@
+# K12Study 核心 ER 设计
+## 1. 建模范围
+- 基础域:租户、组织、用户、角色、菜单、文件。
+- 课程域:课程、章节、学习节点、知识点、学习进度。
+- 习题与批改域:题库、试卷、作业、提交、批改、错题与复习。
+- 成就与推荐域:成就定义、用户成就、推荐任务、反馈、学习闭环。
+- AI 知识域:知识文件、同步任务、图谱实体/关系。
+
+## 2. 核心关系图(基础域 + 教学主链路)
+```mermaid
+erDiagram
+ SYS_TENANT ||--o{ SYS_DEPT : contains
+ SYS_DEPT ||--o{ SYS_USER : has
+ SYS_ROLE ||--o{ SYS_ROLE_MENU : grants
+ SYS_MENU ||--o{ SYS_ROLE_MENU : mapped
+ SYS_USER ||--o{ SYS_FILE : uploads
+
+ COURSE ||--o{ COURSE_CHAPTER : contains
+ COURSE_CHAPTER ||--o{ COURSE_NODE : contains
+ KNOWLEDGE_POINT ||--o{ CHAPTER_KP_REL : mapped
+ COURSE_CHAPTER ||--o{ CHAPTER_KP_REL : mapped
+ COURSE_NODE ||--o{ NODE_KP_REL : mapped
+ KNOWLEDGE_POINT ||--o{ NODE_KP_REL : mapped
+
+ QUESTION_BANK ||--o{ BANK_QUESTION_REL : contains
+ QUESTION_ITEM ||--o{ BANK_QUESTION_REL : included
+ PAPER ||--o{ PAPER_QUESTION : contains
+ QUESTION_ITEM ||--o{ PAPER_QUESTION : included
+ ASSIGNMENT ||--o{ SUBMISSION : receives
+ SUBMISSION ||--o{ SUBMISSION_ANSWER : has
+ QUESTION_ITEM ||--o{ SUBMISSION_ANSWER : answered
+ GRADING_TASK ||--o{ ANSWER_GRADE : produces
+ SUBMISSION_ANSWER ||--o{ ANSWER_GRADE : graded
+
+ USER_ACHIEVEMENT }o--|| ACHIEVEMENT : awarded
+ USER_ACHIEVEMENT }o--|| SYS_USER : owner
+ RECOMMENDATION_TASK ||--o{ RECOMMENDATION_ITEM : produces
+ RECOMMENDATION_ITEM ||--o{ RECOMMENDATION_FEEDBACK : receives
+```
+
+## 3. AI 知识域关系图
+```mermaid
+erDiagram
+ SYS_FILE ||--o{ AI_KNOWLEDGE_FILE : source
+ AI_KNOWLEDGE_FILE ||--o{ AI_KNOWLEDGE_SYNC_TASK : schedules
+ AI_KNOWLEDGE_FILE ||--o{ AI_GRAPH_ENTITY : extracts
+ AI_GRAPH_ENTITY ||--o{ AI_GRAPH_RELATION : links
+```
+
+## 4. 实体到现有表映射(节选)
+- `SYS_TENANT` -> `upms.tb_sys_tenant`
+- `SYS_DEPT` -> `upms.tb_sys_dept`
+- `SYS_USER` -> `upms.tb_sys_user`
+- `SYS_ROLE` -> `upms.tb_sys_role`
+- `SYS_MENU` -> `upms.tb_sys_menu`
+- `COURSE` -> `course.cl_course`
+- `KNOWLEDGE_POINT` -> `course.cl_knowledge_point`
+- `QUESTION_ITEM` -> `question.hw_question_item`
+- `ASSIGNMENT` -> `question.hw_assignment`
+- `SUBMISSION` -> `question.hw_submission`
+- `GRADING_TASK` -> `question.gd_grading_task`
+- `ACHIEVEMENT` -> `achievement.ac_achievement`
+- `USER_ACHIEVEMENT` -> `achievement.ac_user_achievement`
+- `AI_KNOWLEDGE_FILE` -> `ai.tb_ai_knowledge_file`
+
+## 5. SQL 基线目录(现有)
+- `init/pg/auth/10_create_auth_tables.sql`
+- `init/pg/upms/10_create_upms_tables.sql`
+- `init/pg/course/10_create_course_tables.sql`
+- `init/pg/question/10_create_question_tables.sql`
+- `init/pg/achievement/10_create_achievement_tables.sql`
+- `init/pg/ai/10_create_ai_tables.sql`
+
+## 6. 设计建议
+- 保持跨域依赖单向:`course/question/achievement/recommendation` 通过 `upms` 提供的主体信息关联。
+- 涉及 AI 知识检索的数据优先走异步同步表,避免在线写链路阻塞。
+- 复杂业务报表优先通过事实表/汇总表落地,避免线上实时多表大 join。
diff --git a/docs/architecture/logical-view.md b/docs/architecture/logical-view.md
new file mode 100644
index 0000000..6e2e0e3
--- /dev/null
+++ b/docs/architecture/logical-view.md
@@ -0,0 +1,77 @@
+# K12Study 逻辑视图
+## 1. 分层结构
+- 终端层
+ - `frontend`(React 管理端)
+ - `app`(微信小程序)
+- 接入与安全层
+ - `gateway`(统一入口、鉴权、路由、跨域、trace)
+ - `auth`(登录、刷新、当前用户)
+- 平台能力层
+ - `upms`(租户、组织、用户、角色、菜单、路由元数据)
+ - `ai-client`(Java 侧 AI 适配)
+ - `python-ai`(AI 处理服务)
+- 业务域层(规划中)
+ - `course`、`question`、`achievement`、`recommendation`
+- 数据与基础设施层
+ - PostgreSQL(多 schema)
+ - Redis(缓存、会话、热点)
+ - NebulaGraph / Neo4j(知识图谱)
+ - Milvus(向量检索)
+ - Nacos(注册配置)
+
+## 2. 逻辑架构图
+```mermaid
+graph TD
+ WEB[Frontend React] --> GW[Gateway]
+ MINI[Mini Program] --> GW
+
+ GW --> AUTH[Auth Service]
+ GW --> UPMS[UPMS Service]
+ GW --> COURSE[Course Service]
+ GW --> QUESTION[Question Service]
+ GW --> ACHIEVE[Achievement Service]
+ GW --> REC[Recommendation Service]
+
+ AUTH --> PG[(PostgreSQL)]
+ UPMS --> PG
+ COURSE --> PG
+ QUESTION --> PG
+ ACHIEVE --> PG
+ REC --> PG
+
+ AUTH --> REDIS[(Redis)]
+ UPMS --> REDIS
+ COURSE --> REDIS
+ QUESTION --> REDIS
+ ACHIEVE --> REDIS
+ REC --> REDIS
+
+ QUESTION --> AICLIENT[AI Client]
+ AICLIENT --> PYAI[Python AI]
+ PYAI --> KG[(NebulaGraph / Neo4j)]
+ PYAI --> VDB[(Milvus)]
+ PYAI --> PG
+```
+
+## 3. 主链路视角
+### 3.1 基础架构链路
+- 登录:`frontend/app -> /api/auth/login -> auth -> token`
+- 权限与路由:`frontend/app -> /api/upms/routes -> upms`
+- 组织数据:`/api/upms/areas/tree|tenants/tree|depts/tree`
+
+### 3.2 教学业务链路
+- 教师发作业:题库/试卷/作业配置 -> 投放班级。
+- 学生提交:答题内容 + 文件(可选)。
+- 批改流程:自动批改 + 人工复核 -> 成绩汇总 -> 错题沉淀。
+- 复习与推荐:复习计划 -> 推荐内容 -> 用户反馈 -> 闭环效果。
+- 成就激励:事件触发 -> 规则命中 -> 成就发放/进度更新。
+
+### 3.3 AI 知识链路
+- 文件入库:`upms.tb_sys_file -> ai.tb_ai_knowledge_file`
+- 同步任务:`ai.tb_ai_knowledge_sync_task` 异步推送图谱/向量库
+- 检索与推理:业务服务通过 `ai-client` 调 `python-ai`
+
+## 4. 设计约束
+- API 对外统一前缀 `/api/*`,内部服务前缀保持领域语义。
+- 多租户与组织隔离字段在核心表统一保留(`tenant_id/tenant_path/dept_id/dept_path/adcode`)。
+- 业务域之间优先通过 API 契约与事件解耦,不直接跨模块读写表。
diff --git a/docs/architecture/modules/01-课程学习/er图.drawio b/docs/architecture/modules/01-课程学习/er图.drawio
index 0a05588..d8c4435 100644
--- a/docs/architecture/modules/01-课程学习/er图.drawio
+++ b/docs/architecture/modules/01-课程学习/er图.drawio
@@ -13,7 +13,7 @@
-
+
@@ -22,7 +22,7 @@
-
+
@@ -34,7 +34,7 @@
-
+
@@ -46,15 +46,18 @@
-
+
-
+
+
+
+
@@ -88,13 +91,13 @@
-
+
-
+
@@ -103,6 +106,9 @@
+
+
+
diff --git a/docs/architecture/modules/01-课程学习/数据流图.drawio b/docs/architecture/modules/01-课程学习/数据流图.drawio
index 3b6884f..395501d 100644
--- a/docs/architecture/modules/01-课程学习/数据流图.drawio
+++ b/docs/architecture/modules/01-课程学习/数据流图.drawio
@@ -31,7 +31,7 @@
-
+
diff --git a/docs/architecture/modules/02-习题与作业/er图.drawio b/docs/architecture/modules/02-习题与作业/er图.drawio
index e1647c4..1c58617 100644
--- a/docs/architecture/modules/02-习题与作业/er图.drawio
+++ b/docs/architecture/modules/02-习题与作业/er图.drawio
@@ -7,34 +7,37 @@
-
+
-
+
+
+
+
-
+
-
+
-
+
@@ -43,7 +46,8 @@
-
+
+
diff --git a/docs/architecture/modules/03-批改与反馈/er图.drawio b/docs/architecture/modules/03-批改与反馈/er图.drawio
index 587480b..7e5c2e7 100644
--- a/docs/architecture/modules/03-批改与反馈/er图.drawio
+++ b/docs/architecture/modules/03-批改与反馈/er图.drawio
@@ -28,13 +28,19 @@
-
+
+
+
+
+
+
+
-
+
@@ -43,7 +49,7 @@
-
+
@@ -88,6 +94,12 @@
+
+
+
+
+
+
diff --git a/docs/architecture/modules/03-批改与反馈/数据流图.drawio b/docs/architecture/modules/03-批改与反馈/数据流图.drawio
index 3e77393..ab764a7 100644
--- a/docs/architecture/modules/03-批改与反馈/数据流图.drawio
+++ b/docs/architecture/modules/03-批改与反馈/数据流图.drawio
@@ -24,17 +24,17 @@
-
-
-
+
+
+
-
+
-
+
@@ -46,14 +46,14 @@
-
-
+
+
-
+
diff --git a/docs/architecture/代码架构.drawio b/docs/architecture/代码架构.drawio
new file mode 100644
index 0000000..a1ddfea
--- /dev/null
+++ b/docs/architecture/代码架构.drawio
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/architecture/完整业务流程图.drawio b/docs/architecture/完整业务流程图.drawio
index f8ceb05..4bea98f 100644
--- a/docs/architecture/完整业务流程图.drawio
+++ b/docs/architecture/完整业务流程图.drawio
@@ -1,6 +1,6 @@
-
+
@@ -13,16 +13,16 @@
-
+
-
+
-
+
@@ -31,17 +31,17 @@
-
-
+
+
-
+
-
+
-
+
@@ -49,7 +49,7 @@
-
+
@@ -61,7 +61,7 @@
-
+
@@ -85,65 +85,65 @@
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
@@ -190,7 +190,7 @@
-
+
@@ -217,7 +217,7 @@
-
+
@@ -253,6 +253,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/architecture/数据流图(多角色).drawio b/docs/architecture/数据流图(多角色).drawio
index 117398c..f620364 100644
--- a/docs/architecture/数据流图(多角色).drawio
+++ b/docs/architecture/数据流图(多角色).drawio
@@ -1,130 +1,202 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/architecture/系统架构.drawio b/docs/architecture/系统架构.drawio
new file mode 100644
index 0000000..d4ecfaf
--- /dev/null
+++ b/docs/architecture/系统架构.drawio
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/architecture/系统架构与代码架构.drawio b/docs/architecture/系统架构与代码架构.drawio
deleted file mode 100644
index 4406aa3..0000000
--- a/docs/architecture/系统架构与代码架构.drawio
+++ /dev/null
@@ -1,450 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/milestone.md b/docs/milestone.md
new file mode 100644
index 0000000..d4eb365
--- /dev/null
+++ b/docs/milestone.md
@@ -0,0 +1,81 @@
+# K12Study 项目里程碑(4.13-7.1)
+## 1. 目标
+- 在 7.1 前完成从基础架构到业务闭环的可交付版本。
+- 形成“可开发、可联调、可测试、可发布”的完整流程。
+
+## 2. 里程碑总览
+### M0 逻辑视图梳理(04.13-04.15)
+- 产出
+ - 逻辑分层、模块边界、核心调用链路。
+- 完成标准
+ - 前后端、网关、服务、数据层职责边界确认。
+
+### M1 ER 图与 SQL 设计(04.16-04.21)
+- 产出
+ - 核心域 ER 关系与 SQL 基线。
+- 完成标准
+ - 关键业务实体关系可支持后续 API 定义与实现。
+
+### M2 基础服务配置(04.22-04.26)
+- 产出
+ - PostgreSQL、Redis、Nacos、知识图谱、向量库基础编排。
+- 完成标准
+ - 本地环境可稳定启动并通过健康检查。
+
+### M3 基础架构 API 定义(04.27-05.03)
+- 产出
+ - 认证、权限、组织、动态路由 API 契约冻结。
+- 完成标准
+ - API 输入输出、鉴权策略、错误码规范完成评审。
+
+### M4 基础架构前后端构建(05.04-05.10)
+- 产出
+ - auth/upms/gateway 联调通过,前端与小程序可调用基础 API。
+- 完成标准
+ - 登录、当前用户、动态路由、组织树查询链路可演示。
+
+### M5 核心课程/学习业务 API 与模型冻结(05.11-05.17)
+- 产出
+ - 课程、学习路径、学习记录、学习效果相关 API 与数据模型冻结。
+- 完成标准
+ - 核心课程/学习域接口契约可直接指导开发实现。
+
+### M6 核心课程/学习业务前后端构建(05.18-06.14)
+- 产出
+ - 课程与学习主流程前后端实现并完成联调。
+- 完成标准
+ - 课程发布 -> 学习执行 -> 学习记录回写闭环可运行。
+
+### M7 核心内容专项测试与修复(06.15-06.24)
+- 产出
+ - 课程/学习相关模块测试报告与修复记录。
+- 完成标准
+ - 阻塞级问题清理并完成修复回归。
+
+### M8 全链路回归与发布评估(06.25-07.01)
+- 产出
+ - 端到端测试结果、发布准入结论(Go/No-Go)。
+- 完成标准
+ - 核心主流程通过,具备发布条件。
+
+## 3. 关键交付物映射
+- 架构与设计
+ - `docs/architecture.md`
+ - `docs/architecture/logical-view.md`
+ - `docs/architecture/er-diagram.md`
+ - `docs/architecture/schema-baseline.sql`
+- 服务与环境
+ - `docker/dev-common/docker-compose.yml`
+ - `docker/dev-common/docker-compose.public-services.yml`
+ - `.devcontainer/external/docker-compose.yml`
+ - `.devcontainer/internal/docker-compose.yml`
+- API 与测试
+ - `docs/architecture/api-design.md`
+ - `docs/architecture/test-plan.md`
+
+## 4. 发布前检查清单
+- API 契约无破坏性变更。
+- 基础服务编排可稳定启动。
+- 核心闭环 E2E 全通过。
+- P0/P1 缺陷清零。
+- 关键指标可观测(错误率、超时率、任务积压)。
diff --git a/docs/plan/architecture.md b/docs/plan/architecture.md
deleted file mode 100644
index 2eb111a..0000000
--- a/docs/plan/architecture.md
+++ /dev/null
@@ -1,205 +0,0 @@
-# K12Study 首版项目框架规划
-
-## Progress
-
-- [x] 根目录骨架已创建:`backend`、`frontend`、`app`、`init/pg`
-- [x] `backend` Maven 多模块目录与基础 POM 已落地
-- [x] `gateway`、`auth`、`upms`、`boot-dev`、`python-ai` 首版占位代码已创建
-- [x] `init/pg` 已按模块拆分,并接入根目录 `tb_sys_area.sql`
-- [x] 学校租户表命名已修正为 `tb_sys_tenant`
-- [x] 跨包 DTO、Enums 已收口到对应 `api-*` 模块
-- [x] Web 端已从 workspace 收敛为单一 React 项目
-- [x] Web 端请求层已切换为原生 `fetch` 封装
-- [x] 根目录已新增独立 `app` 微信小程序骨架
-- [x] VS Code `docker-dev` 双模式已补齐:`external` 直连外部 PG/Redis,`internal` 内置 PG/Redis
-- [x] `backend` Maven 聚合编译、`frontend` 构建、`python-ai` 语法校验已通过
-- [x] `boot-dev` 本地模式烟测已通过,`/api/auth/login`、`/api/upms/routes`、`/api/actuator/health` 可访问
-- [ ] 下一步继续补真实持久化、分片路由封装与更完整联调
-
-## Summary
-
-- 根目录固定为:
- - `backend`
- - `frontend`
- - `app`
-- 参考 `Tik / urbanLifeline` 的模块拆分和基础层组织,但不复用 `Dubbo`、`Vue` 和旧业务实现。
-- 首版目标是“可运行骨架”:
- - 后端:`Spring Boot + Spring Cloud Gateway + Spring RESTful + JWT + RBAC + MyBatis-Plus + PostgreSQL + Redis`
- - Web:单一 `React` 项目,只保留 `api / types / utils / components / dynamic layout / dynamic route`
- - App:独立微信小程序骨架
- - AI:独立 `Python` 服务占位
-- 本地开发采用双模式:
- - 分布式目录与服务边界长期保留
- - `boot-dev` 聚合模块一键启动主要 Java 能力
-- `/api` 只属于 `gateway` 和 `boot-dev` 的外层上下文,不属于子服务自身前缀。
-
-## Key Changes
-
-- `backend` 采用 Maven 多模块,固定为:
- - `backend/common`:`common-core`、`common-web`、`common-security`、`common-mybatis`、`common-redis`、`common-api`
- - `backend/apis`:`api-auth`、`api-upms`、`api-ai`
- - `backend/gateway`:统一入口、鉴权、路由、跨域、trace 透传
- - `backend/auth`:登录、token、当前用户
- - `backend/upms`:用户、角色、菜单授权、菜单与动态路由元数据、组织与区域基座
- - `backend/ai-client`:Java 调 Python 的适配层
- - `backend/boot-dev`:本地聚合启动模块
- - `backend/python-ai`:独立 Python AI 服务占位
-- 服务通信固定为 `REST`,不使用 `Dubbo`
-- 认证固定为 `JWT + RBAC`
- - `gateway` 统一验签
- - 下游服务走网关信任模式
-- 数据访问固定为 `MyBatis-Plus + PostgreSQL + Redis`
- - PostgreSQL 预留分库分表、分区、读写分离扩展位
- - Redis 负责 token、权限缓存、动态路由缓存、热点数据
-- `frontend` 固定为单一 React 项目:
- - `frontend/src/api`
- - `frontend/src/types`
- - `frontend/src/utils`
- - `frontend/src/components`
- - `frontend/src/layouts`
- - `frontend/src/router`
- - 只保留基础壳与底层能力,不做业务页面堆砌
-- `app` 固定为根目录独立微信小程序工程:
- - `app/src/app.*`
- - `app/src/pages/*`
- - `app/src/api`
- - `app/src/utils`
-- `boot-dev` 本地模式固定策略:
- - 保持和分布式服务同样的模块结构与接口边界
- - 本地可单进程启动
- - 可用一个 PostgreSQL 实例承载所有逻辑分片,但字段、路由键、表结构必须与未来分布式模式一致
-
-## Region / Tenant Model
-
-- 系统是“总校 -> 省级分校 -> 市区分校”的多层级租户结构
-- 每个校区下再有部门,当前部门维度至少支持“年级、学科”等业务组织
-- 区域是分库分表的核心路由维度:
- - 以“省份区域”作为首要分片依据
- - 业务表设计时必须显式保留区域路由字段
-- `tb_sys_area.sql` 视为区域基础数据来源约束:
- - 首版必须预留 `tb_sys_area` 基础表和初始化脚本接入位
- - 区域编码、层级、父子关系以 `tb_sys_area.sql` 为准
-- 首版数据模型明确区分两棵树:
- - 区域树:省 / 市 / 区县
- - 组织树:总校 / 分校 / 校区 / 部门
-- 首版 `upms` 基础对象固定包含:
- - `SysArea`
- - `SysTenant`
- - `SysDept`
- - `SysUser`
- - `SysRole`
-- 所有租户级业务主表统一预留字段:
- - `adcode`
- - `tenant_id` 或 `school_id`
- - `tenant_path`
- - `dept_id`
- - `dept_path`
-- 路由与隔离规则固定:
- - 区域字段负责数据库路由与物理分片
- - `tenant_path` / `dept_path` 负责组织级数据隔离
- - 不允许只靠 `dept_path` 承担全部多租户职责
-
-## Database Init Layout
-
-- PostgreSQL 初始化脚本固定放在 `init` 目录下,且按模块独立管理
-- 目录结构固定为类似:
- - `init/pg/00_create_db.sql`
- - `init/pg/01_create_schema.sql`
- - `init/pg/sys/tb_sys_area.sql`
- - `init/pg/auth/*.sql`
- - `init/pg/upms/*.sql`
- - `init/pg/ai/*.sql`
-- 原则固定:
- - 每个模块维护自己的建表 SQL、索引 SQL、初始化数据 SQL
- - 不把所有表混在一个超大 SQL 文件中
- - 公共基础表单独归 `sys` 或 `common` 目录
-- `tb_sys_area.sql` 固定归属:
- - 放在 `init/pg/sys/`
- - 作为区域基础数据的首批初始化脚本
-- 模块 SQL 的职责边界固定:
- - `auth`:登录、token、认证相关表
- - `upms`:用户、角色、菜单、角色菜单授权、学校租户、部门、区域引用关系
- - `ai`:AI 调用记录、任务记录、模型配置占位
-- 初始化脚本执行规则固定:
- - 先执行库 / Schema 基础脚本
- - 再执行 `sys`
- - 再执行各业务模块
- - 各模块内部按 `create table -> index -> init data` 顺序组织
-- 本地开发模式固定:
- - `boot-dev` 使用同一套 `init/pg` 脚本
- - 即使单库启动,也不能做一套“临时简化 SQL”绕过正式字段设计
-
-## Public APIs / Interfaces
-
-- 外部访问前缀固定为:
- - `/api/auth/**`
- - `/api/upms/**`
- - `/api/actuator/health` 或 `/actuator/**`
-- 子服务内部前缀固定为:
- - `auth`:`/auth/**`
- - `upms`:`/upms/**`
-- 路由映射固定为:
- - `gateway: /api/auth/** -> auth: /auth/**`
- - `gateway: /api/upms/** -> upms: /upms/**`
- - `boot-dev` 本地聚合模式下同样暴露 `/api/auth/**`、`/api/upms/**`
-- 统一响应结构固定为:
- - `code`
- - `message`
- - `data`
- - `traceId`
-- `upms` 首版接口能力必须覆盖:
- - 当前用户信息
- - 用户 / 角色 / 菜单授权基座
- - 区域树查询
- - 学校租户树查询
- - 部门树查询
- - 动态路由元数据查询
-- React 动态路由元数据至少包含:
- - `id`
- - `path`
- - `name`
- - `component`
- - `layout`
- - `children`
- - `meta`
-- Python AI 服务首版只固定:
- - `GET /health`
-
-## VS Code Docker Dev
-
-- 支持两种开发模式:
- - `external`:PG、Redis 等直连外部数据库
- - `internal`:开发容器内部创建 PG、Redis
-- 代码同步方式为 bind mount:
- - 宿主机目录直接挂载到容器工作目录
- - 本地修改会实时反映到容器内
- - 不依赖额外“同步脚本”复制代码
-
-## Test Plan
-
-- `backend` 根级 Maven 聚合可编译通过
-- `backend/boot-dev` 可单进程启动,并对外暴露 `/api/auth/**`、`/api/upms/**`
-- `backend/gateway` 可独立启动并完成到 `auth`、`upms` 的路由转发
-- JWT 鉴权链路可烟测通过
-- `frontend` 单项目可完成 `install`、`dev`、`build`
-- React 端可基于 `upms` 返回的动态路由元数据完成路由挂载
-- `app` 可用微信开发者工具直接打开骨架工程
-- `backend/python-ai` 可独立启动并通过 `/health`
-- PostgreSQL 与 Redis 本地联调配置可跑通
-- SQL 初始化验证必须覆盖:
- - `init/pg` 下脚本可按顺序执行
- - `tb_sys_area.sql` 可独立导入
- - `auth`、`upms` 模块脚本可独立维护且组合执行无冲突
-- 区域与租户模型最小验证必须覆盖:
- - 区域树可查询
- - 学校租户树可查询
- - 部门树可挂接到学校租户下
-- 带不同 `adcode` 的数据写入与查询能走统一路由键封装
-
-## Assumptions
-
-- 不使用 `Dubbo`
-- 首版不做真实业务页面、不做学生端真实业务、不做真实 AI 推理
-- `upms` 继续承担首版系统管理中心职责
-- `tb_sys_area.sql` 是必须接入的区域基础数据脚本,且归档在 `init/pg/sys/`
-- 部门当前先按“年级、学科等组织维度”建模,不在首版细化更复杂教学组织规则
diff --git a/docs/plan/课程学习-习题-批改ER与对象类数据流图实施计划.md b/docs/plan/课程学习-习题-批改ER与对象类数据流图实施计划.md
deleted file mode 100644
index 2b4165f..0000000
--- a/docs/plan/课程学习-习题-批改ER与对象类数据流图实施计划.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# 课程学习-习题-批改ER与对象类数据流图实施计划
-问题陈述
-基于现有《AI智能学习系统功能清单》与现有架构图,在不纳入 AI 实现细节的前提下,先完成“课程学习、习题、批改”三条主链路的抽象建模,产出可直接指导后续代码与数据库落地的模块化 ER 图与对象类数据流图(每模块一个目录、两个 drawio 文件)。
-## 当前状态(已核实)
-* 现有 `docs/architecture/数据流图(多角色).drawio (1-158)` 已定义 P1-P7 过程与 D1-D6 数据存储,但把 AI 处理作为主链路节点之一,需要按新范围裁剪为“无 AI 依赖的批改主链路”。
-* 现有 `docs/architecture/系统架构与代码架构.drawio (1-260)` 已给出分层和目标服务域(如 homework-content、grading-orchestrator 等),但仓库当前可运行模块仍以骨架为主。
-* 后端现有聚合模块为 `common/apis/ai-client/auth/upms/gateway/boot-dev`(`backend/pom.xml (13-21)`),尚未落地课程/习题/批改独立业务模块。
-* 数据库当前已落地的核心表主要是组织权限与认证、AI占位:`init/pg/upms/10_create_upms_tables.sql (1-153)`、`init/pg/auth/10_create_auth_tables.sql (1-56)`、`init/pg/ai/10_create_ai_tables.sql (1-47)`;尚无课程、题库、作业、提交、批改结果等业务实体。
-* `auth`/`upms` 服务当前仍以示例返回为主(`backend/auth/src/main/java/com/k12study/auth/service/AuthService.java (1-66)`、`backend/upms/src/main/java/com/k12study/upms/service/UpmsQueryService.java (1-111)`),说明本次需要先补齐领域建模图,再推进代码实现。
-## 目标范围与输出目录
-* 范围仅覆盖:课程学习、习题(题库/作业/提交)、批改(规则批改+教师复核+结果反馈)。
-* 明确不纳入:AI 子系统实现细节(OCR/LLM/ASR/模型编排)。
-* 统一输出根目录:`docs/architecture/modules/`
-* 每模块目录固定包含:`er图.drawio`、`数据流图.drawio`
-* 模块目录规划:
- * `docs/architecture/modules/00-组织与权限上下文/`
- * `docs/architecture/modules/01-课程学习/`
- * `docs/architecture/modules/02-习题与作业/`
- * `docs/architecture/modules/03-批改与反馈/`
-## 方案设计(ER + 对象类数据流图)
-* 00-组织与权限上下文(复用现有 upms/auth)
- * ER 聚焦租户、部门、用户、角色、权限、登录态对业务域的外键约束与数据隔离键(`adcode/tenant_id/tenant_path/dept_id/dept_path`)。
- * 数据流图仅保留“身份鉴权、租户路由、权限校验”对象类交互,作为其它模块前置边界。
-* 01-课程学习
- * ER 规划课程主数据与学习行为:课程、章节、课时、知识点、课程资源、标签、课程-标签关联、学习会话、学习进度。
- * 数据流图采用“学生/教师/机构管理员 -> Boundary(前端/API) -> Control(课程编排/进度服务) -> Entity(课程与进度聚合)”表达。
-* 02-习题与作业
- * ER 规划题库与作业域:题目、题目版本、题目-知识点关联、试卷、试卷题目、作业、作业发布对象、作业提交、题目作答明细。
- * 数据流图区分教师发布链路与学生作答链路,明确命令流(发布/提交)与查询流(拉题/看作业)。
-* 03-批改与反馈
- * ER 规划批改闭环:批改任务、批改规则、客观题判分、主观题复核、评分明细、错因标签、错题沉淀、复习任务。
- * 数据流图体现“自动规则判分 + 教师复核”双通道,不依赖 AI;预留抽象接口节点(GradingEnginePort)用于未来替换实现。
-## 采用的设计方法与原因
-* DDD 限界上下文:将课程、习题作业、批改反馈分域,避免单一大模型导致职责耦合与演进困难。
-* 聚合根与不变量建模:以“作业、提交、批改任务”为聚合根,保证状态转换与评分一致性在事务边界内成立。
-* 状态机建模:对作业(草稿/已发布/已截止)与批改任务(待处理/处理中/待复核/已完成)建状态流,减少流程分支歧义。
-* CQRS(读写分离)思路:发布、提交、批改等写操作与学情统计、作业列表等读模型分离,适配高并发查询场景。
-* 事件驱动与Outbox预留:提交后触发批改、批改后触发错题沉淀/复习任务,降低模块同步耦合并提高可扩展性。
-* 多租户路由键统一:所有核心业务实体强制带租户/组织路径字段,保持与现有 upms 路由策略一致。
-## 实施阶段与验收标准
-* 第一阶段:建立统一 drawio 模板(图例、命名规范、主键/外键/基数标注规范、同步/异步箭头规范)。
-* 第二阶段:完成 4 个模块目录与 8 个 drawio 文件,先 ER 后对象类数据流图,确保跨模块实体引用一致。
-* 第三阶段:做跨图一致性校验(实体命名、状态枚举、事件名、租户字段、API对象名),并补充“代码落地映射”注释(对应未来 backend 模块与 init/pg 子目录)。
-* 验收标准:每个模块必须同时具备 ER 与对象类数据流图;ER 图必须标明主外键、基数、关键索引与租户字段;数据流图必须标明 Actor/Boundary/Control/Entity 与关键输入输出对象。
diff --git a/init/pg/01_create_schema.sql b/init/pg/01_create_schema.sql
index d346aab..99b5c3a 100644
--- a/init/pg/01_create_schema.sql
+++ b/init/pg/01_create_schema.sql
@@ -4,4 +4,3 @@ 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;
diff --git a/init/pg/02_init_modules.sql b/init/pg/02_init_modules.sql
index bb6f136..d210d47 100644
--- a/init/pg/02_init_modules.sql
+++ b/init/pg/02_init_modules.sql
@@ -4,7 +4,6 @@
\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
diff --git a/init/pg/question/10_create_question_tables.sql b/init/pg/question/10_create_question_tables.sql
index 39f36af..8e8fd25 100644
--- a/init/pg/question/10_create_question_tables.sql
+++ b/init/pg/question/10_create_question_tables.sql
@@ -563,6 +563,13 @@ CREATE TABLE IF NOT EXISTS question.gd_review_plan (
plan_status VARCHAR(32) NOT NULL DEFAULT 'TODO',
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_review_plan_stage
+ CHECK (plan_stage IN ('E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'E10', 'CUSTOM')),
+ CONSTRAINT chk_gd_review_plan_status
+ CHECK (plan_status IN ('TODO', 'DONE', 'SKIPPED', 'EXPIRED')),
+ CONSTRAINT uq_gd_review_plan_wrong_stage
+ UNIQUE (wrong_question_id, plan_stage),
CONSTRAINT fk_gd_review_plan_wrong_question
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id)
);
@@ -570,10 +577,99 @@ 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_stage IS '阶段(E1-E10/CUSTOM)';
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 '创建时间';
+COMMENT ON COLUMN question.gd_review_plan.updated_at IS '更新时间';
+
+DROP TABLE IF EXISTS question.gd_review_execution CASCADE;
+DROP TABLE IF EXISTS question.gd_review_stage_policy CASCADE;
+CREATE TABLE IF NOT EXISTS question.gd_review_stage_policy (
+ policy_id VARCHAR(64) PRIMARY KEY,
+ stage_code VARCHAR(16) NOT NULL,
+ stage_order INTEGER NOT NULL,
+ interval_days INTEGER NOT NULL,
+ retry_interval_days INTEGER NOT NULL DEFAULT 1,
+ pass_threshold NUMERIC(5,2) NOT NULL DEFAULT 60.00,
+ max_retry_count INTEGER NOT NULL DEFAULT 3,
+ 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_gd_review_stage_policy_stage
+ CHECK (stage_code IN ('E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'E10')),
+ CONSTRAINT chk_gd_review_stage_policy_stage_order
+ CHECK (stage_order > 0),
+ CONSTRAINT chk_gd_review_stage_policy_interval
+ CHECK (interval_days >= 0),
+ CONSTRAINT chk_gd_review_stage_policy_retry_interval
+ CHECK (retry_interval_days >= 0),
+ CONSTRAINT chk_gd_review_stage_policy_pass_threshold
+ CHECK (pass_threshold >= 0 AND pass_threshold <= 100),
+ CONSTRAINT chk_gd_review_stage_policy_max_retry
+ CHECK (max_retry_count >= 0),
+ CONSTRAINT uq_gd_review_stage_policy_tenant_stage
+ UNIQUE (tenant_id, stage_code),
+ CONSTRAINT uq_gd_review_stage_policy_tenant_order
+ UNIQUE (tenant_id, stage_order)
+);
+COMMENT ON TABLE question.gd_review_stage_policy IS '艾宾浩斯复习阶段策略表';
+COMMENT ON COLUMN question.gd_review_stage_policy.policy_id IS '策略ID';
+COMMENT ON COLUMN question.gd_review_stage_policy.stage_code IS '阶段编码(E1-E10)';
+COMMENT ON COLUMN question.gd_review_stage_policy.stage_order IS '阶段顺序';
+COMMENT ON COLUMN question.gd_review_stage_policy.interval_days IS '通过后下阶段间隔天数';
+COMMENT ON COLUMN question.gd_review_stage_policy.retry_interval_days IS '失败后重试间隔天数';
+COMMENT ON COLUMN question.gd_review_stage_policy.pass_threshold IS '通过阈值(0-100)';
+COMMENT ON COLUMN question.gd_review_stage_policy.max_retry_count IS '当前阶段最大重试次数';
+COMMENT ON COLUMN question.gd_review_stage_policy.enabled IS '是否启用';
+COMMENT ON COLUMN question.gd_review_stage_policy.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.gd_review_stage_policy.created_at IS '创建时间';
+COMMENT ON COLUMN question.gd_review_stage_policy.updated_at IS '更新时间';
+
+CREATE TABLE IF NOT EXISTS question.gd_review_execution (
+ execution_id VARCHAR(64) PRIMARY KEY,
+ review_plan_id VARCHAR(64) NOT NULL,
+ wrong_question_id VARCHAR(64) NOT NULL,
+ student_id VARCHAR(64) NOT NULL,
+ stage_code VARCHAR(16) NOT NULL,
+ result_status VARCHAR(16) NOT NULL,
+ score NUMERIC(8,2),
+ reviewed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ next_plan_date DATE,
+ retry_count INTEGER NOT NULL DEFAULT 0,
+ evidence_json JSONB NOT NULL DEFAULT '{}'::JSONB,
+ tenant_id VARCHAR(64) NOT NULL,
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT chk_gd_review_execution_stage
+ CHECK (stage_code IN ('E1', 'E2', 'E3', 'E4', 'E5', 'E6', 'E7', 'E8', 'E9', 'E10', 'CUSTOM')),
+ CONSTRAINT chk_gd_review_execution_result
+ CHECK (result_status IN ('PASS', 'FAIL', 'SKIPPED')),
+ CONSTRAINT chk_gd_review_execution_retry
+ CHECK (retry_count >= 0),
+ CONSTRAINT chk_gd_review_execution_evidence_json
+ CHECK (jsonb_typeof(evidence_json) = 'object'),
+ CONSTRAINT fk_gd_review_execution_plan
+ FOREIGN KEY (review_plan_id) REFERENCES question.gd_review_plan(review_plan_id) ON DELETE CASCADE,
+ CONSTRAINT fk_gd_review_execution_wrong_question
+ FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id),
+ CONSTRAINT fk_gd_review_execution_student
+ FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id)
+);
+COMMENT ON TABLE question.gd_review_execution IS '复习执行记录表';
+COMMENT ON COLUMN question.gd_review_execution.execution_id IS '执行记录ID';
+COMMENT ON COLUMN question.gd_review_execution.review_plan_id IS '复习计划ID';
+COMMENT ON COLUMN question.gd_review_execution.wrong_question_id IS '错题ID';
+COMMENT ON COLUMN question.gd_review_execution.student_id IS '学员ID';
+COMMENT ON COLUMN question.gd_review_execution.stage_code IS '复习阶段编码';
+COMMENT ON COLUMN question.gd_review_execution.result_status IS '执行结果(PASS/FAIL/SKIPPED)';
+COMMENT ON COLUMN question.gd_review_execution.score IS '本次得分';
+COMMENT ON COLUMN question.gd_review_execution.reviewed_at IS '执行时间';
+COMMENT ON COLUMN question.gd_review_execution.next_plan_date IS '建议下次复习日期';
+COMMENT ON COLUMN question.gd_review_execution.retry_count IS '当前阶段重试次数';
+COMMENT ON COLUMN question.gd_review_execution.evidence_json IS '执行证据JSON(答案摘要/耗时/反馈等)';
+COMMENT ON COLUMN question.gd_review_execution.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.gd_review_execution.created_at IS '创建时间';
DROP TABLE IF EXISTS question.gd_teacher_comment CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_teacher_comment (
@@ -604,6 +700,7 @@ 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,
+ question_id VARCHAR(64) NOT NULL,
wrong_question_id VARCHAR(64),
source_submission_id VARCHAR(64),
source_answer_id VARCHAR(64),
@@ -618,6 +715,8 @@ CREATE TABLE IF NOT EXISTS question.gd_explanation_submission (
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_question
+ FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_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
@@ -630,6 +729,7 @@ CREATE TABLE IF NOT EXISTS question.gd_explanation_submission (
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.question_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';
@@ -717,13 +817,456 @@ 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_review_plan_status_date
+ ON question.gd_review_plan(plan_status, plan_date);
+CREATE INDEX IF NOT EXISTS idx_gd_review_plan_stage_status
+ ON question.gd_review_plan(plan_stage, plan_status, plan_date);
+CREATE INDEX IF NOT EXISTS idx_gd_review_stage_policy_tenant_enabled
+ ON question.gd_review_stage_policy(tenant_id, enabled, stage_order);
+CREATE INDEX IF NOT EXISTS idx_gd_review_execution_plan_time
+ ON question.gd_review_execution(review_plan_id, reviewed_at DESC);
+CREATE INDEX IF NOT EXISTS idx_gd_review_execution_wrong_question
+ ON question.gd_review_execution(wrong_question_id, reviewed_at DESC);
+CREATE INDEX IF NOT EXISTS idx_gd_review_execution_student_time
+ ON question.gd_review_execution(student_id, reviewed_at DESC);
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_question
+ ON question.gd_explanation_submission(question_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);
+
+-- recommendation模块并入question习题模块(rc_*)
+DROP TABLE IF EXISTS question.rc_recommendation_task CASCADE;
+CREATE TABLE IF NOT EXISTS question.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 question.rc_recommendation_task IS '推荐任务表';
+COMMENT ON COLUMN question.rc_recommendation_task.task_id IS '推荐任务ID';
+COMMENT ON COLUMN question.rc_recommendation_task.user_id IS '推荐目标用户ID';
+COMMENT ON COLUMN question.rc_recommendation_task.scene_code IS '推荐场景编码(HOME/WRONGBOOK/REVIEW等)';
+COMMENT ON COLUMN question.rc_recommendation_task.trigger_source IS '触发来源(SCHEDULE/EVENT/MANUAL)';
+COMMENT ON COLUMN question.rc_recommendation_task.strategy_version IS '推荐策略版本';
+COMMENT ON COLUMN question.rc_recommendation_task.profile_snapshot_json IS '画像快照JSON';
+COMMENT ON COLUMN question.rc_recommendation_task.status IS '任务状态';
+COMMENT ON COLUMN question.rc_recommendation_task.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_recommendation_task.created_at IS '创建时间';
+COMMENT ON COLUMN question.rc_recommendation_task.updated_at IS '更新时间';
+
+DROP TABLE IF EXISTS question.rc_recommendation_item CASCADE;
+CREATE TABLE IF NOT EXISTS question.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 question.rc_recommendation_task(task_id) ON DELETE CASCADE
+);
+COMMENT ON TABLE question.rc_recommendation_item IS '推荐结果明细表';
+COMMENT ON COLUMN question.rc_recommendation_item.item_id IS '推荐项ID';
+COMMENT ON COLUMN question.rc_recommendation_item.task_id IS '推荐任务ID';
+COMMENT ON COLUMN question.rc_recommendation_item.content_type IS '内容类型';
+COMMENT ON COLUMN question.rc_recommendation_item.content_object_id IS '内容对象ID';
+COMMENT ON COLUMN question.rc_recommendation_item.content_object_type IS '内容对象类型';
+COMMENT ON COLUMN question.rc_recommendation_item.rank_no IS '推荐位次';
+COMMENT ON COLUMN question.rc_recommendation_item.score IS '推荐分';
+COMMENT ON COLUMN question.rc_recommendation_item.reason_codes IS '推荐原因编码集合';
+COMMENT ON COLUMN question.rc_recommendation_item.metadata_json IS '扩展元数据JSON';
+COMMENT ON COLUMN question.rc_recommendation_item.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_recommendation_item.created_at IS '创建时间';
+
+DROP TABLE IF EXISTS question.rc_recommendation_feedback CASCADE;
+CREATE TABLE IF NOT EXISTS question.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 question.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 question.rc_recommendation_feedback IS '推荐反馈事件表';
+COMMENT ON COLUMN question.rc_recommendation_feedback.feedback_id IS '反馈事件ID';
+COMMENT ON COLUMN question.rc_recommendation_feedback.item_id IS '推荐项ID';
+COMMENT ON COLUMN question.rc_recommendation_feedback.user_id IS '用户ID';
+COMMENT ON COLUMN question.rc_recommendation_feedback.event_type IS '事件类型';
+COMMENT ON COLUMN question.rc_recommendation_feedback.event_value IS '事件值(可选)';
+COMMENT ON COLUMN question.rc_recommendation_feedback.event_detail_json IS '事件详情JSON';
+COMMENT ON COLUMN question.rc_recommendation_feedback.event_time IS '事件发生时间';
+COMMENT ON COLUMN question.rc_recommendation_feedback.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_recommendation_feedback.created_at IS '创建时间';
+
+DROP TABLE IF EXISTS question.rc_recommendation_effect_daily CASCADE;
+CREATE TABLE IF NOT EXISTS question.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 question.rc_recommendation_effect_daily IS '推荐效果日统计表';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.stat_date IS '统计日期';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.scene_code IS '推荐场景编码';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.strategy_version IS '策略版本';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.expose_count IS '曝光次数';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.click_count IS '点击次数';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.complete_count IS '完成次数';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.dislike_count IS '不喜欢次数';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.ctr IS '点击率';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.complete_rate IS '完成率';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.created_at IS '创建时间';
+COMMENT ON COLUMN question.rc_recommendation_effect_daily.updated_at IS '更新时间';
+
+CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_user_scene
+ ON question.rc_recommendation_task(user_id, scene_code, created_at DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_tenant_status
+ ON question.rc_recommendation_task(tenant_id, status, created_at DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_task
+ ON question.rc_recommendation_item(task_id, rank_no);
+CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_content
+ ON question.rc_recommendation_item(content_object_type, content_object_id);
+CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_item_event
+ ON question.rc_recommendation_feedback(item_id, event_type, event_time DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_user_event
+ ON question.rc_recommendation_feedback(user_id, event_type, event_time DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_recommendation_effect_daily_tenant_date
+ ON question.rc_recommendation_effect_daily(tenant_id, stat_date DESC);
+
+DROP TABLE IF EXISTS question.rc_student_profile_feature CASCADE;
+DROP TABLE IF EXISTS question.rc_student_profile_snapshot CASCADE;
+CREATE TABLE IF NOT EXISTS question.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 question.rc_student_profile_snapshot IS '学员画像快照表';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_id IS '画像ID';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.user_id IS '学员ID';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_version IS '画像版本';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_date IS '画像日期';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.feature_source_version IS '特征来源版本';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_json IS '画像JSON';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.confidence_score IS '画像置信度';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.profile_status IS '画像状态';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.created_at IS '创建时间';
+COMMENT ON COLUMN question.rc_student_profile_snapshot.updated_at IS '更新时间';
+
+CREATE TABLE IF NOT EXISTS question.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 question.rc_student_profile_snapshot(profile_id) ON DELETE CASCADE
+);
+COMMENT ON TABLE question.rc_student_profile_feature IS '学员画像特征明细表';
+COMMENT ON COLUMN question.rc_student_profile_feature.feature_id IS '特征ID';
+COMMENT ON COLUMN question.rc_student_profile_feature.profile_id IS '画像ID';
+COMMENT ON COLUMN question.rc_student_profile_feature.feature_group IS '特征组';
+COMMENT ON COLUMN question.rc_student_profile_feature.feature_key IS '特征键';
+COMMENT ON COLUMN question.rc_student_profile_feature.feature_value_num IS '数值特征值';
+COMMENT ON COLUMN question.rc_student_profile_feature.feature_value_text IS '文本特征值';
+COMMENT ON COLUMN question.rc_student_profile_feature.feature_value_json IS 'JSON特征值';
+COMMENT ON COLUMN question.rc_student_profile_feature.value_type IS '值类型';
+COMMENT ON COLUMN question.rc_student_profile_feature.source_domain IS '来源域';
+COMMENT ON COLUMN question.rc_student_profile_feature.source_ref_id IS '来源对象ID';
+COMMENT ON COLUMN question.rc_student_profile_feature.sample_time IS '采样时间';
+COMMENT ON COLUMN question.rc_student_profile_feature.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_student_profile_feature.created_at IS '创建时间';
+
+DROP TABLE IF EXISTS question.rc_learning_loop_event CASCADE;
+DROP TABLE IF EXISTS question.rc_learning_loop_case CASCADE;
+CREATE TABLE IF NOT EXISTS question.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 question.rc_student_profile_snapshot(profile_id),
+ CONSTRAINT fk_rc_learning_loop_case_recommendation_task
+ FOREIGN KEY (recommendation_task_id) REFERENCES question.rc_recommendation_task(task_id),
+ CONSTRAINT fk_rc_learning_loop_case_recommendation_item
+ FOREIGN KEY (recommendation_item_id) REFERENCES question.rc_recommendation_item(item_id),
+ CONSTRAINT fk_rc_learning_loop_case_feedback
+ FOREIGN KEY (feedback_id) REFERENCES question.rc_recommendation_feedback(feedback_id)
+);
+COMMENT ON TABLE question.rc_learning_loop_case IS '学习闭环主表(错题-复习-推荐-成就)';
+COMMENT ON COLUMN question.rc_learning_loop_case.loop_case_id IS '闭环案例ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.user_id IS '用户ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.wrong_question_id IS '错题ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.review_plan_id IS '复习计划ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.profile_id IS '画像ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.recommendation_task_id IS '推荐任务ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.recommendation_item_id IS '推荐项ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.feedback_id IS '反馈事件ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.user_achievement_id IS '成就记录ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.loop_status IS '闭环状态';
+COMMENT ON COLUMN question.rc_learning_loop_case.effect_score IS '闭环效果分';
+COMMENT ON COLUMN question.rc_learning_loop_case.opened_at IS '闭环开启时间';
+COMMENT ON COLUMN question.rc_learning_loop_case.closed_at IS '闭环关闭时间';
+COMMENT ON COLUMN question.rc_learning_loop_case.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_learning_loop_case.created_at IS '创建时间';
+COMMENT ON COLUMN question.rc_learning_loop_case.updated_at IS '更新时间';
+
+CREATE TABLE IF NOT EXISTS question.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 question.rc_learning_loop_case(loop_case_id) ON DELETE CASCADE
+);
+COMMENT ON TABLE question.rc_learning_loop_event IS '学习闭环阶段事件表';
+COMMENT ON COLUMN question.rc_learning_loop_event.loop_event_id IS '闭环事件ID';
+COMMENT ON COLUMN question.rc_learning_loop_event.loop_case_id IS '闭环案例ID';
+COMMENT ON COLUMN question.rc_learning_loop_event.stage_code IS '阶段编码';
+COMMENT ON COLUMN question.rc_learning_loop_event.stage_status IS '阶段状态';
+COMMENT ON COLUMN question.rc_learning_loop_event.event_time IS '事件时间';
+COMMENT ON COLUMN question.rc_learning_loop_event.event_score IS '阶段得分';
+COMMENT ON COLUMN question.rc_learning_loop_event.event_detail_json IS '事件详情JSON';
+COMMENT ON COLUMN question.rc_learning_loop_event.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_learning_loop_event.created_at IS '创建时间';
+
+DROP TABLE IF EXISTS question.rc_learning_loop_effect_daily CASCADE;
+CREATE TABLE IF NOT EXISTS question.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 question.rc_learning_loop_effect_daily IS '学习闭环效果日统计表';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.stat_date IS '统计日期';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.opened_case_count IS '开启案例数';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.closed_case_count IS '关闭案例数';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.achieved_case_count IS '达成成就案例数';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.avg_effect_score IS '平均效果分';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.avg_close_hours IS '平均闭环时长(小时)';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.created_at IS '创建时间';
+COMMENT ON COLUMN question.rc_learning_loop_effect_daily.updated_at IS '更新时间';
+
+CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_user_date
+ ON question.rc_student_profile_snapshot(user_id, profile_date DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_tenant_version
+ ON question.rc_student_profile_snapshot(tenant_id, profile_version, created_at DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_profile_group
+ ON question.rc_student_profile_feature(profile_id, feature_group, feature_key);
+CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_source
+ ON question.rc_student_profile_feature(source_domain, source_ref_id);
+CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_user_status
+ ON question.rc_learning_loop_case(user_id, loop_status, opened_at DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_wrong_question
+ ON question.rc_learning_loop_case(wrong_question_id, opened_at DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_tenant_status
+ ON question.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 question.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 question.rc_learning_loop_event(tenant_id, event_time DESC);
+CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_effect_daily_tenant_date
+ ON question.rc_learning_loop_effect_daily(tenant_id, stat_date DESC);
+
+DROP TABLE IF EXISTS question.rc_loop_metric_map CASCADE;
+DROP TABLE IF EXISTS question.rc_loop_stage_achievement_map CASCADE;
+CREATE TABLE IF NOT EXISTS question.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')
+);
+COMMENT ON TABLE question.rc_loop_stage_achievement_map IS '闭环阶段到成就事件映射表(通用配置)';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.stage_map_id IS '阶段映射ID';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.stage_code IS '闭环阶段编码';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.event_code IS '成就事件编码';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.template_id IS '成就规则模板ID';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.metric_id IS '成就指标ID';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.award_source IS '发放来源';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.priority IS '优先级';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.enabled IS '是否启用';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.ext_json IS '扩展配置JSON';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.created_at IS '创建时间';
+COMMENT ON COLUMN question.rc_loop_stage_achievement_map.updated_at IS '更新时间';
+
+CREATE TABLE IF NOT EXISTS question.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')
+);
+COMMENT ON TABLE question.rc_loop_metric_map IS '闭环指标映射表(通用配置)';
+COMMENT ON COLUMN question.rc_loop_metric_map.metric_map_id IS '指标映射ID';
+COMMENT ON COLUMN question.rc_loop_metric_map.stage_code IS '闭环阶段编码';
+COMMENT ON COLUMN question.rc_loop_metric_map.metric_id IS '成就指标ID';
+COMMENT ON COLUMN question.rc_loop_metric_map.source_field IS '来源字段名';
+COMMENT ON COLUMN question.rc_loop_metric_map.agg_method IS '聚合方法';
+COMMENT ON COLUMN question.rc_loop_metric_map.weight IS '权重';
+COMMENT ON COLUMN question.rc_loop_metric_map.enabled IS '是否启用';
+COMMENT ON COLUMN question.rc_loop_metric_map.ext_json IS '扩展配置JSON';
+COMMENT ON COLUMN question.rc_loop_metric_map.tenant_id IS '租户ID';
+COMMENT ON COLUMN question.rc_loop_metric_map.created_at IS '创建时间';
+COMMENT ON COLUMN question.rc_loop_metric_map.updated_at IS '更新时间';
+
+CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_tenant_stage
+ ON question.rc_loop_stage_achievement_map(tenant_id, stage_code, enabled, priority);
+CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_event
+ ON question.rc_loop_stage_achievement_map(event_code, tenant_id, enabled);
+CREATE INDEX IF NOT EXISTS idx_rc_loop_metric_map_tenant_stage
+ ON question.rc_loop_metric_map(tenant_id, stage_code, enabled);
diff --git a/init/pg/question/20_init_question_seed.sql b/init/pg/question/20_init_question_seed.sql
index f8e4c4a..86f168c 100644
--- a/init/pg/question/20_init_question_seed.sql
+++ b/init/pg/question/20_init_question_seed.sql
@@ -3,3 +3,50 @@
-- 批改与反馈模块初始化数据占位
-- 按需补充错因标签、统一答案批改结果、复习计划策略等种子数据
+
+-- 艾宾浩斯复习阶段默认策略(E1-E6:1/2/4/7/15/30天)
+WITH stage_defaults(stage_code, stage_order, interval_days, retry_interval_days, pass_threshold, max_retry_count) AS (
+ VALUES
+ ('E1', 1, 1, 1, 60.00, 3),
+ ('E2', 2, 2, 1, 65.00, 3),
+ ('E3', 3, 4, 1, 70.00, 2),
+ ('E4', 4, 7, 2, 75.00, 2),
+ ('E5', 5, 15, 2, 80.00, 2),
+ ('E6', 6, 30, 3, 85.00, 1)
+)
+INSERT INTO question.gd_review_stage_policy (
+ policy_id,
+ stage_code,
+ stage_order,
+ interval_days,
+ retry_interval_days,
+ pass_threshold,
+ max_retry_count,
+ enabled,
+ tenant_id,
+ created_at,
+ updated_at
+)
+SELECT
+ 'RSP_' || SUBSTRING(MD5(t.tenant_id || ':' || s.stage_code) FROM 1 FOR 24) AS policy_id,
+ s.stage_code,
+ s.stage_order,
+ s.interval_days,
+ s.retry_interval_days,
+ s.pass_threshold,
+ s.max_retry_count,
+ TRUE,
+ t.tenant_id,
+ CURRENT_TIMESTAMP,
+ CURRENT_TIMESTAMP
+FROM upms.tb_sys_tenant t
+CROSS JOIN stage_defaults s
+ON CONFLICT (tenant_id, stage_code)
+DO UPDATE SET
+ stage_order = EXCLUDED.stage_order,
+ interval_days = EXCLUDED.interval_days,
+ retry_interval_days = EXCLUDED.retry_interval_days,
+ pass_threshold = EXCLUDED.pass_threshold,
+ max_retry_count = EXCLUDED.max_retry_count,
+ enabled = EXCLUDED.enabled,
+ updated_at = CURRENT_TIMESTAMP;
diff --git a/init/pg/recommendation/10_create_recommendation_tables.sql b/init/pg/recommendation/10_create_recommendation_tables.sql
deleted file mode 100644
index ea9dbeb..0000000
--- a/init/pg/recommendation/10_create_recommendation_tables.sql
+++ /dev/null
@@ -1,440 +0,0 @@
-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);