先更新1版记录下

This commit is contained in:
2026-04-16 11:30:30 +08:00
parent ead8e2edd5
commit 2f2d796e30
40 changed files with 393714 additions and 4071 deletions

View File

@@ -1,4 +1,7 @@
CREATE SCHEMA IF NOT EXISTS sys;
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SCHEMA IF NOT EXISTS upms;
CREATE SCHEMA IF NOT EXISTS ai;
CREATE SCHEMA IF NOT EXISTS course;
CREATE SCHEMA IF NOT EXISTS question;
CREATE SCHEMA IF NOT EXISTS achievement;
CREATE SCHEMA IF NOT EXISTS recommendation;

View File

@@ -1,7 +1,14 @@
\i /docker-entrypoint-initdb.d/upms/10_create_upms_tables.sql
\i /docker-entrypoint-initdb.d/auth/10_create_auth_tables.sql
\i /docker-entrypoint-initdb.d/ai/10_create_ai_tables.sql
\i /docker-entrypoint-initdb.d/course/10_create_course_tables.sql
\i /docker-entrypoint-initdb.d/question/10_create_question_tables.sql
\i /docker-entrypoint-initdb.d/achievement/10_create_achievement_tables.sql
\i /docker-entrypoint-initdb.d/recommendation/10_create_recommendation_tables.sql
\i /docker-entrypoint-initdb.d/sys/sys_area.sql
\i /docker-entrypoint-initdb.d/upms/20_init_upms_seed.sql
\i /docker-entrypoint-initdb.d/auth/20_init_auth_seed.sql
\i /docker-entrypoint-initdb.d/ai/20_init_ai_seed.sql
\i /docker-entrypoint-initdb.d/course/20_init_course_seed.sql
\i /docker-entrypoint-initdb.d/question/20_init_question_seed.sql
\i /docker-entrypoint-initdb.d/achievement/20_init_achievement_seed.sql

View File

@@ -0,0 +1,407 @@
DROP SCHEMA IF EXISTS achievement CASCADE;
CREATE SCHEMA IF NOT EXISTS achievement;
DROP TABLE IF EXISTS achievement.ac_achievement CASCADE;
CREATE TABLE IF NOT EXISTS achievement.ac_achievement (
achievement_id VARCHAR(64) PRIMARY KEY,
achievement_code VARCHAR(64) UNIQUE NOT NULL,
achievement_name VARCHAR(128) NOT NULL,
achievement_desc TEXT,
achievement_category VARCHAR(32) NOT NULL DEFAULT 'SCORE',
trigger_event VARCHAR(32) NOT NULL DEFAULT 'GRADE_PUBLISHED',
trigger_rule_json JSONB NOT NULL DEFAULT '{}'::JSONB,
badge_level VARCHAR(16) NOT NULL DEFAULT 'BRONZE',
badge_icon_url VARCHAR(512),
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
tenant_id VARCHAR(64) NOT NULL,
created_by VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_ac_achievement_category
CHECK (achievement_category IN ('SCORE', 'PROGRESS', 'TEACHING', 'ENGAGEMENT', 'MANUAL')),
CONSTRAINT chk_ac_achievement_trigger_event
CHECK (trigger_event IN ('GRADE_PUBLISHED', 'WRONG_QUESTION_MASTERED', 'TASK_COMPLETED', 'REVIEW_COMPLETED', 'MANUAL')),
CONSTRAINT chk_ac_achievement_badge_level
CHECK (badge_level IN ('BRONZE', 'SILVER', 'GOLD', 'PLATINUM')),
CONSTRAINT chk_ac_achievement_status
CHECK (status IN ('ACTIVE', 'DISABLED')),
CONSTRAINT chk_ac_achievement_trigger_rule_json
CHECK (jsonb_typeof(trigger_rule_json) = 'object'),
CONSTRAINT fk_ac_achievement_created_by
FOREIGN KEY (created_by) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE achievement.ac_achievement IS '成就定义表';
COMMENT ON COLUMN achievement.ac_achievement.achievement_id IS '成就ID';
COMMENT ON COLUMN achievement.ac_achievement.achievement_code IS '成就编码';
COMMENT ON COLUMN achievement.ac_achievement.achievement_name IS '成就名称';
COMMENT ON COLUMN achievement.ac_achievement.achievement_desc IS '成就描述';
COMMENT ON COLUMN achievement.ac_achievement.achievement_category IS '成就类别SCORE/PROGRESS/TEACHING/ENGAGEMENT/MANUAL';
COMMENT ON COLUMN achievement.ac_achievement.trigger_event IS '触发事件GRADE_PUBLISHED/WRONG_QUESTION_MASTERED/TASK_COMPLETED/REVIEW_COMPLETED/MANUAL';
COMMENT ON COLUMN achievement.ac_achievement.trigger_rule_json IS '触发规则JSON可配置成绩阈值、次数阈值等';
COMMENT ON COLUMN achievement.ac_achievement.badge_level IS '徽章等级BRONZE/SILVER/GOLD/PLATINUM';
COMMENT ON COLUMN achievement.ac_achievement.badge_icon_url IS '徽章图标URL';
COMMENT ON COLUMN achievement.ac_achievement.status IS '状态ACTIVE/DISABLED';
COMMENT ON COLUMN achievement.ac_achievement.tenant_id IS '租户ID';
COMMENT ON COLUMN achievement.ac_achievement.created_by IS '创建人ID';
COMMENT ON COLUMN achievement.ac_achievement.created_at IS '创建时间';
COMMENT ON COLUMN achievement.ac_achievement.updated_at IS '更新时间';
DROP TABLE IF EXISTS achievement.ac_achievement_role_rel CASCADE;
CREATE TABLE IF NOT EXISTS achievement.ac_achievement_role_rel (
achievement_id VARCHAR(64) NOT NULL,
role_id VARCHAR(64) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (achievement_id, role_id),
CONSTRAINT fk_ac_achievement_role_rel_achievement
FOREIGN KEY (achievement_id) REFERENCES achievement.ac_achievement(achievement_id) ON DELETE CASCADE,
CONSTRAINT fk_ac_achievement_role_rel_role
FOREIGN KEY (role_id) REFERENCES upms.tb_sys_role(role_id)
);
COMMENT ON TABLE achievement.ac_achievement_role_rel IS '成就-角色绑定表';
COMMENT ON COLUMN achievement.ac_achievement_role_rel.achievement_id IS '成就ID';
COMMENT ON COLUMN achievement.ac_achievement_role_rel.role_id IS '角色ID';
COMMENT ON COLUMN achievement.ac_achievement_role_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN achievement.ac_achievement_role_rel.created_at IS '创建时间';
DROP TABLE IF EXISTS achievement.ac_user_achievement CASCADE;
CREATE TABLE IF NOT EXISTS achievement.ac_user_achievement (
user_achievement_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
achievement_id VARCHAR(64) NOT NULL,
role_id VARCHAR(64) NOT NULL,
summary_id VARCHAR(64),
grading_task_id VARCHAR(64),
score_snapshot NUMERIC(8,2),
grade_level VARCHAR(8),
surpass_ratio NUMERIC(5,2),
used_seconds INTEGER,
award_source VARCHAR(32) NOT NULL DEFAULT 'RULE_ENGINE',
award_reason VARCHAR(256),
evidence_json JSONB NOT NULL DEFAULT '{}'::JSONB,
achieved_count INTEGER NOT NULL DEFAULT 1,
first_achieved_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_achieved_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_ac_user_achievement_user_achievement_role
UNIQUE (user_id, achievement_id, role_id),
CONSTRAINT chk_ac_user_achievement_award_source
CHECK (award_source IN ('RULE_ENGINE', 'MANUAL', 'SYSTEM')),
CONSTRAINT chk_ac_user_achievement_evidence_json
CHECK (jsonb_typeof(evidence_json) = 'object'),
CONSTRAINT chk_ac_user_achievement_achieved_count
CHECK (achieved_count > 0),
CONSTRAINT fk_ac_user_achievement_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_ac_user_achievement_role_binding
FOREIGN KEY (achievement_id, role_id) REFERENCES achievement.ac_achievement_role_rel(achievement_id, role_id),
CONSTRAINT fk_ac_user_achievement_summary
FOREIGN KEY (summary_id) REFERENCES question.gd_score_summary(summary_id),
CONSTRAINT fk_ac_user_achievement_task
FOREIGN KEY (grading_task_id) REFERENCES question.gd_grading_task(grading_task_id)
);
COMMENT ON TABLE achievement.ac_user_achievement IS '用户成就记录表';
COMMENT ON COLUMN achievement.ac_user_achievement.user_achievement_id IS '用户成就记录ID';
COMMENT ON COLUMN achievement.ac_user_achievement.user_id IS '用户ID';
COMMENT ON COLUMN achievement.ac_user_achievement.achievement_id IS '成就ID';
COMMENT ON COLUMN achievement.ac_user_achievement.role_id IS '获奖时角色ID';
COMMENT ON COLUMN achievement.ac_user_achievement.summary_id IS '成绩汇总ID来源于question.gd_score_summary';
COMMENT ON COLUMN achievement.ac_user_achievement.grading_task_id IS '批改任务ID来源于question.gd_grading_task';
COMMENT ON COLUMN achievement.ac_user_achievement.score_snapshot IS '成绩快照分数';
COMMENT ON COLUMN achievement.ac_user_achievement.grade_level IS '成绩快照等级';
COMMENT ON COLUMN achievement.ac_user_achievement.surpass_ratio IS '成绩快照超越比例';
COMMENT ON COLUMN achievement.ac_user_achievement.used_seconds IS '成绩快照耗时(秒)';
COMMENT ON COLUMN achievement.ac_user_achievement.award_source IS '发放来源RULE_ENGINE/MANUAL/SYSTEM';
COMMENT ON COLUMN achievement.ac_user_achievement.award_reason IS '发放原因';
COMMENT ON COLUMN achievement.ac_user_achievement.evidence_json IS '发放证据JSON可存规则命中明细';
COMMENT ON COLUMN achievement.ac_user_achievement.achieved_count IS '累计达成次数';
COMMENT ON COLUMN achievement.ac_user_achievement.first_achieved_at IS '首次达成时间';
COMMENT ON COLUMN achievement.ac_user_achievement.last_achieved_at IS '最近达成时间';
COMMENT ON COLUMN achievement.ac_user_achievement.tenant_id IS '租户ID';
COMMENT ON COLUMN achievement.ac_user_achievement.created_at IS '创建时间';
COMMENT ON COLUMN achievement.ac_user_achievement.updated_at IS '更新时间';
DROP TABLE IF EXISTS achievement.ac_user_achievement_progress CASCADE;
CREATE TABLE IF NOT EXISTS achievement.ac_user_achievement_progress (
progress_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
achievement_id VARCHAR(64) NOT NULL,
role_id VARCHAR(64) NOT NULL,
current_value NUMERIC(12,2) NOT NULL DEFAULT 0,
target_value NUMERIC(12,2) NOT NULL,
progress_rate NUMERIC(5,2) NOT NULL DEFAULT 0,
effort_index NUMERIC(6,2) NOT NULL DEFAULT 0,
improvement_rate NUMERIC(6,2) NOT NULL DEFAULT 0,
resilience_index NUMERIC(6,2) NOT NULL DEFAULT 0,
streak_days INTEGER NOT NULL DEFAULT 0,
last_checkin_date DATE,
progress_status VARCHAR(16) NOT NULL DEFAULT 'IN_PROGRESS',
progress_snapshot_json JSONB NOT NULL DEFAULT '{}'::JSONB,
last_evaluated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
achieved_at TIMESTAMP,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_ac_user_achievement_progress_user_achievement_role
UNIQUE (user_id, achievement_id, role_id),
CONSTRAINT chk_ac_user_achievement_progress_current
CHECK (current_value >= 0),
CONSTRAINT chk_ac_user_achievement_progress_target
CHECK (target_value > 0),
CONSTRAINT chk_ac_user_achievement_progress_rate
CHECK (progress_rate >= 0 AND progress_rate <= 100),
CONSTRAINT chk_ac_user_achievement_progress_effort_index
CHECK (effort_index >= 0 AND effort_index <= 100),
CONSTRAINT chk_ac_user_achievement_progress_improvement_rate
CHECK (improvement_rate >= -100 AND improvement_rate <= 1000),
CONSTRAINT chk_ac_user_achievement_progress_resilience_index
CHECK (resilience_index >= 0 AND resilience_index <= 100),
CONSTRAINT chk_ac_user_achievement_progress_streak_days
CHECK (streak_days >= 0),
CONSTRAINT chk_ac_user_achievement_progress_status
CHECK (progress_status IN ('IN_PROGRESS', 'ACHIEVED', 'EXPIRED')),
CONSTRAINT chk_ac_user_achievement_progress_snapshot_json
CHECK (jsonb_typeof(progress_snapshot_json) = 'object'),
CONSTRAINT chk_ac_user_achievement_progress_achieved_at
CHECK (
(progress_status = 'ACHIEVED' AND achieved_at IS NOT NULL)
OR (progress_status IN ('IN_PROGRESS', 'EXPIRED') AND achieved_at IS NULL)
),
CONSTRAINT fk_ac_user_achievement_progress_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_ac_user_achievement_progress_role_binding
FOREIGN KEY (achievement_id, role_id) REFERENCES achievement.ac_achievement_role_rel(achievement_id, role_id)
);
COMMENT ON TABLE achievement.ac_user_achievement_progress IS '用户成就进度表(含未达成记录)';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.progress_id IS '成就进度ID';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.user_id IS '用户ID';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.achievement_id IS '成就ID';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.role_id IS '角色ID学生/教师)';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.current_value IS '当前进度值';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.target_value IS '目标值';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.progress_rate IS '完成率0-100';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.effort_index IS '努力指数0-100';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.improvement_rate IS '进步率(可为负)';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.resilience_index IS '学习韧性指数0-100';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.streak_days IS '连续打卡天数';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.last_checkin_date IS '最近打卡日期';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.progress_status IS '进度状态IN_PROGRESS/ACHIEVED/EXPIRED';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.progress_snapshot_json IS '进度快照JSON可记录最近一次评估明细';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.last_evaluated_at IS '最近评估时间';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.achieved_at IS '达成时间';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.tenant_id IS '租户ID';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.created_at IS '创建时间';
COMMENT ON COLUMN achievement.ac_user_achievement_progress.updated_at IS '更新时间';
DROP TABLE IF EXISTS achievement.ac_user_achievement_daily_fact CASCADE;
CREATE TABLE IF NOT EXISTS achievement.ac_user_achievement_daily_fact (
stat_date DATE NOT NULL,
user_id VARCHAR(64) NOT NULL,
role_id VARCHAR(64) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
gained_achievement_count INTEGER NOT NULL DEFAULT 0,
active_achievement_count INTEGER NOT NULL DEFAULT 0,
progress_delta NUMERIC(10,4) NOT NULL DEFAULT 0,
effort_index NUMERIC(6,2) NOT NULL DEFAULT 0,
improvement_rate NUMERIC(6,2) NOT NULL DEFAULT 0,
resilience_index NUMERIC(6,2) NOT NULL DEFAULT 0,
streak_days INTEGER NOT NULL DEFAULT 0,
learning_minutes INTEGER NOT NULL DEFAULT 0,
checkin_status VARCHAR(16) NOT NULL DEFAULT 'NONE',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (stat_date, user_id, role_id),
CONSTRAINT chk_ac_user_achievement_daily_fact_gained
CHECK (gained_achievement_count >= 0),
CONSTRAINT chk_ac_user_achievement_daily_fact_active
CHECK (active_achievement_count >= 0),
CONSTRAINT chk_ac_user_achievement_daily_fact_effort
CHECK (effort_index >= 0 AND effort_index <= 100),
CONSTRAINT chk_ac_user_achievement_daily_fact_improvement
CHECK (improvement_rate >= -100 AND improvement_rate <= 1000),
CONSTRAINT chk_ac_user_achievement_daily_fact_resilience
CHECK (resilience_index >= 0 AND resilience_index <= 100),
CONSTRAINT chk_ac_user_achievement_daily_fact_streak
CHECK (streak_days >= 0),
CONSTRAINT chk_ac_user_achievement_daily_fact_learning_minutes
CHECK (learning_minutes >= 0),
CONSTRAINT chk_ac_user_achievement_daily_fact_checkin
CHECK (checkin_status IN ('NONE', 'CHECKED_IN', 'BROKEN')),
CONSTRAINT fk_ac_user_achievement_daily_fact_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_ac_user_achievement_daily_fact_role
FOREIGN KEY (role_id) REFERENCES upms.tb_sys_role(role_id)
);
COMMENT ON TABLE achievement.ac_user_achievement_daily_fact IS '用户成就激励日事实表(激励数据即成就数据)';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.stat_date IS '统计日期';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.user_id IS '用户ID';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.role_id IS '角色ID';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.tenant_id IS '租户ID';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.gained_achievement_count IS '当日新增成就数';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.active_achievement_count IS '当日活跃成就数';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.progress_delta IS '当日进度变化量';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.effort_index IS '当日努力指数0-100';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.improvement_rate IS '当日进步率(可为负)';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.resilience_index IS '当日学习韧性指数0-100';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.streak_days IS '当日连续打卡天数';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.learning_minutes IS '当日学习分钟数';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.checkin_status IS '打卡状态';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.created_at IS '创建时间';
COMMENT ON COLUMN achievement.ac_user_achievement_daily_fact.updated_at IS '更新时间';
DROP TABLE IF EXISTS achievement.ac_achievement_rule_template CASCADE;
DROP TABLE IF EXISTS achievement.ac_achievement_metric_def CASCADE;
DROP TABLE IF EXISTS achievement.ac_achievement_event_dict CASCADE;
CREATE TABLE IF NOT EXISTS achievement.ac_achievement_event_dict (
event_code VARCHAR(64) PRIMARY KEY,
event_name VARCHAR(128) NOT NULL,
event_domain VARCHAR(32) NOT NULL DEFAULT 'LEARNING_LOOP',
event_desc TEXT,
payload_schema_json JSONB NOT NULL DEFAULT '{}'::JSONB,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_ac_achievement_event_dict_domain
CHECK (event_domain IN ('LEARNING_LOOP', 'GRADING', 'REVIEW', 'CHECKIN', 'SYSTEM')),
CONSTRAINT chk_ac_achievement_event_dict_payload_json
CHECK (jsonb_typeof(payload_schema_json) = 'object')
);
COMMENT ON TABLE achievement.ac_achievement_event_dict IS '成就事件字典表(通用触发事件定义)';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.event_code IS '事件编码';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.event_name IS '事件名称';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.event_domain IS '事件域';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.event_desc IS '事件描述';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.payload_schema_json IS '事件载荷结构定义JSON';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.enabled IS '是否启用';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.tenant_id IS '租户ID';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.created_at IS '创建时间';
COMMENT ON COLUMN achievement.ac_achievement_event_dict.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS achievement.ac_achievement_metric_def (
metric_id VARCHAR(64) PRIMARY KEY,
metric_code VARCHAR(64) NOT NULL,
metric_name VARCHAR(128) NOT NULL,
metric_type VARCHAR(16) NOT NULL DEFAULT 'NUM',
metric_unit VARCHAR(32),
source_domain VARCHAR(32) NOT NULL DEFAULT 'SYSTEM',
agg_method VARCHAR(16) NOT NULL DEFAULT 'LATEST',
default_value_num NUMERIC(18,6),
default_value_text VARCHAR(255),
expr_json JSONB NOT NULL DEFAULT '{}'::JSONB,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_ac_achievement_metric_def_tenant_code
UNIQUE (tenant_id, metric_code),
CONSTRAINT chk_ac_achievement_metric_def_type
CHECK (metric_type IN ('NUM', 'BOOL', 'TEXT', 'RATE', 'COUNT')),
CONSTRAINT chk_ac_achievement_metric_def_source_domain
CHECK (source_domain IN ('COURSE', 'HOMEWORK', 'GRADING', 'REVIEW', 'RECOMMENDATION', 'ACHIEVEMENT', 'SYSTEM')),
CONSTRAINT chk_ac_achievement_metric_def_agg_method
CHECK (agg_method IN ('LATEST', 'SUM', 'AVG', 'MAX', 'MIN', 'COUNT')),
CONSTRAINT chk_ac_achievement_metric_def_expr_json
CHECK (jsonb_typeof(expr_json) = 'object')
);
COMMENT ON TABLE achievement.ac_achievement_metric_def IS '成就指标定义表(通用指标注册)';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_id IS '指标ID';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_code IS '指标编码';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_name IS '指标名称';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_type IS '指标类型';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.metric_unit IS '指标单位';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.source_domain IS '指标来源域';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.agg_method IS '聚合方式';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.default_value_num IS '默认数值';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.default_value_text IS '默认文本值';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.expr_json IS '计算表达式JSON';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.enabled IS '是否启用';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.tenant_id IS '租户ID';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.created_at IS '创建时间';
COMMENT ON COLUMN achievement.ac_achievement_metric_def.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS achievement.ac_achievement_rule_template (
template_id VARCHAR(64) PRIMARY KEY,
template_code VARCHAR(64) NOT NULL,
template_name VARCHAR(128) NOT NULL,
event_code VARCHAR(64) NOT NULL,
role_id VARCHAR(64),
achievement_category VARCHAR(32) NOT NULL DEFAULT 'PROGRESS',
rule_condition_json JSONB NOT NULL DEFAULT '{}'::JSONB,
reward_action_json JSONB NOT NULL DEFAULT '{}'::JSONB,
priority INTEGER NOT NULL DEFAULT 100,
template_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
valid_from TIMESTAMP,
valid_to TIMESTAMP,
tenant_id VARCHAR(64) NOT NULL,
created_by VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_ac_achievement_rule_template_tenant_code
UNIQUE (tenant_id, template_code),
CONSTRAINT chk_ac_achievement_rule_template_category
CHECK (achievement_category IN ('SCORE', 'PROGRESS', 'TEACHING', 'ENGAGEMENT', 'MANUAL')),
CONSTRAINT chk_ac_achievement_rule_template_status
CHECK (template_status IN ('ACTIVE', 'INACTIVE', 'ARCHIVED')),
CONSTRAINT chk_ac_achievement_rule_template_condition_json
CHECK (jsonb_typeof(rule_condition_json) = 'object'),
CONSTRAINT chk_ac_achievement_rule_template_reward_json
CHECK (jsonb_typeof(reward_action_json) = 'object'),
CONSTRAINT chk_ac_achievement_rule_template_valid
CHECK (valid_to IS NULL OR valid_from IS NULL OR valid_to >= valid_from),
CONSTRAINT fk_ac_achievement_rule_template_event
FOREIGN KEY (event_code) REFERENCES achievement.ac_achievement_event_dict(event_code),
CONSTRAINT fk_ac_achievement_rule_template_role
FOREIGN KEY (role_id) REFERENCES upms.tb_sys_role(role_id),
CONSTRAINT fk_ac_achievement_rule_template_created_by
FOREIGN KEY (created_by) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE achievement.ac_achievement_rule_template IS '成就规则模板表(通用配置驱动)';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.template_id IS '模板ID';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.template_code IS '模板编码';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.template_name IS '模板名称';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.event_code IS '触发事件编码';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.role_id IS '适用角色ID';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.achievement_category IS '成就类别';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.rule_condition_json IS '规则条件JSON';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.reward_action_json IS '奖励动作JSON';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.priority IS '优先级';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.template_status IS '模板状态';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.valid_from IS '生效开始时间';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.valid_to IS '生效结束时间';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.tenant_id IS '租户ID';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.created_by IS '创建人';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.created_at IS '创建时间';
COMMENT ON COLUMN achievement.ac_achievement_rule_template.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_ac_achievement_tenant_status
ON achievement.ac_achievement(tenant_id, status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ac_achievement_role_rel_role
ON achievement.ac_achievement_role_rel(role_id, tenant_id);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_user_role
ON achievement.ac_user_achievement(user_id, role_id, last_achieved_at DESC);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_tenant_time
ON achievement.ac_user_achievement(tenant_id, last_achieved_at DESC);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_summary
ON achievement.ac_user_achievement(summary_id);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_task
ON achievement.ac_user_achievement(grading_task_id);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_progress_user_status
ON achievement.ac_user_achievement_progress(user_id, role_id, progress_status, last_evaluated_at DESC);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_progress_tenant_status
ON achievement.ac_user_achievement_progress(tenant_id, progress_status, last_evaluated_at DESC);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_progress_achievement
ON achievement.ac_user_achievement_progress(achievement_id, role_id);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_progress_streak
ON achievement.ac_user_achievement_progress(user_id, role_id, streak_days DESC, last_evaluated_at DESC);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_daily_fact_tenant_date
ON achievement.ac_user_achievement_daily_fact(tenant_id, stat_date DESC);
CREATE INDEX IF NOT EXISTS idx_ac_user_achievement_daily_fact_user_date
ON achievement.ac_user_achievement_daily_fact(user_id, role_id, stat_date DESC);
CREATE INDEX IF NOT EXISTS idx_ac_achievement_event_dict_tenant_enabled
ON achievement.ac_achievement_event_dict(tenant_id, enabled, event_domain);
CREATE INDEX IF NOT EXISTS idx_ac_achievement_metric_def_tenant_enabled
ON achievement.ac_achievement_metric_def(tenant_id, enabled, source_domain);
CREATE INDEX IF NOT EXISTS idx_ac_achievement_rule_template_tenant_event
ON achievement.ac_achievement_rule_template(tenant_id, event_code, template_status, priority);

View File

@@ -0,0 +1,2 @@
-- 成就模块初始化数据占位
-- 按需补充学生成就、教师成就及角色绑定关系种子数据

View File

@@ -1,47 +1,215 @@
CREATE TABLE IF NOT EXISTS ai.tb_ai_model_config (
model_id VARCHAR(64) PRIMARY KEY,
model_name VARCHAR(128) NOT NULL,
provider VARCHAR(64) NOT NULL,
endpoint VARCHAR(255),
adcode VARCHAR(12),
tenant_id VARCHAR(64),
tenant_path VARCHAR(255),
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE ai.tb_ai_model_config IS 'AI模型配置表';
COMMENT ON COLUMN ai.tb_ai_model_config.model_id IS '模型ID';
COMMENT ON COLUMN ai.tb_ai_model_config.model_name IS '模型名称';
COMMENT ON COLUMN ai.tb_ai_model_config.provider IS '模型提供商';
COMMENT ON COLUMN ai.tb_ai_model_config.endpoint IS '模型服务地址';
COMMENT ON COLUMN ai.tb_ai_model_config.adcode IS '行政区划编码';
COMMENT ON COLUMN ai.tb_ai_model_config.tenant_id IS '租户ID';
COMMENT ON COLUMN ai.tb_ai_model_config.tenant_path IS '租户路径';
COMMENT ON COLUMN ai.tb_ai_model_config.dept_id IS '部门ID';
COMMENT ON COLUMN ai.tb_ai_model_config.dept_path IS '部门路径';
COMMENT ON COLUMN ai.tb_ai_model_config.created_at IS '创建时间';
DROP SCHEMA IF EXISTS ai CASCADE;
CREATE SCHEMA IF NOT EXISTS ai;
CREATE TABLE IF NOT EXISTS ai.tb_ai_task_log (
task_id VARCHAR(64) PRIMARY KEY,
task_type VARCHAR(64) NOT NULL,
task_status VARCHAR(32) NOT NULL,
adcode VARCHAR(12),
tenant_id VARCHAR(64),
tenant_path VARCHAR(255),
dept_id VARCHAR(64),
dept_path VARCHAR(255),
payload_json TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
DROP TABLE IF EXISTS ai.tb_ai_knowledge_file CASCADE;
CREATE TABLE IF NOT EXISTS ai.tb_ai_knowledge_file (
file_id VARCHAR(64) PRIMARY KEY,
file_code VARCHAR(64) UNIQUE NOT NULL,
file_name VARCHAR(255) NOT NULL,
source_file_id VARCHAR(64),
file_type VARCHAR(32) NOT NULL DEFAULT 'DOCUMENT',
file_status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
graph_sync_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
vector_sync_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
graph_doc_ref VARCHAR(128),
vector_doc_ref VARCHAR(128),
content_checksum VARCHAR(128),
metadata_json JSONB NOT NULL DEFAULT '{}'::JSONB,
adcode VARCHAR(12),
tenant_id VARCHAR(64),
tenant_path VARCHAR(255),
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_ai_knowledge_file_type
CHECK (file_type IN ('DOCUMENT', 'PDF', 'IMAGE', 'OTHER')),
CONSTRAINT chk_ai_knowledge_file_status
CHECK (file_status IN ('ACTIVE', 'DISABLED')),
CONSTRAINT chk_ai_knowledge_file_graph_status
CHECK (graph_sync_status IN ('PENDING', 'SYNCED', 'FAILED')),
CONSTRAINT chk_ai_knowledge_file_vector_status
CHECK (vector_sync_status IN ('PENDING', 'SYNCED', 'FAILED')),
CONSTRAINT chk_ai_knowledge_file_metadata_json
CHECK (jsonb_typeof(metadata_json) = 'object'),
CONSTRAINT fk_ai_knowledge_file_source_file
FOREIGN KEY (source_file_id) REFERENCES upms.tb_sys_file(file_id)
);
COMMENT ON TABLE ai.tb_ai_task_log IS 'AI任务日志表';
COMMENT ON COLUMN ai.tb_ai_task_log.task_id IS '任务ID';
COMMENT ON COLUMN ai.tb_ai_task_log.task_type IS '任务类型';
COMMENT ON COLUMN ai.tb_ai_task_log.task_status IS '任务状态';
COMMENT ON COLUMN ai.tb_ai_task_log.adcode IS '行政区划编码';
COMMENT ON COLUMN ai.tb_ai_task_log.tenant_id IS '租户ID';
COMMENT ON COLUMN ai.tb_ai_task_log.tenant_path IS '租户路径';
COMMENT ON COLUMN ai.tb_ai_task_log.dept_id IS '部门ID';
COMMENT ON COLUMN ai.tb_ai_task_log.dept_path IS '部门路径';
COMMENT ON COLUMN ai.tb_ai_task_log.payload_json IS '任务载荷JSON';
COMMENT ON COLUMN ai.tb_ai_task_log.created_at IS '创建时间';
COMMENT ON TABLE ai.tb_ai_knowledge_file IS '知识文件主表(统一管理图谱和向量知识库入库对象)';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_id IS '知识文件ID';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_code IS '知识文件编码';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_name IS '知识文件名称';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.source_file_id IS '来源文件IDupms.tb_sys_file.file_id';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_type IS '文件类型';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.file_status IS '文件状态';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.graph_sync_status IS '图谱同步状态';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.vector_sync_status IS '向量库同步状态';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.graph_doc_ref IS '图谱侧文档/对象引用';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.vector_doc_ref IS '向量库侧文档/对象引用';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.content_checksum IS '内容校验值';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.metadata_json IS '文件元数据JSON';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.adcode IS '行政区划编码';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.tenant_id IS '租户ID';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.tenant_path IS '租户路径';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.dept_id IS '部门ID';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.dept_path IS '部门路径';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.created_at IS '创建时间';
COMMENT ON COLUMN ai.tb_ai_knowledge_file.updated_at IS '更新时间';
DROP TABLE IF EXISTS ai.tb_ai_knowledge_sync_task CASCADE;
CREATE TABLE IF NOT EXISTS ai.tb_ai_knowledge_sync_task (
task_id VARCHAR(64) PRIMARY KEY,
file_id VARCHAR(64) NOT NULL,
target_store VARCHAR(16) NOT NULL,
task_type VARCHAR(16) NOT NULL DEFAULT 'UPSERT',
task_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
retry_count INTEGER NOT NULL DEFAULT 0,
error_message TEXT,
payload_json JSONB NOT NULL DEFAULT '{}'::JSONB,
adcode VARCHAR(12),
tenant_id VARCHAR(64),
tenant_path VARCHAR(255),
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_ai_knowledge_sync_task_store
CHECK (target_store IN ('GRAPH', 'VECTOR')),
CONSTRAINT chk_ai_knowledge_sync_task_type
CHECK (task_type IN ('UPSERT', 'DELETE', 'REBUILD')),
CONSTRAINT chk_ai_knowledge_sync_task_status
CHECK (task_status IN ('PENDING', 'RUNNING', 'SUCCESS', 'FAILED')),
CONSTRAINT chk_ai_knowledge_sync_task_retry
CHECK (retry_count >= 0),
CONSTRAINT chk_ai_knowledge_sync_task_payload_json
CHECK (jsonb_typeof(payload_json) = 'object'),
CONSTRAINT fk_ai_knowledge_sync_task_file
FOREIGN KEY (file_id) REFERENCES ai.tb_ai_knowledge_file(file_id) ON DELETE CASCADE
);
COMMENT ON TABLE ai.tb_ai_knowledge_sync_task IS '知识文件同步任务表(同步到图谱或向量知识库)';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.task_id IS '同步任务ID';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.file_id IS '知识文件ID';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.target_store IS '目标存储GRAPH/VECTOR';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.task_type IS '任务类型UPSERT/DELETE/REBUILD';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.task_status IS '任务状态';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.retry_count IS '重试次数';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.error_message IS '错误信息';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.payload_json IS '任务载荷JSON';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.adcode IS '行政区划编码';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.tenant_id IS '租户ID';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.tenant_path IS '租户路径';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.dept_id IS '部门ID';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.dept_path IS '部门路径';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.created_at IS '创建时间';
COMMENT ON COLUMN ai.tb_ai_knowledge_sync_task.updated_at IS '更新时间';
DROP TABLE IF EXISTS ai.tb_ai_graph_entity CASCADE;
CREATE TABLE IF NOT EXISTS ai.tb_ai_graph_entity (
entity_id VARCHAR(64) PRIMARY KEY,
file_id VARCHAR(64),
entity_type VARCHAR(64) NOT NULL,
entity_name VARCHAR(255) NOT NULL,
normalized_name VARCHAR(255),
graph_vertex_id VARCHAR(128),
entity_status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
sync_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
properties_json JSONB NOT NULL DEFAULT '{}'::JSONB,
adcode VARCHAR(12),
tenant_id VARCHAR(64),
tenant_path VARCHAR(255),
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_ai_graph_entity_file_name_type
UNIQUE (file_id, entity_name, entity_type),
CONSTRAINT chk_ai_graph_entity_status
CHECK (entity_status IN ('ACTIVE', 'DISABLED')),
CONSTRAINT chk_ai_graph_entity_sync_status
CHECK (sync_status IN ('PENDING', 'SYNCED', 'FAILED')),
CONSTRAINT chk_ai_graph_entity_properties_json
CHECK (jsonb_typeof(properties_json) = 'object'),
CONSTRAINT fk_ai_graph_entity_file
FOREIGN KEY (file_id) REFERENCES ai.tb_ai_knowledge_file(file_id) ON DELETE SET NULL
);
COMMENT ON TABLE ai.tb_ai_graph_entity IS '图谱实体表(用于实体绑定)';
COMMENT ON COLUMN ai.tb_ai_graph_entity.entity_id IS '实体ID';
COMMENT ON COLUMN ai.tb_ai_graph_entity.file_id IS '来源知识文件ID';
COMMENT ON COLUMN ai.tb_ai_graph_entity.entity_type IS '实体类型';
COMMENT ON COLUMN ai.tb_ai_graph_entity.entity_name IS '实体名称';
COMMENT ON COLUMN ai.tb_ai_graph_entity.normalized_name IS '标准化实体名称';
COMMENT ON COLUMN ai.tb_ai_graph_entity.graph_vertex_id IS '图数据库顶点ID';
COMMENT ON COLUMN ai.tb_ai_graph_entity.entity_status IS '实体状态';
COMMENT ON COLUMN ai.tb_ai_graph_entity.sync_status IS '同步状态';
COMMENT ON COLUMN ai.tb_ai_graph_entity.properties_json IS '实体属性JSON';
COMMENT ON COLUMN ai.tb_ai_graph_entity.adcode IS '行政区划编码';
COMMENT ON COLUMN ai.tb_ai_graph_entity.tenant_id IS '租户ID';
COMMENT ON COLUMN ai.tb_ai_graph_entity.tenant_path IS '租户路径';
COMMENT ON COLUMN ai.tb_ai_graph_entity.dept_id IS '部门ID';
COMMENT ON COLUMN ai.tb_ai_graph_entity.dept_path IS '部门路径';
COMMENT ON COLUMN ai.tb_ai_graph_entity.created_at IS '创建时间';
COMMENT ON COLUMN ai.tb_ai_graph_entity.updated_at IS '更新时间';
DROP TABLE IF EXISTS ai.tb_ai_graph_relation CASCADE;
CREATE TABLE IF NOT EXISTS ai.tb_ai_graph_relation (
relation_id VARCHAR(64) PRIMARY KEY,
src_entity_id VARCHAR(64) NOT NULL,
dst_entity_id VARCHAR(64) NOT NULL,
relation_type VARCHAR(64) NOT NULL,
relation_name VARCHAR(128),
graph_edge_id VARCHAR(128),
relation_weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
relation_status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
sync_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
properties_json JSONB NOT NULL DEFAULT '{}'::JSONB,
adcode VARCHAR(12),
tenant_id VARCHAR(64),
tenant_path VARCHAR(255),
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_ai_graph_relation_src_dst_type
UNIQUE (src_entity_id, dst_entity_id, relation_type),
CONSTRAINT chk_ai_graph_relation_src_dst
CHECK (src_entity_id <> dst_entity_id),
CONSTRAINT chk_ai_graph_relation_weight
CHECK (relation_weight > 0 AND relation_weight <= 1),
CONSTRAINT chk_ai_graph_relation_status
CHECK (relation_status IN ('ACTIVE', 'DISABLED')),
CONSTRAINT chk_ai_graph_relation_sync_status
CHECK (sync_status IN ('PENDING', 'SYNCED', 'FAILED')),
CONSTRAINT chk_ai_graph_relation_properties_json
CHECK (jsonb_typeof(properties_json) = 'object'),
CONSTRAINT fk_ai_graph_relation_src
FOREIGN KEY (src_entity_id) REFERENCES ai.tb_ai_graph_entity(entity_id) ON DELETE CASCADE,
CONSTRAINT fk_ai_graph_relation_dst
FOREIGN KEY (dst_entity_id) REFERENCES ai.tb_ai_graph_entity(entity_id) ON DELETE CASCADE
);
COMMENT ON TABLE ai.tb_ai_graph_relation IS '图谱实体关联表(用于实体关系绑定)';
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_id IS '关系ID';
COMMENT ON COLUMN ai.tb_ai_graph_relation.src_entity_id IS '源实体ID';
COMMENT ON COLUMN ai.tb_ai_graph_relation.dst_entity_id IS '目标实体ID';
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_type IS '关系类型';
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_name IS '关系名称';
COMMENT ON COLUMN ai.tb_ai_graph_relation.graph_edge_id IS '图数据库边ID';
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_weight IS '关系权重';
COMMENT ON COLUMN ai.tb_ai_graph_relation.relation_status IS '关系状态';
COMMENT ON COLUMN ai.tb_ai_graph_relation.sync_status IS '同步状态';
COMMENT ON COLUMN ai.tb_ai_graph_relation.properties_json IS '关系属性JSON';
COMMENT ON COLUMN ai.tb_ai_graph_relation.adcode IS '行政区划编码';
COMMENT ON COLUMN ai.tb_ai_graph_relation.tenant_id IS '租户ID';
COMMENT ON COLUMN ai.tb_ai_graph_relation.tenant_path IS '租户路径';
COMMENT ON COLUMN ai.tb_ai_graph_relation.dept_id IS '部门ID';
COMMENT ON COLUMN ai.tb_ai_graph_relation.dept_path IS '部门路径';
COMMENT ON COLUMN ai.tb_ai_graph_relation.created_at IS '创建时间';
COMMENT ON COLUMN ai.tb_ai_graph_relation.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_ai_knowledge_file_sync_status
ON ai.tb_ai_knowledge_file(graph_sync_status, vector_sync_status, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_ai_knowledge_sync_task_status
ON ai.tb_ai_knowledge_sync_task(task_status, target_store, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ai_graph_entity_name
ON ai.tb_ai_graph_entity(entity_type, entity_name);
CREATE INDEX IF NOT EXISTS idx_ai_graph_relation_src_dst
ON ai.tb_ai_graph_relation(src_entity_id, dst_entity_id, relation_type);

View File

@@ -1,6 +1,60 @@
INSERT INTO ai.tb_ai_model_config (
model_id, model_name, provider, endpoint, adcode, tenant_id, tenant_path, dept_id, dept_path
INSERT INTO ai.tb_ai_knowledge_file (
file_id, file_code, file_name, file_type, file_status,
graph_sync_status, vector_sync_status, metadata_json,
adcode, tenant_id, tenant_path, dept_id, dept_path
) VALUES (
'MODEL-MOCK-001', 'mock-python-ai', 'local', 'http://localhost:9000', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
'KF-DEMO-001', 'K12_DEMO_KNOWLEDGE_FILE', 'K12示例知识文件', 'DOCUMENT', 'ACTIVE',
'PENDING', 'PENDING', '{"source":"seed","scenario":"knowledge_file_management"}'::JSONB,
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
)
ON CONFLICT (model_id) DO NOTHING;
ON CONFLICT (file_id) DO NOTHING;
INSERT INTO ai.tb_ai_graph_entity (
entity_id, file_id, entity_type, entity_name, normalized_name, sync_status, properties_json,
adcode, tenant_id, tenant_path, dept_id, dept_path
) VALUES (
'GE-KP-001', 'KF-DEMO-001', 'KNOWLEDGE_POINT', '一次函数', '一次函数', 'PENDING',
'{"subject":"math","grade":"7"}'::JSONB,
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
)
ON CONFLICT (entity_id) DO NOTHING;
INSERT INTO ai.tb_ai_graph_entity (
entity_id, file_id, entity_type, entity_name, normalized_name, sync_status, properties_json,
adcode, tenant_id, tenant_path, dept_id, dept_path
) VALUES (
'GE-QUESTION-001', 'KF-DEMO-001', 'QUESTION', '一次函数应用题', '一次函数应用题', 'PENDING',
'{"difficulty":"MEDIUM"}'::JSONB,
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
)
ON CONFLICT (entity_id) DO NOTHING;
INSERT INTO ai.tb_ai_graph_relation (
relation_id, src_entity_id, dst_entity_id, relation_type, relation_name, relation_weight, sync_status, properties_json,
adcode, tenant_id, tenant_path, dept_id, dept_path
) VALUES (
'GR-001', 'GE-QUESTION-001', 'GE-KP-001', 'BELONGS_TO', '题目关联知识点', 1.0000, 'PENDING',
'{"source":"manual_binding"}'::JSONB,
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
)
ON CONFLICT (relation_id) DO NOTHING;
INSERT INTO ai.tb_ai_knowledge_sync_task (
task_id, file_id, target_store, task_type, task_status, payload_json,
adcode, tenant_id, tenant_path, dept_id, dept_path
) VALUES (
'KST-GRAPH-001', 'KF-DEMO-001', 'GRAPH', 'UPSERT', 'PENDING',
'{"trigger":"seed","description":"sync to graph store"}'::JSONB,
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
)
ON CONFLICT (task_id) DO NOTHING;
INSERT INTO ai.tb_ai_knowledge_sync_task (
task_id, file_id, target_store, task_type, task_status, payload_json,
adcode, tenant_id, tenant_path, dept_id, dept_path
) VALUES (
'KST-VECTOR-001', 'KF-DEMO-001', 'VECTOR', 'UPSERT', 'PENDING',
'{"trigger":"seed","description":"sync to vector store"}'::JSONB,
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'
)
ON CONFLICT (task_id) DO NOTHING;

View File

@@ -1,3 +1,7 @@
DROP SCHEMA IF EXISTS auth CASCADE;
CREATE SCHEMA IF NOT EXISTS auth;
DROP TABLE IF EXISTS auth.tb_auth_refresh_token CASCADE;
CREATE TABLE IF NOT EXISTS auth.tb_auth_refresh_token (
token_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
@@ -26,6 +30,7 @@ COMMENT ON COLUMN auth.tb_auth_refresh_token.expire_at IS '过期时间';
COMMENT ON COLUMN auth.tb_auth_refresh_token.revoked IS '是否撤销';
COMMENT ON COLUMN auth.tb_auth_refresh_token.created_at IS '创建时间';
DROP TABLE IF EXISTS auth.tb_auth_login_audit CASCADE;
CREATE TABLE IF NOT EXISTS auth.tb_auth_login_audit (
audit_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64),

View File

@@ -0,0 +1,512 @@
DROP SCHEMA IF EXISTS course CASCADE;
CREATE SCHEMA IF NOT EXISTS course;
DROP TABLE IF EXISTS course.cl_course CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_course (
course_id VARCHAR(64) PRIMARY KEY,
title VARCHAR(256) NOT NULL,
subject_code VARCHAR(32) NOT NULL,
grade_code VARCHAR(32) NOT NULL,
difficulty_level VARCHAR(16),
status VARCHAR(32) NOT NULL DEFAULT 'DRAFT',
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_by VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE course.cl_course IS '课程主表';
COMMENT ON COLUMN course.cl_course.course_id IS '课程ID';
COMMENT ON COLUMN course.cl_course.title IS '课程标题';
COMMENT ON COLUMN course.cl_course.subject_code IS '学科编码';
COMMENT ON COLUMN course.cl_course.grade_code IS '年级编码';
COMMENT ON COLUMN course.cl_course.difficulty_level IS '难度等级';
COMMENT ON COLUMN course.cl_course.status IS '状态DRAFT/PUBLISHED/ARCHIVED';
COMMENT ON COLUMN course.cl_course.adcode IS '行政区划编码';
COMMENT ON COLUMN course.cl_course.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_course.tenant_path IS '租户路径';
COMMENT ON COLUMN course.cl_course.dept_id IS '部门ID';
COMMENT ON COLUMN course.cl_course.dept_path IS '部门路径';
COMMENT ON COLUMN course.cl_course.created_by IS '创建人ID';
COMMENT ON COLUMN course.cl_course.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_course_chapter CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_course_chapter (
chapter_id VARCHAR(64) PRIMARY KEY,
course_id VARCHAR(64) NOT NULL,
chapter_no INTEGER NOT NULL,
chapter_title VARCHAR(256) NOT NULL,
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_cl_course_chapter_course
FOREIGN KEY (course_id) REFERENCES course.cl_course(course_id)
);
COMMENT ON TABLE course.cl_course_chapter IS '课程章节表';
COMMENT ON COLUMN course.cl_course_chapter.chapter_id IS '章节ID';
COMMENT ON COLUMN course.cl_course_chapter.course_id IS '课程ID';
COMMENT ON COLUMN course.cl_course_chapter.chapter_no IS '章节序号';
COMMENT ON COLUMN course.cl_course_chapter.chapter_title IS '章节标题';
COMMENT ON COLUMN course.cl_course_chapter.adcode IS '行政区划编码';
COMMENT ON COLUMN course.cl_course_chapter.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_course_chapter.tenant_path IS '租户路径';
COMMENT ON COLUMN course.cl_course_chapter.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_course_node CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_course_node (
node_id VARCHAR(64) PRIMARY KEY,
chapter_id VARCHAR(64) NOT NULL,
node_no INTEGER NOT NULL,
node_title VARCHAR(256) NOT NULL,
node_type VARCHAR(32) NOT NULL DEFAULT 'LESSON',
class_type VARCHAR(32),
duration_sec INTEGER,
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_cl_course_node_type
CHECK (node_type IN ('LESSON', 'IN_CLASS_ACTIVITY', 'AFTER_CLASS_TASK', 'MATERIAL', 'OTHER')),
CONSTRAINT fk_cl_course_node_chapter
FOREIGN KEY (chapter_id) REFERENCES course.cl_course_chapter(chapter_id)
);
COMMENT ON TABLE course.cl_course_node IS '课程学习节点表';
COMMENT ON COLUMN course.cl_course_node.node_id IS '学习节点ID';
COMMENT ON COLUMN course.cl_course_node.chapter_id IS '章节ID';
COMMENT ON COLUMN course.cl_course_node.node_no IS '节点序号';
COMMENT ON COLUMN course.cl_course_node.node_title IS '节点标题';
COMMENT ON COLUMN course.cl_course_node.node_type IS '节点类型LESSON/IN_CLASS_ACTIVITY/AFTER_CLASS_TASK/MATERIAL/OTHER';
COMMENT ON COLUMN course.cl_course_node.class_type IS '课堂类型(如讲授/讨论/测验)';
COMMENT ON COLUMN course.cl_course_node.duration_sec IS '时长(秒)';
COMMENT ON COLUMN course.cl_course_node.adcode IS '行政区划编码';
COMMENT ON COLUMN course.cl_course_node.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_course_node.tenant_path IS '租户路径';
COMMENT ON COLUMN course.cl_course_node.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_knowledge_point CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_knowledge_point (
kp_id VARCHAR(64) PRIMARY KEY,
kp_code VARCHAR(64) NOT NULL,
kp_name VARCHAR(256) NOT NULL,
kp_alias VARCHAR(256),
kp_type VARCHAR(32) NOT NULL DEFAULT 'ATOMIC',
parent_kp_id VARCHAR(64),
kp_path VARCHAR(512),
kp_desc TEXT,
subject_code VARCHAR(32) NOT NULL,
grade_code VARCHAR(32) NOT NULL,
difficulty_level VARCHAR(16),
importance_level NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
graph_entity_id VARCHAR(64),
source_db VARCHAR(64) NOT NULL DEFAULT 'postgresql',
source_table VARCHAR(128) NOT NULL DEFAULT 'course.cl_knowledge_point',
source_pk VARCHAR(128),
metadata_json JSONB NOT NULL DEFAULT '{}'::JSONB,
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_cl_knowledge_point_tenant_code
UNIQUE (tenant_id, kp_code),
CONSTRAINT uq_cl_knowledge_point_tenant_graph_entity
UNIQUE (tenant_id, graph_entity_id),
CONSTRAINT chk_cl_knowledge_point_type
CHECK (kp_type IN ('DOMAIN', 'TOPIC', 'ATOMIC')),
CONSTRAINT chk_cl_knowledge_point_importance
CHECK (importance_level > 0 AND importance_level <= 1),
CONSTRAINT chk_cl_knowledge_point_metadata_json
CHECK (jsonb_typeof(metadata_json) = 'object'),
CONSTRAINT fk_cl_knowledge_point_parent
FOREIGN KEY (parent_kp_id) REFERENCES course.cl_knowledge_point(kp_id) ON DELETE SET NULL
);
COMMENT ON TABLE course.cl_knowledge_point IS '知识点表';
COMMENT ON COLUMN course.cl_knowledge_point.kp_id IS '知识点ID';
COMMENT ON COLUMN course.cl_knowledge_point.kp_code IS '知识点编码';
COMMENT ON COLUMN course.cl_knowledge_point.kp_name IS '知识点名称';
COMMENT ON COLUMN course.cl_knowledge_point.kp_alias IS '知识点别名';
COMMENT ON COLUMN course.cl_knowledge_point.kp_type IS '知识点类型DOMAIN/TOPIC/ATOMIC';
COMMENT ON COLUMN course.cl_knowledge_point.parent_kp_id IS '父知识点ID';
COMMENT ON COLUMN course.cl_knowledge_point.kp_path IS '知识点树路径';
COMMENT ON COLUMN course.cl_knowledge_point.kp_desc IS '知识点描述';
COMMENT ON COLUMN course.cl_knowledge_point.subject_code IS '学科编码';
COMMENT ON COLUMN course.cl_knowledge_point.grade_code IS '年级编码';
COMMENT ON COLUMN course.cl_knowledge_point.difficulty_level IS '难度等级';
COMMENT ON COLUMN course.cl_knowledge_point.importance_level IS '重要性权重0,1]';
COMMENT ON COLUMN course.cl_knowledge_point.graph_entity_id IS '图谱实体IDNebula/Neo4j 映射)';
COMMENT ON COLUMN course.cl_knowledge_point.source_db IS '来源数据库';
COMMENT ON COLUMN course.cl_knowledge_point.source_table IS '来源表';
COMMENT ON COLUMN course.cl_knowledge_point.source_pk IS '来源主键';
COMMENT ON COLUMN course.cl_knowledge_point.metadata_json IS '扩展元数据JSON';
COMMENT ON COLUMN course.cl_knowledge_point.status IS '状态ACTIVE/DISABLED';
COMMENT ON COLUMN course.cl_knowledge_point.adcode IS '行政区划编码';
COMMENT ON COLUMN course.cl_knowledge_point.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_knowledge_point.tenant_path IS '租户路径';
COMMENT ON COLUMN course.cl_knowledge_point.dept_id IS '部门ID';
COMMENT ON COLUMN course.cl_knowledge_point.dept_path IS '部门路径';
COMMENT ON COLUMN course.cl_knowledge_point.created_at IS '创建时间';
COMMENT ON COLUMN course.cl_knowledge_point.updated_at IS '更新时间';
DROP TABLE IF EXISTS course.cl_chapter_kp_rel CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_chapter_kp_rel (
chapter_id VARCHAR(64) NOT NULL,
kp_id VARCHAR(64) NOT NULL,
relation_type VARCHAR(32) NOT NULL DEFAULT 'TEACHES',
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
is_core BOOLEAN NOT NULL DEFAULT FALSE,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (chapter_id, kp_id),
CONSTRAINT chk_cl_chapter_kp_rel_type
CHECK (relation_type IN ('TEACHES', 'REVIEWS', 'EXTENDS')),
CONSTRAINT chk_cl_chapter_kp_rel_weight
CHECK (weight > 0 AND weight <= 1),
CONSTRAINT fk_cl_chapter_kp_rel_chapter
FOREIGN KEY (chapter_id) REFERENCES course.cl_course_chapter(chapter_id),
CONSTRAINT fk_cl_chapter_kp_rel_kp
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
);
COMMENT ON TABLE course.cl_chapter_kp_rel IS '章节-知识点关联表';
COMMENT ON COLUMN course.cl_chapter_kp_rel.chapter_id IS '章节ID';
COMMENT ON COLUMN course.cl_chapter_kp_rel.kp_id IS '知识点ID';
COMMENT ON COLUMN course.cl_chapter_kp_rel.relation_type IS '关系类型TEACHES/REVIEWS/EXTENDS';
COMMENT ON COLUMN course.cl_chapter_kp_rel.weight IS '关联权重0,1]';
COMMENT ON COLUMN course.cl_chapter_kp_rel.is_core IS '是否核心知识点';
COMMENT ON COLUMN course.cl_chapter_kp_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_chapter_kp_rel.created_at IS '创建时间';
COMMENT ON COLUMN course.cl_chapter_kp_rel.updated_at IS '更新时间';
DROP TABLE IF EXISTS course.cl_student_kp_mastery CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_student_kp_mastery (
mastery_id VARCHAR(64) PRIMARY KEY,
student_id VARCHAR(64) NOT NULL,
kp_id VARCHAR(64) NOT NULL,
mastery_score NUMERIC(6,4) NOT NULL DEFAULT 0,
mastery_level VARCHAR(16) NOT NULL DEFAULT 'UNKNOWN',
mastery_status VARCHAR(16) NOT NULL DEFAULT 'LEARNING',
evidence_json JSONB NOT NULL DEFAULT '{}'::JSONB,
last_source_type VARCHAR(32),
last_source_id VARCHAR(64),
last_assessed_at TIMESTAMP,
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_cl_student_kp_mastery_student_kp
UNIQUE (student_id, kp_id),
CONSTRAINT chk_cl_student_kp_mastery_score
CHECK (mastery_score >= 0 AND mastery_score <= 1),
CONSTRAINT chk_cl_student_kp_mastery_level
CHECK (mastery_level IN ('UNKNOWN', 'LOW', 'MEDIUM', 'HIGH', 'MASTERED')),
CONSTRAINT chk_cl_student_kp_mastery_status
CHECK (mastery_status IN ('LEARNING', 'STABLE', 'FORGOTTEN')),
CONSTRAINT chk_cl_student_kp_mastery_evidence_json
CHECK (jsonb_typeof(evidence_json) = 'object'),
CONSTRAINT fk_cl_student_kp_mastery_student
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_cl_student_kp_mastery_kp
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
);
COMMENT ON TABLE course.cl_student_kp_mastery IS '学生知识点掌握度表';
COMMENT ON COLUMN course.cl_student_kp_mastery.mastery_id IS '掌握度记录ID';
COMMENT ON COLUMN course.cl_student_kp_mastery.student_id IS '学生ID';
COMMENT ON COLUMN course.cl_student_kp_mastery.kp_id IS '知识点ID';
COMMENT ON COLUMN course.cl_student_kp_mastery.mastery_score IS '掌握度分值0-1';
COMMENT ON COLUMN course.cl_student_kp_mastery.mastery_level IS '掌握等级';
COMMENT ON COLUMN course.cl_student_kp_mastery.mastery_status IS '掌握状态LEARNING/STABLE/FORGOTTEN';
COMMENT ON COLUMN course.cl_student_kp_mastery.evidence_json IS '证据数据JSON错题、批改、学习行为等';
COMMENT ON COLUMN course.cl_student_kp_mastery.last_source_type IS '最近更新来源类型';
COMMENT ON COLUMN course.cl_student_kp_mastery.last_source_id IS '最近更新来源ID';
COMMENT ON COLUMN course.cl_student_kp_mastery.last_assessed_at IS '最近评估时间';
COMMENT ON COLUMN course.cl_student_kp_mastery.adcode IS '行政区划编码';
COMMENT ON COLUMN course.cl_student_kp_mastery.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_student_kp_mastery.tenant_path IS '租户路径';
COMMENT ON COLUMN course.cl_student_kp_mastery.dept_id IS '部门ID';
COMMENT ON COLUMN course.cl_student_kp_mastery.dept_path IS '部门路径';
COMMENT ON COLUMN course.cl_student_kp_mastery.created_at IS '创建时间';
COMMENT ON COLUMN course.cl_student_kp_mastery.updated_at IS '更新时间';
DROP TABLE IF EXISTS course.cl_kp_material_rel CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_kp_material_rel (
rel_id VARCHAR(64) PRIMARY KEY,
kp_id VARCHAR(64) NOT NULL,
material_type VARCHAR(32) NOT NULL,
material_ref_id VARCHAR(64) NOT NULL,
material_uri VARCHAR(512),
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
source_table VARCHAR(128),
source_pk VARCHAR(128),
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_cl_kp_material_rel_type
CHECK (material_type IN ('VECTOR_DOCUMENT', 'VECTOR_CHUNK', 'COURSE_RESOURCE', 'EXTERNAL_URI')),
CONSTRAINT chk_cl_kp_material_rel_weight
CHECK (weight > 0 AND weight <= 1),
CONSTRAINT fk_cl_kp_material_rel_kp
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
);
COMMENT ON TABLE course.cl_kp_material_rel IS '知识点-学习资料关联表';
COMMENT ON COLUMN course.cl_kp_material_rel.rel_id IS '关联ID';
COMMENT ON COLUMN course.cl_kp_material_rel.kp_id IS '知识点ID';
COMMENT ON COLUMN course.cl_kp_material_rel.material_type IS '资料类型VECTOR_DOCUMENT/VECTOR_CHUNK/COURSE_RESOURCE/EXTERNAL_URI';
COMMENT ON COLUMN course.cl_kp_material_rel.material_ref_id IS '资料对象ID';
COMMENT ON COLUMN course.cl_kp_material_rel.material_uri IS '资料URI';
COMMENT ON COLUMN course.cl_kp_material_rel.weight IS '关联权重0,1]';
COMMENT ON COLUMN course.cl_kp_material_rel.source_table IS '来源表';
COMMENT ON COLUMN course.cl_kp_material_rel.source_pk IS '来源主键';
COMMENT ON COLUMN course.cl_kp_material_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_kp_material_rel.created_at IS '创建时间';
COMMENT ON COLUMN course.cl_kp_material_rel.updated_at IS '更新时间';
DROP TABLE IF EXISTS course.cl_course_knowledge_rel CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_course_knowledge_rel (
course_id VARCHAR(64) NOT NULL,
kp_id VARCHAR(64) NOT NULL,
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (course_id, kp_id),
CONSTRAINT chk_cl_course_knowledge_rel_weight
CHECK (weight > 0 AND weight <= 1),
CONSTRAINT fk_cl_course_knowledge_rel_course
FOREIGN KEY (course_id) REFERENCES course.cl_course(course_id),
CONSTRAINT fk_cl_course_knowledge_rel_kp
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
);
COMMENT ON TABLE course.cl_course_knowledge_rel IS '课程-知识点关联表';
COMMENT ON COLUMN course.cl_course_knowledge_rel.course_id IS '课程ID';
COMMENT ON COLUMN course.cl_course_knowledge_rel.kp_id IS '知识点ID';
COMMENT ON COLUMN course.cl_course_knowledge_rel.weight IS '关联权重0,1]';
COMMENT ON COLUMN course.cl_course_knowledge_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_course_knowledge_rel.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_course_tag CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_course_tag (
tag_id VARCHAR(64) PRIMARY KEY,
tag_name VARCHAR(128) NOT NULL,
tag_type VARCHAR(32) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE course.cl_course_tag IS '课程标签表';
COMMENT ON COLUMN course.cl_course_tag.tag_id IS '标签ID';
COMMENT ON COLUMN course.cl_course_tag.tag_name IS '标签名称';
COMMENT ON COLUMN course.cl_course_tag.tag_type IS '标签类型';
COMMENT ON COLUMN course.cl_course_tag.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_course_tag.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_course_tag_rel CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_course_tag_rel (
course_id VARCHAR(64) NOT NULL,
tag_id VARCHAR(64) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (course_id, tag_id),
CONSTRAINT fk_cl_course_tag_rel_course
FOREIGN KEY (course_id) REFERENCES course.cl_course(course_id),
CONSTRAINT fk_cl_course_tag_rel_tag
FOREIGN KEY (tag_id) REFERENCES course.cl_course_tag(tag_id)
);
COMMENT ON TABLE course.cl_course_tag_rel IS '课程-标签关联表';
COMMENT ON COLUMN course.cl_course_tag_rel.course_id IS '课程ID';
COMMENT ON COLUMN course.cl_course_tag_rel.tag_id IS '标签ID';
COMMENT ON COLUMN course.cl_course_tag_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_course_tag_rel.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_node_resource CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_node_resource (
resource_id VARCHAR(64) PRIMARY KEY,
node_id VARCHAR(64) NOT NULL,
resource_type VARCHAR(32) NOT NULL,
file_id VARCHAR(64),
resource_url VARCHAR(512),
resource_title VARCHAR(256),
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_cl_node_resource_type
CHECK (resource_type IN ('PDF', 'VIDEO', 'DOC', 'IMAGE', 'LINK', 'OTHER')),
CONSTRAINT chk_cl_node_resource_ref
CHECK (file_id IS NOT NULL OR resource_url IS NOT NULL),
CONSTRAINT fk_cl_node_resource_node
FOREIGN KEY (node_id) REFERENCES course.cl_course_node(node_id),
CONSTRAINT fk_cl_node_resource_file
FOREIGN KEY (file_id) REFERENCES upms.tb_sys_file(file_id)
);
COMMENT ON TABLE course.cl_node_resource IS '学习节点资源表';
COMMENT ON COLUMN course.cl_node_resource.resource_id IS '资源ID';
COMMENT ON COLUMN course.cl_node_resource.node_id IS '学习节点ID';
COMMENT ON COLUMN course.cl_node_resource.resource_type IS '资源类型PDF/VIDEO/DOC/IMAGE/LINK/OTHER';
COMMENT ON COLUMN course.cl_node_resource.file_id IS '资源文件ID';
COMMENT ON COLUMN course.cl_node_resource.resource_url IS '资源地址';
COMMENT ON COLUMN course.cl_node_resource.resource_title IS '资源标题';
COMMENT ON COLUMN course.cl_node_resource.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_node_resource.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_node_kp_rel CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_node_kp_rel (
node_id VARCHAR(64) NOT NULL,
kp_id VARCHAR(64) NOT NULL,
relation_type VARCHAR(32) NOT NULL DEFAULT 'TEACHES',
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (node_id, kp_id),
CONSTRAINT chk_cl_node_kp_rel_type
CHECK (relation_type IN ('TEACHES', 'PRACTICES', 'REVIEWS')),
CONSTRAINT chk_cl_node_kp_rel_weight
CHECK (weight > 0 AND weight <= 1),
CONSTRAINT fk_cl_node_kp_rel_node
FOREIGN KEY (node_id) REFERENCES course.cl_course_node(node_id),
CONSTRAINT fk_cl_node_kp_rel_kp
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
);
COMMENT ON TABLE course.cl_node_kp_rel IS '学习节点-知识点关联表';
COMMENT ON COLUMN course.cl_node_kp_rel.node_id IS '学习节点ID';
COMMENT ON COLUMN course.cl_node_kp_rel.kp_id IS '知识点ID';
COMMENT ON COLUMN course.cl_node_kp_rel.relation_type IS '关系类型TEACHES/PRACTICES/REVIEWS';
COMMENT ON COLUMN course.cl_node_kp_rel.weight IS '关联权重0,1]';
COMMENT ON COLUMN course.cl_node_kp_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_node_kp_rel.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_node_homework_rel CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_node_homework_rel (
node_id VARCHAR(64) NOT NULL,
assignment_id VARCHAR(64) NOT NULL,
relation_type VARCHAR(32) NOT NULL DEFAULT 'AFTER_CLASS',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (node_id, assignment_id),
CONSTRAINT chk_cl_node_homework_rel_type
CHECK (relation_type IN ('IN_CLASS', 'AFTER_CLASS')),
CONSTRAINT fk_cl_node_homework_rel_node
FOREIGN KEY (node_id) REFERENCES course.cl_course_node(node_id)
);
COMMENT ON TABLE course.cl_node_homework_rel IS '学习节点-作业关联表';
COMMENT ON COLUMN course.cl_node_homework_rel.node_id IS '学习节点ID';
COMMENT ON COLUMN course.cl_node_homework_rel.assignment_id IS '作业IDquestion.hw_assignment.assignment_id';
COMMENT ON COLUMN course.cl_node_homework_rel.relation_type IS '关系类型IN_CLASS/AFTER_CLASS';
COMMENT ON COLUMN course.cl_node_homework_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_node_homework_rel.created_at IS '创建时间';
DROP TABLE IF EXISTS course.cl_learning_session CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_learning_session (
session_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
course_id VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'STARTED',
started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
ended_at TIMESTAMP,
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
dept_id VARCHAR(64),
dept_path VARCHAR(255),
CONSTRAINT fk_cl_learning_session_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_cl_learning_session_course
FOREIGN KEY (course_id) REFERENCES course.cl_course(course_id)
);
COMMENT ON TABLE course.cl_learning_session IS '学习会话表';
COMMENT ON COLUMN course.cl_learning_session.session_id IS '会话ID';
COMMENT ON COLUMN course.cl_learning_session.user_id IS '学员ID';
COMMENT ON COLUMN course.cl_learning_session.course_id IS '课程ID';
COMMENT ON COLUMN course.cl_learning_session.status IS '会话状态STARTED/PAUSED/COMPLETED';
COMMENT ON COLUMN course.cl_learning_session.started_at IS '开始时间';
COMMENT ON COLUMN course.cl_learning_session.ended_at IS '结束时间';
COMMENT ON COLUMN course.cl_learning_session.adcode IS '行政区划编码';
COMMENT ON COLUMN course.cl_learning_session.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_learning_session.tenant_path IS '租户路径';
COMMENT ON COLUMN course.cl_learning_session.dept_id IS '部门ID';
COMMENT ON COLUMN course.cl_learning_session.dept_path IS '部门路径';
DROP TABLE IF EXISTS course.cl_learning_progress CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_learning_progress (
progress_id VARCHAR(64) PRIMARY KEY,
session_id VARCHAR(64) NOT NULL,
node_id VARCHAR(64) NOT NULL,
progress_pct NUMERIC(5,2) NOT NULL DEFAULT 0,
last_position_sec INTEGER NOT NULL DEFAULT 0,
mastery_level VARCHAR(16),
tenant_id VARCHAR(64) NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_cl_learning_progress_session
FOREIGN KEY (session_id) REFERENCES course.cl_learning_session(session_id),
CONSTRAINT fk_cl_learning_progress_node
FOREIGN KEY (node_id) REFERENCES course.cl_course_node(node_id)
);
COMMENT ON TABLE course.cl_learning_progress IS '学习进度表';
COMMENT ON COLUMN course.cl_learning_progress.progress_id IS '进度ID';
COMMENT ON COLUMN course.cl_learning_progress.session_id IS '学习会话ID';
COMMENT ON COLUMN course.cl_learning_progress.node_id IS '学习节点ID';
COMMENT ON COLUMN course.cl_learning_progress.progress_pct IS '进度百分比';
COMMENT ON COLUMN course.cl_learning_progress.last_position_sec IS '最后学习位置(秒)';
COMMENT ON COLUMN course.cl_learning_progress.mastery_level IS '掌握等级';
COMMENT ON COLUMN course.cl_learning_progress.tenant_id IS '租户ID';
COMMENT ON COLUMN course.cl_learning_progress.updated_at IS '更新时间';
DROP TABLE IF EXISTS course.cl_learning_event CASCADE;
CREATE TABLE IF NOT EXISTS course.cl_learning_event (
event_id VARCHAR(64) PRIMARY KEY,
session_id VARCHAR(64) NOT NULL,
event_type VARCHAR(32) NOT NULL,
event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
payload_json JSONB,
tenant_id VARCHAR(64) NOT NULL,
CONSTRAINT fk_cl_learning_event_session
FOREIGN KEY (session_id) REFERENCES course.cl_learning_session(session_id)
);
COMMENT ON TABLE course.cl_learning_event IS '学习行为事件表';
COMMENT ON COLUMN course.cl_learning_event.event_id IS '事件ID';
COMMENT ON COLUMN course.cl_learning_event.session_id IS '会话ID';
COMMENT ON COLUMN course.cl_learning_event.event_type IS '事件类型start/pause/seek/finish';
COMMENT ON COLUMN course.cl_learning_event.event_time IS '事件时间';
COMMENT ON COLUMN course.cl_learning_event.payload_json IS '事件扩展信息';
COMMENT ON COLUMN course.cl_learning_event.tenant_id IS '租户ID';
CREATE INDEX IF NOT EXISTS idx_cl_course_tenant_subject_grade
ON course.cl_course(tenant_id, subject_code, grade_code);
CREATE INDEX IF NOT EXISTS idx_cl_course_chapter_course_no
ON course.cl_course_chapter(course_id, chapter_no);
CREATE INDEX IF NOT EXISTS idx_cl_course_node_chapter_no
ON course.cl_course_node(chapter_id, node_no);
CREATE INDEX IF NOT EXISTS idx_cl_knowledge_point_subject_grade
ON course.cl_knowledge_point(tenant_id, subject_code, grade_code);
CREATE INDEX IF NOT EXISTS idx_cl_knowledge_point_parent
ON course.cl_knowledge_point(tenant_id, parent_kp_id);
CREATE INDEX IF NOT EXISTS idx_cl_knowledge_point_graph_entity
ON course.cl_knowledge_point(tenant_id, graph_entity_id);
CREATE INDEX IF NOT EXISTS idx_cl_chapter_kp_rel_chapter
ON course.cl_chapter_kp_rel(tenant_id, chapter_id, relation_type);
CREATE INDEX IF NOT EXISTS idx_cl_chapter_kp_rel_kp
ON course.cl_chapter_kp_rel(tenant_id, kp_id, relation_type);
CREATE INDEX IF NOT EXISTS idx_cl_student_kp_mastery_student
ON course.cl_student_kp_mastery(tenant_id, student_id, mastery_score DESC);
CREATE INDEX IF NOT EXISTS idx_cl_student_kp_mastery_kp
ON course.cl_student_kp_mastery(tenant_id, kp_id, mastery_score DESC);
CREATE INDEX IF NOT EXISTS idx_cl_kp_material_rel_kp
ON course.cl_kp_material_rel(tenant_id, kp_id, material_type);
CREATE INDEX IF NOT EXISTS idx_cl_node_resource_node
ON course.cl_node_resource(node_id, resource_type);
CREATE INDEX IF NOT EXISTS idx_cl_node_kp_rel_node
ON course.cl_node_kp_rel(tenant_id, node_id, relation_type);
CREATE INDEX IF NOT EXISTS idx_cl_node_kp_rel_kp
ON course.cl_node_kp_rel(tenant_id, kp_id, relation_type);
CREATE INDEX IF NOT EXISTS idx_cl_node_homework_rel_node
ON course.cl_node_homework_rel(tenant_id, node_id, relation_type);
CREATE INDEX IF NOT EXISTS idx_cl_learning_session_user_course
ON course.cl_learning_session(tenant_id, user_id, course_id);
CREATE INDEX IF NOT EXISTS idx_cl_learning_progress_session_node
ON course.cl_learning_progress(session_id, node_id);
CREATE INDEX IF NOT EXISTS idx_cl_learning_event_session_time
ON course.cl_learning_event(session_id, event_time DESC);

View File

@@ -0,0 +1,2 @@
-- 课程学习模块初始化数据占位
-- 按需补充课程、章节、课时、知识点、标签等种子数据

View File

@@ -0,0 +1,729 @@
DROP SCHEMA IF EXISTS question CASCADE;
CREATE SCHEMA IF NOT EXISTS question;
DROP TABLE IF EXISTS question.hw_question_bank CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_question_bank (
bank_id VARCHAR(64) PRIMARY KEY,
bank_name VARCHAR(256) NOT NULL,
subject_code VARCHAR(32) NOT NULL,
grade_code VARCHAR(32) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_by VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE question.hw_question_bank IS '题库表';
COMMENT ON COLUMN question.hw_question_bank.bank_id IS '题库ID';
COMMENT ON COLUMN question.hw_question_bank.bank_name IS '题库名称';
COMMENT ON COLUMN question.hw_question_bank.subject_code IS '学科编码';
COMMENT ON COLUMN question.hw_question_bank.grade_code IS '年级编码';
COMMENT ON COLUMN question.hw_question_bank.status IS '状态';
COMMENT ON COLUMN question.hw_question_bank.adcode IS '行政区划编码';
COMMENT ON COLUMN question.hw_question_bank.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_question_bank.tenant_path IS '租户路径';
COMMENT ON COLUMN question.hw_question_bank.dept_id IS '部门ID';
COMMENT ON COLUMN question.hw_question_bank.dept_path IS '部门路径';
COMMENT ON COLUMN question.hw_question_bank.created_by IS '创建人ID';
COMMENT ON COLUMN question.hw_question_bank.created_at IS '创建时间';
DROP TABLE IF EXISTS question.hw_question_item CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_question_item (
question_id VARCHAR(64) PRIMARY KEY,
question_type VARCHAR(32) NOT NULL,
stem TEXT NOT NULL,
stem_json JSONB,
difficulty VARCHAR(16),
answer_payload JSONB,
analysis TEXT,
scoring_rule_json JSONB,
question_status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_hw_question_item_type
CHECK (question_type IN ('SINGLE_CHOICE', 'MULTIPLE_CHOICE', 'FILL_BLANK', 'JUDGE', 'SUBJECTIVE')),
CONSTRAINT chk_hw_question_item_answer_payload
CHECK (answer_payload IS NOT NULL AND jsonb_typeof(answer_payload) = 'object')
);
COMMENT ON TABLE question.hw_question_item IS '题目表(含标准答案)';
COMMENT ON COLUMN question.hw_question_item.question_id IS '题目ID';
COMMENT ON COLUMN question.hw_question_item.question_type IS '题型';
COMMENT ON COLUMN question.hw_question_item.stem IS '题干';
COMMENT ON COLUMN question.hw_question_item.stem_json IS '结构化题干JSON富文本/图片公式等)';
COMMENT ON COLUMN question.hw_question_item.difficulty IS '难度';
COMMENT ON COLUMN question.hw_question_item.answer_payload IS '统一标准答案载荷JSONB对应后端题型答案类型';
COMMENT ON COLUMN question.hw_question_item.analysis IS '题目解析';
COMMENT ON COLUMN question.hw_question_item.scoring_rule_json IS '评分规则JSON含主观题评分点';
COMMENT ON COLUMN question.hw_question_item.question_status IS '题目状态ACTIVE/DISABLED';
COMMENT ON COLUMN question.hw_question_item.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_question_item.created_at IS '创建时间';
COMMENT ON COLUMN question.hw_question_item.updated_at IS '更新时间';
DROP TABLE IF EXISTS question.hw_bank_question_rel CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_bank_question_rel (
bank_id VARCHAR(64) NOT NULL,
question_id VARCHAR(64) NOT NULL,
question_order INTEGER,
source_type VARCHAR(32) NOT NULL DEFAULT 'MANUAL',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (bank_id, question_id),
CONSTRAINT chk_hw_bank_question_rel_source_type
CHECK (source_type IN ('MANUAL', 'AI_GENERATED', 'IMPORTED', 'MIGRATED')),
CONSTRAINT fk_hw_bank_question_rel_bank
FOREIGN KEY (bank_id) REFERENCES question.hw_question_bank(bank_id),
CONSTRAINT fk_hw_bank_question_rel_question
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id)
);
COMMENT ON TABLE question.hw_bank_question_rel IS '题库-题目关联表';
COMMENT ON COLUMN question.hw_bank_question_rel.bank_id IS '题库ID';
COMMENT ON COLUMN question.hw_bank_question_rel.question_id IS '题目ID';
COMMENT ON COLUMN question.hw_bank_question_rel.question_order IS '题库内题目顺序';
COMMENT ON COLUMN question.hw_bank_question_rel.source_type IS '题目来源类型MANUAL/AI_GENERATED/IMPORTED/MIGRATED';
COMMENT ON COLUMN question.hw_bank_question_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_bank_question_rel.created_at IS '创建时间';
DROP TABLE IF EXISTS question.hw_question_kp_rel CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_question_kp_rel (
question_id VARCHAR(64) NOT NULL,
kp_id VARCHAR(64) NOT NULL,
relation_type VARCHAR(32) NOT NULL DEFAULT 'TESTS',
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
confidence NUMERIC(5,4),
graph_relation_id VARCHAR(64),
source_table VARCHAR(128),
source_pk VARCHAR(128),
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (question_id, kp_id),
CONSTRAINT chk_hw_question_kp_rel_type
CHECK (relation_type IN ('TESTS', 'RELATED', 'SUPPORTS')),
CONSTRAINT chk_hw_question_kp_rel_weight
CHECK (weight > 0 AND weight <= 1),
CONSTRAINT chk_hw_question_kp_rel_confidence
CHECK (confidence IS NULL OR (confidence >= 0 AND confidence <= 1)),
CONSTRAINT fk_hw_question_kp_rel_question
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id),
CONSTRAINT fk_hw_question_kp_rel_kp
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id)
);
COMMENT ON TABLE question.hw_question_kp_rel IS '题目-知识点关联表';
COMMENT ON COLUMN question.hw_question_kp_rel.question_id IS '题目ID';
COMMENT ON COLUMN question.hw_question_kp_rel.kp_id IS '知识点ID';
COMMENT ON COLUMN question.hw_question_kp_rel.relation_type IS '关系类型TESTS/RELATED/SUPPORTS';
COMMENT ON COLUMN question.hw_question_kp_rel.weight IS '关联权重0,1]';
COMMENT ON COLUMN question.hw_question_kp_rel.confidence IS '抽取置信度';
COMMENT ON COLUMN question.hw_question_kp_rel.graph_relation_id IS '图谱关系ID';
COMMENT ON COLUMN question.hw_question_kp_rel.source_table IS '来源表';
COMMENT ON COLUMN question.hw_question_kp_rel.source_pk IS '来源主键';
COMMENT ON COLUMN question.hw_question_kp_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_question_kp_rel.created_at IS '创建时间';
COMMENT ON COLUMN question.hw_question_kp_rel.updated_at IS '更新时间';
DROP TABLE IF EXISTS question.hw_paper CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_paper (
paper_id VARCHAR(64) PRIMARY KEY,
paper_name VARCHAR(256) NOT NULL,
paper_type VARCHAR(32) NOT NULL DEFAULT 'STANDARD',
subject_code VARCHAR(32) NOT NULL,
total_score NUMERIC(8,2) NOT NULL DEFAULT 100,
source_file_id VARCHAR(64),
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
created_by VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
grading_policy_json JSONB NOT NULL DEFAULT '{}'::JSONB,
CONSTRAINT chk_hw_paper_type
CHECK (paper_type IN ('STANDARD', 'EXTERNAL_UPLOAD')),
CONSTRAINT chk_hw_paper_grading_policy_json
CHECK (jsonb_typeof(grading_policy_json) = 'object'),
CONSTRAINT fk_hw_paper_source_file
FOREIGN KEY (source_file_id) REFERENCES upms.tb_sys_file(file_id)
);
COMMENT ON TABLE question.hw_paper IS '试卷表';
COMMENT ON COLUMN question.hw_paper.paper_id IS '试卷ID';
COMMENT ON COLUMN question.hw_paper.paper_name IS '试卷名称';
COMMENT ON COLUMN question.hw_paper.paper_type IS '试卷类型STANDARD/EXTERNAL_UPLOAD';
COMMENT ON COLUMN question.hw_paper.subject_code IS '学科编码';
COMMENT ON COLUMN question.hw_paper.total_score IS '总分';
COMMENT ON COLUMN question.hw_paper.source_file_id IS '来源文件ID课外上传作业解析来源';
COMMENT ON COLUMN question.hw_paper.adcode IS '行政区划编码';
COMMENT ON COLUMN question.hw_paper.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_paper.tenant_path IS '租户路径';
COMMENT ON COLUMN question.hw_paper.created_by IS '创建人';
COMMENT ON COLUMN question.hw_paper.created_at IS '创建时间';
COMMENT ON COLUMN question.hw_paper.grading_policy_json IS '试卷批改策略JSON单选/判断完全匹配、多选得分策略、填空匹配策略、简答评估模式)';
DROP TABLE IF EXISTS question.hw_paper_question CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_paper_question (
paper_id VARCHAR(64) NOT NULL,
question_id VARCHAR(64) NOT NULL,
question_order INTEGER NOT NULL,
score NUMERIC(8,2) NOT NULL DEFAULT 0,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (paper_id, question_id),
CONSTRAINT fk_hw_paper_question_paper
FOREIGN KEY (paper_id) REFERENCES question.hw_paper(paper_id),
CONSTRAINT fk_hw_paper_question_question
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id)
);
COMMENT ON TABLE question.hw_paper_question IS '试卷-题目关联表';
COMMENT ON COLUMN question.hw_paper_question.paper_id IS '试卷ID';
COMMENT ON COLUMN question.hw_paper_question.question_id IS '题目ID';
COMMENT ON COLUMN question.hw_paper_question.question_order IS '题目顺序';
COMMENT ON COLUMN question.hw_paper_question.score IS '题目分值';
COMMENT ON COLUMN question.hw_paper_question.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_paper_question.created_at IS '创建时间';
DROP TABLE IF EXISTS question.hw_assignment CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_assignment (
assignment_id VARCHAR(64) PRIMARY KEY,
paper_id VARCHAR(64) NOT NULL,
assignment_mode VARCHAR(32) NOT NULL DEFAULT 'PAPER',
title VARCHAR(256) NOT NULL,
publish_time TIMESTAMP,
deadline TIMESTAMP,
status VARCHAR(32) NOT NULL DEFAULT 'DRAFT',
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_by VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_hw_assignment_mode
CHECK (assignment_mode IN ('PAPER', 'EXTERNAL_UPLOAD')),
CONSTRAINT fk_hw_assignment_paper
FOREIGN KEY (paper_id) REFERENCES question.hw_paper(paper_id)
);
COMMENT ON TABLE question.hw_assignment IS '作业表';
COMMENT ON COLUMN question.hw_assignment.assignment_id IS '作业ID';
COMMENT ON COLUMN question.hw_assignment.paper_id IS '试卷ID';
COMMENT ON COLUMN question.hw_assignment.assignment_mode IS '作业模式PAPER/EXTERNAL_UPLOAD';
COMMENT ON COLUMN question.hw_assignment.title IS '作业标题';
COMMENT ON COLUMN question.hw_assignment.publish_time IS '发布时间';
COMMENT ON COLUMN question.hw_assignment.deadline IS '截止时间';
COMMENT ON COLUMN question.hw_assignment.status IS '状态DRAFT/PUBLISHED/CLOSED';
COMMENT ON COLUMN question.hw_assignment.adcode IS '行政区划编码';
COMMENT ON COLUMN question.hw_assignment.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_assignment.tenant_path IS '租户路径';
COMMENT ON COLUMN question.hw_assignment.dept_id IS '部门ID';
COMMENT ON COLUMN question.hw_assignment.dept_path IS '部门路径';
COMMENT ON COLUMN question.hw_assignment.created_by IS '创建人';
COMMENT ON COLUMN question.hw_assignment.created_at IS '创建时间';
DROP TABLE IF EXISTS question.hw_assignment_target CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_assignment_target (
target_id VARCHAR(64) PRIMARY KEY,
assignment_id VARCHAR(64) NOT NULL,
target_type VARCHAR(32) NOT NULL,
target_ref_id VARCHAR(64) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_hw_assignment_target_assignment
FOREIGN KEY (assignment_id) REFERENCES question.hw_assignment(assignment_id)
);
COMMENT ON TABLE question.hw_assignment_target IS '作业投放对象表';
COMMENT ON COLUMN question.hw_assignment_target.target_id IS '投放记录ID';
COMMENT ON COLUMN question.hw_assignment_target.assignment_id IS '作业ID';
COMMENT ON COLUMN question.hw_assignment_target.target_type IS '投放类型CLASS/STUDENT';
COMMENT ON COLUMN question.hw_assignment_target.target_ref_id IS '投放对象ID';
COMMENT ON COLUMN question.hw_assignment_target.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_assignment_target.created_at IS '创建时间';
DROP TABLE IF EXISTS question.hw_submission CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_submission (
submission_id VARCHAR(64) PRIMARY KEY,
assignment_id VARCHAR(64) NOT NULL,
student_id VARCHAR(64) NOT NULL,
submission_mode VARCHAR(32) NOT NULL DEFAULT 'ASSIGNMENT',
origin_file_id VARCHAR(64),
submit_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
used_seconds INTEGER,
status VARCHAR(32) NOT NULL DEFAULT 'SUBMITTED',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_hw_submission_mode
CHECK (submission_mode IN ('ASSIGNMENT', 'EXTERNAL_UPLOAD')),
CONSTRAINT fk_hw_submission_assignment
FOREIGN KEY (assignment_id) REFERENCES question.hw_assignment(assignment_id),
CONSTRAINT fk_hw_submission_student
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_hw_submission_origin_file
FOREIGN KEY (origin_file_id) REFERENCES upms.tb_sys_file(file_id)
);
COMMENT ON TABLE question.hw_submission IS '作业提交表';
COMMENT ON COLUMN question.hw_submission.submission_id IS '提交ID';
COMMENT ON COLUMN question.hw_submission.assignment_id IS '作业ID';
COMMENT ON COLUMN question.hw_submission.student_id IS '学员ID';
COMMENT ON COLUMN question.hw_submission.submission_mode IS '提交模式ASSIGNMENT/EXTERNAL_UPLOAD';
COMMENT ON COLUMN question.hw_submission.origin_file_id IS '原始上传文件ID课外作业';
COMMENT ON COLUMN question.hw_submission.submit_time IS '提交时间';
COMMENT ON COLUMN question.hw_submission.used_seconds IS '作答耗时(秒)';
COMMENT ON COLUMN question.hw_submission.status IS '提交状态';
COMMENT ON COLUMN question.hw_submission.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_submission.created_at IS '创建时间';
DROP TABLE IF EXISTS question.hw_submission_answer CASCADE;
CREATE TABLE IF NOT EXISTS question.hw_submission_answer (
answer_id VARCHAR(64) PRIMARY KEY,
submission_id VARCHAR(64) NOT NULL,
question_id VARCHAR(64) NOT NULL,
answer_type VARCHAR(32) NOT NULL,
answer_payload JSONB,
file_id VARCHAR(64),
file_type VARCHAR(32),
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_hw_submission_answer_type
CHECK (answer_type IN ('SINGLE_CHOICE', 'MULTIPLE_CHOICE', 'FILL_BLANK', 'JUDGE', 'SUBJECTIVE')),
CONSTRAINT chk_hw_submission_answer_payload
CHECK (
answer_payload IS NOT NULL
OR file_id IS NOT NULL
),
CONSTRAINT chk_hw_submission_answer_payload_json
CHECK (answer_payload IS NULL OR jsonb_typeof(answer_payload) = 'object'),
CONSTRAINT chk_hw_submission_answer_file_type
CHECK (file_type IS NULL OR file_type IN ('IMAGE', 'AUDIO', 'VIDEO', 'DOCUMENT', 'OTHER')),
CONSTRAINT chk_hw_submission_answer_file_ref
CHECK (
(file_id IS NULL AND file_type IS NULL)
OR (file_id IS NOT NULL AND file_type IS NOT NULL)
),
CONSTRAINT uq_hw_submission_answer_submission_question
UNIQUE (submission_id, question_id),
CONSTRAINT fk_hw_submission_answer_submission
FOREIGN KEY (submission_id) REFERENCES question.hw_submission(submission_id),
CONSTRAINT fk_hw_submission_answer_question
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id),
CONSTRAINT fk_hw_submission_answer_file
FOREIGN KEY (file_id) REFERENCES upms.tb_sys_file(file_id)
);
COMMENT ON TABLE question.hw_submission_answer IS '提交答案明细表(一次提交中每题一行)';
COMMENT ON COLUMN question.hw_submission_answer.answer_id IS '答案ID';
COMMENT ON COLUMN question.hw_submission_answer.submission_id IS '提交ID';
COMMENT ON COLUMN question.hw_submission_answer.question_id IS '题目ID';
COMMENT ON COLUMN question.hw_submission_answer.answer_type IS '答案类型(与题型一致)';
COMMENT ON COLUMN question.hw_submission_answer.answer_payload IS '统一作答载荷JSONB对应后端答案类型';
COMMENT ON COLUMN question.hw_submission_answer.file_id IS '作答文件ID图片/音频等)';
COMMENT ON COLUMN question.hw_submission_answer.file_type IS '作答文件类型';
COMMENT ON COLUMN question.hw_submission_answer.tenant_id IS '租户ID';
COMMENT ON COLUMN question.hw_submission_answer.created_at IS '创建时间';
COMMENT ON COLUMN question.hw_submission_answer.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_hw_question_bank_tenant_subject_grade
ON question.hw_question_bank(tenant_id, subject_code, grade_code);
CREATE INDEX IF NOT EXISTS idx_hw_question_item_type
ON question.hw_question_item(question_type, difficulty);
CREATE INDEX IF NOT EXISTS idx_hw_question_item_tenant
ON question.hw_question_item(tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_hw_bank_question_rel_question
ON question.hw_bank_question_rel(question_id, bank_id);
CREATE INDEX IF NOT EXISTS idx_hw_bank_question_rel_tenant_bank
ON question.hw_bank_question_rel(tenant_id, bank_id, question_order);
CREATE INDEX IF NOT EXISTS idx_hw_question_kp_rel_tenant_question
ON question.hw_question_kp_rel(tenant_id, question_id);
CREATE INDEX IF NOT EXISTS idx_hw_question_kp_rel_tenant_kp
ON question.hw_question_kp_rel(tenant_id, kp_id);
CREATE INDEX IF NOT EXISTS idx_hw_question_kp_rel_graph_relation
ON question.hw_question_kp_rel(graph_relation_id);
CREATE INDEX IF NOT EXISTS idx_hw_assignment_tenant_status_deadline
ON question.hw_assignment(tenant_id, status, deadline);
CREATE INDEX IF NOT EXISTS idx_hw_assignment_mode
ON question.hw_assignment(assignment_mode, tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_hw_assignment_target_assignment
ON question.hw_assignment_target(assignment_id, target_type);
CREATE INDEX IF NOT EXISTS idx_hw_submission_assignment_student
ON question.hw_submission(assignment_id, student_id);
CREATE INDEX IF NOT EXISTS idx_hw_submission_mode
ON question.hw_submission(submission_mode, tenant_id, submit_time DESC);
CREATE INDEX IF NOT EXISTS idx_hw_submission_origin_file
ON question.hw_submission(origin_file_id);
CREATE INDEX IF NOT EXISTS idx_hw_submission_answer_submission
ON question.hw_submission_answer(submission_id);
CREATE INDEX IF NOT EXISTS idx_hw_submission_answer_question
ON question.hw_submission_answer(question_id);
CREATE INDEX IF NOT EXISTS idx_hw_submission_answer_file_id
ON question.hw_submission_answer(file_id);
DROP TABLE IF EXISTS question.gd_grading_task CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_grading_task (
grading_task_id VARCHAR(64) PRIMARY KEY,
submission_id VARCHAR(64) NOT NULL,
paper_id VARCHAR(64) NOT NULL,
task_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
trigger_source VARCHAR(32) NOT NULL DEFAULT 'SUBMISSION',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_gd_grading_task_submission
FOREIGN KEY (submission_id) REFERENCES question.hw_submission(submission_id),
CONSTRAINT fk_gd_grading_task_paper
FOREIGN KEY (paper_id) REFERENCES question.hw_paper(paper_id)
);
COMMENT ON TABLE question.gd_grading_task IS '批改任务表';
COMMENT ON COLUMN question.gd_grading_task.grading_task_id IS '批改任务ID';
COMMENT ON COLUMN question.gd_grading_task.submission_id IS '提交ID';
COMMENT ON COLUMN question.gd_grading_task.paper_id IS '试卷ID';
COMMENT ON COLUMN question.gd_grading_task.task_status IS '任务状态PENDING/RUNNING/WAIT_REVIEW/DONE';
COMMENT ON COLUMN question.gd_grading_task.trigger_source IS '触发来源';
COMMENT ON COLUMN question.gd_grading_task.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_grading_task.created_at IS '创建时间';
COMMENT ON COLUMN question.gd_grading_task.updated_at IS '更新时间';
DROP TABLE IF EXISTS question.gd_answer_grade CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_answer_grade (
answer_grade_id VARCHAR(64) PRIMARY KEY,
grading_task_id VARCHAR(64) NOT NULL,
answer_id VARCHAR(64) NOT NULL,
paper_id VARCHAR(64) NOT NULL,
grade_mode VARCHAR(32) NOT NULL DEFAULT 'AUTO',
grade_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
score NUMERIC(8,2),
grader_id VARCHAR(64),
grade_comment TEXT,
evidence_json JSONB,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_gd_answer_grade_mode
CHECK (grade_mode IN ('AUTO', 'MANUAL', 'AI', 'AI_REVIEW')),
CONSTRAINT chk_gd_answer_grade_status
CHECK (grade_status IN ('PENDING', 'DONE', 'REVIEWED')),
CONSTRAINT uq_gd_answer_grade_task_answer
UNIQUE (grading_task_id, answer_id),
CONSTRAINT fk_gd_answer_grade_task
FOREIGN KEY (grading_task_id) REFERENCES question.gd_grading_task(grading_task_id),
CONSTRAINT fk_gd_answer_grade_answer
FOREIGN KEY (answer_id) REFERENCES question.hw_submission_answer(answer_id),
CONSTRAINT fk_gd_answer_grade_paper
FOREIGN KEY (paper_id) REFERENCES question.hw_paper(paper_id),
CONSTRAINT fk_gd_answer_grade_grader
FOREIGN KEY (grader_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE question.gd_answer_grade IS '答案批改结果表(统一客观/主观)';
COMMENT ON COLUMN question.gd_answer_grade.answer_grade_id IS '答案批改结果ID';
COMMENT ON COLUMN question.gd_answer_grade.grading_task_id IS '批改任务ID';
COMMENT ON COLUMN question.gd_answer_grade.answer_id IS '答案ID';
COMMENT ON COLUMN question.gd_answer_grade.paper_id IS '试卷ID';
COMMENT ON COLUMN question.gd_answer_grade.grade_mode IS '批改模式AUTO/MANUAL/AI/AI_REVIEW';
COMMENT ON COLUMN question.gd_answer_grade.grade_status IS '批改状态PENDING/DONE/REVIEWED';
COMMENT ON COLUMN question.gd_answer_grade.score IS '最终得分';
COMMENT ON COLUMN question.gd_answer_grade.grader_id IS '评阅人ID';
COMMENT ON COLUMN question.gd_answer_grade.grade_comment IS '批改评语';
COMMENT ON COLUMN question.gd_answer_grade.evidence_json IS '判定依据JSON';
COMMENT ON COLUMN question.gd_answer_grade.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_answer_grade.created_at IS '创建时间';
COMMENT ON COLUMN question.gd_answer_grade.updated_at IS '更新时间';
DROP TABLE IF EXISTS question.gd_score_summary CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_score_summary (
summary_id VARCHAR(64) PRIMARY KEY,
grading_task_id VARCHAR(64) UNIQUE NOT NULL,
total_score NUMERIC(8,2) NOT NULL DEFAULT 0,
grade_level VARCHAR(8),
surpass_ratio NUMERIC(5,2),
used_seconds INTEGER,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_gd_score_summary_task
FOREIGN KEY (grading_task_id) REFERENCES question.gd_grading_task(grading_task_id)
);
COMMENT ON TABLE question.gd_score_summary IS '批改结果汇总表';
COMMENT ON COLUMN question.gd_score_summary.summary_id IS '汇总ID';
COMMENT ON COLUMN question.gd_score_summary.grading_task_id IS '批改任务ID';
COMMENT ON COLUMN question.gd_score_summary.total_score IS '总分';
COMMENT ON COLUMN question.gd_score_summary.grade_level IS '等级A/B/C';
COMMENT ON COLUMN question.gd_score_summary.surpass_ratio IS '超越比例';
COMMENT ON COLUMN question.gd_score_summary.used_seconds IS '作答耗时';
COMMENT ON COLUMN question.gd_score_summary.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_score_summary.created_at IS '创建时间';
DROP TABLE IF EXISTS question.gd_error_tag CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_error_tag (
error_tag_id VARCHAR(64) PRIMARY KEY,
tag_code VARCHAR(64) UNIQUE NOT NULL,
tag_name VARCHAR(128) NOT NULL,
category VARCHAR(32) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE question.gd_error_tag IS '错因标签表';
COMMENT ON COLUMN question.gd_error_tag.error_tag_id IS '标签ID';
COMMENT ON COLUMN question.gd_error_tag.tag_code IS '标签编码';
COMMENT ON COLUMN question.gd_error_tag.tag_name IS '标签名称';
COMMENT ON COLUMN question.gd_error_tag.category IS '分类(审题/计算/概念等)';
COMMENT ON COLUMN question.gd_error_tag.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_error_tag.created_at IS '创建时间';
DROP TABLE IF EXISTS question.gd_answer_error_rel CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_answer_error_rel (
answer_id VARCHAR(64) NOT NULL,
error_tag_id VARCHAR(64) NOT NULL,
confidence NUMERIC(5,4),
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (answer_id, error_tag_id),
CONSTRAINT fk_gd_answer_error_rel_answer
FOREIGN KEY (answer_id) REFERENCES question.hw_submission_answer(answer_id),
CONSTRAINT fk_gd_answer_error_rel_tag
FOREIGN KEY (error_tag_id) REFERENCES question.gd_error_tag(error_tag_id)
);
COMMENT ON TABLE question.gd_answer_error_rel IS '答案-错因标签关联表';
COMMENT ON COLUMN question.gd_answer_error_rel.answer_id IS '答案ID';
COMMENT ON COLUMN question.gd_answer_error_rel.error_tag_id IS '错因标签ID';
COMMENT ON COLUMN question.gd_answer_error_rel.confidence IS '置信度';
COMMENT ON COLUMN question.gd_answer_error_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_answer_error_rel.created_at IS '创建时间';
DROP TABLE IF EXISTS question.gd_answer_kp_analysis CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_answer_kp_analysis (
answer_id VARCHAR(64) NOT NULL,
kp_id VARCHAR(64) NOT NULL,
grading_task_id VARCHAR(64),
correctness VARCHAR(16) NOT NULL DEFAULT 'UNKNOWN',
mastery_score NUMERIC(6,4),
confidence NUMERIC(5,4),
evidence_json JSONB NOT NULL DEFAULT '{}'::JSONB,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (answer_id, kp_id),
CONSTRAINT chk_gd_answer_kp_analysis_correctness
CHECK (correctness IN ('CORRECT', 'PARTIAL', 'WRONG', 'UNKNOWN')),
CONSTRAINT chk_gd_answer_kp_analysis_mastery
CHECK (mastery_score IS NULL OR (mastery_score >= 0 AND mastery_score <= 1)),
CONSTRAINT chk_gd_answer_kp_analysis_confidence
CHECK (confidence IS NULL OR (confidence >= 0 AND confidence <= 1)),
CONSTRAINT chk_gd_answer_kp_analysis_evidence_json
CHECK (jsonb_typeof(evidence_json) = 'object'),
CONSTRAINT fk_gd_answer_kp_analysis_answer
FOREIGN KEY (answer_id) REFERENCES question.hw_submission_answer(answer_id),
CONSTRAINT fk_gd_answer_kp_analysis_kp
FOREIGN KEY (kp_id) REFERENCES course.cl_knowledge_point(kp_id),
CONSTRAINT fk_gd_answer_kp_analysis_grading_task
FOREIGN KEY (grading_task_id) REFERENCES question.gd_grading_task(grading_task_id)
);
COMMENT ON TABLE question.gd_answer_kp_analysis IS '答案-知识点分析表';
COMMENT ON COLUMN question.gd_answer_kp_analysis.answer_id IS '答案ID';
COMMENT ON COLUMN question.gd_answer_kp_analysis.kp_id IS '知识点ID';
COMMENT ON COLUMN question.gd_answer_kp_analysis.grading_task_id IS '批改任务ID';
COMMENT ON COLUMN question.gd_answer_kp_analysis.correctness IS '正确性CORRECT/PARTIAL/WRONG/UNKNOWN';
COMMENT ON COLUMN question.gd_answer_kp_analysis.mastery_score IS '答案层知识点掌握度0-1';
COMMENT ON COLUMN question.gd_answer_kp_analysis.confidence IS '分析置信度';
COMMENT ON COLUMN question.gd_answer_kp_analysis.evidence_json IS '知识点分析依据JSON';
COMMENT ON COLUMN question.gd_answer_kp_analysis.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_answer_kp_analysis.created_at IS '创建时间';
COMMENT ON COLUMN question.gd_answer_kp_analysis.updated_at IS '更新时间';
DROP TABLE IF EXISTS question.gd_wrong_question CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_wrong_question (
wrong_question_id VARCHAR(64) PRIMARY KEY,
student_id VARCHAR(64) NOT NULL,
question_id VARCHAR(64) NOT NULL,
source_submission_id VARCHAR(64) NOT NULL,
mastery_status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
review_count INTEGER NOT NULL DEFAULT 0,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_gd_wrong_question_student
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_gd_wrong_question_question
FOREIGN KEY (question_id) REFERENCES question.hw_question_item(question_id),
CONSTRAINT fk_gd_wrong_question_submission
FOREIGN KEY (source_submission_id) REFERENCES question.hw_submission(submission_id)
);
COMMENT ON TABLE question.gd_wrong_question IS '错题沉淀表';
COMMENT ON COLUMN question.gd_wrong_question.wrong_question_id IS '错题记录ID';
COMMENT ON COLUMN question.gd_wrong_question.student_id IS '学员ID';
COMMENT ON COLUMN question.gd_wrong_question.question_id IS '题目ID';
COMMENT ON COLUMN question.gd_wrong_question.source_submission_id IS '来源提交ID';
COMMENT ON COLUMN question.gd_wrong_question.mastery_status IS '掌握状态';
COMMENT ON COLUMN question.gd_wrong_question.review_count IS '复习次数';
COMMENT ON COLUMN question.gd_wrong_question.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_wrong_question.created_at IS '创建时间';
COMMENT ON COLUMN question.gd_wrong_question.updated_at IS '更新时间';
DROP TABLE IF EXISTS question.gd_review_plan CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_review_plan (
review_plan_id VARCHAR(64) PRIMARY KEY,
wrong_question_id VARCHAR(64) NOT NULL,
plan_date DATE NOT NULL,
plan_stage VARCHAR(16) NOT NULL,
plan_status VARCHAR(32) NOT NULL DEFAULT 'TODO',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_gd_review_plan_wrong_question
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id)
);
COMMENT ON TABLE question.gd_review_plan IS '复习计划表';
COMMENT ON COLUMN question.gd_review_plan.review_plan_id IS '复习计划ID';
COMMENT ON COLUMN question.gd_review_plan.wrong_question_id IS '错题记录ID';
COMMENT ON COLUMN question.gd_review_plan.plan_date IS '计划日期';
COMMENT ON COLUMN question.gd_review_plan.plan_stage IS '阶段E1/E2/E3';
COMMENT ON COLUMN question.gd_review_plan.plan_status IS '计划状态';
COMMENT ON COLUMN question.gd_review_plan.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_review_plan.created_at IS '创建时间';
DROP TABLE IF EXISTS question.gd_teacher_comment CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_teacher_comment (
comment_id VARCHAR(64) PRIMARY KEY,
answer_grade_id VARCHAR(64) NOT NULL,
reviewer_id VARCHAR(64) NOT NULL,
comment_type VARCHAR(16) NOT NULL DEFAULT 'TEXT',
content_ref TEXT,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_gd_teacher_comment_answer_grade
FOREIGN KEY (answer_grade_id) REFERENCES question.gd_answer_grade(answer_grade_id),
CONSTRAINT fk_gd_teacher_comment_reviewer
FOREIGN KEY (reviewer_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE question.gd_teacher_comment IS '教师点评表';
COMMENT ON COLUMN question.gd_teacher_comment.comment_id IS '点评ID';
COMMENT ON COLUMN question.gd_teacher_comment.answer_grade_id IS '答案批改结果ID';
COMMENT ON COLUMN question.gd_teacher_comment.reviewer_id IS '教师ID';
COMMENT ON COLUMN question.gd_teacher_comment.comment_type IS '点评类型TEXT/VOICE';
COMMENT ON COLUMN question.gd_teacher_comment.content_ref IS '点评内容或资源引用';
COMMENT ON COLUMN question.gd_teacher_comment.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_teacher_comment.created_at IS '创建时间';
DROP TABLE IF EXISTS question.gd_explanation_dimension_score CASCADE;
DROP TABLE IF EXISTS question.gd_explanation_assessment CASCADE;
DROP TABLE IF EXISTS question.gd_explanation_submission CASCADE;
CREATE TABLE IF NOT EXISTS question.gd_explanation_submission (
explanation_id VARCHAR(64) PRIMARY KEY,
student_id VARCHAR(64) NOT NULL,
wrong_question_id VARCHAR(64),
source_submission_id VARCHAR(64),
source_answer_id VARCHAR(64),
audio_file_id VARCHAR(64) NOT NULL,
duration_seconds INTEGER,
transcript_text TEXT,
submission_status VARCHAR(16) NOT NULL DEFAULT 'SUBMITTED',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_gd_explanation_submission_status
CHECK (submission_status IN ('SUBMITTED', 'EVALUATED', 'REJECTED')),
CONSTRAINT fk_gd_explanation_submission_student
FOREIGN KEY (student_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_gd_explanation_submission_wrong_question
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id),
CONSTRAINT fk_gd_explanation_submission_source_submission
FOREIGN KEY (source_submission_id) REFERENCES question.hw_submission(submission_id),
CONSTRAINT fk_gd_explanation_submission_source_answer
FOREIGN KEY (source_answer_id) REFERENCES question.hw_submission_answer(answer_id),
CONSTRAINT fk_gd_explanation_submission_audio_file
FOREIGN KEY (audio_file_id) REFERENCES upms.tb_sys_file(file_id)
);
COMMENT ON TABLE question.gd_explanation_submission IS '费曼讲解提交表';
COMMENT ON COLUMN question.gd_explanation_submission.explanation_id IS '讲解提交ID';
COMMENT ON COLUMN question.gd_explanation_submission.student_id IS '学员ID';
COMMENT ON COLUMN question.gd_explanation_submission.wrong_question_id IS '关联错题ID';
COMMENT ON COLUMN question.gd_explanation_submission.source_submission_id IS '来源作业提交ID';
COMMENT ON COLUMN question.gd_explanation_submission.source_answer_id IS '来源答案ID';
COMMENT ON COLUMN question.gd_explanation_submission.audio_file_id IS '讲解音频文件ID';
COMMENT ON COLUMN question.gd_explanation_submission.duration_seconds IS '时长(秒)';
COMMENT ON COLUMN question.gd_explanation_submission.transcript_text IS '转写文本';
COMMENT ON COLUMN question.gd_explanation_submission.submission_status IS '提交状态';
COMMENT ON COLUMN question.gd_explanation_submission.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_explanation_submission.created_at IS '创建时间';
COMMENT ON COLUMN question.gd_explanation_submission.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS question.gd_explanation_assessment (
assessment_id VARCHAR(64) PRIMARY KEY,
explanation_id VARCHAR(64) NOT NULL UNIQUE,
evaluator_type VARCHAR(16) NOT NULL DEFAULT 'AI',
evaluator_id VARCHAR(64),
total_score NUMERIC(8,2),
pass_status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
improvement_suggestion TEXT,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_gd_explanation_assessment_evaluator_type
CHECK (evaluator_type IN ('AI', 'TEACHER', 'HYBRID')),
CONSTRAINT chk_gd_explanation_assessment_pass_status
CHECK (pass_status IN ('PENDING', 'PASSED', 'FAILED')),
CONSTRAINT fk_gd_explanation_assessment_explanation
FOREIGN KEY (explanation_id) REFERENCES question.gd_explanation_submission(explanation_id) ON DELETE CASCADE,
CONSTRAINT fk_gd_explanation_assessment_evaluator
FOREIGN KEY (evaluator_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE question.gd_explanation_assessment IS '费曼讲解评估汇总表';
COMMENT ON COLUMN question.gd_explanation_assessment.assessment_id IS '评估ID';
COMMENT ON COLUMN question.gd_explanation_assessment.explanation_id IS '讲解提交ID';
COMMENT ON COLUMN question.gd_explanation_assessment.evaluator_type IS '评估类型';
COMMENT ON COLUMN question.gd_explanation_assessment.evaluator_id IS '评估人ID';
COMMENT ON COLUMN question.gd_explanation_assessment.total_score IS '总分';
COMMENT ON COLUMN question.gd_explanation_assessment.pass_status IS '通过状态';
COMMENT ON COLUMN question.gd_explanation_assessment.improvement_suggestion IS '改进建议';
COMMENT ON COLUMN question.gd_explanation_assessment.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_explanation_assessment.created_at IS '创建时间';
COMMENT ON COLUMN question.gd_explanation_assessment.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS question.gd_explanation_dimension_score (
dimension_score_id VARCHAR(64) PRIMARY KEY,
assessment_id VARCHAR(64) NOT NULL,
dimension_code VARCHAR(32) NOT NULL,
dimension_name VARCHAR(64) NOT NULL,
score NUMERIC(8,2) NOT NULL DEFAULT 0,
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
comment_text TEXT,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_gd_explanation_dimension_score_weight
CHECK (weight > 0 AND weight <= 1),
CONSTRAINT fk_gd_explanation_dimension_score_assessment
FOREIGN KEY (assessment_id) REFERENCES question.gd_explanation_assessment(assessment_id) ON DELETE CASCADE
);
COMMENT ON TABLE question.gd_explanation_dimension_score IS '费曼讲解维度评分表';
COMMENT ON COLUMN question.gd_explanation_dimension_score.dimension_score_id IS '维度评分ID';
COMMENT ON COLUMN question.gd_explanation_dimension_score.assessment_id IS '评估ID';
COMMENT ON COLUMN question.gd_explanation_dimension_score.dimension_code IS '维度编码';
COMMENT ON COLUMN question.gd_explanation_dimension_score.dimension_name IS '维度名称';
COMMENT ON COLUMN question.gd_explanation_dimension_score.score IS '维度得分';
COMMENT ON COLUMN question.gd_explanation_dimension_score.weight IS '维度权重';
COMMENT ON COLUMN question.gd_explanation_dimension_score.comment_text IS '维度评语';
COMMENT ON COLUMN question.gd_explanation_dimension_score.tenant_id IS '租户ID';
COMMENT ON COLUMN question.gd_explanation_dimension_score.created_at IS '创建时间';
CREATE INDEX IF NOT EXISTS idx_gd_grading_task_submission
ON question.gd_grading_task(submission_id, task_status);
CREATE INDEX IF NOT EXISTS idx_gd_grading_task_paper_status
ON question.gd_grading_task(paper_id, task_status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_gd_answer_grade_task_status
ON question.gd_answer_grade(grading_task_id, grade_status);
CREATE INDEX IF NOT EXISTS idx_gd_answer_grade_paper_status
ON question.gd_answer_grade(paper_id, grade_status, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_gd_answer_error_rel_answer
ON question.gd_answer_error_rel(answer_id);
CREATE INDEX IF NOT EXISTS idx_gd_answer_kp_analysis_kp
ON question.gd_answer_kp_analysis(kp_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_gd_answer_kp_analysis_task
ON question.gd_answer_kp_analysis(grading_task_id, kp_id);
CREATE INDEX IF NOT EXISTS idx_gd_wrong_question_student_status
ON question.gd_wrong_question(student_id, mastery_status);
CREATE INDEX IF NOT EXISTS idx_gd_review_plan_wrong_question_date
ON question.gd_review_plan(wrong_question_id, plan_date);
CREATE INDEX IF NOT EXISTS idx_gd_teacher_comment_answer_grade
ON question.gd_teacher_comment(answer_grade_id);
CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_student
ON question.gd_explanation_submission(student_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_gd_explanation_submission_wrong_question
ON question.gd_explanation_submission(wrong_question_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_gd_explanation_assessment_pass_status
ON question.gd_explanation_assessment(pass_status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_gd_explanation_dimension_score_assessment
ON question.gd_explanation_dimension_score(assessment_id, dimension_code);

View File

@@ -0,0 +1,5 @@
-- 习题与作业模块初始化数据占位
-- 按需补充题库、题目、试卷、作业模板等种子数据
-- 批改与反馈模块初始化数据占位
-- 按需补充错因标签、统一答案批改结果、复习计划策略等种子数据

View File

@@ -0,0 +1,440 @@
DROP SCHEMA IF EXISTS recommendation CASCADE;
CREATE SCHEMA IF NOT EXISTS recommendation;
DROP TABLE IF EXISTS recommendation.rc_recommendation_task CASCADE;
CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_task (
task_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
scene_code VARCHAR(32) NOT NULL,
trigger_source VARCHAR(32) NOT NULL DEFAULT 'SCHEDULE',
strategy_version VARCHAR(32),
profile_snapshot_json JSONB NOT NULL DEFAULT '{}'::JSONB,
status VARCHAR(16) NOT NULL DEFAULT 'CREATED',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_rc_recommendation_task_status
CHECK (status IN ('CREATED', 'RUNNING', 'DONE', 'FAILED')),
CONSTRAINT chk_rc_recommendation_task_profile_json
CHECK (jsonb_typeof(profile_snapshot_json) = 'object'),
CONSTRAINT fk_rc_recommendation_task_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE recommendation.rc_recommendation_task IS '推荐任务表';
COMMENT ON COLUMN recommendation.rc_recommendation_task.task_id IS '推荐任务ID';
COMMENT ON COLUMN recommendation.rc_recommendation_task.user_id IS '推荐目标用户ID';
COMMENT ON COLUMN recommendation.rc_recommendation_task.scene_code IS '推荐场景编码HOME/WRONGBOOK/REVIEW等';
COMMENT ON COLUMN recommendation.rc_recommendation_task.trigger_source IS '触发来源SCHEDULE/EVENT/MANUAL';
COMMENT ON COLUMN recommendation.rc_recommendation_task.strategy_version IS '推荐策略版本';
COMMENT ON COLUMN recommendation.rc_recommendation_task.profile_snapshot_json IS '画像快照JSON';
COMMENT ON COLUMN recommendation.rc_recommendation_task.status IS '任务状态';
COMMENT ON COLUMN recommendation.rc_recommendation_task.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_recommendation_task.created_at IS '创建时间';
COMMENT ON COLUMN recommendation.rc_recommendation_task.updated_at IS '更新时间';
DROP TABLE IF EXISTS recommendation.rc_recommendation_item CASCADE;
CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_item (
item_id VARCHAR(64) PRIMARY KEY,
task_id VARCHAR(64) NOT NULL,
content_type VARCHAR(32) NOT NULL,
content_object_id VARCHAR(64) NOT NULL,
content_object_type VARCHAR(64) NOT NULL,
rank_no INTEGER NOT NULL,
score NUMERIC(10,6),
reason_codes VARCHAR(255),
metadata_json JSONB NOT NULL DEFAULT '{}'::JSONB,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_rc_recommendation_item_content_type
CHECK (content_type IN ('COURSE', 'QUESTION', 'PAPER', 'WRONG_QUESTION', 'REVIEW_PLAN', 'MIXED')),
CONSTRAINT chk_rc_recommendation_item_metadata_json
CHECK (jsonb_typeof(metadata_json) = 'object'),
CONSTRAINT uq_rc_recommendation_item_task_rank
UNIQUE (task_id, rank_no),
CONSTRAINT fk_rc_recommendation_item_task
FOREIGN KEY (task_id) REFERENCES recommendation.rc_recommendation_task(task_id) ON DELETE CASCADE
);
COMMENT ON TABLE recommendation.rc_recommendation_item IS '推荐结果明细表';
COMMENT ON COLUMN recommendation.rc_recommendation_item.item_id IS '推荐项ID';
COMMENT ON COLUMN recommendation.rc_recommendation_item.task_id IS '推荐任务ID';
COMMENT ON COLUMN recommendation.rc_recommendation_item.content_type IS '内容类型';
COMMENT ON COLUMN recommendation.rc_recommendation_item.content_object_id IS '内容对象ID';
COMMENT ON COLUMN recommendation.rc_recommendation_item.content_object_type IS '内容对象类型';
COMMENT ON COLUMN recommendation.rc_recommendation_item.rank_no IS '推荐位次';
COMMENT ON COLUMN recommendation.rc_recommendation_item.score IS '推荐分';
COMMENT ON COLUMN recommendation.rc_recommendation_item.reason_codes IS '推荐原因编码集合';
COMMENT ON COLUMN recommendation.rc_recommendation_item.metadata_json IS '扩展元数据JSON';
COMMENT ON COLUMN recommendation.rc_recommendation_item.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_recommendation_item.created_at IS '创建时间';
DROP TABLE IF EXISTS recommendation.rc_recommendation_feedback CASCADE;
CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_feedback (
feedback_id VARCHAR(64) PRIMARY KEY,
item_id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
event_type VARCHAR(16) NOT NULL,
event_value NUMERIC(10,4),
event_detail_json JSONB NOT NULL DEFAULT '{}'::JSONB,
event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_rc_recommendation_feedback_event_type
CHECK (event_type IN ('EXPOSE', 'CLICK', 'START', 'COMPLETE', 'DISLIKE', 'SKIP')),
CONSTRAINT chk_rc_recommendation_feedback_detail_json
CHECK (jsonb_typeof(event_detail_json) = 'object'),
CONSTRAINT fk_rc_recommendation_feedback_item
FOREIGN KEY (item_id) REFERENCES recommendation.rc_recommendation_item(item_id) ON DELETE CASCADE,
CONSTRAINT fk_rc_recommendation_feedback_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE recommendation.rc_recommendation_feedback IS '推荐反馈事件表';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.feedback_id IS '反馈事件ID';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.item_id IS '推荐项ID';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.user_id IS '用户ID';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_type IS '事件类型';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_value IS '事件值(可选)';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_detail_json IS '事件详情JSON';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.event_time IS '事件发生时间';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_recommendation_feedback.created_at IS '创建时间';
DROP TABLE IF EXISTS recommendation.rc_recommendation_effect_daily CASCADE;
CREATE TABLE IF NOT EXISTS recommendation.rc_recommendation_effect_daily (
stat_date DATE NOT NULL,
scene_code VARCHAR(32) NOT NULL,
strategy_version VARCHAR(32) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
expose_count BIGINT NOT NULL DEFAULT 0,
click_count BIGINT NOT NULL DEFAULT 0,
complete_count BIGINT NOT NULL DEFAULT 0,
dislike_count BIGINT NOT NULL DEFAULT 0,
ctr NUMERIC(10,6) NOT NULL DEFAULT 0,
complete_rate NUMERIC(10,6) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (stat_date, scene_code, strategy_version, tenant_id)
);
COMMENT ON TABLE recommendation.rc_recommendation_effect_daily IS '推荐效果日统计表';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.stat_date IS '统计日期';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.scene_code IS '推荐场景编码';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.strategy_version IS '策略版本';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.expose_count IS '曝光次数';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.click_count IS '点击次数';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.complete_count IS '完成次数';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.dislike_count IS '不喜欢次数';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.ctr IS '点击率';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.complete_rate IS '完成率';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.created_at IS '创建时间';
COMMENT ON COLUMN recommendation.rc_recommendation_effect_daily.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_user_scene
ON recommendation.rc_recommendation_task(user_id, scene_code, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_task_tenant_status
ON recommendation.rc_recommendation_task(tenant_id, status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_task
ON recommendation.rc_recommendation_item(task_id, rank_no);
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_item_content
ON recommendation.rc_recommendation_item(content_object_type, content_object_id);
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_item_event
ON recommendation.rc_recommendation_feedback(item_id, event_type, event_time DESC);
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_feedback_user_event
ON recommendation.rc_recommendation_feedback(user_id, event_type, event_time DESC);
CREATE INDEX IF NOT EXISTS idx_rc_recommendation_effect_daily_tenant_date
ON recommendation.rc_recommendation_effect_daily(tenant_id, stat_date DESC);
DROP TABLE IF EXISTS recommendation.rc_student_profile_feature CASCADE;
DROP TABLE IF EXISTS recommendation.rc_student_profile_snapshot CASCADE;
CREATE TABLE IF NOT EXISTS recommendation.rc_student_profile_snapshot (
profile_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
profile_version VARCHAR(32) NOT NULL,
profile_date DATE NOT NULL,
feature_source_version VARCHAR(64),
profile_json JSONB NOT NULL DEFAULT '{}'::JSONB,
confidence_score NUMERIC(6,4),
profile_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_rc_student_profile_snapshot_user_version
UNIQUE (user_id, profile_version),
CONSTRAINT chk_rc_student_profile_snapshot_json
CHECK (jsonb_typeof(profile_json) = 'object'),
CONSTRAINT chk_rc_student_profile_snapshot_status
CHECK (profile_status IN ('ACTIVE', 'ARCHIVED')),
CONSTRAINT fk_rc_student_profile_snapshot_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE recommendation.rc_student_profile_snapshot IS '学员画像快照表';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_id IS '画像ID';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.user_id IS '学员ID';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_version IS '画像版本';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_date IS '画像日期';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.feature_source_version IS '特征来源版本';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_json IS '画像JSON';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.confidence_score IS '画像置信度';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.profile_status IS '画像状态';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.created_at IS '创建时间';
COMMENT ON COLUMN recommendation.rc_student_profile_snapshot.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS recommendation.rc_student_profile_feature (
feature_id VARCHAR(64) PRIMARY KEY,
profile_id VARCHAR(64) NOT NULL,
feature_group VARCHAR(32) NOT NULL,
feature_key VARCHAR(64) NOT NULL,
feature_value_num NUMERIC(18,6),
feature_value_text VARCHAR(512),
feature_value_json JSONB,
value_type VARCHAR(16) NOT NULL DEFAULT 'NUM',
source_domain VARCHAR(32) NOT NULL,
source_ref_id VARCHAR(64),
sample_time TIMESTAMP,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_rc_student_profile_feature_value_type
CHECK (value_type IN ('NUM', 'TEXT', 'JSON', 'BOOL')),
CONSTRAINT chk_rc_student_profile_feature_source_domain
CHECK (source_domain IN ('COURSE', 'HOMEWORK', 'GRADING', 'REVIEW', 'RECOMMENDATION', 'ACHIEVEMENT', 'SYSTEM')),
CONSTRAINT chk_rc_student_profile_feature_json
CHECK (feature_value_json IS NULL OR jsonb_typeof(feature_value_json) IN ('object', 'array', 'string', 'number', 'boolean')),
CONSTRAINT fk_rc_student_profile_feature_profile
FOREIGN KEY (profile_id) REFERENCES recommendation.rc_student_profile_snapshot(profile_id) ON DELETE CASCADE
);
COMMENT ON TABLE recommendation.rc_student_profile_feature IS '学员画像特征明细表';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_id IS '特征ID';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.profile_id IS '画像ID';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_group IS '特征组';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_key IS '特征键';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_num IS '数值特征值';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_text IS '文本特征值';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.feature_value_json IS 'JSON特征值';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.value_type IS '值类型';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.source_domain IS '来源域';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.source_ref_id IS '来源对象ID';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.sample_time IS '采样时间';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_student_profile_feature.created_at IS '创建时间';
DROP TABLE IF EXISTS recommendation.rc_learning_loop_event CASCADE;
DROP TABLE IF EXISTS recommendation.rc_learning_loop_case CASCADE;
CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_case (
loop_case_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
wrong_question_id VARCHAR(64) NOT NULL,
review_plan_id VARCHAR(64),
profile_id VARCHAR(64),
recommendation_task_id VARCHAR(64),
recommendation_item_id VARCHAR(64),
feedback_id VARCHAR(64),
user_achievement_id VARCHAR(64),
loop_status VARCHAR(16) NOT NULL DEFAULT 'OPEN',
effect_score NUMERIC(10,4),
opened_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
closed_at TIMESTAMP,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_rc_learning_loop_case_status
CHECK (loop_status IN ('OPEN', 'IN_PROGRESS', 'CLOSED', 'FAILED')),
CONSTRAINT chk_rc_learning_loop_case_closed_at
CHECK (closed_at IS NULL OR closed_at >= opened_at),
CONSTRAINT fk_rc_learning_loop_case_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id),
CONSTRAINT fk_rc_learning_loop_case_wrong_question
FOREIGN KEY (wrong_question_id) REFERENCES question.gd_wrong_question(wrong_question_id),
CONSTRAINT fk_rc_learning_loop_case_review_plan
FOREIGN KEY (review_plan_id) REFERENCES question.gd_review_plan(review_plan_id),
CONSTRAINT fk_rc_learning_loop_case_profile
FOREIGN KEY (profile_id) REFERENCES recommendation.rc_student_profile_snapshot(profile_id),
CONSTRAINT fk_rc_learning_loop_case_recommendation_task
FOREIGN KEY (recommendation_task_id) REFERENCES recommendation.rc_recommendation_task(task_id),
CONSTRAINT fk_rc_learning_loop_case_recommendation_item
FOREIGN KEY (recommendation_item_id) REFERENCES recommendation.rc_recommendation_item(item_id),
CONSTRAINT fk_rc_learning_loop_case_feedback
FOREIGN KEY (feedback_id) REFERENCES recommendation.rc_recommendation_feedback(feedback_id),
CONSTRAINT fk_rc_learning_loop_case_user_achievement
FOREIGN KEY (user_achievement_id) REFERENCES achievement.ac_user_achievement(user_achievement_id)
);
COMMENT ON TABLE recommendation.rc_learning_loop_case IS '学习闭环主表(错题-复习-推荐-成就)';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.loop_case_id IS '闭环案例ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.user_id IS '用户ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.wrong_question_id IS '错题ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.review_plan_id IS '复习计划ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.profile_id IS '画像ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.recommendation_task_id IS '推荐任务ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.recommendation_item_id IS '推荐项ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.feedback_id IS '反馈事件ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.user_achievement_id IS '成就记录ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.loop_status IS '闭环状态';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.effect_score IS '闭环效果分';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.opened_at IS '闭环开启时间';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.closed_at IS '闭环关闭时间';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.created_at IS '创建时间';
COMMENT ON COLUMN recommendation.rc_learning_loop_case.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_event (
loop_event_id VARCHAR(64) PRIMARY KEY,
loop_case_id VARCHAR(64) NOT NULL,
stage_code VARCHAR(32) NOT NULL,
stage_status VARCHAR(16) NOT NULL DEFAULT 'DONE',
event_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
event_score NUMERIC(10,4),
event_detail_json JSONB NOT NULL DEFAULT '{}'::JSONB,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_rc_learning_loop_event_stage_code
CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')),
CONSTRAINT chk_rc_learning_loop_event_stage_status
CHECK (stage_status IN ('PENDING', 'DONE', 'FAILED', 'SKIPPED')),
CONSTRAINT chk_rc_learning_loop_event_detail_json
CHECK (jsonb_typeof(event_detail_json) = 'object'),
CONSTRAINT fk_rc_learning_loop_event_case
FOREIGN KEY (loop_case_id) REFERENCES recommendation.rc_learning_loop_case(loop_case_id) ON DELETE CASCADE
);
COMMENT ON TABLE recommendation.rc_learning_loop_event IS '学习闭环阶段事件表';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.loop_event_id IS '闭环事件ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.loop_case_id IS '闭环案例ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.stage_code IS '阶段编码';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.stage_status IS '阶段状态';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_time IS '事件时间';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_score IS '阶段得分';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.event_detail_json IS '事件详情JSON';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_event.created_at IS '创建时间';
DROP TABLE IF EXISTS recommendation.rc_learning_loop_effect_daily CASCADE;
CREATE TABLE IF NOT EXISTS recommendation.rc_learning_loop_effect_daily (
stat_date DATE NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
opened_case_count BIGINT NOT NULL DEFAULT 0,
closed_case_count BIGINT NOT NULL DEFAULT 0,
achieved_case_count BIGINT NOT NULL DEFAULT 0,
avg_effect_score NUMERIC(10,4) NOT NULL DEFAULT 0,
avg_close_hours NUMERIC(10,4) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (stat_date, tenant_id)
);
COMMENT ON TABLE recommendation.rc_learning_loop_effect_daily IS '学习闭环效果日统计表';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.stat_date IS '统计日期';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.opened_case_count IS '开启案例数';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.closed_case_count IS '关闭案例数';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.achieved_case_count IS '达成成就案例数';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.avg_effect_score IS '平均效果分';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.avg_close_hours IS '平均闭环时长(小时)';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.created_at IS '创建时间';
COMMENT ON COLUMN recommendation.rc_learning_loop_effect_daily.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_user_date
ON recommendation.rc_student_profile_snapshot(user_id, profile_date DESC);
CREATE INDEX IF NOT EXISTS idx_rc_student_profile_snapshot_tenant_version
ON recommendation.rc_student_profile_snapshot(tenant_id, profile_version, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_profile_group
ON recommendation.rc_student_profile_feature(profile_id, feature_group, feature_key);
CREATE INDEX IF NOT EXISTS idx_rc_student_profile_feature_source
ON recommendation.rc_student_profile_feature(source_domain, source_ref_id);
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_user_status
ON recommendation.rc_learning_loop_case(user_id, loop_status, opened_at DESC);
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_wrong_question
ON recommendation.rc_learning_loop_case(wrong_question_id, opened_at DESC);
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_case_tenant_status
ON recommendation.rc_learning_loop_case(tenant_id, loop_status, opened_at DESC);
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_event_case_stage_time
ON recommendation.rc_learning_loop_event(loop_case_id, stage_code, event_time DESC);
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_event_tenant_time
ON recommendation.rc_learning_loop_event(tenant_id, event_time DESC);
CREATE INDEX IF NOT EXISTS idx_rc_learning_loop_effect_daily_tenant_date
ON recommendation.rc_learning_loop_effect_daily(tenant_id, stat_date DESC);
DROP TABLE IF EXISTS recommendation.rc_loop_metric_map CASCADE;
DROP TABLE IF EXISTS recommendation.rc_loop_stage_achievement_map CASCADE;
CREATE TABLE IF NOT EXISTS recommendation.rc_loop_stage_achievement_map (
stage_map_id VARCHAR(64) PRIMARY KEY,
stage_code VARCHAR(32) NOT NULL,
event_code VARCHAR(64) NOT NULL,
template_id VARCHAR(64),
metric_id VARCHAR(64),
award_source VARCHAR(32) NOT NULL DEFAULT 'RULE_ENGINE',
priority INTEGER NOT NULL DEFAULT 100,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
ext_json JSONB NOT NULL DEFAULT '{}'::JSONB,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_rc_loop_stage_achievement_map
UNIQUE (tenant_id, stage_code, event_code, priority),
CONSTRAINT chk_rc_loop_stage_achievement_map_stage_code
CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')),
CONSTRAINT chk_rc_loop_stage_achievement_map_award_source
CHECK (award_source IN ('RULE_ENGINE', 'MANUAL', 'SYSTEM')),
CONSTRAINT chk_rc_loop_stage_achievement_map_ext_json
CHECK (jsonb_typeof(ext_json) = 'object'),
CONSTRAINT fk_rc_loop_stage_achievement_map_event
FOREIGN KEY (event_code) REFERENCES achievement.ac_achievement_event_dict(event_code),
CONSTRAINT fk_rc_loop_stage_achievement_map_template
FOREIGN KEY (template_id) REFERENCES achievement.ac_achievement_rule_template(template_id),
CONSTRAINT fk_rc_loop_stage_achievement_map_metric
FOREIGN KEY (metric_id) REFERENCES achievement.ac_achievement_metric_def(metric_id)
);
COMMENT ON TABLE recommendation.rc_loop_stage_achievement_map IS '闭环阶段到成就事件映射表(通用配置)';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.stage_map_id IS '阶段映射ID';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.stage_code IS '闭环阶段编码';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.event_code IS '成就事件编码';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.template_id IS '成就规则模板ID';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.metric_id IS '成就指标ID';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.award_source IS '发放来源';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.priority IS '优先级';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.enabled IS '是否启用';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.ext_json IS '扩展配置JSON';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.created_at IS '创建时间';
COMMENT ON COLUMN recommendation.rc_loop_stage_achievement_map.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS recommendation.rc_loop_metric_map (
metric_map_id VARCHAR(64) PRIMARY KEY,
stage_code VARCHAR(32) NOT NULL,
metric_id VARCHAR(64) NOT NULL,
source_field VARCHAR(64) NOT NULL,
agg_method VARCHAR(16) NOT NULL DEFAULT 'LATEST',
weight NUMERIC(6,4) NOT NULL DEFAULT 1.0000,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
ext_json JSONB NOT NULL DEFAULT '{}'::JSONB,
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_rc_loop_metric_map
UNIQUE (tenant_id, stage_code, metric_id, source_field),
CONSTRAINT chk_rc_loop_metric_map_stage_code
CHECK (stage_code IN ('WRONG_CAPTURED', 'REVIEW_PLANNED', 'PROFILE_UPDATED', 'RECOMMENDED', 'EXPOSED', 'ENGAGED', 'REVIEW_COMPLETED', 'ACHIEVED')),
CONSTRAINT chk_rc_loop_metric_map_agg_method
CHECK (agg_method IN ('LATEST', 'SUM', 'AVG', 'MAX', 'MIN', 'COUNT')),
CONSTRAINT chk_rc_loop_metric_map_weight
CHECK (weight > 0 AND weight <= 1),
CONSTRAINT chk_rc_loop_metric_map_ext_json
CHECK (jsonb_typeof(ext_json) = 'object'),
CONSTRAINT fk_rc_loop_metric_map_metric
FOREIGN KEY (metric_id) REFERENCES achievement.ac_achievement_metric_def(metric_id)
);
COMMENT ON TABLE recommendation.rc_loop_metric_map IS '闭环指标映射表(通用配置)';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.metric_map_id IS '指标映射ID';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.stage_code IS '闭环阶段编码';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.metric_id IS '成就指标ID';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.source_field IS '来源字段名';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.agg_method IS '聚合方法';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.weight IS '权重';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.enabled IS '是否启用';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.ext_json IS '扩展配置JSON';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.tenant_id IS '租户ID';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.created_at IS '创建时间';
COMMENT ON COLUMN recommendation.rc_loop_metric_map.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_tenant_stage
ON recommendation.rc_loop_stage_achievement_map(tenant_id, stage_code, enabled, priority);
CREATE INDEX IF NOT EXISTS idx_rc_loop_stage_achievement_map_event
ON recommendation.rc_loop_stage_achievement_map(event_code, tenant_id, enabled);
CREATE INDEX IF NOT EXISTS idx_rc_loop_metric_map_tenant_stage
ON recommendation.rc_loop_metric_map(tenant_id, stage_code, enabled);

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
-- 学校租户表
DROP TABLE IF EXISTS upms.tb_sys_tenant CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_tenant (
tenant_id VARCHAR(64) PRIMARY KEY,
parent_tenant_id VARCHAR(64),
@@ -19,6 +20,7 @@ COMMENT ON COLUMN upms.tb_sys_tenant.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_sys_tenant.status IS '租户状态';
COMMENT ON COLUMN upms.tb_sys_tenant.created_at IS '创建时间';
DROP TABLE IF EXISTS upms.tb_sys_dept CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_dept (
dept_id VARCHAR(64) PRIMARY KEY,
parent_dept_id VARCHAR(64),
@@ -41,6 +43,10 @@ COMMENT ON COLUMN upms.tb_sys_dept.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_sys_dept.dept_path IS '部门路径';
COMMENT ON COLUMN upms.tb_sys_dept.created_at IS '创建时间';
DROP TABLE IF EXISTS upms.tb_school_class_course_rel CASCADE;
DROP TABLE IF EXISTS upms.tb_school_class_member CASCADE;
DROP TABLE IF EXISTS upms.tb_school_class CASCADE;
DROP TABLE IF EXISTS upms.tb_sys_user CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_user (
user_id VARCHAR(64) PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
@@ -66,7 +72,132 @@ COMMENT ON COLUMN upms.tb_sys_user.dept_id IS '部门ID';
COMMENT ON COLUMN upms.tb_sys_user.dept_path IS '部门路径';
COMMENT ON COLUMN upms.tb_sys_user.status IS '用户状态';
COMMENT ON COLUMN upms.tb_sys_user.created_at IS '创建时间';
CREATE TABLE IF NOT EXISTS upms.tb_school_class (
class_id VARCHAR(64) PRIMARY KEY,
tenant_id VARCHAR(64) NOT NULL,
dept_id VARCHAR(64) NOT NULL,
class_code VARCHAR(64),
class_name VARCHAR(128) NOT NULL,
grade_code VARCHAR(32),
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
adcode VARCHAR(12),
tenant_path VARCHAR(255),
dept_path VARCHAR(255),
created_by VARCHAR(64),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_school_class_status
CHECK (status IN ('ACTIVE', 'INACTIVE', 'ARCHIVED')),
CONSTRAINT fk_school_class_dept
FOREIGN KEY (dept_id) REFERENCES upms.tb_sys_dept(dept_id),
CONSTRAINT fk_school_class_created_by
FOREIGN KEY (created_by) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE upms.tb_school_class IS '班级主表';
COMMENT ON COLUMN upms.tb_school_class.class_id IS '班级ID';
COMMENT ON COLUMN upms.tb_school_class.tenant_id IS '租户ID';
COMMENT ON COLUMN upms.tb_school_class.dept_id IS '所属部门ID';
COMMENT ON COLUMN upms.tb_school_class.class_code IS '班级编码';
COMMENT ON COLUMN upms.tb_school_class.class_name IS '班级名称';
COMMENT ON COLUMN upms.tb_school_class.grade_code IS '年级编码';
COMMENT ON COLUMN upms.tb_school_class.status IS '班级状态';
COMMENT ON COLUMN upms.tb_school_class.adcode IS '行政区划编码';
COMMENT ON COLUMN upms.tb_school_class.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_school_class.dept_path IS '部门路径';
COMMENT ON COLUMN upms.tb_school_class.created_by IS '创建人';
COMMENT ON COLUMN upms.tb_school_class.created_at IS '创建时间';
COMMENT ON COLUMN upms.tb_school_class.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS upms.tb_school_class_member (
class_id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
member_role VARCHAR(16) NOT NULL DEFAULT 'STUDENT',
member_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
tenant_id VARCHAR(64) NOT NULL,
joined_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
left_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (class_id, user_id),
CONSTRAINT chk_school_class_member_role
CHECK (member_role IN ('STUDENT', 'HEAD_TEACHER', 'TEACHER', 'ASSISTANT')),
CONSTRAINT chk_school_class_member_status
CHECK (member_status IN ('ACTIVE', 'LEFT', 'SUSPENDED')),
CONSTRAINT chk_school_class_member_left_at
CHECK (left_at IS NULL OR left_at >= joined_at),
CONSTRAINT fk_school_class_member_class
FOREIGN KEY (class_id) REFERENCES upms.tb_school_class(class_id) ON DELETE CASCADE,
CONSTRAINT fk_school_class_member_user
FOREIGN KEY (user_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE upms.tb_school_class_member IS '班级成员表';
COMMENT ON COLUMN upms.tb_school_class_member.class_id IS '班级ID';
COMMENT ON COLUMN upms.tb_school_class_member.user_id IS '成员用户ID';
COMMENT ON COLUMN upms.tb_school_class_member.member_role IS '成员角色';
COMMENT ON COLUMN upms.tb_school_class_member.member_status IS '成员状态';
COMMENT ON COLUMN upms.tb_school_class_member.tenant_id IS '租户ID';
COMMENT ON COLUMN upms.tb_school_class_member.joined_at IS '加入时间';
COMMENT ON COLUMN upms.tb_school_class_member.left_at IS '离开时间';
COMMENT ON COLUMN upms.tb_school_class_member.created_at IS '创建时间';
COMMENT ON COLUMN upms.tb_school_class_member.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS upms.tb_school_class_course_rel (
class_id VARCHAR(64) NOT NULL,
course_id VARCHAR(64) NOT NULL,
relation_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
tenant_id VARCHAR(64) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (class_id, course_id),
CONSTRAINT chk_school_class_course_rel_status
CHECK (relation_status IN ('ACTIVE', 'INACTIVE')),
CONSTRAINT fk_school_class_course_rel_class
FOREIGN KEY (class_id) REFERENCES upms.tb_school_class(class_id) ON DELETE CASCADE
);
COMMENT ON TABLE upms.tb_school_class_course_rel IS '班级课程关联表';
COMMENT ON COLUMN upms.tb_school_class_course_rel.class_id IS '班级ID';
COMMENT ON COLUMN upms.tb_school_class_course_rel.course_id IS '课程ID';
COMMENT ON COLUMN upms.tb_school_class_course_rel.relation_status IS '关联状态';
COMMENT ON COLUMN upms.tb_school_class_course_rel.tenant_id IS '租户ID';
COMMENT ON COLUMN upms.tb_school_class_course_rel.created_at IS '创建时间';
COMMENT ON COLUMN upms.tb_school_class_course_rel.updated_at IS '更新时间';
DROP TABLE IF EXISTS upms.tb_sys_file CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_file (
file_id VARCHAR(64) PRIMARY KEY,
media_type VARCHAR(32) NOT NULL,
object_key VARCHAR(512) NOT NULL,
file_name VARCHAR(256),
mime_type VARCHAR(128),
file_size BIGINT,
file_hash VARCHAR(128),
duration_ms INTEGER,
uploaded_by VARCHAR(64),
adcode VARCHAR(12),
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_sys_file_media_type
CHECK (media_type IN ('IMAGE', 'AUDIO', 'VIDEO', 'DOCUMENT', 'OTHER')),
CONSTRAINT fk_sys_file_uploaded_by
FOREIGN KEY (uploaded_by) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE upms.tb_sys_file IS '系统文件资源表';
COMMENT ON COLUMN upms.tb_sys_file.file_id IS '文件ID';
COMMENT ON COLUMN upms.tb_sys_file.media_type IS '媒体类型';
COMMENT ON COLUMN upms.tb_sys_file.object_key IS '对象存储Key';
COMMENT ON COLUMN upms.tb_sys_file.file_name IS '文件名';
COMMENT ON COLUMN upms.tb_sys_file.mime_type IS 'MIME类型';
COMMENT ON COLUMN upms.tb_sys_file.file_size IS '文件大小(字节)';
COMMENT ON COLUMN upms.tb_sys_file.file_hash IS '文件哈希';
COMMENT ON COLUMN upms.tb_sys_file.duration_ms IS '音频/视频时长(毫秒)';
COMMENT ON COLUMN upms.tb_sys_file.uploaded_by IS '上传人ID';
COMMENT ON COLUMN upms.tb_sys_file.adcode IS '行政区划编码';
COMMENT ON COLUMN upms.tb_sys_file.tenant_id IS '租户ID';
COMMENT ON COLUMN upms.tb_sys_file.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_sys_file.created_at IS '创建时间';
DROP TABLE IF EXISTS upms.tb_sys_role CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_role (
role_id VARCHAR(64) PRIMARY KEY,
role_code VARCHAR(64) UNIQUE NOT NULL,
@@ -89,28 +220,8 @@ COMMENT ON COLUMN upms.tb_sys_role.dept_id IS '部门ID';
COMMENT ON COLUMN upms.tb_sys_role.dept_path IS '部门路径';
COMMENT ON COLUMN upms.tb_sys_role.created_at IS '创建时间';
CREATE TABLE IF NOT EXISTS upms.tb_sys_permission (
permission_id VARCHAR(64) PRIMARY KEY,
permission_code VARCHAR(128) UNIQUE NOT NULL,
permission_name VARCHAR(128) NOT NULL,
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE upms.tb_sys_permission IS '权限表';
COMMENT ON COLUMN upms.tb_sys_permission.permission_id IS '权限ID';
COMMENT ON COLUMN upms.tb_sys_permission.permission_code IS '权限编码';
COMMENT ON COLUMN upms.tb_sys_permission.permission_name IS '权限名称';
COMMENT ON COLUMN upms.tb_sys_permission.adcode IS '行政区划编码';
COMMENT ON COLUMN upms.tb_sys_permission.tenant_id IS '租户ID';
COMMENT ON COLUMN upms.tb_sys_permission.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_sys_permission.dept_id IS '部门ID';
COMMENT ON COLUMN upms.tb_sys_permission.dept_path IS '部门路径';
COMMENT ON COLUMN upms.tb_sys_permission.created_at IS '创建时间';
DROP TABLE IF EXISTS upms.tb_sys_menu CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_menu (
route_id VARCHAR(64) PRIMARY KEY,
parent_route_id VARCHAR(64),
@@ -146,8 +257,181 @@ COMMENT ON COLUMN upms.tb_sys_menu.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_sys_menu.dept_id IS '部门ID';
COMMENT ON COLUMN upms.tb_sys_menu.dept_path IS '部门路径';
COMMENT ON COLUMN upms.tb_sys_menu.created_at IS '创建时间';
DROP TABLE IF EXISTS upms.tb_sys_role_menu CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_role_menu (
role_id VARCHAR(64) NOT NULL,
route_id VARCHAR(64) NOT NULL,
adcode VARCHAR(12) NOT NULL,
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255) NOT NULL,
dept_id VARCHAR(64),
dept_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (role_id, route_id)
);
COMMENT ON TABLE upms.tb_sys_role_menu IS '角色菜单授权表';
COMMENT ON COLUMN upms.tb_sys_role_menu.role_id IS '角色ID';
COMMENT ON COLUMN upms.tb_sys_role_menu.route_id IS '路由ID';
COMMENT ON COLUMN upms.tb_sys_role_menu.adcode IS '行政区划编码';
COMMENT ON COLUMN upms.tb_sys_role_menu.tenant_id IS '租户ID';
COMMENT ON COLUMN upms.tb_sys_role_menu.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_sys_role_menu.dept_id IS '部门ID';
COMMENT ON COLUMN upms.tb_sys_role_menu.dept_path IS '部门路径';
COMMENT ON COLUMN upms.tb_sys_role_menu.created_at IS '创建时间';
DROP TABLE IF EXISTS upms.tb_sys_area CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_area (
id BIGINT PRIMARY KEY,
pid BIGINT NOT NULL,
name VARCHAR(128) NOT NULL,
letter VARCHAR(32),
adcode BIGINT NOT NULL,
location VARCHAR(255),
area_sort INTEGER,
area_status VARCHAR(8) NOT NULL DEFAULT '1',
area_type VARCHAR(8) NOT NULL DEFAULT '2',
hot VARCHAR(8) NOT NULL DEFAULT '0',
city_code VARCHAR(16),
create_by VARCHAR(64),
create_time TIMESTAMP,
update_by VARCHAR(64),
update_time TIMESTAMP,
del_flag VARCHAR(8) NOT NULL DEFAULT '0'
);
COMMENT ON TABLE upms.tb_sys_area IS '行政区划表';
COMMENT ON COLUMN upms.tb_sys_area.id IS '主键ID';
COMMENT ON COLUMN upms.tb_sys_area.pid IS '父级行政区划编码';
COMMENT ON COLUMN upms.tb_sys_area.name IS '名称';
COMMENT ON COLUMN upms.tb_sys_area.letter IS '首字母';
COMMENT ON COLUMN upms.tb_sys_area.adcode IS '行政区划编码';
COMMENT ON COLUMN upms.tb_sys_area.location IS '经纬度';
COMMENT ON COLUMN upms.tb_sys_area.area_sort IS '排序';
COMMENT ON COLUMN upms.tb_sys_area.area_status IS '状态';
COMMENT ON COLUMN upms.tb_sys_area.area_type IS '层级类型';
COMMENT ON COLUMN upms.tb_sys_area.hot IS '是否热门';
COMMENT ON COLUMN upms.tb_sys_area.city_code IS '城市区号';
COMMENT ON COLUMN upms.tb_sys_area.create_by IS '创建人';
COMMENT ON COLUMN upms.tb_sys_area.create_time IS '创建时间';
COMMENT ON COLUMN upms.tb_sys_area.update_by IS '更新人';
COMMENT ON COLUMN upms.tb_sys_area.update_time IS '更新时间';
COMMENT ON COLUMN upms.tb_sys_area.del_flag IS '删除标记';
DROP TABLE IF EXISTS upms.tb_sys_message_recipient CASCADE;
DROP TABLE IF EXISTS upms.tb_sys_message CASCADE;
CREATE TABLE IF NOT EXISTS upms.tb_sys_message (
message_id VARCHAR(64) PRIMARY KEY,
message_type VARCHAR(32) NOT NULL DEFAULT 'INFO',
biz_type VARCHAR(64) NOT NULL,
title VARCHAR(256) NOT NULL,
content TEXT NOT NULL,
content_object_type VARCHAR(64) NOT NULL,
content_object_id VARCHAR(64) NOT NULL,
web_jump_url VARCHAR(512) NOT NULL,
send_channel VARCHAR(16) NOT NULL DEFAULT 'INBOX',
sender_user_id VARCHAR(64),
adcode VARCHAR(12),
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255),
message_status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
send_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
expire_at TIMESTAMP,
ext_json JSONB NOT NULL DEFAULT '{}'::JSONB,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_sys_message_type
CHECK (message_type IN ('TODO', 'INFO', 'ALERT', 'SYSTEM')),
CONSTRAINT chk_sys_message_channel
CHECK (send_channel IN ('INBOX')),
CONSTRAINT chk_sys_message_status
CHECK (message_status IN ('ACTIVE', 'CANCELLED', 'EXPIRED')),
CONSTRAINT chk_sys_message_url
CHECK (web_jump_url LIKE '/%'),
CONSTRAINT chk_sys_message_expire
CHECK (expire_at IS NULL OR expire_at >= send_at),
CONSTRAINT chk_sys_message_ext_json
CHECK (jsonb_typeof(ext_json) = 'object'),
CONSTRAINT fk_sys_message_sender
FOREIGN KEY (sender_user_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE upms.tb_sys_message IS '站内信消息主表';
COMMENT ON COLUMN upms.tb_sys_message.message_id IS '消息ID';
COMMENT ON COLUMN upms.tb_sys_message.message_type IS '消息类型TODO/INFO/ALERT/SYSTEM';
COMMENT ON COLUMN upms.tb_sys_message.biz_type IS '业务类型(如 SUBJECTIVE_REVIEW';
COMMENT ON COLUMN upms.tb_sys_message.title IS '消息标题';
COMMENT ON COLUMN upms.tb_sys_message.content IS '消息正文';
COMMENT ON COLUMN upms.tb_sys_message.content_object_type IS '内容对象类型(如 GRADING_TASK/TENANT';
COMMENT ON COLUMN upms.tb_sys_message.content_object_id IS '内容对象ID用于业务定位';
COMMENT ON COLUMN upms.tb_sys_message.web_jump_url IS 'Web跳转URL前端点击消息后跳转';
COMMENT ON COLUMN upms.tb_sys_message.send_channel IS '发送通道当前仅INBOX';
COMMENT ON COLUMN upms.tb_sys_message.sender_user_id IS '发送人ID系统消息可为空';
COMMENT ON COLUMN upms.tb_sys_message.adcode IS '行政区划编码';
COMMENT ON COLUMN upms.tb_sys_message.tenant_id IS '租户ID';
COMMENT ON COLUMN upms.tb_sys_message.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_sys_message.message_status IS '消息状态ACTIVE/CANCELLED/EXPIRED';
COMMENT ON COLUMN upms.tb_sys_message.send_at IS '发送时间';
COMMENT ON COLUMN upms.tb_sys_message.expire_at IS '过期时间';
COMMENT ON COLUMN upms.tb_sys_message.ext_json IS '扩展字段JSON';
COMMENT ON COLUMN upms.tb_sys_message.created_at IS '创建时间';
COMMENT ON COLUMN upms.tb_sys_message.updated_at IS '更新时间';
CREATE TABLE IF NOT EXISTS upms.tb_sys_message_recipient (
message_id VARCHAR(64) NOT NULL,
recipient_user_id VARCHAR(64) NOT NULL,
delivery_status VARCHAR(16) NOT NULL DEFAULT 'DELIVERED',
read_status VARCHAR(16) NOT NULL DEFAULT 'UNREAD',
read_at TIMESTAMP,
clicked_at TIMESTAMP,
read_source VARCHAR(16),
tenant_id VARCHAR(64) NOT NULL,
tenant_path VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (message_id, recipient_user_id),
CONSTRAINT chk_sys_message_recipient_delivery
CHECK (delivery_status IN ('DELIVERED', 'FAILED', 'RECALLED')),
CONSTRAINT chk_sys_message_recipient_read
CHECK (read_status IN ('UNREAD', 'READ', 'ARCHIVED')),
CONSTRAINT chk_sys_message_recipient_read_source
CHECK (read_source IS NULL OR read_source IN ('WEB', 'APP', 'MINI_PROGRAM')),
CONSTRAINT chk_sys_message_recipient_read_at
CHECK (
(read_status = 'UNREAD' AND read_at IS NULL)
OR (read_status IN ('READ', 'ARCHIVED') AND read_at IS NOT NULL)
),
CONSTRAINT chk_sys_message_recipient_click_at
CHECK (clicked_at IS NULL OR read_at IS NULL OR clicked_at >= read_at),
CONSTRAINT fk_sys_message_recipient_message
FOREIGN KEY (message_id) REFERENCES upms.tb_sys_message(message_id) ON DELETE CASCADE,
CONSTRAINT fk_sys_message_recipient_user
FOREIGN KEY (recipient_user_id) REFERENCES upms.tb_sys_user(user_id)
);
COMMENT ON TABLE upms.tb_sys_message_recipient IS '站内信收件人状态表';
COMMENT ON COLUMN upms.tb_sys_message_recipient.message_id IS '消息ID';
COMMENT ON COLUMN upms.tb_sys_message_recipient.recipient_user_id IS '收件人用户ID';
COMMENT ON COLUMN upms.tb_sys_message_recipient.delivery_status IS '投递状态DELIVERED/FAILED/RECALLED';
COMMENT ON COLUMN upms.tb_sys_message_recipient.read_status IS '读取状态UNREAD/READ/ARCHIVED';
COMMENT ON COLUMN upms.tb_sys_message_recipient.read_at IS '读取时间';
COMMENT ON COLUMN upms.tb_sys_message_recipient.clicked_at IS '点击跳转时间';
COMMENT ON COLUMN upms.tb_sys_message_recipient.read_source IS '读取来源WEB/APP/MINI_PROGRAM';
COMMENT ON COLUMN upms.tb_sys_message_recipient.tenant_id IS '租户ID';
COMMENT ON COLUMN upms.tb_sys_message_recipient.tenant_path IS '租户路径';
COMMENT ON COLUMN upms.tb_sys_message_recipient.created_at IS '创建时间';
COMMENT ON COLUMN upms.tb_sys_message_recipient.updated_at IS '更新时间';
CREATE INDEX IF NOT EXISTS idx_sys_tenant_adcode ON upms.tb_sys_tenant(adcode);
CREATE INDEX IF NOT EXISTS idx_dept_tenant ON upms.tb_sys_dept(tenant_id, dept_path);
CREATE INDEX IF NOT EXISTS idx_user_tenant ON upms.tb_sys_user(tenant_id, dept_id);
CREATE INDEX IF NOT EXISTS idx_school_class_tenant_dept ON upms.tb_school_class(tenant_id, dept_id, grade_code);
CREATE INDEX IF NOT EXISTS idx_school_class_member_tenant_user ON upms.tb_school_class_member(tenant_id, user_id, member_status);
CREATE INDEX IF NOT EXISTS idx_school_class_course_rel_tenant_course ON upms.tb_school_class_course_rel(tenant_id, course_id, relation_status);
CREATE INDEX IF NOT EXISTS idx_sys_file_tenant_media ON upms.tb_sys_file(tenant_id, media_type, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_sys_file_uploaded_by ON upms.tb_sys_file(uploaded_by, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_route_tenant ON upms.tb_sys_menu(tenant_id, route_path);
CREATE INDEX IF NOT EXISTS idx_role_menu_tenant ON upms.tb_sys_role_menu(tenant_id, role_id);
CREATE INDEX IF NOT EXISTS idx_sys_area_pid ON upms.tb_sys_area(pid);
CREATE INDEX IF NOT EXISTS idx_sys_area_adcode ON upms.tb_sys_area(adcode);
CREATE INDEX IF NOT EXISTS idx_sys_message_tenant_biz ON upms.tb_sys_message(tenant_id, biz_type, send_at DESC);
CREATE INDEX IF NOT EXISTS idx_sys_message_object ON upms.tb_sys_message(content_object_type, content_object_id);
CREATE INDEX IF NOT EXISTS idx_sys_message_status_send_at ON upms.tb_sys_message(message_status, send_at DESC);
CREATE INDEX IF NOT EXISTS idx_sys_message_recipient_user_status ON upms.tb_sys_message_recipient(recipient_user_id, read_status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_sys_message_recipient_tenant_user ON upms.tb_sys_message_recipient(tenant_id, recipient_user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_sys_message_recipient_unread ON upms.tb_sys_message_recipient(recipient_user_id, created_at DESC) WHERE read_status = 'UNREAD';

View File

@@ -25,12 +25,6 @@ INSERT INTO upms.tb_sys_role (
('ROLE-ORG-ADMIN', 'ORG_ADMIN', '机构管理员', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/')
ON CONFLICT (role_id) DO NOTHING;
INSERT INTO upms.tb_sys_permission (
permission_id, permission_code, permission_name, adcode, tenant_id, tenant_path, dept_id, dept_path
) VALUES
('PERM-DASHBOARD', 'dashboard:view', '查看控制台', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'),
('PERM-TENANT', 'tenant:view', '查看租户组织', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/')
ON CONFLICT (permission_id) DO NOTHING;
INSERT INTO upms.tb_sys_menu (
route_id, parent_route_id, route_path, route_name, component_key, layout_type, title, icon, permission_code,
@@ -41,3 +35,84 @@ INSERT INTO upms.tb_sys_menu (
('ROUTE-TENANT', NULL, '/tenant', 'tenant-management', 'tenant', 'SIDEBAR', '租户组织', 'building-2', 'tenant:view', FALSE,
'330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/')
ON CONFLICT (route_id) DO NOTHING;
INSERT INTO upms.tb_sys_role_menu (
role_id, route_id, adcode, tenant_id, tenant_path, dept_id, dept_path
) VALUES
('ROLE-ORG-ADMIN', 'ROUTE-DASHBOARD', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/'),
('ROLE-ORG-ADMIN', 'ROUTE-TENANT', '330100', 'SCH-HQ', '/SCH-HQ/', 'DEPT-HQ-ADMIN', '/DEPT-HQ/DEPT-HQ-ADMIN/')
ON CONFLICT (role_id, route_id) DO NOTHING;
INSERT INTO upms.tb_sys_message (
message_id, message_type, biz_type, title, content, content_object_type, content_object_id, web_jump_url,
send_channel, sender_user_id, adcode, tenant_id, tenant_path, message_status, send_at, ext_json
) VALUES
(
'MSG-20260415001',
'TODO',
'SUBJECTIVE_REVIEW',
'有 1 份主观题待审阅',
'高一数学《函数综合训练》有主观题待你审阅,请尽快处理。',
'GRADING_TASK',
'GD-TASK-20260415001',
'/grading/review?gradingTaskId=GD-TASK-20260415001&messageId=MSG-20260415001',
'INBOX',
NULL,
'330100',
'SCH-HQ',
'/SCH-HQ/',
'ACTIVE',
CURRENT_TIMESTAMP,
'{"priority":"high","source":"grading"}'::JSONB
),
(
'MSG-20260415002',
'INFO',
'TENANT_NOTICE',
'杭州分校租户信息有更新',
'杭州分校基础资料已更新,可进入租户组织页查看详情。',
'TENANT',
'SCH-ZJ-HZ-01',
'/tenant?tenantId=SCH-ZJ-HZ-01&messageId=MSG-20260415002',
'INBOX',
NULL,
'330100',
'SCH-HQ',
'/SCH-HQ/',
'ACTIVE',
CURRENT_TIMESTAMP,
'{"priority":"normal","source":"upms"}'::JSONB
)
ON CONFLICT (message_id) DO NOTHING;
INSERT INTO upms.tb_sys_message_recipient (
message_id, recipient_user_id, delivery_status, read_status, read_at, clicked_at, read_source,
tenant_id, tenant_path, created_at, updated_at
) VALUES
(
'MSG-20260415001',
'U10001',
'DELIVERED',
'UNREAD',
NULL,
NULL,
NULL,
'SCH-HQ',
'/SCH-HQ/',
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
),
(
'MSG-20260415002',
'U10001',
'DELIVERED',
'READ',
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP,
'WEB',
'SCH-HQ',
'/SCH-HQ/',
CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP
)
ON CONFLICT (message_id, recipient_user_id) DO NOTHING;