# Litigation Hub

> 诉讼信息中枢系统。接收法院短信、送达链接、纸质文书照片，自动 OCR 识别、下载、归档、归类到标准案卷目录。基于 12 种期限规则库自动匹配，通过系统日历 + QQ 邮件（微信送达）+ 本机电脑提醒（系统通知 + 桌面 Markdown 文件）三条线提醒，支持 macOS / Windows / Linux 全平台。

- Skill: `cslawyer1985/litigation-hub-2` (Agent Skill, multi-file: 21 files)
- Install (CLI): `npx skillmds@latest add cslawyer1985/litigation-hub-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cslawyer1985/litigation-hub-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: cslawyer1985 (https://skillmd.com/u/cslawyer1985)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cslawyer1985/litigation-hub-2

---


# litigation-hub · 诉讼信息中枢系统

> 💡 **默认模型：DeepSeek V4 Pro**。处理长工作流和浏览器交互时最稳定。
>
> 💰 日常 zxfw 短信（80% 场景）可切 **DeepSeek V Flash** 省钱——脚本承担了 90% 工作量，模型只做调度。非 zxfw 平台切回 V4 Pro。

## ⚡ Quick Start（模型执行指南）

**默认全自动，零停顿。** 收到用户输入后，一条命令推到底：

```
短信/链接 → python3 scripts/court_full_pipeline.py --sms "原文"
照片     → python3 scripts/court_full_pipeline.py --photo 照片路径
```

脚本自动完成全部 6 步：解析 → 下载 → PDF解析 → 归档 → **日历提醒** → **通知+桌面MD** → **QQ邮件**。模型只需呈报最终结果。

> 需要暂停确认时加 `--no-remind`，提醒单独跑 `--remind`。

## 功能概述

处理法院文书的完整流程：**粘贴短信 / 发链接 / 上传照片 → 解析内容（OCR 或文本）→ 匹配案件 → 下载/归档文书 → 开庭日历提醒（传票自动写入系统日历 + 本机电脑提醒 + QQ 邮件（微信送达））**。

## 首次使用：案卷目录设置（必须执行）

**当用户第一次使用本技能时，必须先确认案卷存放目录。**

> ⚠️ 如果没有事先确认目录，后续搜遍整台电脑都找不到案卷，效率极低。这一步一劳永逸。

### 判断是否首次使用

检查是否已有缓存的案卷目录记录：

1. 先读取 `config/case-root.json`，检查 `case_root` 字段是否为有效路径
2. 如果文件不存在或 `case_root` 为空 → **首次使用**，执行下方交互流程
3. 如果 `case_root` 已配置且路径存在 → 跳过，直接进入正式工作流

### 首次使用交互流程

**询问格式**：

```
您好！我是诉讼中枢系统。在开始处理法院文书之前，我需要确认一件事：

您平时把案卷资料存在哪个文件夹？

常见的例子供参考：
  · macOS   → /Users/你的名字/项目/律师业务/案件/
  · Windows → C:\Users\你的名字\Documents\律师业务\案件\
  · Linux   → /home/你的名字/项目/律师业务/案件/

💡 最简单的方式：打开你的案卷文件夹，把地址栏里的路径直接复制粘贴给我就行。
  · macOS：在访达里选中文件夹 → 按 ⌘+⌥+C (Cmd+Option+C) → 路径就复制好了，直接粘贴过来
        （或者：访达菜单 → 显示 → 显示路径栏，然后在底部路径栏上右键 → 拷贝路径）
  · Windows：在资源管理器打开文件夹 → 点一下顶部地址栏 → 按住 Shift 右键空白处 → 选"复制为路径"（或直接 Ctrl+Shift+C）
        （如果不行，就点地址栏后 Ctrl+C 复制）

请把路径发给我：
```

**用户回复后**：

1. 验证路径是否存在（`ls` / `dir`），如果不存在，创建之
2. 将路径写入 `config/case-root.json`：
   ```json
   {"case_root": "/Users/xxx/项目/02-律师业务", "configured_at": "YYYY-MM-DD HH:MM"}
   ```
3. 显示确认信息：
   ```
   ✅ 已记录案卷目录：/Users/xxx/项目/02-律师业务
   以后所有法院文书都会在这个目录下匹配和归档。
   ```
4. 随后继续执行正式工作流

### 后续使用

- 每次技能触发时，从 `config/case-root.json` 读取路径，**仅在该目录下搜索已有案卷**；
- **新建案卷始终放在桌面**，与 `case_root` 无关。桌面上的新案卷用户一眼就能看到，整理后自行移入搜索目录即可。
- 如果用户搬移了案卷目录，用户说"更新案卷目录"即可触发重新设置

---

## ⚡ 一键全链路模式（默认 · 全自动零停顿）

**最省积分的方式**：用户粘贴短信后，模型只跑一条命令，脚本自动完成全部工作——下载到归档到提醒，中间不暂停。

```bash
# zxfw 短信（最常见，全自动）
python3 scripts/court_full_pipeline.py --sms "法院短信原文..."

# 直接给链接
python3 scripts/court_full_pipeline.py --url "https://zxfw.court.gov.cn/..."

# 拍照 OCR
python3 scripts/court_full_pipeline.py --photo /path/to/传票.jpg

# 需要手动控制提醒时
python3 scripts/court_full_pipeline.py --no-remind --sms "..."   # 跳过提醒
python3 scripts/court_full_pipeline.py --remind '<JSON>'          # 单独创建提醒
```

**一次命令覆盖全部三条渠道**：系统日历 + 本机通知/桌面MD + QQ邮件——脚本内置，模型无需参与。

**非 zxfw 平台（gdems / 集约送达 / 湖北 / 司法送达网）**：脚本自动识别并在 `actions_needed` 中返回 `browser_download`。加载 `agent-browser` 技能用 Playwright 打开链接下载。

> **对比**：传统分步模式每步一个对话回合（5-8 回合），一键模式 **1 回合完成**。
> **真实效果**：积分降低约 80%，人手只需复制粘贴一条短信。
> **JSON 过长时**：用 `--remind-file /tmp/court_report.json` 替代 `--remind '<JSON>'`。

## 三种手动触发方式

以下三种方式供需要逐步交互控制的场景使用。

**方式一：粘贴短信原文**

```text
收到法院短信，内容如下：
【xx市人民法院】张三，您好！您有（2025）苏0981民初1234号案件文书送达，请点击链接查收：https://zxfw.court.gov.cn/zxfw/#/pagesAjkj/app/wssd/index?qdbh=DEMO1&sdbh=DEMO2&sdsin=DEMO3
```

**方式二：直接发送送达链接**

用户可能直接粘贴送达链接（非完整短信文本），此时跳过短信文本解析，直接从 URL 中提取 `qdbh`、`sdbh`、`sdsin` 参数，进入第三步下载流程。

```text
https://zxfw.court.gov.cn/zxfw/#/pagesAjkj/app/wssd/index?qdbh=xxx&sdbh=xxx&sdsin=xxx
```

**方式三：上传纸质文书照片 / 扫描件**

适用于刑事律师收到纸质传票、判决书、通知书的场景。上传照片（PNG/JPG），双级 OCR 自动降级提取文字，再解析为结构化字段。

```text
上传：传票照片.jpg
→ 双级 OCR（Tesseract → MinerU）→ 解析 → 复核 → 归档 + 日历提醒
```

> 双级 OCR 按需安装：Tesseract（`brew install tesseract tesseract-lang`）、MinerU（`npm install -g mineru-open-api`，自动安装）。

## 短信类型分类

| 类型 | 特征 | 含下载链接 | 处理方式 |
| --- | --- | --- | --- |
| 文书送达 | 含送达平台链接 + 案号 | 是 | 下载文书并归档到案件目录 |
| 立案通知 | 含"已立案"等关键词 | 可能有 | 展示解析结果 |
| 信息通知 | 无链接，纯信息 | 否 | 展示解析结果 |

**支持的送达平台**：`zxfw.court.gov.cn`（全国）、`sd.gdems.com`（广东）、`jysd.10102368.com`（集约送达）、`dzsd.hbfy.gov.cn`（湖北）、`sfpt.cdfy12368.gov.cn`（司法送达网）。同一平台可能使用不同域名（同构异域名），通过 URL 路径特征识别平台。详见 `references/sms-patterns.json`。

---

## 工作流

> ⚡ **省钱优先**：用户粘贴短信 / 链接 / 照片时，**优先使用一键全链路脚本** `court_full_pipeline.py`——一次命令完成解析→下载→PDF解析→归档→报告。仅在以下情况使用分步模式：①非 zxfw 平台需浏览器交互 ②用户明确要求分步控制。

### 一键模式（推荐）

用户粘贴短信后，直接运行：

```bash
python3 scripts/court_full_pipeline.py --sms "法院短信原文..."
```

脚本返回结构化 JSON。模型只需要：
1. 解析 JSON 结果，向用户展示报告
2. 如果 `actions_needed` 中有日历提醒需求，询问用户是否确认创建
3. 如果 `actions_needed` 中有浏览器下载需求，提示用户打开链接

### 分步模式（四步）

以下为需要逐步交互时的详细步骤。

### 第一步：输入解析

1. 读取 `references/sms-patterns.json` 作为解析参考
2. **判断输入类型**：
   - **完整短信**：包含法院签名（如 `【xx法院】`）+ 正文 + 链接 → 完整解析流程
   - **纯链接**：用户直接发送送达 URL（如 `https://zxfw.court.gov.cn/...?qdbh=xxx&sdbh=xxx&sdsin=xxx`）→ 跳过短信文本解析，直接从 URL 提取参数，进入第三步下载。案号、当事人等信息在下载文书后从文书内容中提取。
3. 对用户粘贴的短信文本进行分析（纯链接输入跳过此步）：

**a) 短信分类**：根据关键词判断类型
- 文书送达：包含 zxfw.court.gov.cn 链接
- 立案通知：包含"已立案"、"立案通知"等
- 信息通知：其他

**b) 案号提取**：使用正则 `[（(〔[]\d{4}[）)〕]]` 匹配标准案号格式

标准案号格式示例：
- `（2025）苏0981民初1234号`
- `(2024)粤0604执保5678号`
- `〔2025〕京0105民初901号`

**c) 当事人提取**：从短信文本初步识别，最终以文书内容为准
- **注意**：短信中的称呼（如"张三，您好"）仅为短信接收人，不作为案件当事人
- 公司名称：`xx有限责任公司`、`xx有限公司`、`xx股份有限公司`
- 诉讼对峙：`A与B`、`A诉B`、`原告A 被告B`
- 角色前缀：`原告：xxx`、`被告：xxx` 等
- 下载文书后，以起诉状、传票中的当事人信息为准，覆盖短信阶段的初步判断

**d) 下载链接提取**：识别短信中的送达平台链接并提取参数

| 平台 | 域名 | 下载方式 | 提取参数 |
|------|------|----------|----------|
| 全国法院统一送达平台 | `zxfw.court.gov.cn` | curl API 直连 | qdbh, sdbh, sdsin |
| 广东法院电子送达 | `sd.gdems.com` | 浏览器自动化 | 路径中的送达标识码 |
| 集约送达平台 | `jysd.10102368.com` | 浏览器自动化 | key |
| 湖北电子送达 | `dzsd.hbfy.gov.cn` | HTTP API（免账号）/ 浏览器自动化（账号模式） | 免账号：msg；账号模式：账号+密码从正文提取 |
| 司法送达网 | `sfpt.cdfy12368.gov.cn` | 纯 Playwright（无 API） | 验证码（手机尾号后6位 / 短信验证码） |

**e) 发送时间提取（P0）**：从送达平台 API 响应中提取发送时间，用于后续上诉期限计算
- **优先来源**：zxfw API 响应中的 `dt_cjsj` 字段（送达记录创建时间，ISO 8601 格式）
- 短信网关时间：部分手机短信会显示发送时间，匹配 `发送：YYYY-MM-DD HH:mm` 格式
- 如果无法提取送达时间，展示"送达时间待确认"，不阻塞后续流程
- 记录到归档 JSON 的 `document.sent_at` 字段

> **排除列表**：法院名称、法官姓名、地名、法律术语等不应作为当事人提取。详见 `sms-patterns.json` → `party_extraction.exclude_keywords`。

**输出格式**（向用户展示）：

```text
📋 短信解析结果：
- 类型：文书送达
- 案号：（2025）苏0981民初1234号
- 当事人：张三、xx有限公司
- 下载链接：已提取（zxfw.court.gov.cn）
```

#### 照片/扫描件输入（方式三）

当用户上传的是**纸质文书照片**（传票、判决书、通知书等），而非短信或链接时：

1. **双级 OCR 降级策略**：`scripts/court_photo_ocr.py` 按以下顺序自动尝试，每级结果经**质量门控**（综合中文字符数+占比+文本长度+案号+法院关键字——满分 100，≥50 分通过）判断，通过即停止降级：

   ```bash
   # 自动双级降级（默认）
   python3 scripts/court_photo_ocr.py /path/to/传票照片.jpg

   # 强制指定 OCR 引擎（调试用）
   python3 scripts/court_photo_ocr.py --ocr-tier tesseract /path/to/传票照片.jpg
   ```

   | 层级 | 引擎 | 方式 | 速度 | 精度 | 依赖 | 适用场景 |
   |------|------|------|------|------|------|----------|
   | Tier 1 | **Tesseract** | 本地 | ⚡ 快 | 中 | `brew install tesseract tesseract-lang` | 清晰打印文书，自动灰度+对比度增强预处理 |
   | Tier 2 | **MinerU** | 云端 | 🐢 中 | 高 | `npm install -g mineru-open-api`（自动安装） | 复杂排版、模糊照片、手写体混排 |

   **Tier 1 图片预处理**：Tesseract 处理前自动对图片做灰度化 + 1.5x 对比度增强 + 超大图缩放——零额外依赖，显著提升模糊/低对比度照片成功率。

   **质量门控**自动判断每级结果是否「可接受」：
   - 有中文字符（≥10 个，且占比 ≥20%）→ +30 分
   - 长度 > 50 字符 → +20 分
   - 长度 > 200 字符 → +20 分
   - 可提取案号 → +15 分
   - 命中法院文书关键字 → +15 分
   - ≥ 50 分 → 通过，停止降级
   - < 50 分 → 自动降级到 Tier 2

   > **设计理念（第一性原理）**：Tesseract 怕模糊但零网络依赖，MinerU 怕断网但对复杂排版好。双级覆盖两种失败路径，且每级都是轻量方案——不做 PaddleOCR 那种 500MB+ 的重武器下载。

2. **结构化解析**：提取的文字经正则匹配得到案号、案由、当事人、开庭时间/地点等，附带置信度标记

3. **后续流程**：识别到传票/出庭通知 → 跳过下载步骤 → 归档 + 日历提醒；识别到判决书 → 计算上诉期限
4. **自动归纳**：OCR 完成后按优先级匹配已有案卷，**但「归入已有案卷」必须经过律师确认，绝不自动写入**：
   - 🥇 **案号匹配**：桌面文件夹名含案号（如 `(2025)苏0411民初6206号`）
   - 🥈 **当事人姓名匹配**：桌面文件夹名含原告/被告姓名（如 `何玉-诉讼材料`）
   - 🥉 **新建案卷**：都没匹配到 → 在桌面自动创建 `{原告}诉{被告} {案由}/`（新建不污染既有案卷，故不询问），内含 **10 个标准子目录**。**当事人名称必须缩写至≤5字**：公司全称取简称（如"江苏海筑建设集团有限公司"→"海筑建设"、"淮安瑞悦房地产开发有限公司"→"淮安瑞悦"），自然人取姓名或简称
   - ⚠️ **匹配到已有案卷 → 暂停并请求律师确认**：展示匹配到的文件夹路径、命中依据（案号/姓名）、以及「同名同姓」风险提示，由律师在对话中确认后再归档；未经确认绝不写入。确认后通过 `--to-folder <路径>` 重新运行脚本完成归档。

   标准案卷目录结构（**新建案卷时用这些固定名称，不得自行编造**）：
   ```
   {原告}诉{被告} {案由}/
   ├── 01 法院送达文书/     ← 传票、判决书、裁定书、通知书等所有法院来源材料
   ├── 02 我方提交资料/     ← 我方向法院递交的材料
   ├── 03 对方提交资料/     ← 对方通过法院送达的材料
   ├── 04 案件原始材料/     ← 从当事人收到的原始材料
   ├── 05 律师工作文本/     ← 法律意见、庭审提纲等
   ├── 06 委托签署材料/     ← 委托合同、授权书等
   ├── 07 邮件收寄记录/     ← 邮件往来记录
   ├── 08 法规类案检索/     ← 法律法规、类案报告
   ├── 09 法院庭审笔录/     ← 庭审、听证笔录
   └── 10 案件保全资料/     ← 财产、证据保全文书
   ```
   - 法院文书（判决书/裁定书/传票/通知书等）归入 `01 法院送达文书/` 对应子目录（新建案卷时自动归入；匹配到已有案卷时，需律师确认后归入）
   - 照片原文件和 OCR 识别文本均保存到案卷下对应文书类型子目录

**输出格式**（向用户展示）：

```text
📷 照片识别结果：
- OCR引擎: Tesseract (轻量本地)  |  质量分: 75/100 ✅
- 类型：传票
- 案号：（2025）沪01刑初1234号
- 当事人：被告人张三
- 开庭时间：2026-05-07 09:30
- 地点：上海市第一中级人民法院 第5法庭
```

> 如双级 OCR 全部失败，返回错误并提示：①检查图片清晰度；②可考虑安装 PaddleOCR（约 500MB，中文法院文书精度最高，无需网络）作为第三级兜底。

### 第二步：确定归档目录

1. **扫描桌面**：识别目录结构，找到与短信案号或当事人匹配的案件目录
2. **查找归档子目录**：在匹配到的案件目录下，查找法院文书相关的子目录（如 `01*`、`法院送达`、`court` 等）
3. **匹配结果分两种情况**：
   - **未找到匹配案件** → **自动在桌面新建** `{原告}诉{被告} {案由}/`（新建不污染既有案卷，故不询问用户），随后直接归档
   - **找到匹配案件** → **不自动归档**，立即向律师展示：①匹配到的文件夹路径 ②命中依据（案号/姓名）③同名同姓误归风险提示，请律师确认后再归档。确认后带 `--to-folder <路径>` 重新运行脚本完成归档

#### 确认代理方（首次接触案件时）

> ⛔ **每个案件第一次处理时必须确认代理方，否则后续无法区分「我方提交资料」和「对方提交资料」。**

当新建案卷或首次匹配到已有案卷时：

1. **读取 `config/case-parties.json`**，检查该案号是否已记录代理方：
   - 已记录 → 直接使用，无需重复询问
   - 未记录 → 执行下方交互

2. **向用户提问**（从解析结果中提取已知当事人）：
   ```
   ⚖️ 案件 (2025)苏0981民初1234号，原告：张三，被告：xx有限公司。
   请问您是代理哪一方？
     → 原告 张三（我方）
     → 被告 xx有限公司（我方）
     → 暂时不确定
   ```

3. **记录答案**：写入 `config/case-parties.json`：
   ```json
   {
     "cases": {
       "（2025）苏0981民初1234号": {
         "represented_party": "原告",
         "party_name": "张三",
         "configured_at": "2026-07-13"
       }
     }
   }
   ```

4. **影响后续分类**：
   - 已知代理方 → 非法院文书归档时自动区分「02 我方提交资料」vs「03 对方提交资料」
   - 选择"暂时不确定" → 暂不区分，后续可通过"更新代理方 (2025)苏0981民初1234号 原告"手动补充

### 第三步：文书下载

> **平台判断**：根据第一步识别的链接域名，选择下载策略。
> - `zxfw.court.gov.cn` → 方案一（API 直连）→ 方案二 → 方案三
> - `sd.gdems.com` 或 `jysd.10102368.com` → 跳过方案一，直接方案二 → 方案三
> - `dzsd.hbfy.gov.cn` → 湖北专属流程（见下方）
> - `sfpt.cdfy12368.gov.cn`（含广西实例 `171.106.48.55:28083`）→ SFDW 专属流程（见下方）
> - 未知域名但 URL 路径匹配已知平台特征 → 按路径识别平台（同构异域名支持）
> - 完全无法识别 → 提示用户提供链接信息
>
> **⛔ 降级铁律**：严格串行，禁止并行。当前方案成功即停止，绝不降级。禁止"双保险"并行尝试多个方案。

#### 依赖

| 依赖 | 用途 | 适用方案 | 安装方式 |
|------|------|----------|----------|
| `curl` | API 下载 | 方案一 | macOS/Linux 预装 |
| `jq` | JSON 解析（可选） | 方案一 | `brew install jq` |
| Playwright | 浏览器自动化 | 方案二/三 | 见下方 |

**Playwright 安装指引**（仅方案二/三需要）：

```bash
# 方案二: Playwright CLI
npm install -g playwright
npx playwright install chromium

# 方案三: Playwright MCP（需在 WorkBuddy 设置中配置）
# 在 settings.json 的 mcpServers 中添加：
# "playwright": { "command": "npx", "args": ["@anthropic-ai/mcp-playwright"] }
```

> **⛔ 大多数情况下不需要 Playwright**：zxfw 平台方案一直接 curl 调用 API，无需浏览器。仅 gdems/jysd 平台或**方案一失败后**才需要方案二/三。禁止在方案一执行期间同时打开浏览器。

#### 方案一 — API 直连（优先）

完全无头，无需浏览器。直接调用 zxfw 后端 API 获取文书下载链接，再用 curl 下载 PDF。

**API 信息**：

- 端点：`POST https://zxfw.court.gov.cn/yzw/yzw-zxfw-sdfw/api/v1/sdfw/getWsListBySdbhNew`
- Content-Type：`application/json`
- 请求体：`{ "qdbh": "xxx", "sdbh": "xxx", "sdsin": "xxx" }`（从短信 URL 提取）
- 响应字段：`data[].c_wsmc`（文书名称）、`data[].wjlj`（OSS 签名下载链接）、`data[].c_fymc`（法院名称）
- 无需认证、无需浏览器

```bash
# 1. 从短信 URL 提取参数（示例）
qdbh="DEMO_qdbh_value"
sdbh="DEMO_sdbh_value"
sdsin="DEMO_sdsin_value"

# 2. 调用 API 获取文书列表
mkdir -p /tmp/court-sms-staging/
resp=$(curl -s -X POST "https://zxfw.court.gov.cn/yzw/yzw-zxfw-sdfw/api/v1/sdfw/getWsListBySdbhNew" \
  -H "Content-Type: application/json" \
  -d "{\"qdbh\":\"$qdbh\",\"sdbh\":\"$sdbh\",\"sdsin\":\"$sdsin\"}")

# 3. 解析文书列表，逐个下载 PDF
echo "$resp" | jq -r '.data[] | "\(.c_wsmc)\t\(.wjlj)"' | while IFS=$'\t' read -r name url; do
  curl -sL -o "/tmp/court-sms-staging/${name}.pdf" "$url"
done

# 4. 验证下载结果
ls -lh /tmp/court-sms-staging/*.pdf
```

#### 方案二 — 无头浏览器（Playwright CLI）

当方案一 API 不可用或链接过期时，用 Playwright CLI 无头模式打开页面，拦截网络请求获取下载链接。

```bash
# 需要先安装 playwright
npx playwright install chromium 2>/dev/null

# 无头模式运行（脚本需自行编写，拦截 getWsListBySdbhNew API 响应）
node scripts/download_court_docs.mjs --url "{短信链接}" --output /tmp/court-sms-staging/
```

#### 方案三 — 交互式浏览器（Playwright MCP）

当方案二不可用时（需要已配置 Playwright MCP）：

```text
1. browser_navigate → 打开短信中的 zxfw URL
2. 等待页面加载
3. browser_evaluate → 直接调用 fetch API 获取文书列表
4. browser_run_code → 下载 PDF 文件到 /tmp/court-sms-staging/
```

如 API 调用未成功，改用页面交互：

```text
1. browser_snapshot → 查看当前页面结构
2. 找到文书列表或 PDF 预览区域
3. 定位下载按钮（可能在 iframe 内）
4. browser_click → 点击下载
5. 等待下载完成，保存到临时目录
```

#### 湖北平台下载流程（`dzsd.hbfy.gov.cn`）

湖北电子送达平台有两种链路，根据 URL 格式自动选择：

**链路一：免账号模式**（URL 含 `/hb/msg=xxx`）

1. 从 URL 提取 `msg` 参数值
2. 尝试 HTTP API 直连：

```bash
msg="从URL提取的msg值"
mkdir -p /tmp/court-sms-staging/

# 查询文书信息
resp=$(curl -s -X POST "http://dzsd.hbfy.gov.cn/delimobile/tDeliSms/findSmsInfo?t=$(date +%s%3N)" \
  -H "Content-Type: application/json" \
  -H "Referer: http://dzsd.hbfy.gov.cn/deli-mobile-ui/" \
  -d "{\"msg\":\"$msg\"}")

# 检查是否需要验证码（data.isNeedCaptcha == "Y"）
# 如需验证码或无可下载文书，降级到 Playwright MCP

# 逐个下载文书
echo "$resp" | jq -r '.data.docList[] | "\(.docName)\t\(.downloadPath)"' | while IFS=$'\t' read -r name path; do
  if [ -n "$path" ]; then
    curl -sL -o "/tmp/court-sms-staging/${name}.pdf" "http://dzsd.hbfy.gov.cn/delimobile${path}"
  fi
done
```

3. 如需验证码或 HTTP 失败，降级到 Playwright MCP（方案三）

**链路二：账号模式**（URL 含 `/sfsddz`）

1. 从短信正文提取凭证：
   - 账号：匹配 `账号\s*(\d{15,20})`
   - 默认密码：匹配 `默认密码[：:]\s*([0-9A-Za-z]+)`
2. 需要浏览器自动化（Playwright MCP），登录页包含验证码
3. 登录后遍历待签收/已签收/已过期文书列表，逐个下载

> **提示**：湖北平台两种模式都可能遇到验证码。免账号模式优先尝试 HTTP API，账号模式建议引导用户手动打开链接或使用 Playwright MCP。

#### 司法送达网下载流程（SFDW - `sfpt.cdfy12368.gov.cn`）

司法送达网所有 POST 请求使用 TDHCryptoUtil 加密，无法通过 HTTP API 下载，只能使用纯 Playwright 流程。

**广西实例**：`171.106.48.55:28083` 域名下的链接路由到同一 SFDW 平台，下载流程相同。

**验证码获取**（两种方式，按优先级尝试）：

1. **手机尾号后6位**（优先）：从案件分配信息中获取律师手机号，取后6位作为验证码输入
2. **短信验证码**：从短信正文中提取，匹配 `验证码[：:]\s*(\w{4,6})`

**Playwright MCP 流程**：

```text
1. browser_navigate → 打开短信中的 SFDW 链接
2. 等待页面自动重定向到 pc.html?tdhParams=xxx
3. browser_snapshot → 查看验证码输入页面（input#checkCode）
4. 输入验证码（优先手机尾号后6位，其次短信验证码）
5. browser_evaluate → 调用 Vue app.checkYzm() 触发验证
6. 验证通过后 browser_evaluate → 获取 app.$data.wsList（文书列表）
7. 遍历 wsList，逐个调用 downloadFile(app, ws) 下载文书
8. 保存到 /tmp/court-sms-staging/
```

> **提示**：如手机尾号验证失败，提示用户查看短信中的验证码并手动输入。wsList 每项包含 wjmc（文件名）、wjgs（格式）。

#### 失败兜底

当三级均失败时：

```text
⚠️ 自动下载失败，请手动访问以下链接下载：
{原始链接}

下载后请将文件放到对应案件目录中。

我将为您创建待处理记录。
```

### 第四步：归档保存

> ⚠️ **归档铁律：匹配到已有案卷必须经律师确认，绝不静默写入。** 同名同姓在中国极为普遍，错误归档会污染他人案卷，因此「找到即归档」是禁止行为。

1. **确定目标目录**：
   - 扫描桌面，匹配与案号或当事人相关的案件目录
   - **未找到匹配案件** → **自动在桌面新建** `{原告}诉{被告} {案由}/`（安全，不询问），随后归档。**当事人名称缩写至≤5字**（公司取简称，自然人取姓名/简称）
   - **找到匹配案件** → **暂停，请求律师确认**：展示文件夹路径、命中依据、同名同姓风险提示；律师确认后带 `--to-folder <路径>` 重新运行脚本，再归档到该目录
   - 如目标目录不存在，自动创建
2. **获取当前日期**：`date "+%Y%m%d"`
3. **确定文书标题**：
   - 优先使用 API 返回的标题
   - 否则根据 `sms-patterns.json` 中的 `document_titles` 映射推断
   - 最后回退到原始文件名（去除扩展名），如仍无法确定则使用 `未知文书`
4. **构建文件名**：`{title}（{case_name}）_{YYYYMMDD}收.pdf`
   - 示例：`受理通知书（张三与李四合同纠纷）_20260404收.pdf`
   - 清理非法字符：`< > : " | ? * \ /`
   - 如同名文件已存在，追加 `_2` 后缀

### 核心规则：按送达批次打包（强制执行）

> ⛔ **所有法院送达材料一律用日期子文件夹打包，不得直接散放在 `01 法院送达文书/` 根目录。**
> 法院可能对同一案件分批发送不同文书，散放会导致批次混乱、无法区分送达时间。

**哪怕是只有一份传票，也放进子文件夹里。**

**文件夹命名规则**：`{命名文书}_{送达日期}送达/`

- `送达日期` 取 API 响应中 `dt_cjsj` 的日期部分（如 `20260119`），或文书落款日期
- `命名文书` 从本次送达的所有文书中选优先级最高的作为文件夹名

**命名优先级**（所有文书地位并列，以下仅用于选文件夹名）：

| 优先级 | 适用场景 | 文件夹名示例 |
|--------|---------|------------|
| 1 | 含判决书 | `一审法院判决书_20260318送达/` |
| 2 | 含裁定书（不含判决书） | `一审法院裁定书_20260401送达/` |
| 3 | 含传票（不含判决/裁定） | `传票_20260601送达/` |
| 4 | 含受理案件通知书（不含以上） | `受理案件通知书_20260119送达/` |
| 5 | 其他（举证通知、应诉通知等） | `举证通知书_20260520送达/` |

**简单理解**：一堆文书里谁最"重"就用谁命名。传票比通知书"重"，判决书比传票"重"——仅此而已，跟文书本身的法律效力无关。 |

**文件夹内文件保持 API 返回的原始文件名**，不重命名、不合并。

**归档路径示例**：
```
海筑建设诉淮安瑞悦/01 法院送达文书/受理案件通知书_20260119送达/
├── 受理案件通知书.pdf
├── 交纳诉讼费用通知书.pdf
├── 民事诉讼权利义务告知书.pdf
└── ...

孙东诉武进建工/01 法院送达文书/传票_20260601送达/
└── 传票.pdf
```

5. **移入目标目录**：将子文件夹整体移入 `01 法院送达文书/`
6. **写入内部记录**：保存本次处理的完整信息到**本技能目录下的 `archive/`**（即 `~/.workbuddy/skills/litigation-hub/archive/`）。格式详见 [`references/archive-format.md`](references/archive-format.md)
7. **自动归纳到桌面案卷**（短信/链接方式同样适用）：
   - 扫描桌面是否已有该案号的文件夹
   - 没有 → 在桌面自动创建 `{原告}诉{被告} {案由}/`（如 `海筑建设诉淮安瑞悦 建设工程施工合同纠纷/`），再放入。**当事人名称缩写至≤5字**
   - 有 → **不自动放入**：展示匹配结果 + 同名同姓风险提示，请律师确认；确认后带 `--to-folder <路径>` 重新运行脚本放入
   - SMS 多文件按文件夹打包规则归纳（如 `一审法院判决书/` 子目录）
8. **基础文书解析**：法院 PDF 通常带文字层，提取首页文本，快速识别文书类型和关键信息
   - **传票**：提取开庭时间、地点、法庭、案号，向用户高亮提醒
   - **通知书/告知书**：提取缴费期限、举证期限等关键日期
   - **起诉状/答辩状**：提取案由、当事人、诉讼请求概要
   - **判决书**：识别为一审判决书，记录文书类型，触发上诉期限计算（P1）
   - **裁定书**：识别裁定类型。**⛔ 如正文含「冻结」「查封」「扣押」任一关键字，必须在后续步骤中强制执行 point 9（期限规则匹配），不得因开庭日期已过、案件状态待定等任何理由跳过。**
   - **其他文书**：展示文书标题和法院名称
   - 如一次下载多份文书，逐一解析，汇总为一份报告

   > 深度分析（如判决书解读、合同审查）不在此技能范围内，请使用专用分析技能处理。

9. **期限规则匹配（P0）**：当识别到任何有期限要求的文书时，自动查表 `references/deadline-rules.json` 匹配对应规则

   规则库覆盖 **12 种文书类型**，包括：

   | 分类 | 文书类型 | 期限 | 提醒策略 |
   |------|---------|------|---------|
   | 📄 上诉 | 民事/行政判决书 | 15日 | 2天前 + 截止当天 |
   | 📄 上诉 | 刑事判决书 | 10日 | 2天前 + 截止当天 |
   | 📑 上诉 | 民事/行政裁定书 | 10日 | 2天前 + 截止当天 |
   | 📑 上诉 | 刑事裁定书 | 5日 | 2天前 + 截止当天 |
| ❄️ 保全 | 冻结银行存款裁定 | 1年 | ⚠️ **提前30天** + 14天 + 7天 + **到期当天** |
| 🏠 保全 | 查封不动产裁定 | 3年 | ⚠️ **提前30天** + 14天 + 7天 + **到期当天** |
   | 📝 举证 | 举证通知书 | ≥15日 | 5天前 + 1天前 |
   | 📬 答辩 | 起诉状副本 | 15日 | 3天前 + 截止当天 |
   | ⚡ 执行 | 生效法律文书 | 2年 | 30天前 + 7天前 |
   | 🔄 再审 | 生效判决/裁定 | 6个月 | 30天前 + 7天前 |
   | 🏛️ 管辖 | 管辖权异议裁定 | 10日 | 2天前 + 截止当天 |
   | 📋 缴费 | 受理通知书 | 7日 | 3天前 + 1天前 |

   > **保全类必须提前处理**：冻结/查封的续期申请不能等到最后一两天，规则库已按 30天 提前提醒。
   > 新增文书类型只需在 `deadline-rules.json` 的 `rules` 数组中追加一条 JSON 条目，无需改代码。

   - **匹配逻辑**：根据文书类型 + 关键字标签（如"冻结""银行存款"）精确匹配规则
   - **提醒生成**：调用 `scripts/court_deadline_reminder.py` 统一设置 日历 + 本机电脑提醒（系统通知 + 桌面 Markdown 文件）+ QQ 邮件（微信送达）
   - **⚠️ 提醒确认（P0 — 必须执行）**：在创建任何提醒之前，必须先展示提醒计划并等待用户确认
     ```
     📋 即将创建以下提醒：
     案号: (2025)苏0411民初6206号
     规则: 民事判决书 → 15日上诉期
     
     · 2026-07-13 09:00 — 上诉期限还剩2天 (日历+通知+邮件)
     · 2026-07-15 09:00 — 上诉期限今天截止 (日历+通知+邮件)
     
     👉 回复「确认」创建提醒，或指出需要修改的项。
     ```
     - 用户确认前，不得调用任何提醒创建脚本
     - 用户可以修改提醒时间（如改为提前 5 天）
     - 用户可以添加自定义额外提醒
   - **自动提醒设置**：识别到判决书/裁定书后，自动为上诉截止日期设置**三重提醒**：
     - 📅 日历事件（macOS Apple Calendar / Windows Outlook / .ics 文件）
    - 🖥️ 本机电脑提醒（系统通知弹窗 + 桌面 Markdown 提醒文件，每次提醒同步生成）
    - 📧 QQ 邮件（微信送达）（每次提醒同步发送）
     - 使用脚本：`python3 scripts/court_deadline_reminder.py setup '<案号>' '<案由>' '<文书类型>' '<标签>' '<送达日期>' '<法院>'`
     - 规则库：`references/deadline-rules.json`（覆盖 12 种文书类型，新增类型只需加 JSON 条目）

   - **🔴 保全裁定强制处理（P0 — 不可跳过）**：当文书类型为「裁定书」且内容包含保全关键字（冻结、查封、扣押）时，**无论其他因素如何（如开庭日期已过、案件状态待定等），都必须执行以下完整流程**：

     1. **提取保全详情**：从裁定书正文中提取以下字段，逐项展示给用户确认：
        - 保全金额（如"145万元"）
        - 保全方式（冻结银行存款 / 查封不动产 / 扣押动产）
        - 被申请人名称
        - 裁定日期
        - 审判员姓名

     2. **计算到期日**：
        | 保全方式 | 期限 | 依据 |
        |---------|------|------|
        | 冻结银行存款 | 1年（365日） | 《民事保全须知》第3条 |
        | 查封、扣押动产 | 2年（730日） | 《民事保全须知》第3条 |
        | 查封不动产、冻结其他财产权 | 3年（1095日） | 《民事保全须知》第3条 |
        - 到期日 = 裁定日期 + 期限 - 1日

     3. **创建全部提醒（逐条核对，不得遗漏）**：根据 `deadline-rules.json` 中匹配到的规则，**逐条创建所有 `reminders` 数组中的提醒**。以 `preservation_freeze`（冻结银行存款）为例：
        ```
        reminders: [
          提前30天 (critical) → 日历 + 通知 + 邮件
          提前14天 (warning)  → 日历 + 通知 + 邮件
          提前7天  (warning)  → 日历 + 通知 + 邮件
        ]
        ```
        - ⛔ **禁止部分创建**：规则里写了几个 `reminders`，就必须全部创建。不允许只创建30天和到期日、漏掉14天和7天。
        - 到期日当天额外创建一条「🔴 保全到期」事件作为最后兜底。

     4. **汇总确认**：创建完成后展示完整提醒时间线，让用户一目了然：
        ```
        📋 (2026)苏0281民初9011号 保全提醒时间线：
        裁定日期：2026-05-27 → 到期日：2027-05-26（1年）
        
        · 2027-04-26 — ⛔ 提前30天：请立即申请续冻！
        · 2027-05-12 — ⚠️ 提前14天：冻结将在14天后到期
        · 2027-05-19 — ⚠️ 提前7天：冻结将在7天后到期
        · 2027-05-26 — 🔴 保全到期日
        ```

10. **向用户汇报**：按 [`references/report-format.md`](references/report-format.md) 输出结构化报告
   - 先确认归档完成（案号、法院、当事人、案由、文件数、位置）
   - 列出所有已归档的文书清单
   - 如含传票，⚠️ 高亮提醒开庭时间、地点、审理程序
   - 如含判决书，⏰ 展示上诉期限信息
   - 如含传票/出庭通知/应诉通知书，自动进入**第五步**创建日历提醒，在汇报末尾追加日历创建结果
   - 如部分失败，列出失败文书和原始链接

11. **归档确认（P0 — 必须执行）**：向用户展示归档结果，等待确认后才能进入提醒步骤
    - 输出归档清单：`文件 → {案卷路径}/{子目录}/文件名`
    - 展示重复检测结果（如有）
    - **阻止后续操作**：在用户回复"确认"之前，**不得创建日历事件、系统通知、或发送邮件**
    - 用户可修改归档位置或报错

12. **❄️ 重复文件检测**：归档前检查目标目录是否已有同名或大小相似的文件
    - 文件名匹配：同名文件 → 提示"已存在，是否覆盖？"
    - 文件大小匹配：大小差异 < 5% → 提示"可能存在重复，是否跳过？"
    - 用户选择：跳过 / 覆盖 / 保留两个文件

13. **📋 未处理文件追踪**：OCR 失败或无法归类的文件，记入 `references/pending-items.json` 待处理清单
    - 每次 skill 启动时检查待处理清单，向用户汇报还有多少未处理文件
    - 用户可指定文件归类位置，归类后自动从清单移除

14. **📂 非法院文书智能归类**：对非法院发布的文件（代理词、委托书、合同、证据等），根据文件名和内容关键词自动归类到对应子目录
    - `02 我方提交资料`: 起诉状、答辩状、代理词、证据清单（**仅当文件涉及代理方时才归入此目录**）
    - `03 对方提交资料`: 对方起诉状、对方答辩状、对方代理词（**仅当文件涉及对方当事人时才归入此目录**）
    - `04 案件原始材料`: 合同、协议、借条、银行流水
    - `06 委托签署材料`: 委托代理合同、授权委托书
    - 等等。详见 `scripts/court_utils.py` → `FILE_CATEGORY_MAP`
    - **代理方感知**：归类前先读取 `config/case-parties.json`，已知代理方时，涉及代理方的诉讼文书（起诉状、答辩状、代理词等）优先归入 `02 我方提交资料`，涉及对方当事人的归入 `03 对方提交资料`。未确认代理方时，不确定归属的文书先暂存桌面，待确认后再归类。

15. **📅 法定节假日感知**：期限截止日如落在周末或法定假期，自动顺延至最近工作日
    - 数据源：`references/china-holidays.json`（来自国务院办公厅通知）
    - 每年 1 月 1 日后首次调用时，主动提醒检查当年节假日数据是否已更新
    - 如期限内包含长假（如春节、国庆），向用户提示"实际可用工作日可能不足"

### 第五步：开庭日历提醒（macOS / Windows）

> **触发条件**：第四步 PDF 解析识别出**传票 / 出庭通知 / 应诉通知书**时自动执行。
> **自动判断**：无需用户操作，下载 PDF 后自动扫描文书标题，符合条件即触发。
> **重复安全**：写入前自动删除同一案号的旧事件和邮件提醒，不会重复创建。

下载 PDF 并解析出开庭信息后，自动完成三项工作（根据系统平台自动选择实现）：
1. **系统日历事件**（macOS Apple Calendar / Windows Outlook "工作"分组）
2. **QQ 邮件（微信送达）通知**（跨平台，发到 YOUR_QQ_EMAIL@qq.com）
3. **本机电脑提醒**（系统通知中心弹窗 + 桌面 Markdown 提醒文件，双保险）

**本机电脑提醒**由两部分组成，确保律师一定能看到开庭提示：
- **系统通知**：macOS 提前1天通过 launchd 弹系统通知中心；Windows 通过 schtasks 弹 MessageBox
- **桌面 Markdown 提醒文件**：同时在桌面生成 `开庭提醒_YYYY-MM-DD_HHMM.md`，写明开庭日期 / 时间 / 案由 / 案号 / 地点及**需准备材料清单**（传票、证据原件、委托手续等）。即使系统通知被勿扰屏蔽或一时没注意，回到桌面也能看到——这是本机提醒的持久化兜底，不依赖系统通知是否真的弹出。

| 平台 | 日历 | 定时提醒 | 本机电脑提醒 |
|------|------|---------|------------|
| macOS | Apple Calendar (AppleScript) | launchd plist | 系统通知 + 桌面 Markdown 文件 |
| Windows | Outlook COM → 失败则 **.ics 文件兜底** | schtasks | MessageBox 弹窗 + 桌面 Markdown 文件 |
| Linux | .ics 日历文件（双击导入） | ❌ | 桌面 Markdown 文件 |

```text
📅 日历事件已创建：
  - 摘要：⚖️ 开庭：{案由}
  - 时间：YYYY年MM月DD日 HH:MM — HH:MM+2h
  - 地点：{开庭地点}
  - PDF链接：已关联（点开事件可查看文书）
  - 日历分组：工作
  - 系统提醒：提前1天通知
📄 桌面提醒文件已生成：/Users/你/Desktop/开庭提醒_YYYY-MM-DD_HHMM.md
📧 QQ 邮件（微信送达）提醒已设置（提前1天发送）

#### 依赖

| 依赖 | 用途 | 安装 |
|------|------|------|
| `pypdf` | PDF 文本提取 | `pip install pypdf` |
| macOS: `osascript` / `launchctl` | 日历 + 系统通知 | 系统预装 |
| Windows: `PowerShell` / `schtasks` | Outlook 日历 + 弹窗通知 | 系统预装 |
| `tesseract` + `chi_sim` | 纸质文书 OCR Tier 1（轻量本地） | `brew install tesseract tesseract-lang` |
| `mineru-open-api` | 纸质文书 OCR Tier 2（云端高精度） | 自动安装 `npm install -g mineru-open-api` |
| `node` | 通过 smtp.js 发送 QQ 邮件（微信送达） | 系统已装 |
| `imap-smtp-email` skill | QQ 邮箱 SMTP 配置 | 已安装 |

#### 操作步骤

**① 从 PDF 中提取开庭信息**

使用脚本 `scripts/court_calendar.py` 的 `parse` 模式：

```bash
# 提取开庭信息（案号、案由、开庭时间、地点）
python3 scripts/court_calendar.py parse /tmp/court-sms-staging/传票.pdf
```

正则匹配规则：

| 字段 | 正则 | 示例 |
|------|------|------|
| 案号 | `（(\d{4})[^）]*\d+号` | （2025）苏0981民初1234号 |
| 案由 | `案　?由[：:]\s*(.+)` | 民间借贷纠纷 |
| 开庭时间 | `应到时间[：:]\s*YYYY年M月D日...HH:MM` 或通用日期时间 | 2026年05月07日 09:30 |
| 地点 | `应到处所[：:]\s*(.+)` 或 `开庭地点[：:]\s*(.+)` | xx市人民法院 第3法庭 |

**② 判断是否需要创建日历**

判定规则：文书标题或类型含以下任一关键词即触发：
- 传票
- 出庭通知
- 开庭
- 应诉通知书

**③ 删除旧事件（去重）**

同一案号可能多次收到文书更新，写入前自动删除该案号的旧日历事件：

```bash
python3 scripts/court_calendar.py '<案号>' '<案由>' '<YYYY-MM-DD HH:MM>' '<地点>'
```

脚本内部自动调用 `delete_court_events()` 去重。

**④ 创建日历事件 + 系统提醒 + QQ 邮件（微信送达）提醒**

```bash
python3 scripts/court_calendar.py '<案号>' '<案由>' '<YYYY-MM-DD HH:MM>' '<地点>' \
  '工作' '<PDF远程链接（如有）>'
```

这步同时完成：
- AppleScript → Apple Calendar "工作" 日历（无弹窗）
- launchd plist → 提前1天系统通知中心提醒
- **桌面 Markdown 文件 → 提前1天生成《开庭提醒_YYYY-MM-DD_HHMM.md》（开庭时间、地点、需准备材料清单），落到桌面**
- **库内脚本 → 提前1天 QQ 邮件（微信送达）通知（到 YOUR_QQ_EMAIL@qq.com）**

#### QQ 邮件（微信送达）内容

开庭前1天在同一时刻自动发送，内容示例：

```
━━━━━━━━━━━━━━━━━━
⚖️ 开庭提醒
━━━━━━━━━━━━━━━━━━

案号：（2025）苏0981民初1234号
案由：民间借贷纠纷
时间：2026年5月7日 09:30（周四）
地点：xx市人民法院 第3法庭

━━━━━━━━━━━━━━━━━━
此邮件由 court-sms 自动发送，请以传票原件为准。
```

#### AppleScript 事件内容

| 字段 | 内容 |
|------|------|
| summary | ⚖️ 开庭：{案由} |
| description | 案号 - 案由 - 开庭地点（纯文本，不含链接） |
| url | PDF 远程访问链接（如无则不传） |
| location | 开庭地点 |
| start | 开庭时间（2小时时长） |
| calendar | 工作 |

#### 日程冲突处理

如果已有同案号日历事件（如之前手动创建或系统自动创建），脚本的 `delete_court_events()` 会先清理再重建，保证不重复。

#### 异常处理

| 状况 | 处理 |
|------|------|
| pypdf 未安装 | 提示安装，跳过日历创建 |
| PDF 无法解析（扫描件） | 跳过自动提取，展示"请手动查看 PDF 确认开庭时间" |
| 提取到案号但无开庭时间 | 只创建事件（摘要以案号命名），不设提醒时间，不发送 QQ 邮件（微信送达） |
| AppleScript 失败 | 展示错误信息，不阻塞后续流程 |
| launchd 加载失败 | 仅提示，日历事件已完成创建 |
| launchd 邮件提醒加载失败 | 仍完成日历创建，展示警告并提示检查 launchd 日志 |
| QQ 邮件（微信送达）发送失败 | 不影响日历和系统通知，检查 `~/.court-email/*-err.log` |

#### 用户侧汇报格式

日历创建成功后，在文书归档汇报末尾追加：

```text
📅 开庭日历提醒：
  - ⚖️ 开庭：{案由}
  - 🕙 {开庭时间}
  - 📍 {开庭地点}
  - 🖥️ 本机电脑提醒：系统通知 + 桌面《开庭提醒_*.md》（含需准备材料清单）
  - 📧 已设置提前1天 QQ 邮件（微信送达）通知（YOUR_QQ_EMAIL@qq.com）
```

---

### 第六步：PDF 后处理（可选）

> **不默认启用**。仅在检测到文件拆分时主动提示用户。

归档完成后，扫描目标目录中的 PDF 文件，检测是否有同一文书被拆分为多个文件的情况。

#### 读取用户偏好

读取 `config/user-preferences.json` 获取用户的合并和重命名偏好。如文件不存在，使用默认值（参考 `config/user-preferences.example.json`）。

关键偏好项：

| 偏好 | 默认值 | 说明 |
|------|--------|------|
| `merge_strategy` | `per_evidence` | 合并策略：`per_evidence`（按编号分别合并）或 `unified`（统一合并） |
| `merge_options.unified.bookmarks.enabled` | `true` | 统一合并时是否添加 PDF 书签 |
| `rename.enabled` | `true` | 是否精简文件名 |

#### 触发检测

读取 `references/sms-patterns.json` → `post_processing.trigger` 配置，按以下规则分组：

```text
分组规则：
1. 证据类：文件名以"证据"开头 → 按编号分组（证据1、证据2、证据3…）
2. 其他文书：按文书类型分组（传票、起诉状、应诉通知书…）
3. 如任一组内文件数 > 3（threshold），触发提示
```

**示例**：证据3 下有 10 个 PDF → 触发。

#### 用户确认

使用 AskUserQuestion 提示用户，列出检测到的拆分情况：

```text
检测到以下文书被拆分为多个 PDF：
- 证据3：10 个文件
- 证据5：4 个文件

是否执行 PDF 后处理（合并 + 重命名）？
  → 是，合并所有
  → 让我选择（逐个确认）
  → 跳过
```

#### 执行后处理

用户确认后，根据 `user-preferences.json` 中的 `merge_strategy` 执行：

**策略一：per_evidence（默认）**

按单个证据编号分别合并，每个证据独立保留：

```text
- 证据3 有 10 个拆分文件 → 合并为「证据3：打印截图.pdf」
- 证据5 有 4 个拆分文件 → 合并为「证据5：电脑截图.pdf」
- 未被拆分的证据（如证据1 只有 1 个文件）保持不动
```

**策略二：unified**

将证据目录 + 所有证据合并为一个「原告证据.pdf」，并添加 PDF 书签：

```text
合并顺序：证据目录 → 证据1 → 证据2 → … → 证据N
书签格式：
  📑 证据目录
  📑 证据1：仲裁申请书、不予受理通知书
  📑 证据2：劳动合同、保密协议
  📑 证据3：被告工资表
  📑 证据4：泄露账号密码的电脑截图
  📑 证据5：打印及拷贝资料的电脑截图
  📑 证据6：删除电脑操作痕迹的截图
```

书签名称使用简洁版：证据编号 + 冒号 + 证据标题（去除当事人和日期后缀）。使用 pypdf 的 `add_outline_item` 添加书签。

> 用户可随时修改 `user-preferences.json` 切换策略，无需改动 skill 本身。

#### 页面尺寸标准化

合并过程中同时标准化页面尺寸为 A4（210×297mm）。使用 pypdf 逐页处理：

```python
from pypdf import PdfReader, PdfWriter, Transformation

A4_W = 595.27  # 210mm in points
A4_H = 841.89  # 297mm in points

for page in reader.pages:
    pw, ph = float(page.mediabox.width), float(page.mediabox.height)
    is_landscape = pw > ph

    # 保持原始方向：纵向→A4纵向，横向→A4横向
    target_w = A4_H if is_landscape else A4_W
    target_h = A4_W if is_landscape else A4_H

    # 等比缩放并居中
    scale = min(target_w / pw, target_h / ph)
    offset_x = (target_w - pw * scale) / 2
    offset_y = (target_h - ph * scale) / 2

    new_page = writer.add_blank_page(width=target_w, height=target_h)
    page.add_transformation(Transformation().scale(scale).translate(offset_x, offset_y))
    new_page.merge_page(page)
```

**规则**：
- 纵向页面 → A4 纵向（210×297mm）
- 横向页面 → A4 横向（297×210mm），不强制旋转为纵向
- 等比缩放、居中放置，不裁剪、不拉伸

#### 精简文件名

根据 `user-preferences.json` → `rename` 配置对所有文件统一重命名：

```text
去除规则（strip_patterns）：
- 去掉括号内的当事人信息：（张三与李四合同纠纷）
- 去掉日期后缀：_20260405收
- 去掉平台标记：（合并）、（自贸法庭）、（素）-

特殊映射（special_mappings）：
- 起诉状（素）… → 起诉状（要素式）.pdf
- 开庭传票 → 传票.pdf
```

**重命名示例**：

| 原始 | 重命名后 |
|------|----------|
| `传票（张三与李四合同纠纷）_20260405收.pdf` | `传票.pdf` |
| `起诉状（合并）.pdf` | `起诉状.pdf` |
| `起诉状（素）-要素式起诉状（合并）.pdf` | `起诉状（要素式）.pdf` |
| `应诉通知书（自贸法庭）.pdf` | `应诉通知书.pdf` |
| `E法桥平台使用告知书（xxx）_20260405收.pdf` | `E法桥平台使用告知书.pdf` |

---

## 内部归档格式

每次处理完成后在 `archive/` 下创建 JSON 记录，格式详见 [`references/archive-format.md`](references/archive-format.md)。

> ⚠️ **发布前清空**：`archive/` 目录在运行时自动写入真实案件记录，对外发布前必须清空所有 `.json` 文件（保留 `.gitkeep` 和目录结构），确保案件数据不外泄。

---

## 常见法院短信格式参考

### 文书送达短信

```text
【xx市人民法院】张三，您好！您有（2025）苏0981民初1234号案件文书送达，
请点击链接查收：
https://zxfw.court.gov.cn/zxfw/#/pagesAjkj/app/wssd/index?qdbh=DEMO1&sdbh=DEMO2&sdsin=DEMO3
如非本人操作请联系法院。
```

### 立案通知短信

```text
【xx市xx区人民法院】您好，您提交的立案材料已审核通过。
案号：（2025）京0105民初54321号
请及时缴纳诉讼费用。
```

### 开庭提醒短信

```text
【xx市xx区人民法院】提醒：您有（2025）苏0508民初567号案件，
定于2025年3月15日上午9:30在第3法庭开庭，请准时到庭。
```

### 湖北电子送达短信（免账号）

```text
【xx人民法院】您有案件文书待查收，请点击链接查收：
http://dzsd.hbfy.gov.cn/hb/msg=XXXXXXX
如有疑问请联系法院。
```

### 湖北电子送达短信（账号模式）

```text
【xx人民法院】您有（2025）鄂xxxx民初xxxx号案件文书送达。
账号 420xxxxxxxxxxxxx
默认密码：xxxxxx
请登录 http://dzsd.hbfy.gov.cn/sfsddz 查收。
```

### 司法送达网短信

```text
【xx人民法院】您有（2025）川xxxx民初xxxx号案件文书送达。
验证码：A1B2C3
请点击链接查收：https://sfpt.cdfy12368.gov.cn/sfsdw//r/xxxxxxxxxxxx
```

---

## 故障排除

| 问题 | 解决方案 |
| --- | --- |
| 短信无法识别类型 | 展示原文，请用户确认类型后继续 |
| 案号提取失败 | 手动输入案号 |
| 当事人识别不准 | 提示用户确认/修正当事人列表 |
| 无匹配案件 | 自动在桌面新建 `{原告}诉{被告} {案由}/`（当事人缩写≤5字，不询问） |
| 找到已有案卷 | **不自动归档**：展示匹配路径 + 命中依据 + 同名同姓风险，请律师确认后带 `--to-folder <路径>` 重新运行脚本归档 |
| Playwright 下载超时 | 检查网络连接，尝试刷新页面重试 |
| 页面需要验证码 | 通知用户，暂停等待手动处理 |
| 下载文件损坏 | 清理临时目录，重新尝试下载 |
| 目标目录不存在 | 自动创建对应目录 |
| SFDW 验证码验证失败 | 尝试手机尾号后6位和短信验证码两种方式，均失败时提示用户联系法院 |
| 日历创建失败 | 检查 pypdf 是否安装，检查 AppleScript 是否有 Calendar 权限（系统偏好设置→隐私与安全性→自动化） |
| 日历事件重复 | 已内置去重（写入前自动删除同案号旧事件），如仍有重复请检查 launchd 是否有残留 plist：`ls ~/Library/LaunchAgents/com.mm.court-*` |
| QQ 邮件（微信送达）未收到 | 检查 `~/.court-email/*-err.log` 查看 smtp.js 报错；检查 QQ 邮箱是否将自动邮件归入垃圾箱；确认授权码未过期（腾讯授权码有效期通常90天） |
| launchd 邮件提醒未触发 | 检查 `~/.court-email/*.log` 查看执行记录；确认 launchd plist 已加载：`launchctl list | grep court-email` |
| 照片文字识别不准 | 系统自动双级降级（Tesseract 图片预处理 → MinerU）；Tier 1 失败自动尝试 Tier 2。可手动指定：`--ocr-tier tesseract` 或 `--ocr-tier mineru` |

---

## 配置

- **解析规则**：`references/sms-patterns.json`。如需修改解析规则（添加新文书标题、调整正则等），编辑该 JSON 文件即可。
- **代理方记录**：`config/case-parties.json`。每个案件第一次处理时自动询问并记录代理方（原告/被告），用于区分「我方提交资料」和「对方提交资料」。可通过"更新代理方 <案号> <原告/被告>"手动修改。
- **案卷目录**：`config/case-root.json`。首次使用询问并缓存案卷根目录路径。

---

## 🔄 变更历史

### ⭐ v2.3.0（2026-07-14）— 双级 OCR 降级 + 图片预处理

**第一性原理重构：解决 OCR 单点故障，同时保持轻量。**

- **双级 OCR 策略**：Tesseract（本地轻量）→ MinerU（云端高精度）。不做 PaddleOCR 等重武器下载（500MB+ 对法院文书 OCR 过度）
- **质量门控**：CJK≥10 且占比 ≥20% +30、长度 >50 +20、>200 +20、案号 +15、关键字 +15，≥50 通过
- **图片预处理（Tier 1 增强）**：Tesseract 处理前自动灰度化 + 1.5x 对比度增强 + 超大图缩放，零额外依赖，显著提升模糊照片成功率
- **MinerU 错误分类**：区分网络不可达、API 认证失败、请求超时、通用错误——精准报错
- **图片校验**：格式检查（仅 PNG/JPG/BMP/TIFF）、文件大小检查（≤20MB）
- **MinerU 安装提示**：不再静默下载，安装前告知 ~30MB 下载量
- **新增 `--ocr-tier` 参数**：tesseract / mineru

**对抗性审查修复**：
- 移除残留的 PaddleOCR 代码（500MB 负债）
- MinerU 错误不再混在一起报
- Tesseract 通过预处理提升成功率，减少对 MinerU 的依赖

### ⭐ v1.0–v1.5.0（杨卫薪律师）— 基础框架

杨卫薪律师（微信 ywxlaw）完成了以下核心基础工作，本技能的所有后续功能均建立在此之上：

| 模块 | 贡献 |
|------|------|
| 📱 **短信解析** | 法院短信文本解析，自动提取案号、当事人、下载链接 |
| 🌐 **zxfw API 下载** | 全国法院统一送达平台 API 直连，curl 无头下载 PDF |
| 📁 **基础归档** | 下载 PDF 后归档到案件目录，写入内部 JSON 记录 |
| 📄 **文书识别** | 正则匹配识别文书类型（传票/判决书/裁定书等） |
| 📋 **多平台支持** | 广东 gdems、湖北 hbfy、集约送达、司法送达网下载 |

上述工作使本技能从零到一，Stone 了法院短信→下载→归档的核心链路。

---

### ⭐ v2.0.0（2026-07-07）— 陆凌燕 · 全面升级

基于 v1.5.0 核心链路，新增以下能力：

**提醒体系（三条线）**
- 开庭传票自动写入系统日历 + 本机电脑提醒（系统通知 + 桌面 Markdown 文件）+ QQ 邮件（微信送达）提醒（YOUR_QQ_EMAIL@qq.com）
- 上诉期限自动设提前2天 + 截止当天双提醒，全部三条线同时到达
- macOS Apple Calendar / Windows .ics 文件 / Linux .ics 文件，跨平台覆盖

**期限规则库（`references/deadline-rules.json`）**
- 覆盖 12 种文书类型：上诉/保全/举证/答辩/执行/再审/管辖/缴费
- 保全类（冻结银行存款 1 年、查封不动产 3 年）特殊处理：提前 30 天 + 14 天 + 7 天三级递进提醒
- 新增文书类型只需追加一条 JSON 条目，无需改代码

**照片 OCR（方式三）**
- 纸质传票/判决书/通知书拍照即可识别（Tesseract 图片预处理 → MinerU 云端双级降级）
- 识别结果强制复核确认（案号、开庭时间永远标记 ⚠️）
- MinerU 首次运行自动安装提示

**归档体系优化**
- 同一条短信多份文书 → 文件夹打包（非合并 PDF），按最高级别文书命名
- 桌面端案件文件夹即案卷唯一根目录（找到已有案卷须律师确认后写入，不静默同步）

**跨平台支持**
- macOS：Apple Calendar + launchd + 通知中心
- Windows：.ics 日历文件 + schtasks + MessageBox 弹窗
- Linux：.ics 日历文件

**自动归纳与案卷管理**
- 照片/扫描件自动 OCR 后按案号或当事人姓名匹配已有案卷
- **匹配到已有案卷 → 必须请求律师确认后才归档**（同名同姓风险，禁止静默写入）；确认后带 `--to-folder <路径>` 重新运行脚本归档
- 未匹配到 → 自动在桌面新建标准案卷（10 个子目录）：法院送达文书/我方提交/对方提交/原始材料/工作文本/委托签署/邮件记录/法规检索/庭审笔录/保全资料
- 法院文书（传票/判决书/裁定书等）归入"01 法院送达文书"子目录（新建时自动归入；匹配到已有案卷时经律师确认后归入）

### ⭐ v2.1.0（2026-07-09）— 代码审查与安全加固

基于第一性原理+对抗性审查，对全部 6 个核心脚本做了一次系统级排查，修复以下问题：

**提醒时机修正**
- 开庭提醒的"本机电脑提醒"（弹通知 + 桌面 Markdown）从"创建日历事件时触发"改为"目标提醒时间（开庭前 1 天）才触发"。
- 新增 `hearing_notify()` 函数：launchd 到点自动调用，弹系统通知 + 在桌面生成 `开庭提醒_YYYY-MM-DD_HHMM.md`（含时间/案号/地点/材料清单）。
- 创建日历时仅写入日历事件 + 调度提醒任务，不弹窗、不生成文件。

**代码清理（P0）**
- 删除 `court_appeal_reminder.py`：已被 `court_deadline_reminder.py` 全覆盖且带硬编码路径（换机必炸）、缺少桌面 Markdown 提醒。
- `TO_EMAIL` 默认值统一为 `YOUR_QQ_EMAIL@qq.com`（修正两处占位符 `YOUR_QQ_EMAIL@qq.com`）。

**安全加固（P1）**
- **AppleScript 注入**：`court_deadline_reminder.py` 的 `send_system_notification` 补齐双引号转义（`replace('"', '\\"')`），与 `court_calendar.py` 的 `_macos_pop_notification` 行为一致。
- **plist XML 注入**：三处 launchd plist 生成函数（`_macos_schedule_notification`、`_macos_schedule_email_script`、`_macos_schedule`）对所有动态数据（case_no/case_type/location/label）增加 `xml.sax.saxutils.escape()` 转义，防止 `&` 等特殊字符导致 plist 解析静默失败。
- **死代码清理**：`_macos_unload_reminder` 的 `suffix.replace('-email', '-email')` 恒等变换 → 写死字面量。

### ⭐ v2.2.0（2026-07-13）— 隐私脱敏与代理方识别

**安全脱敏**
- 全部文件中的 QQ 邮箱硬编码替换为 `YOUR_QQ_EMAIL@qq.com` 占位符（SKILL.md、Python 脚本、报告模板等共 16 处）
- `config/case-root.json` 中本地路径替换为占位符 `/Users/你的用户名/项目/02-律师业务`
- `archive/` 目录发布前清空规则：保留目录结构和 `.gitkeep`，删除所有真实案件 JSON 数据
- `_meta.json` 中 `ownerId` 替换为占位符
- 脱敏审查清单新增第 11 项：运行时数据目录发布前清空

**代理方识别（P1 新增功能）**
- 新增 `config/case-parties.json`：首次接触案件时自动询问律师代理哪一方（原告/被告）
- 非法院文书智能归类（第 14 步）加入代理方感知：已知代理方时自动区分「02 我方提交资料」与「03 对方提交资料」
- 支持手动补充：`更新代理方 <案号> <原告/被告>`

### ⭐ v2.2.1（2026-07-13）— 保全裁定处理强化

**问题**：(2026)苏0281民初9011号保全裁定书下载后未触发期限规则匹配，导致：①未提取保全金额/方式/到期日等具体信息；②未创建任何日历提醒；③未计算保全到期日。

**修复**：
- **新增「保全裁定强制处理」节点**（第四步第9项 `deadline-rules.json` 匹配之后）：当文书为裁定书 + 含保全关键字时，强制走完整流程——提取详情 → 算到期 → 逐条创建全部提醒 → 汇总确认时间线
- **明确禁止部分提醒**：规则里 `reminders` 数组有几个条目就必须全建。以冻结银行存款为例：30天 + 14天 + 7天，一条不能少。到期日当天额外追加一条兜底事件
- 到期日计算公式纳入 SKILL.md 正文（不必每次查《民事保全须知》原文）

### ⭐ v2.2.2（2026-07-13）— 对抗性审查修复

**P0 修复：**
- **保全到期日当天新增提醒**：`deadline-rules.json` 中 `preservation_freeze` 和 `preservation_seal` 规则各追加一条 `before_days=0` (critical) 提醒。此前只提醒 30/14/7 天前，到期日当天无通知无邮件，仅靠日历事件兜底
- **裁定书强制保全检测**：SKILL.md 第四步 point 8（基础文书解析）新增「裁定书」子类——正文含「冻结」「查封」「扣押」关键字时，必须强制执行 point 9 期限匹配，禁止以任何理由跳过

**P1 修复：**
- **AppleScript 注入防护**：`court_deadline_reminder.py` 的 `_macos_create_calendar` 函数对 `summary` 和 `location` 字段增加 `replace('"', '\\"')` 转义，防止案由含双引号导致 AppleScript 解析失败（此前仅 `description` 被转义）

### ⭐ v2.2.3（2026-07-13）— macOS 不再生成 .ics 冗余文件

- `court_deadline_reminder.py`：移除 macOS 上强制生成桌面 `.ics` 备份文件的逻辑。macOS 已通过 AppleScript 直接写入 Apple Calendar，`.ics` 是冗余文件；仅在 Apple Calendar 写入失败时降级生成 `.ics`（与 Windows/Linux 行为一致）

