1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI使ってふるさと納税をお得に納めよう

Last updated at Posted at 2025-12-25

medibaアドベントカレンダーの17日目の記事です。

ふるさと納税の返礼品を選ぶとき、こんな悩みはありませんか?

  • どの返礼品が一番コスパが良いのか分からない
  • 同じカニでも寄付金額も量もバラバラで比較が大変
  • 年末が迫っているのに大量にある返礼品を調べる時間がない

そこで、AIに商品タイトルを読み込ませて自動で単価を計算させましょう

この記事でできること

  • 「カニ1kgあたり」「トイレットペーパー1ロールあたり」などの寄付金額に対する単価を比較

仕組み

1. 商品情報の準備

まず、ふるさと納税サイトから商品一覧を取得します。

注意: データ取得の際は各サイトの利用規約をしっかり読み、規約違反にならないように注意してください。ここでは例として商品タイトルが入ったJSONファイルを用意してます。

商品データの形式(JSON):

[
  {
    "id": 123456,
    "name": "生ズワイガニ ポーション 1kg",
    "amount": 28000,
    "capacity": "生ズワイガニ 1kg(正味重量)"
  },
  {
    "id": 123457,
    "name": "紅ズワイガニ 2kg",
    "amount": 30000,
    "capacity": "紅ズワイガニ 2kg(正味重量)"
  },
  {
    "id": 123458,
    "name": "タラバガニ 足 800g",
    "amount": 31000,
    "capacity": "タラバガニ 足 800g前後"
  }
]

2. AIに商品情報を投げて数量を抽出

商品名や内容量から「重さ(kg)」または「個数」をAIに抽出させます。

ポイント:

  • 食品(カニ、肉、魚)→ 重さ(kg)優先
  • 日用品(トイレットペーパー、ティッシュ)→ 個数優先
  • 定期便 → 配送回数も掛け算

プロンプトの工夫

プロンプト例:

商品リストから、各商品の「重さ(kg)」を抽出してJSON形式で返してください。

【抽出ルール】
■重さ優先ルール
- カニ、肉、魚、果物などの食品は「重さ(kg)」を優先して抽出
- 重さは必ずkg単位で統一(g表記はkgに換算)
- 「正味重量」「NET」がある場合はそちらを優先
- 「総重量」と「正味重量」が両方ある場合は「正味重量」を使用

■重さの例
- 「1kg」→ 1
- 「500g」→ 0.5
- 「総重量1kg(正味量800g)」→ 0.8(正味重量を優先)
- 「1.5kgセット」→ 1.5

■定期便ルール
- 定期便の場合は、配送回数も掛け算
- 「【全3回定期便】1kg」→ 3

【出力形式】
{
  "products": [
    {"id": 123456, "quantity": 1.5},
    {"id": 123457, "quantity": 2.0}
  ]
}

3. 単価を計算してソート

AIから返ってきた数量を使って単価を計算します。

$unitPrice = $amount / $quantity;

// 例:
// 寄付金額 30,000円 ÷ 2kg = 15,000円/kg(紅ズワイガニ)
// 寄付金額 28,000円 ÷ 1kg = 28,000円/kg(生ズワイガニ)
// 寄付金額 31,000円 ÷ 0.8kg = 38,750円/kg(タラバガニ)

実装例(ここではPHPとClaude API)

<?php

class FurusatoNouzeiAnalyzer
{
    private $apiKey;
    private $apiUrl = 'https://api.anthropic.com/v1/messages';

    public function __construct($apiKey)
    {
        $this->apiKey = $apiKey;
    }

    /**
     * AIを呼び出して商品の数量を抽出
     */
    private function extractQuantitiesWithAI($products)
    {
        $productsJson = json_encode($products, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

        $prompt = <<<PROMPT
以下の商品リストから、各商品の「重さ(kg)または個数」を抽出してJSON形式で返してください。

商品リスト:
{$productsJson}

【抽出ルール】
■重さ優先ルール
- カニ、肉、魚、果物などの食品は「重さ(kg)」を優先して抽出
- 重さは必ずkg単位で統一(g表記はkgに換算)
- 「正味重量」「NET」がある場合はそちらを優先

■個数ルール
- トイレットペーパー、ティッシュ、飲料など明確に個数がある商品は個数を抽出
- 「12ロール×6パック」→ 72

■定期便ルール
- 定期便の場合は、配送回数も掛け算してください

【出力形式】
{
  "products": [
    {"id": 123456, "quantity": 1.5},
    {"id": 123457, "quantity": 72}
  ]
}

JSON以外の説明文は不要です。JSONのみを返してください
PROMPT;

        $data = [
            'model' => 'claude-3-5-sonnet-20241022',
            'max_tokens' => 4096,
            'messages' => [
                [
                    'role' => 'user',
                    'content' => $prompt
                ]
            ]
        ];

        $ch = curl_init($this->apiUrl);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'x-api-key: ' . $this->apiKey,
            'anthropic-version: 2023-06-01'
        ]);

        $response = curl_exec($ch);
        curl_close($ch);

        $result = json_decode($response, true);

        if (isset($result['content'][0]['text'])) {
            $text = $result['content'][0]['text'];

            // JSONを抽出
            if (preg_match('/\{[\s\S]*"products"[\s\S]*\}/', $text, $matches)) {
                $jsonData = json_decode($matches[0], true);
                return $jsonData['products'] ?? [];
            }
        }

        return [];
    }

    /**
     * 商品を解析して単価を計算
     */
    public function analyze($products)
    {
        echo "AIで商品情報を解析中...\n";

        // AIで数量を抽出
        $quantities = $this->extractQuantitiesWithAI($products);

        // 数量をマップに変換
        $quantityMap = [];
        foreach ($quantities as $item) {
            $quantityMap[$item['id']] = $item['quantity'];
        }

        // 単価を計算
        $results = [];
        foreach ($products as $product) {
            if (isset($quantityMap[$product['id']]) && $quantityMap[$product['id']] > 0) {
                $quantity = $quantityMap[$product['id']];
                $unitPrice = $product['amount'] / $quantity;

                // 単位を判定(kg or 個)
                $unit = ($quantity < 20 && strpos($product['capacity'], 'kg') !== false) ? 'kg' : '個';

                $results[] = [
                    'name' => $product['name'],
                    'amount' => $product['amount'],
                    'quantity' => $quantity,
                    'unit' => $unit,
                    'unit_price' => $unitPrice
                ];
            }
        }

        // 単価でソート
        usort($results, function ($a, $b) {
            return $a['unit_price'] <=> $b['unit_price'];
        });

        return $results;
    }

    /**
     * 結果を表示
     */
    public function displayResults($results, $limit = 10)
    {
        echo "\n=== 単価が安い返礼品 TOP {$limit} ===\n\n";

        $count = min($limit, count($results));
        for ($i = 0; $i < $count; $i++) {
            $product = $results[$i];
            $quantityDisplay = $product['unit'] === 'kg'
                ? number_format($product['quantity'], 2)
                : $product['quantity'];

            echo sprintf(
                "%d位: %s\n" .
                "  寄付金額: ¥%s\n" .
                "  数量: %s %s\n" .
                "  単価: ¥%s/%s\n\n",
                $i + 1,
                $product['name'],
                number_format($product['amount']),
                $quantityDisplay,
                $product['unit'],
                number_format($product['unit_price'], 2),
                $product['unit']
            );
        }
    }
}

// 使用例
$apiKey = 'your-claude-api-key-here';
$analyzer = new FurusatoNouzeiAnalyzer($apiKey);

// 商品データ
$products = [
    [
        'id' => 1,
        'name' => '生ズワイガニ ポーション 1kg',
        'amount' => 28000,
        'capacity' => '生ズワイガニ 1kg(正味重量)'
    ],
    [
        'id' => 2,
        'name' => 'ボイルズワイガニ 足 1kg',
        'amount' => 20000,
        'capacity' => 'ボイルズワイガニ 足1kg'
    ],
    [
        'id' => 3,
        'name' => '紅ズワイガニ 2kg',
        'amount' => 30000,
        'capacity' => '紅ズワイガニ 2kg(正味重量)'
    ],
    [
        'id' => 4,
        'name' => 'ズワイガニ 500g',
        'amount' => 16000,
        'capacity' => 'ズワイガニ 500g(正味重量)'
    ],
    [
        'id' => 5,
        'name' => 'タラバガニ 足 800g',
        'amount' => 31000,
        'capacity' => 'タラバガニ 足 800g前後'
    ]
];

$results = $analyzer->analyze($products);
$analyzer->displayResults($results);

実行結果例

AIで商品情報を解析中...

=== 単価が安い返礼品 TOP 5 ===

1位: 紅ズワイガニ 2kg
  寄付金額: ¥30,000
  数量: 2.00 kg
  単価: ¥15,000.00/kg

2位: ボイルズワイガニ 足 1kg
  寄付金額: ¥20,000
  数量: 1.00 kg
  単価: ¥20,000.00/kg

3位: 生ズワイガニ ポーション 1kg
  寄付金額: ¥28,000
  数量: 1.00 kg
  単価: ¥28,000.00/kg

4位: ズワイガニ 500g
  寄付金額: ¥16,000
  数量: 0.50 kg
  単価: ¥32,000.00/kg

5位: タラバガニ 足 800g
  寄付金額: ¥31,000
  数量: 0.80 kg
  単価: ¥38,750.00/kg

どれを買えば良いか一目瞭然ですね(美味しいかは置いておいて)

バッチ処理で大量の商品を一気に分析

大量のデータから最安値を絞り込みたい場合はn百件ずつまとめてAIに投げることで、速度上がりお得になります

// 100件ずつ処理
$batchSize = 100;
$allResults = [];

for ($i = 0; $i < count($products); $i += $batchSize) {
    $batch = array_slice($products, $i, $batchSize);
    $quantities = $this->extractQuantitiesWithAI($batch);

    // ...
}

実際にかかるコスト感

  • ~300円程度 (Claude 3.5 Sonnet)

注意点

  • 各サイトの利用規約を確認し、規約違反にならないよう注意
  • 納税しすぎに注意

まとめ

ふるさと納税の期限は 12月31日 です
日常の中にAIを活用して時短とお得を求めることはエンジニアの利点でもあります
ぜひAIを使って時短かつお得に納税しましょう!

1
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?