コンテンツにスキップ

車両コスト・OCR・収支(現行仕様)

読む順: この 1 本で全体像がつかめます。操作手順は operations-manual/07-vehicle-cost.md、 機械が守る不変条件は AGENTS.md の該当節です。同じことは二度書きません。

最終更新: 2026-08-04(mig129〜134/173/186 と現行 OCR・P&L を反映)

この図が答える問い

OCRパイプライン

この図は、請求書・領収書を撮影してレビューし、車両が曖昧なときは誤った車両へ紐付けず止める流れを示します。

全画面で開く

OCRパイプラインの流れ。

P&Lの集計

この図は、二次搬出の収入と車両費用を日付・車両ラベルで束ね、車両未設定の費用を共通枠へ混ぜず残す流れを示します。

全画面で開く

P&Lの集計フロー。


1. 何を扱うか

車両にかかった費用vehicle_cost_entries)と、車両ごとの運行実績vehicle_daily_logs・走行距離 / 給油量)を集め、二次搬出の収入と突き合わせて 収支(P&L)を出します。

  • 費用 1 件 = 車両 × 日付 × 費目 × 税込金額。任意で税額・インボイス登録番号 (T+13 桁手入力のみ、OCR では自動抽出しない)・メモ・原本画像明細行を持つ
  • 費目は 8 種: 燃料費 / メンテ費 / 車検 / 保険 / 税 / タイヤ / ETC / その他
  • 車両なしの共通費vehicle_id が NULL)は請求書の合計表 1 行だけに使う特例で、 単票や領収書を車両未選択のまま保存する経路はない

2. 入力は 1 画面 — OCR も手入力も同じ場所

入力はコスト入力/collector/fleet/costs/new)に集約されています (旧取込ページは廃止済みで、URL はここへリダイレクトされます)。

  1. 請求書(PDF / 画像)や領収書(スマホで複数枚撮影可)を放り込むと、OCR が 店名・日付・金額・税額・費目・明細行を抽出してカードを作る
  2. 必ず人がカードをレビューして保存する(OCR の結果を無確認で登録する経路はない)。 低信頼の項目はハイライトされ、領収書は車両を選ぶまで保存できない
  3. OCR を使わない手入力も同じ画面

レビューを助ける自動化が 3 つ入っています。

  • 車番照合: 請求書に印字された車番を、表記ゆれ(全角半角・かな・空白・ハイフン)を 正規化して自社車両へ照合する。曖昧なら未照合に倒す(誤った車両に付けるより空欄がよい)
  • 実績費目提案: 同じ支払先の直近の登録から最頻の費目を初期値にする(OCR の推測より優先)
  • 請求書の 1 枚目分類: 燃料カード会社の「合計表 + 車両別明細」形式は 1 枚目で判定し、 合計表なら残りページを OCR せず共通費 1 行に変換する(重複計上とコストの節約)

3. OCR の仕組みと制約

  • パイプラインは Google Vision(文字抽出)→ Claude 安価モデル → 確信が低ければ上位モデルへ エスカレーション。モデル ID は API の設定(wrangler.toml)が権威
  • 1 テナント 1 日 200 回の共有上限がある。マニフェスト OCR と車両コスト OCR は 同じ日次バジェットを消費する(Vision / Claude 課金への防波堤)
  • 処理はブラウザから 1 枚ずつ直列で送る(サーバー側のキューは作らない)。上限超過(429)で 残りを打ち切り、個別の失敗は続行して失敗カードとして残す
  • 請求書 PDF は先頭 30 ページまで。明細行は 1 票あたり 40 行まで

4. 明細行 — 親の金額が権威

明細行(vehicle_cost_line_items)は内訳のメモであり、親の税込金額が常に権威です。

  • Σ(明細) と親金額の乖離は保存をブロックしない(値引き行・端数・税があるため)。 画面は参考として差額を表示するだけ
  • 明細の編集は専用 RPC 経由で丸ごと差し替える。0 行にもできる

5. 集計と収支 — コスト集計ダッシュボード

/collector/fleet/costs に 4 つのタブがあります。

タブ 見えるもの 権限
集計 費目別・工場別・車両別の合計、月次推移、燃費(距離 ÷ 給油量) staff
運行ログ明細 日々の走行距離・給油の明細 staff
コスト明細 費用 1 件ずつ(検索・ページング・CSV・編集・削除) staff
収入 / P&L 二次搬出・大型運搬の収入 − 車両費用 admin のみ

P&L の考え方:

  • 収入 = 完了した二次搬出の固定運搬費 + 大型運搬の量×単価。通常回収の売上は含まない
  • 計上日は配車した日ではなく実際に完了した日(日本時間)
  • 範囲は「全体」と「二次搬出専属車のみ」を切り替えられる。切り替わるのは費用側だけで、 収入の定義は変わらない
  • 運搬費が未登録の完了便は警告件数として出る(黙って 0 円にしない)
  • 内訳を掘れる: 日次 P&L 表の日付を押した日だけ内訳が開きます(既定は閉じています)。収入は 1 便ごと(未登録の便も表示)、費用は 1 件ごと + 明細行まで。 修繕費は既定で展開される。内訳の合計は P&L の表示額と必ず一致する (同じ抽出条件を写しており、proof で突き合わせている)

6. 消えない・壊れない

  • 削除は復元できる(soft-delete)。物理削除の経路はなく、削除済みは一覧から消えるが admin の操作で戻せる
  • 原本画像は非公開バケットにテナント別で保存し、短命の署名 URL で見る。 アップロード失敗は警告に留め、費用本体の登録は妨げない
  • 同じ車両 × 日付 × 金額の再登録はソフト警告だけ出す。正当な同額反復(同じ日の 2 回給油) を DB 制約で弾かない
  • 金額列は NaN / ±Infinity を DB の CHECK で拒否する(1 件の壊れた値が月次合計を NaN にするのを防ぐ)

7. 気をつける点(実際に起きた不具合)

存在しないモデル ID の設定で本番 OCR が全停止しました。 設定値のタイプミスは単体テストの モックでは検出できず、実機の tail 観測で発見しています。モデル切替はステージングで疎通を 先に確認します(OpenAI 系モデルへの切替も API 仕様差で 400 → ロールバックの前例あり)。

明細を編集した後、一覧側の表示が古いまま残っていました。 明細の保存は単票と一覧の 両方のキャッシュを無効化する契約になっています。

コスト明細の一覧は上限打ち切りで欠けていました。 1 ページ 50 件 × 明細 40 行で 1000 行の 既定上限に達し得ます。全件取得は共有のページングヘルパー + 全順序キーで行います。

「車両が判らない日はテナント全体の ETC を拾う」という取り違えが運行日報でありました。 費用のクエリは車両未指定だと絞り込まないため、集計側で必ず車両を確定してから使います。


8. 詳細はどこを見るか

知りたいこと 参照先
機械が守る不変条件 AGENTS.md の「Cost Receipt OCR」「Cost Input Integrated OCR P1」「Vehicle Cost Production MVP」「Vehicle Cost Next Wave」「Adhoc Secondary-Transport Dispatch」(P&L)
操作手順 operations-manual/07-vehicle-cost.md / 06-secondary-transport-pnl.md
二次搬出(収入側の業務) dispatch-secondary-transport.md
運搬費マスタ masters.md
認可の境界(P&L が admin 限定の理由) auth-permissions.md
設計時の検討経緯 superpowers/specs/書き捨て・現行ではない

9. コスト OCR レビューキュー(現行)

コスト画面は 集計 / 運行ログ / コスト明細の3タブ(admin は収入 / P&L を加えた4タブ)を持ち、OCR item のレビューキューは業務月の絞り込みと同じ月スコープを共有します。キューは画面遷移や月変更で消えず、未処理 item を永続化します。

OCR item は 待機中 / 解析中 / 要確認 / 確認待ち / 確定済み / 破棄 / 解析失敗の7状態です。原本、車両、費目、日付、金額、税額、仕入先、インボイス登録番号、明細行を確認し、状態に応じて 再解析 / 保存 / 確定 / 破棄を行います。

確定は同じ item がすでに確定済み、または別の費用明細へリンク済みなら停止します。二重確定ガードを画面だけに置かず、RPC と DB の終端状態でも検証します。請求書の合計表は車両なしの共通費として保存できますが、車両必須の領収書を共通費へ逃がすことはできません。

入力経路は2本です。

  1. コスト入力で手入力または OCR カードをその場で確認し、複数行を一括保存する。
  2. OCR item をレビューキューへ送り、月スコープ内で再解析・保存してから1件ずつ確定する。

どちらの経路も、確定前の item を車両コスト集計へ混ぜません。失敗・取得不能は 0 円や空行に変換せず、再試行または手入力へ戻します。