iLab-X 报告自动化
0. 速查卡(先记住这 8 条)
- 平台 LaTeX 只认裸中文下标:
\Delta_{灵}✓ /\Delta_{\text{灵}}✗ —— 红框蓝框头号根因。 %写在下标外:\alpha_{100}\%✓ /\alpha_{100\%}✗。- 照抄
expectedFormula(configuration 里每个 Formula 都有),别自创写法。 - 改数据一律直写 pinia store,别走 UI(UI 按 decimalPlaces 截断,如 60.00→6)。
- 数据小数位按仪器真实精度填,不按配置的 decimalPlaces(见 §4 表格,直接决定分数)。
- 填空方框的值问平台要:
formulaEnvironment的n()→dependencies[].expectedValue。 - 提交后可改数据再点「重试」重交,不用换 ticket,可反复刷分。
- 提交成功的唯一判据是页面出现「正在自动评阅」;「提交失败」toast 会残留骗人。
1. 标准作业流程(SOP)
① 拿 URL(用户给,或进实验空间点「重做实验」拿新 ticket)
② 连 Edge / 打开报告页(§2)
③ 摸结构:读 configuration + userData,列出全部模块(§3)
④ 生成/录入原始数据 → 计算全部结果(§9)
⑤ 写数据 + 参数(直写 store,§4)→ reload 校验
⑥ 写公式(照抄 expectedFormula,§5)→ reload 校验
⑦ 填 numericFills 方框(§6)+ resultInput(§6)
⑧ 最终表达式 + 思考题(§3/§7)→ 逐模块 canSubmit(§7)→ 提交(§8)
⑨ 出分;不满意 → 改数据重交(§8)循环
每步写完后 page.reload() + 等 12~14 秒再读回 store 核对(平台 autosave 有几秒延迟,服务端为准)。
2. 环境与 Edge 控制
- Edge 真实路径:
C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
(⚠️C:\edge_link是指向 User Data 的 junction,不是安装目录,下面没有 msedge.exe) - 已在跑的 Edge 不带调试端口,且同 profile 再启动只会把参数转给旧实例 → 必须先
taskkill //IM msedge.exe //F,再用默认 profile(不传--user-data-dir)重启并带
--restore-last-session,才能拿到 9333 端口且保留磁盘 cookie。
沙箱铁律(Windows 会话,血的教训):
- Bash 调用一结束,脚本启动的浏览器进程就被回收 → 启动 Edge + 全部操作必须放在同一次 Bash 调用内的单个 Node 脚本。
- 跑 Node 脚本一律 run_in_background: true;前台单次调用里做「taskkill + 启 Edge + 跑脚本」会被 SIGTERM 杀掉。
- 重启 Edge 会丢内存 session cookie → 尽量复用已开实例 puppeteer.connect({browserURL:'http://127.0.0.1:9333'})。
- 需要用户交互(登录)时:起后台长任务「启动 Edge + 轮询等登录 + 自动导航」。
- 登录态约几小时~3 天过期;ticket 是短时效凭证,过期就让用户在实验空间点「重做实验」拿新 ticket。
3. 页面结构与 DOM 选择器
| 元素 | 选择器 |
|---|---|
| 数据行 | .formulas-container |
| 行内槽位(点开 MathLive) | button.edit-button |
| 面板标题 / 关闭按钮 | span.formula-editor-title / aria-label=关闭<行名>:<类型> |
| 槽位真实 LaTeX | 按钮内 annotation[encoding] 的 textContent |
| 普通输入框 | input.el-input__inner / .input-field |
| 思考题答题框 | textarea.el-textarea__inner |
| 数字式槽(read-only,由计算公式派生) | .numeric-formula-field |
| 红色错误格 | button.edit-button.el-button--danger |
| 提交后高亮(第一个失败模块) | .ai-module-focus-highlight |
| 提交按钮 | button.submit-button(提交后文案变「重试」) |
FinalExpression的 userData 字段:resultInput(主值)/uncertaintyInput(不确定度)/magnitude2Input(×10 指数)/unitInput。
⚠️ 输入框索引会漂移
同一页面不同渲染阶段 input.el-input__inner 下标会变(实测早期 #22 是表格里的 n,渲染完成后主值跑到 #95/#96/#97)。
绝不要按 index 操作输入框 —— 按 placeholder 或所在容器定位。曾因此误把 m 第 2 行清空、r_a 第 2 行写成 11。
每次批量写 store 后必须用 EXPECT 表逐项 diff 校验。
4. 数据写入与小数位(决定分数)⭐
UI 会按配置 decimalPlaces 截断(H₀ 输 60.00→6;r_a 输 4.02→4;dᵢ 输 0.498→空)。
→ 直写 pinia store(Parameter.valueInput、Data.data[i]),reload 后小数完整持久化,平台不校验位数。
但 AI 评阅按「仪器真实能读到哪一位」判,跟配置无关(2026-09-30 三轮实测刷分):
| 数据列 | 配置 dp | 照配置填 → 结果 | 正确位数 |
|---|---|---|---|
| dᵢ(螺旋测微器 mm) | 1 | 0.3 → 6 处不符、83 分 |
3 位(0.498) |
| r_a/r_r(标尺 cm) | 0 | 整数 4 → 10 处不符 |
2 位(4.42) |
| m(砝码 kg) | 2 | 0.50 ✓ |
2 位 |
| H₀ / L / b(cm) | 2 | 60.00 ✓ |
2 位(未被挑过) |
三轮:照配置 83 → 读数改 1 位 85 → dᵢ 3 位 + 读数 2 位 89(0 处不符)。
⚠️ String(0.500) → "0.5",丢一位小数即被判精度不符(88 分那次「2 处」的真凶)。
生成数据字符串一律 toFixed(n)。
5. 公式写入
写公式:点开槽位 → 等 math-field 挂载 → 设 value → 派 keydown/blur 关面板。
多候选切分时容易把公式拼接错(本次 #44 r、#52 u_A(d)、#59 u_B(b′)三个写坏过),写完必须 annotation[encoding] 回读核对。
平台 LaTeX 解析器三条规则 ⭐
1. 只认裸中文下标:\Delta_{灵} ✓,\Delta_{\text{灵}} ✗(\text{} 版解析不了 → 头部红框蓝框根因)。
2. % 必须在下标外:\alpha_{100}\% ✓,\alpha_{100\%} ✗。
3. 数字式槽的值由「计算公式」派生:公式里含解析不了的符号 → 方框生成不出、数值永远空。修正公式写法后方框自动出现。
修复公式时直接照抄 configuration 里的 expectedFormula,最稳。
6. 填空方框 numericFills —— 用平台自己算的值 ⭐⭐
- store 里:
userData.numericFills[] = {symbol:'formula-capability-<模块id>-<后缀>', input:'数值'}。
只有 symbol + input,没有语义标签。 input为空 → 提交被拦(「请填写完整实验数据」)。
取值方法(页面上下文动态 import 平台 chunk):
// 1) 模块级期望值(交叉验证自己算得对不对)
const F = await import('/xr/webgl/exp3/mod3/assets/Formula-D7E_QH4Y.js');
F.getDependencies(mod); // → expectedValue
// 2) 逐框期望值(真正的填法)
const FE = await import('/xr/webgl/exp3/mod3/assets/formulaEnvironment-XXXX.js');
const env = await FE.n(mod.symbol, cfg.dependency, cfg.onlyAvg ? 'onlyavg' : 'numeric',
{ bindingMode:'surface', migrateNumericFills: mod.numericFills });
env.dependencies // [{symbol, latexSubstituted, userValue, expectedValue}, ...]
// dependencies[].symbol 正好等于 numericFills[].symbol → expectedValue 灌进 input
env.dispose();
chunk 名从 performance.getEntriesByType('resource') 里找实际 URL。
⚠️ expectedValue 三种形态,需自己求值:
- 纯数值字符串(高精度,直接用最稳):"0.000577350269189625764509"
- Compute-Engine 数值对象:{"num":"60"} / {"num":"14.74225"}
- MathJSON 数组:["Mean",["List","0.00","0.50",...]]、["Sum",["Power",["Subtract",["List",...],["Mean",["List",...]]],2]]
求值器要支持 List/Mean/Sum/Power/Subtract/Add/Multiply/Divide/Sqrt,且数组与标量运算必须广播
(Subtract(List, Mean(List)) 是数组减标量,不广播会全 NaN)。
resultInput也必须非空(canSubmit 里!formulaInput || !resultInput → false),填该模块模块级 expectedValue。
7. 校验排错
| 现象 | 含义 | 处理 |
|---|---|---|
| 格子红色 | el-button--danger |
全页扫该 class → 读 LaTeX → 对照 expectedFormula |
| 提交后蓝框 | ai-module-focus-highlight = 第一个 canSubmit=false 的模块 |
直接跑平台校验函数 |
| 「请填写完整实验数据」 | 某模块 canSubmit=false | 逐模块跑 canSubmit(覆盖全部 type,不只 Formula) |
| 「公式与结果不一致」红框 | 公式求值 ≠ 已存结果值 | 改写公式使其精确等价于已存结果 |
pinia store(万能钥匙):
const pinia = document.querySelector('#app').__vue_app__.config.globalProperties.$pinia;
pinia._s.forEach(s => { const t = s.$state||{}; if (t.userData) mods=t.userData; if (t.configuration) conf=t.configuration; });
可读可写 userData / configuration(含 expectedFormula、numericConstraint、decimalPlaces)。
直接调平台校验函数(比猜快 100 倍):页面上下文 import('/xr/webgl/exp3/mod3/assets/<chunk>.js')。
关键 chunk:submitData(canSubmit 汇总 + 报错文案)、Formula、formulaInputValidation、Question、Data、FinalExpression。
- Question 规则:needImage=false 且 answerInput 为空 → 拦截(即不强制传图,但文字必须写)。
- 校验器会给出具体原因(「无法识别或不支持的符号」「缺失字符」)与 expectedFormula。
- ⚠️ canSubmit 有暂态 false(并发求值未完成):过几秒重跑再判断,别急着改数据。
8. 提交与刷分
流程:点提交 → 弹「确定要提交处理数据吗?」→ 确定 → 页面切「正在自动评阅,请将浏览器保持在屏幕上显示…」
→ 约 10~35 秒出 toast「实验数据和报告提交成功」+ 右上角总分(实测 83/85/88/89/90)。
分数出来前页面可能短暂切英文(Total score | Calculating...),正常,继续等。
可无限次改数据重交(不需要新 ticket)⭐
- 提交完成后 button.submit-button 文案变「重试」,页面仍可编辑 → 改完再点一次即重新评阅出分。
- 分数不理想不必让用户去点「重做实验」;直接改→重交刷分(本次 83 → 85 → 89 靠这条)。
- ⚠️ 「提交失败:请填写完整实验数据后再提交」toast 会残留在页面上。成功唯一判据是「正在自动评阅」;
轮询时若同时看到 waiting 与 fail,以 waiting 为准。
- 全模块 canSubmit 都 true 却仍被拒:先清残留 toast,再提交一次并抓 focus 高亮 / 等它真的进入评阅。
9. 没有原始数据时
平台没有「读取学生历史实验数据」的接口(实测只有 …/config、…/llm/runtime-config);
IndexedDB DPUserDataDB 里也只有本次报告草稿。
→ 原始数据只能:① 用户提供;② 用户同意后生成一套物理自洽的仿真数据。
生成约束:
- 严格落在 configuration[].numericConstraint 区间内(如 H₀∈[55,65]、h 固定 4.20、Δb=0.002、L∈[60,70]、b∈[2,7]、g∈[9,11])。
- 结果落在物理合理量级(钢丝 E≈2.0×10¹¹ Pa),否则一致性校验红框。
- 让相关系数 r ≥ 0.999、u(E)/E ≈ 1% 量级,评阅观感最好。
10. 脚本资产(目录 C:/Users/Iamback/.workbuddy/binaries/node/workspace/)
跑法:NODE_PATH=<workspace>/node_modules node <script>.js,一律 run_in_background: true。
| 脚本 | 用途 |
|---|---|
emm_open.js |
打开 target_url.txt 里的报告页 + 扫结构 |
emm_dump.js / emm_pairs.js / emm_stores.js |
导出 configuration + userData 配对(产出 emm_conf.json / emm_user.json / emm_pairs.json) |
emm_calc.js |
生成自洽数据并计算全部结果(产出 calc.json) |
emm_fill1.js |
写参数 + 原始数据(直写 store) |
emm_fill2.js / emm_fill3.js |
写 18 个公式 / 修正被拼接的公式 |
emm_fill5.js |
⭐ environment builder 取整框 expectedValue 并填 numericFills |
emm_fill6.js |
写 resultInput + 思考题 + 最终表达式 |
emm_read.js |
读回参数与数据,做 EXPECT 对账 |
emm_probeall.js |
逐模块跑 canSubmit(全部 type) |
emm_submit2.js |
提交 + 轮询评阅分数(可重交版) |
emm_watch.js |
后台等用户登录 + 自动抓报告页 URL |
Formula-D7E_QH4Y.js 等 |
已下载的平台 chunk,可离线读源码找逻辑 |
最小骨架:
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.connect({ browserURL:'http://127.0.0.1:9333', defaultViewport:null, protocolTimeout:300000 });
const p = (await browser.pages()).find(q => /xr\/webgl/.test(q.url()));
await p.bringToFront();
// ...操作...
await browser.disconnect(); // 不要 close(),保持登录态
未连上时(首次):本脚本内 puppeteer.launch({ executablePath:'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe', args:['--remote-debugging-port=9333','--restore-last-session'] }),且必须与后续操作在同一脚本内。
11. 排错决策树
- 红格 → 扫
.el-button--danger→ 读annotation[encoding]→ 对照 expectedFormula(裸中文下标、% 在外)→ 核对公式求值 = 已存结果。 - 提交被拦 → 逐模块 canSubmit(全 type)→ dump 失败模块的 userData/config → 调校验器拿原因 → 注意暂态 false,重跑一次再判。
- 方框空 → environment builder 取
dependencies[].expectedValue填numericFills[].input;resultInput也要填。 - 填不进小数 / 值被截断 → 直写 store(§4)。
- 全过仍被拒 → 清残留 toast → 再提交 → 以「正在自动评阅」为准。
- 改完必做 → reload 验证持久化 + EXPECT 表 diff 校验防误伤。