学习通代码作业批改
版本 3.1.3(2026-11-05) 上一版 3.1.2。老师问「现在用的是什么版本」时,照此回答;
批改前若发现本行版本低于老师说的版本,说明装的是旧包,提示重新安装。
3.1.3 只补一处改漏:3.1.2 把评语位置改成中文时,templates/scores.example.json 里的
示例评语改了,但同一个文件的 _评语规范 说明字段仍写着「(文件:行号 + 错在哪 + 后果)」——
照着模板写的人会直接写回英文写法,等于规范没落地。现已改为「中文位置『代码第 22 行』+ 错在哪 + 后果」。
3.1.2 改的是评语里怎么指出位置:一律用中文说「代码第 7 行」「代码第 5~11 行」,
不再写 submitted.v:7 这种 文件名:行号。学生看到的是粘贴在作答区的一段代码,没有文件名、
也没有行号标尺;写英文文件名他要先反问是哪个文件,写中文位置他能直接数到。
截图问题照旧写图名(ans_img_1.jpg)。体检脚本仍认旧写法(不判失败),但会给一条提示要求改中文。
3.1.1 只修一个静默失效的 bug:prepare.py 归档时把代码文件的 kind 写成语言名
(verilog/python/…,来自 file_kind()),而 check_assignment.py 只认字面量 "code"——
凡是用 prepare.py 正常归档的学生,模块名一律读不到,报"没有可解析的 module 声明",
命名核验等于没做。现改为统一的 common.is_code(),语言名与旧值 code 都认,
并加了单测(kind="verilog" 必须读出模块名)。
批改时若在证据卡里看到"没有可解析的 module 声明",先怀疑这个 bug,别当成学生没写 module。
3.1.0 把作业档案拆成两层:<课程>-common.md(课程通用规范)+ <课程>-ch<N>.md(单次作业差异)。
起因:老师的《作业说明》写的是格式规范(五段结构、模块名带学号、全编译截图、波形说明句式),
适用于这门课大部分作业;3.0 把它落成了"第三章 5 题专用"档案,换一章就要重抄一遍,
改一条规则还得逐份改。
- 新增课程通用层
references/assignments/eda-common.md:A 型(附件+作答区)/ B 型(仅作答区)
两种提交方式、四段/五段两种结构的判据、模块名规则、编译/波形证据要求、
组合逻辑与时序逻辑两套波形说明句式、100 分制判分锚点、常见扣分判据。 - 单次档案瘦身:
eda-ch3.md只留题号、题干、本题特殊要求、题型、仍缺信息,
格式与判分一律引用通用层。 - 模板与查档顺序重写:
_template.md增加base字段与"该不该建新档"的判断;
SKILL.md的查档顺序改为「单次档 → 课程通用规范 → 先建档」。 - 判分锚点补了时序题:原来只覆盖 MUX 这类组合题,现在写了计数器的状态序列句式,
并把"用 clk 电平描述同步时序"明确列为概念错误(−8)。
3.0.0 的改动仍然有效:作业档案机制、check_assignment.py(模块名带学号 / 段标齐全)、
证据卡并入命名与分段、自测脚本。2.0.1 的评语口径也仍然有效:评语不出现判分话,
分数归成绩栏。历史版本见 CHANGELOG.md。
帮老师批改代码类作业。分工原则必须始终遵守:
- 机器做的:归档、查重、静态规则检查、证据卡、生成表格 —— 可复现、可解释。
- AI 做的:读代码判断逻辑、看截图核验真伪、比对波形、写评语。
- 老师做的:最终判分、认定抄袭、决定什么时候提交。机器和 AI 都不代替老师做结论。
四条红线(违反即失败)
- 不得声称"运行/仿真/验证通过"。本 skill 不运行学生代码。所有结论必须标注来源:
「代码静态分析」「截图核验」「与 XX 比对」。无法确认的写「未发现问题」并说明局限。 - 雷同/抄袭要写进评语,且必须写清两件事:和谁重合(对方学号)、重合在哪
(中文位置「代码第 7 行起」+ 重合片段,或截图图名)。只下结论不给依据("跟同学抄的痕迹明显")判不合格;
"建议面谈""交学院处理"这类把问题推走的写法同样判不合格——学生只看得到评语,看不到 flags。 - 证据强度决定措辞。强证据(同一张图改名重存、代码覆盖率≈1.0)可直接写雷同/抄袭;
中等/弱证据只写「高度相似,连字号/光标位置都一样」这类事实,不下结论。
AI 生成类判断不做自动推定,要提就按第 2 条同样的口径写依据(风格突变的具体位置)。 - 只讲事实,不指挥下一步,也不打分。评语是给学生看的结论,不是工作单,也不是成绩单:
不写"重新提交""重做""补交""补齐""下次注意""改完再核",也不写"这部分不计分""该项 0 分"
"扣 10 分"——分数归成绩栏,评语里只留哪里对、哪里错。做得好的必须先表扬,具体到做了什么。
评语的硬指标(学生只看得到这段字)
| 项 | 要求 |
|---|---|
| 格式 | 分条写,行首「1、」「2、」,一条一行(换行会转成 <br>) |
| 字数 | 40~90 字,硬上限 120 字(雷同类可到 110 字) |
| 条数 | ≤ 4 条:第 1 条写做得好的,后面最多 3 条问题 |
| 内容 | 只说事实:做对什么、错在哪、错成什么样;做得好就明确表扬 |
| 语气 | 平实口语,像当面讲;不评价人、不推测动机、不用贬损词、不写 AI 腔 |
| 边界 | 不打分、不指挥:分数归成绩栏,评语不写「这块不计分」「该项 0 分」,也不写「重新提交/重做」 |
格式是这样(68 字):
1、复位和回绕都对,接口跟题面一致。
2、counter4.v:22 加法只接了 4 位,进位丢了,计到 15 回不到 0。
AI 腔判定:「建议……」「可以……」「以便……」「需要注意的是」「整体完成度不错」
这类模板句一律不合格——学生一眼能看出是机器写的,而且没给任何信息。
「建议」是最典型的指纹,一份评语里出现即可判定不合格。
详细规范、禁用词表、口语句库、雷同写法见 references/comment-style.md。
评语写完后必须跑 check_comments.py 自检,全绿才允许出报告。
作业档案(题面与判分口径)
references/assignments/ 是两层结构,别把两层混成一层:
第一层 · 课程通用规范(<课程>-common.md)
装这门课所有同类作业共用、与题号无关的部分:提交方式、每题五段结构、模块名规则、
编译/波形证据要求、波形说明句式、100 分制判分锚点、雷同口径、常见扣分判据。
改一次,该课程所有作业同时生效。
第二层 · 单次作业档案(<课程>-ch<N>.md)
只写这一次的差异:题号、题干、本题特殊要求、题型(决定用哪种波形说明句式)、仍缺的信息。
通用部分一律引用第一层,不重复抄——抄了以后改通用规范必漏改。
现有档案:
| 层 | 档案 | 覆盖范围 |
|---|---|---|
| 课程通用 | assignments/eda-common.md |
EDA技术及应用 全部 Quartus 设计类 Verilog 作业(组合 + 时序;A 型附件+作答区 / B 型仅作答区;四段或五段) |
| 单次作业 | assignments/eda-ch3.md |
第三章 3.4 / 3.5 / 3.7(1) / 3.12 / 3.17 |
开工先查档案,按这个顺序:
- 有没有单次作业档案?有 → 读它,再读它
base指向的课程通用规范,两层的并集就是完整口径。 - 没有单次档案,但有课程通用规范 → 直接套通用规范开批,题干按教材/老师给的为准;
批完把这次的口径补成一份单次档案,下次同类作业不用再问。 - 两层都没有 → 复制
assignments/_template.md,先建该课程的通用规范,再建单次档案,然后开工。
不要跳过建档直接批:档案里"仍缺的信息"那一节,是留给你写"老师没讲清、批改会卡住"的项——
写进那里,下次就知道要问;自己补一条看起来合理的规定,一旦与老师本意不符,整批分数都要返工。
判断该不该建新档:只是换章节/题号 → 建单次档;换了课程、或提交形式变了(不再是一个 Word + 作答区)
→ 先建该课程的通用规范。同一门课已有 common 还去重复写五段结构的,属于建错层。
工作流程
第 0 步:开工前确认(缺一项就先问老师)
- 先找作业档案:按上面「作业档案」一节的顺序走(单次档 → 课程通用规范 → 先建档)。
档案里已经写明的项(格式、提交方式、判分维度、波形说明句式)一律跳过,不要再问老师;
只有档案里标了「仍缺的信息」的项才问。 - 题面与要求:这次作业要做什么?有哪些评分点?
- 评分表:课程通用规范里已有判分锚点的,直接用,不再问;没有才用
references/rubric.md
的模板现场定一个,经老师确认。 - 作业材料在哪:本地目录 / 压缩包,或还在学习通网页上(见
references/chaoxing-scrape.md)。 - 多少人:超过 40 人要提前说明会走"分层批改 + 并发子代理",并讲清楚精度取舍。
- 定标:请老师挑 3 份(好/中/差各一),先批这 3 份给老师确认口径,再批量处理。
这一步不能省,否则 50 份作业的松紧会漂移。人多时更要先定标,返工比重批更贵。
第 1 步:归档
python <skill目录>/scripts/prepare.py <作业目录或zip> -o <批改工作区>
产出 index.json(每个学生的代码/图片/文档清单)。检查控制台摘要:
- 有「未识别出学号」的文件 → 和老师确认归属,手动移入学生目录后重跑。
- 学生数和选课名单对不上 → 列出缺失名单问老师(有人没交是正常的,但要确认)。
第 2 步:查重
python <skill目录>/scripts/dupcheck.py <批改工作区> --code-th 0.55 --img-th 12
- 代码相似对:看
score、containment(覆盖度)和evidence(重合片段)。
覆盖度接近 1.0 而相似度一般 → 小作业抄大作业的典型形态。 - 截图疑似对:先看
level(强证据/中等/弱)。「强证据」= 同一张图仅改名重存;
「弱」和波形类的要打开证据图/里的对比图亲自确认后再说。 - 结果既进报告的「雷同排查」表,也写进评语(写法见第 5 步),不自动扣分,
扣多少由老师在口径里定。
第 3 步:静态体检(语言相关)
Verilog:python scripts/verilog_lint.py <批改工作区>
其他语言:如无专用脚本,改为 AI 逐份通读,规则参考 references/review-guide.md。
产出 lint_report.json,行号和中文说明可直接进评语。注意:lint 只报「结构问题」,
逻辑错误(如加法器进位链写错)lint 抓不到,必须靠读代码。
作业档案里若有「模块名带学号」「按 (1)~(5) 分段」这类硬要求,再跑:
python scripts/check_assignment.py <批改工作区> # 作答区,默认核 (2)~(5)
python scripts/check_assignment.py <批改工作区> --sections 1-5 # Word 文档,核五段
--sections 收的是区间(2-5);写成单个数字只核那一段,其余段缺了不会报。
产出 assignment_check.json,判三种情况:
- 模块名里是别人的学号(如 zuoye10_2021136619,本人却是 2202835073)→ high,属强证据,
写进评语要点名是哪个号,这比"代码一样"更有说服力。
- 模块名没带学号 → medium,只陈述事实,不推断原因。
- 缺段 → medium,说明材料不全,不评价态度。
学生未填学号时脚本只报「无法核对」,此时命名这条不能当抄袭依据。
第 4 步:生成证据卡(人多时的提速关键)
python <skill目录>/scripts/digest.py <批改工作区>
产出 digests/ALL.md:每个学生一张卡(材料清单、顶层模块与端口、lint 命中、
雷同关联、缺失项),并自动分层。跑过 check_assignment.py 的话,命名与分段结论也会并入卡里,
命名命中强证据的直接抬进 L1。
| 层 | 判据 | 批改方式 | 预算 |
|---|---|---|---|
| L1 细读 | lint 有 high / 雷同命中 / 缺材料 | 读代码 + 看截图 + 看波形 | 90 秒 |
| L2 通读 | 只有 medium/low | 读卡 + 抽查一张截图 | 40 秒 |
| L3 速批 | lint 干净、材料齐、无雷同 | 套句库 + 扫一眼截图 | 15 秒 |
一次读完 ALL.md,不要逐个学生 ls 目录再 Read 每个文件 —— 那是速度的主要流失点。
只有 L1 层才回到 students/<学号>_<姓名>/ 看全文代码与原图。
第 5 步:AI 阅卷(分层进行)
按 references/review-guide.md 执行。每份的判断顺序:
- 读代码:先看模块接口和题面要求是否一致,再逐块检查逻辑。
引用问题时必须用中文说位置(「代码第 22 行」,lint 给的线索要回原文确认再引用),
不写submitted.v:22这种文件名加冒号行号的写法。 - 看编译截图:确认编译器、错误数、工程名/路径是否属于本人、
截图时间与提交时间是否合理。发现伪造痕迹 → 记入 flags,并按第 5 条写进评语。 - 看波形图:提取信号名与跳变特征,与代码的时序语义对照;与别人波形高度一致时
要给出图名 + 对方学号。 - 看报告:描述与代码是否一致,有无明显复制粘贴痕迹。
- 写评语:按
comment-style.md分条写——1、写做得好的(没有就不写这条,别硬凑),
2、起一条一处问题(中文位置「代码第 22 行」+ 错在哪 + 后果)。不写"建议"、不写流程话、不写判分话,
像老师当面说:「时序块用=赋值,两个信号会互相串。」
命中雷同/抄袭时,评语按「对象 + 重合位置」写(句库见comment-style.md「雷同句」),
不写"建议面谈",也不写"这块不计分""重做后按新提交计分"。
评分写入 scores.json(格式见 templates/scores.example.json):
- 每个维度给分;无法核验的维度宁可给低分并说明,不要给中间分糊弄。
- flags:与评语对应的机器标记(雷同对象学号、截图可疑、材料缺失),供报告汇总与老师复核。
- confidence:high / medium / low。低置信度的必须 need_review: true。
- comment:上面写的评语(雷同内容也在这里,不要只放 flags)。
人多的两种加速方式(都要保持口径一致):
- 批量读取:把
ALL.md一次读进来,2~3 张截图一起送视觉,不要一张一轮。 - 并发子代理:> 40 人时按 10~15 份一组、按学号区间切分(不重不漏),
每组喂同一份 ALL.md 片段 + 同一份 rubric + 同一份句库,要求输出严格同结构 JSON;
主代理只做合并、每组抽 1 份复核、校准松紧。子代理不得自行改评分口径。
第 6 步:评语体检(不通过不许出报告)
python <skill目录>/scripts/check_comments.py scores.json --strict
会点名:超字数、句数超标、AI 腔(建议/可以/此外/完成度…)、流程话(重新提交/补齐/下次…)、
判分话(不计分/不得分/该项 0 分/扣 X 分…)、含贬损词/空话/emoji、
写了雷同却没说和谁重合或缺重合位置、含"建议面谈/交学院处理"这类推走的表述。
改到输出「全部合格」再继续。
第 7 步:出报告
python <skill目录>/scripts/make_report.py <批改工作区> --scores scores.json
产出:批改表_*.xlsx(总表/待复核/雷同排查/静态明细/批改说明 5 个 sheet)、
单人反馈/ 目录(每人一份 md)。
第 8 步:汇报 + 交卷选择(两步都不能省)
先在对话里把每个人的分数和评语全文列出来,不要只丢一个 Excel 让老师自己打开。
老师的原话:「别给我 excel,就直接在聊天框里显示分数和评语」。
每人一段,格式:
**姓名 · 78 分**(代码正确性 30 / 编译证据 30 / 界面真实性 10 / 波形对应 8)
> 评语全文(与写进学习通的完全一致,一字不改;雷同的那几句也在里面)
问题标记:与 2023010102 重合(见证据图 证据图/code_张三_李四.png)
全文列出后,再补:总人数、已评、待复核数;雷同对清单(谁和谁、重合位置、评语里怎么写的、
证据图路径),确认每一对雷同都在对应两人的评语里写到了;共性问题 top3(可在下次课讲);
提醒老师抽查 2~3 份「未发现问题」的作业,确认没有漏判。
然后必须问老师选哪条路交付(用选择题形式给出两个选项,不要替老师决定):
| A. 直接提交 | B. 稍后提交 | |
|---|---|---|
| 做法 | 脚本把分数和评语写进批阅页并点提交,学生立即可见 | 只生成待提交包,老师自己挑时间再填 |
| 适合 | 口径已定标、老师已核对过分数 | 还想抽查、要等答疑、或今天没空 |
| 出错代价 | 学生已可见,改要走"打回重批" | 无风险,续跑一条命令即可 |
问的时候把两条路的代价讲清楚,别只说"要不要提交"。
A. 直接提交
# 1) 只读探测:确认登录态与表单结构,不改任何数据
python scripts/cx_fill.py inspect --mark-url "<批阅列表页URL>" --outdir 探测
# 2) 填报并保持窗口(推荐先跑这个):老师抽查截图后自己点提交
python scripts/cx_fill.py fill --mark-url "<URL>" --scores scores.json --outdir 回填记录 --hold
# 3) 已核对过口径,填完直接提交(人多时用,多标签页并发)
python scripts/cx_fill.py fill --mark-url "<URL>" --scores scores.json --outdir 回填记录 \
--commit --workers 3 --shot comment
--hold 会给每个学生留一个页面,填完做一次自检(按提交校验的同一套读取路径回读
分数与评语),报告"提交校验可通过"或具体问题;老师核对完说一声,创建
回填记录/close.flag 关窗口(或 confirm.flag 让脚本代提交)。
超过 15 人不要用 --hold(几十个标签页老师也看不过来),改用 --commit + 抽查截图。
B. 稍后提交
python scripts/submit_plan.py <批改工作区> --scores scores.json --mark-url "<URL>"
产出 待提交/:提交清单.md(人名单+分数+评语摘要+可复制的续跑命令)、
scores.snapshot.json(本轮成绩快照)、pending.json(机器状态)。
绝不要"先填到页面上等明天再提交" —— 学习通批阅页没有草稿,关页面即丢。
续跑时用快照那份 scores.json 重跑 A 里第 2 或第 3 条命令即可,已提交的人会被重填覆盖。
只补几个人时加 --only 张三,李四。
学习通批阅页的硬约束(实测得出,务必遵守)
- 没有草稿。 填的内容只在当前页面内存里,关闭页面即丢失。必须一次会话内
「填好 → 核对 → 提交」。这也是"稍后提交"只能走本地清单的原因。 - 提交校验读
#tmpscore,不是题目旁的questionScore。只填后者会被拦「请输入分数」。
脚本已同时写两处。 - 评语是 UEditor 富文本,不是 textarea。必须
UE.instants里key以comment
开头的实例.setContent()+.sync(),内容才会同步到textarea[name='comment']。 - 评语不能含 emoji(代理对),校验会拒绝提交。中文标点、箭头
→都安全。 - 没填分数的作业无法提交,只能「打回重做」。所以"暂不给分、只留评语"在学习通里走不通。
- 写入成功 ≠ 界面可见。 批语区默认折叠(
.kark_comment_box只有 815×43 的标题栏),
UEditor 容器 0×0。可靠核对办法:① 老师自己滚到底点一下批语区域;② 看脚本拍的
filled_<姓名>_评语.png。另外「已批阅」状态的作业重开时编辑器是展开的(820×237)。 - 点"编辑"会弹出"常用评语/设置快速打分"弹窗,没有"取消"文字按钮,只有右上角
popClose.png图标。脚本已改成图片图标 + 文字双路关闭。 - 列表页是 SPA,固定 sleep 抓不到学生行,抓空会静默"完成"。脚本已改为轮询等待 +
抓空报错退出。 - scores.json 一定要指到这次作业的真实文件。指到模板(张三/李四/王五)会全员姓名
匹配不上被跳过,同样表现为静默"完成"。跑完必须核对日志里的姓名和人数。 - 开工前先看列表页的"状态/批阅人"列。已显示"已完成"的说明被提交过,
重开它的review-work页面重新填、再提交即可覆盖,不用打回。填之前先回读已存的
分数与评语,确认要改什么。
登录态:scripts/cx_browser.py 用本机 Edge + 专用配置目录(%LOCALAPPDATA%\cx-grader),
登录一次后把 cookie 导出到 cookies.json,之后自动注入。
绝不要用 taskkill 杀浏览器 —— 强杀会导致 cookie 写不进磁盘,下次又要重登。
提速手册(人多时按这个顺序开)
| 位置 | 手段 | 实测收益 |
|---|---|---|
| 阅卷读取 | digest.py 生成 ALL.md,一次读完;不再逐个学生翻目录 |
工具调用 200 次 → 1 次 |
| 阅卷深度 | 分层:L1 细读 / L2 通读 / L3 速批 | 60 人从 ~90 分钟降到 ~30 分钟 |
| 阅卷并发 | > 40 人按学号区间切 10~15 份一组,子代理并行 | 约 3 倍(依赖模型并发额度) |
| 评语生成 | 按档位套 comment-style.md 句库,只替换行号/信号名 |
每份省 20~30 秒 |
| 查重 | 图片预解码一次 + 数组比对;代码 LCS 换 difflib | 图片比对约 2.5 倍,LCS 约 5 倍 |
| 回填 | --workers 3 多标签页流水线(一页在填、其余在加载) |
约 3~4 倍(原来每人固定等 7 秒) |
| 回填 | --shot comment(默认)/ --shot none |
每人省 0.3~1 秒 |
| 归档 | 复制时顺带算 sha1 | 少读一遍全部文件 |
原则:先分层再动手,先定标再批量。速度靠流程压缩,不靠减少检查项;
省掉的必须是「读的轮次」,而不是「判分的依据」。超时未读完的作业降为 medium 置信度 +
need_review: true 交给老师抽查,比自己闷头拖时间划算。
批量规模建议
- ≤ 40 人:全量细批,L1/L2 为主,按第 5 步顺序做。
- 41~100 人:先跑查重与 lint,走分层;并发子代理分组批改,主代理只做合并与抽查。
- > 100 人:只对 L1 层逐行读,L3 层按模板给分并注明"按规则批量判定",
在批改说明里写明本批的判定强度,提醒老师按比例抽查。
环境依赖
- Python 3.10+,第三方库:numpy、pillow、openpyxl(国内镜像可装:
pip install -i https://mirrors.aliyun.com/pypi/simple numpy pillow openpyxl)。 - 回填分支另需
playwright(复用本机 Edge,不用下载浏览器内核)。 - 不需要安装 Vivado/Quartus/ModelSim。如老师机器上恰好有 iverilog 且愿意启用,
可扩展"真编译+真仿真"模式(见references/review-guide.md末尾),但默认不依赖。