题库信息架构 · 数据结构设计

题库数据结构:从计分点到材料

这份文档把题库的数据结构自下而上讲清楚:最小的是计分点(一个空),往上是题(一道小题)、题组(一篇文章配的几道题)、材料(文章/音频本身)。每一层只放属于自己的字段。

〇、先回答一个问题:答题要求(Directions)挂在哪一层?

就是试卷上「Directions: In this section, you are going to read a passage with ten statements attached to it…」这类作答指令。挂 item_group(题组),字段名 directions。原因:

  1. 它约束的是「这一组题怎么答」——上面那段长篇匹配的 Directions,管的是同一篇文章下的 10 个句子题,正是一个题组的范围;
  2. 它不是题干:题干(stem)是单个问题,Directions 在同组题之间共享,放题上必然重复存储;
  3. 它不是材料:材料是文章/音频本身,同一份材料完全可以换一组 Directions 改成别的考法。

单个小题也一律裹一层题组(组内只有一道题),所以 Directions 永远有家。命名约定:directions = 答题要求(题组层);translation_direction = 翻译方向(题层,仅翻译题用),两者不要混淆。

一、四层结构总览

④ material 材料 文章 / 音频 / 视频 / 图片 · 体裁、话题、字数、难度、来源挂在这层 例:work-life balance 议论文(312 词 · 话题=职场 · 难度 3) ③ item_group 题组 答题要求(directions)+ 共享同一份材料的一组题 例:「读文章选最佳答案」+ 5 道小题 ② item 题(题原子) 题干 + 选项/空 + 答案 + 六个分类坐标(技能/形式/题型/方向…) 例:What does the author suggest by saying…?(单选,答案 D) ① blank 计分点(最小单位) 一个空:占位符 + 提示 + 可接受答案 + 判分规则 + 分值 从下往上组装
图 1四层结构自下而上:若干计分点组成一道题,若干题组成题组,题组挂在材料上。无材料的题(如语法单选)题组直接留空材料。

二、第一层: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_formCONSTRUCT_CLOSED + TRANSFORM怎么作答
qtype_codeword_form_transform题型(用户检索主入口)
scoringMACHINE / HUMAN / AI怎么判分
关联表:modality / tagsTEXT;考试、语法点、话题…输入模态与标签

真实对照——词形变换题(旧 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_OPEN10 个空: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 / ORDERINGitem.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 / videoUrlmaterial.assets
questionCategoryId经映射表预打标成新坐标;旧值留 legacy_* 字段回溯
questionType / typeb_form 初值(fill_in_the_blank 按有无提示词细分 TRANSFORM / FILL_OPEN)
answer + referenceAnswers 双写合并校验后单写;不一致进人工队列
isSubjective废弃,由 scoring 取代(单选必为 MACHINE)
source: official/originalsource_type: real / original
difficulty直接迁移(约定 1–5)

—— 全文完 ——