init: edu-content 内容库骨架(L1-L4 四层数据模型 + B类迁移 + lint/CI 工具)

这个提交包含在:
2026-08-22 00:16:49 +08:00
当前提交 f18264111b
共有 79 个文件被更改,包括 7182 次插入 和 0 次删除

查看文件

@@ -0,0 +1,11 @@
# exams/ket — 结构化数据(L3)
本目录存放清洗后的可用数据。每条记录须带 `source.source_id`(指向 `sources/`)与 `schema` 版本。
## 消费须知
- 数据来源与 license:见 `sources/` 与根 `LICENSES.md`
- 格式 schema:见 `../../schemas/`(qb-1.1 / vocab-1.1)
- 新增记录请先确认 source 已在 `sources/` 登记
## 当前内容
(待填充)

查看文件

@@ -0,0 +1,44 @@
# 题库 INDEX — 数据总览与使用指南
> 更新:2026-08-21 | 格式规范见 [SCHEMA.md](SCHEMA.md)(qb-1.0)
## 一、现有数据
| 文件 | 级别 | 套数 | 空格/小题 | 多答案小题 | 来源 | 许可 |
|---|---|---|---|---|---|---|
| `cepoc-a2-key.jsonl` | A2 Key | 6 | 68 | 14 | CEPOC(剑桥 ALTA 官方开源) | `cc-nc-education` |
| `cepoc-b1-preliminary.jsonl` | B1 Preliminary | 21 | 150 | 24 | CEPOC | `cc-nc-education` |
| `p2/out/*.jsonl`(6 份) | A2+B1 | 6 卷 | 131 | — | 剑桥官方 2022 样卷 | `official-free` |
| **合计** | | **33** | **349** | | | |
> P2 详情(成功率 112/112 对账、token 成本、风险点)见 [p2/P2-REPORT.md](p2/P2-REPORT.md)。解析器:pymupdf4llm。
## 二、待入库(已规划)
| 优先级 | 数据 | 预估规模 | 许可 |
|---|---|---|---|
| ~~P3~~ | ~~官方词表 → 词库~~ **已完成** → 见 [../vocab/INDEX.md](../vocab/INDEX.md)(A2 1740 + B1 3172 词条) | — | `official-free` |
| ~~P3.5~~ | ~~词库中文释义+音标~~ **已完成** → 见 [../vocab/INDEX.md](../vocab/INDEX.md) §三·五(ECDICT 4804 + LLM 98,音标覆盖 93.3%) | 4902 条 | ECDICT `CC-BY-SA-4.0` / LLM 生成 |
| P4 | tape script + mp3 精听语料 | 2 套对齐文本 | `official-free` |
| P5 | SkoolPlus 教材(ECS 下载后) | 20+ 套模拟题 | `restricted`(仅本地) |
## 三、下游 agent 使用须知
1. **读库**:JSONL 逐行 `json.loads`;一条记录 = 一个任务(语篇+多小题),不是单题
2. **判题**:必须按 SCHEMA.md 第二节规则——`strip` + 大小写不敏感 + `answers_accept` 集合匹配;38 个空有多个可接受答案(如 `["because","as","since"]`),字符串全等会误判
3. **许可红线**(按 `license` 字段过滤):
- `official-free` → 可进部署的英语门户
- `cc-nc-education`(CEPOC 全部数据)→ 自用/练习工具可以,**禁止商业化与再分发**
- `restricted` → 任何对外输出都不允许出现
4. **完形上下文切分**:`passage.split(blank_token)`,段数 = 空数 + 1
5. **CEPOC 的 `part` 为 null**:数据混有 2020 改版前真题(KET 11-12 空/套、PET 6-8 空/套),与新版题型空数不符,见记录内 `note` 字段
6. **P2 记录的媒体引用**:`meta.media[].path` 与 `meta.audio` 为相对路径(前者相对 question-bank 目录,后者相对 english-site 目录);图片选项题(听力 Part 1)`options=null`,必须渲染 `meta.media` 图片作答
## 四、复现
```bash
cd data/question-bank
python3 scripts/convert_cepoc.py <cepoc-main 绝对路径> .
```
转换脚本内置校验:空格数与小题数一致、题号不重复、答案非空、id 唯一。2026-08-21 校验全部通过。

查看文件

@@ -0,0 +1,6 @@
{"schema": "qb-1.1", "id": "a2k-cepoc-0001", "exam": "A2 Key", "cefr": "A2", "paper": "reading_writing", "part": null, "type": "open_cloze", "task_prompt": "For each gap, write ONE word which best completes the text.", "title": null, "passage": "Dear Melissa\n\nHow are things _ London? I had a great time staying with you. Thank you for looking after _ . You are _ great cook and your family were really kind to me. And my English is much better now _ it was before. \n\nMy flight home was fine – there were _ delays at all. It’s good to be back, but I miss you all very _ !\n\n_ you remember my friend Jenny? I think she came _ visit me at your house once _ twice. Well, she took some great photos _ us on our last day at the language school. I’m sending you one with this email. I hope you like \n\n_ .\n\nRosario", "blank_token": "_", "items": [{"no": 1, "stem": null, "options": null, "answers_accept": ["in"], "answer_official": "in", "explain": null, "tags": []}, {"no": 2, "stem": null, "options": null, "answers_accept": ["me"], "answer_official": "me", "explain": null, "tags": []}, {"no": 3, "stem": null, "options": null, "answers_accept": ["a"], "answer_official": "a", "explain": null, "tags": []}, {"no": 4, "stem": null, "options": null, "answers_accept": ["than"], "answer_official": "than", "explain": null, "tags": []}, {"no": 5, "stem": null, "options": null, "answers_accept": ["no"], "answer_official": "no", "explain": null, "tags": []}, {"no": 6, "stem": null, "options": null, "answers_accept": ["much"], "answer_official": "much", "explain": null, "tags": []}, {"no": 7, "stem": null, "options": null, "answers_accept": ["Do"], "answer_official": "Do", "explain": null, "tags": []}, {"no": 8, "stem": null, "options": null, "answers_accept": ["to"], "answer_official": "to", "explain": null, "tags": []}, {"no": 9, "stem": null, "options": null, "answers_accept": ["or"], "answer_official": "or", "explain": null, "tags": []}, {"no": 10, "stem": null, "options": null, "answers_accept": ["of"], "answer_official": "of", "explain": null, "tags": []}, {"no": 11, "stem": null, "options": null, "answers_accept": ["it"], "answer_official": "it", "explain": null, "tags": []}], "source": {"origin": "cepoc", "file": "assets/open-source/cepoc-main/KET.json", "orig_id": 1, "url": "https://github.com/CambridgeALTA/cepoc", "retrieved": "2026-08-21", "source_id": "cepoc", "source_item_id": "cepoc-ket-json"}, "license": "cc-nc-education", "tags": ["cloze", "open_cloze"], "note": "CEPOC 未标注套题年份与卷内 Part 号;空格数与 2020 版题型不完全对应(KET 11-12空/套、PET 6-8空/套),推断混有 2020 改版前真题。完形题型本身对应 A2 Key Reading & Writing / B1 Preliminary Reading 的 open cloze 题型。", "meta": {"multi_answer_items": 0}}
{"schema": "qb-1.1", "id": "a2k-cepoc-0002", "exam": "A2 Key", "cefr": "A2", "paper": "reading_writing", "part": null, "type": "open_cloze", "task_prompt": "For each gap, write ONE word which best completes the text.", "title": null, "passage": "Dear Aunt Fabienne,\n\nMum asked _ to let you know that I arrived home okay. I enjoyed travelling back _ the train, and I was surprised that _ only took me three hours to get home. Thank you very _ for looking after me _ I was in France. \n\nI’m going back to school _ week. My French is a lot better _ it was before, so I hope my marks will improve!\n\nI _ like to keep practising French and I’m looking _ a pen friend. I thought of your friend’s son Elian. We could email each _ once a week. Let me know _ you think.\n\nI hope _ see you soon. \n\nDaniel", "blank_token": "_", "items": [{"no": 1, "stem": null, "options": null, "answers_accept": ["me"], "answer_official": "me", "explain": null, "tags": []}, {"no": 2, "stem": null, "options": null, "answers_accept": ["on"], "answer_official": "on", "explain": null, "tags": []}, {"no": 3, "stem": null, "options": null, "answers_accept": ["it"], "answer_official": "it", "explain": null, "tags": []}, {"no": 4, "stem": null, "options": null, "answers_accept": ["much"], "answer_official": "much", "explain": null, "tags": []}, {"no": 5, "stem": null, "options": null, "answers_accept": ["when", "while"], "answer_official": "when", "explain": null, "tags": []}, {"no": 6, "stem": null, "options": null, "answers_accept": ["next", "this"], "answer_official": "next", "explain": null, "tags": []}, {"no": 7, "stem": null, "options": null, "answers_accept": ["than"], "answer_official": "than", "explain": null, "tags": []}, {"no": 8, "stem": null, "options": null, "answers_accept": ["would", "'d"], "answer_official": "would", "explain": null, "tags": []}, {"no": 9, "stem": null, "options": null, "answers_accept": ["for"], "answer_official": "for", "explain": null, "tags": []}, {"no": 10, "stem": null, "options": null, "answers_accept": ["other"], "answer_official": "other", "explain": null, "tags": []}, {"no": 11, "stem": null, "options": null, "answers_accept": ["what"], "answer_official": "what", "explain": null, "tags": []}, {"no": 12, "stem": null, "options": null, "answers_accept": ["to", "I'll", "we'll"], "answer_official": "to", "explain": null, "tags": []}], "source": {"origin": "cepoc", "file": "assets/open-source/cepoc-main/KET.json", "orig_id": 2, "url": "https://github.com/CambridgeALTA/cepoc", "retrieved": "2026-08-21", "source_id": "cepoc", "source_item_id": "cepoc-ket-json"}, "license": "cc-nc-education", "tags": ["cloze", "open_cloze"], "note": "CEPOC 未标注套题年份与卷内 Part 号;空格数与 2020 版题型不完全对应(KET 11-12空/套、PET 6-8空/套),推断混有 2020 改版前真题。完形题型本身对应 A2 Key Reading & Writing / B1 Preliminary Reading 的 open cloze 题型。", "meta": {"multi_answer_items": 4}}
{"schema": "qb-1.1", "id": "a2k-cepoc-0003", "exam": "A2 Key", "cefr": "A2", "paper": "reading_writing", "part": null, "type": "open_cloze", "task_prompt": "For each gap, write ONE word which best completes the text.", "title": null, "passage": "Hi Marta,\n\nI hope _ are well. I was trying _ call you yesterday but you didn’t answer. Is your phone broken, _ have you lost it again? \n\nAnyway, I wanted to ask _ you can come to my birthday party next Saturday. My birthday was actually two weeks _ , but I couldn’t have the party then _ lots of people were _ holiday. \n\nI’ve invited _ few people from our swimming club, as well as my friends from school. _ you think your brother _ free that day? Please invite _ to come too. The party’s at my house and starts at 8p.m. \n\nParveen", "blank_token": "_", "items": [{"no": 1, "stem": null, "options": null, "answers_accept": ["you"], "answer_official": "you", "explain": null, "tags": []}, {"no": 2, "stem": null, "options": null, "answers_accept": ["to"], "answer_official": "to", "explain": null, "tags": []}, {"no": 3, "stem": null, "options": null, "answers_accept": ["or"], "answer_official": "or", "explain": null, "tags": []}, {"no": 4, "stem": null, "options": null, "answers_accept": ["if", "whether"], "answer_official": "if", "explain": null, "tags": []}, {"no": 5, "stem": null, "options": null, "answers_accept": ["ago"], "answer_official": "ago", "explain": null, "tags": []}, {"no": 6, "stem": null, "options": null, "answers_accept": ["because", "as"], "answer_official": "because", "explain": null, "tags": []}, {"no": 7, "stem": null, "options": null, "answers_accept": ["on"], "answer_official": "on", "explain": null, "tags": []}, {"no": 8, "stem": null, "options": null, "answers_accept": ["a"], "answer_official": "a", "explain": null, "tags": []}, {"no": 9, "stem": null, "options": null, "answers_accept": ["Do"], "answer_official": "Do", "explain": null, "tags": []}, {"no": 10, "stem": null, "options": null, "answers_accept": ["is"], "answer_official": "is", "explain": null, "tags": []}, {"no": 11, "stem": null, "options": null, "answers_accept": ["him"], "answer_official": "him", "explain": null, "tags": []}], "source": {"origin": "cepoc", "file": "assets/open-source/cepoc-main/KET.json", "orig_id": 3, "url": "https://github.com/CambridgeALTA/cepoc", "retrieved": "2026-08-21", "source_id": "cepoc", "source_item_id": "cepoc-ket-json"}, "license": "cc-nc-education", "tags": ["cloze", "open_cloze"], "note": "CEPOC 未标注套题年份与卷内 Part 号;空格数与 2020 版题型不完全对应(KET 11-12空/套、PET 6-8空/套),推断混有 2020 改版前真题。完形题型本身对应 A2 Key Reading & Writing / B1 Preliminary Reading 的 open cloze 题型。", "meta": {"multi_answer_items": 2}}
{"schema": "qb-1.1", "id": "a2k-cepoc-0004", "exam": "A2 Key", "cefr": "A2", "paper": "reading_writing", "part": null, "type": "open_cloze", "task_prompt": "For each gap, write ONE word which best completes the text.", "title": null, "passage": "Hi Marcia, \n\nDo you remember I wanted _ invite you, Sasha and John to my new house? Well, _ about next Saturday evening? We _ a beautiful garden with lots _ trees. We may have a barbeque outside _ it doesn’t rain. My mother is _ really good cook and I _ going to help her make the food. \n\nAfterwards, _ don’t we watch a film? _ you bring a DVD with you as I haven’t got many here? \n\nJohn’s coming _ bike and Sasha is getting a lift _ her father. I hope you’ll be able to come too. \n\nLet me know soon!\n\nConny", "blank_token": "_", "items": [{"no": 1, "stem": null, "options": null, "answers_accept": ["to"], "answer_official": "to", "explain": null, "tags": []}, {"no": 2, "stem": null, "options": null, "answers_accept": ["what", "how"], "answer_official": "what", "explain": null, "tags": []}, {"no": 3, "stem": null, "options": null, "answers_accept": ["have"], "answer_official": "have", "explain": null, "tags": []}, {"no": 4, "stem": null, "options": null, "answers_accept": ["of"], "answer_official": "of", "explain": null, "tags": []}, {"no": 5, "stem": null, "options": null, "answers_accept": ["if"], "answer_official": "if", "explain": null, "tags": []}, {"no": 6, "stem": null, "options": null, "answers_accept": ["a"], "answer_official": "a", "explain": null, "tags": []}, {"no": 7, "stem": null, "options": null, "answers_accept": ["am", "'m"], "answer_official": "am", "explain": null, "tags": []}, {"no": 8, "stem": null, "options": null, "answers_accept": ["why"], "answer_official": "why", "explain": null, "tags": []}, {"no": 9, "stem": null, "options": null, "answers_accept": ["Can", "Could"], "answer_official": "Can", "explain": null, "tags": []}, {"no": 10, "stem": null, "options": null, "answers_accept": ["by"], "answer_official": "by", "explain": null, "tags": []}, {"no": 11, "stem": null, "options": null, "answers_accept": ["from", "with"], "answer_official": "from", "explain": null, "tags": []}], "source": {"origin": "cepoc", "file": "assets/open-source/cepoc-main/KET.json", "orig_id": 4, "url": "https://github.com/CambridgeALTA/cepoc", "retrieved": "2026-08-21", "source_id": "cepoc", "source_item_id": "cepoc-ket-json"}, "license": "cc-nc-education", "tags": ["cloze", "open_cloze"], "note": "CEPOC 未标注套题年份与卷内 Part 号;空格数与 2020 版题型不完全对应(KET 11-12空/套、PET 6-8空/套),推断混有 2020 改版前真题。完形题型本身对应 A2 Key Reading & Writing / B1 Preliminary Reading 的 open cloze 题型。", "meta": {"multi_answer_items": 4}}
{"schema": "qb-1.1", "id": "a2k-cepoc-0005", "exam": "A2 Key", "cefr": "A2", "paper": "reading_writing", "part": null, "type": "open_cloze", "task_prompt": "For each gap, write ONE word which best completes the text.", "title": null, "passage": "How _ you? I’m writing to you _ I need your help.\n\nI’m starting a business course at college _ term. The teacher has asked us to do some office work during _ summer. Your Dad’s _ manager, isn’t he? Can you ask him _ I can work with him? I only need to work _ a few weeks, and he will _ have to pay me anything.\n\nAlso, can I stay _ you while I’m working? Mum says it’s much _ far to go by bike _ day. Tell your mum that I can make _ own meals, so she won’t have to cook for me.\n\nThanks.", "blank_token": "_", "items": [{"no": 1, "stem": null, "options": null, "answers_accept": ["are"], "answer_official": "are", "explain": null, "tags": []}, {"no": 2, "stem": null, "options": null, "answers_accept": ["because", "as", "since"], "answer_official": "because", "explain": null, "tags": []}, {"no": 3, "stem": null, "options": null, "answers_accept": ["this", "next"], "answer_official": "this", "explain": null, "tags": []}, {"no": 4, "stem": null, "options": null, "answers_accept": ["the"], "answer_official": "the", "explain": null, "tags": []}, {"no": 5, "stem": null, "options": null, "answers_accept": ["a"], "answer_official": "a", "explain": null, "tags": []}, {"no": 6, "stem": null, "options": null, "answers_accept": ["if"], "answer_official": "if", "explain": null, "tags": []}, {"no": 7, "stem": null, "options": null, "answers_accept": ["for"], "answer_official": "for", "explain": null, "tags": []}, {"no": 8, "stem": null, "options": null, "answers_accept": ["not"], "answer_official": "not", "explain": null, "tags": []}, {"no": 9, "stem": null, "options": null, "answers_accept": ["with"], "answer_official": "with", "explain": null, "tags": []}, {"no": 10, "stem": null, "options": null, "answers_accept": ["too"], "answer_official": "too", "explain": null, "tags": []}, {"no": 11, "stem": null, "options": null, "answers_accept": ["each", "every"], "answer_official": "each", "explain": null, "tags": []}, {"no": 12, "stem": null, "options": null, "answers_accept": ["my"], "answer_official": "my", "explain": null, "tags": []}], "source": {"origin": "cepoc", "file": "assets/open-source/cepoc-main/KET.json", "orig_id": 5, "url": "https://github.com/CambridgeALTA/cepoc", "retrieved": "2026-08-21", "source_id": "cepoc", "source_item_id": "cepoc-ket-json"}, "license": "cc-nc-education", "tags": ["cloze", "open_cloze"], "note": "CEPOC 未标注套题年份与卷内 Part 号;空格数与 2020 版题型不完全对应(KET 11-12空/套、PET 6-8空/套),推断混有 2020 改版前真题。完形题型本身对应 A2 Key Reading & Writing / B1 Preliminary Reading 的 open cloze 题型。", "meta": {"multi_answer_items": 3}}
{"schema": "qb-1.1", "id": "a2k-cepoc-0006", "exam": "A2 Key", "cefr": "A2", "paper": "reading_writing", "part": null, "type": "open_cloze", "task_prompt": "For each gap, write ONE word which best completes the text.", "title": null, "passage": "Welcome _ my blog! My name’s Lauren and I’m 22 years old. I come from\n\nIllinois in the USA, but _ the moment I’m studying in Florence, in Italy.\n\nI’m living _ a friend of mine from university. Our apartment _ in the centre of town, so we don’t _ to use the buses – we can walk everywhere. I have classes most days, but in _ afternoon, I explore the city or _ swimming. When _ is sunny I visit the Boboli Gardens. _ are beautiful views of Florence from here.\n\nI _ you enjoy my blog. _ you have any questions, please leave me a message.", "blank_token": "_", "items": [{"no": 1, "stem": null, "options": null, "answers_accept": ["to"], "answer_official": "to", "explain": null, "tags": []}, {"no": 2, "stem": null, "options": null, "answers_accept": ["at"], "answer_official": "at", "explain": null, "tags": []}, {"no": 3, "stem": null, "options": null, "answers_accept": ["with"], "answer_official": "with", "explain": null, "tags": []}, {"no": 4, "stem": null, "options": null, "answers_accept": ["is"], "answer_official": "is", "explain": null, "tags": []}, {"no": 5, "stem": null, "options": null, "answers_accept": ["have", "need"], "answer_official": "have", "explain": null, "tags": []}, {"no": 6, "stem": null, "options": null, "answers_accept": ["the"], "answer_official": "the", "explain": null, "tags": []}, {"no": 7, "stem": null, "options": null, "answers_accept": ["go"], "answer_official": "go", "explain": null, "tags": []}, {"no": 8, "stem": null, "options": null, "answers_accept": ["it"], "answer_official": "it", "explain": null, "tags": []}, {"no": 9, "stem": null, "options": null, "answers_accept": ["There"], "answer_official": "There", "explain": null, "tags": []}, {"no": 10, "stem": null, "options": null, "answers_accept": ["hope"], "answer_official": "hope", "explain": null, "tags": []}, {"no": 11, "stem": null, "options": null, "answers_accept": ["If"], "answer_official": "If", "explain": null, "tags": []}], "source": {"origin": "cepoc", "file": "assets/open-source/cepoc-main/KET.json", "orig_id": 6, "url": "https://github.com/CambridgeALTA/cepoc", "retrieved": "2026-08-21", "source_id": "cepoc", "source_item_id": "cepoc-ket-json"}, "license": "cc-nc-education", "tags": ["cloze", "open_cloze"], "note": "CEPOC 未标注套题年份与卷内 Part 号;空格数与 2020 版题型不完全对应(KET 11-12空/套、PET 6-8空/套),推断混有 2020 改版前真题。完形题型本身对应 A2 Key Reading & Writing / B1 Preliminary Reading 的 open cloze 题型。", "meta": {"multi_answer_items": 1}}

查看文件

@@ -0,0 +1,101 @@
# 词库 INDEX — 数据总览与使用指南
> 更新:2026-08-21 | 解析器:`scripts/parse_vocab.py`(pymupdf,纯规则解析,零 LLM 成本)
> 对应计划:题库路线 P3(官方词表 → 结构化词库)+ P3.5(词库增补:中文释义 + IPA 音标)
> 增补管线:`scripts/enrich_gloss.py`(ECDICT 匹配)+ `scripts/make_llm.py`(LLM 增补)+ `scripts/finalize_vocab.py`(音标格式统一 + 补常用词音标)
## 一、现有数据
| 文件 | 级别 | 主表词条 | 仅主题表补充词 | 合计 | 其中 B1 新增 | 来源 | 许可 |
|---|---|---|---|---|---|---|---|
| `a2-key-vocab.jsonl` | A2 Key / for Schools | 1727 | 13 | **1740** | — | 剑桥官方词汇表(2025-08 版) | `official-free` |
| `b1-preliminary-vocab.jsonl` | B1 Preliminary / for Schools | 3055 | 117 | **3172** | 1517(`new_at_b1=true`) | 剑桥官方词汇表(2020 版) | `official-free` |
| `word-sets.json` | A2+B1 通用 | — | — | 6 个词集 | — | Appendix 1(规则生成) | `official-free` |
主题(topic)覆盖:A2 25 个 / B1 23 个(Clothes、Education、Food and Drink、Travel and Transport、Work and Jobs 等)。
## 二、记录结构(每行一个词条)
```json
{
"id": "a2k-vocab-0091",
"level": "A2",
"headword": "attractive", // 原样保留, 含变体斜杠 "café/cafe"、缩写 "(kg)"
"pos": ["adj"], // 词性数组; 多词词性整体保留: "phr v"/"prep phr"/"aux v"
"pos_notes": null, // 用法/地区注记: ["+ ing"], ["(Br Eng) (Am Eng: bathing suit)"], ["unc (uncountable)"]
"examples": [{"text": "The walls are an attractive colour.", "pos": null}],
"phrases": null, // 子短语/搭配: "(be) bad for somebody", "bright pink"
"topics": ["Clothes and Accessories"], // 来自 Appendix 2 主题表
"origin": "main_list", // main_list=字母序主表 / topic_list=仅出现在主题表
"no_pos": true, // 仅少数: PDF 原文无词性标注 (如 "film star")
"new_at_b1": false, // 仅 B1 文件: 该词不在 A2 词表 = B1 新增
"source": {"file": "...", "url": "...", "publisher": "Cambridge English / UCLES", "licence": "official-free"},
"gloss": { // P3.5 增补: 中文释义 + IPA 音标
"zh": "n. 苹果", // 带词性前缀的中文释义
"phonetic": "/ˈæpl/", // 英式 IPA, 统一包裹为 /.../
"source": "ecdict", // ★ 来源标注: "ecdict"=ECDICT 开源词典 / "llm"=LLM 生成
"licence": "CC-BY-SA-4.0", // ecdict 对应 CC-BY-SA-4.0; llm 对应 llm-generated
"phonetic_source": "llm" // 可选: 仅当音标由 LLM 补、释义来自 ECDICT 时出现
}
}
```
## 三、下游 agent 使用须知
1. **背单词优先序**(推荐):A2 全部 → B1 中 `new_at_b1=true` 的 1517 词 → topic_list 补充词。两级共约 3257 去重词条。
2. **例句仍是稀缺资源**:官方词表仅约 11% 词条带例句(A2 170 / B1 218 条),主要标注多义词的分用法。**中文释义与音标已通过 P3.5 增补进 `gloss` 字段**(见 §三·五),但例句本身未生成。
3. **主题背诵**:`topics` 字段支持按场景刷词(如先攻 Food and Drink);同一词可属多个主题。
4. **词性坑**:`be` 的词性是 `["aux v","v"]`(PDF 原文缩写 "av",按剑桥缩写表解释为助动词);不可数名词注记在 `pos_notes`(如 rubbish)。
5. **topic_only 词**(A2 13 / B1 117):官方明确为"考生还应知道的补充词"(如 get dressed、climate change),无例句无主表词性,但 `pos` 可能来自主题表标注。
6. **变体词头**:`trainers`(topic)与 `trainer`(主表)已按复数回退匹配;但 A2 主表无 trainer,故 A2 里 trainers 仍是独立补充词。
7. **word-sets.json**:基数/序数/星期/月份/季节/大洲为确定性生成集;国家·语言·民族开放集合未枚举(官方用 etc.),需要时另行补表。
8. **许可**:全部 `official-free`,可进部署门户;但 PDF 内的 "Additions in green"(2025 新增词标记)在文本层无颜色信息,未能结构化。
## 三·五、P3.5 增补说明(中文释义 + IPA 音标)
官方 PDF 无中文释义、无音标,本步补齐 `gloss` 字段(每条记录已写入,无遗漏)。
**来源标注(用户要求:区分「原词条」与「LLM 增补」)**
- `gloss.source = "ecdict"`:中文释义来自 **ECDICT** 开源英汉词典(CC-BY-SA-4.0),词典级数据,覆盖约 98%。
- `gloss.source = "llm"`:ECDICT 未命中的残余词头(多为短语/复合词/剑桥多义分项,如 `deal with`、`smart (clever)`、`social media`),由 **LLM 生成**中文释义 + 音标(`licence: llm-generated`)。共 98 条记录(83 个去重词头)。
- `gloss.phonetic_source = "llm"`(可选):仅 97 条「释义来自 ECDICT、但 ECDICT 原条目缺音标」的常用单词,音标由 LLM 补、释义 provenance 不变。
**覆盖统计(4902 条)**
| 指标 | 数值 |
|---|---|
| `gloss` 覆盖率 | 100%(4902/4902) |
| `source=ecdict` | 4804 条 |
| `source=llm` | 98 条 |
| 有音标 | 4576 条(93.3%) |
| 无音标 | 326 条(6.7%,**全部为短语/多词**,如 `a few`、`alarm clock`) |
**为什么短语留空音标**:学习者已掌握组成单词的发音,短语整体音标优先级低;若后续需要可单独补(g2p 模型或 LLM)。
**管线与复现**
```bash
cd english-site
python scripts/enrich_gloss.py match # ECDICT 匹配 -> _gloss_cache/ecdict.json
python scripts/make_llm.py # LLM 增补 83 词 -> _gloss_cache/llm.json
python scripts/enrich_gloss.py merge # 合并写回 *.jsonl
python scripts/finalize_vocab.py # 音标统一 /.../ + 补 71 常用单词音标
```
> ECDICT 原始数据:`assets/vocab/ecdict/`(StarDict 格式,67MB,从 github release 经 gh-proxy 镜像下载)。清洗规则见 `enrich_gloss.py: clean_entry`(剥离词频/考试标签、西里尔 ә→ə、ASCII 撇号→IPA 重音符等)。
## 四、已知局限(诚实披露)
- ~~官方 PDF 无中文释义、无音标~~ → 已在 P3.5 通过 ECDICT + LLM 增补进 `gloss` 字段(见 §三·五);仅 326 条短语(6.7%)因 ECDICT 无音标而留空,释义仍在。
- `film star`、`zebra` 等 2 个 B1 词条原文无词性(`no_pos=true`),非解析丢失。
- `driving/driver's licence` 等极少数跨行词头的地区变体注记可能不完整(已抽查主流 5 例均正确)。
- B1 词表基于 2018 © 版本(2020 起生效),A2 基于 2025-08 版,两版之间 A2 的新增词(若 B1 版本更旧)不影响 `new_at_b1` 的判断方向,但个别词的归属可能滞后。
## 五、复现
```bash
cd english-site
python scripts/parse_vocab.py # 输出到 data/vocab/, 隔离区 0 条, id 唯一校验通过
```
## 六、素材勘误(相对调研报告)
- `assets/vocab/b1-preliminary-vocabulary-booklet.pdf`(8MB)**不是词表**,是《B1 for Schools 词汇教学活动手册》(教师用),保留作教学设计参考。
- B1 官方词表直链 `cambridgeenglish.org/Images/506887-*.pdf` 已 404,改用 **cambridgeenglish.cn 域名**下载成功(文件已存 `assets/vocab/b1-preliminary-2020-vocabulary-list.pdf`)。

文件差异内容过多而无法显示 加载差异

查看文件

@@ -0,0 +1,201 @@
{
"note": "Appendix 1 Word sets 为开放集合(官方原文用 etc.), 此处按规则生成确定词集。国家/语言/民族名开放集合未枚举, 需另行补表。",
"levels": [
"A2",
"B1"
],
"sets": [
{
"name": "Cardinal numbers (one to one hundred + hundred/thousand)",
"words": [
"eight",
"eighteen",
"eighty",
"eighty-eight",
"eighty-five",
"eighty-four",
"eighty-nine",
"eighty-one",
"eighty-seven",
"eighty-six",
"eighty-three",
"eighty-two",
"eleven",
"fifteen",
"fifty",
"fifty-eight",
"fifty-five",
"fifty-four",
"fifty-nine",
"fifty-one",
"fifty-seven",
"fifty-six",
"fifty-three",
"fifty-two",
"five",
"forty",
"forty-eight",
"forty-five",
"forty-four",
"forty-nine",
"forty-one",
"forty-seven",
"forty-six",
"forty-three",
"forty-two",
"four",
"fourteen",
"hundred",
"nine",
"nineteen",
"ninety",
"ninety-eight",
"ninety-five",
"ninety-four",
"ninety-nine",
"ninety-one",
"ninety-seven",
"ninety-six",
"ninety-three",
"ninety-two",
"one",
"seven",
"seventeen",
"seventy",
"seventy-eight",
"seventy-five",
"seventy-four",
"seventy-nine",
"seventy-one",
"seventy-seven",
"seventy-six",
"seventy-three",
"seventy-two",
"six",
"sixteen",
"sixty",
"sixty-eight",
"sixty-five",
"sixty-four",
"sixty-nine",
"sixty-one",
"sixty-seven",
"sixty-six",
"sixty-three",
"sixty-two",
"ten",
"thirteen",
"thirty",
"thirty-eight",
"thirty-five",
"thirty-four",
"thirty-nine",
"thirty-one",
"thirty-seven",
"thirty-six",
"thirty-three",
"thirty-two",
"thousand",
"three",
"twelve",
"twenty",
"twenty-eight",
"twenty-five",
"twenty-four",
"twenty-nine",
"twenty-one",
"twenty-seven",
"twenty-six",
"twenty-three",
"twenty-two",
"two"
]
},
{
"name": "Ordinal numbers (first to thirty-first)",
"words": [
"first",
"second",
"third",
"fourth",
"fifth",
"sixth",
"seventh",
"eighth",
"ninth",
"tenth",
"eleventh",
"twelfth",
"thirteenth",
"fourteenth",
"fifteenth",
"sixteenth",
"seventeenth",
"eighteenth",
"nineteenth",
"twentyth",
"twenty-first",
"twenty-second",
"twenty-third",
"twenty-fourth",
"twenty-fifth",
"twenty-sixth",
"twenty-seventh",
"twenty-eighth",
"twenty-ninth",
"thirtieth",
"thirty-first"
]
},
{
"name": "Days of the week",
"words": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
]
},
{
"name": "Months of the year",
"words": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
]
},
{
"name": "Seasons of the year",
"words": [
"spring",
"summer",
"autumn",
"winter"
]
},
{
"name": "Continents",
"words": [
"Africa",
"Antarctica",
"Asia",
"Australia",
"Europe",
"North America",
"South America"
]
}
]
}