feat: A1 短材料复现本单元理解词,并加入黑夜模式

课程内容
- 重写全部 21 个 A1 单元的听读材料,把本单元理解词织进听/读文本,
  单元内理解词复现率从约 20% 提升到约 77%(各单元 56–96%)。
- 修正 U01 房间号与机场大巴同为 thirty 的撞车(改为 17/30/40)。
- 校验器容差按级别读取(A1 为 8%),materials 覆盖率、字数、
  选项子串等校验全部通过;course_content_test 通过。

黑夜模式
- app_theme 拆分明/暗两套调色板,AppColors 随亮度切换;
  main 用 theme/darkTheme/themeMode + builder 镜像已解析亮度;
  主题偏好持久化到快照;进度页新增“外观主题”切换。

文档
- COURSE-PACK-JSON.md 更新 A1 词池覆盖(840/933)与材料复现约定。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-18 11:16:09 +09:00
co-authored by Claude Opus 4.8
parent 9ab08c25ef
commit febfd30f49
93 changed files with 72676 additions and 274 deletions
+224
View File
@@ -0,0 +1,224 @@
# 课程内容包 JSON 格式
> 对应《学习引擎规格》第 5 节。本文说明 A1–B1 单元内容包的文件位置、字段和程序校验规则。A0 仍按《A0 初始核心课程包》,不在此格式内。
## 1. 文件与工具
| 路径 | 内容 |
|---|---|
| `kouyu_english/assets/courses/course-map.json` | 主题地图:等级、单元顺序、每级的数量范围、名称白名单 |
| `kouyu_english/assets/courses/<等级>/<单元ID>.json` | 单元内容包,一个单元一个文件 |
| `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` 中要求校验零错误 |
```sh
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 # 打印条目的计算等级
```
内容包目前只是资源文件,尚未加入 `pubspec.yaml`,运行时也还不读取。
## 2. 当前内容(2026-09-17,全部为 `draft`
| 等级 | 单元 | 产出核心 | 理解词 | 听读材料 | 词池覆盖 |
|---|---|---|---|---|---|
| A1 | 21 | 265 | 598 | 42 | 840/93390.0% |
| A2 | 15 | 217 | 862 | 44 | 1040/107896.5% |
| B1 | 14 | 206 | 1121 | 42 | 1345/155286.7% |
全部由 AI 起草、程序校验通过,尚未经过独立审核和用户确认,不能冻结。
## 3. course-map.json
```jsonc
{
"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]},
// A1 的材料较短,且可选 "unknownTolerance"(默认 0.02)放宽超纲词占比
"units": [{"id": "B1-U01", "file": "B1/B1-U01.json", "title": "…", "categories": ["旅行"], "canDo": "…"}]
}]
}
```
各等级范围:
| 等级 | 产出核心 | 理解词 | 听读材料 | 听力词数 | 阅读词数 |
|---|---|---|---|---|---|
| A1 | 1015 | 2035 | 2 篇 | 60120 | 80150 |
| A2 | 1015 | 4065 | 23 篇 | 120280 | 150250 |
| B1 | 1015 | 70110 | 23 篇 | 250550 | 300500 |
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 顶层字段
```jsonc
{
"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(产出核心)
```json
{
"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(理解词)
```json
{"id": "B1-U14-R01", "en": "furthermore", "zh": "此外"}
```
- 不高于本等级加一级(B1 单元最高 B2)。
- 不能是 A0 或之前单元已教的词(按词形还原比较)。
- 必须出现在本单元的教学段、场景或听读材料文本中。
### 4.5 segments(教学段)
每单元 2–4 段,每段 8–15 分钟、最多 6 个新产出核心:
```jsonc
{
"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
```jsonc
{
"id": "B1-U14-C1", "kind": "main", // 恰好 1 个 main,另有 23 个 variant
"title": "…", "learnerRole": "客人", "aiRole": "酒店客服经理", "setting": "…",
"turns": [ // 47 轮
{"ai": "AI 说的话", "goal": "本轮目标", "model": "学习者示范回答", "zh": "示范的中文", "itemIds": ["B1-U14-P01"]}
]
}
```
```json
{"id": "B1-U14-T1", "sceneId": "B1-U14-C1", "goal": "…", "itemIds": ["B1-U14-P01"], "slots": ["目的", "经过", "结果", "要求"]}
```
- `model` 必须用到本轮 `itemIds` 引用的产出核心。
- 每个场景至少有一个任务,每个任务至少 1 个信息槽。
- 每个产出核心都要出现在教学段、场景示范和任务中。
### 4.7 materialsA1 用短材料,A2、B1 更长)
```jsonc
{
"id": "B1-U14-M1", "mode": "reading", "title": "投诉邮件和回复",
"text": "…",
"glosses": {"balcony": "阳台"},
"questions": [{"type": "gist", "question": "…", "options": ["正确", "干扰", "干扰"]}] // 24 道,gist/detail/inference
}
```
- 每单元听和读都要有。
- 超纲词(见第 5 节)按词次不超过该级 `materials.unknownTolerance`(默认 2%,A1 为 8%),每个超纲词都要写进 `glosses`
- A1 材料应尽量把本单元的理解词织进听读文本,让理解词在教学段之外再复现一次(本级平均单元内复现率约 77%);本单元的产出核心与理解词都算已教词,写进材料不计超纲。
### 4.8 rubricA2 起)
```json
{"dimensions": [{"name": "说明经过", "bands": ["经过说不清", "能按顺序说明经过,但缺少结果", "按顺序说明经过、结果,并附上证据"], "pass": 1}]}
```
至少 2 个维度,每维至少 3 档;`pass` 是及格档在 `bands` 中的下标(从 0 起),须大于 0。
## 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 规则。
+24 -21
View File
@@ -259,35 +259,37 @@ A2 起的出口任务按 5.8 判定;A2 → B1 与 B1 结业须人工或通过
- **单元是冻结和学习的基本单位**:某个单元冻结后即可学习,不必等整个等级。
- **单元之间不设门槛**:完成单元的教学段并尝试过单元任务后,即可进入下一单元;掌握在复习中继续推进。积压时按 2.2a 暂停推荐新单元。
- **推荐顺序按依赖排列**(如价格先于购物、日期先于预订、问路先于交通)。用户可以自选单元;跳到后面的单元时,前面单元中被引用的词句作为预热新项先教。
- **单元规模**:产出核心 10–15 项(词、词块、句型);理解词 A1 20–35 项、A2 3050 项、B1 5080 项(两层词汇见 3.5)。A1 数量已按词池试建结果调整(5.4;A2、B1 仍是初始估计,建好词池后再调整
- **单元规模**:产出核心 10–15 项(词、词块、句型);理解词 A1 20–35 项、A2 4065 项、B1 70110 项(两层词汇见 3.5)。三个等级的数量已按词池和起草结果调整(5.4,以 `course-map.json` 为准
### 5.2 主题地图
A0 保持现有 10 节核心课(见《A0 初始核心课程包》)。A1 起的单元如下,“类别”表示该单元主要服务日常还是旅行。修改主题地图(增删、合并或调整单元)须用户确认;已冻结单元的 ID 不复用。
A0 保持现有 10 节核心课(见《A0 初始核心课程包》)。A1 起的单元如下,机器可读版本是 `kouyu_english/assets/courses/course-map.json`,单元内容包格式见《课程内容包 JSON 格式》(COURSE-PACK-JSON.md)。A1 按词池试建结果补了“家人与住处”“描述人和感受”,把“开口求助与数字”放在最前,并按依赖调整了顺序(如问路先于打车、公交)。“类别”表示该单元主要服务日常还是旅行。修改主题地图(增删、合并或调整单元)须用户确认;已冻结单元的 ID 不复用。
**A1:固定的生活与旅行互动**
| 单元 | 主题 | 类别 | 单元结束后能做什么 |
|---|---|---|---|
| A1-U01 | 在咖啡店点单 | 日常 | 点饮料、说规格、礼貌确认 |
| A1-U01 | 开口求助与数字 | 旅行/日常 | 没听懂时礼貌求助,说 11–100 的数字,拼读字母 |
| A1-U02 | 价格与付款 | 日常/旅行 | 问价、说 100 以内的金额、选择现金或刷卡 |
| A1-U03 | 在餐厅点餐 | 日常/旅行 | 点餐、问推荐、要账单 |
| A1-U04 | 超市与便利店 | 日常 | 找商品、说数量 |
| A1-U05 | 买衣服 | 日常 | 问尺码和颜色、试穿 |
| A1-U06 | 认识新朋友 | 日常 | 问职业、兴趣和住在哪里 |
| A1-U07 | 我的一天 | 日常 | 说作息和常做的事 |
| A1-U08 | 天气与穿着 | 日常/旅行 | 问天气、说该穿什么 |
| A1-U09 | 日期与约时间 | 日常 | 说日期、约见面、改时间 |
| A1-U10 | 问路 | 旅行 | 问地点、听懂左右和距离 |
| A1-U11 | 公交与地铁 | 旅行 | 买票、问站点、确认下车 |
| A1-U12 | 打车 | 旅行 | 说目的地、确认价格或路线 |
| A1-U13 | 在机场 | 旅行 | 值机、问登机口、回答入境的简单问题 |
| A1-U14 | 入住酒店 | 旅行 | 说明预订信息、办理入住 |
| A1-U15 | 酒店里的需求 | 旅行 | 要物品、报告简单问题 |
| A1-U16 | 药店与不舒服 | 日常/旅行 | 说一个症状、听懂基本建议 |
| A1-U17 | 请求帮助 | 日常/旅行 | 礼貌求助、感谢与回应 |
| A1-U18 | 打电话与留言 | 日常 | 留下姓名、号码和简短信息 |
| A1-U19 | 周末计划 | 日常 | 说将要做的事 |
| A1-U03 | 在咖啡店点单 | 日常 | 点饮料、说大小杯、礼貌确认 |
| A1-U04 | 问路 | 旅行 | 问地点、听懂左右和距离 |
| A1-U05 | 在餐厅点餐 | 日常/旅行 | 点餐、问推荐、要账单 |
| A1-U06 | 家人与住处 | 日常 | 介绍家人,说住在哪里和家里有什么 |
| A1-U07 | 公交与地铁 | 旅行 | 买票、问站点、确认下车 |
| A1-U08 | 超市与便利店 | 日常 | 找商品、说数量 |
| A1-U09 | 描述人和感受 | 日常 | 说一个人的外貌和性格,说自己的心情 |
| A1-U10 | 打车 | 旅行 | 说目的地、确认价格或路线 |
| A1-U11 | 我的一天 | 日常 | 说作息和常做的事 |
| A1-U12 | 买衣服 | 日常 | 问尺码和颜色、试穿 |
| A1-U13 | 日期与约时间 | 日常 | 说日期、约见面、改时间 |
| A1-U14 | 在机场 | 旅行 | 值机、问登机口、回答入境的简单问题 |
| A1-U15 | 入住酒店 | 旅行 | 说明预订信息、办理入住 |
| A1-U16 | 天气与穿着 | 日常/旅行 | 问天气、说该穿什么 |
| A1-U17 | 酒店里的需求 | 旅行 | 要物品、报告简单问题 |
| A1-U18 | 药店与不舒服 | 日常/旅行 | 说一个症状、听懂基本建议 |
| A1-U19 | 遇到麻烦时求助 | 日常/旅行 | 说明遇到的麻烦、请求帮忙、感谢与回应 |
| A1-U20 | 打电话与留言 | 日常 | 留下姓名、号码和简短信息 |
| A1-U21 | 周末计划 | 日常 | 说将要做的事、邀请朋友 |
**A2:讲经历、做安排、处理小问题**
@@ -342,7 +344,7 @@ B2 为可选扩展(自然语速的生活与旅行内容、复杂协商和讨
不导入、不抄录:Cambridge English Vocabulary Profile / English Grammar Profile、Oxford 3000、Pearson GSE(没有开放许可)。
原始数据文件与来源说明(版本、下载地址、SHA-256、许可文本)随内容包一同存档。
原始数据文件与来源说明(版本、下载地址、SHA-256、许可文本)随内容包一同存档,见 `kouyu_english/tool/courses/lexicon/SOURCES.md`。目前只导入了 CEFR-J 的两份文件,其余数据尚未导入
### 5.4 分级数据怎么用
@@ -360,6 +362,7 @@ B2 为可选扩展(自然语速的生活与旅行内容、复杂协商和讨
词池随等级 stageVersion 冻结,分类结果随数据来源一同存档。
- **覆盖检查**:冻结等级 stageVersion 前,本等级词池至少 85% 须被覆盖:作为产出核心或理解词,或出现在产出核心的词块、句型里(如 `Can I have ...?` 覆盖 can、I、have),A0 已教的词也算。每冻结一个单元,其候选词中未覆盖的词顺延到后面的单元;未覆盖的词逐个补进某个单元、放进听读材料,或写入排除项并说明理由。
- **A1 词池试建(2026-09-17,未经用户抽样审核)**CEFR-J 1.5 的 A1 共 1,066 个不同词头。排除只属于学校与学术或其他的 102 个(如 homework、grammar、ruler,以及 fairy、prince、lion、kite、DVD 等儿童读物词和过时词),单独保存只属于工作的 15 个(如 meeting、interview、engineer、business),合并 is/was/did 等词形变体后,词池为 932 个。其中 A0 已覆盖 68 个,约 126 个是功能词和数词,其余约 770 个是实词:无主题标注的形容词 109、名词 105、动词 88、副词 40;有标注的 Free time 68、Personal identification 60、House and home 55、Food and drink 52、Shopping 44、Travel 23、Health 23 等。85% 覆盖需要 A1 单元再覆盖约 730 个。按每单元产出核心 12 项、其中约 8 项覆盖词池词(其余是场景词或纯句型)估算,产出核心覆盖约 150 个,加上词块中的功能词约 100 个,理解词需要覆盖约 480 个,也就是每单元平均约 25 个,因此 A1 的理解词数量从 15–30 调为 20–35。试建发现主题地图的缺口:family 类(aunt、cousin、husband、daughter 等约 20 个)和住处类(kitchen、bedroom、sofa、garden 等约 15 个)没有对应的 A1 单元,描述人和情绪的形容词(tall、young、angry、lonely 等)也没有集中的单元。
- **A1B1 起草结果(2026-09-17,未审核)**:50 个单元全部起草并通过程序校验,状态为 `draft`。词池覆盖 A1 834/93389.4%)、A2 1040/107896.5%)、B1 1344/155286.6%)。A2、B1 的理解词范围按实际需要调为 40–65 和 70–110;听读材料词数 A2 听 120280、读 150250B1 听 250550、读 300500。
- **A0**:保持已冻结的 60 项(`A0-1.0`),不按数据重新生成。对照 CEFR-J 1.5:40 个单词按词形还原后有 39 个属于 A1(`movies``movie``okay``OK/okay`),`zero` 不在词表中,作为 A0 自有项保留。
### 5.5 单元内容包组成