😽

【AI駆動開発】複雑な税金計算ロジックを、LLMを使って「実装ゼロ」で構築した話

に公開

はじめに

手取り額シミュレーションサービス「手取りくん」をリリースしました。

https://tedorikun.com/

額面を入力するだけで、所得税・住民税・社会保険料などを瞬時に計算し、手取り額を可視化するツールです。コアとなる計算ロジック(⁠calculator.ts)は約1,100行に及びますが、実は私自身はほとんどコーディングしていません。
今回は、 「人間がアーキテクチャと型定義を決め、複雑な実装はすべてLLMに任せる」 というAI駆動のアプローチで、どのように日本の複雑な税制をコードに落とし込んだかを解説します。

課題:複雑なドメインロジックをどう攻略するか

手取り計算の実装には、以下の複雑さが伴います。

  • 分岐の多さ: フリーランス/会社員/副業という異なるコンテキスト
  • 計算の面倒さ: 累進課税、控除額の計算など
  • 定数の多さ: 年収に応じた税率テーブルなど

これらを人間が手書きするのはバグの温床ですし、何より苦痛です。そこで、私は 「Tech Leadとして設計指示だけ出し、Junior Dev(LLM)に実装させる」 というスタンスで開発を進めました。

1. 設計戦略:AIが扱いやすいアーキテクチャ

LLMに正確なコードを書かせるためには、AIがコンテキストを見失わないような設計が必要です。
そのため、以下のアーキテクチャを採用しました。

  • 純粋関数(Pure Function): 状態を持たせないことで、入力に対する出力の検証を容易にする。
  • テーブル駆動: 計算ロジックと定数(税率)を分離する。
  • コンテキスト分離: 「フリーランス」と「会社員」を混ぜない。

2. 人間の仕事:型定義(Interface)の設計

私が唯一入念に行ったのは、TypeScriptによる型定義です。
これがAIへの「仕様書」になります。
特にこだわったのは、計算に必要なパラメータの構造化です。
以下のように、フリーランスと会社員で型を明確に分けることで、AIに対して「このケースではこのプロパティしか使わない」と強制力を働かせました。

// 1. フリーランス・副業用
// 「副業かどうか」のフラグを持たせ、AIに分岐を意識させる
interface CalculateRequest {
  contractType: 'monthly' | 'hourly';
  isSideBusiness: boolean; 
  mainJobAnnualIncome?: number; // 副業時のみ必須
  // ...
}

// 2. 会社員用
// 構造が全く異なるため、別の型として定義
interface SalaryCalculateRequest {
  annualIncome: number;
  bonus?: number;
  employmentType: 'regular' | 'contract';
  dependents?: number;
}

この型定義ファイルをLLMに渡し、「このInterfaceを満たす計算関数 ⁠calculateTax を実装して」と指示するだけで、整合性の取れたロジックが生成されます。

3. 実装プロセス:アルゴリズムの指示

具体的な計算処理の実装も、コードを書くのではなく 「方針」 を指示しました。

累進課税の実装

例えば所得税の計算。
「日本の所得税の速算表を使って実装して。if文の羅列ではなく、配列で定義したテーブルを線形探索する方式で書いて」と指示しました。
すると、LLMは以下のような「テーブル駆動」のコードを生成してくれます。

// AIが生成したテーブル(数値も概ね正確でした)
const INCOME_TAX_BRACKETS = [
  { threshold: 1950000, rate: 0.05, deduction: 0 },
  { threshold: 3300000, rate: 0.1, deduction: 97500 },
  // ...
];

// AIが生成したロジック
function calculateIncomeTax(taxableIncome: number): number {
  for (const bracket of INCOME_TAX_BRACKETS) {
    if (taxableIncome <= bracket.threshold) {
      return Math.floor(taxableIncome * bracket.rate - bracket.deduction);
    }
  }
  // ...
}

人間が行ったのは、生成された税率テーブルの数値が国税庁のサイトと合っているかのダブルチェックだけです。

簡易シミュレーターとしての割り切り

社会保険料(標準報酬月額)については、厳密に実装しようとすると都道府県別のデータが必要になり、LLMのトークン数も圧迫します。
そこで、「ここはシミュレーターだから、年収に対する固定料率(約15%など)での簡易計算でいいよ」と指示を与え、実装コストを大幅に下げました。
このように、 「どこを厳密にし、どこを妥協するか」 という意思決定こそが、AI時代の人間の役割だと感じました。

4. 成果物

結果として、⁠frontend/src/lib/calculator.ts という1,100行超のファイルができあがりましたが、私が手打ちした行数は数えるほどです。

  • 開発スピード: 圧倒的(体感で10倍以上)
  • 品質: 型定義がしっかりしていれば、ロジックの破綻はほぼ起きない
  • 保守性: 税率が変わっても、LLMに「定数テーブルを更新して」と言うか、そこだけ手動で直せば良い

まとめ:コードを書かない開発は、想像以上に楽しかった

今回の開発を通じて、正直なところ 「プログラミングの楽しさの質が変わった」 と感じました。
これまでは、複雑なロジックを腕力で実装しきることに達成感を感じていました。しかし、今回は 「自分の脳内にある設計図(型定義)を渡すと、動くコードが返ってくる」 という、まるで優秀な部下と阿吽の呼吸で開発しているような新しい快感がありました。
もちろん、AIが書いてきた1,000行のコードをレビューするためには、これまでに培ったコーディング能力が不可欠でした。「書かなくていい」からこそ、コードを「読み解く力」や「バグの匂いを嗅ぎ分ける力」が試される場面も多く、エンジニアとしての地力が問われる面白さもありました。

Discussion