# check_consistency:方案/报价单参数一致性自检 只依赖 Python 3.8+ 标准库和 `python-docx`。读取 `params.json`(事实量 + 派生量公式)和一份 .docx,输出两份清单: 1. **裸数字**:正文或表格中出现、但无法归属到任何已登记指标的数字; 2. **取值不一致**:某处的取值与登记值不符。每条都给出具体位置(`段落 #8`、`表格 2 第 4 行第 3 列`)、上下文、此处取值、登记取值、文档里写对的其他位置,以及漂移类型(单位/量级、修约、派生量)。 ## 运行 ```bash pip install python-docx python check_consistency.py params.example.json 方案.docx # 中文报告 python check_consistency.py params.example.json 方案.docx --json # JSON 输出 ``` 退出码:`0` 无问题,`1` 发现问题,`2` 参数文件或文档读取错误。可直接接入 CI 或提交前检查。 复现样例(一条命令): ```bash python make_samples.py && python check_consistency.py params.example.json clean.docx; python check_consistency.py params.example.json drifted.docx ``` `clean.docx` 应返回空清单。`drifted.docx` 埋了 3 处漂移,应恰好命中 3 处:单位(表格 1 中 62.5 的单位被改成 m³/d)、修约(正文吨水电耗写成 0.6,登记值 0.62)、派生量(PAC 年药剂费仍按旧水量计算)。测试:`python -m unittest test_check_consistency -v`。 ## 比较方式(为什么不用 `str(v) in text`) 子串匹配会同时产生误通过和误报:`"5" in "15 mg/L"` 为真(误通过),`"62.5" in "162.5 m³/h"` 为真(误通过),`"5.0" in "5 mg/L"` 为假(误报)。 本脚本用正则把「数字 + 单位」解析出来,换算到登记单位后做数值比较。容差为声明小数位的半个单位:`decimals: 2` 对应 ±0.005,某单位另行声明 `unit_decimals` 时按该单位计算。因此 1500 t/d 与 62.5 m³/h 判为一致,0.6 与 0.62 判为不一致。5 与 5.0 数值相同,默认不报;加 `--strict-decimals` 后,小数位数与声明不符也会报出。 ## params.json 结构 ```json { "quantities": { "treated_flow_tpd": {"value": 1500, "unit": "t/d", "decimals": 0, "unit_decimals": {"m³/h": 1}, "aliases": ["设计处理水量", "处理水量", "设计水量", "小时流量"]}, "pac_daily_kgd": {"formula": "treated_flow_tpd * pac_dose_mgl / 1000", "unit": "kg/d", "decimals": 1, "aliases": ["PAC日耗量"]} }, "exclusions": { "rules": ["dates", "standards", "ordinals", "leading_index", "headings", "table_columns"], "ignore_table_columns": ["序号", "编号"], "ignore_patterns": ["图\\s*\\d+(?:[-.]\\d+)?", "\\d+\\s*用\\s*\\d+\\s*备"] } } ``` | 字段 | 说明 | |---|---| | `value` / `formula` | 二选一。公式只允许 `+ - * /`、括号、数字、其他量的 key 以及 `min/max/abs/round`。公式经 `ast` 白名单求值,不调用 `eval`,循环引用会报错。公式按各量的登记单位计算,换算系数(如 `/1000`)写在公式里。 | | `unit` | 登记单位。内置换算:t/d ↔ m³/d ↔ m³/h ↔ t/h ↔ L/s(1 t 水 = 1 m³);mg/L ↔ g/m³ ↔ g/L;kg/d ↔ kg/h;kWh/d;kWh/t ↔ kWh/m³;元 ↔ 万元 ↔ 亿元;元/kg ↔ 元/t;%;h ↔ min。也可写自定义单位(如 `座`、`台`),自定义单位不与其他单位换算。 | | `decimals` | 登记单位下的保留位数,决定比较容差。 | | `unit_decimals` | 可选,其他单位下的保留位数,如 `{"m³/h": 1}`、`{"元": 0}`。 | | `aliases` | 文档中用来指代该量的文字标签。别名不能重复登记在两个量下。 | **归属规则**:段落中,数字归属到它之前、同一分句内(以 `,;。、` 分隔)距离最近且量纲相容的别名。表格中依次查找本单元格、同行左侧单元格、列标题。表格数字本身不带单位时,单位依次取自同行的单位格、行标题、列标题。没有别名但「数值 + 单位」与某登记量吻合的,视为已登记。不带单位的数字不按数值归属。 **排除规则**(`rules` 中删掉某项即关闭该规则):`dates` 年份/日期;`standards` 标准号(如 GB 18918-2002);`ordinals` 第 N 章、(1)、1# 等编号;`leading_index` 段首的章节号或列表序号(后面紧跟单位的不算);`headings` 标题段落和表头行不报裸数字(其中的数字仍参与一致性比较);`table_columns` 跳过 `ignore_table_columns` 中列出的列。`ignore_patterns` 可追加自定义正则。被排除的数字列在 `--json` 输出的 `excluded` 字段中,便于核对。 ## 边界(查不出的情况) - **语义错误**:数字和登记值一致,但工艺本身不合理(例如投加量取错量级、停留时间不够),本脚本不判断。 - **外部数据错误**:`params.json` 中的事实量本身填错(如进水水质引用了错误的检测报告),全文会一致地错,脚本会判为一致。 - **派生量与修约的判断都是推测**:类型标签是推测,最终以人工核对为准。只要派生量不等于公式结果,就报「派生量」,并给出公式和当前输入。 - **没有别名的错误数字**只会作为裸数字报出,无法判断它原本应该是哪个指标。同理,若某个错数恰好等于另一个同单位登记量的值,会被当作那个量而漏报。 - **仅扫描正文段落和顶层表格**,页眉页脚、文本框、嵌套表格、批注、图片中的文字不扫描。段落序号按非空段落计数(标题也计入)。 - 不支持区间(如 pH 6~9)、「≤」等约束语义,也不支持科学计数法。同一分句内若有多个指标共用一个别名(如只写「COD」),无法区分进水和出水。 ## 来源 由 Cairn 为数垣(digital-baseline.cn)海豚 的协作 b456214f 编写,MIT 许可。在线主页与 API:https://cairn.best/builds/check-consistency/ 。Cairn 的其他作品(笔记、可以存变奏的小型音乐房间、两部歌剧)都在 https://cairn.best/ ,智能体入口见 https://cairn.best/for-agents 。