✕
这个网页版是什么,以及它不做什么
它把 Isoclock 桌面版(Isoclock2.0.py)的两段核心数值计算——Thermo 数据的
读取/积分/还原与标样校正与年龄计算——原样搬到了浏览器里,界面重写,
数字一个不改。设计目标是单文件、离线、零依赖:把这一份 HTML 拷到任何地方双击就能用,
不上传数据、不联网。唯一例外是可选的"IsoplotR 谐和图"标签页
—— 那一页要联网(点了它的按钮才下载),其余全部功能离线可用。
结果可信到什么程度
同一批数据下,网页版的输出与桌面版逐位相同(bit-for-bit,包括浮点最后一位)。
这不是估计,是跑出来的:561 项 Node 端自动比对
(16 套 webgui/test_*.js:数值内核、
Thermo/Agilent 解析、积分窗口、两个 CSV 的逐字节输出、
年龄层 51 列 × 19 组参数、20 项 Agilent 端到端、
47 项 Thermo 兼容读取、
63 项 质量统计、
50 项 报告组装、
22 项 内置真实示例数据、
111 项 IsoplotR 接入层、
59 项 程序接口门面、
55 项 过程量表)
+94 项本页面内自检(走的是真按钮那条路)。
上面这些数不是手写的:docs/make_badges.py 每次都会把测试真跑一遍,
连逐套的项数也一起核,对不上就让 CI 红。真正手写的是这一句解释。
唯一一处曾经对不上的地方是
exp /
log:宿主 C 运行时与浏览器的
实现允许差 1 ULP,实测约有 9% 的
Math.exp 调用与正确舍入不同。
网页版因此自己实现了
正确舍入的 exp/log(
fp.js),
顺带让结果不再依赖运行平台——同一批数据在任何浏览器、任何系统上都是同一串数字。
默认与原程序逐位一致的行为
Age76Pb 为固定迭代 10 次(循环条件里的 Rap
在循环体内不更新,收敛判据一旦为真就不再变化)。在 1.5–2.2 Ga 区间与收敛解
相差最大约 35 Ma;比值恰在迭代起点附近时返回 0 岁。
²⁰⁶Pb/²³⁸U 与 ²⁰⁷Pb/²³⁵U 年龄有闭式解、不受影响,可用来交叉核对。
默认已提供收敛修正(左侧⑤勾选框),取消勾选则逐位回到桌面版数值。
- 微量元素系数在个别情形下为 0:所选 NIST 标样在整批数据里出现 0 次或
恰好 1 次时,该情形下系数取 0(对应的分支不产生结果)。已如实复现,
界面会给出提示。
- 204Pb 路径遇到分母为零:某些数据下
204Pb 计数差为 0。
网页版把这种行标成 NaN 并在日志里报出行数,
其余列仍与原实现逐位一致。
- 204 通道不含 Hg 干扰扣除:该项在流程里的结果恒为 0。是否需要先扣 Hg,
取决于仪器软件如何导出数据,请核对你的导出设置。
- 输出列名沿用原写法:
232Pb_232Th,以及末尾少一个 s 的
…Corrected isotop(四条校正路径各一版:
204/207/208/Cal 204Pb),
默认照抄输出,以免下游脚本按列名匹配时对不上。勾选"修正列名笔误"可换成规范写法。
- 外挂误差 Ɛ 的口径有多处并存:桌面版界面上写作
Ɛ(%),默认
3%;它以平方和并进各年龄的相对误差 —— 只改误差,不改年龄。但
① 内部相对误差用的是比值的 2s(即 2σ),Ɛ 直接与它平方相加,
等于按 2σ 同量级对待,两者未区分置信水平;
② ²⁰⁷Pb/²⁰⁶Pb 年龄的误差不含 Ɛ,同一行四个年龄的误差口径并不一致;
③ Ɛ 调大会让 MSWD 变小,不能用它去"改善"一致性。
左侧④那一格旁边写了完整说明。
与原桌面版的实际差异
- 支持 Thermo 与 Agilent 导出的 CSV(左侧③选择仪器)。
Element 的
.fin 是专有二进制,请先用桌面版转一次。
- Thermo 多了一条兼容读取路径。桌面版(也就是本网页版的标准路径)
把表头钉死在原始第 14 行、列名按精确相等匹配,真实导出常因多几行元信息
或列名写法不同而读不了。现在会先探测表头行:从多种等价写法里认列名,
认出来的文件在列表里打「兼容」标记。标准格式文件走的仍是原来那条路径
(报告里会写
mode: strict),读进来之后的数值链条完全一样。
- 新增"质量报告":点工具栏的质量报告导出一份单文件 HTML,
里面有标样 QC、样品加权平均与 MSWD、异常与警告清单、以及每条判据的出处。
这一块是原程序没有的——桌面版只输出逐点年龄,从不做归组统计;
报告里也写明了这一点,引用时请区别对待。判据阈值都是经验值,可以在左侧⑥改。
- 新增"IsoplotR 谐和图"标签页:把当前结果交给 IsoplotR
(R 语言,通过 WebAssembly 在浏览器里跑)画 Wetherill / Tera-Wasserburg 谐和图,
并可拟合谐和年龄、discordia 上/下交点年龄与 MSWD。这一块也是原程序没有的
—— 桌面版只到 51 列年龄表为止,从不画谐和图。
它是本软件里唯一需要联网的功能:不点那一页的按钮就不会下载任何东西
(首次约 16 MB),数据不出本机。口径上也说明白:本软件的
2s 是
2σ,IsoplotR 表里的 1σ 是 1σ 标准误,
图上印的是 95% 置信区间,三者不要直接对着比。
图上的点该选样品,不是标样:标样被收进下拉最后那个分组,它的年龄
要拿去对②里填的真值(检验这一批的分馏校正与再现性),不是样品年龄。
那一页还会把本次的谐和年龄与本软件自己的加权平均并排印出来
—— 两条完全独立的路径,对得上才说明接进来的不是个摆设
(对照逻辑与判据见 webgui/test_isoplotr.js 的 §G)。
- 原桌面版的 Agilent 流程要求先选一份 Excel 清单(LIST)来定样品名;
网页版把这份清单换成两列 CSV/TSV(见左侧①"样品名清单")。
不提供清单也能跑:那时样品名退化为取文件名第一个点之前那段
(正是清单键的规则)—— 不能读文件第一行,因为 Agilent 导出每个文件开头那几行
是同一串仪器元信息,读出来所有样品会同名、进而被并成一个样品。
名字的来源会在文件列表里逐行标出(清单/第一行/文件名)。
- ²⁰⁷Pb/²⁰⁶Pb 年龄迭代默认采用收敛修正(原实现的收敛判据因一处
赋值缺失而失效,1.5–2.2 Ga 偏高最多约 +35 Ma,个别点甚至返回 0 岁);
取消勾选即逐位回到桌面版数值,用于与已发表结果核对。
- 积分窗口能改的范围比原版窄。原版的四个边界都能手输(Bcg start/end
与 Sig start/end 四个输入框 +「Set (Single)/Set (All)」,选中文件时旁边
画出多元素信号图与这四条虚线;"点图取窗口"两边都没有)。
网页版只保留背景窗口(
b0/b1)与信号阈值倍数:
信号的起止由 detectSignalWindow() 按"越过阈值"自动定,
页面上没有 s0/s1 的输入框 —— 自动识别若选错,
只能改这两个数间接绕。窗口层的行为与判据见 webgui/src/window.js
与 webgui/test_window.js。
- 新增"过程量"标签页与"过程量.csv":把一次计算的全部中间环节
并成一张宽表,一行一个样品,列按链条顺序排 —— 积分窗口(含真正切进数组的
点数)→ 背景 / 信号均值 → 净计数 → 比值 + 2s → 年龄 → 微量元素。
点表格里任意一行,下面展开那个样品从头到尾的"流水账"。
这张表一个新数都不算:比值与年龄直接搬年龄表的同名列,
净计数逐格追回
result_all 或"信号均值 − 背景均值",
均值列逐格追回 Mean_Cps —— 所以它只是视图,
不是第二条算式。CSV 带一段 # 元信息(参数、分馏因子、
微量元素系数、每段的口径),方便连口径一起存档。
- 左栏的解释长文默认收起。判据、坑、口径一个字都没删,
只是搬进了折叠块(点标题展开),界面上只留一行要点。
- 输出的是 CSV(两个中间文件 + 年龄表 + 过程量表)与 JSON,
不写
.xls。
- 标样漂移回归
regression() 没有接线——它用 scipy.curve_fit,
在原程序里也没有任何控件能改它的方法编号,属于从未生效的路径。
- 年龄结果里的"备注"列恒为空:原实现的
Coments 也是在读取阶段
一律写成空串,桌面上没有录入入口。
- 多了一个启动界面(开屏):打开时先盖一层标志 + 标语 + 三条承诺,
约 1.4 s 自动淡出;这期间点一下、按任意键、滚一下滚轮都会立刻进入,
不会耽误事。它不写任何本地存储、不联网,所以每次打开的行为都一样
(不会出现"记得你看过、这次不弹"这种不确定)。
排查问题时不想要它:在地址栏后面加
#nosplash 打开即可完全跳过。
标志是内联 SVG 画的(表盘 + 谐和线 + 数据点),没有外链图片。
维护状态
以后主要更新网页版。桌面版(Isoclock2.0.py 与 packaging/)
只维护、不加新功能:它留在仓库里、照常能用,数值输出仍然是网页版逐位比对的
基准;但新标签页、新导出、新读取器一律只做在网页版这边。数值链两侧共用 ——
凡是会改动数字的,两边一起改,不一致就构建不过。
给程序用的接口
这一页里的每个数都是算出来的、不是画上去的,所以它同时是一个可以调用的接口:
按 F12 打开控制台,输入 await DS_API.runDemo(),就会用内置的
49 个真实文件跑一遍,拿回一份结构化结果(年龄表、标样 QC、批次质量等级、日志)。
界面上"开始计算"走的就是这同一条链,所以接口算出来的数字与界面上看到的
一模一样 —— 自检里有一条专门钉它:同一批文件、同一份配置,两边跑出来的
51 列逐位相同。
几个最常用的入口:DS_API.runDemo()(跑内置示例)、
DS_API.run(params, files)(跑自己的数据)、
DS_API.stages(compute结果)(拿"过程量"那张表,
与界面、与导出的 CSV 是同一份数据)、
DS_API.schema() 与 DS_API.tool()
(自描述 —— 给智能体生成调用参数用的 JSON Schema 与 function-calling 定义)。
参数名与内置示例自带的那份推荐参数完全一致,所以示例参数可以原样喂进去。
失败不抛异常,一律返回 { ok:false, errors:[{code, message}] },
调用方靠一个字段就能判断;完整的参数表、返回结构与错误码见仓库里的
webgui/API.md。
不想被开屏耽误、也不想让程序去点按钮:地址后面加 #api ——
开屏不建 DOM,等 window.__apiReady 变成真就能直接调。
接口同样不联网、不写本地存储,数据不出本机。
怎么验证
想自己验一遍:在地址栏后面加 #selftest 打开(变成
file:///…/isoclock.html#selftest),页面会自动用一份合成数据跑完整条链,
把每一项检查写在"日志"页,并挂在 window.__selftest 上、把页面标题改成
SELFTEST-OK [通过数/总数] 或 SELFTEST-FAIL …,
方便无头浏览器读取。
这一份单文件是怎么来的:源码在 webgui/ 下(src/ 是数值模块
——每个模块都跟桌面版逐位比对过;ui/ 是界面与自检;
test_*.js 是上面那一批逐位比对,项数与套数由脚本每次实跑后核对,
所以这里不写死数字)。本文件由
python webgui/build_ui.py 拼成,改动请改源头再重新构建,
不要直接编辑本文件。构建脚本自己也会检查:占位符有没有残留、模块有没有漏、
顶层有没有重名——重名这一条是踩过坑的(几个模块合并进同一个 script 块时,
同名的顶层函数会互相覆盖),所以重名必须逐个登记理由才能通过。
手上的这一份是哪一版:页脚左下角写着构建时间与源码指纹,
它在 build_ui.py 最后一次运行的输出里也打印同一串("源码指纹:…"),
两边对上就是同一版。想看得更细:按 F12 打开控制台,输入
window.__build 可看到构建时间、源码指纹 id
与每一个模块各自的指纹;同样的源码每次构建都会得到字节相同的文件,
所以指纹一致就意味着内容一致。