• #tax-assistant
  • #PDF変換
  • #画像表示
  • #マルチクライアント
  • #PyMuPDF
  • #Claude Code
開発tax-assistantメモ

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に連結

処理フロー

  1. PDFファイルを開く
  2. 各ページをPixmap(PNG)として取得
  3. PillowのImageオブジェクトに変換
  4. 全ページを1枚の画像に連結
  5. 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.tsgetImageUrl()ヘルパーを追加
  • 7箇所のコンポーネントで画像URL生成をgetImageUrl()に統一
  • dependencies.pyget_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_categoryaccount_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で構造が乖離する。

関連