iRAS 是什么
iRAS 是面向中国设计院、水产养殖工程师、高校研究者的循环水养殖 (RAS) / 工厂化养殖工程设计开源工具,覆盖工艺设计→P&ID 工艺图→设备清单→投资概算→16 章可研报告完整工作流。支持大西洋三文鱼、罗非鱼、对虾、石斑鱼、大菱鲆等 8 种主流鱼种。AGPL v3 永久免费。v1.9 碳酸盐平衡求解 (pH/NH3/方解石饱和度, 对照 PyCO2SYS 验证); v1.9.1-v1.9.2 修正液氧/蒸发/通风/泵功率口径, 新增机械除湿机; v1.9.3 自然冷却改为容量判据 (支持部分自然冷却) 与年均工况 24 点数值积分。v2.0 热平衡架构重构 (车间温度改为暖通维持, 首次计入气液接触器热湿交换); v2.1 修复车间显热能量守恒并补齐守恒类断言; v2.2 补配置边界工况 (脱气关/旁路/空气曝气) 并建立登记表声明自校验; v2.3 第七轮审计处置 (head 结构/加载钳制/尾气入室); 并在 v2.3 内继续完成四批工作: 简易模式 (自然通风车间自由空气节点求解, 太阳辐射子系统首次建模)、池内曝气拓扑修正 (air 模式 DO 稳态由传质=耗氧决定) 与曝气强度约束、车间模式三档 (恒温恒湿/自然通风/机械通风) 与车间通风机建模、CO₂ 脱除机制澄清与浅池告警。v2.3.3 字段流转工厂化: 鱼种/阶段字段经声明表 + 工厂进入水质链, 配静态源码扫描断言 M15a–d, 从机制上封住"白名单漏字段"这一复发五次的缺陷模式。1000t 三文鱼综合成本 38.88 元/kg (工艺运行成本) / 43.95 元/kg (企业全成本), 青岛冬工况。
🚀 v2.5 · 沿程口径归位:算出来的那个数,未必是它标签上那个点(2026-08-23 · 第八轮审计)
本版主题只有一件事:把几个算在了错误位置的量放回它该在的节点。
外部审计七条 + 复核追加三条, 共十四个批次。自检 35 → 42, 13 条数值基线两次有意变更、
其余批次逐位不变。
本版最重要的一句话:集总口径下算出来的那个数, 未必是它标签上写的那个点的值。
与 v2.4 那族「取值来源型断言」治的是同一个病 —— 这次是取值位置。
守恒断言依然全绿, 因为账本两边同步变化; 标签错了, 守恒式看不见。
① 沿程七个节点的 pH / Ω / NH₃ 是从鱼池复制的
逐节点展开时只覆写各自改变的 CO₂/TAN/TSS/NO₃/DO, 五个碳酸盐字段原封不动复制。
后果: 脱气塔节点 CO₂ 降 65%, 真实 pH 升 0.33、Ω 由 0.75 升到 1.52(跨过饱和线),
而界面上七个节点全标"欠饱和"; 生物滤池节点 TAN 降了 70% 而 NH₃-N 是绝对值没跟着变, 偏高 3.4 倍。
重算需要该节点的 (碱度, CO₂) 两个量 —— 碱度沿程不守恒, 生物滤池每硝化 1 mg/L 氨氮耗 7.14 mg/L 碱度。
② CO₂ 稳态式算出来的不是鱼池, 是脱气塔进水
呼吸产 CO₂ 在鱼池、硝化产 CO₂ 在生物滤池, 而生物滤池位于鱼池下游、脱气塔上游。
链式重导给出 C_塔前 = Δ_总/(η + η_换水) —— 恰好等于原集总式的值 ——
与 C_鱼池 = C_塔前 − δ_n。即原式算的那个量, 标签贴错了地方。
鱼池 CO₂ 实测降 15–28%(纯氧)/ 23–39%(空气曝气)。
按双报落地: 鱼类福利判据取鱼池值, 同时导出脱气塔进水浓度作设备选型口径 ——
原实现把两个工程量混在一个数里, 用鱼的限值去卡一个鱼接触不到的浓度。
③ MBBR 曝气: 风机按 DO=0 选型, 而实际驱动力几乎为零
标准氧转移效率的定义条件是清水 20°C 溶解氧为零, 即驱动力最大点;
而实际 MBBR 内溶解氧 5–12 mg/L, 实算实际效率仅 0.35–0.5%, 按 8% 选出的风量与它能传的氧无关。
新口径把因果倒过来: 风量由载体流化决定(Norsk Vann 导则: 供气量不应低于
10–15 m³/h·m² 池底), 溶解氧由质量平衡算出来 —— 解是两端点的加权平均, 数学上跑不出窗口,
不需要任何钳制或地板。冷水鱼种电耗 +1.9~2.4%, 暖水鱼种 −3.4~−4.6%(原按清水口径虚高三倍多)。
④ 五处"某个量算了一半"
本版十四批里有五批是同一个模式 —— 一个量在这个账本里有、在那个账本里没有:
- 碱度收支缺排水出口项: 补水按 100 mg/L 进、系统水按 150 出, 换水实际是碱度净汇而模型记成净源 ⇒ 碳酸氢钠投加量低估 5–35%
- MBBR 曝气吹脱 CO₂: 风机选了型、电耗算了、气路登记了接触水, 唯独 CO₂ 那份没人管
- 生物滤池异养呼吸产的 CO₂: 注释写着"含池内异养", 而生物滤池内那份整项缺失 —— 实测占滤池产 CO₂ 的四成
- 生物滤池对溶解性 COD 的去除: 算了异养耗氧、也算了异养产 CO₂, 唯独没扣底物
- 气相载量型效率未计碳酸盐缓冲: 算出的是从总无机碳里转移走的质量, 却当作自由 CO₂ 的降幅用
⇒ 反复到第五次时才立了白名单断言: 凡接触水的气流, 要么在 CO₂ 分解里有份额,
要么显式登记为「有意不计 + 理由」。它不判"不计是否正确", 只判"有没有做过这个决定" ——
判据的力量在于把沉默的遗漏变成显式的取舍。
⑤ 淡水方解石饱和度: 四个鱼种从"算不出"到"方向还反了"
钙按海水恒定比例反推 ⇒ 淡水钙恒为 0, Ω 直接算不出, 四个淡水鱼种全场 n/a(占鱼种库一半)。
开放钙硬度输入后, 未加活度修正的实现给出 Ω = 1.22–1.76(过饱和, 会结垢),
而 Langelier 饱和指数独立算出 0.40–0.70(欠饱和, 会溶蚀)—— 方向相反。
而"溶蚀混凝土池体、池壁起砂"正是本模型该提示的工程问题, 不修正等于把它藏起来还反着说。
加 Davies 活度修正后落到 0.58–1.06, 与该指数同侧同量级。
⑥ 外部口径交叉验证: 内部自洽挡不住"一致地偏离物理"
本版新增两条拿库外通行量来对的断言 —— 缓冲因子换算成海洋化学的
Revelle 因子须落在典型区间 8–15(实测 13.9); 淡水 Ω 须与
Langelier 饱和指数同侧且在一个数量级内。
库内多数断言查的是内部自洽, 而内部自洽挡不住整套实现一致地偏离物理 ——
淡水 Ω 那一版方向反了的实现, 若只跑内部断言会全绿通过。
⑦ 两条流程门禁: 台账与 logo 也需要有人看着
交接台账的门禁基线本版过期六次(锚点计数、断言总数、字段计数), 每次都是加完东西忘了同步,
而没有任何门禁在看台账 —— 版本一致性检查查的是一致性不是新鲜度。
新增台账门禁后, 其中三次是被工具抓的。同期发现 logo 有四份拷贝(favicon 内联 + 三处可见 SVG),
而首版门禁只锁了其中一份 —— 若照那样收工, 换 logo 后页头两处仍是旧图而全绿。
本版遗留: 生物滤池模型重构已立范围书(硝化面速率动力学 + 溶解性 COD 按降解性拆分 +
字段改名 + 淡水蛋分默认值, 四件事必须同批, 共用同一套化学计量);
三个待标定系数中 bfAlpha 是唯一进成本链的, 最该先标定。详见交接文档。
🚀 v2.4 · 四个量的口径单一事实源(2026-08-18 · 五批)
起因是一份热平衡专项审计: 它指出气液接触器的质量流量取错了状态点(用了风机加热后的密度)。复核后发现根因更深 —— airFlowM3h 从来没有声明过参考状态, 三个产气者各按一套隐含口径, 相加后再乘一个密度, 本就无解。本版把四个这样的量全部建立单一事实源。
本轮最重要的一句话(引自审计原文): 守恒断言保证账本自洽, 但保证不了记进账本的那个数是对的。 无论取哪个密度、扣不扣哪股风, 守恒式两边都会同步变化、残差恒为零 —— 这类缺陷守恒断言天然抓不到。本版因此新增一整族「取值来源」型断言, 它们不问账本平不平, 只问这个数是从哪儿来的。自检 32 → 35, 门禁判据 136 → 167。
① 室外 CO₂ 本底 — 同一物理量的两份口径
热工侧写死 const co2Out_ppm = 450(无 UI、无默认表条目、无范围登记), 而 PROCESS_DEFAULTS.co2IntakeCO2_ppm = 350 是同一物理量的另一份 —— 值还不一样。后者更麻烦: 它有读取路径且带 300-5000 钳制, 但根本没有输入框, 那句钳制从未执行过。现统一为 co2Outdoor_ppm(默认 450, UI 可调 300-5000), 两个消费点一律经同一 resolver。⚠ 撤字段时把原注携带的 Fig 10.17 论证(本底 1000 ppm 去除率降 10%)迁存到脱气塔气路处 —— 那是「尾气必须排室外」的量化依据, 字段可撤, 依据不可撤。
② 工艺风量 — 声明为标准状态 Nm³/h
全库风量一律为 0 °C · 101.325 kPa 下的体积流量(1.293 kg/m³ × 0.232 = 0.300 kg O₂/Nm³, 国内曝气设计手册经典常数)。理由: 对外向风机厂家提规格时给的就是 Nm³/h + 压力, 质量由该单位本身锁定, 故风量/压力/功率全部同口径、不做进气温度修正。
接触器质量流因此成为常数, v2.2 那段六次不动点迭代整段删除 —— 它当年确实统一了两侧密度, 但统一到了"风机加热后的密度"这一侧, 而风机不改变质量流量。
⚠ 全库 1.2 共 9 处只改 3 处: Lewis 类比的车间空气密度、除湿/暖通选型裕度、通风显热的 0.34(=1.2×1006/3600)均不得改动。0.23 同理 —— 它与鱼种耗氧系数(鲑 0.20–0.23)数值相近而物理无关。禁止全局替换, 由断言 A4d 反向保护(不该改的必须仍在原位)。
③ 风机送进车间的那股空气 — 它就是新风
K-101 鱼池增氧鼓风机没有排气风管: 吸室外空气、气泡穿过池水、从池面进车间。⇒ 它就是新风, 而此前一分未计, 两笔账同时错 —— CO₂ 稀释侧让通风系统重复提供了这部分; 显热侧这股空气已被池水加热到水温饱和(水侧付过了), 通风显热又收了一遍。实测在恒温车间占通风需求 36.9%–44.5%。现按净额抵扣, 并把「机械换气」与「总换气」拆成两个报告口径。纯氧拓扑下抵扣量恒为 0 ⇒ 13 条数值基线逐位不变。
④ 自然通风 — 从"需要多少给多少"改为钳在设定 ACH 上
此前自然通风档同样走 max(ACH×V_room, CO₂需求), 于是用户填 ACH 1 / 2 / 5 结果完全相同(室温 8.4 °C · 车间 1500 ppm · 25.03 元/kg)——CO₂ 需求一直顶着, 用户的输入被架空; 而那 35,414 m³/h 没有风机、零电耗、零 CAPEX, 等于"选自然通风就能免掉通风机"。物理上站不住: 自然通风由风压/热压驱动, 不会因为室内 CO₂ 高就自己变大, 且随开口面积与天气跨近两个数量级(温室夏季敞开 45–60、冬季关口约 3)。
现钳在用户填的 ACH 上, 不足则让车间 CO₂ 真的算超标并给出缺口与两条出路(加大开口 / 改机械通风)。实测 ACH 1 → 4422 ppm、2 → 2894、5 → 1584、15 → 857、30 → 658 —— 用户填的 ACH 终于有意义了。
⑤ 三条车间通风告警 + 一条被否决的候选
ROOM_ACH_TOO_LOW(warn, 阈值 15 取自该档自己的定义注释「简易棚取 15–30」, 零新常数)· NAT_VENT_SHORTFALL(warn, 给出缺口与出路)· VENT_CTRL_BY_CO2(info, 沿用已有标志, 零新阈值)。
⚠ 原遗留清单里的候选项「车间 CO₂ 超限告警」判据恒假, 已改写: 通风量本就是"恰好把浓度压到限值"的反解, 故 CO₂ 一旦成为控制项, 车间浓度恒等于限值(实测四组工况精确 1500)。照原样写会得到一条永远为假的判据。真正要告警的不是浓度, 是为了不超标所要求的通风量。
⚠ VENT_CTRL_BY_CO2 的措辞里明确写着不要为了缩小通风机而开脱气塔 —— 实测在空气曝气下开塔使全场能耗上升 27–93%。脱气塔的作用是压低池水 CO₂(通常配液氧拓扑), 不是省车间通风。这一条拦住的正是"只看通风机风量降了 71%、没看总账"这个推理。
⑥ 门禁: 防线扩到全局配置层, 并补 U1 的反方向
默认值同步门禁的三条判据此前全部对准阶段工艺参数表, 于是全局热工配置层从来没有门禁。本版新增 G0–G4, 上线即咬出两条(均 WARN 级, 结构性隐患非数值错): co2IndoorLimit_ppm 的 UI min=800 与代码下钳 600 分居 HTML 属性与代码字面量; 全局默认里的围护五项逐值等于「一般工业车间」预设(改预设表不会自动改默认值)。
断言 U2 关掉了 U1 的结构性盲区: U1 是从"有框"一侧起判的三件套, 对「无框 + 有读取路径 + 有消费点」这一类结构上判不到。U2 要求凡出现在读取路径里的字段, 要么有框, 要么显式登记为 JSON-only 并写明理由。
⑦ 基线变动: 只有 ② 改数, 且符号随工况方向翻转
五批中只有标准态口径改基线, 52/52 项变动, costPerKg −0.10 ~ +0.36 元/kg: 制热工况下降(青岛冬 −0.102 · 鲈 −0.089 · Bergen −0.013), 制冷工况上升(海南夏 +0.358 · 三亚 +0.255 · 对虾 +0.175)。与 v2.1/v2.2 守恒修复时一致 —— 单向偏移才说明修错方向。
⚠ 不要引用"最大相对差 13.68%": 那一项绝对值仅 +0.024 元/kg, 是小分母造成的假象。引用变动幅度时必须看绝对值。
⑧ 一个被撤销的批次, 与六次同类错
计划中的第四批「air 模式默认关塔」核实后撤销 —— 该联动 v2.3.3 已在 UI 事件层实现(切到空气曝气时脱气塔自动关闭, 并尊重用户的显式选择), 且由断言 S3 锁住不得下沉计算层。立项时据一处默认值的字面量推断了系统行为, 没查 UI 事件层。该行代码已就地留痕防重开。
本轮在六个不同位置犯了同一形态的错: 为判据或探针挑对象时用了记忆里的名字而不是先查。其中两次是恒真型(断言宿主函数填错 ⇒ 写了、跑了、全绿, 但从未判过任何东西), 若不跑负对照就会随本版一起交付。⇒ 固化三条纪律: 静态断言必须核实宿主并在找不到目标时判红 · 探针读数"一半对一半空"时先怀疑量具 · 引用变动幅度必须看绝对值。
⚠ 另有一条判据本身写错的教训: 新门禁 G1 第一版写成"UI 范围必须等于消费点钳制", 于是把合规的 800/600 判成红 —— 而既有设计本就是「UI 引导范围」与「模型接受范围」分开存, 二者允许不等。差一点逼出一个错误的"修复"。判据错比代码错更贵: 它会让人去改对的东西。
⑨ 版本号命名空间: 两套编号并存
代码里的 IRAS_V24_* / IRAS_V25_* / IRAS_V26_* 是内部开发批次号, 与发布版本号不是同一序列 —— 那三批成果都随 v2.3.x 发布, 从未独立发版。故本次发布批的新哨兵一律 IRAS_R24_*。旧哨兵一律不改名(它们是史料; 本库有过全局替换污染历史叙述的先例)。详见源码顶部的编号说明块。
🚀 v2.3.3 版本口径批 · 下游页版本号跟随主页面(2026-08-14)
项目方决定: 下游页版本号不再各自演进, 一律跟随主页面。四页此前是 equipment v1.3 · pid v2.0(图签另写 v2.1)· report v1.7(templateVersion 又是 v1.5-alpha-1)· finance 根本没有版本号 —— 四页四个口径, 全部对齐 2.3.3。
① 判据要分层: 有两类版本号【不】跟随
一刀切"全文版本号都改成 2.3.3"会改坏两样东西: (1) 数据契约版本——pidSchema / pidSchemaMinSupported / SUPPORTED_SCHEMA_MAJOR 表达的是"能读哪些格式", 与产品版本无关, 跟着改会直接破坏向后兼容; (2) 史料标注——注释里的 // v1.7: 鱼池增氧鼓风机 K-101 是"这行代码是哪版加的", 改掉就抹掉了来历。⇒ 只有"页面版本"跟随, 契约与史料原样保留, 这三类在 docver 登记表里被显式区分。
② 每页立一个单一事实源
四页各加 const IRAS_PAGE_VERSION = '2.3.3', 标题字面量与它对齐, docver 同时登记两者 —— 二者不一致即转红。pid 图签的"版本"格优先取【所载入方案自身的 irasVersion】, 无数据时才回落到页面版本: 图纸上的版本应当是"这张图是哪版算出来的", 不是"看图的工具是哪版"。
③ 补上一件此前四页都没有的事
「方案的 irasVersion」与「页面版本」不一致时显式提示。二者是两回事: 前者是"这份数据是哪版算的", 后者是"你在用哪版工具"。旧方案在新页面上打开会走各处兼容回退 —— 结果看起来正常, 但口径是混的, 而此前没有任何信号。现 pid 走 toast, equipment 写进元信息行(方案 v? / 本页 v2.3.3)。
④ docver 登记表 10 → 19 项
负对照 4/4 精确转红。其中"主页面升版而下游七处不动"这个发版全场景, 纳管前只咬中 8 处, 现在咬中 17 处。⚠ 读取下游四页时缺文件即判工具异常退出, 不静默跳过——"文件不在就当它没问题"正是本轮反复咬出的那类假绿。
🚀 v2.3.3 下游审计批② · 设备清单少四台、总价却对得平(2026-08-14)
第二轮清掉 🔴「下游三页深审计」最后四条。三件实事 + 七次量具翻车——后者才是本批真正的收获。
① pid.html 内嵌示例冻结了四个版本
点"载入内嵌示例"得到的是 irasVersion 1.1.0 / pidSchema 1.0 / savedAt 2026-04-30 的一份数据, 带 v1.1 口径(G:L 3 / 塔风压 5 kPa / pumpHeatRatio 0.85 / denitriFlowPct 10), perStage 只有 8 个键(现 17)。而 SUPPORTED_SCHEMA_MAJOR 只比大版本、它确实还是 1 ⇒ 照常载入、不报一句警。根因不是"忘了更新", 是"有机制、无工具": 源码里本来就留了注入哨兵 %%---SAMPLE-PAYLOAD-INJECT-HERE-<hex>---%%, 说明设计上就该有注入工具, 但工具从没被写出来 ⇒ 注入成了一次性手工操作, 做完即失传。现补 inject_pid_sample.js(savedAt 归一化以保证可重复生成)+ 门禁 C6, 载入标签也改为从示例自身读版本号。⇒ 留了钩子不等于钩子会被拉。
② 设备清单少四台设备, 而总价对得平
equipment.html 的行是硬编码的, 冻结在写它那天的设备集合, 缺 K-501 蛋分供气风机 · DH-1001 除湿机 · AHU-1002 暖通机组 · KV-1003 车间通风机——全是 v2.x 后加的。⚠ 金额层面没有错: finance/report 用聚合值 capex.total, 这四台的钱一直在里面。错的是"清单少四台、总价却对得平"——对账对得平, 所以谁也发现不了; 只有拿这份清单去询价/招标时才会发现少买了设备。这类不一致比数值错更难查。修法不是手工补四行(那只解决今天): index 导出 capex.items(逐项 + ISA 位号), equipment.html 改为自 CAPEX 清单反向补全——以后 index 新增任何设备都会自己出现, "两份清单各自维护"从根上消掉。门禁 C7 锁死位号集合包含关系。
③ 让设备自己说清"为什么是这个规格"
过桥的 aeration / aerIntensity / indoorCO2 原本无人消费。现落在采购方真正需要它的地方: K-101 行写明选型控制侧(O₂ 传质 / CO₂ 吹脱, 两侧风量并列)、设计 DO 及其来源、实际 OTE 与清水基础、池底布气强度与超限告警(含该阶段可行密度上限); KV-1003 行写明控制项(CO₂ 稀释需求 / 换气次数, 两侧并列)、车间稳态与限值, 由 CO₂ 顶大时明写"改增氧方式或脱气塔配置后必须重选, 不可沿用"。采购方原来只看到"0.4 kW · 2863 m³/h", 看不出这台风机是被 CO₂ 顶大的。pid/finance 有意不消费(前者画水路, 后者用聚合值), "未消费"是决定, 不是遗漏。
④ 死负载: 371 → 365 → 22 → 10, 前两个数都是量具错
先拿运行时 CONSUMED 表反推得 371——但 Proxy 会把 JSON.stringify/深拷贝这类深度枚举也记成"读取", CONSUMED 与 ORPHAN 两个方向都虚高。改静态文本判据后得 365——而叶子路径被 depth-6 上限截成 label…, 文本判据全军覆没, 365 里有 308 条全是这一个 bug。depth 提到 9 后真实值 22, 其中 17 条正是刚过桥尚无人消费的字段; 消费落地后 10。现判据是保守的: 出现 ≠ 一定在用, 但不出现 = 一定没用。加 C8 棘轮(只许降不许升), 目的是逼迫"过桥"与"消费"同批完成。
⑤ 一条被推翻的旧结论
批① 曾据 CONSUMED 表宣布"ventFan 单价下游同步已验证通过, 该条销号"。错的——ventFan 在 finance.html 里一次都没出现。正确结论是分层的: 金额层面 ✓(用聚合值, 财务数字没错)· 清单层面 ✗(少四台设备)。⇒ 运行时"读过"不等于"在用"。
⑥ 纪律 17 在两轮内被验证 7 次
批② 又踩四次: (4) 门禁只写 localStorage 而 equipment.html 读 sessionStorage ⇒ 空页、C7 满屏假红; (5) equipment.html 读完即 removeItem(一次性消费), 而 jsdom 同源实例共享 storage ⇒ 门禁第二次打开就空手(修法: 每实例独立 origin, 假定被审页面有副作用); (6) 用 Python 写门禁时掉了一层转义, 模板字符串里 \\s 变成 s, 正则退化成 /^s*(...)s*$/——而这次是被我自己写的 catch(e){ return '[]' } 挡住的, 纪律 14 刚写完就又犯一次; (7) 死负载三次报错的数全是量具问题。⇒ 补充一条: "零脚本错误"不等于"渲染出了东西"——空页也零错, C5 已加最低渲染量判据并按各页形态分档(pid 画 SVG 无表格行)。
🚀 v2.3.3 下游审计批① · 告警链从 sim 打通到可研报告(2026-08-14)
二批把告警做成结构化 configWarns 时, 交接文档记的是「报告侧消费未做」。审计实测发现比这严重一档: 数据根本没过桥——configWarns / aeration / aerIntensity / 入室 CO₂ 四组量全都不在 payload 里, 下游想消费也没得消费。于是"报告照出成本、不带任何不可行标记"这句话是字面成立的。本批打通三段。自检 31/31, 13 条基线逐位不变。
① 先造审计器: Proxy 记录下游实际读了什么
index 与四个下游页之间此前没有任何契约: 下游多读一个字段拿到的是 undefined 而不是报错, 上游删一个字段也没人知道——这是本库复发六次的「转发断链」模式在页面之间的翻版, 只是跨了 localStorage。audit_downstream_contract.js 把 payload 包成递归记录 Proxy, 记下每页实际访问的每条路径。不用静态扫描: 下游大量 d[k] 动态取键, 静态扫必漏。
② 过桥: 纯搬运, 判据取逐位相等
产出面 831 → 874 条。结构与 sim 内部值逐字段一致, 不二次加工、不改判据、不新增阈值——呈现方式留给下游各页决定, 但数据必须先在。空值语义分两档: 无告警 → [](与 W0 同口径, 空场也要有骨架), o2 模式无曝气 → null(语义是"该拓扑下不存在这个量", 与"有但为零"不同)。断言 Y0–Y3 判逐位相等而非容差: 过桥是纯搬运, 任何差异都说明中间有人加工了, 而加工正是双源漂移的起点。
③ 呈现: 显式披露, 不硬阻断
report 新增 warnings 命名空间与 15.5 设计边界与模型告警节(有则逐条列: 阶段/级别/实测与判据/建议处置; 无则明写"未检出配置级告警"), 含 15.5.1 车间 CO₂ 与新风量的设计边界; 第 5 章开头就地标注"本章参数是在哪些告警存在的前提下算出的"; 第 16 章结论改条件式——原来无论如何都印「工艺技术上完全可行」, 现在检出 danger 级时降级并要求决策文件留痕。取向是显式披露而非硬阻断: 不拒绝生成报告("我知道超标就是要出个初稿"是正常用法, 挡住只会让人绕过工具), 但白纸黑字。
④ 顺带修掉一处读错层级
report.html:3078 写的是 sproc.peakFactor || 1.5, 而 peakFactor 是阶段级字段、不在 proc 下 ⇒ 该行永远回落到字面量 1.5: 用户把峰值系数改成 2.0, 报告照印 1.5。与 recDepth_m 那六次同一形态: 读错层级 + 字面量兜底 = 静默错值, 没有任何信号。
⑤ 本批最值得记的: 审计器自己被跑瘸了三次
(1) jsdom 不加载外部脚本, finance-core.js 与 nunjucks 都没进去 ⇒ finance/report 被跑成"只访问 9/14 条路径"的假空场, 差点得出「下游几乎不消费」的错误结论; 注入本地依赖后是 933/937 条。(2) Y 断言首版写在逐工况循环外, 只看最后一个工况留下的 lastResults, 而它恰好没告警 ⇒ 负对照「把 configWarns 截断成 []」全绿——这是 M13/M14 陷阱的第三次复发, 且是本批自己踩的; 移进循环后七个工况转红。(3) 门禁用裸 nunjucks.renderString 渲染, 而 report 自己注册了 wan 等过滤器 ⇒ 第 16 章报 filter not found, 判据报出假红; 必须走页面自己的 renderChapter。⇒ 纪律 17: 审计器把被审对象跑瘸了, 得到的"干净"是假的; 把自己跑瘸了, 得到的"红"也是假的。出现"全绿"或"全红"这类整齐结果时, 先怀疑审计器, 再怀疑代码。
⑥ 一个要撤回的误报
stages[].subSystemCount 一开始报了 56 次 orphan(report 占 52), 看着最严重。实测设成 3 就正常导出 3——它只是默认未设定的可选字段, 下游 || 1 兜得住。不是缺陷, 是审计器的判据不能区分"该有而没有"与"本来就可以没有"。已在交接文档标注: 读该工具的 ORPHAN 表须逐条复核, 不可直接当缺陷清单。
🚀 v2.3.3 收尾批 · 默认值口径清账 + 入室 CO₂ 守恒修复(2026-08-14)
清两条遗留。一条是文档写的默认值和代码跑的不是一个数, 一条是一股气流在模型里凭空消失。前者纯文档, 后者动计算——13 条基线仍逐位不变(全 o2 拓扑, 走退化档), 自检 29 → 31。
① 脱气塔默认值: 三处文档停在 v2.0 之前
v2.0 对标 Timmons Ch.10 把 co2StripperGtoL 3→5、co2StripperLoadingRate 40→80、co2PackingHeight_m 1.0→1.5, 但参数速查表、阶段卡片 title、手册 15.4.1 三处都没跟。这不是排版问题而是数值口径错: 用户照文档填 3, 模型按 5 跑, 风机功率差 1.67 倍(手册例题 35 kW 实际是 58.8 kW)。另有两个 死 fallback(: 3 / : 1.0)——与第七轮审计咬中的 : 0.2 同型, 已改读 PROCESS_DEFAULTS。
② 新门禁 check_defaults_sync, 上线即多咬一条
判据对准 PROCESS_DEFAULTS 这张表(纪律 10c), 三条: D1 阶段卡片不得用字面量 fallback · D2 title 里的「默认: X」⇔ 实际 · D3 参数速查表「默认」列 ⇔ 实际。上线当天咬出清单上没有的第四处: denitriFlowPct 文档写默认 10、范围 5-20, 实际默认 2。负对照 4/4 精确转红(含"改默认值而文档不动"的真实发生场景)。⚠ D1 另报告 33 处同值副本(今天不错、改默认值时必漏), 有意不判红——一次性改造属独立重构批次, 混进来会淹没"数值口径错"这件事; 该计数应逐批下降。
③ air + 开塔: 一股气流在模型里凭空消失
开塔时入室 CO₂ 比例取脱气塔的 degasFrac(默认 0), 但曝气气流依然 sink=indoor 且接触水体。更彻底的一层是: 曝气吹脱在水侧的 η 里也没算——水侧不记移除、室侧不记入室, 两头都不记 = 孤儿项, 与 v2.0 的 Q_air_gain、v2.1 的蛋分气流、v2.2 的 K-101 轴功完全同族, 是第五次。最能说明问题的一个对照: 修复前 air+开塔 与 o2+开塔 算出的池内 CO₂ 完全相同(罗非苗种期均 10.55 mg/L)——尽管前者正有 476 m³/h 空气打进池子。
④ 修法: 不是补第三档特判, 是取消特判
原写法是三分特判(o2→degasFrac / air+关塔→1 / air+开塔→degasFrac), 第三档错了, 而且错得"看起来已经考虑过了"——原注释还自称保守方向。现由质量守恒导出 入室份额 = fracAer×sAer + (1−fracAer)×degasFrac, 三档全部成为特例, 不再需要 if: o2 档 fracAer=0 退化为 degasFrac, air+关塔 档 fracAer=1 退化为 1, 两档逐位不变, 只有 air+开塔 得到修正。fracAer 取自水侧 sim.co2Removal 单源, 零新常数。方向标反的代价: 修复前该工况 CO₂ 新风需求恒为 0, 罗非 1000t 成鱼期新风 6,499 → 11,460 m³/h, 通风机原来选小 1.76 倍; 综合成本只涨 0.09 元/kg——靠成本异常根本发现不了, 只能靠守恒。
⑤ 断言 X0–X3 与一个被负对照咬出的断言漏洞
该链此前零导出量、零断言, 所以缺陷是手算发现的。现补 X0 导出面必须存在 · X1 入室份额 ⇔ 水侧移除分解(单源) · X2 车间稳态 ≤ 设计限值(守恒闭环) · X3 静态锁特判不得复活; 并补锚定工况 edge_air_stripper_on(air+开塔+机械通风车间)——全库既有 air 工况原本全部关塔, 不补工况 X 锚写了也走不到(M13/M14 同款教训)。⚠ X0 自身曾有漏洞: 它最初写成 X1 的守卫 if (sim.co2Removal) {…}, 于是"撤掉导出"这一负对照全绿——导出没了, 整块断言被跳过。改为独立判定后当场转红, 并顺带咬出空场骨架漏导出(与二批 W0 同一课)。⇒ 纪律 14: 守卫会吞掉失败——导出面本身必须是受检对象, 不能是判定的前提。负对照 6/6。
🚀 v2.3.3 文档批 · 手册第 15 章与参数速查表追平代码(2026-08-14)
前五批把空气曝气路线的代码补完了, 文档没跟上——手册第 15 章还停在 v1.7 口径, 于是界面写 30、手册写 40, 用户按哪个都不对。本批只改文档与文档级注释, 不动任何计算: 自检 29/29, 13 条基线逐位不变(改前/改后/交付副本三次比对)。
① 手册 15.2: 密度分界 40 → 30, 并把口径分层写进正文
三批已在代码与 UI 改到 30, 手册对比表仍写 40——同一事实两处不同源, 而用户看到的多半是手册。现补齐并把三层口径一并写明: 30(SRAC-453 / GSA 物理上限)进 UI 与参数表 · 20–25(DB46/T 424—2017 + 设备商参数 + 模型反算三源收敛)进选择器提示 · 任一阶段准确上限以 aerIntensity 反算为准。连同"抄外部数字时丢了口径"这条根因一并留档, 免得下一个人再把硬上限当分界线抄一遍。
② 「微孔盘 SOTE 18%」清水常数退役
SOTE 是清水标准态(20 °C、零盐度、洁净水、初始 DO=0)测出来的值, 养殖池四个条件一个都不满足。手册把它当"吸收效率"列在对比表里, 正是二批修掉的那个硬编码 0.18 的文档孪生体。现换成完整 OTE 公式并逐因子标注, 附实算链: 清水标称 9.75% → 加 αβ 7.31% → 加 θ 8.63%(OTE_base) → 加推动力 2.22%, 差 4.4 倍。同时点明 OTE 与 OTE_base 是两个量: 选型反推风量用前者, 水质侧解池内 DO 稳态用后者(后者要把对浓度 C 的依赖显式解出来)。
③ 15.3 标注适用范围 + 新增 15.3b / 15.3c 两节
15.3 的 DO 公式只适用纯氧拓扑(前提是"存在进池浓度"), 此前无任何标注, 而 v2.3 那次"选型侧说 5.00、水质侧说 0.00"的事故根因正是它被误用到 air 模式。新增 15.3b 空气曝气风量选型与设计工况点(池内稳态方程 / O₂ 与 CO₂ 两侧取大 / 设计工况点 / 完整例题 / 守卫与局限)与 15.3c 空气曝气要不要上脱气塔(硬规则与设计选择之分 / 结果级判据 / 实算对照 / 已知缺陷)。正文每个数字都是本版模型实算, 为此建了取数探针 probe_manual_ch15.js 与 probe_manual_ch15b.js 随交付。
④ 两条写进手册的实算结论
安全系数的去向可解析: 实算DO = C* − (C*−设计DO)/safetyAerator, 实算CO₂ = 设计CO₂/safetyAerator(CO₂ 控制时)——裕量看得见, 不是黑箱。塔的价值随密度反向变化: 关塔时池内 CO₂ 与密度无关(石斑成鱼期恒 11.86, 因 CO₂ 产量与曝气风量都正比于生物量), 开塔时正比于密度(2.14 → 6.43, 因塔的去除能力正比于循环流量而流量随水体缩小)。两条曲线走向相反 ⇒ 低密度段塔的优势最大, 而空气曝气恰只用在低密度段——这正是"要不要上塔"值得算一算的原因。
⑤ 参数速查表 D 组补登五个曝气参数
designDO_mgL · co2TargetMgL(哨兵字段, 标明留空回落语义与生效条件)· sotePerMeter · aerationAlphaBeta · co2StripApproach。后三者标明无 UI 入口、仅 JSON 可调但钳制全路径生效; co2StripApproach 另标无成文出处、属待校准项——参数表是用户查"这个数能不能信"的地方, 出处不明必须写在表里而不是只写在注释里。
⑥ 一条失效的"已被断言覆盖"承诺
手册封面口径散布四处(副标题 / 页脚 / 版本演进章标题 / 首段), v2.2 曾在原地留注称「已加入 t21 三文档判据, 下次漏改会转红」。本批实测: 四处全部停在 v2.3, 而自检 29/29 全绿——查明当前 29 项里只有 meta_version_consistency 管版本, 且只查 index 页内 meta。页面内自检读不到 manual.html 与 README.md, 这个判据在浏览器里原理上就做不成, 三次扩展落空的根因在此。现改由工具侧门禁 tools/check_doc_version.js 承担(node 能读三个文件), 并就地订正那条失效的注。⇒ 纪律: 注释里写"已被断言覆盖"不等于真被覆盖; 声称有防线时, 必须给出该防线失败时的可见信号。
🚀 v2.3.3 · 字段流转工厂化(2026-08-13)
「权威源 → 转发 → 多消费点, 某个消费点没接上; 各自内部自洽, 所以断言全绿」—— 这一 bug 模式在本库已复发五次(DOmin_* / recDepth_m / isCrustacean / 通风机风量口径 / 曝气侧池深不同源)。每次补断言都只封住当次的字段, 模式本身换个字段就复发。本版治本: 把「鱼种/阶段 → 水质侧 sp」这条链工厂化, 让"漏进白名单"成为写不出来的代码。纯结构重构, 13 条基线对【v2.3 新基线值】逐位不变, 自检 26 → 27 条。
① 声明表 + 工厂: 白名单不再手写
新增 SPECIES_FIELD_FLOW 声明表(每字段声明 scope 权威源层级 / toSp 是否转发 / resolve 解析函数), recomputeAll 的 sp 改由 buildWaterSp(stage) 工厂按表生成, 五次复发的案发现场——那个手写白名单——退役。池深解析(兜底链 + 0.3 m 下钳)收敛为 resolvePoolDepth 唯一副本(曾有三副本且兜底不一致 3 vs 1.5 的前科)。历史教训注释(v1.1.2 DOmin、recDepth_m 1.68×、池深修一半)全部随迁到表处存档。
② 断言 M15a–d: 静态源码扫描, 免疫"工况走不到"
M13/M14 两次踩过同一个坑: 断言写好后负对照仍全绿, 因为没有工况能走到那条路径。本版的 M15 从机制上免疫它——对水质链四个消费函数(simulateWaterQualityProcess / isWarmWaterStage / co2ThresholdFor / tankAerationDesign)做 .toString() 静态源码扫描, 不依赖任何工况, 每次自检对全部代码路径无条件全覆盖。四条子断言: M15a 消费面每个 sp.X 读取必须在表中声明且转发(抓"消费了但没转发", 五次复发的标准形态); M15b 每个转发字段必须有消费点(抓死转发); M15c recomputeAll 的 sp 必须来自工厂、手写字面量不得复活; M15d 设备侧 tankAerationDesign 调用必须经同一工厂。
③ 断言落地当天咬出第六例: thermalClass
M15a 上线即红: isWarmWaterStage 的消费代码支持鱼种显式声明温冷水类别 thermalClass, 但该字段从未进过白名单——与 recDepth_m 完全同型的第六例, 只因尚无鱼种声明它、还没被踩响: 哪天有人给某鱼种加上 thermalClass: 'cool', 水质侧会静默无视、继续按 tempOpt 推断。已补声明转发(阶段显式 > 鱼种级), 声明能力自此真正生效; 现状无鱼种声明 ⇒ 数值逐位不变。同批 M15b 咬出 protein 死转发(消费面零读取, 真实消费者走 stage 直连), 已撤转发并以 toSp:false 存档。
④ 设备侧接入同一工厂: M11 从"事后比对"升级为"结构单源"
此前设备侧传完整 stage、水质侧传白名单 sp, 两侧传参形状不同, M11 只能事后比对两侧风量是否相等。现设备侧改为 tankAerationDesign(buildWaterSp(stage), …)——两侧吃同一工厂产物, "两侧不同源"在结构上写不出来; 两侧解析规则的微分歧(设备侧原缺 0.3 m 下钳与 proc 兜底)一并消失。M11/M14 保留为纵深防御。
⑤ 验证: 红→绿仪式 + 四条负对照 + 级联实证
按纪律先红后绿: 批①忠实等价替换(基线逐位不变) → 批② M15 入列预期转红(红项恰为 thermalClass + protein, 预飞探针 tools/probe_fieldflow.js 事先命中) → 批③处置转绿 27/27。四条人工负对照全部精确转红: 表删 DOmin_abs → M15a · 塞假字段 → M15b · 手写白名单复活 → M15c 两条且级联咬中 M11/M14 数值断言(结构层与数值层双重防御实证) · 设备侧退回 raw stage → M15d。
⑥ 二批(同日): aerationMode 硬告警 —— configWarns 结构化导出
遗留清单第 5 位落地。侦察修正了问题定义: 渲染层其实已有曝气强度告警与 CO₂ 超标告警, 真实缺口是三样——告警困在渲染层(A.6 家族: 无导出面、无断言、渲染改坏无人知, 报告照出成本不带"该方案不可行"标记); o2 关塔这一物理上必然积累 CO₂ 的配置与"塔效率不足"共用一句轻描淡写的文案; 参数表 aerationMode 处无联动。现 sim 新增结构化导出 configWarns: AIR_INTENSITY_OVER(复用 aerIntensity 全部字段)与 CO2_OVER(结果级判据 CO₂稳态>阈值 + rootCause 三分 stripperOff/airUnderperf/stripperUnderperf——不选"o2 关塔一律响"的配置级判据, 因为高换水/低密度下确实可达标, 天天误报就没人看告警了)。判据只复用已算出的量, 零新阈值。渲染两块改为消费该数组单源, o2 关塔文案升格为「物理上必然积累, o2 模式必须启用脱气塔」; 参数表联动落地(选择器随每次计算着色 + title 给可行密度上限, 只提示不自动切换——不替用户改配置)。这也是 A.6 十一类告警未来迁移的目标形态。
⑦ 二批断言 W0–W3 与验证
预飞探针 tools/probe_warnmatrix.js 先出触发矩阵定锚: W1 锚 edge_air_aeration(池底布气 4.84–7.24 vs 限 1.7)、W2 锚 edge_no_degasser(CO₂ 124–148 vs 阈 15/20), 13 条基线两标志全假, edge_shrimp_air 提供"合规不响"侧(甲壳类档 0.79–1.77 vs 4.0)。断言四条: W0 导出必须存在(红仪式之红: 断言先行上线即 FAIL 3/27, W0 遍布全库)· W1/W2 标志与底层状态双向一致(该响必响 + 不该响必不响, 防告警恒真化)+ 两处锚定(锚定与 ⇔ 不同源: ⇔ 查一致性, 锚定查物理预期)· W3 静态 toString 锁渲染层必须消费 configWarns 与两个 code 字面量——防"数据层↔渲染层双源各自自洽"这一复发六次模式的翻版(自检 27 → 28 条)。负对照 3/3 精确转红: 撤数据层标志 → W1⇔+锚咬(FAIL 24/28)· 渲染退回旧读法 → W3 咬· 判据改错阈值×10 → W2⇔+锚咬。13 条基线全程逐位不变。
📌 局限如实记录: M15 只覆盖「鱼种/阶段 → sp」这条链(五次复发的案发地)。派生中间量的流转(V_vent_eff 一类, 第四次复发所在)不在本表射程, 仍由 M12a 数值断言压着——那是另一个量级的依赖图, 有意不在本版摊开。消费点内残余的 || sp.recDepth_m || 1.5 兜底对工厂产物是死代码, 仅为诊断脚本手造 sp 保留防御。
🚀 v2.3.3 四/五批 · 空气曝气设计工况点 + 加载路径哨兵盲区(2026-08-13)
由产业侧一句追问("关塔时是不是该让用户输 CO₂ 浓度")牵出的一串: 先是发现参数早已存在却无入口, 再顺藤查出加载路径防线的判据错位, 最后补成对称的一对设计输入。自检 29 项, 13 条基线全程逐位不变。
① 加载路径防线的判据错位(P1 同族残留)
ensureStageProc 的钳制以「默认值是不是数字」为判据, 而哨兵字段(默认 null = 未设定)的 typeof 是 object, 整类字段漏在防线之外——表里登记了 clamp 也不生效。实测注入 co2TargetMgL=0.0001 原样通过, 关塔风量 4,239 → 1,271,584,656 m³/h(放大 30 万倍), 与第七轮审计 P1 的 co2StripperGtoL=1e6 同族同路径。修法是把判据改为「FIELD_SPEC 表里有没有条目」, 与默认值类型无关; 空值仍保持 null——"未设定"不能被钳成"设定为下限", 那是另一种静默错配。断言 S1/S2 直接构造注入复验, 不依赖工况。
② 两侧都在拿"安全红线"当设计目标
查证发现这是同一个毛病的两面: CO₂ 侧拿鱼种阈值(安全上限)当选型目标, DO 侧拿 DOmin(安全下限)当设计 DO。两边都贴着红线设计, 没有裕量应对投饵高峰/水温波动/设备衰减。故补成对称的一对: 设计 DO 与 设计 CO₂, 留空则回落鱼种值(现状, 基线因此逐位不变)。二者共同决定风量并可看到控制侧翻转——实测罗非成鱼期留空时 CO₂ 侧 4239 险胜 O₂ 侧 4228, 设计 DO 提到 6.0 立刻翻成 O₂ 控制(7243); 设计 DO 7.0 时风量 15993, 是留空的近四倍。这个决策以前藏在鱼种库的"最低允许值"里替用户做了。
③ 池内曝气拓扑: 两参数与循环流量无关
产业侧指出这与液氧拓扑的关键差别, 已实测确认: turnover 1→4(流量 4×)时 air 模式池内 DO 恒为 4.9813 纹丝不动, 而 o2 模式同一扫描 DO 由 0 → 8.39。两个设计参数的算式中都不含流量(O₂ 侧 ∝ 1/驱动力, CO₂ 侧 ∝ 1/(亨利载量×目标))。⚠ 如实记录一处间接耦合: air+关塔时 CO₂ 稳态会随流量小幅变化(23.7→27.3), 但那是经由风量的二阶效应(流量低→池内 TSS 高→耗氧高→O₂ 侧风量大→顺带多吹脱), 非直接依赖。
④ air ⇒ 脱气塔默认关(只改默认, 不禁改回)
产业实际: 空气曝气用于低密度(苗种/小鱼期最常见), 该场景基本不另配脱气塔——曝气本来就在跑, CO₂ 顺带被吹脱, 一台设备干两件事。模型实测支持: 罗非1000t air 开塔 31.25 元/kg·14.23 kWh/kg, 关塔 26.20·8.35(省 16%, 比电耗近腰斩), 最高 CO₂ 27.3 < 阈值 30 守得住。⚠ 代价不是消失而是转移: 关塔后曝气吹脱的 CO₂ 全部入室, 车间通风 ACH 1.0 → 6.05——敞开式车间无妨, 恒温恒湿要掂量。故联动只做在 UI 切换事件层、不下沉计算层(计算层猜意图会静默改掉用户显式配置, 且会动基线), 且用户亲手动过塔开关后不再被覆盖。断言 S3 静态扫描计算层源码防止后人挪下去。
⑤ 一次"UI 加了但白加"的教训
批B 给 co2TargetMgL 加输入框后随即发现 readStageProcFromInputs 根本没读这个字段——UI 加了也不生效。同型风险已写进断言 S4: 不仅查"留空必须回落鱼种值", 也查"填了必须真的生效"。另修一处误导: 新增字段最初无条件常显, 而它们在纯氧模式下填了完全不起作用(o2 模式根本不调 tankAerationDesign), 已补反向灰显标记 data-aeration-air, CO₂ 目标另加"仅关塔"二级条件。
🚀 v2.3.3 三批 · 空气曝气密度分界 40 → 30(2026-08-13)
产业侧质疑"UI 里 40 kg/m³ 太高"。查证结果不止确认了这个判断, 还更正了我们自己的一处引用错误: 代码注释记的是「GSA >40 kg/m³ 必须纯氧」, 而原文给的是区间 + 硬上限两句话——仅靠曝气的密度典型限于 30–40, 超过 40 则确定不足。旧注释把硬上限抄成了分界线, UI 又据此写成"<40 都可用空气", 等于把区间上沿当成了推荐值。纯文案与注释改动, 13 条基线逐位不变, 自检 28/28。
① 30 的出处: SRAC-453
美南水产中心《RAS 设计实践综述》给出最明确的一条: 密度 <30 kg/m³ 时气提(airlift)可提供全部需氧, 而满密度 60 kg/m³ 时仅能满足约一半。这是"空气路线物理上限"的独立出处, 非经验感觉。⚠ 其装置为开放池气提(效率约 0.80), 与本模型池内微孔盘不完全同源, 已在注释标明。另有 TheFishSite 的「>50 kg/m³ 才常规上纯氧」明显更宽松, 但它答的是经济性问题(何时不得不上纯氧), SRAC 答的是工程能力问题(空气供不供得上)——两问不同, 故不并列取值。
② 口径分层: 固定数字给量级, 活数据给准数
三个数各有各的位置, 不混用: 30(物理上限, SRAC/GSA)进 UI 选项与参数表; 20–25(DB46/T 424—2017 + 设备商大菱鲆系统参数 + 本模型按池底布气 1.7 反算, 三源收敛的国内浅池实配)进选择器 title 与代码注释; 而任一阶段的准确上限始终以 aerIntensity 反算为准——它随水深/温度/鱼种变(石斑成鱼期 23.0, 甲壳类另档), 二批已把它接进选择器提示。三批后 title 在合规工况下也显示口径谱系, 不再只在超限时出现。
③ 教训: 引用外部数字必须连口径一起记
这次的根因不是数字取错, 而是抄数字时丢了它的口径(范围 / 硬上限 / 推荐值)。一个失去口径的数在传递中会自动变成另一种含义: GSA 的"超过 40 肯定不行"传成了"低于 40 都行"。已写入注释作为纪律: 凡引用外部数字, 须连同原文口径一并记录。这与"不拿来源不同的数字当阈值"是同一条纪律的两面——前者管来源, 后者管语义。
🚀 v2.3.3 二批 · aerationMode 硬告警(2026-08-13)
处置遗留清单第 5 位: 两条物理不可行/不合理配置(air 超密度、o2 关塔)此前"静默算出成本"。侦察发现真实缺口比清单文字更窄——渲染层其实已有曝气强度与 CO₂ 超标告警, 真正的问题是它们困在渲染层(A.6 家族: 无导出面、无断言、渲染改坏无人知, 报告照出成本不带任何标记), 且 o2 关塔与"塔效率不足"共用一句轻描淡写的文案。本批把两条判定提升为 sim 级结构化导出 configWarns + 断言 W0–W3 锁定。纯新增字段, 13 条基线逐位不变, 自检 27 → 28 条。
① configWarns: 结构化导出, A.6 迁移的目标形态
AIR_INTENSITY_OVER(复用 aerIntensity 全部字段与可行密度反算)与 CO2_OVER(结果级判据 CO₂稳态>阈值, 附 rootCause 三分: stripperOff / airUnderperf / stripperUnderperf)。选结果级而非"o2 关塔一律响"的配置级: 高换水/低密度下 o2 关塔确实可达标, 配置级会天天误报——"天天见红字就没人看告警"。判据只复用水质链已算出的量, 零新阈值(纪律: 不拿来源不同的数字当阈值)。渲染层改为消费本数组(单源), CO₂ 文案按根因三分——o2 关塔从"建议启用"升格为"物理上必然积累, o2 模式必须启用脱气塔"。这也是 A.6 十一类告警未来迁移的目标形态: 新告警按此建, 旧的逐步搬。
② 断言 W0–W3 与工况锚定
W0 导出必须存在(红仪式的红即由此: 断言先行上线, FAIL 3/27 全库咬中)· W1/W2 标志与底层状态双向 ⇔(该响必响、不该响必不响, 防告警恒真化)· 锚定: edge_air_aeration 必须有 W1 标志(预飞实测池底布气 4.84–7.24 vs 限 1.7)、edge_no_degasser 必须有 W2 标志(CO₂ 124–148 vs 阈 15/20)——锚定与 ⇔ 不同源: ⇔ 查一致性, 锚定查物理预期, 且是对"存在能走到断言的工况"的显式保证(M13/M14 教训, 本批以预飞探针 tools/probe_warnmatrix.js 事先确定触发矩阵)。W3 静态锁渲染挂钩(.toString() 扫描, 与 M15 同机制): 渲染层若退回"直接判 aerIntensity / tank.CO2"的旧读法, 就是复发六次那个模式在数据层↔渲染层之间的翻版, W3 当场咬。
③ 参数表联动: 只提示, 不自动切换
aerationMode 选择器随每次计算结果着色, title 给出本阶段池底布气实测值与 air 模式可行密度上限(活数据, 经 renderStageWarnings 每次 recompute 刷新, 无陈旧问题)。有意不做自动切换: 不替用户改配置, 增氧方式是工程决策——模型的职责是把不可行性摆到明处, 不是代替拍板。
④ 验证与顺带发现
负对照 3/3 精确转红: 撤数据层标志 → W1⇔+锚咬(FAIL 24/28)· 渲染退回旧读法 → W3 咬 · 判据改错(阈值×10)→ W2⇔+锚咬。顺带发现: edge_yield_tiny 的鱼池 CO₂ 实际在 21.5–23.7 mg/L、略超阈值 20——该工况的 CO2_OVER 标志为真且 W2 一致性通过, 行为正确; 此前无人知道这个边界工况处于超标状态, 结构化导出的第一天就多看见了一件事。⚠ v2.5 补注: 链式重导 (审计 B8) 后该工况鱼池 CO₂ 已降到阈值内, 当前只有 edge_no_degasser 触发 CO2_OVER。本段保留为当时的记录, 结论不再描述现状。
🚀 车间模式三档 + 车间通风机(2026-08-13)
车间模式重新划分为三档, 分档判据只有一条: 能否自动调节温湿度 —— 不是"简易还是正式", 也不是"自然还是机械"。conditioned 恒温恒湿车间 / natural 自然通风车间 / mechanical 机械通风车间。同时补上一台此前两档共同缺失的设备: 车间通风机 KV-1003。13 条数值基线因此全部重算(方向与量级逐条对预测表)。
① 恒温恒湿车间本身就是机械通风的
这是本版的关键判断, 来自产业侧一句反问: "恒温恒湿车间也有机械通风"。确实如此 —— 恒温档的 ACH 新风量一直在参与计算(UA_vent_sens 进暖通负荷), 但从来没人给它配风机。全场四台风机(脱气塔/BF/蛋分/池内增氧)里没有一台是车间通风机。所以通风方式不是 ventilated 的子类型, 而是横跨两档的独立事实; 撤销三档并列后, "恒温恒湿 + 自然通风"这个物理上不成立的组合也被天然排除(一边花钱控温, 一边让室外热风穿堂)。
② 通风机 KV-1003: 风量口径是关键
功率 = 风量(m³/s) × 全压 ÷ 效率, 与既有四台风机同一套公式。两个易错点都封在断言里: 其一, 风量必须取 V_vent_eff_m3h(= max(ACH×V_room, CO₂ 稀释需求))而非 ACH×V_room —— air+关塔 时 CO₂ 才是控制项, 挂错口径实测低估 58–66%, 且只在 CO₂ 控制的工况暴露, 只测 o2 模式永远不显形。其二, 全压走独立键 250 Pa, 绝不复用池内曝气盘的 70 kPa —— 后者要克服水柱静压, 差两个数量级(173,123 m³/h 下: 250 Pa 给 21.9 kW, 70 kPa 给 6,121 kW), 与 K0_W74 传摄氏度那次事故同族。热去向为新增的 exhaust 类别: 直驱轴流排风机电机在排风气流中, 空气离开车间后才经过电机, 废热随排风带走。⚠ 已知边界: 恒温恒湿若用送风式 AHU, 送风机废热确实进车间并需再制冷, 本版按单风机排风统一处理, 该情形偏低。
③ 13 条基线重算: 预测先行, 逐条吻合
补一台真实存在的设备必然改变基线, 故按纪律先出预测表再改代码: 预测全部 13 条上升、幅度 +0.01~+0.08 元/kg(≤0.1%)、最大为 turbot_500(水体大、通风量 5 万 m³/h)。实测方向 13/13 为正、量级 13/13 吻合, 最大偏差 0.006 元/kg(bass_300 预测 +0.02 实测 +0.026, 差在预测未计 CAPEX 折旧那一份)。幅度之所以远小于口头估算的 +0.8 元/kg, 是因为基线默认 ACH 为 1 而非简易棚的 15; 同一台风机放到海南简易棚工况(ACH 15)是 21.9 kW(密度 20)/ 10.9 kW(密度 40) —— 密度高反而小, 因为车间体积减半。
④ 空气曝气 × 车间模式的三条耦合告警
实测(石斑/三亚/200t/密度20/年均, 恒温恒湿档) air+关塔 相对 o2: 新风 7,116 → 9,835 m³/h(+38%, CO₂ 成为控制项)、除湿量 1,378.6 → 1,776.9 kg/d(+29%)、除湿电 551 → 711 kWh/d; 尾气显热恰为零(A3 下 T_room=T_w, 数学上严格为零, 到通风档立刻复活)。据此三条告警: 恒温恒湿+空气曝气(新风与除湿双重加价)· 新风被 CO₂ 顶高(同一股 CO₂ 在通风车间只需付风机的钱)· air 模式开塔冗余(曝气本身即脱气, 唯一合理用途是当风量被 CO₂ 侧控制时降低曝气强度 —— 实测 d40 成鱼期开塔可把池底布气从 3.43 降到 2.96 而合规)。每条都写明什么时候可以忽略它: 育苗/亲鱼车间本就是恒温恒湿+空气曝气的常态配置(低密度、精确控温、鱼苗对纯氧过饱和敏感), 否则天天见红字就没人看告警了。
⑨ 全批次审计: 三项发现(2026-08-13)
对本批全部改动做了一次系统审计, 查出三项, 其中一项是真 bug。
① 池深不同源(真 bug, 且是我上一批"只修了一半"留下的)。poolDepth_m 是阶段卡片上用户可编辑的字段(0.5–8 m), 注释明写它是「统一池深数据源」。但曝气侧三处全都绕过它、直接读鱼种推荐值 recDepth_m: tankAerationDesign 的 OTE、aerIntensity 的池底面积、浅池 CO₂ 告警; 而且兜底值也不一致(统一源 1.5 / 曝气侧 3)。实测: 用户把池深由 1.5 改到 5 m, 热工侧池面积 1348 → 404 m² 响应了, 曝气侧仍按 1348 算, 池底布气强度纹丝不动。上一批修 recDepth_m 白名单时只让它读到了推荐值, 没接到权威源 —— 修一半比不修更隐蔽。现三处统一改读 poolDepth_m || recDepth_m || proc.poolDepth || 1.5, 与热工侧同口径。13 条基线逐位不变(基线均未设 poolDepth_m, 走同一分支)。
新增断言 M14(池深单源): 曝气侧池面积必须等于热工侧池面积。⚠ M10a 抓不到它 —— M10a 校验「体积×水深=池底」, 两边用同一个 _depth, 自洽但不同源。这是本会话第五次同模式(DOmin_* / recDepth_m / isCrustacean / 风量口径 / 本项)。凡"权威源 → 转发 → 多消费点"的链, 断言必须跨消费点比对, 不能只查单侧自洽。
⚠ M14 写好后负对照仍全绿 —— 基线工况都没设 poolDepth_m, 两个源恰好相等, 没有工况能触发它。这正是 M13 那次教训的第二次复发, 根因是自检工况只能施加 proc 与 climate、无法施加阶段级字段。故补齐 c.stage 施加机制并新增工况 edge_pool_depth_override(自检 25 → 26 条), 负对照方精确转红(44.737 ≠ 13.421, 偏差 233%)。断言的表达能力受限于工况的表达能力 —— 这次是从工况层解决, 不是再写一条注释。
② heatVia 取值文档缺 'exhaust'(文档缺口)。登记表新增了该热去向类别但注释里的取值清单没同步, E1 断言只查是否声明、不查枚举, 故功能无碍但会误导后人。已补。③ 发布章小节编号乱序: 每次追加都插在前一节之前, 导致显示为 ①②③④⑧⑦⑥⑤。已重排。
审计中复核无误的部分: 通风机 CAPEX 实际入清单(14.3 kW → 28,595 元 @2000 元/kW, 苗种期 0.13 kW 低于 0.3 门槛未单列, 与 capexMinKw 一致); 风机风量在 CO₂ 控制工况下亦与热工侧同源(此前担心 pass-1 粗算 thermal 与最终值分叉, 实测多趟迭代已收敛); 档位白名单回落正常(open/非法值均落 conditioned, 与 conditioned 输出逐位一致); 自然通风档无风机、零产量边界不崩。
⑤ 找到成文标准: DB46/T 424—2017 与石斑成鱼期池深修正(2026-08-13)
此前几轮关于"空气曝气能撑多少密度"的推算, 全部依赖设备商配置反算与媒体报道, 直到取得海南省地方标准 DB46/T 424—2017《豹纹鳃棘鲈工厂化养殖技术规程》正文 —— 这是本轮最硬的一份依据, 因为它是成文标准而非推算。
标准表 1 分规格养殖密度: 体长 10–15 cm → 150–250 尾/m³、1–5 kg/m³; 15–20 cm → 100–150 尾、5–15; 20–30 cm → 70–100 尾、15–20; 30–40 cm → ≤70 尾、≥20 kg/m³。标准同时明写密度的约束条件: 「养殖密度与养殖设施、换水量、溶氧量密切相关, 换水量越大、溶氧量越高的养殖水体, 可适当增加养殖密度」—— 正是本模型一直在建的那条逻辑链。增氧设施 (4.4) 规定「采用鼓风机+纳米管或纯氧统一供氧」, 二者并列为合规方案, 未规定密度分界 —— 结合密度表看, 20 kg/m³ 量级下鼓风机+纳米管是标准认可配置, 再次印证空气曝气承载量 20–25 的落点。另: 水质指标 DO≥5 mg/L 与模型 DO_design = max(DOmin_abs 5, 0.7×DO_sat) 逐字相符, 修正后的池内曝气稳态 DO 5.16–5.41 正在标准线之上。
石斑成鱼期 recDepth_m 由 3.0 m 改为 1.5 m, 依据标准 4.2「养殖池面积 10–20 m²、池深 1.0 m–1.5 m」; 万宁在建工厂化项目实测池型 Ø8 m / 90 m³ ⇒ 水深 1.79 m, 亦远浅于原值。⚠ 该字段是统一池深数据源(poolArea = V_tank/poolDepth → A_building = 1.6×poolArea → V_room), 改它会连锁改池面积、建筑面积、蒸发面与通风量, 故基线随之重算。按纪律先出预测: 池面积翻倍 ⇒ 蒸发↑、除湿↑、围护与通风↑ ⇒ 仅 grouper_500 上升且幅度显著, 其余 12 条逐位不变。实测完全命中: grouper_500 工艺成本 44.6291 → 44.8341(+0.2050)、企业全成本 +0.2176、比电耗 +0.2217 kWh/kg、热泵成本 +0.1400(+8.7%, 蒸发面翻倍导致的潜热负荷, 涨幅最大); 其余 12 条 diff = 0。副效果: 成鱼期池底布气强度由 1.71 降至 1.48, 从超限转为合规, 可行密度上限 19.8 → 23.0 kg/m³, 与标准表 1「30–40 cm 段 ≥20」及产业口径 20–25 一致。
控光作为已知未建模维度记入代码注释: 标准把控光列为要求项(4.2「配备通风、控光、控温设施」; 6.2「光照 2000–5000 Lux」, 与 DO/pH/温度/盐度同级)。产业侧佐证: 东星斑养殖场用蓝光红光模拟 20–30 m 深海光照, 因体色直接决定售价。iRAS 缺口有三: 灯具电耗未进 POWER_REGISTRY(比电耗系统性低估)、灯具废热未进车间空气节点(与补 UV/泵废热同族的漏项)、体色分级溢价未进财务侧 —— 这是唯一一条收入侧缺口, 其余遗留全在成本侧。不在本批建模: 需灯具选型口径、光谱配比、光周期, 且体色-售价的量化关系无公开数据。
⑥ 曝气强度限值下调并按养殖对象分档(2026-08-13)
原限值 2.0 / 3.0 m³/(m³·h) / m³/(m²·h) 的依据是搅拌式反应器能效拐点与对虾育苗盘密度反推, 用在鱼身上偏松 2–3 倍。经产业配置反算后下调至鱼类档 1.0 / 1.7, 并将原值保留为甲壳类档(工厂化对虾的正常工况就是"水面气泡成沸腾状", 絮团还需曝气维持颗粒悬浮)。
标定口径写死为全压 20 kPa · 效率 0.60(产业确认: 1 m 深的池子 20 kPa 够用; 罗茨风机压力经验 9.8 kPa/m 水深)。⚠ 不写"20–30 kPa"这类区间 —— 全压取 20 与 50 kPa 结果差 2.5 倍, 含糊比精确更危险。同口径反算: 山东海水工厂化空气悬浮 18 kW/2000 m²(深 0.8)→ 池底 0.97·体积 1.22; 老式罗茨 22 kW/1440 m²(深 0.8)→ 池底 1.65·体积 2.06; 工厂化对虾 15 kW×2/1000 m³(深 1.2)→ 池底 3.89·体积 3.24(该工况正常状态即水面沸腾, 鱼类不适用)。布气密度交叉印证: 纳米盘每 m² 池底 1 个; 对虾池 5.5×5.5 m 配纳米管 28 m ≈ 每 m² 0.93 m 管。承载量交叉校验: 鱼类取池底 1.7 时石斑成鱼期反算可行密度约 20 kg/m³ —— 与设备商系统参数(同一系统: 罗茨风机 20 kg/m²、液氧 40 kg/m², 大菱鲆)及 GSA「>40 kg/m³ 必须纯氧」一致, 三个独立来源收敛于空气曝气鱼类承载量 20–25 kg/m³; 产业侧确认 30–40 配空气曝气偏高。
⚠ 二次修订: 两个判据曾自相矛盾。一次修订把池底 1.7(来自养殖池实配反算)与体积 1.0(来自搅拌式生物反应器能效拐点 0.9)并列当判据用, 结果在 0.8 m 浅池上打架 —— 同一套真实运行的系统按池底判据合格(0.97/1.65 < 1.7)、按体积判据却超限(1.22/2.06 > 1.0)。因 池底 = 体积 × 水深, 池底 1.7 在 0.8 m 下隐含的体积上限本应是 2.1。现改为: 池底为主判据(有真实配置支撑), 体积为次判据且阈值取在实测之上只拦极端 —— 鱼类 池底 1.7 / 体积 2.5, 甲壳类 池底 4.0 / 体积 3.5(一次修订误设的 3.0/2.0 实际低于对虾真实配置 3.89/3.24)。告警文案按主/次判据分别措辞。⚠ 仍依赖假设的风机效率 0.60, 且多数来源未给对应密度, 属待校准项。
新增断言 M13 + 工况 edge_shrimp_air(自检 24 → 25 条)。立此断言的直接原因: 新增鱼种字段 isCrustacean 后, 负对照证明把它从 sp 白名单撤掉24 条自检依然全绿 —— 与 recDepth_m 完全同一模式的第四次复发, 注释提醒挡不住, 只有断言能。更值得记的是: 断言写好后负对照仍全绿, 查明是没有工况能触发它(aerIntensity 仅在 air 模式下计算, 而 shrimp 基线走 o2) —— 新断言必须确认存在能走到它的工况, 否则等于没写。补工况后负对照精确转红(实得 1/1.7, 应为 2/3)。
⑦ 补丁: CO₂ 脱除的机制澄清与浅池告警(2026-08-13, 无数值变动)
交付后有人问"空气曝气对 CO₂ 的去除如何建模", 顺带发现一处我误判为缺陷、实际是正确设计的地方, 特此记录以防后人"修正"它。模型对 O₂ 用 OTE ∝ 水深、对 CO₂ 用与水深无关的亨利平衡载量 kLoad —— 这看似不一致, 查证后确认是两种传质机制的正确区分: O₂ 溶入是【速率控制】(推动力大, 气泡全程远未饱和, 接触越久溶入越多); CO₂ 脱除是【气相载量控制】(气泡在离开液面前已被 CO₂ 饱和, 再深也装不下)。生物反应器放大实测显示 O₂ 的 kLa 随液深线性增长, 而 CO₂ 的 kLa 仅在液深 < 约 1 m 且最弱搅拌下线性, 更深时出现明显饱和效应; CFD 研究同样确认大型反应器中气泡在离开培养液前即被 CO₂ 饱和。若照"补上水深依赖"去改, 反而会把一个正确的模型改坏。
但饱和的前提是停留时间够, 浅池不成立。鱼种库里大菱鲆全程 0.8–1.0 m、石斑苗种期 1.5 m 均在转折点附近或以下 —— 这些工况的 CO₂ 去除被系统性高估, 而大菱鲆是冷水鱼、CO₂ 阈值更严(20 而非 30 mg/L), 三重叠加。本补丁新增浅池告警(水深 < 1.5 m 且 air 模式关塔时触发; 实测大菱鲆三阶段全触发、石斑 1.5 m 及以上不触发、开塔与 o2 模式均不触发)。只告警不折减: 那个 ~1 m 转折点出自搅拌式生物反应器, 气泡尺寸/上升速度/湍流强度与养殖池微孔盘曝气都不同, 硬套来源不同的数字比宽带更糟(与标定探针头注同款教训)。另记一条待查: 级联塔实测显示海水的 CO₂ 吹脱效率显著低于淡水(归因于无机碳电离分数差异), 而模型现有的 marineCO2Factor 挂在 CO₂ 限值上且注释自承"无出处、应单独查证"—— 该系数可能挂错了位置: 海水该被区别对待的或许是去除效率而非限值。
⑧ 断言 M12 与三组负对照
M12a 通风机风量与热工侧真实通风量同源 · M12b 档位一致性(自然通风档必须无风机, 恒温恒湿/机械通风必须有 —— 后者是"恒温恒湿本身即机械通风"的运行时证据)。负对照: 风量挂错口径 → M12a 红(429.5 ≠ 1023.3, −58%)· 恒温档不装风机 → M12b 红(FAIL 3/24)· 误用 70 kPa → 13 条基线红(FAIL 11/24)。实施中另有一次真实被咬: CAPEX 门槛 0.3 kW 与登记表 capexMinKw 不同源时, 断言 F4 当场报出"有电功率但 CAPEX 明细中无对应条目"。
🚀 池内曝气拓扑修正 + 曝气强度约束(2026-08-13)
本版起于一个产业侧提问: 空气曝气是直接打进鱼池的, 鱼在同时消耗氧, 所以风量必然远大于"把水打到 95% 饱和"所需。顺着这句话查下去, 发现模型对同一台鼓风机 K-101 用了两套互相矛盾的拓扑, 并连带挖出一个潜伏的跨模块不同源缺陷。13 条数值基线逐位不变, 自检 24 条工况不变, 新增断言 M9/M10/M11。
① 同一台设备, 两套物理
选型侧 tankAerationDesign 按池内曝气算, 是对的: 池水维持 DO_design(≈5 mg/L), 推动力只剩 (C_sat−C)/C_sat ≈ 26%, OTE 折算后仅 2.2–4.5%, 风量据此反推 —— 确实远大于饱和所需。水质侧却按主流增氧算: DO = DO_sat×0.95 − 单循环降幅, 假设水"进池时"已 95% 饱和。可 air 模式的气是直接打进池子的, 根本不存在"进池浓度"。后果: 同一台按 5.00 mg/L 选型的风机, 水质侧在密度 40 时算出 DO = 0.00, 密度 20 苗种期算出 2.70, 而成本照出、无人报错。本批改为池内质量平衡 C = C_sat − OCR·C_sat/(A·ρ·w_O₂·OTE_base), 与循环流量无关(循环流量只管 TAN/CO₂/TSS)。修正后密度 20/31/40 全部稳在 5.31–5.41, 且开塔工况解析验算 (6.73−1.73/1.10 = 5.158) 与模型三阶段输出逐位吻合。零新常数。
② 潜伏缺陷: sp 白名单漏 recDepth_m(同模式第三次复发)
诊断中发现两条路径的风量对不上。根因: recDepth_m 是鱼种库逐阶段字段, 设备选型侧传完整 stage(有), 水质侧传 recomputeAll 构造的白名单 sp(没有) → 静默兜底 3 m。OTE ∝ 水深, 苗种期两侧风量差 1.68×(222 vs 132 m³/h) —— CAPEX 买的机器与水质链假设的机器不是同一台。该白名单上方就挂着 v1.1.2 的注释「sp 必须包含 DOmin_satRatio 和 DOmin_abs, 否则鱼种内置 DO 阈值失效」: 同一个白名单、同一种漏字段、第三次。负对照证明撤掉修复后24 条自检依然全绿(两条路径各自内部自洽, 只是彼此不同源), 故封在断言层 M11 而非注释层。
③ 曝气强度: DO 与 CO₂ 都达标后先撞线的约束
总风量由鱼的耗氧定, 几乎不随密度变(密度 20→60, 222→332 m³/h); 而水体 ∝ 1/密度 ⇒ 强度 ∝ 密度。新增派生量 aerIntensity(体积强度 m³/(m³·h) 与池底布气 m³/(m²·h)), 超限告警并反算该阶段 air 模式可行密度上限(因 V=生物量/密度, 反解精确而非外推)。阈值 2.0 / 3.0 的出处与局限均写在常量注释里: 前者源自 33.3 m³ 池 CFD+实测的能效拐点(30 vs 60 m³/h 效率差 32.1%, 即 0.90 vs 1.80), 是能效拐点而非福利阈值; 后者由纳米盘每 m² 1 个 × 单盘 1.5–3 m³/h 反推的工程惯例, 非规范条文。定性佐证(气泡扰动噪音不利生长、紊流碎化残饵、三文鱼通气频率随曝气升高且与 DO 无关)只入注释不入判据。实算: 石斑成鱼期可行上限 35 kg/m³ —— 与 40 kg/m³ 的国际经验线接近, 但这次是推导出来的。
④ 断言 M9 / M10 / M11 与四组负对照
M9 气曝-DO 闭环(与 M8 气曝-CO₂ 闭环成对: 一个锁 O₂ 侧、一个锁 CO₂ 侧) · M10a/b/c 曝气强度几何自洽与可行密度反算自洽 · M11 设备侧/水质侧风量同源。负对照逐一验证: 撤 DO 修正 → M9 红(三条 air 工况全咬, 罗非默认密度即 2.08 vs 4.70) · 池底面积错水深 → M10a 红 · 反算写成加法 → M10b 红 · 池底口径误用体积阈值 → M10c 红 · 撤 recDepth_m 白名单 → 修 M11 前无人咬住, 修后转红。另补车间高温告警(与简易模式批次的寒潮告警对称: 比水温高 >5 K 管热泵负荷、绝对 >38°C 管作业与设备降容)。
⑤ 车间模式撤销「露天」档
简易模式批次的车间模式做了三档, 其中 open(露天, 车间热工整体旁路)在中国工厂化循环水里没有实际工程场景 —— 该领域以简易棚为主。保留一个永不被选中的分支只会扩大维护面与审计面, 故本版连同白名单、UI 选项、派发分支、R4 断言枚举一并撤销。向后兼容按既有路径处理: 若方案 JSON 写着 roomMode='open', 白名单不认 ⇒ 与任何非法值同样回落 conditioned(断言 R4b 覆盖, 实测确认)。因该档从未随任何发布出厂, 实际不存在此类文件。
⑥ 海南简易棚实证(本版工作的产业来源)
石斑/三亚/200t/简易棚/关塔/关臭氧与 AOP/放大系数 1.8: 密度 20 → 70.29 元/kg, 密度 40 → 54.23 元/kg。密度 40 下 DO 5.31–5.38 达标、CO₂ 20.45 mg/L 在阈值 22.5 内(曝气本身即脱气, 无需另配脱气塔)、全年凝水为零(海南冬季室外 18–20°C, 围护内表面始终高于露点) —— 唯独曝气强度全线超限。真正的限制是夏季车间温度(设计夏 36–41°C, 池面转得热进热泵)与曝气强度, 不是 DO、不是 CO₂、不是湿负荷。
🚀 简易模式: 自然通风(2026-08-12)
国内多数循环水项目是简易棚: 无暖通无除湿, 车间温湿度由太阳、设备废热与自然通风共同决定。此前该场景无表达路径 —— 关掉暖通开关只是不收电费, 模型仍强制室温=水温。本版新增全局「车间模式」: conditioned(恒温车间, 既有路径逐字保留)/ ventilated(简易棚·自然通风, 新求解器)。(本批原含第三档 open 露天, 后续批次已撤销, 见下。)13 条数值基线在恒温档下逐位不变(全精度导出比对, 非仅容差级), 自检 23 → 24 条。
① 一次决定性考古: v1.x 做过同款自由求解, 失败了
车间温度自由求解正是 v1.2–v1.9.3 的做法, 当年热带鱼种算出车间 9–14°C / RH 99%, 与摩洛哥温室实测 16°C、大连简易棚实测 22–23°C 方向反了 15–20 K, v2.0 因此撤退到「暖通维持」。本轮复盘确认: 方程没错, 是热源清单漏了三类 —— 太阳辐射(全库无建模)、设备废热(未进空气节点)、接触器尾气显热(被 A3 的温差恰为零掩盖)。空气节点唯一热源只剩池水对流小项, 围护+通风大项把解拽向室外温度。三类全部补齐后重启求解。
② 太阳辐射子系统(全库首次)与自由空气节点求解器
九个气候区各补 GHI 冬/夏/年(Global Solar Atlas / NASA POWER, 三亚年均 5.2 kWh/m²·d); 四档围护各配窗 SHGC / 屋面 SHGC / 不透明面吸收率(ASHRAE Fundamentals Ch.15, 温室档透光屋面 0.55, 近被动房 0.35); 不透明面按 sol-air 折算(h_o = 22.7 W/m²K, 墙面 ×0.5 朝向因子)。求解器 solveRoomAir 对显热与湿平衡两线性式经结露潜热耦合做阻尼不动点迭代; 池面显热系数不另取 5 或 15 的常数, 由既有蒸发系数经 Lewis 类比反推(蒸发与显热永远同一张换热面); 通风显热直接复用 0.34 系数的 UA 项, 与报告口径同源。超饱和时钳到饱和线、结露潜热回注、凝水量作腐蚀风险告警。
③ 简易档的"零"是设备不存在, 不是省略
ventilated/open 档下暖通显热与除湿电耗严格为 0 并由断言 R3 锁死 —— 防「算了不计」复发。池面显热带符号接入水侧热平衡: 三亚设计夏车间解至 36.7°C(高于 27°C 水温), 池面转为得热, 水侧冷负荷 3,894 → 4,520 kWh/d。同基准三方对照(石斑/三亚/密度20/air/关塔/annual): 恒温车间 hvac-off 63.9 元/kg(仍计除湿 711 kWh/d 电, 那台机在该档真实存在)→ 简易棚 62.6 元/kg(除湿整机移除省电 > 池面得热代价, 而热泵冷负荷确实上升 6,157 → 6,298 kWh/d)。v1.x 失败特征(车间坍缩到室外之下、池面白捡散热)经三组实测锚点标定确认未重演。
④ 断言 R1–R5 与分层负对照
R1 收敛性(不收敛即红, 界面同步 danger 告警)· R2 物理界(T_room ∈ [min(室外,水温,地温)−0.1, max(室外,水温)+30] —— 对规格原文下界做了推广: 冷水鱼种的池面是合法低于室外的热汇)· R3 模式互斥 · R4 取值合法性与向后兼容(未设 roomMode 的旧方案 JSON 必落恒温档)· R5 空气节点显热闭合(围护+通风流出 = 太阳+内热+池面+尾气+结露, 守恒断言 B 的自由节点版)。五组破坏性负对照逐一验证每道闸门真会咬人: 强制不收敛→R1 红、撤暖通守卫→R3 红、求解器丢尾气项→R5 红、太阳归零→标定钩子红、池面归零→标定钩子红。新增标定探针固化三组实测锚点(摩洛哥温室鳟场 / 大连河豚棚 / ASHRAE 自然通风泳池馆), 判据为方向+有界偏移+三类热源在场, 点值降级为诊断输出 —— 追着实测点调工况旋钮是曲线拟合表演, 探针头注保留了这条教训。
⑤ 顺带修复: 关塔空气曝气的 CO₂ 新风份额(v2.3 遗留)
air 模式气泡把池水 CO₂ 吹入车间, 新风需求此前只按脱气塔份额计。现关塔且曝气入室时份额取 1(全部 CO₂ 产量入室, 零新常数)。该修复对 air+关塔的恒温档工况有真实影响(新风↑ → 除湿/暖通构成变化, 三亚案例约 +0.3 元/kg); 13 条数值基线全 o2 模式, 逐位不受影响。开塔时曝气残余吹脱份额仍未细分, 维持开放项。
⑥ 使用要点
全局热工面板顶行新增「🏠 车间模式」。选简易棚后建议把 ACH 调到 15–30(输入上限已放宽到 30); 简易档下若出现结露告警(围护内表面凝水 kg/d), 优先加大通风或对围护做防凝露处理; 车间比水温低超过 2 K 时提示寒潮核对鱼种耐受下限。恒温车间档行为与 v2.3 完全一致。
🚀 v2.3 · 第七轮审计处置(2026-08-10)
第七轮独立审计报出两条严重回归, 共性诊断准确: 都是一次正确的重构越过了自己的边界, 且检测缺口同构 —— 断言查的是内容, 失效发生在结构。本版逐条实测核实后处置 (两条属实且比报告更重、一条属实、一条含误报更正、一条按设计决策收窄), 并把「结构」维度补进断言体系。自检 21 → 22 项, 工况 22 → 23 条。
① P0 · <head> 被多余字符提前闭合(实测三处, 审计报一处)
v2.2 构建期补丁写入的三处 description(meta / og / twitter)行尾各多一个 >。HTML5 解析器在第一处把 <head> 提前闭合, 20 个 meta + canonical 全部掉进 <body> —— 爬虫只解析 head, canonical / robots / og / twitter 全部失效; 而 v2.2 的 description 断言用 document.querySelector, 元素在哪个父节点它不关心, 照样全绿。修复三字符, 并新增 head 结构断言(元素必须真的在 document.head 内), 按「先转红再修复转绿」落地 —— jsdom 内部即 parse5, 负对照免费。
② P1 · 方案 JSON 加载路径绕过全部钳制
v2.2 撤消费处 || 默认 时把 Math.max 取值域守卫一并删了 —— 前者是真相源问题、后者是校验边界问题, 写法相似位置相邻但性质不同。而 clampByFieldSpec 只在 UI 读取路径调用, 载入越界方案 JSON 长驱直入(实测 co2BlowerEfficiency=0 → NaN 遍布, co2StripperGtoL=1e6 → 成本 109 万元/kg)。修复: ensureStageProc 兑现自己注释里宣称的不变式(对 PROCESS_DEFAULTS 数值键逐一钳制)—— 它是 recomputeAll / rebuildStages 的公共入口, 钳制放这里后「UI 有守卫而加载没有」在结构上写不出来。新增工况 edge_hostile_json(六个越界值), 修复前咬出 Infinity/NaN 于能量 A2/B/C、登记 E1、物质 M4a, 修复后转绿且 13 条数值基线逐位不变。
③ P2 · 鱼池曝气尾气进车间(进气维持室外取, 设计决策)
鱼池是车间内的敞开水面, 气泡上升后在池面破裂 —— 尾气必然进入车间且已被水温饱和; OUTDOOR→OUTDOOR 只对有排风管的脱气塔成立。sink 改 INDOOR, 车间湿平衡产湿侧新增 ṁ_air × (W_sat(T_w) − W_room)。实测罗非养成(28°C/65%): 1,384 m³/h → 336.1 kg/d, 为池面蒸发的 +29%(审计估 335, 手算 337, 三方吻合)。显热恰为零(A3: T_room = T_w); 水侧补水股无重复(接触器 netWater 记水体失水、本项记车间得湿, 两本账各一次)。成本影响: 仅 air 模式的除湿机设计选型 CAPEX(罗非 +0.007 元/kg), 年均电耗不变; 13 条数值基线全部 o2 模式, 逐位不变。进气侧维持室外取气(2026-08-10 设计决策: 进气经风管自室外), 接触器进气温湿度继续用室外值。开放项: air 模式气泡吹脱的 CO₂ 同样入室, 新风需求目前只计脱气塔份额, 二阶项未建模。
④ P3 · 全局热工配置的分散声明(含审计误报更正)
默认值同时写在 GLOBAL_THERMAL_DEFAULTS 与 readGlobalThermalConfig 的字面量 fallback 里, degasserExhaustIndoorFrac 已实际漂移(表 0 / fallback 0.2 —— 0 正是 v2.1 为气路 A5 改的值, 副本停在旧值, 输入框缺失即复活旧矛盾)。实测更正: 缺失键为 4 个非审计所报 6 个(三个 U 值在表内, 审计漏了 fcDesignDT_K); 另抓到审计未报的第三处副本 —— 消费点一处 : 0.2 死 fallback(合并链路保证键必在, 永不触发, 但停在旧值待复活)。处置: 补 4 键入表 → 32 处 fallback 一律引表 → 死 fallback 改引表(保留 [0,1] 取值域守卫)→ 两条断言: 键集锁定(配置出现表外键即红)+ 运行时 toString 扫函数源码禁字面量 fallback(首版正则曾漏 3 处对齐多空格行, 是脚本残留自查抓到的 —— 故把这层也固化为断言)。双向负对照精确。
⑤ chart.js SRI 落地(含一处对 v2.2 注释的事实更正)
v2.2 注释预想「联网后 curl 原 URL 取哈希」—— 实测该 URL 的 chart.umd.min.js 在 npm 包内不存在, 是 jsDelivr 对缺失 .min.js 的动态压缩产物, 给它钉 SRI 与给 Tailwind Play 钉 SRI 是同类错误的温和版。处置: URL 改指包内真实发布件 chart.umd.js, 哈希链条 = registry 声明 sha512 → 本地 tarball 复算逐位一致(供应链验证)→ 发布件 sha384。integrity 与 crossorigin 成对上线(v2.2 教训)。新增断言: jsdelivr 脚本必须两者成对、Tailwind Play 必须两者皆无(防 v2.2 样式崩掉事故复发), 双向负对照精确。
⑥ 第二批 · 曝气风量物理选型(海南低密度主案例驱动)
旧 air 模式风量 = O₂峰值 ÷ (1.2×0.23×SOTE 0.18) —— 0.18 是清水标准态值(20°C、DO=0), 无水深/α β θ/驱动力修正, 实际 OTE 只有它的 1/3–1/2, 风量系统性低估 2–4×; CO₂ 侧风量需求完全缺席, 关塔时只有一个与风量不联动的 fallback 旋钮(海南石斑 20 kg/m³ 关塔实测: 鱼池 CO₂ 55–179 mg/L 超海水阈值 2.5–8 倍, 选型侧毫无反应)。本批实装物理选型: OTE = SOTE(%/m)×水深×αβ×θ^(T−20)×驱动力, CO₂ 侧按 Henry 平衡载量(复用碳酸盐模块 K0_W74 与 ρ 口径, 零副本), 风量取 max(O₂侧, CO₂侧) 并标注控制侧; 关塔稳态 η 由风量反算(η = A·k·逼近/Q), 选型与稳态自此共源。新断言 M8(air+关塔 ⇒ 稳态 CO₂ ≤ 选型目标)按红→绿落地: 旧模型下 144–179 mg/L 转红, 实装后 20.5 mg/L 转绿; DO 饱和(Benson 1984)与 CO₂ 阈值两段内联逻辑抽为共享函数(表达式逐字符不变), 13 条 o2 数值基线逐位未动。新工况 edge_air_no_stripper(石斑/air/关塔), 自检 23 条工况。实施事故记录: 调 K0_W74 时未读函数体先传了 °C(其 Weiss 式入参为 Kelvin), airCO₂ 爆至 1e10 m³/h —— 本会话第六次「猜接口」, 已在调用处留 Kelvin 警示注释。
⑦ 交付方式变更: 补丁链封存
v2.2 的 patches/ 与 tools/ 目录不可得(无法从原环境下载)。自 v2.3 起直接在 v2.2 交付产物上编辑, 「从 v2.1 全量重建」的可复现性就此封存; 验证工具按 HANDOFF §4 规格重建并显式标注(run_selftest / probe / _edit 编辑纪律库, replace_once 强制唯一命中保留)。编辑纪律不变: 每处替换恰好命中一次、断言先负对照转红、基线变动先预测符号量级。
⚙️ v2.2 发布后持续加固
以下三节发生在 v2.2 发布之后, 不改变 v2.2 的功能范围, 只增加护栏与修正 —— 依次为: 碳酸盐接口一致性与 a11y/CDN、物质平衡断言 M、第三方独立复核处置。v2.2 本体的两批发布内容见下方「v2.2 第二批」与「v2.2 升级说明」。
🔗 碳酸盐接口一致性 · a11y · CDN 加固
A. 碳酸盐模块的【接口】验证(此前只验过模块本身)
第三方复核已用 PyCO2SYS 1400 工况验证 carbonate.js 模块本身(淡水 pH 一致到 8.88e-16)。但主模型与它之间隔着一层单位换算,那一层从未验证:TA = 碱度/50043/ρ、CO₂ = 浓度/44010/ρ(mg/L ↔ mol/kg-SW),三个因子任一错,pH 与 Ω 都会偏而无人察觉。
- I1 往返自洽 —
solveAlk(pH*) → solve(TA) 应还原 pH*。240 点网格(S∈{0,15,30,35} × T∈{10…30} × pH*∈{6.5…8} × CO₂∈{3,10,25}),最大 |ΔpH| = 0,零抛错
- I2 换算层 — 手工按 50043 / 44010 / ρ 复算,pH 与 Ω 与模型逐位相同(Δ = 0)
- I3 双副本 —
work/carbonate.js 与 index 内联块,20 个函数、63 个平衡常数,归一化后md5 完全相同
📌 真正的风险不是「当前是否一致」(是),而是将来会不会漂移 —— 同一份求解器存在两处,没有任何东西阻止有人只改一处。I3 即为此而设。
B. a11y:65 处 label↔input 关联 + 12 个表格横向滚动
UI 审计发现 163 个 <label> 中零个有 for,输入框也全部无 id。后果不止屏幕阅读器:点击标签无法聚焦输入框 —— 这是所有人都损失的常规交互,而工艺参数标签只有 10px,点击目标本就很小。现按 f_${idx}_字段名 生成 id 并关联。另 31 个表格中 12 个未包 overflow-x-auto,窄屏撑破布局,已全部包裹(已验证表格无嵌套,配对包裹安全)。
新增自检三条:工艺参数输入框必须有 id、for 不得悬空、表格必须可横向滚动。
📌 实施过程的教训:正则版本改了三轮仍漏 1 处 —— 查明是 Python re 在超长 title 属性上回溯超限后【静默放弃匹配】(不报错,只是不匹配)。改为线性扫描后一次到位。这类「工具自己悄悄少做了事」与 t21 三次扩展全部落空是同一形态,而两次都是断言先报出数目不对才发现的。
C. CDN 加固:能做的做,不能做的说清楚
两个 CDN 资源性质不同:Tailwind Play CDN 按页面内容动态生成响应体,SRI 哈希无法预先计算 —— 加了必然阻断加载,这是技术限制而非疏忽(要 SRI 须改为构建期产出静态 CSS)。chart.js 是固定版本文件,可以且应该加 SRI。
但本版不填 chart.js 的哈希:当前环境拿不到真实值,而编造哈希会让浏览器拒绝执行脚本、图表直接消失,比没有 SRI 更糟。故只做两件确定的事 —— 两个 script 都加 crossorigin(SRI 前置条件,且让跨域错误可见),并把取真值的一行命令写进代码注释供联网环境补上。
📌 与本项目「判据先负对照证明会红」同源:安全措施也不能靠猜值上线。
🧪 物质平衡断言 M (补上最后一块无护栏区域)
此前断言覆盖能量 (A1/A2/A3/B/C)、水量 (F1/F2/F3)、登记 (D/E/G)、资本 (F4) —— 唯独物质平衡一条没有,而它是本工具的物理核心。第三方独立复核给了这个判断一次印证:三条缺陷全部落在无断言区,断言覆盖区一条问题都没有。
实测确认今天全部闭合后立即固化 —— 此刻成本最低:闭合状态是免费拿到的验收基线;等日后有人改了硝化公式再来查,就得先分辨「是新改的破坏了平衡,还是本来就不平衡」。
断言清单
- M1 氧-构成 总需氧 = 鱼呼吸 + 硝化 + 异养
- M2 氧-硝化 硝化耗氧 = TAN × 4.57
- M3 氧-供给 液氧耗量 = 鱼呼吸耗氧 / 吸收率(纯氧模式)
- M4 碱度链条 净额 = 消耗 − 回收;投加 = max(0, 净额 − 补水带入);药剂 = 投加 / 0.595
- M6 碳-构成 CO₂ = 呼吸 × 1.375 + TAN × 6.286 × (1 − BF 内脱气)
- M5 氮-稳态 TAN = 单程增量 / (1 − R_tan),R_tan = (1−η_bio)(1−换水稀释)
- M7 TSS-稳态 TSS = 单程增量 / (1 − R_tss),R_tss = 四路径 (1−η_rdf)(1−η_skim)(1−η_bioTSS)(1−稀释)
M5/M7 适用域: 水体 ≥ 1 m³。实测产量 1000 t 时四阶段偏差 0.01–0.03%(方程完全成立),产量 0.001 t 时散乱在 −9.7% ~ +11.2% 且方向不一 —— 散乱而非单向偏置,说明是极小规模下的数值行为(水体仅 2 升级、源水本底与各种钳制浮现),不是方程实现有误。极小产量工况的价值在于测崩溃,不在测稳态精度。
TSS 结案:模型正确,是核算漏项
v2.2 首测曾报「TSS 残差 −1.8%,四阶段同号且随规模成比例,未确证为缺陷」。查证结果:模型正确。TSS 单程残留是四条路径,而首测只算了微滤、蛋分、换水三条,漏了 η_bioTSS = 0.2 的生物滤池截留 —— 它是 PROCESS_FIXED 常数而非 proc 字段,按字段名扫描根本看不见。补齐第四路后残差降到 0.02–0.04%。质量侧另有独立证据:代码 6766 行 disch_TSS_kg_d = load.tssDaily,注释「守恒 — 生成 ≈ 排出(不溶不降解,全部最终出系统)」。
📌 这次漏项的形态与前十次不同:前十次错在取值的键名或单位,这次错在漏了一整条物理路径。教训相应更新 —— 核对稳态方程时应以方程本身为准逐项对齐,而不是从导出字段反推路径。建立 M5/M7 过程中另有两次同族错误:exchangeDaily 取值位置错(它是 stage 级字段,不在 proc 里,正是 t23 白名单三项之一)、以及首版适用域用比值而非物理条件。
三条负对照复现了历史上真实发生过的 bug
把过去修过的缺陷形态人为注回代码,断言逐条咬住:
- M6 ← 注入「CO₂ 漏硝化项」(第三方复核 BUG-2 的形态):报 −41.5%,与报告实测的 34.5–43.4% 同量级。若 v1.9 之前就有这条断言,那个 bug 根本写不出来
- M3 ← 注入「液氧用总需氧而非鱼呼吸」(v1.9.2 修过的口径混淆):报 +104.7%,与当年记录的「高估 1.65 倍」同量级
- M2 ← 化学计量 4.57 改 4.60:报 +0.656%
另有 M4b 落地当场咬中 edge_yield_0 / edge_yield_tiny 八条 —— 零产量时补水带入的碱度超过消耗,净需求为负而 alkToAdd 被钳到 0。经核实钳制是正确行为(不可能投负量药剂),故改断言纳入下钳而非改代码。
建立过程本身的教训
外部核算这三条口径连续错了三次:液氧误用设备选型口径 o2Demand(含 peakFactor 1.5 与 sAerator 1.10 两个容量系数,报出 −48% 的假不闭合)、碱度消耗误用 tanDaily 而非被硝化的 TAN、碱度回收误用理论值 3.57 而非代码里的工程系数 2.8(注释写明「理论 3.57,工程实测 2.5–3.0 取中位」)。加上此前物质平衡首测的三连错,同族「猜口径」已达十次。
📌 这正是要把核算放进产品路径的理由:断言从 load / sim / eq 的真实导出面取值,不会像外部工具那样猜错口径。「人工核对过所以没问题」不可信 —— 十次实证。
🔍 v2.2 第三方独立复核处置 (2026-08-08)
一份独立复核报告 (jsdom 走真实产品路径 + PyCO2SYS 1.8.3.4 / USGS·APHA 溶解氧表 / ASHRAE·IAPWS 湿空气数据 / 第一性原理手算) 提出三条模型误差。三条全部落在本版自述的断言盲区 (氧侧 / 碳侧 / 水汽侧) —— 该判断准确, 也正是 F 节所说「今天闭合是因为公式恰好写对, 不是因为有东西防止它写错」的直接印证。逐条实测核实后: 一条采纳、一条部分采纳、一条驳回。
✅ 采纳 · co2DailyAvg 漏硝化产 CO₂ (已删除)
co2DailyAvg = o2FishDailyAvg × 1.375 只算呼吸, 缺硝化项, 实测少算 34.5%–43.4%。而同文件的设计口径 co2Daily 是对的 —— v1.9 memo F2 修过同一个 bug「v1.7 之前只算呼吸, 漏算硝化产 CO₂, 导致脱气塔规格偏小 30-50%」, 设计口径已修而日均口径未跟上。这是「同一公式两处实现」的又一实例, 与本版反复处理的「分散重复声明」同族。
全库 0 消费点, 属死字段 ⇒ 删除而非修正。复核报告引用本版删 sourceTemp 时确立的原则「给死控件加钳制等于承认它还活着」来主张删除, 该引用恰当。其风险论证也成立: 带错误公式的死字段比空字段更危险 —— 谁把它接进年度碳核算, 会静默低估三四成且不触发任何现有断言。
◐ 部分采纳 · 0°C 以下饱和水蒸气压 (只订正注释)
复核指出 0°C 以下应使用冰面式, 数值全部属实 (−3.5°C 高 3.50% / −10°C 高 10.44% / −20°C 高 21.97%), 原注释「±0.4% in [−20,60]°C」的下界确实不成立。
但结论需改: 本项目继续用水面式, 且这是自洽的。本项目 RH 数据源标注为「WMO/Climate.gov 30 年均值」, 而 WMO 对 0°C 以下的相对湿度以水面 (过冷水) 饱和压定义, 是气象观测通行口径。humidityRatio(T, RH) 用同一相态把 RH 还原为分压, 前后一致 ⇒ 含湿量正确。改用冰面式而 RH 仍是水面口径, 反会使含湿量系统性偏低 —— 相态必须与数据定义口径一致。故只订正注释适用区间为 [0, 60]°C, 并在函数头写明相态选择依据, 避免下一位读者重复得出「用错相」的结论。
✗ 驳回 · 海水 DO 饱和度「单向偏高 3.3%」
复核建议「改用 Benson & Krause (1984)」—— 而代码用的就是该式本身。逐点比对: S ∈ {0, 30} × T ∈ {10,15,20,25,28,30} 共 12 点, iRAS 与 B-K 标准式偏差全部为 0.0000% (盐度项写法 +S·(−a+b/T−c/T²) 与 −S·(a−b/T+c/T²) 代数等价)。
报告「参考值」反解出的盐度为 ≈35.5‰ (15°C 参考 8.11 → 35.5‰; 20°C 参考 7.38 → 35.4‰; 25°C 参考 6.75 → 35.6‰), 而它对比的是 iRAS 在 S=30 的计算值。拿 S=35 的表值比 S=30 的计算值, 差的 3.3% 正是盐度效应本身, 不是公式误差 —— 报告自己也写着「工况盐度 28-30‰」。「四个测点全部同号」确为系统性偏置, 但偏的是盐度输入。另有一处内部佐证: 报告 28°C 行「iRAS 6.410 / 参考 6.41」数值相同却标 +3.33%, 那个 6.410 实为 T=30 的值, 表格行错位。
📌 此条与第六轮审计的 NH₃/Ω 误报属同一类: 基准取值与被测对象的工况参数不一致。复核方法本身 (jsdom 走真实路径 + 外部权威基准) 是正确的, 出问题的是基准侧的参数对齐 —— 这与本版自己七次「猜接口」错误同源: 从外部取值时必须先确认口径。
附带处置
towerFlowM3h 零值噪声: 复核实测加载时触发 8 次告警, 已自查为误报 (全部来自 edge_yield_0 / edge_yield_tiny, flowM3h = 0)。已加零值短路 —— 噪声会淹没这条告警要抓的真回归, 消除噪声本身就是在保护告警。自检项数陈述: 第一批章节的「17 → 20 项」是当时的准确记录, 第二批新增 field_spec_consistency 后总数为 21, 已加衔接说明。
复核报告的「复核通过项」值得一并记录: 化学计量 8 项对第一性原理手算全部吻合; 碳酸盐体系对 PyCO2SYS 1400 工况, 淡水 pH 一致到机器精度 8.88e-16、海水最大偏差 1.78e-05; 能量守恒 B 断言残差 0.000e+0。这些是本版断言已覆盖的区域, 复核未发现问题 —— 与「三条缺陷全在盲区」互为印证。
📦 v2.2 发布内容
v2.2 分两批发布 (同版分批, 沿用 v2.1 先例)。
📌 v2.2 第二批 (2026-08-07) — UI 审计 · 字段范围单一事实源
源于本版的首次 UI 专项审计。与第一批的登记表工作是同一个模式在 UI 侧的实例: 分散的重复声明必然漂移。自检 20 → 21 项, 13 条数值基线逐位不变 (纯重构)。
A. 🔴 填 0 → 界面回显默认值 → 用户输入被静默丢弃
完整危害链 (已在真实事件路径复现): 用户填 0 → 模型收 0 → 任何触发重渲染的动作后输入框显示默认值 (模板 ${p.X || 默认} 把 0 当 falsy) → 再改任何别的字段, readStageProcFromInputs 全量读回时那个假显示值覆盖 proc, 用户的设置无声消失。这与 v1.9.2「对外发布数字与产品路径脱节」同族, 只是发生在 UI 层。
⚠ 关键教训: 陷阱在三层 (渲染 31 处 / 读取 / 消费 71 处), 只修一层反而更糟 —— 实测只修渲染侧会造成「界面诚实显示 0、模型仍用默认值」, 比原状态危害更大 (原状态至少界面与模型口径一致)。三层必须同批落地, 本批即如此。
B. 🟠 FIELD_SPEC —— 字段范围的单一事实源
同一字段的取值约定此前散布在四处: 默认值表、渲染模板的 min/max/step 与 || 默认值、读取侧的 getNumClamped、消费侧的 || 默认值。实测已漂移四条 —— co2StripperEff 在三处是三个不同的值 (65 / 65 / 75); co2StripperGtoL 的事实源在 v2.0 由 3 改为 5, 另两份副本停在 3; co2PackingHeight_m 同理 (1.5 vs 1.0); climateRegion 事实源 china_qingdao 而消费侧 custom。这些副本平时是永不触发的死代码 (默认表已填好值), 所以漂移了也无人发现 —— 直到用户填 0 让它们复活。
FIELD_SPEC 只管范围 (min/max/step/clamp), 默认值仍归 PROCESS_DEFAULTS, 由断言 H 保证两表对齐 —— 而不是再复制一份 (那会制造第五处声明)。三个消费方 (渲染 / 读取 / 断言) 从同一张表取值。clamp 取UI 范围与原代码钳制的并集: 这条规则是机械的, 不含逐字段工程判断, 且不收紧任何现有能力 ⇒ 无回归风险。
C. 🟠 一并消灭的三条存量问题
- UI 范围 ≠ 代码钳制 (7 字段) — 如吸收率 UI 写 [70,99] 而代码钳 [0,100]: UI 上那些有文献依据的工程范围只是装饰 (
type=number 的 min/max 不阻止键盘输入)
- 22 字段完全无钳制 — 各安全系数 / COP / HRT / G:L 等, 手打任意值直接进模型。现统一由
FIELD_SPEC.clamp 执行
- 同字段多处渲染范围矛盾 (3 处) —
co2BlowerEfficiency 一处 max=90 一处 85 等, 用户在 A 面板能填的值到 B 面板填不进去。三处渲染同一张表后自动消失
D. 断言 H 与两条判据
新增自检项 field_spec_consistency: H1 每个带范围的输入框 (含全部渲染实例) 的 min/max/step 必须与表逐位相等; H2 clamp ⊇ [min,max] (UI 能填的模型必须接受); H3 表内字段必须在 PROCESS_DEFAULTS 有默认值且落在 clamp 内。落地时精确抓出三处渲染矛盾, 负对照 (改回硬编码) 转红。
E. 🟠 覆盖扩展与 clamp 规则修正 (讨论后落地)
第一版 FIELD_SPEC 只收了原本带 min/max 属性的 34 个字段, 另有 22 个字段完全无钳制 —— 实测 mainPumpEta=0 使主泵报 1383.6 kW (成本 +6.6 元/kg)、VTR20=0 使成本 +12.0 元/kg, 全部有限无 NaN: 危害是「看起来正常的错误数字」, 会原样进可研报告。现已全部接入 —— 依据取自各字段自己的 title 提示 (22 条无一缺失地写明了工程范围, 只是从未落到属性上)。
本批 min/max 取 tooltip 的工程范围 (spinner 边界与用户指引), clamp 取更宽的物理边界 —— 与第一批规则不同, 因两批字段性质不同: 第一批多是效率/比例类硬边界, 本批多是「典型值区间」, 若当硬边界会挡掉合法探索 (大型轴流泵 η 可达 90 而 tooltip 写 60-85)。
并集规则的缺陷与修正: 第一批的 clamp = UI 范围 ∪ getNumClamped 漏了第三个来源 —— 消费处的 Math.max(x, 下限) 隐式约束。它与前两者性质不同: 前两者是允许区间 (取并集=不收紧能力), 后者是物理硬保护 (必须遵守而非并入)。后果: co2BlowerEfficiency 的 clamp 是 [0,100] 而消费处写着 Math.max(η/100, 0.3) —— 用户填 0 时 proc 存 0、界面显示 0、模型实际用 30%。现规则修正为 clamp_lo = max(消费处硬下限, 原下限), 并撤除全部 8 处隐式兜底。
⚠ 该缺陷是判据扩展后才发现的: t22 检查的三个量全在 proc 层, 看不见消费处, 故它一直报「保真 ✅」—— 这是第二次实证「一条判据绿了不等于该层被覆盖」。t23 现已扩展为同时禁止 || 默认 与已登记字段的 Math.max(x, 下限) 两种同族写法。
另删除死控件 sourceTemp: 双证据确认 —— 静态全库无消费点、动态填 -99 结果逐位不变。源水温自 v1.2 起归全局气候 (T_source_annual 等)。给死控件加钳制等于承认它还活着, 故删而不修, 与第一批删 skipClimate 同一原则。
F. 物质平衡实测 (本版仅验证, 未建断言)
发布前对四条基本物质平衡做了产品路径实测 (三文鱼 1000 t): 氮 (TAN) 残差 0.08% · 硝酸盐 0.00 · CO₂ 0.13% (即换水那份) · TSS −1.8%。前三条闭合, 说明稳态方程是自洽解出的; TSS 的 −1.8% 四阶段同号且随规模成比例, 但未确证为模型缺陷 (更可能是核算漏了排污股)。
⚠ 需明确记录: 现有断言覆盖能量 (A1/A2/A3/B/C)、水量 (F1/F2/F3)、登记 (D/E/G)、资本 (F4), 没有一条物质平衡断言 —— 今天闭合是因为公式恰好写对, 不是因为有东西防止它写错。氧平衡、碱度平衡、碳酸盐体系 (carbonate.js 与主模型的交叉一致性) 三项连「今天是否闭合」都未测过, 已列为下一版首要工作。
📌 该实测过程本身产出一条教训: 外部核算连续三次出错 (co2Eff 单位误除 100 倍、猜错反硝化字段名、NO₃ 以 N 计却做了 62/14 换算), 每次都差点报出巨额假不闭合。「人工核对过所以没问题」不可信, 只有跑在产品路径上的内建断言可信 —— 这正是应当把这四条平衡固化为断言的理由。
⚠ 判据盲区实录: t22 (显示保真) 检查的三个量全在 proc 层, 而消费侧的 || 不影响 proc —— t22 结构上覆盖不了第三层, 负对照实测它对恢复消费侧兜底毫无反应。故另补静态判据 t23 禁止 proc.X || 默认 复发。一条判据绿了不等于该层被覆盖。
📌 v2.2 升级说明 (2026-08-07) — 工况覆盖补盲 · 登记表声明自校验
本版源于第三方第六轮审计。审计的核心判断本版照单全收: 断言的质量已超过工况的覆盖面 —— 本轮两条真缺陷现有断言本可抓到, 只是 13 条数值工况全部「mainline + 脱气开 + 纯氧」, 断言从没机会跑到那条路径。自检 17 → 20 项 (新增三条配置边界工况), 13 条数值基线重算 (|Δ| ≤ 0.02 元/kg)。〔第二批另加 field_spec_consistency, 本版最终为 21 项〕
※ v2.1 变更日志曾写「本批并入 v2.1, 不另开 v2.2」—— 指的是当时那批补丁不单独成版, 与本版无冲突。
A. 🟠 三条配置边界工况 (edge_no_degasser / edge_bypass / edge_air_aeration)
只跑不变量断言、不比数值。落地顺序刻意为先加工况 (转红) 再修缺陷 (转绿) —— 红→绿转换本身就是工况有效性的负对照。实测 edge_no_degasser 一个开关同时触发 F4 与 A3 两条断言 (审计称其为「目前最有信息量的未覆盖工况」), edge_bypass 精确复现 capexVia 假阳性 (92.2 kW 与审计逐位一致)。
B. 🔴 蛋分风机 CAPEX 嵌套 (P1) + ΔT/mdot 不动点 (P1)
CAPEX 嵌套: 蛋分风机条目嵌在脱气塔 if 两层内 —— 两套互不相干的设备, 脱气关闭时蛋分照常运行照常计费, 投资却随脱气塔消失。这是 F4 目标模式的第四次出现, 且出现在修复它的同一次提交里。现提出与各独立设备并列。
ΔT/mdot 密度不闭合: 风机温升 ΔT 按冷密度算、焓流按热密度算, 注入热 = 轴功 × (mdot/mdot₀), 丢失的那份默认工况仅 1.2% (藏在 A3 的 0.15 K 容差内), 脱气关闭时风量骤小、ΔT≈30 K, 丢 10.1%。现解联立方程做不动点迭代至 mdot·cp·ΔT ≡ Pshaft (6 次内收敛到 10⁻⁴ K)。
C. 🟠 轴功 η 逐台加权 (P2)
旧口径 ΣkW × 0.57 与各风机自身效率参数脱节 —— 用户把 co2BlowerEfficiency 从 30 调到 90, 电耗 174→95 kW, 模型轴功却恒按 0.57 折, 方向反直觉 (效率越高, 模型认为进气流的热越少)。物理上 kWi×ηi = Q·Δp (气侧有用功), 与 η 无关, η 只该影响电机损失分成 —— 逐台加权自动恢复该性质, η 与各功率公式严格同源。
D. 🟠 第四台风机: 鱼池增氧鼓风机 K-101 全量接入 (P2)
空气曝气模式的鱼池增氧鼓风机此前「计费有、CAPEX 有、热去向无、气路登记无」—— 与 v2.0 的 Q_air_gain、v2.1 的蛋分气流、第五轮的风机轴功完全同族, 这是第四次。它鼓的气直接打进鱼池水体, 比脱气塔更无疑问地接触水。现补 airFlowM3h + airPath (OUTDOOR→OUTDOOR, 与 A5 一致)、进登记表、并入轴功加权。逃逸机制值得单独记一笔: 32.3 kW 藏在混合数值与格式化字符串的展示对象里, 所有「数值字段 + 命名规则」的扫描器集体失明。
E. 🟠 断言 E2b: 递归扫 *Spec 子对象
封住 D 项那一类逃逸路径: 对每个 /Spec$/ 普通对象再扫一层, 数值且形如功率的字段, 其点路径必须出现在某登记项的 path 或 aliasPaths (同一设备的已知副本路径, 如 skimmerBlowerSpec.power = 顶层标量的同值副本 —— 声明而非豁免)。⚠ 这是治标; 治本是 registerDevice 工厂化 (登记成为设备定义入口而非事后清点, 「漏登记」变成写不出来的代码), 已立项 v2.3。
F. 🟠 capexVia 由正则改精确名称数组 + capexMinKw
旧正则两个方向都错: /曝气|风机|鼓风/ 命中 4 条, 删掉真目标断言仍绿 (假阴性); bypass 下旁路泵计入「支路泵合计」无独立条目, 零命中导致 F4 四阶段误报。CAPEX 条目名全是字符串字面量, 正则无必要 —— 改精确名称数组, 五台支路泵显式声明共享聚合条目 (capexShared)。精确化随即曝光一条被误命中掩盖了四个版本的存量不一致: BF/脱气塔风机的 CAPEX 选型门槛是 0.5 kW (小于此并入 MBBR 曝气配件价), F4 却统一按 0.05 —— 鳗/虾苗种期 0.4 kW 恰落中间。门槛现显式化为登记表的 capexMinKw, 与 CAPEX 侧同源。
G. 🔴 断言 G: 登记表声明自校验 (堵 v2.1 明示的最后盲区)
v2.1 HANDOFF 原话:「billedVia 可以指向一个不存在的成本字段而断言不会发现」。断言 G1 (billedVia 必须真实存在于 cost 导出、设备活跃时非零) 落地当天抓到 5 条活体 —— 声明写的全是内部变量名 (*CostDaily), cost 导出面是 *CostMax/*CostAvg。认识论上与第六轮审计连错两轮的 dehumCostDaily 是同一个坑: 靠字段名猜接口, 而不是以导出面为准。G2 校验非共享 capexVia 名称不被两条登记项重复认领 (防双计)。三条负对照 (拼错字段 / 指向零值 / 重复认领) 全部精确转红。
H. 🟡 skipClimate 死参数删除 · 渲染与计算解耦
skipClimate: 全库零调用点 (restoreState 直接恢复 stagesState、不经 loadStagesFromDB) —— 为不存在的调用方准备的开关只会在将来被忘传时静默伤人, 已删。渲染解耦: 渲染函数抛错此前被计算 catch 吞掉, 报「阶段 N 计算异常」且销毁本已算好的结果 —— 渲染故障伪装成计算故障。现独立捕获, 计算结果保留, 错误明确标注「渲染异常, 计算结果有效」。负对照双向实测: 修复前人为渲染故障使 13 条数值全灭为 0, 修复后零影响。
I. 基线重算与审计误报驳回
13 条基线 |Δ| ≤ 0.02 元/kg, 符号随工况方向翻转 (制热 ↓ 青岛冬 −0.02 / Bergen −0.02, 制冷 ↑ 海南·三亚 +0.01) —— 与 v2.1 两轮同款的正确性旁证。另驳回审计三条: NH₃ 阈值「六轮未动」与 Ω 告警「六轮未动」均为第四轮已驳回误报的复发 (v1.9.3 起分别按鱼种三档着色、两级徽章告警, 本版实测在位); 「接触器进气节点不可选」为记录在案的设计决策而非漏项。
📌 v2.1 升级说明 (2026-08-01) — 能量守恒修复 · 守恒断言落地
本版源于第三方第四轮审计。核心是修复 v2.0 架构重构时留下的能量守恒破口, 并补齐审计建议了三轮、唯一没落地的守恒类断言。13 条数值基线全部重算, 自检 17 → 17 项(新增三条守恒断言并入既有不变量)。
A. 🔴 车间显热能量守恒破口 (P0)
v1.x 的能量分配是自洽的: 泵/UV 电功率拆两份, 一份进水 (Q_pumps), 一份进车间空气 (Q_air_gain_kW), 相加恰为电功率。进空气那份在旧模型里进入空气节点能量方程。
v2.0 删除空气节点时, 挂在该节点上的产热项没有被重新安置。Q_air_gain_kW 仍在计算、仍在返回, 但不再进入任何一侧平衡 —— 既不在水侧, 也不在暖通侧。这部分能量从模型里凭空消失。
同因还有第二处 (本轮自查发现, 审计未列): dehumHeatDest === 'indoor' 时的除湿机冷凝热。第 5461 行注释写着「排入车间的部分已在 T_room 迭代中计入」—— 而那个迭代在 v2.0 已被删除, 注释与 UI 提示都停留在旧模型。
实测规模 (三文鱼 1000 t 青岛设计冬): 丢失 1,617 kWh/d, 达 HVAC 显热负荷 3,864 的 41.8%。模型一边说要给车间供暖, 一边把电机与灯管废热倒进同一个车间却不入账。
修法: Q_hvac_sens = Q_envelope + Q_vent_sens − Q_room_internal, 其中室内产热 = 设备废热 + 除湿冷凝热(入室时)。符号由 Q_hvac_sens 自身携带, 不需分支。
✅ 修复后成本变化的符号随工况方向翻转, 这是正确性的旁证: 制热工况下降 (青岛冬 −0.22, Bergen −0.15 元/kg, 废热减轻供暖), 制冷工况上升 (海南冬 +0.11, 青岛年均 +0.06, 废热加重制冷)。若为单向偏移, 反而说明修错了方向。
B. 🟠 守恒类不变量断言落地 (建议三轮未落地)
A 项那类缺陷现有三类断言全都抓不到: golden-master 抓不到 (基线跟着一起变)、有限性断言抓不到 (数值完全正常)、口径一致断言也抓不到 (不涉及同一量的两处换算)。只有守恒断言能抓。而同一版里出现两处同样成因的孤儿项, 说明这不是偶发失误, 是架构改动时缺少守恒护栏的系统性风险。
现已并入 ?selftest=1 的逐阶段不变量 (亦有外部脚本 t20_energy_closure.js 同源可挂 CI), 三条:
- A 设备产热分配完整性 —
Q_pumps + Q_uv + Q_air_gain×24 = 泵电功率 + UV电功率。电功率 100% 终归变成热, 只有进水/进空气两个去处, 不得有第三个
- B 车间显热闭合 —
hvacSensible = Q_envelope + Q_vent_sens − Q_room_internal
- C 水侧净热闭合 — 含代谢/设备/补水/蒸发/除湿/接触器六项
修复前实测: 断言 A、C 通过, 断言 B 在 56 个「工况×阶段」组合中 48 项失配, 残差与 Q_air_gain×24 逐位相等 —— 诊断被精确坐实。⚠ 新增任何进/出车间或水侧的热流时, 必须同步这三条式子, 那正是目的。
C. 🟠 自然冷却可见性回归 (P0)
v2.0 新增 fcUnavailReason 时的单行编辑失误, 把同行的 freeCoolingSinkT: _fcSink, 并进了行尾注释 —— 该字段自 v2.0 起不再导出, UI 第四档分支因而永不触发。v1.9.3 加这一档的动机是「用户拨动开关看不到任何回应, 无法区分『因不可用而无效』和『坏了』」, 修好一版之后又回归了。而本该替代它的 fcUnavailReason 做好了却全代码库无消费点。
本轮另发现第三层: 整个状态段包在 pumpDirection === 'cool' 里, 而 v2.0 最常见的原因恰是 not_cool (接触器把热排光, 水侧转制热) —— 即便前两层都修好, 最该解释的情形仍然不说话。
现改为读 fcUnavailReason 枚举 (四档 + 显式 default), 并把 not_cool 提到方向判断之外。缺字段会落到显式分支而非静默跳过 —— 这类「注释吞代码」用眼睛复查很难发现, 改成枚举后才有护栏。
D. 🟡 口径与参数四处
totalMax 补 hvacCostDaily —— 此前只有 totalAvg 含它, 而 totalMax 是面向用户的「小计·工艺运行成本」并导出下游。同为暖通电耗, 除湿计入而显热不计, 无理由 (青岛冬 1,166 元/d, 约 0.97%)
dehumEnabled 开关对成本完全无效却仍 gate 显示 —— v2.0 把除湿改为无条件的暖通职责后, 用户设「不配置」照付 2,989 kWh/d (海南夏) 却看不到卡片。实测开/关成本一字不差。现改为按是否真有除湿负荷显示
degasserExhaustIndoorFrac 默认 0.2 → 0 —— 与气路拓扑 A5「工艺气路全程不碰车间」对齐。同一股尾气此前算热时 0%、算 CO₂ 时 20% 进车间; v2.0 把暖通显热计入能耗后该矛盾首次影响成本
co2StripperGtoL 引擎侧钳到 [1, 30] —— 旧式只有下界。UI 有 min/max 但可被载入方案 JSON 绕过, 填 1e6 得接触器排热 4.1×10⁹ kWh/d (有限但荒谬)
E. 🟡 鱼种默认气候联动
v2.0 修正了自检基线的鱼种-气候配对, 但 UI 默认仍是青岛且切鱼种不联动 —— 用户点「罗非鱼」拿到的就是被修掉的那个组合 (28°C 的鱼在 −7°C 过冬)。测试修好了, 产品还在发同一个陷阱。
新增 DEFAULT_CLIMATE_BY_SPECIES (与 SEED_PRICE_BY_SPECIES 同样板), 切鱼种时联动。实测比电耗: 罗非 16.21 → 13.48, 对虾 36.85 → 18.84, 鳜鱼 28.56 → 22.95。仅改默认起点, 用户仍可自由切换。
⚠ 剩余差距来自产品默认工况是设计冬而基线用年均 —— 定容工况与运行工况的正当区别, 不是配对问题。
F. 关于审计的两处不同判断
审计报「HVAC 潜热算出来但未计费, 海南夏 +0.42 元/kg」—— 经复算不成立。v2.0 已把 m_dehum_kgs 改为无条件计算的暖通除湿职责, dehumCostDaily 已进 totalMax/totalAvg; 海南夏实测潜热 5,117 kWh/d → 除湿电 2,989 kWh/d, 按 SMER 2.5 折算已计费。真正的问题是反向的 (见 D 项第二条: 开关无效却 gate 显示)。
G. 🟡 补齐三处漏项 (第二批)
在审计「仍然开放」表逐条实测复核后补上, 均为纯增项。
- 车间暖通机组 CAPEX (AHU-1002) —— v2.0 引入车间显热能耗、v2.1 又修准了口径, 却一直没有对应设备与投资 (实测: CAPEX 17 条里有热泵、有除湿机, 暖通 0 条)。与 v1.9.2 给除湿机补 CAPEX 前的状态同构。按设计冬/夏显热峰值选型, 合计 85.5 kW / 24.4 W/m²。⚠ 与热泵不重复: 热泵调水温, 本机组维持室温
- 制冷↔制热切换余量
regimeMarginPct —— 审计指出默认工况正坐在制度切换边界上 (单项 +0.24~0.36 而合计 +2.49, 严重非线性)。而 v2.1 的守恒修复恰好移动了这个边界 (青岛冬 hvacSens 3,355 → 769), 边界挪了提示更该有。实测 G:L=3 时余量仅 3.9%, 默认阶段 1 仅 0.5%。余量 < 15% 时在阶段卡片提示
- 蛋分供气风机 (K-501) —— 此前只有支路泵没有风机, 而泡沫分离的效率由气量决定 (没有气就没有泡沫)。新增气水比/风压/效率三参数, 成鱼期 6.41 kW / 508 m³/h
H. 🔴 登记完整性断言 —— 守恒断言抓不到的那一类
加蛋分风机时连犯三个同类错误, 现有断言一个都没抓到:
- 风机并进
totalPumpKw (废热错误地按 pumpHeatRatio 进水) —— 守恒断言 A 等式两边同步放大, 残差为零
- 漏加
elecCostAvgDaily —— 比电耗单向低估, 该口径是手工枚举、无断言
- 气流未登记
airPath —— 508 m³/h 的接触水气流不进接触器热湿交换; 风量平衡断言只校验已登记项之间是否自洽
第三条与 v2.0 的 Q_air_gain 孤儿项完全同类。结论:
守恒/平衡断言只能约束已进入体系的量, 无法发现压根没进体系的量。要抓后者, 必须从设备侧反向枚举。
新增 POWER_REGISTRY 登记表与两条断言 (并入逐阶段不变量):
- D 气流登记完整性 —— 凡带
airFlowM3h > 0 的 spec, 必须 (a) airPath 三字段齐全 (b) 出现在 collectAirStreams() 输出中 (c) spec.power 与同名标量一致
- E 电功率登记完整性 —— 凡
/Kw$|Power$/ 且 > 0 的字段必须在 POWER_REGISTRY 声明计费去向; 且 totalPumpKw 必须等于其声明成员之和
四条负对照全部精确转红 (用上面三个真实犯过的错 + 缺 airPath 构造)。其中「风机并进 totalPumpKw」正是 t20 守恒断言抓不到而登记断言 E 抓住的那条。⚠ 新增任何风机/泵/用电设备时必须同步 POWER_REGISTRY —— 那正是目的。
⚠ 本断言仍有一层盲区: 登记表本身可以声明错 (如 billedVia 指向不存在的成本字段)。堵它需校验该字段真实存在且非零 —— 未做。
J. 🟠 接触器净水汽计入水量平衡
v2.0 把接触器的热计入了水侧平衡, 但水的质量一直没进 V_makeup。本版补上, 并顺带修掉一处已存在但从未显形的盐度错误。
发现的两个问题
- 符号不对称 —— 凝结水减少补水 (已建模), 但蒸发不增加补水。补水与排污都等于
V_total × exchangeDaily, 注释写着「质量守恒」而蒸发不在其中。与 v2.0.1 修的 totalKw_field 同族: 同一物理量的两个方向只守一边
- 纯水扣错了股 —— 凝结水与除湿水是纯水, 却去扣减含盐的换水股。后果: 海水进得少、纯水补进来, 盐度会漂低 —— 一个已经存在但从未显形的错误 (盐度不是状态变量, 无处报警)
采用的简化: 拆两股水流
蒸发走掉的是纯水, 就补等量淡水, 独立成股, 不动换水份额:
换水股 补水 V_exch (同盐度) ←→ 排污 V_exch (同盐度)
蒸发股 补淡水 E ←→ 蒸发失纯水 E
体积平衡与盐平衡各自闭合, 盐度因此恒等守恒 —— 不需要盐度状态变量, 不需要「补水盐度」参数, 不需要补盐 OPEX。工程上这也是实际做法: 海水 RAS 的蒸发补水本就走独立的淡水补给管路。实测换水股与排水量逐阶段完全相等。
实测规模 —— 订正 HANDOFF v2.0.0 的记载
| 工况 | 换水股 | 淡水股 | 余纯水 | 净水汽 | 稳态盐度 |
| 青岛设计冬 | 461.2 | +29.6 | 0 | +29.6 | 30‰ |
| 青岛年均 | 461.2 | +11.7 | 0 | +12.5 | 30‰ |
| 海南设计夏 | 461.2 | 0 | 41.4 | −34.9 | 27.2‰ (−2.83) |
| 对虾三亚 | 126.8 | +4.6 | 0 | +5.7 | 15‰ |
⚠ HANDOFF v2.0.0 记的「584.6 kg/h, 占补水 6.0%, 单向系统性偏差」三点都不准确: 实测范围 −10.8% ~ +10.7%, 双向。湿热气候下净结露, 模型反而高估补水需求。字段因此命名 netWater 而非 evap —— 「热带养冷水鱼」正是 v2.0 新增的能力, 净结露是常态而非边角情形。
净结露时没有「补负淡水」这回事: 纯水先抵扣蒸发股, 抵到零仍有余量的转为排水, 并按现有假设推出稳态盐度 S × V_exch / (V_exch + V_surplus)。这不是新建模, 是把模型现有假设的推论说出来。
断言 F (三条)
- F1 水量闭合 —— 总补水 = 换水股 + 淡水股, 两股均 ≥ 0。拆股后这是恒等式, 转红即说明两股水流的分离被破坏
- F2 盐平衡 —— 无净结露时盐度不得漂移; 有净结露时稳态盐度须等于稀释式
- F3 热质同源 —— 由导出的
mdot / W_in / W_out 独立重推净水汽量并比对。若有人把它改成独立的经验蒸发系数, 立刻转红 —— 这是本方案的核心护栏
⚠ F3 初版写成「潜热占比须在 [50%, 105%]」, 用 Karimi 2020 的 91% 做区间 —— 判据错了。那 91% 是近等温工况(出口气温与进水温差 0.77°C) 的实测值; 青岛冬进气 −7°C、水温 12°C 时显热 19.1 kJ/kg、潜热 18.0, 占比 48.5% 是物理正确的, 却被区间判红。用文献单点值当断言区间, 必须先确认该值的适用工况。
K. 🔴 第五轮审计 — 风机热与登记表升级
K1. 🔴 三台风机的热此前没有任何去处 (P0)
模型对泵与 UV 做了完整的电-热分配 (进水 Q_pumps + 进空气 Q_air_gain = 电功率), 但三台风机 (脱气 53.6 + BF 42.4 + 蛋分 11.7 = 107.7 kW) 的电功率既不在这个分配里, 也不在任何其它热流里。代码注释写了「不走 pumpHeatRatio 进水」—— 这是对的, 但它也没走别的任何地方。
后果: 风机轴功全部变成气流温升, 而这三台吹的正是接触水的那三股气流, 接触器却用原始室外温度。方向与 v2.0 的 Q_air_gain 完全一致 —— 模型付电费让风机运转, 却让这些电产生的热凭空消失, 然后再付一次热泵的钱把水加热回来。
修法: 轴功 (η=0.57) 抬高接触器进气温度 (青岛设计冬 −7 → −3.5°C, 升温 3.3–3.5 K), 电机损失并入 Q_room_internal。⚠ 加热是等湿过程, 绝对含湿量不变, 只有温度抬高。
✅ 成本变化符号随工况方向翻转 —— 制冷工况上升 (海南夏 +0.42 / 三亚 +0.41), 制热工况下降 (Bergen −0.23 / 鳜鱼 −0.36)。与 v2.1 守恒修复时一致, 单向偏移反而说明修错方向。
K2. 登记表升级为唯一真相源
审计指出同一缺陷已在三个抽象层各出现一次: Q_air_gain (v2.0) → 蛋分气流漏登记 (v2.1 自查) → 风机热无去处 (本轮)。成因是计费去向、热去向、资本去向分散在三处不同机制里, 每处覆盖边界都是手工划的。
POWER_REGISTRY 每条现声明四项: path (支持嵌套) / billedVia / heatVia / capexVia。断言改为从登记表反向枚举 —— 旧断言 E 用 Object.keys(eq) 正向扫描 + 命名规则, 实测漏掉 co2StripperSpec.blowerKw (53.6 kW) 与 bfBlowerSpec.power (42.4 kW)。审计原话:
「为『漏了蛋分风机』而建的断言, 覆盖了蛋分风机, 却覆盖不了另外两台大 4–5 倍的风机。」
新增断言: A2 (全量电-热分配, 由登记表枚举 heatVia) 与 F4 (登记设备必须有 CAPEX 条目)。F4 落地后立刻抓到蛋分风机「有电费无投资」 —— 这是同一模式的第三次 (v1.9.3 加药计量泵 / v2.1 车间暖通机组 / 本项), 前两次只写了注释总结、没变成断言。
⚠ 实现过程中 A2 连报两次「已接入 0.00 kW」, 两次都是真错 (哨兵串被后续补丁注释污染导致静默跳过; const 块作用域导致变量在 return 处不可见)。若没有 A2, 第二个错会完全静默 —— 数值正常、无 NaN、守恒 B/C 照常通过, 风机热又一次凭空消失。
K3. 气候联动只覆盖点击路径
审计报「温水鱼种 × 青岛未修」—— 结论不成立 (字段真名是 DEFAULT_CLIMATE_BY_SPECIES, 点击鱼种实测罗非 13.83 / 对虾 19.27), 但他们给的数字恰好是没有联动时的值。查因: 联动只挂在鱼种按钮的 click 处理器里, loadStagesFromDB() 不调用 —— 载入方案 / 下游导入 / 测试脚手架全部绕过。审计撞到了真洞, 只是归因错了。
已移入 loadStagesFromDB()(所有切鱼种路径的必经之处),并加 skipClimate 开关 —— 载入用户保存的方案时不覆盖用户当初的显式选择。〔v2.2 订正: 该开关全库零调用点 —— restoreState 直接恢复 stagesState、不经 loadStagesFromDB, 开关从未被使用, 已于 v2.2 删除 (第六轮审计 P3)〕
📌 意外收获: 对虾基线 −3.58 元/kg (热泵费 2.77 → 1.90)。原因是基线只覆盖年均字段, 而设备选型用设计冬/夏 —— 三亚年均运行的对虾场一直按青岛设计冬选热泵。属选型工况与运行工况长期不一致的修正, 不是回归。
K4. 与审计的两处不同判断
「HVAC 潜热未计费」不成立 (第二次报同一条)。实测海南夏潜热 4,473 → 除湿电 2,613 kWh/d / 1,829 元/d。hvacLatentKWhPerDay 与 dehumKWhPerDay 是同一个 m_dehum 的两种表达, 再计一次就是重复。
「买了 92 万除湿机一天不开」表述有误。CAPEX 按设计夏峰值选型是正常工程做法 (夏季峰值选型、冬季不运行), 默认视图是设计冬所以显示 0 电费。经产品方确认, 除湿按独立机组处理: 潜热由独立除湿机承担、AHU 只管显热, 二者不重复。
K5. 收尾两项
- 蛋分风机 1+1 备用 —— 补
modulePerSpec 规格后, 三台风机的备用政策终于一致 (co2Blower / bfBlower / skimmerBlower 均 1+1)。与 v1.9.3 给脱气塔风机补规格时完全同一处境, 当时的原注释即适用:「此前没有模块化规格, 因而设备清单不显示备用、CAPEX 也不计备用。」
- v1.9.2 章节 G / I 两表加历史横幅 —— 数字 (37.53 / 比电耗 9.5) 是当时实测, 原样保留为历史记录; 但同章节末尾「请以本节为当前基线」这句在 v1.9.2 时是对的、现在已指错, 会让读者把 37.53 当成当前值, 已加订正批注。当前基线由
<meta description> 自基线自动生成。
⚠ 补丁顺序教训: 「meta 自基线生成」是构建期动作, 正确位置是链尾。第一次修复给了编号 48, 结果新增基线补丁 51 又排到它后面, 同一问题立刻重现 —— 现改为 99, 让「链尾」由编号本身保证, 而不是每次靠人记得。
I. 版本号与位号
- 版本号统一为 v2.1 (此前 v2.1.0)。本批并入 v2.1, 不另开 v2.2。v2.0.0 / v2.0.1 的历史叙述保持三段式原样
- 蛋分供气风机补位号 K-501 —— 蛋分区已有 C-501 (塔) 与 P-501 (支路泵), 同类的 K-101 (池增氧) / K-302 (BF 风机) 都有位号, 唯独新加的风机没有。P&ID 注册表内带
isBlower 标志, 供下游区分泵与风机符号
审计报「三处对外基线未跟上重算」—— 仅一处成立。<meta description> 确是当前对外事实, 已更新; 另两处 (行 1544「G. v1.9.2 成本基线」/ 行 1576「I. mainline vs bypass」) 都在 help-changelog-v192 章节内, 是 v1.9.2 变更日志的历史记录, 改它们等同于污染版本演进记录 —— 正是 v2.0.0 版本号全局替换踩过的坑。「常年需要制冷」那句按同一原则处理: 加 v2.0 订正批注, 不改原句。