最終更新: 2026年9月12日
このアプリは、受け入れた資料の中から個人情報の候補を探して一覧(pii-report.csv)に出します。
その検出がどれくらい当たるのかを実際に測った結果と、測っても直らない限界をここにまとめます。
最初に結論。検出が 0 件でも、個人情報が無いことの証明にはなりません。 この機能は「人が確認すべき箇所の候補出し」です。公開可否の判断そのものには使えません。
1. 何を検出しているか
検出する種別は次の 5 つだけです。
| 種別 | 判定のしかた |
|---|---|
| マイナンバー | 12 桁の数字で、検査用数字(12 桁目)が規定どおり |
| クレジットカード番号 | 13〜19 桁の数字で、Luhn の検査を通り、かつ実在ブランドの先頭桁(3/4/5/6、または 2221〜2720) |
| メールアドレス | 名前@ドメイン.トップレベル の形 |
| 電話番号 | 0 で始まりハイフンか括弧で区切られた、合計 10 桁または 11 桁の数字 |
| 郵便番号 | 〒 付きなら 7 桁(ハイフン任意)、〒 無しなら 3 桁‐4 桁 |
次のものは検出対象に入っていません。 氏名、住所、生年月日、銀行口座、被保険者番号、旅券番号、 運転免許証番号、家族構成、病歴。 ストアの説明文(「個人情報の候補の検出(マイナンバー、クレジットカード番号など)」)を読むと もっと広く見ているように受け取れますが、実際に見ているのは上の 5 つです。
検出した値は必ずマスクして出します(末尾 4 桁だけ、郵便番号は全桁伏せ、メールはローカル部の先頭 1 文字だけ)。 レポート自体が漏洩源にならないようにするためです。
2. どうやって測ったか
tests/fixtures/pii/cases.jsonl に、正解ラベルを付けた 119 行を用意しました。
1 行が 1 件の文面で、「この行から何が検出されるべきか」を書いてあります。
- すべて合成データです。実在の個人情報は 1 件も含みません。
マイナンバーは検査用数字を計算して作った架空の番号、カード番号は各ブランドが公開している試験用番号
(
4242 4242 4242 4242など)です。 - 文面は、日本の文書館・史料館の受け入れ資料に出てきそうなものにしました (申請書の記載欄、差出人欄、台帳の行、目録の請求記号、マイクロフィルムのリール番号など)。
内訳:
| 区分 | 件数 | 中身 |
|---|---|---|
| 陽性(検出されるべき) | 59 | 5 種別の記載。うち 6 件は「取れないと分かっている表記」(§5) |
| 陰性(検出されてはいけない) | 44 | 整理番号・請求記号・年度+連番・ISBN・頁範囲・和暦の日付など |
| 敵対的な陰性 | 5 | 検査用数字や Luhn を偶然通ってしまう別種の番号(§4) |
| 未対応種別 | 11 | 住所・氏名・生年月日・口座など、そもそも見ていないもの |
表記のゆれも入れてあります。全角数字、全角@、ハイフンあり/なし、スペース区切り、タブ区切り、 括弧書きの市外局番、縦書き資料の OCR でハイフンが長音符(ー)や別種のダッシュ(‐ ‑ – −)に化けたもの。
測り方は 1 行ごとの多重集合の突き合わせです(同じ行に同じ種別が 2 件あるケースも数え分けます)。
テストは tests/test_pii_accuracy.py。uv run pytest -q tests/test_pii_accuracy.py で再現できます。
3. 実測値(2026年9月12日)
| 種別 | 正解 | 見逃し | 誤検出 | 適合率 | 再現率 | F値 |
|---|---|---|---|---|---|---|
| マイナンバー | 13 | 1 | 2 | 0.8667 | 0.9286 | 0.8966 |
| クレジットカード番号 | 12 | 2 | 3 | 0.8000 | 0.8571 | 0.8276 |
| メールアドレス | 12 | 0 | 0 | 1.0000 | 1.0000 | 1.0000 |
| 電話番号 | 18 | 3 | 0 | 1.0000 | 0.8571 | 0.9231 |
| 郵便番号 | 9 | 0 | 0 | 1.0000 | 1.0000 | 1.0000 |
| 合計 | 64 | 6 | 5 | 0.9275 | 0.9143 | 0.9209 |
適合率 = 検出したもののうち本当に個人情報だった割合。 再現率 = 本来検出すべきもののうち実際に検出できた割合。
参考として、同じ 119 行を今回の修正前の実装で測ると 適合率 0.7313 / 再現率 0.7000 / F値 0.7153 でした(誤検出 18 件、見逃し 21 件)。 主な原因は、全角数字と OCR のダッシュを一切拾えなかったこと、 カード番号の一部を電話番号として二重に報告していたこと、 ISBN をカード番号として報告していたことです。
今回直したこと
- 全角・互換文字の正規化(NFKC)。全角数字・全角@・全角ハイフンで書かれた申請書を取りこぼしていました。
- OCR で化けたダッシュ(‐ ‑ ‒ – — ― − ー)をハイフンとして扱うようにしました。
- カード番号に先頭桁の条件を追加。Luhn は 10 回に 1 回は偶然通るため、 ISBN-13(978/979 始まり)のおよそ 1 割がカード番号として報告されていました (ISBN-13 の検査用数字と Luhn は別の計算なので、全部が通るわけではありません)。
- 電話番号の桁数を 10 桁か 11 桁に限定し、代わりに 3 桁終わり(フリーダイヤル
0120-123-456)と 括弧書き((03)1234-5678、03(1234)5678、03 - 1234 - 5678)に対応しました。 12 桁の文書番号0123-4567-8901を電話番号と誤認しなくなります。 - 長い数字列の一部を切り出さないようにしました。24 桁の整理番号の先頭 16 桁が 偶然 Luhn を通ってカード番号として報告される、という切り出し方をしていました。
- 郵便番号の前後の語を見るようにしました。「第 003-0045 号」「頁 113-0033」は 郵便番号と同じ形で、目録には郵便番号よりこちらの方がよく出てきます。
〒付きならハイフン無しの 7 桁も拾うようにしました(〒があれば別物の可能性が低いため)。
誤検出と見逃しのどちらに倒したか
見逃しを減らす方向に倒しています。 この機能は「人が確認する候補を出す」ものなので、 出てきたものを人が捨てるのは容易ですが、出てこなかったものには誰も気づけないからです。
ただし再現率を上げるために誤検出を増やす変更はしていません。 上の 1〜7 のうち、3・5・6 は誤検出だけを減らすもの、1・2・4・7 は表記のゆれを吸収して 見逃しだけを減らすものです。両者を取り引きした箇所はありません。
一方で、次の 2 つは「誤検出を減らせるが見逃しが増える」ため採用しませんでした。
- 13 桁のカード番号を対象から外す(§4 の誤検出 3 件はこれで消えるが、古い資料にある 13 桁の Visa を落とす)
- 郵便番号の検出を都道府県名や
〒がある行に限る(「113-0033 本郷…」のような書き方を落とす)
4. 誤検出の中身(5 件)
すべて「検証を偶然通ってしまう別種の番号」です。これは原理的に選り分けられません。
| 文面 | 何と誤認するか | なぜ避けられないか |
|---|---|---|
整理番号 111111111118 |
マイナンバー | 任意の 12 桁は約 10 回に 1 回、検査用数字を満たす |
資料 ID 202600000006 |
マイナンバー | 同上。年度+連番でも起こる |
JAN コード 4901234567894 |
カード番号 | 13 桁の数値コードは約 10 回に 1 回 Luhn を満たす。JAN の先頭 49 はカードの先頭桁と重なる |
法人番号 5000000000005 |
カード番号 | 同上 |
伝票番号 4900000000007 |
カード番号 | 同上 |
12 桁・13 桁の連番を大量に含む台帳を受け入れると、その 1 割前後が候補として出ます。 候補一覧が長いこと自体は異常ではありません。
確率の根拠(2026-09-12 に 30 万件の乱数で実測):
| 条件 | 実測 | 理屈 |
|---|---|---|
| 任意の 12 桁が検査用数字を満たす | 9.89% | 検査用数字は 11 を法として求めるが、10・11 は 0 に写すので取りうる値は 0〜9 の 10 通り。末尾 1 桁が偶然一致する確率は 1/11 ではなく 1/10 |
| 任意の 13 桁が Luhn を満たす | 10.11% | 同じく 1/10 |
| 有効な ISBN-13 が Luhn も満たす | 10.05% | ISBN-13 の検査用数字は重み 1,3 の mod 10 で、Luhn(交互 2 倍の mod 10)とは別の計算。したがって ISBN だからといって通るわけではなく、偶然一致する分だけ通る |
「ISBN はカード番号として報告される」ではありません。 報告されるのは ISBN のうち およそ 1 割です。ここを「ISBN は通る」と書くと、検査の性質を誤って伝えることになります。
5. 検出できないもの
仕組み上できないもの
- 画像の中の文字。 写真・スキャン画像・図面の中に書かれた番号は読みません。OCR は行いません。
- 画像だけの PDF。 テキスト層が無い PDF からは文字が取れません。
- 暗号化された PDF、壊れた PDF。 開けないので走査できません。 この場合は「走査できなかった」ことがレポートに残ります(無害と記録されるわけではありません)。
- 手書き。
- Word / Excel / PowerPoint の中身。 圧縮アーカイブの中までは掘りません。
- ファイル先頭からの上限バイト数までしか読みません。長大なファイルの末尾は走査されません。
表記のせいで取れないもの(評価データに含めて固定済み)
- 区切りの無い電話番号(
09012345678、0332345678)。 区切り無しの 10〜11 桁を拾うと、資料番号との誤検出が一気に増えるため意図的に対象外にしています。 - 漢数字(
〇三-一二三四-五六七八)。縦書き資料では珍しくありません。 - 表のセルに分割された番号(
| 4242 | 4242 | 4242 | 4242 |)。 CSV の列境界や改行をまたいだ番号は、1 行としてつながらないため検証が通りません。 - 長い数字列に埋もれた番号(
整理 12 4242424242424242)。 前後の数字とひと続きに見えるため、番号だけを切り出せません。 - ピリオド区切り(
1234.5678.9018)。 ピリオドを区切りとして許すと、寸法や小数の表から偽の 12 桁が大量に生まれます。
そもそも見ていないもの
氏名、住所、生年月日、本籍、銀行口座、被保険者番号、旅券番号、運転免許証番号。 日本語の住所や氏名を正規表現で拾うと誤検出が実用に耐えない量になるため、入れていません。 資料の大半の個人情報はこの区分に入ることに注意してください。
6. この数字をどう読むべきか
- 「検出 0 件」は「個人情報が無い」ではありません。 §5 のとおり、画像・手書き・住所・氏名は最初から見ていません。
- 再現率 0.91 は、この評価データに対する値です。 実際の資料での見逃し率ではありません。 評価データはこの実装を直した者が同じ時期に作っており、実装が想定した表記に偏っている可能性があります。 実際の資料はもっと多様なので、実運用での再現率はこれより低いと考えてください。
- 出てきた候補は必ず人が現物で確認してください。 「候補」であって「個人情報」ではありません。
- 公開・非公開の判断、マスキングの最終確認を、この一覧だけで済ませないでください。 この機能は、目視確認の当たりを付けるための道具です。
7. 数字を更新するとき
評価データを増やしたり実装を直したときは、次の順で行ってください。
tests/fixtures/pii/cases.jsonlに行を足す(正解ラベル付き、合成データのみ)uv run pytest -q tests/test_pii_accuracy.pyで測るuv run python tests/test_pii_accuracy.pyを直接実行すると種別ごとの表(TP / FP / FN 付き)が出ますtests/test_pii_accuracy.pyのしきい値と、この文書の §3 を同時に直す
しきい値は実測値より少し低く置いてあります。上げるためではなく、下がったときに気づくためのものです。