1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

家計簿のネイティブアプリを作ろうとしたら、Geminiに『LINE Botで十分』と諭された話

1
Last updated at Posted at 2026-03-02

📢 2026.05.13 追記: この記事をちょこちょこ見ていただけているようなので、リポジトリを公開しました。
https://github.com/Oakums/receipt-reader-public
リファクタリングを行いGASにパラメータを持たせる形に変更しています。

1. はじめに

既存の家計簿アプリでは痒い所に手が届かず、年末休暇を利用して「わが家専用」の精算システムを構築しました。解決したかったのは以下の3点です。

  • 手計算の排除
    レシートから個人分を抜き出す作業の自動化。
  • 転記の自動化
    スプレッドシート(GSS)への自動入力。
  • 運用負荷の軽減
    LINEで写真を送るだけで完結させる。

当初はネイティブアプリを作る気満々でGeminiに相談したのですが、そこで思わぬ 「逆提案」 を食らいました。
Gemini:「UIの実装やストア公開は面倒ですよ。LINE Bot + GAS + Gemini APIの構成なら、今すぐ無料で実用的なものが作れます。それで十分じゃないですか?」

正直、意表を突かれましたが、AIに開発の方向性を論破されるのも面白いなと思い、その提案に乗っかることにしました。

2. システム構成と開発環境

「サクッと作る」とはいえ、後で自分が苦しまないように開発環境はモダンに整えました。GASのエディタは使わず、VSCodeでガシガシ書いています。

設計やプロンプトの叩き台は Gemini 1.5 Pro と対話し、実際のコーディングやユニットテストの量産には VSCode + GitHub Copilot を活用しました。
「AIに設計を相談し、AIに実装を補完させる」というフローにより、年末年始の数日間で使えるくらいのものにはなりました。

3. AIとの責任分界点

AI(Gemini)には 「人間が読み取るのが面倒な情報の構造化」 に専念させ、計算などの 「決定的な算術処理」 はプログラム(GAS)側で行うよう責務を厳密に分離しました。

コンポーネント 役割 (Responsibility) 設計のポイント
Gemini API 画像 → JSONへの構造化 OCR結果から「品目」「単価」「割引」を抽出。
GAS (Logic層) ビジネスロジック (案分) 抽出データから「特定商品の除外」や「割引の案分」を算術計算。
Google Sheets 永続化層 (Database) LockService を活用し、同時実行時の競合(Race Condition)を防止

4. 実装のキモ-共同作業-

AIには「レシートの各行に番号を振ったJSON」を返させ、ユーザーが特定の番号を指定して「このお酒分だけ引いておいて」とGAS側に計算させる仕組みにしました。
これにより機能追加をプロンプト(ガチャ)ではなく関数で実装できるようにしました。

プロンプトの工夫(System Instruction)

Geminiには、計算をさせず「抽出」に徹するように以下のニュアンスで指示を出しています。

「レシート画像を解析し、各商品に1から順に番号を振ったリストを作成してください。また、レシート全体の『小計』『割引額』も個別に抽出してJSON形式で返してください。計算は不要です。」

案分計算のロジック(抜粋)

全体の割引額(クーポンなど)を考慮し、特定の商品を除外した際の実質価格を算出するロジックです。ここはハルシネーションを防ぐため、敢えてGAS側でガチガチに計算しています。

/**
 * 商品価格から、全体割引の比率を考慮して実質価格を算出する
 * 外部APIに依存しない純粋関数のため、Jestでのテスト対象とする
 */
export function calculateActualPrice(itemPrice: number, subtotal: number, totalDiscount: number): number {
    if (subtotal <= 0) return itemPrice;
    
    // 割引率を算出して適用(端数は四捨五入)
    const discountAmount = Math.round(itemPrice * (totalDiscount / subtotal));
    return itemPrice - discountAmount;
}

5. 今後の課題:あえて「完璧」を目指さない

実際に運用してみると、いくつか技術的な限界も見えてきました。

  • 重複登録防止の精度
    LockService.getScriptLock().waitLock(30000) を使用していますが、GSS側の反映遅延により稀に動作が不安定になることがあります。
  • 例外フォーマットへの対応
    店舗ごとに異なるレシートレイアウトや、税率混在(8%/10%)への対応はプロンプトの調整が必要です。

「仕様書をガチガチに固めて、一度作ったら不変!」みたいな堅苦しい開発は、もうやめました。
家族の要望が変わったら、またGeminiとあーだこーだ言いながらコードを書き換える。
そんな、AIと「二人三脚でゆるく育てていく」くらいのスタンスが、個人開発にはちょうどいいみたいです。

6. 結び

導入後、レシートの写真を撮って送るだけでよくなり、レシートはその場で捨てられるようになりました。
「もしバグが出て困るなら、またGeminiと対話してその場でロジックを直してもらえばいい」という緩いスタンスで、今後も「わが家専用」をアップデートし続けていきます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?