Skip to main content
Glama
README.md
# kp-graph-408

面向计算机考研 408 的**考点图谱与教学诊断记忆层**:提供细粒度考点编号体系、题目—考点映射、带置信度的跨科关系、答题轨迹 schema 与诊断查询。

不提供刷题界面、RAG、向量库、题库正文、教材正文。前端与检索层可由使用者自行搭配或替换。

> **中立声明**:本项目不包含、不分发、不引导获取任何第三方题库、教材或真题内容。仓库内不含题库下载链接、爬虫、破解或绕过付费/DRM 的代码。导入器只接受使用者自备的合法数据,**导入数据的合法性由使用者负责**。详见 [`DATA-LICENSE.md`](DATA-LICENSE.md)。

---

## 这个项目提供什么

- 细粒度考点编号体系(`kp_code`)
- 题目—考点多对多映射 schema
- 考点之间的跨科关系 schema(带 `origin` / `confidence`)
- 答题轨迹与**考点级错因** schema
- 诊断查询(`broken_links`)
- MCP 工具契约

## 这个项目解决什么

市面上的 408 工具大多停在「问了有答案、刷了有对错」。它们缺的是下面这一层:

- 一个**稳定编号的考点体系**(不是按章节标题,而是可被程序引用的 `kp_code`)
- 题目到考点的**多对多映射**,带主次权重与置信度
- 考点之间的**跨科关系**(Cache ↔ TLB、页表 ↔ 映射方式、调度队列 ↔ 队列结构),且关系本身带来源与置信度
- 答题轨迹里**考点级的错因**,而不是只有对错
- 由此才能得出的结论:**「你 A 掌握了,但没迁移到 B」**

最后一条是整个项目的验收标准。它在 `diagnose.broken_links()` 里。

---

## 包结构

同一仓库发布两个包,职责不重叠:

| 包 | 角色 | 入口 |
|---|---|---|
| `kp_graph` | **核心数据层**:schema、导入器、校验器、诊断查询、讲解层(生活类比)、真题抽取链路 | `python -m kp_graph.cli` |
| `tutor_memory` | **MCP 服务层**:把 `kp_graph` 的能力暴露成 MCP 工具契约,外加可插拔记忆层(`MemoryBackend` 协议 + 内置 none/memory/json/eraherm) | `python -m tutor_memory.server` |

`tutor_memory` 只是 `kp_graph` 的协议外壳,不含独立数据逻辑。单独用 `kp_graph`(写脚本、做批处理)完全可行;只想接 Agent 时用 `tutor_memory`。

`kp_graph` 内部分层:`diagnose`(诊断:断链/弱点/下一步)与 `explain`(讲解:生活类比)职责分开——一个回答「哪里塌了」,一个回答「该怎么讲」,都输出纯数据、不调 LLM。
抽取链同样分层:`exam_pdf`(PDF → 文本块 + 图)→ `exam`(文本 → 题目,纯函数)→ `importer`(题目 → 库),`textmatch` 是 `migrate_legacy` 与 `exam` 共用的打分层。

MCP 部分只做 **manifest + stdio server 骨架**,`mcp[cli]` 为可选依赖:未安装时 `--manifest` 仍可打印全部工具契约,`server.py` 单文件、无硬依赖,全部逻辑在 `tools.py`(纯函数,可单测)。

---

## 快速开始

```bash
python -m kp_graph.cli seed --dir examples/seed
python -m kp_graph.cli validate
python -m kp_graph.cli coverage
python examples/simulate_student.py
python -m kp_graph.cli diagnose --student S-001
python -m kp_graph.cli analogy --kp DS-08-01-03   # 把考点讲成生活场景
```

在**真实考纲考点表**(387 条)上跑跨科诊断:

```bash
python scripts/build_kp_ds.py && python scripts/build_kp_co.py
python scripts/build_kp_os.py && python scripts/build_kp_cn.py
python scripts/build_cross_relation.py
python examples/simulate_cross_subject.py
```

`simulate_student.py` 造一个在「Cache 映射」上全对、在跨科同构的「页表与地址转换」上全错的学生,输出:

```
关系置信度 >= 0.9:
1. [high] OS-03-02-01  (broken_link)
   CO「Cache 映射方式」正确率 100%(3 题),OS「页表与地址转换」仅 0%(3 题),
   二者关系为 analogous:概念已具备但未迁移
```

历史数据导入与错因补判(只有对错、没有错因的老数据走这条路径):

```bash
python -c "from kp_graph import db, importer; c=db.init_db(); importer.record_attempt(c, {'student_id':'S-002','question_id':'q-OS-0002','chosen':'D','is_correct':False,'defer_diagnosis':True})"
python -m kp_graph.cli pending
python -m kp_graph.cli resolve --attempt 1 --kp OS-03-02-01 --error-type knowledge_gap
```

测试与 MCP:

```bash
python -m unittest discover -s tests -v
python -m tutor_memory.server --manifest
pip install "mcp[cli]"   # 可选;安装后 python -m tutor_memory.server 即 stdio MCP server
```

导出教学记忆,并推送到任意记忆层(换记忆层只改 `--backend`,不改代码):

```bash
python -m kp_graph.cli export-memory --student S-001                        # 只打印 JSON
python -m kp_graph.cli export-memory --student S-001 --backend json --dry-run  # 看要写什么
python -m kp_graph.cli export-memory --student S-001 --backend json --push    # 真写入

# 内置:none(默认)/ memory / json / eraherm;第三方用 module:factory
# 也可用环境变量 KP408_MEMORY_BACKEND 指定
```

`tutor_memory/memory_backend.py` 定义了 `MemoryBackend` 协议(`remember` / `recall` /
`supersede` / `capabilities` / `health`,`user_id` 贯穿全部方法)。新增一家记忆层只需实现这五个方法
并暴露一个工厂函数,本项目零改动 —— 不支持纠正的后端会自动降级成「正文标记旧判断作废」的追加写入。

---

## 真题 PDF → 题库(抽取链路)

`extract_exam` 把 PDF 变成 `importer` 能直接吃的题目 JSON,中间不做任何「猜」:

```bash
# 1. 只抽不写库(题 + 图 + 文本块)
python -m kp_graph.cli extract --pdf 2024年408真题.pdf --out-dir extracted/2024

# 2. 一条命令跑到底:抽取 → 入库 → 规则候选 → 覆盖统计
python -m kp_graph.cli --db all.sqlite3 import-exam --pdf 2024年408真题.pdf

# 3. 看还差哪些考点的题
python -m kp_graph.cli --db all.sqlite3 coverage --missing 40
```

产出两个文件:

| 文件 | 内容 |
|---|---|
| `extract.json` | 图(含 bbox 与归属题号)、每页文本块、拒绝入库的题及原因 |
| `questions.json` | 可直接 `import --questions` 的题目数组,`kp` 留空 |

三层职责不混:

- `exam_pdf` 只把 PDF 变成「文本块 + 裁好的图」,图按 y 坐标挂到上方最近的题号
- `exam` 只把文本块变成「题号 / 题干 / 选项 / 答案 / 解析」,不碰 PDF,因此可以纯文本单测
- `importer` 只把结构化 JSON 写库,不做语义判断

**默认不替你拍板**,这是与「一键生成题库」最大的差别:

- `suggest_kp` 的候选分数硬封顶 0.85,落库阈值默认 0.9 —— 题干提到某个术语不等于这题考它,导入后 `question_kp` 一定是空的
- 想让规则先垫一层必须显式降阈值:`--min-confidence 0.8`,且写入的 `method='rule'`
- 没抽到答案的题**直接拒绝并列出原因**,不伪造字段(`question.answer` 是 NOT NULL)
- 题号落不进任何科目区间的题同样拒绝:`subject` 有 CHECK 约束,猜一个比丢掉更糟

### 卷面结构(题号 → 科目)

`syllabus/exam_spans.json` 只登记公开的卷面结构(题型、题号跨度),`years` 里没登记的年份回落到 `default`,
并在结果里标 `span_source=default(entity=default)` —— 科目是推断出来的,这件事必须留痕。用 `--structure <json>` 可覆盖。

### 扫描件

无文本层的 PDF 走降级路径:整页渲染成 `pNN_scan.png`,文字靠人工转录;加 `--ocr` 才调 Tesseract
(需要系统装 tesseract 且有 `chi_sim` 语言包,只 `pip install` 是不够的)。
缺 Tesseract 时只报错不抛异常 —— 不能因为没装 OCR 就让整份卷子作废。

扫描件上的图**没法单独裁出来**:一页就是一张位图,文字与图形不可分离。这是该路径的硬限制,不是待办事项。

### 已知边界

- 综合题的答案与解析在卷面上通常不分离,抽取结果里会合并进 `answer` 并标 warning
- 抽取出的题一律标 `source` 为「PDF 抽取,需人工校对」,`license_status=user_supplied`
- 试卷排版差异大(选项并排、跨页题干、答案夹注),正则覆盖不到的地方靠 `warnings`/`rejected` 暴露,不静默丢弃

### Agent 侧(任意支持 MCP 的客户端)

这五个工具专门给 Agent 用 —— 人的「复核」环节正好由模型判断承担:

| 工具 | 用途 |
|---|---|
| `extract_exam_pdf` | 拿到 PDF 先看摘要与预览,别把整卷正文塞回上下文 |
| `import_exam_questions` | 预览无误后导入题库 |
| `get_question_kp_candidates` | 每题取候选考点(封顶 0.85,需复核) |
| `map_question_kp` | 复核后回写映射 |
| `get_coverage` | 查还差哪些考点的题 |

整条链不依赖任何外部题库,Agent 只需要使用者自备的 PDF。

接进 MCP 客户端(Claude Desktop / Cursor / Hermes / OpenClaw 都是同一套 `mcpServers` 的变体,字段名以各自文档为准):

```jsonc
{
  "mcpServers": {
    "kp-graph-408": {
      "command": "python",
      "args": ["-m", "tutor_memory.server", "--db", "<绝对路径>/all.sqlite3"],
      "cwd": "<绝对路径>/kp-graph-408"
    }
  }
}
```

先用 `python -m tutor_memory.server --manifest` 确认 18 个工具都列得出来 —— 没装 `mcp[cli]` 也能打印,
所以配置排错了能一眼分清是「没装依赖」还是「路径错」。

**本项目不绑定任何 Agent,也不绑定任何记忆层。** 是否需要再接一层长期记忆、接哪一家,由使用者自己选;
选型标准、可插拔边界与踩坑清单见 [`AGENT-MEMORY.md`](AGENT-MEMORY.md)。

---

## 考点编号规则

```
SUBJECT[-NN]...        例: DS-08-01-03
  depth 1 = 章   2 = 节   3 = 考点   4 = 子考点 / 题型细分
```

四条硬规则,`validate` 强制检查:前缀与 `subject` 一致、`depth` 等于编号级数、非章级必须挂 `parent`、`parent` 不得跨科。

### 编号体系的来源与许可证

**本项目的考点编号体系为原创**:按 408 考试大纲的章节结构自行编码,`kp_code` 只承诺结构稳定,不复刻任何第三方材料的小节标题。

- 不从任何第三方材料的清洗整理稿提取编号或标题。标题一律使用考纲通用术语(如「Cache 映射方式」「页表与地址转换」),不使用某本教材的独有表述。
- `kp.origin` 记录每行产出方式:`syllabus`(考纲)/ `manual`(人工)/ `llm`(模型生成,必须人工复核)。`origin='llm'` 且 `confidence<0.8` 的行会被校验器 warn。
- 若将来确需引入第三方来源,必须在 [`DATA-LICENSE.md`](DATA-LICENSE.md) 逐条登记来源、授权方式、覆盖范围、引入日期,不得静默混入。

上一代 284 条知识点通过 `kp_legacy_map` 保留可回溯映射,不直接改 `code`。一条旧知识点可映射到多个新考点,未人工确认的多元映射会被标 warn。

---

## 跨科关系与置信度闸门

关系类型:`prereq` 依赖 / `analogous` 类比 / `contrast` 易混 / `compose` 组成。

`kp_relation` 带 `origin` 与 `confidence`,这是诊断可信度的闸门:

| confidence | 含义 |
|---|---|
| 1.0 | 人工按考纲结构确认 |
| 0.6 ~ 0.8 | 规则推断(同名词、同层级、跨科同名考点) |
| < 0.6 | LLM 抽取候选,未复核 |

`broken_links` 默认只消费 `confidence >= 0.6` 的边(`--min-confidence` 可调),未复核关系不会进入诊断结论。示例数据里留了一条 `origin=llm, confidence=0.3` 的边,`validate` 会同时报 `rel.low_confidence` 与 `rel.llm_unreviewed`。

**边端点的正确率按子树聚合(rollup)**,这是一个容易踩空的坑:真实关系常挂在章或节上(`OS-03-02 --analogous--> CO-03-06`),而题目与作答只发生在考点级(`OS-03-02-01`)。只比边端点自身的正确率,两端都查不到记录,**跨科断链在真实数据上恒为空**。实测:子考点作答时,`rollup=False` 输出 0 条,`rollup=True` 输出 1 条。`--no-rollup` 可关闭对照。

gap 相同的多条断链按 `weak_depth` 降序,让建议落到具体考点而不是整个章节。

> 诊断查询本身不调用 LLM,可复现、可审计。但**诊断质量由关系图谱的构建质量决定**——「不需要 LLM」不等于「关系数据天然可信」,这也是关系必须带置信度的原因。

---

## 生活类比:解释层,不是定义层

抽象概念挂在 `kp` 上是一句话(「Cache 和主存之间的映射方式」),学生看不懂的不是这句话,是它背后的机制。`kp_analogy` 把考点映射到学习者已经熟悉的场景:抽屉与档案室、食堂餐盘堆、酒店超售。

**每条类比必须写 `breaks_at`。** 这不是装饰字段:任何类比都有失效边界,「内存像桌面」会让学生以为内存断电不丢东西、「出队像排队」解释不了循环队列。「不标边界的类比比不讲类比更糟」是这张表的设计前提,`validate` 会对空 `breaks_at` 报 `analogy.no_breakpoint`。

闸门与 `kp_relation` 一致:

| 字段 | 作用 |
|---|---|
| `origin` | `manual`(人写)/ `llm`(模型起草,必须复核) |
| `confidence` | 低于 0.6 不进入讲解 |
| `reviewed` | 只有 `1` 才进入讲解;导入时 `manual` 默认 1,`llm` 默认 0 |

一个考点可以挂多个类比;查叶子考点时默认**沿 parent 链继承**上级章节的通用类比(自身的最先返回),`--no-ancestors` 可关闭。

```bash
# 把考点讲成生活场景(含失效边界)
python -m kp_graph.cli --db all.sqlite3 analogy --kp CO-03-06-02

# 按某个学生的弱考点批量看类比
python -m kp_graph.cli --db all.sqlite3 analogy --student S-001

# 诊断输出默认附一条弱端的类比,--no-analogy 关掉
python -m kp_graph.cli --db all.sqlite3 diagnose --student S-001

# 导入(自己写或模型起草后人工过一遍)
python -m kp_graph.cli --db all.sqlite3 import --analogy examples/analogy_sample.json
```

`examples/seed/analogy.json`(7 条,四科齐全,含一条故意留空边界的 LLM 草稿用于演示闸门)随 `seed` 一起导入;`examples/analogy_sample.json` 面向 387 条真实考纲库。MCP 侧对应 `get_kp_analogy` 工具。

`validate` 的类比相关告警:`analogy.no_breakpoint` / `analogy.bad_mapping` / `analogy.unreviewed` / `analogy.low_confidence`(后两者聚合成一条,不刷屏)。

---

## 数据表

| 表 / 视图 | 作用 |
|---|---|
| `kp` | 考点,含 `origin` / `confidence` / `note`(低置信原因,复核依据) |
| `kp_relation` | 关系,含 `origin` / `confidence` / `weight` |
| `question` | 题目结构;`license_status` 标注来源合法性 |
| `question_kp` | 多对多映射,带 `weight` 与 `method`(manual/rule/llm) |
| `attempt` | 作答记录,含自评把握、耗时、`diagnosis_status` |
| `attempt_kp_diagnosis` | **考点级错因**,每题每个涉及考点一行 |
| `teaching_memory` | 教学策略记忆:`strategy` / `outcome` / `evidence` |
| `kp_legacy_map` | 旧编号 → 新编号 |
| `kp_analogy` | 生活案例类比(场景 / 逐项映射 / **失效边界**),带 reviewed 闸门 |
| `v_cross_kp_relation` | 跨科关系(含 origin/confidence) |
| `v_kp_accuracy` | 学生 × 考点正确率(**排除 pending**) |
| `v_pending_diagnosis` | 待判定错因队列 |
| `v_kp_coverage` | 考点题目覆盖数 |

### 错因与 pending 过渡态

错因枚举:`concept_confusion` / `calculation` / `misread` / `forgetting` / `knowledge_gap` / `none` / `pending`。

- 实时作答:答错不给 `diagnoses` 直接抛错,宁可失败不写脏数据
- 历史数据导入:显式传 `defer_diagnosis=True`,为该题所有考点写 `pending`,`attempt.diagnosis_status='pending'`
- `pending` **不参与正确率统计**(`v_kp_accuracy` 显式过滤),避免把「未判定」当「答错」
- 补判用 `resolve_diagnosis` / `python -m kp_graph.cli resolve`,该 attempt 的 pending 清零后自动转 `complete`

**为什么错因挂在考点而不是题目上**:同一道综合题两道小问可能分别塌在不同考点。只记题目对错,诊断层拿不到任何可用信号。

---

## teaching_memory 的归属边界

**这张表在本项目中只是 schema 规范 + 本地工作副本,不是跨项目记忆主存储。**

本项目不承担记忆治理职责:不做分层淘汰、不做跨科目聚合、不做跨会话召回。这些是**记忆层**的职责 —— [EraHerm-Memory](https://github.com/yangwenhua212/eraherm-memory)(MIT,© 2026 杨文华)是其中一例,换成 Zep / Mem0 / 自建表同样成立。把记忆主存储绑在单个学科项目里,换个科目就是孤岛。

已实现的一个适配器在 [`tutor_memory/eraherm_bridge.py`](tutor_memory/eraherm_bridge.py)(面向 EraHerm-Memory,尚未抽象成通用接口),核心映射:

| kp-graph-408 | EraHerm-Memory |
|---|---|
| `student_id` | `user_id`,形如 `kp408:<student_id>` |
| `kp_code` + 考点标题 | `content` 开头的考点锚点 |
| `strategy` / `outcome` | `content` 主体 |
| `evidence` | `metadata.evidence`,同时以 JSON 围栏块嵌入 `content` 兜底 |
| `status='effective'` | `pinned=true`(不被时间衰减淘汰) |
| `status='failed'` | **不导出**(失败策略只留本地避坑) |
| `superseded_by` | 取代链 / `eraherm_evolve`(纠正即进化) |

导出的 `content` 刻意保留 `kp_code` 与考点标题原文:EraHerm 的召回是语义 + 词法混合,术语写全比写得漂亮更有用。

---

## 408 题型约束

408 统考只有**单选题**与**综合应用题**。schema 中保留 `multi` 仅为未来扩展到非 408 科目,校验器会对 408 科目下的 `multi` 报 warn,示例数据也不含多选。

---

## 示例数据的边界

`examples/seed/` 只放自编示例数据,规模固定:

- **22 条**示例考点
- **5 道**自编示例题(题干、选项、答案、解析均为原创或通用表述)
- 关系与旧编号映射仅用于演示闸门效果

`tests/test_smoke.py` 中有断言锁定这两个数字,防止示例数据膨胀成事实上的题库。

`data/`、`*.sqlite`、`*.db`、用户导入文件一律由 `.gitignore` 排除,不进入版本库。

---

## 真实考纲目录(四科全部落地)

考纲的章节层级是公开发布的官方文件结构,属于「原材料」;本项目做的是「加工」——给每一级分配稳定的 `kp_code` 与 `depth`,并校验编号规则能否覆盖官方层级。**不涉及任何教材或题库正文。**

`examples/seed/` 受 22 条上限约束,所以真实目录放在独立的 `syllabus/` 目录,不影响示例数据边界。

```
syllabus/kp_DS.json            71 条:7 章 / 40 节 / 24 三级 /  0 四级
syllabus/kp_CO.json           102 条:6 章 / 30 节 / 59 三级 /  7 四级
syllabus/kp_OS.json           104 条:5 章 / 18 节 / 62 三级 / 19 四级
syllabus/kp_CN.json           110 条:6 章 / 32 节 / 69 三级 /  3 四级
                              合计 387 条考点
syllabus/kp_*_relation.json    58 条科内先修关系
syllabus/kp_cross_relation.json 10 条跨科关系
syllabus/legacy_from_demo.json 示例编号 → 考纲编号的迁移映射
scripts/outline_common.py      公共构建器(编号/depth/parent/syllabus_ref 推导)
scripts/build_kp_{ds,co,os,cn}.py  各科结构数据
scripts/build_cross_relation.py    跨科关系
```

```bash
python scripts/build_kp_ds.py && python scripts/build_kp_co.py
python scripts/build_kp_os.py && python scripts/build_kp_cn.py
python scripts/build_cross_relation.py

python -m kp_graph.cli --db all.sqlite3 import \
    --kp syllabus/kp_DS.json --kp syllabus/kp_CO.json \
    --kp syllabus/kp_OS.json --kp syllabus/kp_CN.json
```

每个脚本自带结构断言(章/节/三级/四级的数量),数字对不上就退出非零——这不是装饰:OS 与 CN 首次生成时正是靠它抓到我自己手算错了四级条目数。

实测:**387 条考点,0 错误**。warn 全部是 `kp.no_question`(尚无题目)与 `legacy.ambiguous`,属于预期。

### 旧编号 → 考纲编号:只出候选,不替你拍板

旧条目和考纲条目常常不是一词之差(「顺序表」→「顺序存储」、「内部排序」→ 10 条平铺条目)。
词法相似度算不出来的部分必须交给人——硬猜出来的映射会污染整条诊断链路。

```bash
# 1. 出报告(只读,不写库)
python scripts/migrate_legacy.py --db all.sqlite3 --legacy examples/legacy_sample.json

# 2. 产出候选文件,逐条人工复核
python scripts/migrate_legacy.py --db all.sqlite3 --legacy examples/legacy_sample.json \
    --out syllabus/legacy_candidates.json

# 3. 人工确认的一对多映射写进 alias,再落库
python scripts/migrate_legacy.py --db all.sqlite3 --legacy examples/legacy_sample.json \
    --alias examples/legacy_alias_sample.json --apply
```

分档规则:`>= 0.90` 可直接采用,`0.60 ~ 0.90` 进人工复核桶,`< 0.60` 记为未匹配。
只有「完全相同」和「人工确认」才可能落库;包含/词形相似一律封顶在复核区间。
落库前会校验 `kp_code` 是否存在,写错的编号会整体拒绝而不是留半条脏数据。

`syllabus_ref` 采用「章.节.子项」的阿拉伯数字路径,与大纲 PDF 的章序一致:

| 层级 | 形式 | 示例 |
|---|---|---|
| 章 | `章` | `3` |
| 节 | `章.节` | `3.6` |
| 三级 | `章.节.子项` | `3.6.2` |
| 四级 | `章.节.子项.孙项` | `3.7.2.1` |

### 核对记录(数据结构)

初版 117 条按教材体例组织,与官方大纲有 10 处结构性偏差,已全部推翻重建。其中 4 处是漏条目或错条目:

| 初版 | 官方大纲 | 性质 |
|---|---|---|
| 线性表的实现拆成两个二级 | 「线性表的实现」为二级,顺序/链式是其下三级 | 层级错 |
| 数组与特殊矩阵合成一个二级 | 「多维数组的存储」「特殊矩阵的压缩存储」两个并列二级 | 层级错 |
| 前缀编码与判定树 | 「堆及其应用」 | **条目错** |
| 树型查找下挂 B 树、B+ 树 | B 树/B+ 树为独立二级 | 层级错 |
| 顺序/折半/分块查找合并 | 三个独立二级 | 层级错 |
| 缺字符串模式匹配 | 「(八) 字符串模式匹配」 | **漏条目** |
| 缺查找算法的分析及应用 | 「(九)」 | **漏条目** |
| 排序按插入/交换/选择分组,希尔挂在插入下 | 12 个平铺二级 | 层级错 + **漏条目** |
| 图遍历 BFS 在前 | 深度优先在前 | 顺序错 |
| 自造 76 个三级细分 | 大纲三级共 24 个,无四级 | 虚构层级 |

拿到官方大纲 PDF 后,又修正了 9 处**逐字抄录**层面的偏差(这类偏差不会报错,但会让 `kp` 与原文对不上):

| 曾写 | 大纲原文 |
|---|---|
| 二叉树的定义及其主要**特性** | 主要**特征** |
| 排序算法的**分析**与**应用** | 分析**和**应用 |
| 哈夫曼**(**Huffman**)**树 | 哈夫曼**(**Huffman**)**树(半角) |
| 最小**(**代价**)**生成树 | 最小**(**代价**)**生成树 |
| B 树…B+ 树 | B树…B+树(无空格) |
| 散列**(**Hash**)**表 | 散列**(**Hash**)**表 |
| 起泡排序**(Bubble Sort)** | 起泡排序**(bubble sort)** |
| 希尔排序**(Shell Sort)** | 希尔排序**(shell sort)** |
| 二路归并排序**(Merge Sort)** | 二路归并排序**(merge sort)** |

`syllabus/` 中全部节点 `origin='syllabus'`。唯一一个 `confidence=0.9` 的是 `DS-06-05-01`:大纲 PDF 该处字形不清(可能是「二叉搜索树」也可能是「二叉树搜索树」),按标准术语记为前者并降置信度留痕,待原文核对后再提为 1.0。

### 核对记录(组成原理)

按官方大纲 PDF 逐条抄录,共六章 30 节 59 三级 7 四级。四级只集中在两处:

- `3.7.2 页式虚拟存储器` → 「基本原理、页表、地址转换、TLB(快表)」
- `6.3 I/O方式` → 程序中断方式下 4 项、DMA方式下 2 项

### 核对记录(操作系统)

共五章 18 节 62 三级 19 四级。四级分布较散(进程通信、调度实现、互斥实现、银行家算法、页面置换、文件操作、I/O 各层等),是四科中层级最深的一科。

有一处需要判断的是「进程管理」的组织方式。大纲 PDF 中:

- `CPU调度与上下文切换` 下挂着 `调度的基本概念 / 调度的目标 / 调度的实现`
- 而 `CPU调度算法 / 多处理机调度 / 上下文及其切换机制` 在下一页以同级项目符号出现

若按后者也判为二级,则「CPU调度与上下文切换」与「上下文及其切换机制」语义重复,不像官方体例。结合《考试大纲解析》目录对进程管理的描述(「进程与线程、CPU调度、进程同步、死锁」**四个节级**),判定这三个条目是 `CPU调度与上下文切换` 下的三级:

```
2.2 CPU调度与上下文切换
  2.2.1 调度的基本概念
  2.2.2 调度的目标
  2.2.3 调度的实现 → 调度器/调度程序(scheduler)、调度的时机和调度方式…
  2.2.4 CPU调度算法 → 先来先服务(FCFS)、短作业优先(SJF)、时间片轮转、优先级、高响应比、多级反馈队列
  2.2.5 多处理机调度
  2.2.6 上下文及其切换机制
```

这一判定同时被 `tests/test_smoke.py::test_process_management_has_four_sections` 与 `test_scheduling_topics_are_third_level` 锁定。

### 核对记录(计算机网络)

共六章 32 节 69 三级 3 四级。四级只有 3 处,都在介质访问控制下:信道划分、随机访问、轮询访问各自聚合的那一行明细。

### 跨科关系

`kp_cross_relation.json` 共 10 条,是本项目诊断能力的核心:

| 方向 | 条数 | 代表 |
|---|---|---|
| CO ↔ OS | 7 | `OS-03-02-01 虚拟内存基本概念` ↔ `CO-03-07-01 虚拟存储器的基本概念`(confidence 1.0) |
| DS ↔ OS | 3 | `DS-04-01 树的基本概念` ↔ `OS-04-02 目录`(树形目录) |

这些边让「在组成原理会、在操作系统不会」这种结论成为可能。实测(`python examples/simulate_cross_subject.py`):

```
强端 CO-03-07-02-01  CO 基本原理、页表、地址转换、TLB(快表)   acc=100%
弱端 OS-03-02-02     OS 请求页式管理                        acc=0%
关系 analogous (confidence=0.95)  gap=1.00
→ CO「…TLB(快表)」正确率 100%,OS「请求页式管理」仅 0%:概念已具备但未迁移
```

单看任何一科的正确率都得不出这句话。

`kp` 表的 `depth ≤ 4` 约束覆盖了四科全部层级,无需放宽。

**任何一科的目录修订都必须先做逐条核对**——DS 初版的错误证明,凭印象写层级会系统性偏离官方体例。

---

## 许可证

- 代码:MIT(见 `LICENSE`)
- 考点编号体系、关系数据、schema 文档:CC BY 4.0(见 `DATA-LICENSE.md`)
- **不覆盖**任何第三方教材、真题、题库内容

设计思路参考以下项目,但**不包含其代码与数据的任何副本**:

```
CacheFlow408     — MIT, Copyright (c) 2026 ya1u737
EraHerm-Memory   — MIT, © 2026 杨文华 (Wenhua Yang)
```

---

## 路线

- [x] schema + 导入器 + 校验器 + 诊断查询 + 关系置信度闸门 + pending 过渡态
- [x] MCP 工具契约(manifest + stdio 骨架,`mcp[cli]` 可选)
- [x] 教学记忆导出格式
- [x] 记忆层适配器协议(`MemoryBackend`)+ `--push` 落库,换记忆层只改配置
- [x] 数据结构考纲目录落地并与官方大纲逐条核对(71 条,0 错误,`syllabus_ref` 已回填)
- [x] 计算机组成原理考纲目录落地并与官方大纲逐条核对(102 条,0 错误)
- [x] 操作系统考纲目录落地并与官方大纲逐条核对(104 条,0 错误)
- [x] 计算机网络考纲目录落地并与官方大纲逐条核对(110 条,0 错误)
- [x] 跨科关系 10 条,跨科断链诊断在真实考纲编号上跑通
- [x] 真题 PDF → 题库:题/图结构化、选项与答案解析、扫描件 OCR 可选、一卷一命令(`extract` / `import-exam`)
- [x] 题目→考点候选 + 复核回写 + 覆盖统计,全部暴露为 MCP 工具(Agent 直接可用)
- [ ] 扫描件上的**配图**单独切出(需要版面分析,OCR 解决不了)
- [ ] 284 条旧编号 → 考纲编号映射工具(规则候选 + 人工复核队列 + 覆盖率报告)
- [ ] 通用导入适配层:接受使用者自备的 JSON / SQLite(不内置任何来源)
- [ ] 错因自动判定(LLM 只产出候选,落库必须经人工或高置信度规则确认)
- [ ] 题目落地后跑 `coverage`,定位哪些叶子考点还没有任何题

### 四科落地情况

| 科目 | 状态 | 条目量(章 / 节 / 三级 / 四级) |
|---|---|---|
| 数据结构 | 已核对 | 7 / 40 / 24 / 0 = 71 |
| 计算机组成原理 | 已核对 | 6 / 30 / 59 / 7 = 102 |
| 操作系统 | 已核对 | 5 / 18 / 62 / 19 = 104 |
| 计算机网络 | 已核对 | 6 / 32 / 69 / 3 = 110 |
| 合计 | | 387 |

每科的落地流程固定为:**先取官方大纲原文逐条抄录** → 写 `scripts/build_kp_<科>.py` → 生成 `syllabus/kp_<科>.json` → `import` → `validate` 必须 0 错误 → 补先修关系 → 加一组锁定测试。第一步不可跳过。

`examples/seed/` 始终维持 22 条考点、5 道自编示例题不变,仅用于跑通链路;真实目录只进 `syllabus/`。

Maintenance

ActivityMaintained
ResponsivenessNo issues