Metadata-Version: 2.4
Name: taiwan-legal-deid
Version: 0.2.1
Summary: 台灣法律文件去識別化：人名、個資、私人機構換成擬真假名或代號；假名化留對照表可還原，匿名化不留（本機 CPU 執行）
Author-email: LawChat <opensource@lawchat.com.tw>
License-Expression: MIT AND Apache-2.0
Project-URL: Repository, https://github.com/lawchat-oss/taiwan-legal-deid
Project-URL: Issues, https://github.com/lawchat-oss/taiwan-legal-deid/issues
Keywords: de-identification,pseudonymization,PII,Taiwan,legal,Traditional Chinese
Classifier: Programming Language :: Python :: 3
Classifier: Natural Language :: Chinese (Traditional)
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSES/Apache-2.0.txt
License-File: LICENSES/MIT-jieba.txt
License-File: NOTICE
Requires-Dist: numpy
Requires-Dist: onnxruntime>=1.16
Requires-Dist: tokenizers>=0.19
Provides-Extra: train
Requires-Dist: mlx; extra == "train"
Requires-Dist: transformers; extra == "train"
Requires-Dist: safetensors; extra == "train"
Provides-Extra: export
Requires-Dist: torch; extra == "export"
Requires-Dist: transformers; extra == "export"
Requires-Dist: safetensors; extra == "export"
Requires-Dist: onnx; extra == "export"
Dynamic: license-file

# taiwan-legal-deid

[English](https://github.com/lawchat-oss/taiwan-legal-deid/blob/main/README.en.md)

台灣法律文件去識別化工具。在自己的電腦上找出文件裡的人名、個資、私人公司，依用途換成擬真假名或代號；要再把 AI 的回覆換回原文，就留一份對照表。針對台灣法律文件特化，一般文件也能用。

參照 ISO/IEC 20889 的去識別化技術分類設計，提供假名化（可以換回原文）與匿名化（無法還原）兩種模式。這是設計上的參照，不是認證或合規驗證。

| 輸出 | 樣子 | 對照表 | 能不能換回 | 適合的情境 |
|---|---|---|---|---|
| 擬真假名化（預設） | 陳美玲 → 郭淑瑩 | 留 | 可以 | 丟給 AI 分析，再換回原文 |
| 代號假名化（`--codes`） | 陳美玲 → 甲、手機 → 〔電話1〕 | 留 | 可以（只留年的生日換不回月日） | 要讓人一眼看出哪些地方處理過，又想能換回的時候 |
| 匿名化（`--anonymize`） | 陳美玲 → 甲、手機 → 〔電話1〕、113年5月2日 → 113年5月 | 不留 | 不行 | 人工檢查後，分享給第三方或對外發表 |

- **分辨該換與該留的**：程式先列出可能的人名、個資、機構，再由模型逐一判斷「要換／保留／不是」。法官、檢察官、律師、書記官等執行職務的人名保留，當事人、證人等私人換掉；政府機關保留，私人公司換掉。
- **擬真假名**：同一個人整份文件換成同一個假名，姓、名分開對應（陳小姐 → 郭小姐）；地址換成同縣市的真實路名；身分證換成檢查碼正確的號碼；生日保留年份（年齡不變）；上市櫃公司的簡稱、「下稱」的別名跟著換。
- **代號**：人名換成甲、乙（同一個人整份一致；只寫姓、只寫名字、綽號能對到全名的也用同一個代號，同姓多人對不上的寫「〇」，不指錯人）；機構只把品牌換成字母（澄嶼顧問股份有限公司 → A顧問股份有限公司），「下稱」的別名跟著換（澄嶼公司 → A公司）；號碼、Email、網址換成〔電話1〕這類編號。代號會避開原文已經用過的字（例如原文有「甲方」，代號就不用甲）。生日只留年、地址只留縣市。
- **匿名化**：使用代號、不留對照表；另外把個人行程日期只留年月（判決日、起訴日等照留，Python 可以調整）。
- **法律模式（預設）**：假名化時個人行程日期不動，AI 才能照原本的日期推算期間、時效；地號保留段名，號碼整案一致地換。
- **在本機 CPU 執行**：不需要 GPU；除了第一次下載模型，不會連網。對照表是你電腦上的 JSON 檔。

> 假名化留著對照表，有對照表就能換回原文：請把對照表當成個資保管，跟假名版分開存放。匿名化不留對照表、無法還原，但不保證達到法律上的匿名：事件經過等文字仍可能讓人認出是誰，輸出是否已達法律上的匿名，要由使用者依情境判斷。工具一定會有漏網，對外分享前務必人工檢查；匿名化的輸出全是代號，漏網的真名比較顯眼，對外前更要逐一檢查。

## 範例

原文（虛構）：

> 原告陳美玲（身分證 F223456781，民國71年3月8日生，住臺中市西屯區文心路三段1999號7樓，手機 0912-345-678）與被告澄嶼顧問股份有限公司（下稱澄嶼公司）間確認僱傭關係存在事件。原告自113年5月2日起任職於澄嶼公司，陳小姐主張澄嶼公司違法解僱。被告訴訟代理人王大同律師。法官宋芷蘅。

擬真假名化（每次執行的假名不同）：

> 原告姚靜慧（身分證 F279190746，民國71年9月8日生，住臺中市太平區太平十一街四段554號7樓，手機 0976-232-860）與被告岑智顧問股份有限公司（下稱岑智公司）間確認僱傭關係存在事件。原告自113年5月2日起任職於岑智公司，姚小姐主張岑智公司違法解僱。被告訴訟代理人王大同律師。法官宋芷蘅。

代號假名化：

> 原告甲（身分證 〔身分證1〕，民國71年生，住臺中市〔地址1〕，手機 〔電話1〕）與被告A顧問股份有限公司（下稱A公司）間確認僱傭關係存在事件。原告自113年5月2日起任職於A公司，甲小姐主張A公司違法解僱。被告訴訟代理人王大同律師。法官宋芷蘅。

匿名化（跟代號版只差在個人行程日期只留年月，而且沒有對照表）：

> 原告甲（身分證 〔身分證1〕，民國71年生，住臺中市〔地址1〕，手機 〔電話1〕）與被告A顧問股份有限公司（下稱A公司）間確認僱傭關係存在事件。原告自113年5月起任職於A公司，甲小姐主張A公司違法解僱。被告訴訟代理人王大同律師。法官宋芷蘅。

範例全為虛構；擬真版換出的姓名、公司名、身分證、手機、地址是隨機產生的，可能剛好跟真實的人、公司、號碼或門牌相同。

## 安裝

需要 Python 3.10 以上。

```bash
pip install taiwan-legal-deid
```

第一次使用時會從 GitHub Release 下載模型（6 層約 244 MB、3 層約 159 MB），驗過 sha256 後存在 `~/.cache/taiwan-legal-deid`。

## 使用

### 指令列

```bash
taiwan-legal-deid 起訴狀.txt                                  # → 起訴狀.txt.fake.txt（假名版）、起訴狀.txt.map.json（對照表）
taiwan-legal-deid 起訴狀.txt --codes                          # 代號版：甲、A公司、〔電話1〕，一樣留對照表
taiwan-legal-deid 證物.txt --map 案件對照表.json             # 同一案件沿用對照表：同一個人每份文件都換成同一個假名
taiwan-legal-deid --restore AI回覆.txt 起訴狀.txt.map.json    # AI 回覆換回原文
taiwan-legal-deid 起訴狀.txt 證物.txt --anonymize             # 匿名化：每份寫 <檔名>.anon.txt，不留對照表；同一批代號一致
taiwan-legal-deid 起訴狀.txt --model 3l                       # 較快的 3 層模型
taiwan-legal-deid 病歷.txt --general                          # 一般模式：個人行程日期也處理（擬真版整份位移、代號版只留年月）
```

輸入是 UTF-8 純文字；PDF、Word 請先轉成文字。

每份文件旁邊都會留一份它自己的對照表（`<檔名>.map.json`），還原那份文件的 AI 回覆時用它。同一案件共用的對照表（`--map`）偶爾會停用少數對照：前一份文件的假名剛好是後一份文件裡的真名時，那個假名就分不出是誰。同一案件請用同一種樣子（都加或都不加 `--codes`）。

匿名化輸出無法還原。`--anonymize` 一律使用代號、不留對照表（加不加 `--codes` 都一樣），不能搭配 `--map`、`--general`。

### Python

```python
from taiwan_legal_deid import Anonymizer, Detector, Obfuscator, restore

det = Detector()                                                  # 預設 6 層；Detector("3l") 較快
fake, mapping = Obfuscator(det).pseudonymize(text)                # 擬真假名化：假名版文字、對照表
original = restore(ai_reply, mapping)                             # AI 回覆換回原文
fake, mapping = Obfuscator(det, style="code").pseudonymize(text)  # 代號假名化：甲、A公司、〔電話1〕，一樣可以還原
anon = Anonymizer(det).anonymize(text)                            # 匿名化：只回傳文字，沒有對照表
```

同一案件有多份文件時，每份文件建一個新的 `Obfuscator`，把上一份回傳的對照表傳進去（跟指令列的 `--map` 相同）；不要拿同一個 `Obfuscator` 連續處理多份文件。

```python
mapping = None
for text in texts:
    fake, mapping = Obfuscator(det, mapping=mapping).pseudonymize(text)  # 還原這份文件的 AI 回覆時，用這次回傳的 mapping
```

匿名化一次處理多份：`Anonymizer(det).anonymize_many(texts)`（先掃過全部原文，避開原文已經用過的代號；同一個 `Anonymizer` 處理的文件代號一致）。`Anonymizer` 的參數：`event_dates`（個人行程日期：`month` 預設、`year`、`keep`）、`other_dates`（判決日等其他日期：`keep` 預設、`month`、`year`）、`addr`（地址：`city` 預設、`district` 留到鄉鎮市區）。

0.1.x 的 `Obfuscator.anonymize()` 還能用、結果不變（做的是假名化，回傳對照表），會發出 FutureWarning 警告、提醒改用 `pseudonymize()`，1.0 移除。要不可還原的匿名化請用 `Anonymizer`。

只要偵測、不替換：`Detector().detect(text)` 回傳每個值的位置、類型與分數。

### 離線使用

先在有網路的機器執行 `taiwan-legal-deid --download`（加 `--model 3l` 下載 3 層），再把 `~/.cache/taiwan-legal-deid` 整個資料夾複製到離線機器的同一個位置，或用環境變數 `TAIWAN_LEGAL_DEID_HOME` 指到放模型的資料夾。設 `TAIWAN_LEGAL_DEID_OFFLINE=1` 時一律不連網。

## 準確度與速度

數字為 weights-v1（套件 0.2.1）量測，可以用 `python eval/reproduce.py` 重跑（另需 `pip install pyarrow`）。三種輸出用同一個偵測，偵測漏掉的值三種都會漏。

| 假名化 | 6 層（預設） | 3 層 |
|---|---|---|
| 合成法律文件：要換的人名完整換掉 | 93.3% | 91.1% |
| 合成法律文件：執行職務的人名保留 | 98.0% | 98.0% |
| 合成法律文件：其他個資完整換掉（一般模式） | 93.6% | 93.0% |
| tw-PII-bench 測試半：人名以外的個資完整換掉 | 95.7% | 95.1% |
| 速度：一份約 10 萬字的文件，找出並替換（Apple M5 Max CPU） | 約 12 秒 | 約 8 秒 |

| 匿名化（`Anonymizer` 預設） | 6 層（預設） | 3 層 |
|---|---|---|
| 合成法律文件：直接識別資料完整遮蔽 | 94.5% | 93.2% |
| 合成法律文件：生日只留年 | 95.1% | 95.1% |
| 合成法律文件：地址只留縣市 | 83.3% | 80.0% |
| 合成法律文件：個人行程日期只留年月 | 95.8% | 95.8% |
| 合成法律文件：執行職務的人名照留 | 96.1% | 96.1% |
| tw-PII-bench 測試半：人名以外的直接識別資料完整遮蔽 | 96.7% | 96.2% |
| tw-PII-bench 測試半：地址只留縣市 | 93.8% | 91.2% |
| tw-PII-bench 測試半：日期粗化 | 89.6% | 89.9% |

**本頁數字皆來自合成測試集；合成資料通常比實際文件單純，實際表現可能較低，對外分享前務必人工檢查。**

- **合成法律文件測試集**（`eval/data/synthetic_legal_test.jsonl`）：75 份虛構的起訴狀、判決、筆錄、存證信函、對話紀錄、Email 等，共 21,823 字。題目在任何評測之前封存，開發時沒有看過裡面的錯題。標了 462 個要換的人名、51 個執行職務的人名、498 個其他個資。每份標了難點類型：1 罕見姓與原住民、外籍人名；2 像一般詞的名字；3 OCR 雜訊；4 同一個值多種寫法；5 該保留與該換的人名並存；6 一般文件。
- **tw-PII-bench**（[lianghsun/tw-PII-bench](https://huggingface.co/datasets/lianghsun/tw-PII-bench)，Liang Hsun Huang，Apache-2.0）：取 `crc32(id) % 2 == 1` 的 453 份，以一般模式計分。人名類別在切分之前整份看過，所以不列。
- 「完整換掉」「完整遮蔽」＝標註的每個字都被換掉，只換一部分算沒換掉。直接識別資料＝人名、身分證、電話、Email、網址、帳號、密碼、車牌、社群帳號等；間接識別資料（生日、地址、日期）算有沒有粗化，地址扣掉照留的縣市。
- 假名化表看偵測結果（模型判斷）；匿名化表看實際輸出（同一個名字別處要換時會一起換，所以執行職務的人名照留比假名化表低）。
- 匿名化的數字看實際輸出：直接識別資料的輸出不能留下原值（例如密碼被當成日期換成「1986年」算沒遮好），生日只能剩年份，日期不能留日。
- 代號假名化跟匿名化用同一套替換：直接識別資料、生日、地址、執行職務人名的數字跟上表相同；差別是個人行程日期照留（合成法律文件 98.3%，兩個模型相同）。
- 法律模式下，生日完整換掉 95.1%、個人行程日期保留 98.3%（兩個模型相同）。

## 範圍與已知限制

**範圍**：針對繁體中文的台灣文件。日文（只寫片假名的人名、株式会社、日本地址、和曆日期）不在處理範圍。

**偵測一定會有漏網和誤判**（模型的限制，見上表），對外分享前務必人工檢查：

- 聊天裡只寫名字或綽號的人，常抓不到。
- 地址的頭尾較難切準（合成測試集的地址完整換掉：6 層 88%、3 層 84%）。
- 律師、法官的名字沒有職稱、單獨列在頁首或名單裡時，有時會被當成要換的人名。
- 很長的網址（超過約 220 字）有時整段抓不到。
- 事務所（律師、會計師、地政士）以及部分連鎖店、國營事業，即使是當事人或當事人任職的地方，也常被判成保留。

**設計上的取捨**（照設計運作，使用前要知道）：

- 匿名化只處理上面列的直接、間接識別資料；年齡、職業、學校、親屬關係、事件細節，以及機構名稱裡的類型、產業詞與縣市（A顧問股份有限公司的「顧問」），不會處理。
- 代號版還原時，單獨的代號在會組成一般詞的位置不換回（甲方、甲說、甲級）；單獨的機構字母只在「被告A」「（下稱A）」這類稱呼後面換回；A公司，以及稱呼後面接公司類型、產業詞的寫法（被告A科技、被告A集團），名稱拆得出品牌時只把字母換回品牌、後面的字照留（澄嶼公司、澄嶼科技）；字母跟下一個字組成一般詞的不換（A棟、A咖、X光），維生素A、A/B測試也不換；只留年、年月、縣市的值不會換回（細節已經拿掉）；〔電話1〕被改寫成別的寫法就換不回。
- 同姓多人時，只寫姓的稱呼（陳小姐）對不到特定的人，會寫成「〇小姐」。
- 代號只在同一個 `Anonymizer` 或同一次指令內一致；分批處理時，前後批的甲不是同一個人。逐份呼叫 `Anonymizer.anonymize()` 時，後面的文件原文如果已經用了前面編好的代號，那個人會在那份文件改用新代號並發出警告；同一批請用 `anonymize_many()` 或指令列一次處理。
- 同一案件共用對照表（`--map`）時，前一份文件的假名如果剛好是後一份文件裡的真名，那筆對照會停用、之後改用新的假名；還原各份文件的 AI 回覆時，請用那份文件自己的對照表。

## 運作方式

1. **找候選（程式）**：依姓氏表（內政部）與稱謂列出可能的人名切法；身分證、電話、帳號、Email、網址、地址（內政部路名）、日期等個資；公司、商號、社區、學校等機構名稱與上市櫃簡稱。程式只負責多抓，不做判斷。
2. **判斷（模型）**：每段文字只跑一次模型，同時判斷段內每個候選「要換／保留／不是」。模型由 bert-base-chinese 微調，再從 12 層蒸餾成 6 層與 3 層，以 ONNX 在 CPU 執行。
3. **替換（程式）**：依類型換成擬真假值，或換成代號、粗化（ISO/IEC 20889 的假名化、遮蔽、粗化）；整份文件一致，同一個值在別處的寫法也一起換。

訓練資料包括公開判決（判決列名的當事人、代理人、法官、書記官等人名，先一致地換成假名）、合成法律文件與程式擴增。訓練程式在 `taiwan_legal_deid/train.py`（MLX，Apple Silicon），要從 repo 執行（會讀寫 repo 裡的 `data/`）；訓練資料不公開。

## 關於

由 [LawChat](https://lawchat.com.tw) 維護 — 一個台灣法律 AI 平台。

- 回報問題與詢問：[GitHub Issues](https://github.com/lawchat-oss/taiwan-legal-deid/issues)
- 官網：[lawchat.com.tw](https://lawchat.com.tw)
- 聯絡：opensource@lawchat.com.tw

Best-effort 維護，不保證 issue 的回覆時效。歡迎回報漏換或換錯的例子；貼上來之前，請先把真實個資換成虛構的內容。

## 授權

- 程式碼：MIT（`LICENSE`）。
- 模型權重：Apache-2.0（`LICENSES/Apache-2.0.txt`），由 bert-base-chinese（Google，Apache-2.0）微調而來。
- 內含的第三方資料（jieba 詞典、內政部與金管會證期局的政府開放資料）見 `NOTICE`。
- 合成法律文件測試集跟程式碼一樣以 MIT 授權。

## 免責聲明

**不提供擔保。** 本工具的程式依 MIT 授權、模型權重依 Apache-2.0 授權「按現狀」提供，不提供任何明示或默示的擔保。在法律許可的範圍內，維護者對使用本工具或其結果所生的損害不負賠償責任。

本工具協助降低文件中個資外流的風險，但不保證找出所有個人資料，也不構成法律意見。文件是否可以分享、上傳或公開，請自行判斷並人工檢查；使用者仍應自行遵守個人資料保護相關法規。

## 引用

見 `CITATION.cff`。
