ZJUI-Learn 题目设置指引(独立版)¶
本文从docs/ZJUILEARN_0_TO_1_HANDBOOK_ZH.md中提取离“问题配置”相关内容,形成单独可用的建题手册。
适用于教师、助教、题目开发同学。
1. 题目文件结构(必须掌握)¶
每道题对应一个目录(QID):
questions/<QID>/
info.json # 题目元信息与策略开关
question.html # 题面与输入组件
server.py # 参数生成、解析、评分
2. 三核心文件配置详解¶
2.1 info.json:问题配置中心¶
最小模板:
{
"uuid": "11111111-2222-3333-4444-555555555555",
"type": "v3",
"title": "欧姆定律计算",
"topic": "电路基础",
"tags": ["ohm", "intro"]
}
高频字段:
uuid:唯一ID,必须唯一type:新题固定"v3"title:题目标题topic:主题主题分类tags:筛选标签singleVariant:true表示单变体(不随机)partialCredit:是否允许部分得分showCorrectAnswer:面板是否显示正确答案gradingMethod:Internal/External/Manual
常见坑:
- 修改了
info.json后界面不刷新(本地开发需点击Load from disk) type写错导致行为与预期不一致
2.2 question.html:题面与输入组件¶
模板示例:
<pl-question-panel>
已知电压 $V={{params.v}}\\,V$,电阻 $R={{params.r}}\\,\\Omega$,求电流 $I=V/R$。
</pl-question-panel>
<pl-number-input answers-name="i" label="$I=$" suffix="A"></pl-number-input>
<pl-submission-panel> {{feedback.i}} </pl-submission-panel>
硬性规则:
{{params.xxx}}必须在server.py的generate()赋值中- 所有输入组件的
answers-name必须唯一 - 优先使用官方元素(如
pl-number-input、pl-multiple-choice)
常见坑:
answers-name重复,导致评分异常- 使用了
{{params.xxx}}但server.py未提供 - 题面文案修改后,评分逻辑未同步
2.3 server.py:参数与评分核心¶
入门模板(生成 + 反馈):
import random
import math
import prairielearn as pl
def generate(data):
v = random.randint(5, 20)
r = random.choice([1, 2, 4, 5, 10])
data["params"]["v"] = v
data["params"]["r"] = r
data["correct_answers"]["i"] = v / r
def grade(data):
i_is_correct = math.isclose(data["partial_scores"]["i"]["score"], 1.0)
if not i_is_correct:
submitted = float(data["submitted_answers"]["i"])
if submitted > 0:
data["feedback"]["i"] = "方向正确,但请再检查除法计算。"
函数职责:
generate(data):生成参数与标准答案parse(data):输入清洗/格式错误处理(任选)grade(data):卡通评分与反馈(任选)test(data):复杂题自动测试(可选)
评分建议:
- 浮点比较避免
==,优先math.isclose - 自定义
grade()时覆盖避免组件已判对结果 - 反馈要能指导学生下一步修改
3. 题目模板库(可直接复用)¶
每个模板都包含三个文件。复制后改uuid、题面与参数即可。
3.1 填空题(数字填空)¶
info.json
{
"uuid": "2d66b1bf-a4ac-4f49-bf8b-1c2bbad1a001",
"type": "v3",
"title": "一次函数求值",
"topic": "代数",
"tags": ["fill-blank", "number"]
}
question.html
<pl-question-panel>
已知 $y=2x+3$,当 $x={{params.x}}$ 时,$y=$
<pl-integer-input answers-name="y" label=""></pl-integer-input>
</pl-question-panel>
server.py
import random
def generate(data):
x = random.randint(1, 10)
data["params"]["x"] = x
data["correct_answers"]["y"] = 2 * x + 3
3.2 填空题(文本,忽略大小写)¶
info.json
{
"uuid": "2d66b1bf-a4ac-4f49-bf8b-1c2bbad1a002",
"type": "v3",
"title": "术语拼写",
"topic": "计算机基础",
"tags": ["fill-blank", "string"]
}
question.html
<pl-question-panel>
请补全短语:Prairie
<pl-string-input
answers-name="term"
ignore-case="true"
remove-leading-trailing="true"
></pl-string-input>
</pl-question-panel>
server.py
def generate(data):
data["correct_answers"]["term"] = "Learn"
3.3 单选题¶
info.json
{
"uuid": "2d66b1bf-a4ac-4f49-bf8b-1c2bbad1a003",
"type": "v3",
"title": "复杂度判断",
"topic": "算法",
"tags": ["single-choice"]
}
question.html
<pl-question-panel> 对于归并排序,其平均时间复杂度是? </pl-question-panel>
<pl-multiple-choice answers-name="mc" order="fixed">
<pl-answer correct="false">$O(n)$</pl-answer>
<pl-answer correct="true">$O(n\log n)$</pl-answer>
<pl-answer correct="false">$O(n^2)$</pl-answer>
<pl-answer correct="false">$O(\log n)$</pl-answer>
</pl-multiple-choice>
server.py
def generate(data):
pass
3.4 多选题(部分得分)¶
info.json
{
"uuid": "2d66b1bf-a4ac-4f49-bf8b-1c2bbad1a004",
"type": "v3",
"title": "Python 语法判断",
"topic": "编程基础",
"tags": ["multi-choice", "checkbox"],
"partialCredit": true
}
question.html
<pl-question-panel> 下面哪些是 Python 的合法数据类型?(可多选) </pl-question-panel>
<pl-checkbox answers-name="types" partial-credit="each-answer" order="fixed">
<pl-answer correct="true">int</pl-answer>
<pl-answer correct="true">dict</pl-answer>
<pl-answer correct="false">char</pl-answer>
<pl-answer correct="false">pointer</pl-answer>
</pl-checkbox>
server.py
def generate(data):
pass
3.5 文字题(关键词自动评分)¶
info.json
{
"uuid": "2d66b1bf-a4ac-4f49-bf8b-1c2bbad1a005",
"type": "v3",
"title": "HTTP 状态码解释",
"topic": "网络基础",
"tags": ["short-answer", "text"]
}
question.html
<pl-question-panel> 请用一句话解释 HTTP 404 的含义。 </pl-question-panel>
<pl-string-input
answers-name="desc"
multiline="true"
remove-leading-trailing="true"
placeholder="请输入你的解释"
></pl-string-input>
<pl-submission-panel> {{feedback.desc}} </pl-submission-panel>
server.py
def generate(data):
pass
def grade(data):
text = str(data["submitted_answers"]["desc"]).lower()
has_not_found = ("not found" in text) or ("不存在" in text)
has_resource = ("resource" in text) or ("资源" in text)
if has_not_found and has_resource:
data["partial_scores"]["desc"]["score"] = 1.0
data["feedback"]["desc"] = "解释完整。"
elif has_not_found or has_resource:
data["partial_scores"]["desc"]["score"] = 0.5
data["feedback"]["desc"] = "方向正确,可补充“请求资源不存在”的完整语义。"
else:
data["partial_scores"]["desc"]["score"] = 0.0
data["feedback"]["desc"] = "建议围绕“资源不存在 / Not Found”作答。"
3.6 文字题(人工评分)¶
info.json
{
"uuid": "2d66b1bf-a4ac-4f49-bf8b-1c2bbad1a006",
"type": "v3",
"title": "算法设计思路说明",
"topic": "算法",
"tags": ["essay", "manual"],
"gradingMethod": "Manual"
}
question.html
<pl-question-panel>
请描述你设计该算法的核心思路、时间复杂度与正确性依据(200 字以内)。
</pl-question-panel>
<pl-string-input
answers-name="essay"
multiline="true"
remove-leading-trailing="true"
placeholder="请输入简答内容"
></pl-string-input>
server.py
def generate(data):
pass
3.7 一题多空¶
info.json
{
"uuid": "2d66b1bf-a4ac-4f49-bf8b-1c2bbad1a007",
"type": "v3",
"title": "基础代数多空",
"topic": "代数",
"tags": ["multi-blank"]
}
question.html
<pl-question-panel> 已知 $a={{params.a}}, b={{params.b}}$,请填写: </pl-question-panel>
<p>$a+b=$ <pl-integer-input answers-name="sum"></pl-integer-input></p>
<p>$a-b=$ <pl-integer-input answers-name="diff"></pl-integer-input></p>
server.py
import random
def generate(data):
a = random.randint(3, 10)
b = random.randint(1, 2)
data["params"]["a"] = a
data["params"]["b"] = b
data["correct_answers"]["sum"] = a + b
data["correct_answers"]["diff"] = a - b
4.从建题到发布(最短SOP)¶
- 在
Questions新建问题(QID/标题) - 完成三文件:
info.jsonquestion.htmlserver.py
- 在
preview页面反复生成变体验证 - 检查错误输入反馈是否合理
- 将题目加入评估并配置分值/尝试规则
- 发布前做一次学生视角走查
5. 发布前检查清单¶
-
info.json字段完整(uuid、type、title、topic) -
question.html所有answers-name唯一 -
server.py生成了题面所需全部参数 - 至少测试 2 个变体
- 错误答案反馈可读、可执行
- 评估配置(分值、时间、次数)已核对
6. 常见问题速查¶
- 修改不生效:本地开发点击
Load from disk(尤其是JSON元信息) - 页面渲染报错:先检查
question.html标签和组件属性 - 评分异常:检查
answers-name与correct_answers关键一致性 - 仅改题面未改逻辑:对
server.py的评分与反馈通路至关重要
7. 关联文档¶
- 总手册:
docs/ZJUILEARN_0_TO_1_HANDBOOK_ZH.md - 教师页面总表:
docs/TEACHER_ADMIN_ALL_PAGES_MANUAL_ZH.md - 教师培训版:
docs/TEACHER_TRAINING_PLAYBOOK_ZH.md