tax-assistant: PDF複数ページ変換と画像表示修正
領収書の裏表をスキャンしたPDFを画像に変換する機能の追加と、マルチクライアント環境での画像表示404問題の修正を行った。合わせてCodexレビュースキルの分割とDBスキーマの不整合修正も対応した。
1. pdf_converter.py新規作成
領収書PDFの各ページを画像に変換し、1枚のJPEGに連結するpdf_converter.pyを新規作成した。
技術選定
- PyMuPDF(fitz): PDFの各ページをPNG画像としてレンダリング
- Pillow: 複数のPNG画像を1枚のJPEGに連結
処理フロー
- PDFファイルを開く
- 各ページをPixmap(PNG)として取得
- PillowのImageオブジェクトに変換
- 全ページを1枚の画像に連結
- JPEG形式で出力
Codex(GPT-5.3)レビュー
設計段階でCodexによるコードレビューを実施し、6件の指摘を反映した。型ヒントの追加、エラーハンドリングの強化、リソース解放のwith文統一など。テスト25件全パス。
2. 連結方向の修正(縦連結から横連結へ)
問題
当初は縦方向に連結していた。出力サイズは451 x 1012pxとなり、レシート画像が縦に長く縮小されて見づらかった。
解決
横連結に変更し、出力サイズを902 x 506pxに改善した。領収書の表裏が左右に並ぶため、1画面で裏表を比較しやすくなった。
# Before: 縦連結 - 451 x 1012px
def _concatenate_vertically(images: list[Image.Image]) -> Image.Image:
total_height = sum(img.height for img in images)
max_width = max(img.width for img in images)
result = Image.new("RGB", (max_width, total_height))
# ...
# After: 横連結 - 902 x 506px
def _concatenate_horizontally(images: list[Image.Image]) -> Image.Image:
total_width = sum(img.width for img in images)
max_height = max(img.height for img in images)
result = Image.new("RGB", (total_width, max_height))
# ...
テストも_concatenate_verticallyから_concatenate_horizontallyにリネームして更新。
3. 画像表示404問題の修正
マルチクライアント環境で、client_001以外のクライアントで画像が404になる問題を修正した。
原因
tax-assistantではクライアント識別にX-Client-IdHTTPヘッダーを使っている。しかし、<img>タグのsrc属性からHTTPリクエストが発行される際、カスタムHTTPヘッダーを付与する手段がない。
client_001はサーバー側のデフォルトフォールバックで偶然動作していたが、client_002やclient_003は正しいクライアントディレクトリを参照できず404になっていた。
解決策: クエリパラメータ方式
画像エンドポイントにクエリパラメータ?client_id=client_003を追加する方式に変更した。
// api.ts - 画像URL生成ヘルパー
export const getImageUrl = (
baseUrl: string,
imagePath: string,
clientId: string
): string => {
const url = new URL(`${baseUrl}/images/${imagePath}`)
url.searchParams.set("client_id", clientId)
return url.toString()
}
修正範囲
api.tsにgetImageUrl()ヘルパーを追加- 7箇所のコンポーネントで画像URL生成を
getImageUrl()に統一 dependencies.pyにget_client_paths_from_queryを追加(クエリパラメータからクライアントパスを解決)- 既存の
get_client_paths(ヘッダー方式)はAPI呼び出し用にそのまま残存
imgタグとHTTPヘッダーの制約
ブラウザの<img>タグは以下の制約がある:
src属性に指定されたURLへのGETリクエストにカスタムヘッダーを付与できない- Service Workerでインターセプトする方法もあるが、複雑すぎる
- Blob URLに変換する方法もあるが、キャッシュが効かなくなる
クエリパラメータ方式が最もシンプルで、ブラウザキャッシュも正常に機能する。
4. Codexレビュースキルの分割
既存の/codex-reviewスキルを用途別に2つに分割した。
/codex-review-doc(リネーム)
- 旧
/codex-reviewをリネーム - ドキュメント全体(Markdown等)のレビューに使用
- 対象ファイルを指定してCodexに投げる
/codex-review-uncommitted(新規)
- 未コミットの変更差分をレビューする新スキル
codex review --uncommittedコマンドを内部で実行- コミット前のコードレビューに活用
5. subagent_readsテーブルのカラム不足修正
問題
read_account_categoryとaccount_category_confidenceの2カラムが、SQLiteスキーマファイル(schema.sql)に未定義だった。
既存クライアントのDBにはマイグレーションで追加済みだったが、新規クライアントのDB作成時にschema.sqlから初期化するため、これらのカラムが存在せず500エラーが発生した。
解決
マイグレーション016を作成し、両カラムを追加。同時にschema.sqlにも反映して、新規DBと既存DBの両方で整合性を確保した。
-- migration_016_add_account_category_columns.sql
ALTER TABLE subagent_reads
ADD COLUMN read_account_category TEXT;
ALTER TABLE subagent_reads
ADD COLUMN account_category_confidence REAL;
学び
imgタグからHTTPヘッダーは送れない
マルチテナント環境で画像を配信する場合、<img>タグのsrcにはカスタムヘッダーを付与できない。テナント識別にはクエリパラメータを使うのが最もシンプルで実用的。
PDF連結方向は用途で決める
レシートの裏表確認には横連結(左右並び)が適している。縦連結だと個々のページが小さくなりすぎる。用途に応じて連結方向を選択すべき。
スキーマファイルとマイグレーションの二重管理
SQLiteでスキーマファイル(初期化用)とマイグレーション(更新用)を併用する場合、カラム追加時は両方に反映する必要がある。片方だけ更新すると、新規DB vs 既存DBで構造が乖離する。
関連
- tax-assistant リポジトリ(プライベート)
- PyMuPDF公式ドキュメント: https://pymupdf.readthedocs.io/
- Pillow公式ドキュメント: https://pillow.readthedocs.io/