管线组
# tmkit 后端管线 · 进展报告(管线组)
**日期**:2026-11-03 **状态**:✅ 最小可用版交付,pytest 12/12 全绿
## 做了什么
在 `金唐智法/tm/tmkit/` 交付 Python 库 `tmkit`(纯库,不含 Web),四大核心模块 + 测试 + 文档:
| 模块 | 内容 |
|---|---|
| `splitter.py` | PDF 分流:`get_text` 检测文字层,逐页返回 `native` / `scanned`(阈值可调) |
| `extractor.py` | 原生页按词抽取 bbox(`get_text('words')`);扫描页 `raise NotImplementedError('OCR 待接入 PaddleOCR')` |
| `detector.py` | PII 识别引擎:身份证 18 位(GB 11643 校验位验证)、大陆手机号、银行卡(Luhn 校验)、统一社会信用代码(GB 32100 校验)、邮箱;输出 `[{type, text, page, bbox}]`,支持 `register()` 自定义规则 |
| `redactor.py` | 坐标回写:两种方案都实现了——`add_redact_annot + apply_redactions`(默认,真删文字,测试验证 PII 从文本层消失、黑块像素可见)和 `draw_rect` 黑条(对比保留);输出脱敏后 PDF bytes |
| `tests/` | reportlab 动态生成 3 个样例 PDF(纯中文文本页 / 身份证+手机号+银行卡+邮箱页 / 空白扫描页,中文字体用 STSong-Light CID),12 个用例全链路验证 |
| `README.md` | 模块说明 + 代码示例 + 已知限制 |
## 测试结果(python3 -m pytest,12 passed)
- **全链路断言**:脱敏后 PDF 用 `get_text` 验证——身份证/手机号/银行卡/邮箱原文**不存在**、非敏感文本("张三""脱敏"等)**保留**、页数**不变**、bbox 区域像素为纯黑
- **校验算法单测**:身份证校验位、Luhn 各有正反例
- **零误报**:无 PII 的普通文本页检测结果为空
- **自定义规则**:注册"案号"正则规则后命中正常
- **扫描页**:正确抛 `NotImplementedError`(OCR 接口预留)
**覆盖 PII 类型(5 类)**:身份证、手机号、银行卡、统一社会信用代码、邮箱
## 开发中解决的坑(经验沉淀)
1. reportlab 默认字体 Helvetica 画中文全部变 "IIII",需注册 `UnicodeCIDFont("STSong-Light")`
2. Python 正则 `\b` 在"中文紧贴数字"(如 `身份证号110101...`)处不成立(中文和数字都是 Unicode `\w`),已改用 ASCII-only lookaround 边界
3. 回写方案选择:`apply_redactions` 删字最干净(文本层彻底无 PII),定为默认;`draw_rect` 黑条仅视觉遮挡、文字仍可复制提取,仅留作对比
## 已知限制 → 下一步
| 项 | 说明 | 计划 |
|---|---|---|
| OCR 未接入 | 扫描页直接抛 NotImplementedError | 接入 PaddleOCR,补齐扫描页链路(最高优先级) |
| 跨行 PII | 身份证分两行排版时不命中(行内拼接策略) | 行间相邻性合并 |
| 中文姓名/地址 | 属于 NER 范畴,正则不适用 | 待接入 NER 模型(第二优先级) |
| 旋转页/加密 PDF | 未处理 | 视真实样本需求补 |
| `draw` 模式 | 不删字,仅演示用 | 文档已标注风险 |
## 环境备忘
- `python3`(系统 3.9)+ pymupdf 1.26.5 / reportlab 5.0.0 / pytest 8.4.2(pip --user)
- 复跑:`cd 金唐智法/tm && python3 -m pytest tmkit/tests/ -v`
平台组(Web)
# PROGRESS-web.md · 平台组(Web 前端)
更新:2026-11-03 11:50
## 已完成
- `app.py`:FastAPI 应用,端口 8736,绑定 0.0.0.0
- `GET /`:中文单页应用(HTML/CSS/JS 全部内嵌,无外部 CDN,适配 2G 内存服务器)
- 拖拽 + 按钮两种上传方式
- 处理进度条 + 阶段文字(页面分流 → 抽取识别 → 脱敏回写)
- 结果区:敏感项表格(类型/原文/页码)、下载脱敏文件按钮、保留原件下载按钮
- `POST /api/process`(multipart):上传存 `/tmp/tm-uploads/<task_id>/input.pdf`,后台线程处理,立即返回 task_id
- `GET /api/tasks` / `GET /api/tasks?task_id=`:任务状态轮询(含 tmkit 是否就绪标志)
- `GET /download/{task_id}?type=redacted|original`:文件下载,未完成任务返回 409
- `GET /progress`:开发进展看板,渲染 PROGRESS-backend/web/deploy.md,缺失显示「进行中」
- tmkit 集成:按当前实际签名调用
`classify_pages(pdf_bytes)` → 逐页 `extract_page_words(page)` + `detector.scan_page_words(words, page)` → `redact_pdf(pdf_bytes, hits)`
tmkit 导入失败时自动降级为占位逻辑(正则粗扫手机号 + 原样拷贝输出),页面顶部/结果区会提示「占位模式」——TODO:tmkit 正式就绪后该分支可删
- 非 PDF 上传返回 400;不存在任务/文件返回 404
## 自测结果(2026-11-03,本机)
- `uvicorn app:app --host 0.0.0.0 --port 8736` 启动正常
- `GET /` → 200;`GET /progress` → 200
- reportlab 生成测试 PDF(含手机号 13812345678 / 15987654321、身份证号)
- `POST /api/process` → `{"task_id":"b603facc8ddc"}`
- 轮询 `GET /api/tasks?task_id=` → `status:"done", items:[{"type":"phone","text":"15987654321","page":1}]`
- `GET /download/...?type=redacted` → 200(966B,已脱敏)
- `GET /download/...?type=original` → 200;错误 task_id → 404
## 已知问题
1. **检测漏项(疑似分词问题,归管线组)**:测试 PDF 里 `13812345678` 紧跟在 `Phone: ` 后,fitz 抽词时与前一截文字合并成一个 word,detector 逐词正则匹配不到;`15987654321` 前有空格可正常检出。建议 detector 对 word 做跨词滑窗或先拼接整行文本再定位 bbox。
2. 任务表在内存中,重启后 /tmp 文件还在但任务状态丢失;上传目录无自动清理(长期运行会占盘)。
3. 处理为单线程逐任务并发(threading),大文件并发多了内存可能吃紧(2G 服务器);未做限流。
## 下一步
- 与管线组确认 detector 跨词匹配方案(已知问题 1)
- 加 /tmp/tm-uploads 过期清理(如 24h 定时或启动时清理)
- 看板加自动刷新/最后更新时间;部署组联调 systemd 常驻
部署组
# 部署线进展(铁柱 · 2026-11-03 11:40)
## 已完成
- ✅ tm.tongueclaw.com / git.tongueclaw.com 双域名 HTTPS 证书签发(Let's Encrypt,有效期至 2027-02-01)
- ✅ git.tongueclaw.com:Gitea 代码仓库上线(管理员 tiezhu,仓库 tiezhu/tm-desensitize),HTTPS + systemd 常驻
- ✅ tm.tongueclaw.com:nginx 反代就绪(127.0.0.1:8736,上传上限 30MB)
- ✅ 服务器 Python 3.12 venv 环境(fastapi/uvicorn/pymupdf)
- ✅ 代码推送仓库 + systemd 服务部署
## 访问入口
- 系统使用:https://tm.tongueclaw.com/
- 开发看板:https://tm.tongueclaw.com/progress
- 代码仓库:https://git.tongueclaw.com/tiezhu/tm-desensitize
## 下一步
- PaddleOCR 接入(扫描件路径)
- 跨词/跨行 PII 识别优化(平台组已发现紧贴文字漏检问题)
- 人审界面 + 审计日志