# dsh-patent-reference-annotation **Repository Path**: hc905/dsh-patent-reference-annotation ## Basic Information - **Project Name**: dsh-patent-reference-annotation - **Description**: DSH 插件:在对比文件里标出公开了涉案专利哪些技术特征的段落(红色大括号 + 红字标签),输出标注版 PDF 并在末尾附「对应关系说明」 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-13 - **Last Updated**: 2026-10-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # dsh-patent-reference-annotation DSH 插件:把**对比文件的说明书里公开了涉案专利哪些技术特征**标出来——在段落左侧画正红大括号 + 红字标签,输出标注版 PDF,并在末尾附「对应关系说明」(四张表)。 它是 [`dsh-patent-claim-annotation`](../dsh-patent-claim-annotation) 的姊妹插件:那个回答"**涉案专利**的每条权利要求对应说明书的哪一段",这个回答"**对比文件**的哪一段公开了涉案专利的哪个技术特征"。 - 独立权利要求 → 按**拆分后的技术特征**标,且**特征号照抄意见陈述书的原文写法**:意见陈述书里权1 的特征写作「特征1-1…特征1-7」,所以标签写 `权1-特征1-1`;多个特征并列写 `权1-特征1-1,1-5,1-6,1-7`(每个特征都保留 `1-x` 形式,不做区间折叠) - 从属权利要求 → `权2-附加特征`;多条权利要求的附加特征同处 → `权2-4-附加特征`(权利要求号才折叠区间) - 同一段落公开多条权利要求的多个特征 → **合并为一条标注**:`权1-特征1-1,1-5,1-6,1-7;权2-4-附加特征` - 一条标注跨页 → 按页断开,后续页标签自动加「(续)」 - 老公告文本**没有 [00xx] 段号** → 用「页+段序」定位(`p10-2` = 第 10 页第 2 段,与请求书「D1 第10页第二段」同口径),拿不准就传原文片段 - 没有文字层 → 插件内 Tesseract OCR - 交付前自检:框外不许有任何改动(未改动页按**逐对象字节**判定、免渲染;有标注页逐像素) ## 安装 **方式一:npm(不需要本机安装 git)** DSH 桌面端 → 侧栏「插件」→「添加插件」,输入: ``` dsh-patent-reference-annotation ``` **方式二:从 Gitee 仓库安装**(需要本机已安装 git;地址**必须带结尾的 `.git`**) ``` https://gitee.com/hc905/dsh-patent-reference-annotation.git ``` > 不带 `.git` 的裸地址(`https://gitee.com/hc905/dsh-patent-reference-annotation`)会被 pnpm > 当成 tarball 去解包,报 `ERR_PNPM_TARBALL_EXTRACT`;实测 pnpm 11.7.0 必须带 `.git`。 **方式三:本地路径**(开发用) ``` link:<插件目录的绝对路径> ``` 装完**重启 DSH**。改代码后同样必须重启:host 进程按模块 URL 缓存已加载的插件,切换开关/重装都不会重新导入(`plugin_manager` 自己会报 `restart-required`)。 > 同一台机器不要同时装「npm 版」「git 版」「本地 link 版」——三者的挂载 id 都是 > `patent-reference-annotation`,会冲突。 ## 本地开发安装(本机现状) ```powershell # 已在本机桌面 profile 装好(link 安装 + bundle 登记): # C:\Users\hanch\.dsh\profiles\desktop\package.json # "dsh-patent-reference-annotation": "link:D:/Dgit/Dplugins/dsh-patent-reference-annotation" # dsh.profile.bundles 里加 "dsh-patent-reference-annotation" # C:\Users\hanch\.dsh\profiles\desktop\cordis.patch.yml # - id: patent-reference-annotation # disabled: false # C:\Users\hanch\.dsh\profiles\desktop\node_modules\dsh-patent-reference-annotation → 本目录(junction) # 依赖解析靠目录内 node_modules 下的**逐包 junction**(pdf-lib / pdfjs-dist / # @napi-rs/canvas / @pdf-lib/fontkit / @deepseek-ai/schemastery),指向 profile 的 # node_modules。不要把它换成"整个 node_modules 一条 junction":profile 里 # node_modules\dsh-patent-reference-annotation 又指回本目录,会形成无限递归, # 任何 -Recurse 扫描(备份、索引、杀毒)都会被绕进去。 ``` ## 卸载 ``` # 在 profile 目录执行(npm / git / 本地 link 都用这个包名) pnpm remove dsh-patent-reference-annotation # 再把 profile 的 dsh.profile.bundles 里的 dsh-patent-reference-annotation 删掉,然后重启 DSH ``` ## 交付物约定 **一次 `patent_reference_annotate` 调用只产出一个 PDF**: 1. **标注版 PDF** —— `<原文件名>对比文件标注版.pdf`。默认范围 `scope:"description"`(说明书全文:技术领域/背景技术/发明内容/附图说明/具体实施方式)——**对比文件的公开内容也常写在背景技术或发明内容里**(本案请求书就引了 D1「第5页第3段」)。权利要求书与**附图不落笔**(附图归 `dsh-patent-figure-annotation`)。 2. **「对应关系说明」直接渲染成新页附在这个 PDF 的最后**(`lib/pdfreport.js`)——不单独出 docx。说明页**只有标题 + 四张表格的小标题 + 表格**:标题固定为「本文件公开的技术特征对应关系说明」,**没有**"文字来源/页码口径""对照范围:…"这类说明性正文行。 说明页的四张表:逐条对应关系与依据 / 标注块-标签-页码 / 无对应与部分对应 / 未标注但可作旁证的段落。排版规则(写死,`patent_reference_verify` 会检查):每行统一最小行高(29pt)+ 行内不跨页,所以**依据要短(中文 ≈70 字以内)**;固定列宽、`特征` 与 `对应段落` 两列收窄;表头浅灰底纹;全篇字体优先宋体(`STSONG.TTF`)。 **成品要与别的 PDF 合成一本时**:说明页必须排在**整份文件的最后**。流程是「先合并、再用 `annotate:false` 追加说明页」——`annotate:false` 只追加说明、不重复画括号,自检改为证明原有各页未变(同样走逐对象字节判定,基本免渲染)。 > 本案 D1 的输入就是**已经做过附图标注**的 `D1-CN100416828C-附图标注版.pdf`:附图页与附图标记对照表原样保留,只在说明书页落笔,说明页排在整份文件(含对照表)的最后。 ## 七个工具 | 工具 | 作用 | |---|---| | `patent_reference_outline` | 读**对比文件** PDF:认页、说明书小标题、**段落块清单**(`p<页>-<段序>`、页码、页眉印刷页码、所属小节、正文)+ 权利要求原文 + **段号体检**(区间/缺号/首段号)。认页可被 `descriptionPages`/`claimsStart`/`headingWords` override(最优先);`expect:["p14-3=…"]` 逐条断言回归。段落总量 > 4 万字符时只给摘要。只读 | | `patent_reference_features` | 读**意见陈述书/无效请求书**(**.docx 或 .pdf**):独立权利要求的特征划分(特征1-1…1-7)、各从属权利要求的附加技术特征(**含表格**:「公开依据」列作 disclosedBy 权威源)、每条特征被哪些 D 主张公开(含证据句原文)。只读 | | `patent_reference_candidates` | 每个技术特征的候选段落块(**恒定 top-K、不按分数截断**,低分标 `low`)+ 逐字命中的片段。只读 | | `patent_reference_text` | 把**外部干净文本**与 PDF 段落块对齐,登记文字来源与版次(含 SHA-256)。只读 | | `patent_reference_annotate` | 落笔:红色大括号 + 红字标签 → 标注版 PDF;**同一次调用把「对应关系说明」附在这个 PDF 的最后**;预览默认给省 token 的标注联系表。支持 `planPath/planJson`(载入计划)、`textFrom`(复用已有文字层)、`reportDryRun`(行高预检)、`report.conventions`(口径说明);落盘 `.plan.json` + sha256,覆盖输出默认 `.bak-<时间戳>` 备份 | | `patent_reference_verify` | 自检:原件各页框外**零差异**(未改动页按逐页内容判定并免渲染,有标注页逐像素)、墨色、无白底、页边距;并核对说明页。`planPath`/`planSha256` 直读落盘计划(**不再重传 items/report**,排版参数与每处 box 与生成同源);`originalPath` 把证明链延伸到真正原件(跨文字层只比渲染);输出结构化 `failures`/每页 `diffPixels`+`box` | | `patent_reference_ocr` | 没有文字层时 OCR:**只走插件内 Tesseract**(chi_sim);返回**逐页渲染前后像素哈希**自证(`renderCheck`),支持 `outputDir`(按解析后实际路径校验权限) | 插件**不替你判断**哪一段公开了哪个特征:那要读原文、看上下文、认上下位概念,由模型判定;工具只给语料与线索,并把「落笔」「自证」做成可复核的两步。 ## 段落定位:为什么是「页+段序」 2006 年前后的中国公告文本**根本没有 [00xx] 段落号**(本案 D1 = CN100416828C,2004 年申请),而无效请求书引用这类对比文件时写的是「D1 第10页第二段」「D1 第8页倒数第2段」。所以段落块 id 直接对齐这个口径:`p<物理页>-<该页第几段>`,**段序含小标题、不含页眉页脚**。 已用本案 5 处引用逐字验证(`scripts/inspect.mjs --expect`): | 请求书引用 | 段落块 | 首句 | |---|---|---| | D1 第5页第3段 | `p5-3` | 本发明的一种形式是包含反射器、散热装置… | | D1 第6页倒数第3段 | `p6-5` | 实际上,LED部件20的结构组成取决于…图2 表示采用在基底23… | | D1 第8页倒数第2段 | `p8-2` | 图3 表示实现LED系统10(图1)的灯部件50 和遥控器41… | | D1 第10页第二段 | `p10-2` | 如前所述,天线66 从遥控器41 接收射频信号… | | D1 第10页倒数第二段 | `p10-6` | 驱动电路所需的电功率通过在常规的光插座中放置灯基座67 来传送… | 两个页码口径**同时给出**,别混:请求书里的「D1 第X页」= 该 PDF 的**物理页**;页眉上的「说明书第Y/8页」是**印刷页码**(`pages[].printLabel` / `paragraphs[].printLabel`)。 ### 译文没有页眉时(PCT / 外文专利中文译文的常态) 本案 D2 = WO2008106855A1 中文译文**整份文件没有页眉**(只有页面正中的页码),按页眉认页会全线失效(说明书 0 页、段落块 0 个)。这时插件走兜底: 1. **按内文小节标题定位起点**:技术领域/背景技术/发明内容/具体实施方式…所在页为说明书起点(D2 = p3); 2. **向后连续收正文页**:行数够、汉字占比 ≥50%、不带"国际检索报告/第X栏/对照表/指定国"等附件标记(D2 = p3–p8); 3. **页内在块级截断**:说明书正文遇到独立成行的「权利要求(书)」「国际检索报告」即止——PCT 译文常把权利要求紧接在具体实施方式之后、**甚至与说明书同页**(D2 的 p8:上半页是实施方式结尾,紧接着「权利要求」,其后是权利要求 1–35)。其后内容既不参与对照也不落笔; 4. 判定的页范围与截断位置**都写进 warnings**,必须人工核对(认页错了段落号全错);`pages[].role` 也会据此改写(D2 的 p9/p10 = claims,p11–p21 = other)。 D2 已用 4 处引用逐字验证:`p5-12`(第5页最后一段)、`p6-2`(第6页第二段)、`p6-5`(第6页第五段)、`p6-8`(第6页倒数第三段)。注意**译文文字层里没有空格**,断言串不要带空格。 ### 扫描件 + 繁体(中国台湾公告本):先 OCR,再按书眉/页脚认页 本案 D3 = TW569491B 是**没有文字层的扫描件**(38 页,只有附图标注页有字),而且是**繁体**。三步: 1. **OCR 只做"没有文字层的页"**:`patent_reference_ocr` 默认逐页挑(`keepTextPages:true`),已有文字层的页**原样保留矢量内容**——附图页与附图标记对照表上别的插件画的矢量红字标注不会被烤成图片(D3 实测:只识别 31 页,保留 7 页)。整份栅格化要显式传 `keepTextPages:false`。 2. **语言包按文字选**:繁体用 `chi_tra`(本机在 `D:\App\小奕\resources\app\external\ocr\traineddata`,插件会缓存到 LOCALAPPDATA 并补 `configs\`),简体用 `chi_sim`。38 页 300DPI 约 100 秒。 3. **繁体认页/切段**:插件内置「繁体→简体」的**关键词**折算(`normalizeKeyword`,只用于比对、**不改引用原文**),并认台湾公告本的用词: - 书眉「五、發明說明(3)」→ 按**页眉**剔除(不剔除的话每个说明书页都多出一段,段序整体比请求书多 1); - 小节标题 發明之領域/背景說明/發明概述/圖式說明/較佳實施例; - 权利人用词 申請專利範圍(=权利要求书); - 页脚的**印刷页码**「第 7 頁」(`pages[].printLabel`)。 **台湾公告本的页序可能倒装**:本案 D3 的第 7、8 页在扫描件里是**反的**(物理 p8 页脚印「第7頁」、物理 p7 页脚印「第8頁」,内容流 p6→p8→p7 才连贯)。请求书按**页脚页码**引用,所以「D3第7页第二段」落在本 PDF 的**第 8 页第 2 段**(`p8-2`)——落笔位置在页面上读起来与引用一致。这种情况**必须在说明表里写清页码口径**,别让合议组对不上页。 D3 的断言:`--expect "p8-2=對於一移動通訊裝置而言"`(该段即请求书引的「第7页第二段」,含「該天線及其電路板間之距離需大於…波長的八分之一大小」)。 ## 配置(`cordis.patch.yml`) ```yaml color: '#FF0000' # 标注色(正红) outputSuffix: '对比文件标注版' fontPath: '' # 留空=自动挑单体 TTF 中文字体(宋体优先:华文宋体→仿宋→黑体→等线→楷体) labelScale: 1.35 # 标签字号 = 正文字号 × 该倍数 labelMinSize: 5.5 labelSingleLineMinSize: 8 # 优先排一行,为此最多缩到这个字号 maxLabelLines: 4 # 对比文件一段常公开多个特征,标签长;4 行比 3 行能大一档字号 braceWidth: 0 # 0=自动(约 1.5 倍正文字号) braceStroke: 0 # 0=自动(约 0.22 倍正文字号) continuationSuffix: '(续)' scope: 'description' # 默认说明书全文 maxTextChars: 500 # outline 默认单条正文截断长度 fullTextCharLimit: 40000 # 段落块正文总量超过它,outline 只给"段落块+摘要" previewDpi: 150 ocrLang: chi_sim # OCR 只走插件内 Tesseract ocrDpi: 300 ocrConcurrency: 3 # OCR 渲染与 Tesseract 各同时跑几路(调大更快、更占内存) ocrVerifyDpi: 100 # OCR 前后渲染哈希自证(renderCheck)的比对分辨率 headingWords: [] # E1:追加的小节标题词(本案惯用词认不出时免改源码) tesseractPath: '' # 留空则按常见路径找 tessdataDir: '' # 留空则复用系统/语言包并缓存到 LOCALAPPDATA verifyDpi: 150 # 自检逐像素比对分辨率(只作用于有标注/可疑的页) verifyConcurrency: 4 # 自检逐像素页的并发渲染数 verifyDiffTolerance: 16 # 单通道像素差超过它才算"不同"(原 8) verifyInkTolerance: 72 # 成品像素与目标色的最大通道差,超过不算本插件的墨(原 60) verifyColorDelta: 24 # 各处主色与配置色的最大通道差(原 8) verifyMarginInkMax: 0.08 # 括号框内允许的原件墨迹占比(原 0.05) verifyRuleColumnRatio: 0.4 # 整页墨迹覆盖高度比例超过它判为框线/装订线列(原 0.5) ``` ## 性能与自检阈值(1.2.0 起) **慢在哪**:一次完整标注里,最贵的两处是 ①每次工具调用都重新认页/切段(扫描件 1.7 秒/次, 一次任务要调 outline→candidates→annotate→verify 各一遍);②`patent_reference_verify` 把 **原件 + 标注版的每一页**都渲染成 150DPI 位图再逐像素比(32 页 = 64 次渲染 ≈ 17 秒)。 **怎么快起来**(实测,样本 D3 = TW569491B 扫描件 32 页): | 阶段 | 1.0.1 | 1.2.0 | 靠什么 | |---|---|---|---| | 认页/切段(每次调用) | 1.7s | 0(首次 1.7s) | 按 `路径+mtime+大小+范围` 缓存切段结果(`lib/structure.js`) | | 落笔 `patent_reference_annotate` | 2.3s | 0.4s | 切段缓存 + 预览联系表按页并行渲染 | | 自检 `patent_reference_verify` | **18.9s** | **0.8s** | **未改动页按逐对象字节判定、免渲染**(见下)+ 有标注页两文档并行渲染 + Uint32 整字快判 | | OCR(6 页 300DPI) | 24.8s | 13.9s | 渲染与 Tesseract 各 3 路并行 | **自检的两条证明路径**(先字节、后像素,逐级退让): 1. **逐对象字节比对**(`lib/integrity.js`):落笔只**追加**对象,原件既有对象一个字节不改。 于是逐个对象比哈希——页面叶子对象没变 ⇒ 同一个渲染器必然渲染出同样的像素。这比逐像素 **更强**(逐像素会漏掉小于一个像元的位移,字节相等不会),而且**免渲染**,0.1 秒量级。 只有页树节点(Kids 追加了说明页)与文档信息字典允许变化;页树另按"前缀未变"核对页序。 2. **逐页渲染逐像素**(慢路径):**有标注的页**(顺带证明墨真的落了)、以及字节比对发现 **可疑**的页(有对象变了、页树对不上、pdf-lib 打不开)一律照旧渲染比对。 > 正确性优先:换了写出器、对象编号被重排时,逐对象比对会**自认不可用**并退回全量逐像素, > 只慢不假。负样本回归实测:未标注页被加内容、已标注页框外被加内容、计划与成品对不上、 > 页被删掉、落笔颜色配错——五种情况**全部被抓**。 **阈值为什么放宽**(默认值与理由,均可在 `cordis.patch.yml` 里覆盖): | 阈值 | 旧 → 新 | 理由 | |---|---|---| | `verifyMarginInkMax` | 5% → **8%** | 扫描件页边有框线/噪点/装订痕迹(D3 实测 5.5% 来自一条竖框线);而括号真压住正文时墨迹占比通常 >25%,仍抓得住 | | `verifyDiffTolerance` | 8 → **16** | 150DPI 抗锯齿抖动在 ±10 内,真改动(文字位移、换图)是 ±100 起步 | | `verifyInkTolerance` | 60 → **72** | 红字压在灰底/扫描噪点上会整体偏移;蓝色标注差 255 照样挡在外面 | | `verifyColorDelta` | 8 → **24** | 抗锯齿边缘把主色往白色拉;配错颜色(#FF00FF)差 255 必被抓 | | `verifyRuleColumnRatio` | 0.5 → **0.4** | 多认一些细框线与虚线;正文列(<20%)不会被误认 | 想更严就把这些键调回去,或把 `verifyDpi` 提到 200——有标注的页才付这个代价。 ## 踩过的坑(改代码前先看) 1. **老文本没有段号 ⇒ 段序全靠切段算**:切段一错,全部标注指错段落。切段规则=`[00xx]` 段号 > 小标题(独占一段,其后必开新段)> **首行缩进 >0.55 字宽** > 行距 >max(1.6×行距中位数, 0.8 字宽)。改完必须用 `--expect` 断言重跑那 5 处引用。 2. **视觉上的"段"与人数的"段"未必一致**:D1 第 8 页的「LED部件51布置在光反射区域53内…」在版面上**没有缩进**,与「图3表示实现…」同属一段——请求书说的"第8页倒数第2段"正是指这一段。切段结果要与请求书引用对齐后再落笔,别按语感硬切。 3. **页眉识别**:老文本的申请号**没有 CN 前缀**(`200480025822.X`),早先只认 `CN\d{6,}` 会把 `X` 剩下来、判不出页眉,页脚页码混进段落。 4. **扉页要先判**:扉页里有"说明书 6 页"这类页数说明,晚判就会被认成说明书页,把扉页也纳入落笔范围(D1 实测多出 16 个段落块)。扉页特征是著录项编号 `[19]/[51]…`、`国家知识产权局`。 5. **特征号不许折叠区间、也不许简写**:意见陈述书把特征写成「特征1-1…特征1-7」(权利要求号-特征号),所以标签是 `权1-特征1-1`。若把 `[1,2]` 折成 `1-2`,`权1-特征1-2` 会被读成另一个特征;若简写成 `权1-特征1,2`,又与原文的「特征1-2」对不上。**只有权利要求号折叠区间**(`权2,3,4-附加特征` → `权2-4-附加特征`)。 6. **同一段落块的多个特征必须写成一条标注**:写成多条会让同一个段落块重复落笔(括号完全重叠)并报重叠警告。标签会自动按权利要求分桶(`权1-特征1-1,1-5,1-6,1-7;权2-4-附加特征`)。 7. **版面盒必须按字号留边**:这个盒同时是自检掩膜的边界、同页避让判断和联系表裁剪范围。只按"原点+行高"算包络,150DPI 渲染的字形边缘与抗锯齿会溢出 1–2px(D1 实测第 6 页 53px 落在框外,自检报"框外差异")。现在按 `max(1.6, 0.22×字号)` 留边。 8. **标签折行有两条禁则 + 一条放宽**:行首不许出现 `-`(否则出现 `-附加特征` 这种像续行的行首);一个特征号/权利要求号区间(`1-5`、`2-4`)不可拆。但**允许在逗号处换行**——早先把整串 `1-1,1-5,1-6,1-7` 锁成不可拆,结果被这个 15 字的整体顶住、字号压到 6pt(D1 实测);现在每个号码都自带 `1-` 前缀,在逗号处断开也不会读错,字号回到 8pt 左右。 9. **左页边距只有 40–50pt**:标签自动缩到 6–9pt、最多 3 行是正常的。真放不下才出警告——那时应该**合并标签**而不是继续缩。 10. **`disclosedBy` 只是线索**:它是"同时含该特征指代与公开性措辞的句子里的 D 代号"的机械汇总,最终判定要读 `basis` 原文。请求人会明确写"某特征**不**由 D1 公开"(本案特征1-1 中的"天线布置在外包壳内"、特征1-3 的热沉材料),这类必须进 `gaps`。 11. **host 对输出 schema 严格校验**:未声明字段会让整次调用作废。`patent_reference_candidates` 的候选对象里混过 `label`、`features` 的输出里漏过 `key`/`feature`,都是 `scripts/sandbox-tools.ps1` 抓出来的——**改完工具代码务必跑一遍**。 12. **预览联系表的题注要传 fontPath**:不传就退回 `sans-serif`,中文题注全变方框(`scripts/annotate.mjs` 曾漏传)。 13. **字体不能乱挑**:`simsun.ttc`/`msyh.ttc` 是 TrueType 集合,pdf-lib 的 `embedFont` 吃不了;`simsunb.ttf` 名字像"宋体粗体",实际只有扩展 B 区。现在候选从黑体开始,落笔前逐个字符验字形,缺字直接报错并自动兜底换字体。 14. **目标文件被占用**:交付物常被 PDF 阅读器锁着,统一走 `writeFileChecked()` 报"正被其它程序占用"。 15. **Windows PowerShell 5.1 把无 BOM 的 UTF-8 `.ps1` 当 GBK 读**,本插件的 `.ps1` 一律纯 ASCII。 16. **junction 递归**:profile 的 `node_modules\<插件名>` 指回插件目录;插件目录下的 `node_modules` 必须是**逐包 junction**,不能整体指向 profile 的 `node_modules`。 17. **PCT 中文译文没有页眉、且权利要求与说明书同页**(D2 实测):只按"整页归属"认页会得到 0 段落块;只按内文标题收页又会把权利要求书也当成说明书标上(D2 的 p8-9)。所以认页要"内文标题 + 连续正文页"兜底,分节还要在**块级**再截断一次(见「段落定位」一节)。 18. **host 会缓存插件模块**:改了 `lib/` 里的代码,**工具(`patent_reference_*`)仍然跑旧代码**,只有 `scripts/*.mjs`(另起 node 进程)才是新代码。改完必须重启 DSH;拿不准就对比一次 `node scripts/inspect.mjs` 与 `patent_reference_outline` 的返回是否一致。 19. **OCR 的文字层是"逐字"的**:Tesseract 识别中文扫描件时常把每个字当独立词输出,文字层里是 `五 、發 明 說明 (1﹚`。关键词正则**必须先去掉所有空白**再比对,否则「发明说明」「权利要求书」一个都匹配不上(D3 实测:认页全线失效、说明书页被判成 other、段落块 0)。现在统一走 `normalizeKeyword()`(去空白 + 繁体折简体)。引用文本**不动**。 20. **扫描件的印刷框线会落进左页边距**:台湾公告本正文外面有一圈框线(D3 在 ~21pt 处有一条竖线),自检的"括号有没有压住正文"会把这条线算成墨迹 → 误报(D3 实测 5.5%)。现在按列统计、**覆盖整页高度 >40%(`verifyRuleColumnRatio`)的列判为框线列并排除**,而且**只统计标注盒覆盖到的列**(这些列是唯一用得上的地方)。检查详情里会写"已排除框线/装订线列 N 列"。真要落到框线上也不影响识读——它没压住任何一个字。 21. **两个连号不折成区间**:`权2,3-附加特征`(而不是 `权2-3-附加特征`)——两个号的"2-3"读起来像"权2"加"3-附加特征"。**连续 3 个以上**才折(`权2-4-附加特征`、`权5,12-14-附加特征`)。 22. **左页边距窄到 20pt 时**:标签会大到 11–12pt、每行 2 个字、4 行(中文可在任意处折行,读起来仍是连续的 `权2,3-附加特征`)。看着别扭但比缩到 6pt 更清楚;要更好看就把括号调窄(`braceWidth:13`,D3 用的就是这个)。 23. **段号会漂移,文字不会**:请求书声称按 [00xx] 引用,但同一个 [00xx] 在**不同译本/版次**里未必是同一段——D4 实测:这份 PDF 是专利权人原译文、请求书按《更正译文(修订稿)》引用,两本段号基本一致但个别处相差 1 段(请求书 [0163]=本 PDF 第28页 [0162]),措辞也不同(PDF 作「妨碍天线4收发无线电波」,请求书引作「妨碍天线4收发电波」)。**落笔要用文字定位**(`scripts/find.mjs` 或计划里的 `quotes`),并在说明页的表4 里写清段号口径。 24. **自检快路径靠的是"落笔只追加对象"**(1.2.0):`lib/integrity.js` 逐对象比哈希,页面叶子对象没变就免渲染。两个前提要看住——①**写出器必须保留对象编号**(pdf-lib 是;哪天换库、编号被重排,比对会自认不可用并退回全量逐像素,只会慢不会假);②**有标注的页永远走像素路径**,因为那是唯一能同时证明"墨真的落了"的路径。别图快把这条也跳过。 25. **切段结果是缓存共享的**(1.2.0):`buildStructure` 按 `路径+mtime+大小+范围` 缓存,同一份文件一次会话只切一次段。代价是返回的结构**只读**——在上面原地 `sort`/`push` 会污染别的调用方。 ## 开发脚本(`scripts/`) | 脚本 | 用途 | |---|---| | `inspect.mjs` | 打印结构轮廓与段落块:`node scripts/inspect.mjs <对比文件.pdf> [--page 3,4,5] [--chars N] [--expect "p5-3=文字"]`(`--expect` 是切段回归断言,不通过退出码 1) | | `find.mjs` | **按原文片段**找段落块(忽略空白):`node scripts/find.mjs <对比文件.pdf> "片段1" "片段2"`。请求书的段号在不同译本/版次之间会漂移(D4 实测),**用文字定位最稳**;命中唯一就打印 `[id]`,可直接写进标注计划 | | `check-json.mjs` | **元信息 JSON 自检**:插件目录下所有 `.json`(跳过 `node_modules`)逐个 `JSON.parse`,`node scripts/check-json.mjs [目录]`,不合法退出码 1;`selfcheck-tools.mjs`(因而 `sandbox-tools.ps1`)把它当必过项跑。**为什么要单列**:`locale/*.json` 里一个未转义的直引号就会让 DSH 加载插件时报「包元信息错误」,而工具 schema 与 render 当时全绿——本插件 `locale/zh.json` 真栽过一次(`…页面上没有"文字来源/对照范围"这类…`) | | `features.mjs` | 看从意见陈述书抽出了什么特征:`node scripts/features.mjs <意见陈述书.docx> [--patent <涉案专利.pdf>] [--json out.json]` | | `annotate.mjs` | 吃标注计划 JSON 落笔 → 标注版 PDF(末尾含「对应关系说明」页),可加 `--verify`、`--preview dir`、`--full-pages` | | `verify.mjs` | 对已产出的标注版 PDF 跑自检:`node scripts/verify.mjs --input <原件.pdf> --output <标注版.pdf> [--dpi 150]` | | `merge.mjs` | 按页搬页组新 PDF(合并/取页都行) | | `text.mjs` | 外部文本对齐:`node scripts/text.mjs <对比文件.pdf> <外部文本> [...]` | | `ocr.mjs` | `node scripts/ocr.mjs <无文字层.pdf> [--dpi 300]` | | `selfcheck-tools.mjs` | 工具层离线自检:真实跑 7 个工具的 execute,把返回值对 `output.schema` 逐字段验一遍,并跑 render | | `regression.mjs` | **回归测试**(1.3.0 起):合成夹具走工具入口验 E1–E19 关键修复 + 1.2.x 负样本五例,`node scripts/regression.mjs`,全绿退出码 0 | | `sandbox-tools.ps1` | 给上面那个脚本搭沙箱(复制插件 + 逐包 junction + harness 模块桩),顺带做作者侧 schema 静态检查 | ```powershell # 一键工具层自检 powershell -ExecutionPolicy Bypass -File scripts/sandbox-tools.ps1 ` -Pdf "<对比文件.pdf>" -Docx "<意见陈述书.docx>" ``` > DSH 对工具**输出**做严格校验(未声明字段会让整次调用作废),而 host 又会缓存插件模块, > 所以改完工具代码务必跑一遍 `scripts/sandbox-tools.ps1`——它能在不重启 host 的情况下 > 抓住 schema 不匹配。若 `.ps1` 被执行策略拦住,用 `powershell -ExecutionPolicy Bypass -File ...`。 ## 1.3.0 改进(认页/定位可靠性 + 计划与证明链) > 六件对比文件(26-125299CN D1–D7)实测:**六份里四份认页 0 段落块**、OCR 页眉致段序 +1、引文分歧硬失败、表格特征漏抽 9 条。1.3.0 逐条修掉: - **E1 认页鲁棒化**:`descriptionPages`/`claimsStart`/`figuresPages`/`printLabel`/`headingWords` override(**最优先**,免改 PDF 骗认页);标题剥序号前缀(「三、发明内容」照认);词表补美/日/欧译本惯用词且可配;认页作用域放宽到整页短行;块级复用页级权利要求正则 + **无标记权利要求书**(`1.…/根据权利要求` 连续条目)截断;无标题译本按**版式兜底**(连续正文页行数≥15+汉字占比)。 - **E2 页眉版式启发**:OCR 认不出关键词的页眉(「说明忆第N/M页」)按"页宽短行+第N/M页模式+相邻页同构+容 2–3 字 OCR 错"剔除,warnings 报"第N–M页页眉疑似未剔除,段序 +1";pN-1 残留另有体检。 - **E3 段落定位体检**:段号区间/缺号/首段号是否 [0001];段距判据改**双簇**(行距簇/段距簇)并放宽;整页并段报"本页仅切出 1 段";`outline` 增 `expect:["p14-3=…"]` 断言回显。 - **E4 quotes 引文分歧**:未命中给相似度 top-N 候选 + ⟨差异高亮⟩;`paragraphs`+`quotes` 同给=**段号优先、quotes 兜底**;命中回显首句 30 字。 - **E5 features 表格/PDF**:docx 表格「权利要求/技术特征/公开依据」列逐行抽 tails(**公开依据列作 disclosedBy 权威源**);支持 PDF 请求书;漏抽警告"疑似表格 N 张未解析"。 - **E6 candidates schema bug**:从属权 targets 不带 `feature` 不再整次作废,标签按「权9-附加特征」。 - **E7/E8 计划与自检同源**:`planPath`/`planJson` 载入计划;annotate 落盘 `.plan.json` + sha256(**实际排版参数 + 每处 box**);verify 直读落盘事实、`planSha256` 机械确认同一份计划、输出结构化 `failures`/每页 `diffPixels`+`box`。 - **E9 标签折行按语义 token**(`1-2`、`9,10`、`附加特征` 不拆);放不下自动外移右边距/页眉空白带(`placement` 回显)。 - **E10 证明链延伸**:verify `originalPath`(真正原件 vs 成品跨文字层只比渲染);ocr 返回逐页渲染前后像素哈希(`renderCheck`)。 - **E11 OCR 引用口径**(写进 SKILL 铁律):扫描件依据按**印刷原文**引用、OCR 差异入表4 注明、`report.ocrSource:true` 声明;优先 `patent_reference_text` 干净文本对齐通道。 - **E12–E19**:`reportDryRun` 行高预检(点名第几表第几行第几列);重叠给可执行合并建议(含粒度损失提示);`textFrom` 文字层复用(免整册重 OCR);`outputDir` 按解析后实际路径校验权限(报错回显"输出路径 X、工作区 Y");`plugin` 回显路径/版本/lib 加载时间(热改警告);覆盖输出默认 `.bak-<时间戳>` 备份;`report.conventions` 口径说明落点;SKILL 固化子代理编排四约束 + 崩溃收口。 - **N9–N11 定死口径**:不做热重载(只回显+警告重启/走 CLI);不自动合并段落块(只给候选建议);不自动改写引文(分歧只登记提示)。 回归:`node scripts/regression.mjs`(**55 项**,走工具入口/schema 校验路径;含 1.2.x 负样本五例 + annotate:false 正样本,全部继续抓得住/过得绿)。 ## 已知限制 - 页面 `/Rotate` 不为 0 时拒绝落笔(要强行试就传 `allowRotated: true`)。 - 左页边距很窄时标签会缩到 6–9pt;真放不下会自动外移右边距/页眉带(`placement` 回显)并出警告,不会静默画错。 - **段落块切分是启发式的**:版式异常(多栏、竖排、表格化)的老文本可能需要用 `quotes` 定位;整页并段会报"本页仅切出 1 段"。 - `patent_reference_features` 的 `disclosedBy`(非表格来源)是机械汇总,不是法律结论;表格「公开依据」列是请求人主张的权威记录。 - 标注是**追加**的内容流:原页面内容原样搬运,框外零差异已由 `patent_reference_verify` 证明(未改动页按逐页内容判定、有标注页逐像素)。 - 涉案专利 PDF 只用来登记**授权文本**的权利要求;无效程序中被修改过的权利要求以意见陈述书为准。 - **N9 不做热重载**:改 `lib/*.js` 后运行中的 MCP 仍跑旧代码——工具返回 `plugin.staleLibs` 非空时会警告"请重启会话或走 CLI",但不会在运行中替换模块。 - **N10 不自动合并段落块**:版面重叠只给可执行合并建议(含粒度损失提示)或自动错位/外移,合并由人决定。 - **N11 不自动改写引文**:请求书引文与译文/OCR 的分歧只登记与提示(候选+差异高亮+表4 注明),不"修正"证据引用。 - **扫描件引用口径(E11)**:依据按印刷原文引用、OCR 差异入表4 注明、`report.ocrSource:true` 声明;引文分歧同 N11。 ## 变更记录 见 [CHANGELOG.md](./CHANGELOG.md)。 ## 许可证 MIT(见 [LICENSE](./LICENSE))。