# 数学知识点诊断小工具 纯静态、可离线的网页小工具:学生或家长选一个目标知识点(或整章),工具沿先修关系自适应出题,找出**薄弱的根因**,给出**按先修顺序排列的学习路径**,并列出相关的课标条目。无构建步骤、无外部 CDN、无后端;进度只存在浏览器的 localStorage 里。 > 数据(知识点、先修边、课标映射)都是 AI 生成的初稿,未经教师审核,诊断结论仅供参考。 ## 运行 在项目根目录(不是 `diagnose/` 里): ```bash python3 -m http.server 8000 # 浏览器打开 http://localhost:8000/diagnose/ ``` 页面用 `fetch('../data/*.json')` 读数据(含 `questions.json` 题库;缺失时自动回退到 `topics.json` 里每个知识点的 `diagnostic_question`),不能直接用 `file://` 打开。全站顶部导航都能进来:首页是根目录的 `index.html`,知识图谱在 `../explore/`。样式与首页、图谱共用 `../assets/site.css`,本目录的 `style.css` 只写诊断页自己的布局。 ### URL 参数(演示 / 截图 / 分享入口) | 参数 | 含义 | |---|---| | `target=cnm_221` | 直接以某个知识点为目标(可用逗号写多个 id)。没有 `demo` 时,若本机已有同一目标的存档就继续,否则开始新诊断 | | `chapter=七上 第一章` | 以整章为目标(写法同列表里的“册 + 章号”,URL 里记得编码空格) | | `grade=七年级` | 按年级整体诊断(筛查模式):一年级 … 九年级,等于该年级上、下两册;也可写 `七`。与 `target` / `chapter` 同时出现时以 `target` / `chapter` 为准 | | `volume=七上` | 只诊断一册(`七上`、`八下` 等);可单独使用,也可和 `grade` 一起用(须是同一年级,否则报错;`grade=七年级&volume=上` 也行) | | `demo=1` | 演示模式:由程序模拟一位学生作答并直接到结果页,**不读写存档**。模拟规则:目标两题都错(薄弱);它的第一个可测先修也两题都错(薄弱,根因);再往前一个知识点(没有就取目标的另一个先修)第一题错、第二题对(不稳);其余都一次答对(掌握)——所以结果页能同时看到 掌握 / 不稳 / 薄弱。对 `grade` / `volume` 的筛查会话,模拟学生按知识点序号:约 1/6 不会、1/6 不稳(第一题错、第二题对)、其余都会,结果页有按章汇总 | | `step=2` | 与 `demo=1` 合用:只自动答前 2 题,停在第 3 题的出题页 | | `limit=16` | 本次诊断的题量上限(**按题计**,不写则用设置里的值:单点 / 整章默认 16,可调 8–30;整体诊断默认 40,可调 20–60) | | `seed=abc` | 固定选题的随机种子:同一目标、同一种子、同样的作答,出的题完全一样(测试、复现用)。不写则每次随机;演示模式默认种子 `demo` | 例:`http://localhost:8000/diagnose/?target=cnm_221&demo=1`、`http://localhost:8000/diagnose/?grade=七年级&demo=1`、`?volume=七上&limit=50` ## 使用流程 1. **开始页**:先选学段(全部 / 小学 / 初中 / 高中),再用搜索找知识点。册、章列表仍在,默认折叠,需要时再展开;点「选为目标」,或点章标题旁的「以整章为目标」。题量上限可在 8–30 之间调(保存在本机)。手机上选项按钮不小于 44px。 **按年级整体诊断**:开始页的第二张卡片。选一~九年级,或高一、高二、选必一、选必二、选必三。小学和初中再选「整年级 / 上册 / 下册」;高中不拆上下册,必修两册的范围按钮是「必修整册」,选择性必修三册是「整册」。卡片上会显示范围内的知识点数;题量上限可在 20–60 之间调(默认 40,保存在本机,和单点的上限分开)。点「开始整体诊断」后用筛查模式出题:诊断范围就是所选年级 / 册的知识点,答对就连带跳过它在范围内的先修,两题都错才在范围内回溯先修,**不会追到所选范围之外**。存档会记下模式,刷新后「继续」仍是整体诊断。URL:`grade=高一`、`grade=高二`、`grade=选必二`(也接受 `高一必修`、`选择性必修第二册`)。英语在三年级到九年级之外,还有必修一、必修二、必修三、选必一到选必四,URL 必须带 `subject=english`(例如 `grade=选必一`、`grade=必修一`,也接受 `选择性必修第一册`)。英语高中不拆上下册:必修三册的范围按钮是「必修整册」,选择性必修是「整册」。不带 `subject` 时这些写法不会选到数学。 2. **出题页**:每题显示知识点、难度(基础 / 标准 / 提高)、题型、这是本知识点的第几题,以及“为什么现在问这个”(目标本身 / 因为某点两题都没通过而回溯 / 第一题没过的复测)。 - 选择题:点选项再「提交答案」;数值 / 短答题:输入答案后「提交答案」(回车也行);开放题:可写思路,再「看参考答案并自评」; - 「看参考答案并自评」→ 自己点 对 / 不对 / 不会; - 「不会」→ 先显示参考答案和解析,再点「下一题」,记为没答对。 **每题作答后都会显示参考答案和解析**(自动判题时也显示),然后点「下一题」。自动判定后可点「判错了,…」手动改判。还有「撤销上一题」「重新开始」。 3. **结果页**:薄弱根因、**不稳的知识点**、推荐学习路径(文字列表)、路径图(Cytoscape + dagre,风格与知识图谱一致:小学 / 初中 / 高中分色,硬先修实线,软先修虚线,薄弱根因高亮;长名称省略,悬停或点按看全名)、已掌握 / 未测列表、相关课标条目汇总、答题记录(含题目、你的答案、参考答案,可逐题更正,更正后整份诊断重新计算)。整体诊断的结果页在顶部统计之后多一个**按章汇总**:每章的 掌握 / 不稳 / 薄弱 / 未测 数量(含色条)和薄弱、不稳的知识点名称,薄弱的章排前面;如果有薄弱点的先修在所选范围之外,会提示“部分薄弱点的先修在所选范围之外,未检查,可对其做单独诊断”,并在对应的根因下写出是哪些先修。整体诊断里不标「目标」,课标汇总只含学习路径上的点。可「导出 JSON」(含每道题的 id、你的答案、结果、参考答案、解析),或「打印 / 另存为 PDF」(有 `@media print` 样式,打印时自动展开所有折叠项)。也可以「生成分享图」:用 canvas 画一张 1080×1920 的竖图(站名、学科和范围、已掌握 / 不稳 / 薄弱 / 未测、最薄弱的 2–3 章或知识点、1–2 个根因、日期),底部二维码指向 `https://math.pansgo.com/?from=share`,旁边是「扫码打开本站」。图上没有姓名,也不写付费。桌面端在弹层里下载,窄屏用图片本身提示长按保存。二维码由 `assets/vendor/qrcode.js`(qrcode-generator 2.0.4,MIT)在本地绘制。 4. 刷新页面不会丢进度;开始页会提示“继续 / 放弃存档”。出过的题 id 另存在 localStorage(`cnmDiagnose.seen.v1`),下次诊断优先出没出过的题。 ## 算法 全部在 `engine.js`(UMD:浏览器里是 `window.DiagEngine`,Node 里 `require` 即可;纯函数、无 DOM,会话对象可 JSON 序列化)。UI 只负责调用和渲染。 **图与子图**:`dependencies.json` 里 `topicId` 以 `prerequisiteId` 为先修。目标集合的先修子图 = 目标 + 所有祖先(小学正式知识点和初中知识点都在同一张图里),并做稳定的拓扑排序(先修在前,同层按 `topics.json` 的顺序,保证结果可复现)。每个节点的先修按 **hard 在前、soft 在后** 排序。开始页可以用「全部学段 / 小学 / 初中 / 高中」筛选。URL 里的旧编号 `cnm_ext_*` 会按 `data/id-aliases.json` 展开成对应的小学知识点再诊断(例如 `?target=cnm_ext_002`)。 **自适应出题**(`createSession` / `nextQuestion` / `nextItem` / `applyAnswer`)维护一个出题栈。**每个知识点最多问 2 题**: - 初始栈 = 目标按“离目标最近”(拓扑序靠后)排序;所以**先问目标本身**,整章作为目标时先问章末最靠后的点。 - **第一题答对** → 该点记为**掌握**,它的**所有祖先**(含小学节点)记为“掌握(推断)”,不再问。 - **第一题答错或「不会」** → 还没有结论,**同一知识点再出第二题**(同级或更容易,见下「选题」): - **第二题也错 / 不会** → **薄弱**(weak):把它还没判定的直接先修(hard 先、soft 后)压到栈顶,**深度优先**继续往回走。**只有这种情况才向先修回溯。** - **第二题答对** → **不稳**(shaky):结果页单独标出,并**放进学习路径作为需要补的点**;但**不**向它的先修回溯(默认如此:一次对一次错更可能是粗心或不牢,不足以推断先修有问题;它的先修保持「未测」,除非别处的回溯、或后续目标的答对把它们判定了)。如果以后想改成「不稳也回溯」,改 `applyAnswer` 里 `nth === 2 && result === 'correct'` 那一支即可。 - 如果一个知识点只有 1 道题(或第二题已无可出的题),第一题答错就直接判薄弱。 - 已判定(掌握 / 不稳 / 薄弱)的点不会重复问;直接作答的判定不会被“推断掌握”覆盖。 - 小学知识点与初中知识点一样出题(每个知识点最多 2 题)。图里已经没有不出题的 `cnm_ext_*` 节点。 - 题量上限按**题**计(不是按知识点),默认 **16**,可调 8–30(结果页还可以「再多答 4 题」)。到上限时剩下没测的点标为「未测」;如果某个知识点只答了第一题(答错)就到上限,没来得及复测,会暂按薄弱处理并在结果页提示「未复测」。 **选题**(`pickQuestion`,纯函数,由会话里的 `seed` 决定): - 用 mulberry32 伪随机数,随机数只取决于 `(seed, 知识点, 第几题)`,所以**同一种子、同样的作答 → 出题完全相同**,`replay`(刷新恢复、撤销、更正)也因此一致。URL 参数 `seed=` 可固定种子;不写则每次会话随机生成并存进存档。 - **第一题**:在本次会话没出过的题里随机;优先**没出过**的(已出过的题 id 记在 localStorage,创建会话时作为 `avoid` 传给引擎;全都出过就允许重复);有非 advanced 的题时不抽 advanced。 - **第二题**:**同级或更容易,优先 basic,其次与第一题同难度**,再其次其它更容易的,最后(没有别的选择时)才出更难的;难度优先于「没出过」,同一难度档里仍优先没出过的。 - 本次会话里同一道题不会出第二次。 **结果**(`summarize`): - **根因** = 判为薄弱、且它的直接先修里**没有**薄弱点的节点(先修里是「不稳」的不算薄弱)。若它的先修里还有题量用完没测到的点,会标出“真正的根因可能更靠前”。手造图里如果还有没有诊断题的节点,结果里会单独标出这些先修;现行数据里小学知识点都有题。 - **不稳** = 第一题错、第二题对的点(`summary.shaky`)。 - **学习路径** = 从所有根因**和不稳的点**出发、沿“被依赖”方向可达的、在先修子图内的节点,去掉已掌握的,**按年级从低到高排列**(同一年级里保持拓扑序,先修在前)。薄弱点和不稳的点一定都在路径上;路径里的「未测」点建议先自查。答错初中题会一路问到小学。 - **路径图**(`layout` 选出节点和边,页面用 Cytoscape + dagre 排版):画“路径 ∪ 目标 ∪ 根因 ∪ 实际问过的点”。边取这些点上可达关系的传递约简;直接的硬先修画实线,软先修画虚线,中间省略了未画出的点时画点线。先修在上、目标在下。文字列表仍是完整的学习顺序。 **一致性**:`replay(graph, targets, answers)` 由作答记录重建会话。刷新恢复、撤销、更正某题都是“改记录再重放”,所以不会出现状态与记录不一致。 ## 判题规则 按题型判(`judgeQuestion(question, userText)`),只在**可靠**时给出对 / 错,其余让用户对照参考答案自评(返回 `correct` / `wrong` / `manual`): - **choice**:直接比字母(`B`、`b`、`(B)`、`B.` 都行),也接受输入选项文字(规范化后相同);是别的选项就判错;认不出来交给自评。界面里选择题是点选项。 - **numeric**:用下面的数值规则;`answer` 里有多个答案时,带 `unordered: true` 才顺序无关,否则**按顺序**逐项比较;`accept` 里的等价写法也算对。 - **short**:规范化后与 `answer` 或 `accept` 精确匹配才判对;不匹配**不判错**,交给自评(文字、代数式写法太多)。 - **self**:一律自评。 - 回退模式(没有题库,题是 `diagnostic_question`):按下面的通用规则。 下面是数值 / 文本的通用规则(`judgeAnswer`): 规范化(两边都做):全角转半角(NFKC:`1/2`→`1/2`,`,;:()-` 等)、去所有空白、统一中文标点(`、`→`,`,`。`→`.`,`×÷`→`*/`)、`²`→`^2`、`90 度`/`90 °`/`90度角`→`90°`、`≦≧`→`≤≥`、去掉结尾句号和“答:”前缀、千分位 `1,000`→`1000`、带分数 `1 1/2` / `1又1/2`。 比较规则: 1. 规范化后文本完全相同 → 对。 2. 拆成多个答案(按 `, ; 、 ,` 以及“和 / 或 / 与 / 及”拆,括号里的逗号不拆,所以坐标 `(1,2)` 是一个答案),**顺序无关**地比较:`-3 和 -1/2` ≡ `-1/2,-3`。 3. 每个答案若能安全解析为数值,就用**精确有理数**比较(整数、小数、分数、百分数、带分数、带正负号或括号):`1/2` ≡ `0.5` ≡ `2/4` ≡ `50%`;`1/3` ≢ `0.333`。坐标 `(a,b)` 逐项比较。 4. 变量名:只有一个答案时 `x=3` ≡ `3`;多个答案且参考答案带不同变量名时(`x=3,y=2`),写了变量名就按变量比较,没写就按参考答案的出现顺序比较,所以 `x=2,y=3` 判错。 5. 单位:别名等价(`米`=`m`,`平方米`=`m²`,`度`=`°`…);一边没写单位放行;两边是不同的已知单位判错;不认识的单位交给自评。 6. 双方都是数值但不等价 → 错;**代数式、文字、其它写法** → 只在规范化后完全相同时判对,否则**不下结论**,显示参考答案让用户自评(例如 `y=3x+2` vs `y=2x+3` 也交给人,因为 `y=(x-2)²-1` 与 `y=x²-4x+3` 这类等价写法无法可靠判断)。 界面上始终提示:自动判题是近似的,以参考答案为准,可以手动更正。 ## 文件 ``` diagnose/index.html 页面骨架(引用 ../assets/site.css、style.css、图形库、engine.js、app.js) diagnose/engine.js 核心算法与判题(纯函数,浏览器 / Node 通用) diagnose/app.js 界面:数据加载、开始页 / 出题页 / 结果页、Cytoscape 路径图、localStorage、导出、打印 diagnose/style.css 诊断页布局(含 @media print);配色和导航在 ../assets/site.css tests/test_engine.js Node 测试(无依赖):node tests/test_engine.js scripts/screenshot.js 无头 Chrome 冒烟测试 + 截图:node scripts/screenshot.js screenshots/ 诊断流程 01–05,首页 / 图谱 / 窄屏 06–16,全部祖先与长名称 17–20,高中 21–27,选择性必修 28–31,小学英语 32–35,分享图 38–40,初中英语 43–46,高中英语 51–54 data/questions.json 题库(6159 题:数学 3355 + 英语 2804,见 docs/questions.md) ``` ### 测试 ```bash node tests/test_engine.js # 手造图 + 两题判定 + 真实数据 + 小学回溯 + 旧 id 别名 + 题库;含随机“一致学生”性质测试 node scripts/screenshot.js # 需要本机 Chrome/Chromium(可用 CHROME=… 指定);会临时起 http.server(PORT,默认 8765) ``` 性质测试里,“一致的学生”指已掌握集合对祖先封闭;题量不限时,诊断出的薄弱集合应恰好等于该学生不会的点,根因应恰好是“自己不会、先修都会”的点。 ## 导出 JSON 的结构 `version: 2`、`mode`(`target` 或 `screen`)、`label`、`targets`、`limit`、`seed`、`asked`、`finished`、`limitReached`、`answers`(每题:`order`、`topicId`、`questionId`、`difficulty`、`type`、`prompt`、`userAnswer`、`result`、`referenceAnswer`、`explanation`)、`weakRoots`(含掌握标准、课标 id、未测先修、范围外先修 `outOfScopePrereqs`)、`path`(带序号、状态)、`weak`、`shaky`、`chapters`(仅整体诊断:每章的 `label`、各状态数量,以及 `weakTopics` / `shakyTopics` / `untestedTopics`)、`mastered`(直接 / 推断)、`untested`、`standards`(id + 原文)。 ## 局限 - **每个知识点最多 2 题**:题库每个知识点 4–5 题,由 AI 生成、未经教师审核(核验方法与局限见 [../docs/questions.md](../docs/questions.md))。两题仍然很少:选择题有 25%–33% 的蒙对概率,一对一错的「不稳」只是提示,结果是“线索”不是“结论”。重测多次会遇到重复的题。 - **不稳不回溯**是一个设计取舍:第一题错第二题对时不继续追查先修,可能漏掉真正的薄弱先修;它们会在路径里标出,建议自查。 - 答对一个点就推断它的所有祖先已掌握,依赖先修图的准确性;先修边是 AI 生成、未经教师审核的初稿。 - 小学、初中、高中必修和选择性必修在同一张图上。题量上限默认 16,一次诊断往往问不到一条很长的先修链的最前面;把上限调大,或对结果里的「未测」点再诊断一次,才能走到一年级。旧链接 `cnm_ext_*` 只跳到别名文件里的代表知识点,不是该主题的全部小学知识点。选择性必修的 `grade` 12–14 只用来分册,不是教学年份。英语高中的 `grade` 10–16 同样只分册(必修三册、选择性必修四册),页面不写成高一到高四。 - 判题只覆盖数值、分数、坐标、多答案和带单位的简单答案;代数式、文字题、证明题一律自评。`2/4` 这类未约分的写法也会被判对(只比数值)。 - 薄弱点只由“答错 / 不会”决定,不区分概念性错误和计算失误。 - 路径图由 dagre 排版。跨很多层时边可能从别的节点旁边穿过;名称在图上会省略,完整顺序以文字列表为准。 - 进度只存在当前浏览器的 localStorage,换浏览器或清除数据就没了;隐私模式下可能无法保存。 - 手机上可以使用:开始页以搜索为主,选项和主要按钮不小于 44px,窄屏不会横向撑出页面。无障碍(键盘 / 读屏)只做了基本的语义标签。