题库信息架构 · 数据结构设计
题库数据结构:从计分点到材料
这份文档把题库的数据结构自下而上讲清楚:最小的是计分点(一个空),往上是题(一道小题)、题组(一篇文章配的几道题)、材料(文章/音频本身)。每一层只放属于自己的字段。
〇、先回答一个问题:答题要求(Directions)挂在哪一层?
就是试卷上「Directions: In this section, you are going to read a passage with ten statements attached to it…」这类作答指令。挂 item_group(题组),字段名 directions。原因:
- 它约束的是「这一组题怎么答」——上面那段长篇匹配的 Directions,管的是同一篇文章下的 10 个句子题,正是一个题组的范围;
- 它不是题干:题干(stem)是单个问题,Directions 在同组题之间共享,放题上必然重复存储;
- 它不是材料:材料是文章/音频本身,同一份材料完全可以换一组 Directions 改成别的考法。
单个小题也一律裹一层题组(组内只有一道题),所以 Directions 永远有家。命名约定:directions = 答题要求(题组层);translation_direction = 翻译方向(题层,仅翻译题用),两者不要混淆。
一、四层结构总览
二、第一层:blank 计分点
评分落到的最小单位。填空题的一个空、改错的一处错,都是一个计分点。它有自己的提示、答案和判分规则:
{
"blank_key": "0", // 题干里的占位符 {{blank:0}}
"hint": { "word": "important" }, // 给词提示(词形变换题)
"accepted_answers": ["most important"], // 可接受答案(可多个)
"match_rule": { "caseSensitive": false, "trim": true },
"points": 1
}
真实对照:你们填空题数据里的 referenceAnswers: {"64131-0": "most important"},这个 64131-0 就是一个计分点——新结构把它从「答案 map 里的一个隐式 key」变成显式记录。
为什么计分点要有自己的作答形式(b_form_override):高考语法填空 10 个空里,6 个给了词(词形变换)、4 个没给词(无备选填空)——同一道题内两种作答形式,靠计分点级覆盖表达。
三、第二层:item 题(题原子)
一道小题 = 题干 + 选项或空 + 答案 + 六个分类坐标。坐标全部是字典 code,独立成列,可索引可统计:
| 坐标字段 | 取值示例 | 管什么 |
|---|---|---|
skill_primary / 次技能 | K / R / W / T / S / L | 考什么能力(翻译题另加 translation_direction) |
b_category + b_form | CONSTRUCT_CLOSED + TRANSFORM | 怎么作答 |
qtype_code | word_form_transform | 题型(用户检索主入口) |
scoring | MACHINE / HUMAN / AI | 怎么判分 |
| 关联表:modality / tags | TEXT;考试、语法点、话题… | 输入模态与标签 |
真实对照——词形变换题(旧 id 64131)在新结构下:
{
"skill_primary": "K", "b_category": "CONSTRUCT_CLOSED", "b_form": "TRANSFORM",
"qtype_code": "word_form_transform", "scoring": "MACHINE",
"stem": { "format": "html", "text": "Among these important things, the {{blank:0}} one is to assess…
" },
"blanks": [ { "blank_key": "0", "hint": {"word":"important"}, "accepted_answers": ["most important"] } ]
}
注意旧数据里的 <input name="64131-0"> 这种把数据库 id 写进题干的标记,新结构统一换成与 id 无关的占位符 {{blank:0}}。
四、第三层:item_group 题组
「一篇文章配 5 道题」的「配」字就落在题组上:它持有答题要求(directions),并把若干题和一份材料连起来。真实对照——仔细阅读(旧 id 49934):
{
"item_group": { "material_id": 9001, "directions": "Directions: In this section, you are going to read a passage… Choose the best answer." },
"items": [ 64488, 64489, 64490, 64491, 64492 ] // 5 道单选,各自带坐标
}
你给的那段长篇匹配 Directions("…a passage with ten statements attached to it…")同理:1 篇文章 + 10 个句子题 = 1 个题组,这段话就是它的 directions 字段值。
旧结构里 referenceAnswers 把 5 道题的答案平铺在一个 map 里;新结构里答案收回每道题自己身上(answer: {"optionKeys": ["D"]}),题组不再背答案。
五、第四层:material 材料
文章、音频、视频、图片这些「被读被听的东西」。话题、字数、难度、来源挂在这层,一次录入、全组共享。真实对照——旧数据塞在 mainTitle 里的那篇 work-life balance 文章:
{
"genre": "passage", // 材料体裁(C 维度)
"content": { "format": "html", "text": "The work-life balance is dead…
" },
"word_count": 312, "topic_code": "work_life", "difficulty": 3,
"source_type": "original", // 自编题;真题 = real
"fingerprint": "sha1:…" // 去重键:同一篇文章只存一份
}
听力题的材料则带音频资产:assets: [{type:"audio", url:…, duration:95, marks:[…]}]。
六、更多例子:六种题各就各位
除了两条真实数据,再看四种常见题在同一结构下的样子(坐标卡):
| 例子 | 材料 | 题组 | 题(坐标) | 计分点 |
|---|---|---|---|---|
| 语法填空(高考型) | 短文 1 篇 | 「用正确形式填空」 | 1 题:K + FILL_OPEN | 10 个空:6 个 override=TRANSFORM(给词),4 个 FILL_OPEN |
| 听力长对话(CET 型) | 对话音频 1 段(assets 带 url/duration) | 「听对话选答案」 | 3 题:L + SINGLE_CHOICE,modality=AUDIO | 无(选择题无空) |
| 句子翻译 中译英(AB 级型) | 无(题干即中文句) | 「译为英文」 | 1 题:T + TRANSLATION_TASK,direction=ZH2EN,scoring=AI | 无(整句开放作答) |
| 完形填空(四选一型) | 短文 1 篇 | 「选最佳答案」 | 20 题:每空是独立小题 R + SINGLE_CHOICE | 无 |
对照着看就能感到结构的好处:同样是「填空」,词形变换、语法填空、完形在坐标上清清楚楚;同样是「一篇文章配多题」,阅读和听力的差别只在材料类型和模态。
七、答案模型速查
| 作答形式 b_form | 答案存哪 | 结构 |
|---|---|---|
| SINGLE_CHOICE / MULTI_CHOICE / 判断 | item.answer | {"optionKeys": ["D"]} |
| MATCHING / ORDERING | item.answer | {"pairs": {"q1":"C"}} / {"sequence": ["B","D","A"]} |
| FILL_* / TRANSFORM / DICTATION / 改错 | blank.accepted_answers | ["most important"] + 判分规则 |
| 简答 / 开放回答 | item.answer | {"reference": "…", "keywords": […]} |
| 写作 / 翻译 / 口语 | item.answer | {"reference": "…", "rubricCode": "…"},scoring=HUMAN/AI |
八、旧字段兼容速查
| 旧字段 | 新去向 |
|---|---|
mainTitle(塞着文章的那个) | material.content,fingerprint 去重 |
audioUrl / transcript / videoUrl | material.assets |
questionCategoryId | 经映射表预打标成新坐标;旧值留 legacy_* 字段回溯 |
questionType / type | b_form 初值(fill_in_the_blank 按有无提示词细分 TRANSFORM / FILL_OPEN) |
answer + referenceAnswers 双写 | 合并校验后单写;不一致进人工队列 |
isSubjective | 废弃,由 scoring 取代(单选必为 MACHINE) |
source: official/original | source_type: real / original |
difficulty | 直接迁移(约定 1–5) |
—— 全文完 ——