MENU
JAEN
【中小企業必見】e-Tax / 年金通知のZIPをA4 PDFに変換するOSSツール「office-toolkit」
博士の視点

【中小企業必見】e-Tax / 年金通知のZIPをA4 PDFに変換するOSSツール「office-toolkit」

土居 意弘
e-Tax年金機構OSSPythonClaude Code中小企業DX

はじめに

サムライアプス株式会社の社内事務処理用に作った CLI ツール office-toolkit を、MIT ライセンスで OSS 公開しました。

github.com/samurai-apps/office-toolkit

中小企業や個人事業主の方、税理士事務所の皆さんで、e-Tax や日本年金機構から届く電子通知 ZIP の取り回しに困っている方の助けになれば嬉しいです。

本記事では、

  • なぜこのツールを作ったか(政府公式 ZIP のあるある問題)
  • どんな技術的工夫が必要だったか(XSL を捨てた話、cp932 のハマりどころ等)
  • Claude Code を使った設計・実装・OSS 化の流れ

を紹介します。AI と一緒に実務問題を解決する具体例として、参考になればと思います。

問題: そもそも、政府の電子通知を読む環境が今は無い

e-Tax や日本年金機構からは、月次や年次でいろいろな通知書が 電子ファイル (ZIP) として送られてきます。

例として年金機構が送ってくる ZIP を開けてみると、中身はこんな構成です。

1381260411422339_20260420053844/
├── 202604200245493606.xml                    # 表紙メタ情報
├── 保険料納入告知額・領収済額通知書_…(202604200245493606).xml  # 明細本体
├── 保険料納入告知額・領収済額通知書_…_明細(…).csv             # 明細CSV
├── kagami.xsl                                # 表紙用スタイルシート
└── yoshiki_29_zumitsu_001.xsl                # 様式用スタイルシート

本体は XML データで、同梱の XSL スタイルシートを Internet Explorer でレンダリングして読む という設計になっています。

ところが、IE はとっくに退役しました。後継の Edge には IE 互換モードがあるものの、近年のバージョンで 互換モードはデフォルト OFF になっています。ファイルごとに互換モードを ON にする手順を踏めば一応レンダリングはできますが、毎月の事務処理で毎回それをやるのはあまりに不便です。

つまり、現代のマシン環境では、政府の電子通知を素直に読む手段が事実上なくなっている のが現状です。これは数年前から個人事業主や税理士の界隈で「e-Tax あるある」として共有されている話で、書類を A4 の PDF にして保管したい・誰かに渡したい・印刷したい、という普通のニーズに公式ツールチェーンが応えてくれません。

そこで、XML を直接読んで自前で PDF 化してしまおう というのがこのツールを作った発端です。

作ったもの: ZIP 1 個 → 見やすい A4 PDF

そこで、ZIP を 1 個食わせると、自前 HTML/CSS で再構築した A4 PDF を吐き出す CLI を作りました。

office-toolkit etax --input notification.zip
# → 保険料納入告知_令和8年4月分_<元stem>.pdf を出力

特徴は次の 3 点です。

完全ローカル動作

  • ZIP は あなたのマシンから一歩も出ません
  • インターネット接続不要(オフラインで動きます)
  • LLM や外部 API への送信は一切なし
  • 実行時の依存パッケージは WeasyPrint ひとつだけ

税務情報を扱う以上、Web 上の変換サービスにアップロードするのはちょっと…という方にも安心して使ってもらえる設計です。

対応書類

現状サポートしている書類は次の通りです(いずれも日本年金機構)。

書類XML ルートタグ
保険料納入告知額・領収済額通知書HokenRyouRyoshuzumigakutsuchiShoList
社会保険料額情報ShakaiHokenRyouGakuJouhouList
保険料増減内訳書ZougenUchiwakeSho
被保険者データ送付通知(鑑型 + SHFD0039.DTA

書類の自動判定は内側 XML のルートタグを見ています。新しい通知タイプは Issue / PR で受け付けます(拡張手順は後述)。

環境フォントに依存しない

WeasyPrint で日本語を出すとき悩みになるのが「配布先に CJK フォントが入っていない」問題です。本ツールは Noto Sans JP を @font-face でパッケージ同梱 しているので、システムにフォントが入っていない環境でも豆腐 (□) になりません。

ハマりどころ: e-Tax XML の「あるある」

実装中にぶつかった、政府系電子文書ならではの罠を 4 つ紹介します。同じ問題で悩んでいる方には、たぶんどれか刺さると思います。

1. XSL は使えない(リスペクトしたが崩れた)

最初は 公式 XSL をリスペクト する方向で進めました。せっかく政府がスタイル定義を提供しているので、そのまま使えるのが筋だろうと考えたわけです。XSL → HTML 変換を WeasyPrint に食わせて PDF にする…という素直な経路を試したのですが、見事に崩れました。

  • 外枠の罫線が見切れる
  • <pre> で表示されるはずの欄が改行されず横に流れる
  • 印字ピッチが画面用に決め打ちされていて、印刷時に変

原因はおそらく、官公庁の XSL が 古い IE + 特定の表示環境の想定 で書かれていて、モダンなレンダラの組版とは相性が悪いんだと思います。

対策: ここで方針を切り替え、XSL を完全に無視して、XML を直接読んで自前の HTML/CSS でレイアウトを再構築する ことにしました。元設計をリスペクトする気持ちは美しいのですが、「読みやすい A4 PDF を吐く」という目的に対しては、ゼロから組み直すほうが速くて確実でした。これなら見た目を完全に制御でき、A4 縦 1 枚に確実に収められます。

2. ZIP のファイル名が cp932 で入っている

e-Tax / 年金機構の ZIP は、内部のファイル名が Shift-JIS (cp932) でエンコードされているのに UTF-8 フラグが立っていません。Python 標準の zipfile でそのまま展開すると、ファイル名が文字化けして XML が見つからなくなります。

対策: 展開時に明示的に encode("cp437").decode("cp932") で復号してます。

with zipfile.ZipFile(zip_path) as zf:
    for info in zf.infolist():
        try:
            raw = info.filename.encode("cp437")
            info.filename = raw.decode("cp932")
        except (UnicodeEncodeError, UnicodeDecodeError):
            pass
        zf.extract(info, dest)

3. 金額フィールドの円記号エスケープ

XML の中で金額が \26,575 のように バックスラッシュ + 数字 で入っています。これは cp932 の円記号 (¥) がバックスラッシュにマップされた名残で、エスケープ復元が必要です。

total = record.findtext("total").replace("\\", "¥")  # → "¥26,575"

4. 和暦アルファベット形式 (R080226)

増減内訳書では日付フィールドが R080226 のような 元号 1 文字 + YYMMDD 形式で入ってきます。これを 令和8年2月26日 に変換するヘルパーを書きました。

_GENGOU = {"M": "明治", "T": "大正", "S": "昭和", "H": "平成", "R": "令和"}

def format_wareki_ymd(s: str) -> str:
    if len(s) != 7 or s[0] not in _GENGOU:
        return s
    y, m, d = int(s[1:3]), int(s[3:5]), int(s[5:7])
    return f"{_GENGOU[s[0]]}{y}{m}{d}日"

おまけ: 鑑型 + 添付バイナリ (DTA)

被保険者データ送付通知のような ZIP は構造が違っていて、鑑(送付状) XML 1 個 + バイナリの .DTA ファイル が入っています。DTA は CRLF 区切りで、

  • 1 行目: ヘッダ(CSV ではない位置情報)
  • 2 行目以降: cp932 + CSV (1 レコード = 被保険者 1 名)
  • 最後の数行: バイナリの電子署名(%F で始まる)

というハイブリッド形式。これも自前パーサで処理しています。

このあたり、設計判断と既知の制約は doc/etax-design.md に詳しく書いてあります。

Claude Code で書いた話(技術受託のヒント)

このツールはほぼ全部、Claude Code (Anthropic の CLI コーディングエージェント) との対話で書きました。サムライアプスでは AI 導入支援や受託開発をしているので、自社プロダクトを AI と一緒に作るのは、いわば日々のドッグフーディングです。

書きながら気付いた、AI と協働するときのコツを 3 つ紹介します。

設計ドキュメントを先に書く

実装に入る前に doc/etax-design.md を書いて、なぜ XSL を捨てるのか / cp932 をどう処理するのか / 新書類を追加する手順は何か、を明文化してから手を動かしました。

設計ドキュメントは「未来の自分」と「Claude」の両方への申し送り書になります。次のセッションで Claude にこの文書を読ませると、文脈の再構築が一瞬で終わって、開発スピードが大きく変わります。

動くものを作ってから、レジストリで整える

最初の実装は 1500 行の単一ファイル etax.py でした。動くようになってから、後付けで以下のようにリファクタしました。

src/office_toolkit/
├── etax.py               # オーケストレーション層 (160行)
└── notices/              # 書類タイプ別モジュール
    ├── __init__.py       # NoticeHandler + REGISTRY (ディスパッチ)
    ├── _common.py
    ├── _css.py
    ├── hokenryou.py      # 保険料納入告知額・領収済額通知書
    ├── shakai.py         # 社会保険料額情報
    ├── zougen.py         # 保険料増減内訳書
    └── kagami.py         # 鑑型

新書類を追加するときは「notices/<新書類>.py を作って、REGISTRY に 1 行足す」だけ。既存ファイルの変更がほぼ不要な構造です。

最初からこの構造を狙って書こうとすると、書類が 1 種類しかないうちは過剰設計になります。動くものを作ってから、種類が増えてきたタイミングで構造化する のが、AI 協働でも変わらない原則です。

テストデータを匿名化する仕組み

実物の e-Tax XML には法人番号・氏名・住所・金額といった機微情報が入っています。これをそのまま OSS リポジトリにコミットするわけにはいきません。

そこで tests/_synthetic.py架空の XML を動的に組み立てる 仕組みを作りました。

  • 法人名 = 「サンプル株式会社」
  • 法人番号 = 「00000000」
  • 個人名 = 「山田太郎」 / 「鈴木花子」
  • 金額・日付 = 適当なキリ番

これらから ZIP を組み立てて pytest のフィクスチャとして提供します。これで CI でフルにパース・レンダリング・PDF 生成までテストでき、かつ機微情報は一切公開リポジトリに乗りません。

@pytest.fixture(scope="session")
def hokenryou_zip(tmp_path_factory) -> Path:
    out = tmp_path_factory.mktemp("synthetic-hokenryou") / "hokenryou.zip"
    return _synthetic.build_hokenryou_zip(out)

OSS で「実データを使うテストがあるけど、データは公開できない」という場面で、よく使えるパターンだと思います。

使い方

git clone して pip install -e . するだけです。

git clone https://github.com/samurai-apps/office-toolkit.git
cd office-toolkit
python3 -m venv .venv
.venv/bin/pip install -e .

.venv/bin/office-toolkit etax --input path/to/notification.zip
# → 保険料納入告知_令和8年4月分_<元stem>.pdf

ライブラリとしても使えます。

from pathlib import Path
from office_toolkit.etax import convert
convert(Path("notification.zip"), output_pdf=Path("out.pdf"))

PyPI への登録は様子を見て検討します。要望があればぜひ Issue で。

OSS にした理由

サムライアプスの本業は AI 導入支援とソフトウェア受託開発で、このツール自体で大きく収益化を狙うつもりはありません。それでも MIT ライセンスで公開した理由は単純で、

  • 同じことで困っている個人事業主・税理士の役に立てば嬉しい
  • 「中小企業の IT 活用」という弊社のスコープと、目に見える形で整合する
  • AI で実務問題を高速に解決する具体例として、当社の技術姿勢を示せる

の 3 点です。

弊社のスローガン「中小企業の IT 化を支援する」というのは、抽象的な話ではなく、こういう「e-Tax のあるある」を地味に解決していく積み重ねだと思っています。富の集中ではなく、各事業者の能動性が増えていく方向の小さな貢献ができれば良いなと。

なお、社内では普段の業務上やはり GUI のほうが便利な場面が多く、このツールを呼び出す GUI バージョンも作っており、いずれ商用サービスとしての提供も検討しています(時期は未定です)。

技術的なフィードバック、新書類対応のご要望、PR、いずれも歓迎です。

余談: 同じような実務問題、お仕事として承ります

「うちの会社にも、政府系の文書やら基幹システムからのレガシーフォーマットやらで似たような困りごとがある」というケースは、たぶん多いと思います。

サムライアプスでは、AI を活用した業務効率化ツールの内製支援・受託開発をしています。今回の office-toolkit のように、「困りごとを聞いて、数日〜数週間で動くプロトタイプを出す」というスタイルです。

ご相談があれば お問い合わせフォーム からどうぞ。

これにて御免!