確定申告書PDFの作成機能を追加

確定申告書(第一表・第二表、令和5年分)のPDF出力に対応した。先日の青色申告決算書と同じく、国税庁の様式PDFに帳簿の数字を重ねて出力する方式。今回はこの作業のほとんどをOpus 4.8に任せてみたら、拍子抜けするくらい素直にできた。その進め方が面白かったので記録しておく。

決算書と同じ「重ねる」方式

やることは前回と変わらない。様式を自前で描くのではなく、国税庁配布のPDFをPNG化して背景に敷き、数字と○だけをその上に重ねる。そのために様式の各欄が「どの座標にあるか」を知る必要がある、というのも同じ。

ラッキーだったこと — Claudeの「文字の位置が取れるかもしれません」

座標の大半は、前回の決算書と同じ抽出ツールで機械的に取れた。金額のマス目、区分の小欄、種類の「青色」みたいな破線の○印まで、様式PDFの罫線や矩形として拾える。ここは既定路線で、デザインソフトを持ち出すまでもなかった。

問題は、ごく一部の「印字された文字そのものを○で囲む/脇に書く」欄だった。表題の「所得税及び復興特別所得税の◯◯申告書」の空白に「確定」と書く欄、電話番号の「自宅・勤務先・携帯」を○で囲む欄、還付口座の「本店・支店/出張所/本所・支所」を○で囲む欄。これらは丸で囲む対象が印字済みの文字そのもので、枠も破線円もない。だから罫線・矩形からは位置が取れず、普通なら定規仕事になる。わたしは「座標はこちらのデザインソフトで起こすこともできるから、指示して」と伝えて、ここは手で測って渡すつもりでいた。

ところが第一表の残りの欄を埋める段になって、Claudeがこんなメッセージを出してきた。

第一表を固めます。塞ぐ穴は「確定」欄・電話番号区分・本店支店の別の3つで、いずれも○や文字を置く位置が要ります。

ひとつ可能性に気づきました。この確定申告書PDFは pdftotext でテキストが読めています(決算書PDFはアウトライン化されていて読めませんでした)。文字の位置が取れるかもしれません。

これが効いた。セッションのいちばん最初、どんな欄があるかを把握するために様式PDFをpdftotextにかけていて、そのとき日本語の項目名がきれいなテキストで出てきていた。決算書のPDFでは何も出てこなかったのに。その最初の何気ない一手を、Claudeはここで思い出したわけだ。実際にpdfplumberに通したら、「自宅」「勤務先」「携帯」の一文字ずつの座標までそのまま取れて、手で測るつもりだった最後の数欄がまるごと要らなくなった。

こちらが工夫したというより、たまたま国税庁のPDFがそういう作りだった、というだけの話だ。もし最初にPNG画像だけ見て進めていたら、この幸運には気づかず、○で囲む欄を一つずつ定規で測っていたと思う。

Opusに任せた進め方

最初に「 https://www.nta.go.jp/taxes/shiraberu/shinkoku/yoshiki/01/shinkokusho/pdf/r05/01.pdf https://www.nta.go.jp/taxes/shiraberu/shinkoku/tebiki/2023/pdf/001.pdf の中を見て、必要な要素を抽出して」とだけ投げた。そこから先はほぼOpusが方針を立てて進めていった。ざっとこういう流れ。

  1. 様式PDFから座標(罫線・マス目・矩形)をJSONに抽出する
  2. 「どの座標がどの欄か」の対応表を作り、そこからPHPのオーバーレイ定義を機械生成する
  3. 全欄をテスト値で埋めた校正PDFを出し、様式画像と見比べて欄の取り違えを目視で潰す
  4. カタログ(欄の一覧)とオーバーレイ(座標)が過不足なく一致することをテストで固定する

特に良かったのが3と4。欄を1つ取り違えても、校正PDFを見れば「この数字が違う欄に乗っている」と一目で分かる。実際この工程で、自分では気づかないような取りこぼしが何件も見つかった。扶養控除の区分欄の見落とし、預金種類の○が実は5つあること、そして表題の「所得税及び復興特別所得税の◯◯申告書」の空白に「確定」と書く欄がまるごと抜けていたこと。

「確定」と書く欄については、手引きPDFを渡したら「手引きにこう書いてあるので、この欄が要る」と拾ってきた。自分は様式の枠だけ見ていて完全に見落としていた欄だった。

これはロジックじゃない

このスクリプトの中身は、正直ほとんど読んでいない。でもそれでいいと思っている。

座標を起こす作業は、business logicではない。「この矩形はどの欄か」を突き合わせて、できあがったPDFを目で見て正しいか確かめる、という作業だ。だから成果物さえ目視で検証できれば、ソースの中身は任せてしまってよい。実際、校正PDFを何枚か見て微調整を頼むだけで、第一表135欄・第二表242欄がきれいに収まった。

逆に、帳簿の集計や税額の計算みたいな「間違っていても一見それらしく動いてしまう」ロジックは、こうはいかない。そこは自分でコードを読んで納得する必要がある。今回のPDF生成は、帳簿とはまだ繋いでいない。欄キー => 印字する文字列の配列を渡すとPDFが出るだけの、純粋な「様式を埋める」機能として独立させてある。帳簿との連携はこの後、別で繋ぐ。この切り分けができたから、中身を任せる判断もしやすかった。

コミットするのはPHPの定義だけ

前回と方針は同じ。様式の元PDF、PNG化した背景画像、座標抽出スクリプトとその出力JSONは、どれもリポジトリに入れていない。背景画像は再配布していいか著作権の判断がつかないため、抽出ツールはコードレビューしておらず出力の妥当性しか確認していないため。「把握・許諾しきれていないものは公開しない」という同じ基準で外している。

コミットするのは、座標を持つPHPのオーバーレイ定義と、欄の一覧(カタログ)だけ。これは自分で読んで中身を把握しているコードだ。……というより、ここが「把握しておくべき境界」なんだと思う。中身を任せる部分と、自分が握る部分。今回はその線をどこに引くかが、わりときれいに見えた作業だった。

令和5年分だけ通したので、次は令和6年・令和7年分。年によって欄番号がずれる(令和6年は定額減税、令和7年は特定親族特別控除が挟まる)が、欄の意味は変わらないので、ずれる番号だけを年度ごとに差し替える構造にしてある。ここも同じ進め方でいけるはず。