基于Python的高校学业预警系统:从规则引擎到落地实践
简介这是一套面向高校毕业设计、课程设计与毕业论文场景的Python学业预警系统完整源码包适合具备Python与Django基础、需要完成教育管理类项目的学生参考。系统围绕学生成绩、出勤与作业等学业数据构建数据收集、清洗分析、机器学习预警模型与可视化后台可帮助教师和辅导员提前识别学业风险并触发干预。压缩包共317个文件约4.13MB以27个py源码、45个pyc编译文件、33个js脚本、24个css样式、14个html页面及1个sql数据库文件为主另含gif、png、jpg等界面素材与woff2、ttf字体资源覆盖后端逻辑、前端页面与数据存储。已有213人学习下载。读者可据此掌握Django项目结构、Scikit-Learn模型训练流程与预警机制实现思路并直接用于论文撰写与答辩演示。1. 从一份成绩单到一张预警名单高校学业预警系统到底在算什么每学期期末教务老师手里都会多出一份让辅导员头疼的名单挂科两门以上的、绩点跌破 1.5 的、连续两学期学分修不满的。过去这份名单靠人工翻 Excel、对培养方案、逐个核对学分一个学院几百号人两天都理不清。基于 Python 的高校学生学业预警系统要解决的就是把这件事从「事后统计」变成「事前拦截」——在学生还只是出现挂科苗头、学分进度落后、单科成绩断崖下滑的时候系统就自动把风险等级算出来推给辅导员和班主任。这套系统适合谁一类是高校信息化岗、教务处的技术老师手里有成绩库但缺一套能跑起来的预警逻辑另一类是计算机专业做课程设计或毕业设计的学生想找一个数据真实、逻辑完整、能写进简历的项目。它不复杂核心就是 Python 做数据处理和规则引擎配上 MySQL 存成绩、Flask 或 Django 出个可视化界面。但真正决定它好不好用的不是界面多漂亮而是预警规则怎么定、数据怎么清洗、阈值怎么调。下面我按自己落地过的思路把这条路走一遍。2. 预警规则怎么定从学分绩点到行为特征的建模思路2.1 三类预警指标结果型、过程型、趋势型很多人一上来就写「挂科两门就预警」这是最粗糙的结果型规则。真正能提前拦截的是把指标分成三层。结果型看已经发生的事实比如累计挂科门数、平均绩点、已修学分与培养方案要求的差值过程型看当前状态比如本学期已修学分占比、重修通过率、单科成绩在班级的百分位趋势型看变化比如绩点环比下降幅度、连续两学期排名下滑名次。我一般会把这三类指标做成一张宽表每个学生一行字段包括student_id、gpa_current、gpa_prev、fail_count、credit_gap、rank_drop等。规则引擎不直接读原始成绩表而是读这张宽表好处是规则调整时不用反复写复杂 SQL改 Python 里的阈值就行。提示趋势型指标最容易出效果但也最容易误报。一个学生从 3.8 掉到 3.5 可能只是选了几门硬课不一定是学业危机。所以趋势型规则一定要配合绝对阈值使用比如「绩点下降超过 0.5 且当前绩点低于 2.0」才触发。2.2 用 Python 做规则引擎阈值表 权重打分规则引擎有两种写法一种是硬编码 if-else改一个阈值要翻代码另一种是配置化把规则写成表Python 读表执行。我推荐后者因为教务处的老师经常要调阈值你不可能每次都帮他们改代码。先建一张warning_rules表字段包括rule_id、rule_name、metric、operator、threshold、level、weight。比如rule_idrule_namemetricoperatorthresholdlevelweight1累计挂科超限fail_count2高302绩点过低gpa_current1.5高303学分进度落后credit_gap10中204绩点大幅下滑gpa_drop0.5中205单科班级垫底rank_percent0.1低10Python 侧用一个函数遍历规则表对每个学生逐条判断命中就累加权重最后按总分划分预警等级80 分以上红色预警50 到 80 黄色预警50 以下蓝色关注。import pandas as pd def evaluate_student(student_row, rules_df): student_row: 单个学生的指标字典 rules_df: 规则表 DataFrame 返回: (总分, 命中规则列表) total_score 0 hit_rules [] for _, rule in rules_df.iterrows(): metric_value student_row.get(rule[metric]) if metric_value is None: continue # 根据操作符判断是否命中 op rule[operator] threshold rule[threshold] hit False if op : hit metric_value threshold elif op : hit metric_value threshold elif op : hit metric_value threshold elif op : hit metric_value threshold if hit: total_score rule[weight] hit_rules.append(rule[rule_name]) return total_score, hit_rules这段代码的关键在于student_row是一个字典字段名和规则表里的metric一一对应。实际跑的时候先用 pandas 从数据库读成绩聚合出每个学生的指标宽表再逐行调用evaluate_student。参数说明rules_df从数据库加载每次预警任务开始前刷新一次保证阈值改动即时生效weight字段决定该规则对总分的影响高权重规则通常对应硬性学业危机。2.3 数据清洗成绩表里的四种脏数据真实成绩库比想象中脏。我遇到过四种典型问题一是重修成绩和正考成绩混在一起同一个学生同一门课两条记录二是缓考、缺考标记为特殊字符直接算平均分会报错三是学分字段有的是数字有的是文本四是转专业学生的培养方案和原方案不一致学分要求对不上。处理方式重修取最高分缓考缺考先剔除再单独标记学分字段统一pd.to_numeric(errorscoerce)转专业学生按当前专业方案重新计算credit_gap。这些清洗步骤建议写成独立的 Python 脚本每次预警前跑一遍输出一张干净的student_metrics表规则引擎只读这张表。3. 用 Python MySQL 把预警流程跑通从建表到出名单3.1 数据库表设计五张核心表一套能跑的预警系统最少需要五张表student学生基本信息、course课程信息含学分、score成绩记录、training_plan培养方案每个专业要求的总学分和必修课、warning_result预警结果。成绩表是核心字段包括student_id、course_id、score、exam_type正考/重修、semester。建表时注意两点一是score表加联合索引(student_id, course_id, semester)否则几千学生几万条成绩聚合查询会慢到怀疑人生二是warning_result表存每次预警的批次号、总分、命中规则、等级方便回溯。下面是一个简化的建表 SQLCREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20) NOT NULL, course_id VARCHAR(20) NOT NULL, score DECIMAL(5,2), exam_type VARCHAR(10) DEFAULT 正考, semester VARCHAR(20), INDEX idx_student_course (student_id, course_id, semester) ); CREATE TABLE warning_result ( id INT PRIMARY KEY AUTO_INCREMENT, batch_no VARCHAR(30), student_id VARCHAR(20), total_score INT, hit_rules TEXT, warning_level VARCHAR(10), created_at DATETIME DEFAULT CURRENT_TIMESTAMP );score表的score字段用DECIMAL(5,2)而不是FLOAT避免浮点误差导致绩点计算偏差。warning_result的hit_rules存 JSON 字符串或逗号分隔的规则名方便辅导员看到具体是哪条规则触发的。3.2 指标计算脚本从成绩表到宽表有了表结构下一步是用 Python 算出每个学生的指标。核心逻辑是先按学生分组算平均绩点、挂科数再关联培养方案算学分缺口最后算趋势指标。下面这段代码展示从 MySQL 读数据到生成宽表的过程import pandas as pd from sqlalchemy import create_engine engine create_engine(mysqlpymysql://user:passlocalhost:3306/warning_db) # 读成绩和课程学分 score_df pd.read_sql(SELECT * FROM score, engine) course_df pd.read_sql(SELECT course_id, credit FROM course, engine) # 合并学分 score_df score_df.merge(course_df, oncourse_id, howleft) # 重修取最高分 score_df[score] pd.to_numeric(score_df[score], errorscoerce) score_df score_df.sort_values(score, ascendingFalse) score_df score_df.drop_duplicates(subset[student_id, course_id], keepfirst) # 计算每个学生的挂科数和平均分 def calc_metrics(group): fail_count (group[score] 60).sum() avg_score group[score].mean() # 绩点简化算法60分以下060以上(score-50)/10 gpa group[score].apply(lambda x: 0 if x 60 else (x - 50) / 10).mean() return pd.Series({fail_count: fail_count, avg_score: avg_score, gpa_current: gpa}) metrics score_df.groupby(student_id).apply(calc_metrics).reset_index()这段代码里drop_duplicates配合排序实现「重修取最高分」calc_metrics函数按学生分组计算三个核心指标。参数说明绩点算法各校不同有的用五分制有的用四分制这里用的是常见的「60 分绩点 1.0每多 1 分加 0.1」的线性算法实际落地时要按学校教务规定替换。errorscoerce把无法转换的成绩变成 NaN后续sum和mean会自动忽略避免脏数据导致脚本崩溃。3.3 生成预警名单并写入结果表宽表算好后遍历每个学生调用规则引擎把结果写回warning_result。这一步建议加一个批次号比如20250115_001方便区分不同学期或不同次预警。写入时用to_sql批量插入比逐条INSERT快一个数量级。rules_df pd.read_sql(SELECT * FROM warning_rules, engine) batch_no 20250115_001 results [] for _, stu in metrics.iterrows(): score, hit evaluate_student(stu.to_dict(), rules_df) if score 80: level 红色 elif score 50: level 黄色 elif score 0: level 蓝色 else: continue # 无风险不写入 results.append({ batch_no: batch_no, student_id: stu[student_id], total_score: score, hit_rules: ,.join(hit), warning_level: level }) result_df pd.DataFrame(results) result_df.to_sql(warning_result, engine, if_existsappend, indexFalse)逻辑说明只有总分大于 0 的学生才写入结果表避免名单里全是无关人员。hit_rules用逗号拼接规则名辅导员在界面上看到「累计挂科超限,绩点过低」就知道该找学生谈什么。参数说明红色、黄色、蓝色的阈值 80 和 50 不是固定的第一年跑的时候可以先用历史数据回测看多少学生被划进红色如果红色名单超过全院 5%说明阈值太松要往上调。4. 避坑与排查预警系统落地时最容易翻车的五个地方4.1 现象预警名单里出现大量已毕业学生原因成绩表里保留了历史毕业生数据指标计算时没有过滤学籍状态。解决在student表加status字段计算宽表前先WHERE status 在读或者用enroll_year和当前学年做差超过标准学制的不参与预警。4.2 现象同一个学生上学期红色预警这学期突然消失原因趋势型规则里gpa_drop是拿本学期和上学期比如果上学期已经很低这学期没继续掉反而不触发。解决趋势指标要配合绝对阈值或者改成「连续两学期绩点低于 2.0」这种持续性规则避免预警名单剧烈波动。4.3 现象规则表改了阈值但跑出来的结果没变原因Python 脚本把rules_df缓存在内存里或者用了lru_cache装饰器没清缓存。解决每次预警任务开始时重新pd.read_sql读规则表不要跨批次复用 DataFrame。如果用了 Flask 做界面规则修改后要主动触发一次重新加载。4.4 现象学分缺口算出来是负数学生显示已修学分超过培养方案原因转专业学生同时修了两个专业的课或者选修课学分重复计算。解决培养方案表要区分必修和选修学分缺口只算必修课差额转专业学生按当前专业方案重新关联历史课程用课程代码映射表转换。4.5 现象预警结果写入 MySQL 时中文规则名变成乱码原因数据库连接字符集不是utf8mb4或者 pandasto_sql时没指定编码。解决建库时用CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci连接字符串加?charsetutf8mb4写入前确认hit_rules字段类型是TEXT而不是VARCHAR(50)避免截断。5. 让预警名单真正被用起来两个进阶技巧和一个验证习惯系统跑通只是第一步真正决定它有没有价值的是辅导员会不会打开看。我踩过的最大坑是第一版做出来红色预警名单 200 多人辅导员看了一眼就关了说「这些人我早就知道」。问题出在预警没有增量信息。后来我加了两个东西使用率才上来。第一个是「变化标记」。在预警结果表里加一列is_new标记这个学生是本次新进名单还是连续预警。辅导员最关心的是「谁是新出现的」连续红色预警的反而优先级低因为已经在处理了。实现方式很简单写入结果前查上一次批次的结果用student_id做左连接上次没出现的标记为新进。第二个是「单科下钻」。红色预警名单点进去不只看总分还能看到具体哪门课拖了后腿。这个功能不需要复杂前端用 Flask 写一个路由接收student_id返回该生所有低于 60 分的课程和对应学分辅导员谈话时直接有抓手。app.route(/detail/student_id) def detail(student_id): # 查该生所有挂科课程 sql SELECT c.course_name, s.score, c.credit FROM score s JOIN course c ON s.course_id c.course_id WHERE s.student_id %s AND s.score 60 ORDER BY s.score ASC df pd.read_sql(sql, engine, params(student_id,)) return df.to_html(indexFalse)这段 Flask 路由把挂科明细直接渲染成 HTML 表格不用写前端模板也能看。参数说明params传参防止 SQL 注入ORDER BY score ASC让最差的课排最前面。实际部署时可以把to_html换成 JSON 返回前端用 ECharts 画个雷达图但那是锦上添花核心是让辅导员三秒内看到问题课程。最后一个习惯每学期预警跑完后别急着发名单先做一次「回测」。拿上学期的预警结果和这学期的实际成绩对一下看红色预警的学生里有多少真的出现了新的挂科黄色预警里有多少绩点回升了。如果红色预警的准确率低于 60%说明规则太激进要调权重如果黄色预警里超过一半人没事说明阈值太松。这个回测不用写复杂代码用 pandas 做个交叉表就行但它能让你的预警系统每学期都在变准而不是拍脑袋定完阈值就不管了。我自己做这套系统最大的教训是别追求规则多先把三条核心规则跑准比堆二十条花哨指标有用得多。希望帮到你。本文还有配套的精品资源点击获取