采购合同审核 Skill
规则驱动、可插拔规则包的合同签约前风险审核引擎。AI 只出意见,采纳权在人;本工具不替代律师/法务最终判断,不构成法律意见,不直接改写合同正文。
何时使用
- 用户上传/粘贴一份采购合同(货物采购、软件与系统开发、服务外包、工程建设等),要求签约前风险自查或复核。
- 用户拿到供应商回签的改版合同,要求和我方原版逐条比对差异(走「版本对比」流程)。
- 用户要求维护/新增/修改审核规则,或对规则包做回归测试、自检。
- 用户要求把同一引擎换规则包复用到其他合同类型(劳动、租赁、保密、SaaS、营销合规等)。
不适用于:代写合同、直接代签、出具法律意见书。
立场约定(固定,不询问)
本 Skill 默认且始终站在买方(采购方)立场,不询问用户是买方还是卖方。理由:
- 买方合同量大、供应商杂、还要处理供应商回签的改动版本,审核是持续刚需;
- 卖方拿到合同签就是了,审核频次低,不是本工具的目标场景。
因此报告中统一显示「立场:买方(采购方)」,风险判定以买方利益为基准(如预付比例过高、账期过长、验收权旁落、管辖地不利、单方免责等,一律从买方视角定性)。
核心设计原则(不可妥协)
- 规则是数据,不是代码:引擎与规则包解耦。规则以 md/json 文本存在,每次审核前重载(热更新),改规则不碰引擎。
- 确定性归代码,语义归模型:字符级可判定的(失效法规、违禁表述、必备要素关键词、阈值验算)走脚本,结果确定、零漏报;需理解语义的走 LLM。
- 可溯源、防幻觉:每条问题必须给出【章节定位 + 原文摘录 + 命中规则 + 修改建议】;
quote必须能在合同原文中检索到,检索不到一律丢弃。原文无依据不输出。 - 未命中也透明:每条规则在报告中体现"已检查 / 未发现问题"。
- 人机协同:建议可接受 / 拒绝 / 编辑,采纳权在人。
- 规则可运营:规则包带版本号、生效日期、来源依据;改完能回归测试、能回滚。
资产结构
procurement-contract-review/
├── SKILL.md # 本文件:定位 + 编排流程 + 硬性不变量
├── package.json
├── references/
│ ├── LLM_SEMANTIC_PROMPT.md # 语义/要件规则的 LLM 评审提示词与 JSON 出参规范
│ ├── OPS_REPORT_SPEC.md # 命中项 Ops JSON 标准 + HTML 报告字段约定
│ ├── RULE_PACK_AUTHORING.md # 如何新增/修改规则、写回归测试、换规则包
│ └── LEGAL_BASIS_LIBRARY.md # 法条依据库说明(人读版)
├── scripts/ # 纯 Python 标准库(除 docx/pdf 可选解析外,见下)
│ ├── parse_contract.py # F1/F2 解析 docx/pdf/txt + 章节切块
│ ├── hard_check.py # F4 硬规则:hard_match / value_check,字符级零漏报
│ ├── money_check.py # F4b 金额核对:大小写一致/分项合计/含税换算/币种统一
│ ├── validate_rules.py # F9 规则包自检 + tests/ 回归测试
│ ├── render_report.py # F7 汇总去重 + 交互式 HTML 报告 + 修订稿导出
│ ├── diff_contract.py # F8 版本对比:原版 vs 供应商回签版
│ └── contract_review.py # 编排入口(parse → hard → money → [LLM] → render)
├── tests/
│ └── test_diff_regression.py # 版本对比的回归测试(配对/风险/排序/高亮)
├── rules-pack-企业采购合同/ # 首发规则包(可插拔)
│ ├── manifest.yaml # 包名/版本/生效日期/规则清单/来源/更新记录
│ ├── hard_terms.json # 硬规则词表:失效法规/违禁表述/必备要素/阈值
│ ├── legal_basis.json # 法条依据库:34 条法条 + 规则→依据映射
│ ├── rules/R-*.md # 一条规则一个文件(12 条)
│ └── tests/*.json # 回归测试集(正例应命中 / 反例不误报)
└── examples/ # 演示合同 + 生成的示例报告
执行流程(F1→F7 最小闭环)
> 所有脚本用托管 Python 运行:C:\Users\About\.workbuddy\binaries\python\envs\default\Scripts\python.exe(无该 venv 时用 ...\versions\3.13.12\python.exe)。脚本仅依赖标准库;.docx 解析优先用标准库读 zip 内 XML,无需第三方包。
第 0 步(每次审核前)· 规则包自检 + 热加载
- 运行 python scripts/validate_rules.py --pack <规则包目录> --lint 校验规则格式;缺字段时报错并定位到具体文件。
- 引擎每次审核都重新扫描规则目录(不过夜缓存),新增/改规则下一份合同即生效。规则状态 draft 仅提示、不计入正式问题(灰度用)。
第 1 步 · F1/F2 解析与切块
- python scripts/parse_contract.py <合同文件> --out contract.json
- 支持 .docx/.pdf/.txt;文件类型读内容首段判断,不靠后缀。按"第 X 章/条/节/附件"切分并保留章节路径与字符偏移;单块超阈值(默认 800 字)按行二次切分;保留章节编号用于定位。
- 两级标题策略:第X章/条/节 为一级标题,总是切分;一、/1./(一) 为二级标题,仅在合同未使用一级标题体例时才参与切分——否则条款内部的 1. 2. 款项列表会被误判为章节,导致该条正文被拆散甚至整条丢失。
- 每个切块携带 start/end 字符偏移,供命中项按位置精确定位章节。
- PDF 解析按 pypdf → pymupdf → 标准库正则兜底 的优先级尝试;扫描件/图片型 PDF 仍会失败,此时提示改用 .docx/.txt。
第 2 步 · F4 硬规则审核(代码,零漏报)
- python scripts/hard_check.py contract.json --pack <规则包目录> --out hard_hits.json
- 词表来自 hard_terms.json:失效法规(如援引已被《民法典》替代的原《合同法》等)、违禁表述、必备要素关键词缺失、阈值验算(账期/尾款比例/违约金上限)。字符级精确匹配,不调 LLM、可重复。隐性规则(失效法规)未命中不展示、命中置顶。
- 每条命中附带法条依据指引:由 legal_basis.json 的 rule_basis 映射到具体法条(含名称、条号、现行/已废止状态)。依据以折叠面板「⚖ 依据指引」呈现,并标注 current / repealed(援引已废止法条时会显式警示)。
第 2b 步 · F4b 金额核对(代码,独立模块)
- python scripts/money_check.py contract.json --out money_hits.json(编排入口已自动调用)
- 四类校验,全部纯代码、零 LLM:
- M1 大小写一致:中文大写(「肆拾伍万元整」)与阿拉伯数字(450,000)同小句配对,容差 1 元;
- M2 分项合计:分项金额之和 vs 合同总价,容差 max(10 元, 总价 × 1%);
- M3 含税换算:不含税金额 ×(1+税率)与含税金额比对;
- M4 币种统一:同一合同检出多种币种表述即提示。
- 误报防控:is_money_like 过滤掉百分比、条款序号(1. 2.);中文数字经 cn_to_num 解析;「不含税」不误读为「含税」。命中归入硬规则轨一并渲染。
第 3 步 · F5 语义/要件规则审核(LLM)
- 读取 rules/ 中判定方式为 rule_check / semantic 且状态 active 的规则,按 references/LLM_SEMANTIC_PROMPT.md 的提示词,逐规则(或按章节批量)让模型评审。
- 强制 JSON 出参:{rule_id, hit, severity, location, quote, problem, suggestion};低温、关思考保稳定。
- 防幻觉硬约束:模型返回的每个 quote 必须能在 contract.json 原文中检索到(去空白后子串匹配),检索不到则丢弃该条;超出规则范围的复杂情形输出 hit=false + need_human=true(建议人工复核),不强行判定。
- 单条规则失败/超时/JSON 异常 → 兜底重试一次,仍失败则该规则标记"评审失败-建议人工",不阻断整体。
第 4 步 · F6 汇总去重
- 合并硬规则 hits 与 LLM hits;同一定位(章节 + 近似 quote)的问题归并;硬规则置顶,其余按严重度(高>中>低)排序。
第 5 步 · F7 交互式 HTML 报告
- python scripts/render_report.py hard_hits.json llm_hits.json --pack <规则包目录> --contract contract.json --out 报告.html
- 顶部汇总:合同名、审核时间、立场:买方(采购方)、规则包名+版本+生效日期、命中数/未命中数/其中金额问题数;命中卡片含严重度、规则编码+名称、章节定位(原文高亮)、问题、可一键复制的修改建议、法条依据指引(折叠)、接受/拒绝复选框 + 可编辑;未命中规则折叠为"已检查 N 项未发现问题"。
- 报告为单文件、内联 CSS/JS、无外部依赖,可直接双击在浏览器打开。
第 6 步 · F7b 修订稿生成(勾选后导出)
- 报告工具栏含「📝 生成修订稿(仅已接受项)」按钮。用户在卡片上勾选"接受"后点击,前端 exportAccepted() 收集所有已接受卡片,生成 采购合同修订说明.md 并触发下载,内容含:定位 / 原条款 / 问题 / 修改为 / 法律依据。
- 设计意图:先给修改建议、由用户决定是否采纳;只有用户主动勾选并点击,才产出改好的合同说明。默认不改动、不生成,采纳权完全在人(不覆盖也不改写用户原始合同文件)。
- 导出前若一条都没勾选,提示「请先勾选要接受的建议」。
第 7 步 · F8 版本对比(供应商回签场景)
- python scripts/diff_contract.py 原版.docx 改版.docx --outdir <输出目录>
- 输出 合同版本对比报告.html(单文件交互式)与 diff.json。
- 条款级配对:对两版归一化标题序列求最长公共子序列(LCS)。匹配锚点之间的块按位置一一互配 → 识别为「修改」;仅在两侧数量不等时才落「删除」/「新增」。这样「连续多条标题被重命名」不会被错报成一堆删除+新增。
- 词级 diff:中文字符级、英文按词、数字整体切分,<del>/<ins> 精确到字(「甲→乙方所在地」也能标出单字差异)。
- 实质影响分排序:卡片按影响分降序,而非字符占比。影响分叠加三类信号——数字值变化(30%→80%、30→180 日、一年→三年)、主体/义务方翻转(甲方↔乙方、供方↔需方)、整句增减。≥2.0 打「⚠ 实质影响」标。避免「管辖权甲→乙」这类只差一字、占比 2% 却最要害的改动被埋到末尾。
- 高风险字段标注:六类(金额相关/付款条件/交付验收/违约责任/权属保密/争议解决)。结构件(签署栏、开头当事人段)一律不打标,避免噪声。
第 8 步 · 交付
- 用 present_files 打开 HTML 报告;同时可导出 markdown 审核意见(报告页内含"导出意见"按钮,或脚本 --format md)。
- 提醒用户:AI 意见仅供参考,采纳前请法务复核;命中失效法规等硬规则项建议优先处理。
规则包判定方式
| 方式 | 判定方 | 适用 | 漏报要求 |
|---|---|---|---|
hard_match |
代码字符匹配 | 援引失效法规、违禁表述 | 0 漏报 |
value_check |
代码/简单验算 | 比例、期限、金额逻辑异常(阈值在词表,可配) | 0 漏报 |
rule_check |
LLM 按明确要件核验 | 必备条款要素是否齐全、表述是否可执行 | ≤5% |
semantic |
LLM 语义理解 | 权责对等、归属清晰、约定有效 | ≤5% |
阈值类(尾款比例、违约金上限、账期天数)不写死在引擎里,放在 hard_terms.json 的 thresholds,企业按自身采购政策调整;默认值仅行业常见参考,不构成硬性法律标准。
硬性不变量(违反即返工)
- 任何命中项四要素不全(定位/原文摘录/问题/建议)或
quote在原文检索不到 → 不得进入报告。 - 硬规则不得走 LLM;语义规则不得用字符匹配假装判定。
- 规则包每次审核前必须重新加载;不得缓存旧规则。
- 报告必须包含"未命中规则"清单项(透明),不得只报命中。
- 不修改用户原始合同文件;所有产出写入新文件。
- 报告/导出件须带规则包名称 + 版本 + 生效日期水印,保证"当时按哪版规则审的"可追溯。
- 同类风险必须全量上报:硬规则扫描不得在首个命中处提前退出(禁止在匹配循环内
break)。 - 字符级匹配前须归一化:全角转半角、去空白、统一括号引号后再比对,不得因排版差异漏报。
- 必备要素判定须校验实质内容:仅出现"甲方/乙方"等称谓不构成"主体要素齐全"。
- 立场固定为买方:不得询问用户是买方还是卖方;所有风险定性以买方视角为基准。
- 改合同须先问后改:只输出修改建议,用户勾选「接受」并点「生成修订稿」后才导出改好的版本;不得擅自改写或覆盖用户原合同。
- 版本对比配对须容忍重命名:不得用「标题精确匹配 + 位置补位」的简单策略(会把连续重命名错报为删除+新增),须用 LCS 对齐。
- 变更排序按实质影响而非字符占比:数字/主体/整句变化权重高于字符改动比例。
- 金额核对不得误报:百分比、条款序号不得当作金额;「不含税」不得误读为「含税」。
修订日志
| 日期 | 版本 | 位置 | 内容 | 影响 |
|---|---|---|---|---|
| 2026-09-20 | 1.0.1 | scripts/parse_contract.py |
拆分为一级/二级标题两级策略;切块新增 start/end 字符偏移 |
修复条款内 1. 2. 被误判为章节导致正文丢失(示例合同「第二条 合同价款与支付」整条丢失);命中定位不再退化为「(相关条款)」 |
| 2026-09-20 | 1.0.1 | scripts/hard_check.py |
阈值/违禁/法规扫描移除循环内 break,改为全量扫描并按位置去重;付款上下文窗口 12→24 字、关键词扩至 13 个 |
修复多账期、多预付款、多违约金率只报首项的系统性漏报 |
| 2026-09-20 | 1.0.1 | scripts/hard_check.py |
实现 section_of(),改为按字符偏移定位章节 |
四要素中的「定位」恢复有效 |
| 2026-09-20 | 1.0.1 | scripts/hard_check.py + hard_terms.json |
必备要素新增 required_any 实质要件;主体要素要求出现住所/法定代表人/统一社会信用代码等 |
修复「只出现甲方乙方即视为主体齐全」的假阳性 |
| 2026-09-20 | 1.0.1 | scripts/rulekit.py |
新增 fold_text(全角转半角+去空白+统一括号引号)、find_folded、locate_offset |
硬匹配抗排版差异;跨行/全角/含空格法规名不再漏报 |
| 2026-09-20 | 1.0.1 | scripts/parse_contract.py |
extract_pdf 改为 pypdf → pymupdf → 正则兜底 |
中文 CID 字体 PDF 可解析(此前等同不支持 PDF) |
| 2026-09-20 | 1.0.1 | scripts/render_report.py |
dedupe_llm 改为浅拷贝后处理,不再写入入参 |
修复函数非幂等、污染调用方数据 |
| 2026-09-20 | 1.0.1 | scripts/validate_rules.py |
语义规则用例不再因 track:hard 被标记为「通过」;词表项做真实断言校验 |
消除回归「假全绿」:此前 7 项语义用例被错误计为通过 |
| 2026-09-20 | 1.0.1 | rules-pack-企业采购合同/tests/regression-fix-20260920.json |
新增 5 条回归用例 | 锁定本次修复,防止缺陷复发 |
| 2026-09-20 | 1.1.0 | rules-pack-企业采购合同/legal_basis.json + references/LEGAL_BASIS_LIBRARY.md |
新增法条依据库(34 条法条 + 15 条规则→依据映射);rulekit.py 新增 load_legal_basis/basis_for_rule;报告新增「⚖ 依据指引」折叠面板 |
每条命中可追溯到具体法条,标注现行/已废止 |
| 2026-09-20 | 1.1.0 | scripts/render_report.py |
报告 header 固定显示「立场:买方(采购方)」 | 立场固化,不再询问用户角色 |
| 2026-09-20 | 1.1.0 | scripts/money_check.py(新增)+ contract_review.py |
新增金额核对模块(M1 大小写/M2 分项合计/M3 含税换算/M4 币种统一),接入编排流程 [3/4] | 金额类错误由代码零漏报检出;7/7 用例通过、示例合同 0 误报 |
| 2026-09-20 | 1.1.0 | scripts/render_report.py |
新增「📝 生成修订稿(仅已接受项)」按钮与 exportAccepted() |
先给建议、用户勾选后才导出改好的合同说明,采纳权在人 |
| 2026-09-20 | 1.1.0 | scripts/diff_contract.py(新增)+ tests/test_diff_regression.py |
新增版本对比:LCS 条款配对、字符级词 diff、实质影响分排序、六类高风险字段标注、结构件免打标 | 供应商回签版可逐条比对;修复配对错位(新增2删除1 → 新增1删除0)、风险误报(9→6 处)、排序失真(要害改动前置)、中文高亮失效;18/18 回归用例通过 |
| 2026-09-20 | 2.0.0 | 全包 | 版本号升位(1.1.0 → 2.0.0),四处声明统一:SKILL.md / package.json / _meta.json / _skillhub_meta.json |
对外分发/打包版本标识统一;打包产物 procurement-contract-review-v2.0.zip |
复用与扩展
- 换场景 = 换规则包:复制
rules-pack-企业采购合同/为新包,改 manifest 与规则/词表/测试,引擎不动。详见references/RULE_PACK_AUTHORING.md。 - 新增规则后务必在
tests/加正例(应命中)与反例(不应误报),跑validate_rules.py --test回归。