14 KiB
课程内容包 JSON 格式
对应《学习引擎规格》第 5 节。本文说明 A0–B1 单元内容包的文件位置、字段和程序校验规则。所有等级共用这一套结构,由
CourseRepository用同一套规则转换成学习和复习数据;A0 的内容标准仍以《A0 初始核心课程包》为准(见 4.10)。
1. 文件与工具
| 路径 | 内容 |
|---|---|
kouyu_english/assets/courses/course-map.json |
主题地图:等级、单元顺序、每级的数量范围、名称白名单 |
kouyu_english/assets/courses/<等级>/<单元ID>.json |
单元内容包,一个单元一个文件 |
kouyu_english/assets/courses/A0/assessment.json |
A0 阶段测评题(见 4.11) |
kouyu_english/tool/courses/lexicon/ |
锁定的 CEFR-J 数据与 SOURCES.md |
kouyu_english/tool/courses/pools/ |
等级词池(build_word_pools.py 生成) |
kouyu_english/tool/courses/validate.dart |
校验全部内容包,报告词池覆盖率 |
kouyu_english/test/course_content_test.dart |
在 flutter test 中要求校验零错误 |
cd kouyu_english
dart run tool/courses/validate.dart # 错误和覆盖率
dart run tool/courses/validate.dart --uncovered B1 # 按主题列出未覆盖的词池词
dart run tool/courses/validate.dart --levels B1-U14 # 打印条目的计算等级
运行时由 lib/core/courses/course_repository.dart 读取:main 启动前先加载 A0,AppState.load 再加载其余等级。学习(教学段、对话、写作、独立表达)和复习(产出核心的 match 与 review)都只读这些 JSON,代码里不再写死课程数据。test/course_self_consistency_test.dart 要求每个示范答案都能通过它自己的判定规则。
2. 当前内容(2026-09-18,A1–B1 为 draft)
| 等级 | 单元 | 产出核心 | 理解词 | 听读材料 | 词池覆盖 |
|---|---|---|---|---|---|
| A0 | 10 | 见 A0 标准 | — | — | —(approved,不参与词池校验) |
| A1 | 21 | 265 | 598 | 42 | 840/933(90.0%) |
| A2 | 15 | 217 | 862 | 44 | 1040/1078(96.5%) |
| B1 | 14 | 206 | 1121 | 42 | 1345/1552(86.7%) |
A1–B1 全部由 AI 起草、程序校验通过,尚未经过独立审核和用户确认,不能冻结。
3. course-map.json
{
"schemaVersion": 1,
"goal": "日常生活与旅行",
"sources": ["CEFR-J Vocabulary Profile 1.5(…)", "CEFR-J Grammar Profile 20180315(…)"],
"minimumPoolCoverage": 0.85, // 等级词池覆盖下限
"alwaysAllowedWords": ["oh", "ok", …], // 任何文本都可用的语气词
"names": ["tom", "london", "mr", …], // 全局专名、缩写
"levels": [{
"id": "B1",
"title": "独立应对大多数情况",
"coreRange": [10, 15],
"receptiveRange": [70, 110],
"materials": {"count": [2, 3], "listeningWords": [250, 550], "readingWords": [300, 500]},
"assessment": "A0/assessment.json", // 可选:本级阶段测评题,目前只有 A0 有
// A1 的材料较短,且可选 "unknownTolerance"(默认 0.02)放宽超纲词占比
"units": [{"id": "B1-U01", "file": "B1/B1-U01.json", "title": "…", "categories": ["旅行"], "canDo": "…"}]
}]
}
各等级范围:
| 等级 | 产出核心 | 理解词 | 听读材料 | 听力词数 | 阅读词数 |
|---|---|---|---|---|---|
| A1 | 10–15 | 20–35 | 2 篇 | 60–120 | 80–150 |
| A2 | 10–15 | 40–65 | 2–3 篇 | 120–280 | 150–250 |
| B1 | 10–15 | 70–110 | 2–3 篇 | 250–550 | 300–500 |
A1 材料短、面向零基础,unknownTolerance 设为 0.08(听读材料默认 0.02):允许更多“课程尚未正式教、但零基础者需要”的常用词,前提是每个都写进 glosses 给出中文,问题选项用中文以免受“选项须在原文出现”的限制。
units 的顺序就是推荐学习顺序,校验“已教的词”时按此顺序累积。
4. 单元文件
4.1 ID 规则
所有 ID 都以单元 ID 开头,序号从 1 连续编号:
| 前缀 | 对象 | 例子 |
|---|---|---|
W |
产出核心:单词 | B1-U14-W01 |
P |
产出核心:词块或句型 | B1-U14-P01 |
R |
理解词 | B1-U14-R01 |
S |
教学段 | B1-U14-S1 |
C |
场景 | B1-U14-C1 |
T |
任务 | B1-U14-T1 |
M |
听读材料 | B1-U14-M1 |
4.2 顶层字段
{
"schemaVersion": 1,
"id": "B1-U14",
"revision": 1,
"level": "B1",
"title": "写说明与投诉", // title/categories/canDo 必须与 course-map 一致
"categories": ["日常", "旅行"],
"canDo": "写邮件说明经过并提出要求",
"status": "draft", // draft / reviewed / frozen
"meta": {
"draftedBy": "claude-opus-5",
"draftedAt": "2026-09-17",
"sources": ["CEFR-J Vocabulary Profile 1.5", "CEFR-J Grammar Profile 20180315"],
"reviews": [], // 审核记录
"frozenAt": null
},
"names": ["seaview", "palace"], // 本单元专名,小写
"grammar": [{"id": "142", "use": "active"}, {"id": "215", "use": "receptive", "reason": "…"}],
"coreItems": […], "receptiveWords": […], "segments": […], "scenes": […], "tasks": […],
"materials": […], // A1 起(A1 为短材料)
"rubric": {"dimensions": […]}, // A2 起(A1 无开放任务评分标准)
"excluded": [{"en": "…", "reason": "…"}]
}
4.3 coreItems(产出核心)
{
"id": "B1-U14-P08",
"type": "phrase",
"en": "I would like to request a full refund.",
"zh": "我想申请全额退款。",
"sceneWord": false,
"match": [["request a full refund", "request a refund"]],
"example": {"en": "I would like to request a refund for the second night.", "zh": "我想申请退还第二晚的费用。"},
"review": {"prompt": "在邮件里提出退款要求。"}
}
type:word/phrase/pattern。match:判定用户是否用到本项。外层数组之间为“且”,内层为“或”;每个词条按连续短语匹配(缩写展开、忽略大小写和标点)。en和example.en都必须满足。sceneWord:高于本等级或 CEFR-J 未收录、但场景必需的词句设为true,并写sceneReason;每单元不超过产出核心的 1/3。review.prompt:复习时的中文任务,不能直接包含英文答案。
4.4 receptiveWords(理解词)
{"id": "B1-U14-R01", "en": "furthermore", "zh": "此外"}
- 不高于本等级加一级(B1 单元最高 B2)。
- 不能是 A0 或之前单元已教的词(按词形还原比较)。
- 必须出现在本单元的教学段、场景或听读材料文本中。
4.5 segments(教学段)
每单元 2–4 段,每段 8–15 分钟、最多 6 个新产出核心:
{
"id": "B1-U14-S1", "title": "说明写信目的", "minutes": 12,
"itemIds": ["B1-U14-P01", "B1-U14-P02"],
"listening": {"text": "…", "question": "…", "options": ["正确", "干扰", "干扰"]},
"speaking": [{"text": "…", "tip": "可选的发音提示"}],
"reading": {"text": "多行用 \n", "question": "…", "options": ["…", "…", "…"]},
"writing": {"prompt": "…", "example": "…", "itemIds": ["B1-U14-P01"]},
"independent": {"prompt": "…", "itemIds": ["B1-U14-P02"]}
}
- 选项恰好 3 个,第一个是正确答案(界面随机排序);选项不能互为子串。英文选项至少两个在原文出现,且正确选项必须出现。
writing.example必须用到它引用的每个产出核心。
4.6 scenes 与 tasks
{
"id": "B1-U14-C1", "kind": "main", // 恰好 1 个 main,另有 2–3 个 variant
"title": "…", "learnerRole": "客人", "aiRole": "酒店客服经理", "setting": "…",
"turns": [ // 4–7 轮
{"ai": "AI 说的话", "goal": "本轮目标", "model": "学习者示范回答", "zh": "示范的中文", "itemIds": ["B1-U14-P01"]}
]
}
{"id": "B1-U14-T1", "sceneId": "B1-U14-C1", "goal": "…", "itemIds": ["B1-U14-P01"], "slots": ["目的", "经过", "结果", "要求"]}
model必须用到本轮itemIds引用的产出核心。- 每个场景至少有一个任务,每个任务至少 1 个信息槽。
- 每个产出核心都要出现在教学段、场景示范和任务中。
4.7 materials(A1 用短材料,A2、B1 更长)
{
"id": "B1-U14-M1", "mode": "reading", "title": "投诉邮件和回复",
"text": "…",
"glosses": {"balcony": "阳台"},
"questions": [{"type": "gist", "question": "…", "options": ["正确", "干扰", "干扰"]}] // 2–4 道,gist/detail/inference
}
- 每单元听和读都要有。
- 超纲词(见第 5 节)按词次不超过该级
materials.unknownTolerance(默认 2%,A1 为 8%),每个超纲词都要写进glosses。 - A1 材料应尽量把本单元的理解词织进听读文本,让理解词在教学段之外再复现一次(本级平均单元内复现率约 77%);本单元的产出核心与理解词都算已教词,写进材料不计超纲。
4.8 rubric(A2 起)
{"dimensions": [{"name": "说明经过", "bands": ["经过说不清", "能按顺序说明经过,但缺少结果", "按顺序说明经过、结果,并附上证据"], "pass": 1}]}
至少 2 个维度,每维至少 3 档;pass 是及格档在 bands 中的下标(从 0 起),须大于 0。
4.9 可选字段:显式规则优先
以下字段都可省略;省略时由 itemIds 引用的产出核心推导(match 合并成一组“或”,示范取 en),写了就以写的为准。规则写法见 lib/core/courses/answer_rules.dart:外层“且”、内层“或”,内层首项可写 #min:N 表示至少用上 N 项;词条可写 a + b、/正则/,以及 #spelling、#digit、#word、#question、#numbers:N、#words:N 这些结构。
| 位置 | 字段 | 作用 | 省略时 |
|---|---|---|---|
| 单元 | status |
approved / draft 等 |
draft |
| 单元 | vocabulary |
整课词表(离线查词) | 产出核心 + 理解词 |
| 教学段 | grammarNote |
本段语法提示 | 不显示 |
| 教学段 | vocabulary |
预习词表 [{id, word, meaning, example, exampleMeaning}] |
本段产出核心 |
| 教学段 | sceneId |
本段对话用的场景 | 第 N 段用第 N 个场景,没有则用 main 场景 |
| 教学段 | writing.accept / writing.hint |
写作判定规则 / 未通过时的提示 | 由 writing.itemIds 推导 |
| 教学段 | independent.accept / independent.help |
独立表达的完整规则 / 帮助 | 由 independent.itemIds 推导;为空则任何真实尝试都算 |
| 场景 | goal |
对话目标 | setting,再没有则 title |
| 场景 | practice |
同时作为自由练习场景:{title, summary, recapPrompt, alwaysOpen},都可省略;写 {} 即可 |
不进入自由练习 |
| 轮次 | aiZh |
ai 这句的中文(zh 是示范回答的中文) |
不提供翻译 |
| 轮次 | accept |
本轮判定用的一组“或” | 由本轮 itemIds 推导 |
自由练习场景在其所属单元完成后解锁,alwaysOpen: true 则一开始就开放。
4.10 A0 单元
A0 用同一结构,文件为 A0/a0-01.json … a0-10.json,在 course-map.json 中排在最前。为兼容已保存的学习进度,A0 保留原有 ID:单元 a0-01,教学段 a0-01-a(拆段课为 a0-04-a/b/c、a0-08-a/b/c),产出核心 A0-W01 / A0-P01。A0 的规则、提示和对话都用上面的显式字段写明;validate.dart 只把 A0 的产出核心和听写句当作“已教的词”,不按 A1–B1 的数量范围和词池校验 A0。
4.11 阶段测评(assessment.json)
等级在 course-map.json 里写了 assessment,就由 CourseRepository 加载对应文件,运行时代码在 lib/core/courses/assessment.dart。目前只有 A0 的 A0/assessment.json:
{
"schemaVersion": 1,
"packs": [
{"id": "A0-E1", "level": "A0", "tasks": [
{"id": "A0-A-L1", "skill": "listening", "prompt": "选择正确答案",
"audio": "Hello. I’m Mia.", "choices": ["自我介绍", "买东西", "问时间"], "answerIndex": 0},
{"id": "A0-A-W1", "skill": "writing", "prompt": "用英语介绍自己。",
"accept": [["hello", "hi"], ["i am", "my name is"]],
"guide": "试着同时写一句问候和姓名,例如:Hello. I’m …"}
]},
{"id": "A0-E1R", "level": "A0", "replaces": "A0-E1", "tasks": […]}
]
}
skill取listening/reading/writing/speaking。听力、阅读是选择题,写choices和answerIndex(从 0 开始);听力另写audio。- 写作、口语是开放题,
accept用 4.9 的规则写法,guide是答错后展示的要求。 - 没有
replaces的套题按文件顺序解锁:第一套直接开放,之后每套要在上一套通过 24 小时后才开放。 - 有
replaces的是替换题,用不同的人名、地点、数字考同样的能力,成绩记在原题 ID 上。 - 题目和套题 ID 写进学习记录,发布后不要改。
test/course_self_consistency_test.dart 检查:每道开放题都有 accept 和 guide,每道选择题的 answerIndex 有效,replaces 指向存在的套题。
5. 文本用词检查
“已知词”包括:alwaysAllowedWords、功能词、A0 已教的词、推荐顺序在前的单元的产出核心和理解词、本单元的产出核心和理解词(均按词形还原),以及全局与本单元的 names。数字不计。
| 文本 | 允许的未知词 |
|---|---|
| 教学段的听、说、读、写示范,场景的 AI 句和示范句 | 0 |
| 听读材料 | ≤ materials.unknownTolerance(默认 2%,A1 为 8%),且全部有 glosses |
注意:等级低于本单元、但还没有被任何单元教过的词(如 history、culture)同样算未知词。要在文本中使用,须改写,或把它加为本单元的理解词。
6. 等级与覆盖
- 语法:
active语法项不高于本等级,否则写reason。 - 产出核心中的词不高于本等级,除非是场景词。
- 覆盖率:等级词池(
tool/courses/pools/)中,被产出核心、match词块或理解词覆盖的词(含 A0 已教)占比须 ≥minimumPoolCoverage。
7. 状态流转
draft(AI 起草、校验通过)→ reviewed(独立审核后,在 meta.reviews 记录审核模型、日期和问题清单)→ frozen(用户确认,填 frozenAt)。已冻结单元的 ID 不复用;修改须提升 revision 并遵循《学习引擎规格》5.5 的 stageVersion 规则。