MAS-013: 投資回収シミュレーション
⚠️ 2026-04-28 PR #397 で
calcEffectiveTaxRate_の均等割ロジックを変更: 旧仕様 (本 spec の本文記述) は「赤字年のみlocalMinimumAnnualを加算」だったが、税法上は均等割が赤字でも黒字でも常時発生する固定費であるため、PR #397 で黒字時も常時加算に変更済。本 spec 本文中の「赤字年のみ加算」「EBT ≤ 0 の場合のみ」等の記述は 2026-04-28 時点で古い (実装は税法準拠で常時加算)。本 spec 全体の追従更新は別 PR で予定。詳細は MAS-057 spec v2.1.1 注意事項 #20 (f) を参照。
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-013 |
| カテゴリ | FP&A・シミュレーション |
| Phase | P3 |
| 優先度 | ★ |
| 対象ファイル | 100_config/101_sys_config.js(DDL スキーマに 30_bud_investment_case を登録)000_infra/002_constants.js(MENU_DEFINITION にメニュー 1 項目追加)600_report/610_service_investment_analysis.js(新規・計算エンジン + 出力シート生成)CLAUDE.md(「DDL (setupAllSchemas) で管理されないタブ」リストに 67_report_investment_analysis を追記) |
| 前提案件 | なし(Constants.TAX_RATES と Utils.parseAmt / Utils.parseDateToYm が既に存在すれば独立着手可能) |
目的
新規事業・CAPEX・SaaS 新規導入等の投資案件ごとに、初期投資額と年次キャッシュフロー(売上・変動費・固定費・減価償却費)を入力として、NPV(正味現在価値)・IRR(内部収益率)・回収期間(Payback Period)・ROI(投資収益率)を自動計算し、投資判断の定量化を支援する。
- 試行錯誤型の分析ツールとして位置づけ、入力シート(
30_bud_investment_case)と出力シート(67_report_investment_analysis)の 2 枚構成で完結させる。 - 実効税率は
Constants.TAX_RATESの累進ブラケットを適用して年次ごとに動的に算出し、赤字年はlocalMinimumAnnual(地方税均等割)のみ加算する。 - 割引率(WACC / ハードルレート)はユーザーが案件ごとに入力する方式とし、自動算出はスコープ外とする。
- 本機能の出力シートは動的上書き型(DDL 管理外)で、シミュレーション実行ごとに
clearContents()→ 再書き込みされる。履歴保持・シナリオ比較は後続案件で別途検討する。
Human-in-the-Loop の原則に従い、計算結果は 22_bud_headcount や 41_trn_budget 等の予算シートへ自動反映しない。計算前提パラメータ(割引率・分析期間・残存価額・計算実行日時)を出力シートに明記し、再現性と監査可能性を担保する。
現在のコード
新機能のため「現在のコード」は存在しない。以下は実装時に参照する既存実装・データ構造。
Constants.TAX_RATES — 累進課税ブラケット定義
000_infra/002_constants.js L21-30。中小法人(資本金 1 億円以下)向けの実効税率ブラケットを保持する。本案件の実効税率算出はここから動的に取得する(数値の直書き禁止)。
// 000_infra/002_constants.js L21-30
TAX_RATES: {
brackets: [
{ upTo: 8000000, national: 0.165, local: 0.049 }, // 〜800万: 実効約 21.4%
{ upTo: Infinity, national: 0.256, local: 0.080 }, // 800万超: 実効約 33.6%
],
localMinimumAnnual: 70000, // 地方税均等割(年額) 都道府県2万+市町村5万=7万
foundingYear: 2025,
foundingMonth: 11,
},
| 参照プロパティ | 本案件での用途 |
|---|---|
brackets[0].national + brackets[0].local (= 0.214) | 課税所得 ≤ 800 万円 部分の実効税率 |
brackets[1].national + brackets[1].local (= 0.336) | 課税所得 800 万円超 部分の実効税率 |
brackets[0].upTo (= 8,000,000) | ブラケット境界値(加重平均計算の基準) |
localMinimumAnnual (= 70,000) | 赤字年(EBT ≤ 0)でも発生する最低税額 |
Utils.parseAmt / Utils.parseDateToYm / Utils.parseDateToYmd — 入力正規化
000_infra/004_utils.js のシグネチャは以下(本案件の入力シート読込に使用):
| 関数 | 引数型 | 戻り値 | 備考 |
|---|---|---|---|
parseAmt(val) | 数値 / カンマ区切り文字列 / 全角数字等 | number(パース不可なら 0) | 002_constants.js L191-198 |
parseDateToYm(val) | Date / 'YYYY-MM' / 'YYYY/MM' / 'YYYY年MM月' | 'YYYY-MM' 文字列、パース不可なら '' | 002_constants.js L92-99 |
parseDateToYmd(val) | Date / 'YYYY-MM-DD' / 'YYYY/MM/DD' 等 | 'YYYY-MM-DD' 文字列(日部分欠落時は -01 を付与)、パース不可なら '' | 002_constants.js L108-118 |
readSheetAsDtos_ / writeDtosToSheet_ — シート I/O 共通ヘルパー
200_data/202_repository.js L19-29・L38-59。本案件の 30_bud_investment_case 読込は readSheetAsDtos_ を流用することで、列番号ハードコードを完全に回避できる。出力シート 67_report_investment_analysis は DDL 管理外・2D 配列を setValues で一括書き込みするため、writeDtosToSheet_ ではなくシート直接操作(getRange().setValues())で十分。
Constants.MENU_DEFINITION と onOpen() — メニュー動的生成
000_infra/002_constants.jsL206-324 のMENU_DEFINITION配列にカテゴリ単位でメニュー項目を定義している。100_config/101_sys_config.jsL323-350 のonOpen()はMENU_DEFINITIONをループして動的にメニューを生成する。- 本機能は FP&A 系のため
📋 サイドバー: 📊 マート更新カテゴリ(L229-239)に追加する。既存のラベル「財務3表の更新」「📅 基準年月指定で更新」「プロジェクト別 採算」「📊 KPIダッシュボード再描画」「📸 前年度P/Lスナップショット」の並びに、新規項目を末尾追加する(MAS-012「🧮 人員計画シミュレーション」と同じ追加位置パターン)。
メニュー名の造語禁止: 上記の実在カテゴリ名 📋 サイドバー: 📊 マート更新 のみ使用する。📊 FP&Aツール 等の存在しないメニュー名をコードに書かない(失敗パターン #18-#20 対策)。
修正方針
アーキテクチャ
[メニュー: 📋 サイドバー: 📊 マート更新 > 📈 投資回収シミュレーション]
↓ onClick → runInvestmentAnalysis()
[30_bud_investment_case] ── readSheetAsDtos_() ──▶ [610_service_investment_analysis.js]
(入力: 案件・CF・割引率) │
├─▶ calcEffectiveTaxRate_(ebt)
│ Constants.TAX_RATES.brackets で累進税率を算出
│
├─▶ calcFcfYearly_(year, input)
│ FCF = (Rev - VC - FC - Dep) × (1 - taxRate) + Dep
│
├─▶ calcNpv_(fcfs, rate, residualValue, initialInvestment)
├─▶ calcIrr_(fcfs, initialInvestment) // 二分法
├─▶ calcPaybackPeriod_(fcfs, initialInvestment)
└─▶ calcRoi_(fcfs, initialInvestment)
↓
[67_report_investment_analysis]
clearContents → setValues (前提パラメータ + 明細表 + サマリ)
入力シート 30_bud_investment_case(新規・DDL 登録対象)
setupAllSchemas 管理下に登録する。100_config/101_sys_config.js のスキーマ定義テーブルに以下の列構成で追加する。
| 列 | ヘッダー名 | 型 | 必須 | 説明 |
|---|---|---|---|---|
| A | 有効フラグ | boolean | — | false / 'FALSE' の行は計算対象外 |
| B | 案件ID | string | ○ | 任意の案件識別子(例: INV_2026_NEWBIZ_A) |
| C | 案件名 | string | ○ | 表示用名称 |
| D | ステータス | string | — | 検討中 / 承認済 / 却下 等(UI バリデーションは任意) |
| E | 初期投資額 | number | ○ | 正の整数(単位: 円) |
| F | 投資実行年月 | string | ○ | 'YYYY-MM' 形式(Utils.parseDateToYm で正規化) |
| G | 分析期間(年) | number | ○ | 1〜5 の整数 |
| H | 割引率(%) | number | ○ | 年利換算のパーセント値(例: 8 → 0.08)。-100% 超のみ有効 |
| I | 残存価額 | number | — | 最終年の終了時に回収される非償却資産価値(単位: 円、未入力は 0) |
| J〜N | 売上_1年目〜売上_5年目 | number | — | 分析期間までの列を参照。未入力は 0 として扱う |
| O〜S | 変動費_1年目〜変動費_5年目 | number | — | 同上 |
| T〜X | 固定費_1年目〜固定費_5年目 | number | — | 同上 |
| Y〜AC | 減価償却費_1年目〜減価償却費_5年目 | number | — | 同上(税効果算出に使用) |
ヘッダー取得は必ず indexOf ベースで行う。列番号のハードコード禁止(CLAUDE.md 規約・失敗パターン #21 対策)。年次列は 売上_ / 変動費_ / 固定費_ / 減価償却費_ を接頭辞に 1 年目 〜 5 年目 のループで解決する。
出力シート 67_report_investment_analysis(新規・DDL 管理外)
毎回 clearContents() → 再書き込み。CLAUDE.md 末尾の「DDL (setupAllSchemas) で管理されないタブ」リストに 67_report_investment_analysis を追加する。
| ブロック | 内容 |
|---|---|
| A. 計算前提パラメータ | 案件ID・案件名・初期投資額・投資実行年月・分析期間・割引率・残存価額・計算実行日時(Utils.parseDateToYmd(new Date())) |
| B. 年次 FCF 計算表 | 各年の 売上 / 変動費 / 固定費 / 減価償却費 / EBT / 実効税率 / 法人税等 / 税引後利益 / FCF / 割引係数 / 割引後 CF |
| C. サマリ指標 | NPV(正味現在価値) / IRR(内部収益率, %) / 回収期間(Payback Period, 年) / ROI(投資収益率, %) |
年月セル(例: 投資実行年月, 計算実行日時)の書き込みは アポストロフィ前置("'2026-03")または setNumberFormat('@') で文字列強制すること(失敗パターン #23 対策)。
計算エンジン 600_report/610_service_investment_analysis.js(新規)
公開関数(メニューから呼び出される):
/**
* 30_bud_investment_case の全有効行に対し投資回収シミュレーションを実行し、
* 67_report_investment_analysis に結果を上書き出力する。
*/
function runInvestmentAnalysis() { /* ... */ }
内部関数(いずれも private = 末尾アンダースコア付き):
| 関数 | シグネチャ | 役割 |
|---|---|---|
calcFcfYearly_(year, revenue, vc, fc, dep) | 戻り値 { ebt, taxRate, tax, fcf } | 各年の税引後 FCF を算出 |
calcEffectiveTaxRate_(ebt) | 戻り値 { rate, tax } | Constants.TAX_RATES.brackets を使い累進実効税率を算出。EBT ≤ 0 は { rate: 0, tax: localMinimumAnnual } |
calcNpv_(fcfs, rate, residualValue, initialInvestment) | 戻り値 number | Σ FCF_t / (1+r)^t + 残存価額/(1+r)^T − 初期投資 |
calcIrr_(fcfs, initialInvestment, residualValue) | 戻り値 number | null | 二分法で NPV=0 となる rate を探索。最大反復 IRR_MAX_ITER = 1000、収束閾値 IRR_EPS = 1e-7。非収束は null |
calcPaybackPeriod_(fcfs, initialInvestment) | 戻り値 number | null | 累積 FCF が初期投資を超過する最初の年を線形補間で返す。未回収は null |
calcRoi_(fcfs, initialInvestment, residualValue) | 戻り値 number | (Σ FCF + 残存価額 − 初期投資) / 初期投資 × 100 |
FCF 計算式(年次)
EBT = 売上 − 変動費 − 固定費 − 減価償却費
実効税率 = calcEffectiveTaxRate_(EBT) により動的算出
法人税等 = max(0, EBT) × rate + (EBT ≤ 0 ? localMinimumAnnual : 0)
※ EBT > 0 で累進適用する場合:
tax = min(EBT, 8_000_000) × 0.214
+ max(0, EBT - 8_000_000) × 0.336
税引後利益 = EBT − 法人税等
FCF = 税引後利益 + 減価償却費 // 非現金費用を足し戻す
NPV / IRR 計算式
割引係数_t = 1 / Math.pow(1 + rate, t) // t = 1〜分析期間
NPV = Σ_{t=1..T} FCF_t × 割引係数_t
+ 残存価額 × 割引係数_T
− 初期投資額
IRR = NPV(fcfs, rate, residualValue, initialInvestment) = 0 となる rate
(二分法: 下限 -0.99, 上限 10.0 で開始。`IRR_MAX_ITER` 回で非収束なら null)
UI・メニュー項目追加
000_infra/002_constants.js の MENU_DEFINITION 配列、📋 サイドバー: 📊 マート更新 カテゴリ(L229-239)の末尾に以下を追加:
{ label: '📈 投資回収シミュレーション', funcName: 'runInvestmentAnalysis', description: '30_bud_investment_case の案件ごとに NPV/IRR/回収期間/ROI を計算し 67_report_investment_analysis に出力' },
二重実行防止
runInvestmentAnalysis() の冒頭で LockService.getScriptLock() を取得し、try { ... } finally { lock.releaseLock(); } で確実に解放する。waitLock(30 * 1000) でタイムアウト 30 秒。
影響範囲
| ファイル | 変更種別 | 内容 |
|---|---|---|
000_infra/002_constants.js | 追加のみ | MENU_DEFINITION → 📋 サイドバー: 📊 マート更新 カテゴリ末尾に 1 項目追加 |
100_config/101_sys_config.js | 追加のみ | setupAllSchemas 配下のスキーマ定義テーブルに 30_bud_investment_case の列構成(29 列)を登録 |
600_report/610_service_investment_analysis.js | 新規作成 | 計算エンジン + 出力シート生成ロジック |
CLAUDE.md | 追加のみ | 「DDL (setupAllSchemas) で管理されないタブ」リストに 67_report_investment_analysis を追記 |
変更しないファイル:
600_report/601_datamart_ingest.js〜608_datamart_render.js— 既存マート生成ロジックは触らない400_domain/配下 — ドメインロジック改変なし200_data/202_repository.js— 共通ヘルパー (readSheetAsDtos_) を読み取り専用で流用するのみ。InvestmentCaseRepositoryを追加するかは「人間が検討すべき事項」参照
注意事項
- 列参照はヘッダー名ベース必須:
indexOf/buildHeaderIndex_を使い、列番号のハードコードは禁止(CLAUDE.md「データアクセス」規約)。年次列(1 年目〜5 年目)は接頭辞 +n 年目ループでヘッダー名を合成して解決する。 getLastColumn()使用禁止: 書式だけが入った空列まで取得してしまうため、年次列の範囲取得には使用しない。スキーマ定義に基づくハードコード列名、またはヘッダースキャン(indexOf)で特定する(失敗パターン #21 対策)。- DDL 登録の明確な分離: 入力シート
30_bud_investment_caseはsetupAllSchemasで自動生成・初期化される。出力シート67_report_investment_analysisは DDL 管理外(動的上書き)なので、CLAUDE.mdの該当リストに必ず追記する(忘れると将来のリファクタで DDL 強制適用が試みられて上書きロジックと衝突する)。 - 年月文字列の書き込み:
投資実行年月(YYYY-MM)や出力シートの計算実行日時(YYYY-MM-DD HH:mm)をsetValue()で書き込む際は、アポストロフィ前置(例:"'2026-03")またはsetNumberFormat('@')で文字列強制すること。日付オブジェクトのままだと GAS が勝手に書式解釈して2026/03/01等に変換される(失敗パターン #23 対策)。 - 実効税率のハードコード禁止:
0.214/0.336/8000000/70000をリテラルで書かず、必ずConstants.TAX_RATES.brackets[0].national + .local/.brackets[1]/.brackets[0].upTo/.localMinimumAnnualから動的に取得する。将来の税制改正時にこの 1 ファイルだけで追従できる設計を維持する。 - IRR 非収束時の扱い: 二分法の最大反復回数(
IRR_MAX_ITER = 1000)で収束しない場合はnullを返す。呼び出し元は「計算不能」文字列を出力シートに表示し、空白や#N/Aは使わない(ユーザーが「エラー」と誤認するため)。 - 割引率入力の単位: 入力シートの
割引率(%)はパーセント値(8→ 8%)で記入させる。計算エンジン内では0.08に変換してから使用する(rate = input.discountRatePct / 100)。-100%以下は(1 + rate) ≤ 0でゼロ除算・発散するため即エラー。 - LockService:
runInvestmentAnalysis()の冒頭でLockService.getScriptLock()を取得し、try/finallyで確実に解放する。同時実行で67_report_investment_analysisが競合書き込みされるのを防ぐ。 - 有効フラグ = FALSE 行のスキップ:
30_bud_investment_caseの A 列がfalse/'FALSE'の行は計算対象外とする(CLAUDE.md「データアクセス」規約)。 - 既存
600_report/ファイルへの変更禁止: 本案件は新規ファイル610_service_investment_analysis.js1 本のみを追加する。既存のマート生成関数群(dmCalcPl_等)は一切触らない。
エッジケース
計算エンジン実装時に以下のケースを必ずテスト対象に含める。
| # | 条件 | 表示値 / 動作 | 理由 |
|---|---|---|---|
| 1 | 初期投資額 = 0 または負値 | エラーメッセージ表示・計算中断(SpreadsheetApp.getUi().alert) | 投資回収の前提が成立しない |
| 2 | 割引率 ≤ -100%(= rate ≤ -1.0) | 入力バリデーションエラー・計算中断 | (1 + 割引率) ≤ 0 でゼロ除算または発散 |
| 3 | 分析期間 = 0 または > 5 | 入力バリデーションエラー・計算中断 | シートの年次列(1〜5年目)の設計外 |
| 4 | 全期間 FCF < 0 | 回収期間・IRR は「計算不能」と表示(NPV / ROI は負値のまま出力) | 回収イベントが発生しない |
| 5 | IRR が最大反復回数(IRR_MAX_ITER = 1000)で非収束 | 「計算不能」と表示 | 符号変換複数回等の多重解ケース |
| 6 | キャッシュフロー入力の途中年が空欄(null / '') | 0 として計算し、ユーザーへ警告ダイアログを表示 | 未入力を 0 とみなす仕様 |
| 7 | 課税所得(EBT)≤ 0 の年 | 法人税 = 0 + Constants.TAX_RATES.localMinimumAnnual(70,000 円)を加算 | 赤字年の最低税額近似(地方税均等割) |
| 8 | 課税所得 > 8,000,000 円 の年 | min(EBT, 8M) × 0.214 + max(0, EBT - 8M) × 0.336 で加重平均 | Constants.TAX_RATES.brackets の累進適用 |
| 9 | 分析期間 > CF 入力列数(例: 分析期間 7 年なのに 売上_5年目以降が全空欄) | 不足年を売上=変動費=固定費=減価償却費=0 として計算し、警告ダイアログを表示 | ユーザーの入力漏れを吸収 |
| 10 | 残存価額が未入力 / 負値 | 0 として扱う | 非償却資産の回収額は 0 円が最も保守的 |
| 11 | 30_bud_investment_case に有効行がゼロ | SpreadsheetApp.getUi().alert('計算対象の投資案件が存在しません') で中断 | 空シミュレーション防止 |
| 12 | 減価償却費 > (売上 − 変動費 − 固定費) で連続赤字 | 毎年 localMinimumAnnual を加算した上で FCF を算出 | 実効税率のダブルカウント防止 |
エラー表示規則
- 計算不能となった指標は文字列リテラルで
"計算不能"と出力(空白や#N/A・エラー記号は使わない)。 - バリデーションエラーは
SpreadsheetApp.getUi().alertで即座にユーザー通知し、67_report_investment_analysisは 書き換えない(前回の正常結果を保持)。
実データ検証
開発 GAS(dev)で以下を順に確認する:
- DDL 生成確認:
npm run push:dev後、メニュー📋 サイドバー: 🔧 開発・設定 > DDL 全更新 (Full)を実行し、30_bud_investment_caseが 29 列構成で生成されていること(列 A〜AC、ヘッダーが有効フラグ〜減価償却費_5年目であること)を確認する。 - 1 案件の手計算突合(CAPEX 想定・明瞭なケース):
- 初期投資 = 1,000 万円 / 分析期間 = 5 年 / 割引率 = 8% / 残存価額 = 100 万円
- 売上 500 万 × 5 年、変動費 100 万 × 5 年、固定費 150 万 × 5 年、減価償却費 200 万 × 5 年
- 手計算値と出力シートの NPV / IRR / Payback / ROI を突合(Excel または電卓で事前計算)。
- IRR 非収束ケース: 全年 FCF < 0 の案件を 1 行追加し、IRR・Payback が
"計算不能"表示されること。 - 累進課税境界ケース: EBT が 800 万円前後で推移する案件を追加し、税額が
min(EBT, 8M) × 0.214 + max(0, EBT - 8M) × 0.336で計算されていること(計算前提パラメータブロックにブラケット境界: 8,000,000 円と表示して検証しやすくする)。 - 有効フラグ OFF 行のスキップ: 2 行投入した状態で片方の A 列を FALSE にし、出力に反映されないこと。
- 二重実行ブロック: メニューを連打しても
LockServiceで 2 回目が待機またはスキップされること。
prod への反映は dev 検証完了後に npm run push:prod で行う。
関連ドキュメント
- CLAUDE.md — データアクセス規約・DDL で管理されないタブ一覧
- docs/prd.md — Human-in-the-Loop 原則
- dev_mas-001_variance_analysis.md — E.5.1 予実差異分析(P/L 系レポート出力の先行事例)
- dev_mas-010_financial_modeling.md — E.5.X MAS-010 中長期 5 カ年財務モデリング(本仕様の投資案件 CF を MAS-010 ベースラインに重ね合わせる接続口、下記 §補強参照)
- dev_mas-011_what_if_simulation.md — E.5.13 ボトムアップ型 What-if(インメモリシミュレーション設計の参考)
- dev_mas-012_headcount_simulation.md — E.5.14 目標逆算型人員計画(同じメニュー配置パターンの先行事例)
- dev_mas-003_kpi_dashboard.md — E.5.5 KPI ダッシュボード(動的上書き型出力シートの先行事例)
- dev_mas-018_financial_statement_linkage.md — E.5.17 MAS-018 財務 3 表完全連動(CAPEX の 3 表波及、MAS-010 との共通連動エンジン)
- failure_patterns.md — 失敗パターン #18-#20(固有名詞ハルシネーション)/ #21(
getLastColumn誤用)/ #23(年月文字列書き込み) - dev_mas-046_investment_catalog_master.md — 投資カタログマスタ。本仕様 (計算機) の上位レイヤーとして投資案件のメタデータ統合管理を提供・MAS-013 シミュレーション結果から本マスタに登録ボタン経由で連携・本マスタが SSoT・MAS-013 は計算機としての位置づけ
- dev_mas-042_investment_hurdle_rate.md — 投資ハードルレートマスタ・本仕様の判定基準補完
docs/_internal/research_prompts/RQ-001_*— 法人税・住民税・事業税の仕訳パターン(累進税率検証用)
補強: MAS-010 連携 + 感度分析強化(2026-04-22 追記)
MVP の NPV/IRR/Payback/ROI 単純計算に加え、以下 2 領域の強化を後続 Phase で実装する。
(A) MAS-010(5 カ年財務モデル)との接続口
MAS-010 が生成する 94_fs_longterm_forecast のベースライン 60 ヶ月 P/L に対し、本機能で算出した投資案件の年次 CF を重ね合わせることで、投資実行後の連結 5 年予測を得る。
- 接続方式:
InvestmentAnalysisService.generateFiveYearCashImpact_(caseId, startYm)公開関数を追加。戻り値はArray<{年月, 追加売上, 追加変動費, 追加固定費, 追加減価償却, 追加税額, 追加税引後 CF}>で MAS-010 と同じ年月キー形式 - 呼出順序: MAS-010 実行時に
Constants.getParam('F10_INVESTMENT_OVERLAY_CASE_ID', '')が非空の場合に本関数を呼び、ベースラインに加算した統合版予測を94_fs_longterm_forecastに併記(列追加方式ベースライン / +投資案件 / 統合) - 入力シート拡張:
30_bud_investment_caseにF-10 重ね合わせ対象フラグ列を追加(TRUE なら MAS-010 統合対象) - 複数案件の合算: フラグ TRUE の全案件を重ね合わせる(相乗効果の検討は人間が判断)。ただし MVP では 1 案件限定とする
- MAS-018 との整合: 減価償却費は P/L 費用計上 + B/S 有形固定資産減 + CF 非現金調整で 3 表整合。MAS-018 の
F18LinkageService.computeBsCfLinkage()が完成次第、本機能の CF 算出を MAS-018 エンジンに委譲(現状の内部実装は暫定、将来リファクタリング)
(B) 感度分析 UI(主要変数の ±20%/±10% マトリクス)
投資判断の堅牢性検証のため、主要パラメータの変動が NPV/IRR に与える影響を一覧化する。
- 対象パラメータ(推奨): (1) 年次売上見込み ±20% / (2) 初期投資額 (CAPEX) ±10% / (3) 割引率 (WACC) ±2pp / (4) 回収期間上限 ±1 年
- 出力レイアウト(
67_report_investment_analysis内の補強ブロック):
| 変動パラメータ | -20% | -10% | ベース | +10% | +20% |
|---|---|---|---|---|---|
| NPV(売上 ±20%) | -X | -Y | Z | +Y' | +X' |
| IRR(売上 ±20%) | A% | B% | C% | D% | E% |
| NPV(CAPEX ±10%) | -X2 | -Y2 | Z | +Y2' | +X2' |
| NPV(WACC ±2pp) | ... | ... | Z | ... | ... |
- トルネード図(任意): 各パラメータの影響度を視覚的に比較する横棒グラフを Google Sheets の
SPARKLINEで簡易実装可能(MAS-003 KPI ダッシュボードのパターン踏襲) - 計算ロジック:
runSensitivityAnalysis_(caseId, paramMatrix)内部関数を追加し、パラメータを順次変動させてcalculateNpv_/calculateIrr_を呼ぶループ実装 - 実装工数見積: 感度分析 +3-4h、MAS-010 連携 +4-5h(MAS-018 完成後の本実装は追加 +6-8h)
人間が検討すべき事項
| # | 検討事項 | 推奨デフォルト | 確定タイミング |
|---|---|---|---|
| 1 | 割引率(WACC / ハードルレート)の案件横断標準値 | ユーザーが案件ごとに入力(自動算出しない) | 運用開始後、案件 5 件程度の実績を見てから再検討 |
| 2 | Constants.TAX_RATES の適用範囲(中小法人前提固定 or 資本金切替) | 中小法人(資本金 1 億円以下)前提固定 | 資本金 1 億円超の成長局面で見直し |
| 3 | InvestmentCaseRepository を 200_data/202_repository.js に新設するか | 新設せず readSheetAsDtos_ を直接呼ぶ(1 画面ツール・1 回読み込み限定のため) | 他機能(例: MAS-016 売上予測への統合)で再利用が必要になったら新設 |
| 4 | シナリオ保存・履歴比較機能 | スコープ外(出力は毎回上書き) | ユーザー要求が発生したら別案件で検討 |
| 5 | 複数案件の一覧比較表(ランキング)出力 | スコープ外(1 案件 1 ブロックで順次出力) | 運用実績を踏まえて MAS-013 派生案件として別途起票 |
| 6 | 赤字年の localMinimumAnnual 適用ロジック | 年間 70,000 円を EBT ≤ 0 の各年に加算(月割考慮なし) | 設立初年度の月割が必要な場合は Constants.TAX_RATES.foundingYear/foundingMonth を参照する派生仕様を検討 |
| 7 | IRR 二分法の探索範囲 | 下限 -0.99、上限 10.0(= -99%〜+1000%) | 特殊案件(VC ファンド等)で範囲外解が発生した場合に拡張 |
| 8 | 減価償却方法(定額 / 定率) | 入力シートで各年の減価償却費を手入力させる方式(減価償却計算自体はスコープ外) | 自動計算が必要になれば CAPEX マスタ(24_bud_capex)との連携案を起票 |
| 9 | 消費税・インボイス税額控除の扱い | スコープ外(売上・費用は税抜金額で入力させる前提) | 税込入力が必要になれば別案件で検討 |
| 10 | 入力シート 30_bud_investment_case の番号帯 | 25_bud_finance(BUD_FIN キー・既存 RPA / DDL / バリデーションで使用中)との衝突回避のため 30 番を新設。29_mst_investment_hurdle は MAS-042 仕様書で予約済み | 20 番台(予算系)が逼迫してきたため 30 番台へ拡張。将来は 30 番台の体系的な割当ルールを CLAUDE.md に明記 |
実装プロンプト(Claude Code 用)
実行前タスク(必須)
1. `100_config/101_sys_config.js` の `setupAllSchemas` 配下のスキーマ定義テーブルを Read し、既存の `21_bud_pipeline` / `22_bud_headcount` / `23_bud_subscription` / `24_bud_capex` の登録パターン(列定義の書式、必須 / 任意フラグ、バリデーション設定)を確認する。
2. `000_infra/002_constants.js` L21-30 の `Constants.TAX_RATES` を Read し、`brackets[0].national` / `brackets[0].local` / `brackets[0].upTo` / `brackets[1].national` / `brackets[1].local` / `localMinimumAnnual` の値を確認する。**これらの値を計算ロジック内でハードコードせず、必ず `Constants.TAX_RATES.*` から参照する**。
3. `000_infra/004_utils.js` の `Utils.parseAmt` (L191-198) / `Utils.parseDateToYm` (L92-99) / `Utils.parseDateToYmd` (L108-118) のシグネチャと戻り値形式を Read で確認する。
4. `200_data/202_repository.js` L19-29 の `readSheetAsDtos_` を Read し、戻り値が `{ headers: string[], dtos: Object[] }` であること、`30_bud_investment_case` 読込時に `result.dtos.filter(r => r['有効フラグ'] !== false && String(r['有効フラグ']).toUpperCase() !== 'FALSE')` で有効行を絞り込むパターンを把握する。
5. `000_infra/002_constants.js` L206-324 の `MENU_DEFINITION` を Read し、`📋 サイドバー: 📊 マート更新` カテゴリ(L229-239)の**実在ラベル文字列**をそのまま使用する(造語禁止)。
6. `600_report/601_datamart_ingest.js` 冒頭を Read し、シート取得パターン(`getWebSpreadsheet_()` / `ss.getSheetByName(...)`)を確認する。
修正対象ファイル
- `100_config/101_sys_config.js` — `setupAllSchemas` スキーマ定義に `30_bud_investment_case`(29 列)を追加
- `000_infra/002_constants.js` — `MENU_DEFINITION` の `📋 サイドバー: 📊 マート更新` カテゴリ末尾に 1 項目追加
- `600_report/610_service_investment_analysis.js` — **新規作成**(計算エンジン + 出力シート生成)
- `CLAUDE.md` — 「DDL (setupAllSchemas) で管理されないタブ」リストに `67_report_investment_analysis` を追記
新規作成シート
- `30_bud_investment_case` — 入力シート(DDL 管理、`setupAllSchemas` で自動生成)
- `67_report_investment_analysis` — 出力シート(DDL 管理外・動的上書き型)
実装手順
Step 1: DDL 登録(Haiku 推奨)
`100_config/101_sys_config.js` の `setupAllSchemas` 配下に `30_bud_investment_case` の列定義(29 列)を追加。既存 `24_bud_capex` の定義パターン(色・バリデーション・数値フォーマット)を踏襲。年次列は `売上_1年目` 〜 `減価償却費_5年目` の計 20 列を規則的に追加する。
Step 2: 計算エンジン実装(Sonnet 推奨)
`600_report/610_service_investment_analysis.js` を新規作成。以下の構成:
1. ファイル冒頭コメント(役割説明・依存 `Constants.TAX_RATES` 明記)
2. 定数定義: `IRR_MAX_ITER = 1000`, `IRR_EPS = 1e-7`, `IRR_LOWER = -0.99`, `IRR_UPPER = 10.0`, `OUTPUT_SHEET = '67_report_investment_analysis'`, `INPUT_SHEET = '30_bud_investment_case'`
3. `runInvestmentAnalysis()` — 公開関数。LockService 取得 → 入力シート読込(`readSheetAsDtos_`)→ 有効行 filter → 各案件で FCF / NPV / IRR / Payback / ROI 計算 → 出力シートに `clearContents` + `setValues` で書き込み
4. `calcFcfYearly_(revenue, vc, fc, dep)` — 税引後 FCF 算出
5. `calcEffectiveTaxRate_(ebt)` — `Constants.TAX_RATES.brackets` を使う累進税率・赤字年 `localMinimumAnnual` 適用
6. `calcNpv_(fcfs, rate, residualValue, initialInvestment)` — 割引現在価値
7. `calcIrr_(fcfs, initialInvestment, residualValue)` — 二分法(非収束は `null`)
8. `calcPaybackPeriod_(fcfs, initialInvestment)` — 線形補間(未回収は `null`)
9. `calcRoi_(fcfs, initialInvestment, residualValue)` — 単純 ROI(%)
10. `buildInvestmentReport_(results)` — 2D 配列を組み立てるヘルパー(ブロック A: 前提パラメータ / ブロック B: 年次明細 / ブロック C: サマリ)
11. `writeInvestmentReport_(rows)` — 出力シート操作(`clearContents()` → `setValues()` → 列幅調整 → `setNumberFormat('@')` を年月セルに適用)
Step 3: メニュー追記(Haiku 推奨)
`000_infra/002_constants.js` L239 付近(`📋 サイドバー: 📊 マート更新` カテゴリの末尾、`📸 前年度P/Lスナップショット` の直後)に以下を追加:
{ label: '📈 投資回収シミュレーション', funcName: 'runInvestmentAnalysis', description: '30_bud_investment_case の案件ごとに NPV/IRR/回収期間/ROI を計算し 67_report_investment_analysis に出力' },
Step 4: CLAUDE.md 追記(Haiku 推奨)
「DDL (setupAllSchemas) で管理されないタブ」セクションの「動的に生成・上書きされるタブ:」リストに `67_report_investment_analysis` を追加:
03_sys_params, 75_ss_equity_changes, 76_notes,
77_pj_raw, 78_pj_pl, 91_fs_bs, 92_fs_pl, 93_kpi_dashboard, 90_test_results,
67_report_investment_analysis
制約
- **列番号のハードコード禁止**: 入力シート列参照は必ず `headers.indexOf('列名')` 経由。年次列は `売上_${n}年目` 等で動的生成。
- **`getLastColumn()` 使用禁止**: 書式入り空列まで拾うため。29 列固定またはヘッダースキャンで対応。
- **`0.214` / `0.336` / `8000000` / `70000` のリテラル直書き禁止**: `Constants.TAX_RATES.brackets[0].national + .local` 等から動的取得。
- **メニュー名の造語禁止**: `📋 サイドバー: 📊 マート更新` のみ使用(Read で確認した実在文字列)。
- **既存 `600_report/6*_datamart_*.js` への変更禁止**: 本案件は `610_service_investment_analysis.js` 1 本のみ新規作成。
- **年月セルの書き込み**: `setValue()` で年月文字列を書く際はアポストロフィ前置または `setNumberFormat('@')` で文字列強制(失敗パターン #23)。
- **`LockService` 必須**: `runInvestmentAnalysis()` 冒頭で取得、`finally` で解放。
- **有効フラグ = FALSE 行のスキップ**: A 列 = `false` / `'FALSE'` は計算対象外。
エッジケース(実装時に必ずテスト)
| # | 条件 | 表示値 / 動作 |
|---|---|---|
| 1 | 初期投資額 = 0 または負値 | エラーメッセージ表示・計算中断 |
| 2 | 割引率 ≤ -100% | エラーメッセージ表示・計算中断 |
| 3 | 分析期間 = 0 または > 5 | エラーメッセージ表示・計算中断 |
| 4 | 全期間 FCF < 0 | 回収期間・IRR は "計算不能" と表示 |
| 5 | IRR 非収束(1000 回反復) | "計算不能" と表示 |
| 6 | 途中年 CF 欄が空欄 | 0 として計算・警告表示 |
| 7 | EBT ≤ 0 | 法人税 = 0 + localMinimumAnnual 加算 |
| 8 | EBT > 8,000,000 | min(EBT, 8M) × 0.214 + max(0, EBT-8M) × 0.336 |
| 9 | 分析期間 > CF 入力列数 | 不足年 0 として計算・警告表示 |
| 10 | 残存価額未入力 / 負値 | 0 として扱う |
| 11 | 有効行ゼロ | SpreadsheetApp.getUi().alert で中断 |
| 12 | 減価償却費 > (売上 − 変動費 − 固定費) | localMinimumAnnual を加算した上で FCF 算出 |
動作確認手順
1. `npm run push:dev` で開発 GAS にデプロイ
2. `📋 サイドバー: 🔧 開発・設定 > DDL 全更新 (Full)` を実行し `30_bud_investment_case` が 29 列で生成されていることを確認
3. `30_bud_investment_case` に投資案件を 1 行入力(例: 初期投資 1,000 万 / 5 年 / 割引率 8% / 残存価額 100 万 / 売上 500 万×5年 / 変動費 100 万×5年 / 固定費 150 万×5年 / 減価償却費 200 万×5年)
4. `📋 サイドバー: 📊 マート更新 > 📈 投資回収シミュレーション` を実行
5. `67_report_investment_analysis` に計算結果が出力されていることを確認し、NPV / IRR / Payback / ROI を Excel 電卓と突合
6. 有効フラグ OFF 行・全 FCF < 0 ケース・IRR 非収束ケースの各エッジケースを順次検証
7. 全件 OK なら `npm run push:prod` で本番にも反映
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| Step 1 DDL 登録・スキーマ定義追加 | Claude Haiku | 29 列の定型的な列定義追加。既存パターン(21/22/23/24)の横展開のみ |
| Step 2 計算エンジン実装(FCF/NPV/IRR/累進税率) | Claude Sonnet | 数式ロジック・エッジケース処理・二分法収束判定・Constants.TAX_RATES 動的取得の実装判断が必要 |
| Step 3 UI・メニュー追記 | Claude Haiku | MENU_DEFINITION への 1 行追加のみ |
| Step 4 出力シート生成・レイアウト | Claude Sonnet | 前提パラメータ + 明細表 + サマリの 3 ブロック構造の組み立て、列幅・数値フォーマット・年月セルの文字列強制判断が必要 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成。NPV / IRR / 回収期間 / ROI 計算エンジン設計、Constants.TAX_RATES 累進課税適用方針、IRR 非収束・赤字年 localMinimumAnnual 適用・割引率 ≤ -100% バリデーション等のエッジケース定義、30_bud_investment_case(DDL 管理・29 列)と 67_report_investment_analysis(DDL 管理外・動的上書き)の 2 枚構成、📋 サイドバー: 📊 マート更新 カテゴリへのメニュー追記方針を含む |
| 2026-04-22 | §補強: MAS-010 連携 + 感度分析強化 を追記。MAS-010(5 カ年財務モデリング)との接続口として generateFiveYearCashImpact_(caseId, startYm) 公開関数 + 30_bud_investment_case への F-10 重ね合わせ対象フラグ 列追加方針を定義。感度分析 UI として主要 4 パラメータ(売上 ±20% / CAPEX ±10% / WACC ±2pp / 回収期間 ±1 年)のマトリクス出力 + トルネード図の設計を追加。MAS-018(3 表連動)の CF 算出エンジン完成後に委譲する段階移行方針も明記。関連ドキュメントに MAS-010 / MAS-018 を追加 |
| 2026-04-23 | 分析期間を 10 年 → 5 年に縮小(列数 49 → 29)。MAS-010 5 カ年モデルと粒度を揃え、列数を抑えて運用負荷を下げるため。併せて「47 列」の誤記を「29 列」に訂正(9 共通列 + 年次 4 項目 × 5 年 = 29 列)。1〜10 → 1〜5 の期間指定、年次列の列範囲(J〜AW → J〜AC)、接頭辞ループの上限(10 年目 → 5 年目)を一括修正 |
| 2026-04-23 | 入力シート番号を 25 → 30 に変更(25_bud_investment_case → 30_bud_investment_case)。MAS-013 実装着手時に 25_bud_finance(BUD_FIN キー・RPA / DDL / バリデーションで使用中)との番号衝突が判明したため。29 は MAS-042 仕様書で 29_mst_investment_hurdle 予約済みのため 30 番を新設。仕様書内 28 箇所を一括置換、人間検討事項の番号採用理由記述を更新 |
仕様書作成プロンプト
展開して表示
<instruction>
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**:
- Phase 1(設計): 拡張思考をフル活用し、ファイル名・エッジケース一覧・Step 分割粒度・固有名詞(関数名/シート名/列名/メニュー名)を**ここで完全に確定**させる。Read によるコード裏取りを妥協しない(失敗パターン #18-#20 の直接対策)。
- Phase 2(清書): 各 Step 内では拡張思考を**最小限**に抑える。Phase 1 で確定済みの内容の書き下しに徹する。出力途中で再考しない。
2. **テキスト報告の禁止**: 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。説明は 1 文以内。直ちに tool を呼ぶ。
3. **4-5 分割の Write/Edit 実行**:
- 2-1 骨格 Write(~20行)
- 2-2 概要〜注意事項 Edit/Bash(~300行)
- 2-3a エッジケース〜人間検討事項 Edit/Bash(~200行)
- 2-3b 実装プロンプト〜変更履歴 Edit/Bash(~250行)
- 2-4 `<details>` にプロンプト全文記録 Edit/Bash(最重量・必ず独立 Step)
4. **各 Step で何を書くかを具体指示**: Phase 2 実行時に設計判断を持ち込まず、Phase 1 で確定した内容の清書に徹する。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 MAS-013「投資回収シミュレーション」の開発仕様書を作成してください。
作成後は `docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。
---
## Phase 1: 実行前タスク(テキスト報告禁止・直ちにツール実行)
以下のファイルを**この順序で** Read/Grep し、Phase 2 の清書に必要な固有名詞・構造をすべて確定させること。「名前から推測した瞬間に手を止めて Read する」(失敗パターン #18-#20 対策)。Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read で行う。
### 1-A: 案件要件の把握
- `docs/_internal/TODO_future.md` — MAS-013 行を検索し、案件名・概要・人間が検討すべき事項を取得する。
### 1-B: 既存メニュー構造の確認(固有名詞ハルシネーション防止・最重要)
- `100_config/101_sys_config.js` — `onOpen()` 関数全体を Read し、**実在するメニュー名・サブメニュー名をそのままの文字列で記録する**。`📊 FP&Aツール` が存在するか確認し、存在しない場合は実在メニューへの追加 or 新規メニュー作成のどちらが適切かを判断する。**Step 2-2 でメニュー名を書く際は、ここで確認した文字列のみを使用すること。造語・記憶からの引用は禁止。**
### 1-C: 定数・ユーティリティの構造確認
- `000_infra/002_constants.js` — `TAX_RATES` オブジェクト全体(`brackets` 配列の `{ upTo, national, local }` 各値、`localMinimumAnnual`、`foundingYear`/`foundingMonth`)を Read して記録する。
- `000_infra/004_utils.js` — `Utils.parseAmt`・`Utils.parseDateToYm`・`Utils.parseDateToYmd` のシグネチャ・戻り値形式・対応フォーマットを Read して記録する。
### 1-D: データアクセス層のパターン確認
- `200_data/202_repository.js` — `readSheetAsDtos_`・`appendDtosToSheet_`・`writeDtosToSheet_` の引数・戻り値・内部挙動を Read する(新規シートへの書き込みパターンの参照元)。
### 1-E: 既存レポート生成パターンの確認
- `600_report/` 配下の既存ファイル 1 本(`601_datamart_ingest.js` を優先)を Read し、シート取得・LockService 使用・clearContents & setValues パターンを把握する。
### 1-F: 仕様書フォーマットの参照
- `docs/dev/` 配下の FP&A 系仕様書 1 本(`dev_mas-001_variance_analysis.md` 等、案件の性質に最も近いもの)を Read し、セクション構成・実装プロンプトの行頭 4 スペース形式を確認する。
---
## Phase 2: 仕様書の分割作成
出力先: `docs/dev/dev_mas-013_investment_simulation.md`
**【厳守】1 回のツール呼び出しで全内容を出力しない。以下の Step に必ず分割して実行する。**
### Step 2-1: 骨格の作成(File Write・~20行)
以下のセクション見出しのみを書き込む(本文は空で可):
```
# F-13: 投資回収シミュレーション
## 概要
## 目的
## 現在のコード
## 修正方針
## 影響範囲
## 注意事項
## エッジケース
## 実データ検証
## 関連ドキュメント
## 人間が検討すべき事項
## 実装プロンプト(Claude Code 用)
## 推奨実行モデル
## 変更履歴
## 仕様書作成プロンプト
```
### Step 2-2: 前半セクションの記述(File Edit または Bash・~300行)
`## 概要` 〜 `## 注意事項` を記述する。以下の設計方針を忠実に反映すること。
**アーキテクチャ方針**:
- **入力シート** `30_bud_investment_case`(新規作成):
- DDL(`setupAllSchemas`)管理対象として `101_sys_config.js` のスキーマ定義に登録が必要。
- 列構成: `案件ID`、`案件名`、`ステータス`、`初期投資額`、`投資実行年月`(YYYY-MM形式)、`分析期間(年)`(1〜10)、`割引率(%)`(-100% 超の値のみ有効)、`残存価額`、`売上_1年目`〜`売上_10年目`、`変動費_1年目`〜`変動費_10年目`、`固定費_1年目`〜`固定費_10年目`、`減価償却費_1年目`〜`減価償却費_10年目`。
- **出力シート** `67_report_investment_analysis`(動的上書き型・DDL管理外):
- 毎回 `clearContents()` してから書き直す。`CLAUDE.md` の「DDL で管理されないタブ」リストへの追記も注意事項に明記すること。
- 出力内容: 計算前提パラメータ(割引率・分析期間・残存価額・計算実行日時)、年次 FCF 計算表、割引後 CF、NPV(正味現在価値)、IRR(内部収益率)、回収期間(Payback Period)、ROI(投資収益率)。
- **計算エンジン** `600_report/610_service_investment_analysis.js`(新規作成):
- FCF 基本計算式: `FCF = (売上 - 変動費 - 固定費 - 減価償却費) × (1 - 実効税率) + 減価償却費`
- **実効税率の算出**(`Constants.TAX_RATES` を使用・ハードコード禁止):
- 各年の課税所得 EBT = `売上 - 変動費 - 固定費 - 減価償却費`
- EBT ≤ 0 の場合: 法人税 = 0。ただし `Constants.TAX_RATES.localMinimumAnnual`(70,000円)を年間最低税として加算する。
- 0 < EBT ≤ 8,000,000 の場合: 実効税率 = `national(0.165) + local(0.049) = 0.214`(ブラケット 1)。
- EBT > 8,000,000 の場合: 8,000,000 までの部分に 0.214、超過部分に `national(0.256) + local(0.080) = 0.336`(ブラケット 2)を適用し、加重平均で当年実効税率を算出する。
- ブラケット値は `Constants.TAX_RATES.brackets[0]` / `brackets[1]` から動的に取得すること(数値の直書き禁止)。
- IRR 計算: 二分法による近似。最大反復回数(例: 1000回)を定数化し、上限到達時は `null` を返して呼び出し元で「計算不能」と表示する。
- NPV 計算: 各年 FCF を `1 / Math.pow(1 + rate, t)` で割引き(`t` = 年次)、残存価額を最終年に加算。初期投資額を差し引いて NPV とする。
- **UI**: `101_sys_config.js` の `onOpen()` に「**Phase 1-B で Read した実在のメニュー名**」→「投資回収シミュレーションを更新」のメニュー項目を追記する。メニュー名は**造語禁止・Read した文字列のみ使用**。
- **二重実行防止**: 計算関数の冒頭で `LockService.getScriptLock()` を取得し、`try { ... } finally { lock.releaseLock(); }` で確実に解放する。
**再利用する既存コード**(Phase 1-C で確認した内容を使用):
- `Utils.parseAmt(val)` — シートから読み取った金額文字列を安全に数値変換する。
- `Utils.parseDateToYm(val)` — `投資実行年月` の Date オブジェクト / 文字列を `YYYY-MM` 形式に正規化する。
**注意事項** に必ず含める事項:
- 列参照はヘッダー名ベース(`indexOf` 使用)。列番号のハードコード禁止(CLAUDE.md 規約)。
- `30_bud_investment_case` は DDL 登録必須。`67_report_investment_analysis` は DDL 管理外(`CLAUDE.md` の該当リストに追記)。
- 年月文字列(`投資実行年月` 等)を `setValue()` で書き込む際は、アポストロフィ前置(`"'2026-03"`)または `setNumberFormat('@')` で文字列強制すること(失敗パターン #23 対策)。
- `getLastColumn()` による動的列取得は、書式が入った空列まで拾うため使用禁止。月・年次の列範囲はスキーマ定義に基づきハードコードまたはヘッダースキャンで特定する(失敗パターン #21 対策)。
### Step 2-3a: エッジケース〜人間検討事項の記述(File Edit または Bash・~200行)
`## エッジケース`・`## 実データ検証`・`## 関連ドキュメント`・`## 人間が検討すべき事項` を記述する。
**エッジケーステーブル**(以下を最低限含めること):
| 条件 | 表示値 / 動作 | 理由 |
|------|-------------|------|
| 初期投資額 = 0 または負値 | エラーメッセージ表示・計算中断 | 投資回収の前提が成立しない |
| 割引率 ≤ -100% | 入力バリデーションエラー・計算中断 | `(1 + 割引率) ≤ 0` でゼロ除算または発散 |
| 全期間 FCF < 0 | 回収期間・IRR は「計算不能」と表示 | 回収イベントが発生しない |
| IRR が最大反復回数(1000回)で非収束 | 「計算不能」と表示 | 符号変換複数回等の多重解ケース |
| キャッシュフロー入力の途中年が空欄 | 0 として計算(ユーザーへ警告表示) | 未入力を 0 とみなす仕様 |
| 課税所得(EBT)≤ 0 の年 | 法人税 = 0 + `localMinimumAnnual`(70,000円)を加算 | 赤字年の最低税額近似 |
| 分析期間 > CF 入力列数 | 不足年を CF = 0 として計算し警告を表示 | ユーザーの入力漏れを吸収 |
**Human-in-the-Loop**:
- 本機能は試行錯誤型分析ツールのため、厳格な承認フローは不要とする。
- 出力シートに計算前提パラメータ(割引率・分析期間・残存価額・計算実行日時)を明記し、再現性を担保する。
- 割引率(WACC / ハードルレート)はユーザーが `30_bud_investment_case` で案件ごとに手動設定する方式とする(自動算出はスコープ外)。
### Step 2-3b: 実装プロンプト・推奨実行モデル・変更履歴の記述(File Edit または Bash・~250行)
`## 実装プロンプト(Claude Code 用)`・`## 推奨実行モデル`・`## 変更履歴` を記述する。
`## 仕様書作成プロンプト` は**見出しのみ**追記し、本文は Step 2-4 に委ねること。
実装プロンプトは**行頭 4 スペースインデント形式**(バッククォート3つ以上で囲まない)で、以下を含めること:
- 実行前タスク(`101_sys_config.js`・`002_constants.js`・`004_utils.js`・`202_repository.js`・`600_report/` 既存ファイル 1 本を Read し、メニュー名・ブラケット値・関数シグネチャを確定すること)
- 修正対象ファイル(`100_config/101_sys_config.js`・新規 `600_report/610_service_investment_analysis.js`)と新規シート(`30_bud_investment_case`・`67_report_investment_analysis`)
- 実装手順(① DDL登録 → ② 計算エンジン `610_service_investment_analysis.js` → ③ `101_sys_config.js` へのメニュー追記 → ④ 出力シート生成ロジック)
- 制約(列番号ハードコード禁止・既存 `600_report/` ファイルへの変更禁止・メニュー名は Read した実在文字列のみ使用・`getLastColumn()` 禁止)
- エッジケーステーブル(Step 2-3a と同内容を転記)
- 動作確認手順(`npm run push:dev` 後: DDL 実行でシート生成確認 → 投資案件を 1 行入力 → メニューから実行 → 出力シートの NPV / IRR / Payback 値を手計算と突合)
推奨実行モデルのテーブル:
| 工程 | 推奨モデル | 理由 |
|------|----------|------|
| DDL 登録・スキーマ定義追加 | Claude Haiku | 定型的な列定義追加 |
| 計算エンジン実装(FCF/NPV/IRR/進行税率) | Claude Sonnet | 数式ロジック・エッジケース処理の判断が必要 |
| UI・メニュー追記 | Claude Haiku | 既存 `onOpen()` パターンの横展開 |
| 出力シート生成・レイアウト | Claude Sonnet | 既存レポートパターン適用 + レイアウト設計判断 |
### Step 2-4: 仕様書作成プロンプトの全文記録(File Edit または Bash・最重量・必ず独立 Step)
`## 仕様書作成プロンプト` セクションに以下を追記する(`<details>` 内にこの `<instruction>` 全文を貼り付ける):
<details><summary>展開して表示</summary>
(この <instruction> 全文をここに記録する)
</details>
---
## Phase 3: 後処理(3 件すべて実行してからコミット)
### 3-B: `docs/_config.json` へのナビゲーション登録(必須)
`§E.5 FP&A・レポーティング` セクションに追加(連番は既存エントリを Read して確定すること):
```json
{ "file": "dev/dev_mas-013_investment_simulation.md", "title": "E.5.X F-13 投資回収シミュレーション" }
```
追記後、`node -e "require('./docs/_config.json')"` で JSON 構文エラーがないことを確認する。
### 3-C: changelog 追記
`docs/_internal/changelog.md` のヘッダー直後に追記:
```
| 2026-04-20 | [dev_mas-013_investment_simulation.md](dev_mas-013_investment_simulation.md) | 初版作成。投資回収シミュレーション(NPV/IRR/PaybackPeriod/ROI)設計、進行税率・エッジケース定義を含む |
```
### 3-D: コミット&プッシュ
```bash
git add docs/dev/dev_mas-013_investment_simulation.md docs/_internal/changelog.md docs/_config.json
git commit -m "docs: F-13 投資回収シミュレーションの開発仕様書を作成
NPV/IRR/PaybackPeriod/ROI計算エンジン設計、Constants.TAX_RATES進行課税適用方針、
IRR非収束・赤字年税処理・割引率バリデーション等のエッジケース定義を含む
https://claude.ai/code/session_XXXXX"
git push -u origin HEAD
```
</instruction>