MAS-176: 開発者向け AI ツール投資 ROI 管理(D.8 派生)
概要
| 項目 | 内容 |
|---|---|
| 案件 ID | MAS-176 (旧 F-176) |
| 案件名 | 開発者向け AI ツール投資 ROI 管理(D.8 派生) |
| カテゴリ | 自動化 / FP&A 派生 |
| Phase | P1.5 |
| 優先度 | ★★★ |
| 所要時間 | 約 1.5-2 週間(週 10h 前提・Step 1-4 段階適用可) |
| 対象レイヤー | 🌐 Cross (Domain + Data + Report) |
| 対象ファイル(変更) | 100_config/101_sys_config.js(BUD_SUBS ヘッダー 2 列追加 + 入力列番号配列の更新)/ 200_data/202_repository.js(SubscriptionRepository 新設・HeadcountRepository.findAll は既存パターン流用)/ 600_report/609_datamart_kpi.js(K7 行追加 + ヘルパー行 35 追加)/ 000_infra/003_contracts.js(SubscriptionDTO JSDoc 型定義追加) |
| 対象ファイル(新規) | 400_domain/450_ai_tool_roi_engine.js(AiToolRoiEngine 名前空間・約 150 行・新規ファイル番号 450) |
新規 03_sys_params キー | MAS176_HOURLY_RATE_BASIS (default EXEC_ANNUAL_DIV_HOURS) / MAS176_EXEC_MEMBER_NAME (default 代表取締役) / MAS176_EXEC_ANNUAL_HOURS (default 2000) / MAS176_ROI_WARN_THRESHOLD (default 2.0) / MAS176_ROI_TARGET_THRESHOLD (default 4.0) / FF_UC8_S01_CALC_ROI (default true・ADR-0028/0029 Feature Flag 命名規約準拠) |
| 前提案件 | MAS-003(KPI ダッシュボード基盤・✅ 完了)/ MAS-048(HC TCO シミュレーター・✅ 完了。HeadcountRepository.findAll パターン参考) |
| 後続連携 | MAS-013(NPV/IRR 一時投資判定)と棲み分け。MAS-230(Jr 採用)後の Jr 学習教材(生産性測定の体感) |
目的
月 2.5 万円の AI ツール投資(Cursor Pro / Claude Max 5x / GitHub Copilot Pro / v0 / ChatGPT Plus 等)が月 10 万円相当の生産性を生むことを定量証明し、継続的 AI ツール投資の正当化と予算確保を行う。
23_bud_subscriptionに 想定月間削減時間 列と 課金サイクル 列を追加し、ツールごとの主観入力エビデンスを蓄積(年額契約と月額契約の混在を月額換算で正規化)- 役員年収÷年間稼働時間で算出した時間単価を乗じて月次の 想定削減金額(≒ 投資効果) を算出
- 月額コスト(税込)と比較した ROI 倍率 を
93_kpi_dashboardに K7 として常時表示 - ROI 閾値(warn=2.0x / target=4.0x)でアラート表示し、コスパ低下したツールの解約判断を促す
- MAS-013(大型一時投資 NPV/IRR)と棲み分け: 本案件は月額 SaaS マイクロ継続投資をサブスクマスタ連動で月次自動モニタリング
修正方針
Phase 0: 実データ Read 裏取り(着手前必須・failure_patterns #18-#20 直接対策)
Step 1 着手前に以下のファイルを Read し、仕様書本文の「実データ検証」セクションに Read 結果のコードスニペットを inline 貼付してから実装に進む。検証完了前は Step 1 以降を凍結。
100_config/101_sys_config.jsのBUD_SUBSスキーマ定義部(ヘッダー配列・inputCols/autoColsの実値)200_data/202_repository.jsのHeadcountRepository.findAllパターンとreadSheetAsDtos_ヘルパー実装22_bud_headcountの DDL ヘッダー定義(氏名・ポジションが単一列か、氏名/ポジション/雇用形態等が分離列かを確認)- メニュー定義の実在ファイル:
grep -rn 'マイグレーション' 000_infra/ 100_config/ 300_ui/で実存ファイルとデータ構造(MENU_DEFINITION配列 /onOpen()内ui.createMenu()等)を特定 migrationF66DividendSeed関数の実在ファイル:grep -rn 'function migrationF66DividendSeed' .で実パスを特定BUD_SUBSの利用ステータス列のdataValidation.values実値(解約済か解約済みか等の表記揺れ確認)
Step 1: 23_bud_subscription に削減時間列・課金サイクル列を追加(DDL 拡張)
100_config/101_sys_config.js の BUD_SUBS ヘッダー定義(現行 24 列・末尾は X 列「最終決済予定日」)の末尾に 以下 2 列を追加し 26 列構成に拡張する。
- 現行ヘッダー(24 列・A 列〜X 列):
["有効フラグ","管理ID","サービス・ツール名","利用者・部門","費用科目","取引先名","契約形態","開始・契約年月","次回更新・終了年月","税抜金額_計画","消費税額_計画","税区分","決済手段","決済ラグ(月)","支払基準日","休日調整","CF計上","自動更新アラート","利用ステータス","組織名","備考","起票ターゲット月","最終起票年月日","最終決済予定日"] - 追加 25 列目(Y 列):
課金サイクル(月/年のいずれか・dataValidation で固定。月額換算のための正規化キー) - 追加 26 列目(Z 列):
想定月間削減時間(est_hours_saved_per_month想定 / 単位: hour)
101_sys_config.js の BUD_SUBS の inputCols / autoCols 配列は、Phase 0 で取得した実値の 配列末尾に 25 および 26 を追加 する(行番号・現行配列リテラルのハードコードは避け、論理的にロケートする):
inputColsに25(Y 列・課金サイクル)と26(Z 列・想定月間削減時間)を追加(手入力)autoColsは不変
整合性チェック: inputCols と autoCols の更新は 同一の Edit ブロックで両方更新(failure_patterns #25 並列実装対称性)。
Step 2: SubscriptionRepository 新設 + DTO 定義(既存パターン踏襲)
Step 2-A: 000_infra/003_contracts.js に DTO 型定義追加
ADR-0028/0029 のデータ境界要件に基づき、SubscriptionDTO の JSDoc 型定義を 003_contracts.js に追加:
/**
* @typedef {Object} SubscriptionDTO
* @property {boolean} 有効フラグ
* @property {string} 管理ID
* @property {string} サービス・ツール名
* @property {string} 利用者・部門
* @property {string} 費用科目
* @property {string} 取引先名
* @property {string} 契約形態
* @property {string|Date} 開始・契約年月
* @property {string|Date} 次回更新・終了年月
* @property {number} 税抜金額_計画
* @property {number} 消費税額_計画
* @property {string} 税区分
* @property {string} 決済手段
* @property {number} 決済ラグ(月)
* @property {number} 支払基準日
* @property {string} 休日調整
* @property {string} CF計上
* @property {string} 自動更新アラート
* @property {string} 利用ステータス
* @property {string} 組織名
* @property {string} 備考
* @property {string|Date} 起票ターゲット月
* @property {string|Date} 最終起票年月日
* @property {string|Date} 最終決済予定日
* @property {string} 課金サイクル - '月' or '年'
* @property {number} 想定月間削減時間 - hour/month
*/
Step 2-B: 200_data/202_repository.js に SubscriptionRepository 追加
HeadcountRepository パターンを参考にしつつ、他リポ(OrderRepository 等)の findAll / save / append 3 メソッド構成と対称化(failure_patterns #25):
var SubscriptionRepository = {
_getSheet: function() {
return Utils.getSheetByKey('BUD_SUBS', '23_bud_subscription');
},
/**
* @returns {{ headers: string[], dtos: SubscriptionDTO[] }}
* Read-only entry point for MAS-176. save/append は将来 RPA 拡張時に実装。
*/
findAll: function() {
return readSheetAsDtos_(SubscriptionRepository._getSheet());
},
/** Read-only by design - MAS-176 v1. RPA 起票ロジックは現状 02_rpa_subscription.js 側で直接シート参照のため未使用。 */
save: function(dto) {
throw new Error('SubscriptionRepository.save: not implemented in MAS-176 v1');
},
append: function(dto) {
throw new Error('SubscriptionRepository.append: not implemented in MAS-176 v1');
}
};
呼び出し側は dto['有効フラグ'] で FALSE スキップ、dto['想定月間削減時間'] で時間取得、dto['課金サイクル'] で月額換算、dto['税抜金額_計画'] + dto['消費税額_計画'] で原価取得。
Step 3: 400_domain/450_ai_tool_roi_engine.js 新設(計算エンジン)
新規ファイル番号 450(449 配当ミックスの次・451 multiyear_planner 手前)。
var AiToolRoiEngine = (function() {
/** 役員時間単価を算出: 年収 / 年間稼働時間。Infinity/NaN scrub は上流で実施。 */
function _calcExecHourlyRate_() {
var basis = String(Constants.getParam('MAS176_HOURLY_RATE_BASIS', 'EXEC_ANNUAL_DIV_HOURS'));
var execName = String(Constants.getParam('MAS176_EXEC_MEMBER_NAME', '代表取締役'));
var annualHours = Number(Constants.getParam('MAS176_EXEC_ANNUAL_HOURS', 2000));
if (!Number.isFinite(annualHours) || annualHours <= 0) {
Utils.persistLog('WARN', 'AiToolRoiEngine._calcExecHourlyRate_', 'annualHours <= 0, fallback to 2000', '');
annualHours = 2000;
}
var hcResult = HeadcountRepository.findAll();
var monthlySalarySum = 0;
// 注: Phase 0 で 22_bud_headcount の実列名を確認。氏名・ポジションが分離列の場合は AND 条件に変更。
for (var i = 0; i < hcResult.dtos.length; i++) {
var dto = hcResult.dtos[i];
var flag = dto['有効フラグ'];
if (flag === false || String(flag).toUpperCase() === 'FALSE') continue;
var name = String(dto['氏名・ポジション'] || '').trim();
if (name !== execName) continue;
monthlySalarySum += Number(dto['月額給与・報酬']) || 0;
}
var annualSalary = monthlySalarySum * 12;
var hourlyRate = annualHours > 0 ? annualSalary / annualHours : 0;
// 上流で Infinity/NaN を scrub(per-tool ロジック簡潔化)
if (!Number.isFinite(hourlyRate)) {
Utils.persistLog('WARN', 'AiToolRoiEngine._calcExecHourlyRate_', 'Non-finite hourlyRate scrubbed to 0', '');
hourlyRate = 0;
}
return { hourlyRate: hourlyRate, basis: basis, execName: execName, annualHours: annualHours, annualSalary: annualSalary };
}
/** 月額正規化: 課金サイクル='年' の場合は 1/12。 */
function _normalizeMonthlyCost_(dto) {
var raw = (Number(dto['税抜金額_計画']) || 0) + (Number(dto['消費税額_計画']) || 0);
var cycle = String(dto['課金サイクル'] || '月').trim();
return cycle === '年' ? raw / 12 : raw;
}
/** 全 AI ツール (有効フラグ=TRUE & 想定月間削減時間 > 0) の月次 ROI を集計 */
function calculateMonthlyRoi() {
var enabled = String(Constants.getParam('FF_UC8_S01_CALC_ROI', 'true'));
if (!/^(true|1|yes)$/i.test(enabled)) {
return { totalCost: 0, totalSavedHours: 0, totalSavedAmount: 0, roi: 0, hourlyRate: 0, items: [], disabled: true };
}
var rateInfo = _calcExecHourlyRate_();
var subResult = SubscriptionRepository.findAll();
var totalCost = 0, totalSavedHours = 0, items = [];
for (var i = 0; i < subResult.dtos.length; i++) {
var dto = subResult.dtos[i];
var flag = dto['有効フラグ'];
if (flag === false || String(flag).toUpperCase() === 'FALSE') continue;
var status = String(dto['利用ステータス'] || '').trim();
// Phase 0 で確定した「解約済」相当値で除外(dataValidation 実値に合わせる)
if (status === '解約済' || status === '解約済み') continue;
var savedHours = Number(dto['想定月間削減時間']) || 0;
if (savedHours <= 0) continue;
var cost = _normalizeMonthlyCost_(dto);
totalCost += cost;
totalSavedHours += savedHours;
var itemRoi = cost > 0 ? (savedHours * rateInfo.hourlyRate) / cost : 0;
if (!Number.isFinite(itemRoi)) itemRoi = 0;
items.push({
toolName: String(dto['サービス・ツール名'] || '').trim(),
cost: cost,
savedHours: savedHours,
savedAmount: savedHours * rateInfo.hourlyRate,
roi: itemRoi
});
}
var totalSavedAmount = totalSavedHours * rateInfo.hourlyRate;
var roi = totalCost > 0 ? totalSavedAmount / totalCost : 0;
if (!Number.isFinite(roi)) roi = 0;
return { totalCost: totalCost, totalSavedHours: totalSavedHours, totalSavedAmount: totalSavedAmount, roi: roi,
hourlyRate: rateInfo.hourlyRate, hourlyRateInfo: rateInfo, items: items, disabled: false };
}
return { calculateMonthlyRoi: calculateMonthlyRoi };
})();
性能監視: 609_datamart_kpi.js から本エンジンを呼び出す際、メイン処理ブロックを Utils.measureRuntime_ でラップ(GAS 6分制限・将来のパフォーマンス劣化監視)。
Step 4: 93_kpi_dashboard に K7 AI ツール ROI を追加
600_report/609_datamart_kpi.js の renderKpiRows_ の rows 配列末尾に K7 ブロックを追加する(K6 ランウェイの後)。MAS-003 失敗パターン #21-#24 回避のため、KPI 値は GAS 側で AiToolRoiEngine.calculateMonthlyRoi() を実行し、リテラル数値として setValue() する(数式ではなくスカラー埋め込み)。
FF_UC8_S01_CALC_ROI = false時の Early Return: フラグ OFF 時は K7 セクション自体を生成・描画しない(行追加もスキップ)。- K7 main 行: 当月・前月・YTD はいずれも
roi倍率(小数表示)。range.setNumberFormat('0.0"x"')で4.0x形式表示。 - sub 行 1: ツール総月額コスト(税込・円・年額契約は月額換算後の値)
- sub 行 2: 想定削減時間合計(時間/月)
- sub 行 3: 想定削減金額(円/月)
- sub 行 4: 役員時間単価(円/h)
- subsub: 各ツールの個別 ROI(ツール名・月額・削減時間・ROI 倍率)
renderKpiHelperRows_ には [helper] K7 行を追加し、SPARKLINE 用列を確保するが、v1 では SPARKLINE 自体を出力しない(過去月スナップショットがないため水平直線になる UX バグ回避)。単一セルで当月 ROI のみ表示し、TODO コメントで「v2 で 99_ai_tool_roi_snap シート新設による月次スナップショット連動」を記録。
applyKpiConditionalFormat_ に以下を追加:
- ROI <
MAS176_ROI_WARN_THRESHOLD(default 2.0)→ 赤系背景 - ROI ≥
MAS176_ROI_TARGET_THRESHOLD(default 4.0)→ 緑系背景 - 中間(2.0 ≤ ROI < 4.0)→ 黄系背景
セル保護: K7 行は range.protect().setWarningOnly(true) で「GAS 書込のみ許容」を運用上明示(人手編集時の警告表示)。
Step 5: migrationMAS176SysParams 投入関数(800_ops/810_migration_mas176.js 新設)
Phase 0 で確定したメニュー定義の実在ファイル(MENU_DEFINITION 配列 or onOpen() 内 ui.createMenu())に登録する。migrationF66DividendSeed(Phase 0 で実在パス確認)と同じパターンで、6 キーを冪等投入する。
特権操作の監査要件(ADR-0028/0029 準拠):
- 実行前:
SpreadsheetApp.getUi().alert()で確認ダイアログを表示 - 実行成功時:
Utils.auditLog('RUN', 'migrationMAS176SysParams', ...)で監査ログ記録 - 各キーの投入時: 既存値 → 新値を
99_audit_logに記録(運用変更時の改定証跡)
| キー | デフォルト値 | 説明 |
|---|---|---|
MAS176_HOURLY_RATE_BASIS | EXEC_ANNUAL_DIV_HOURS | 時間単価算出方式(v1 は固定。v2 で MARKET_RATE / MAS071_BLENDED / JR_SUBSTITUTE_RATE 等を追加) |
MAS176_EXEC_MEMBER_NAME | 代表取締役 | 22_bud_headcount の氏名・ポジション列で照合(Phase 0 で実列名確定) |
MAS176_EXEC_ANNUAL_HOURS | 2000 | 年間稼働時間(8h × 250 営業日) |
MAS176_ROI_WARN_THRESHOLD | 2.0 | ROI < この値で赤系警告 |
MAS176_ROI_TARGET_THRESHOLD | 4.0 | ROI ≥ この値で緑系合格 |
FF_UC8_S01_CALC_ROI | true | Feature Flag(K7 行を非表示にする時は false・ADR-0028/0029 命名規約準拠) |
実装スコープ
| 区分 | ファイル | 変更量 | 備考 |
|---|---|---|---|
| DDL 拡張 | 100_config/101_sys_config.js | +6 行 | BUD_SUBS ヘッダー +2 列・入力列番号 +2 |
| Contracts | 000_infra/003_contracts.js | +30 行 | SubscriptionDTO JSDoc 型定義 |
| Repository | 200_data/202_repository.js | +20 行 | SubscriptionRepository 新設(findAll/save/append 3 メソッド対称) |
| Domain | 400_domain/450_ai_tool_roi_engine.js | +160 行(新規) | AiToolRoiEngine 名前空間・月額正規化ヘルパー追加 |
| KPI Render | 600_report/609_datamart_kpi.js | +60 行 | K7 main + sub 4 + subsub N + helper 行 35 + Feature Flag Early Return + setNumberFormat |
| Migration | 800_ops/810_migration_mas176.js | +60 行(新規) | 6 キー冪等投入 + 確認ダイアログ + auditLog |
| Menu | Phase 0 で特定したメニュー実存ファイル | +1 行 | 「🔧 マイグレーション」に登録 |
| 設定 | docs/_config.json | +1 行 | nav に新規仕様書登録(必須・failure_patterns #11) |
合計: 約 340 行(うち新規 220 行・既存変更 120 行)。
影響範囲
| 対象 | 種別 | 影響 | リスク |
|---|---|---|---|
23_bud_subscription 既存データ | 構造変更 | ヘッダー列 +2(Y/Z 列) | 既存行は Y/Z 列が空欄 → 課金サイクル空→「月」扱い・想定削減時間 = 0 → ROI 計算対象外(後方互換) |
setupAllSchemas | DDL 再実行 | Y/Z 列が新設される | 冪等性あり・既存値の上書きなし |
93_kpi_dashboard | レイアウト | K6 ランウェイの下に K7 が増える | 行数増加で印刷レイアウト要確認(人間検討事項 #11) |
02_rpa_subscription.js (RPA 起票) | 影響なし | Y/Z 列は計算用で起票には不使用 | サブスク INV 起票ロジックは現状維持 |
400_rpa_common.js の科目マスタ | 影響なし | 既存科目を流用(「ソフトウェア利用料」等) | 新規科目不要 |
既存テスト 901_test_runner.js | 拡張 | T-MAS176-01〜14 を追加 | 既存テストへの影響なし |
appsscript.json | 変更なし | CacheService / LockService 等の新規スコープ不要 | failure_patterns #26 遵守 |
注意事項
- #18-#20(命名造語禁止):
SubscriptionRepository/AiToolRoiEngine/MAS176_*/FF_UC8_S01_CALC_ROIキーは既存命名規則と整合する命名。「想定月間削減時間」「課金サイクル」は実装着手前に Phase 0 でBUD_SUBSヘッダーを再確認。新設の場合は本ヘッダー名を採用し、勝手に「削減工数」「効率化時間」「請求サイクル」等の同義語に差し替えない(科目マスタ照合と同様の完全一致原則)。 - #21-#24(Sheets 数式の Unicode / 列範囲膨張):
93_kpi_dashboardの K7 値は GAS 側でAiToolRoiEngine.calculateMonthlyRoi()を実行してスカラーsetValueで埋める。MATCH("AI ツール ROI", ...)のような数式ラベル解決は禁止(絵文字・全角スペース・Unicode 正規化で壊れる)。 - #25(並列実装対称性):
inputCols/autoColsの更新は片側だけにならないよう 同一の Edit ブロックで両方更新。SubscriptionRepositoryは他リポと対称なfindAll/save/append3 メソッド構成(v1 ではsave/appendは未実装エラー throw・読み取り専用を JSDoc 明示)。 - #26(oauthScopes 部分宣言禁止):
appsscript.jsonを一切編集しない。本案件は既存スコープ(spreadsheets)のみで完結。 - #27(外部 API 仕様変動): 該当なし(外部 API 不使用)。
- #29(V8→Java Infinity null): 役員月額給与が空欄・全員退職等で
annualHours = 0となった場合、hourlyRate = annualSalary / 0 = Infinityの risk あり →_calcExecHourlyRate_内で 上流 scrub(Number.isFinite(hourlyRate)ガード)。per-tool / 合算 ROI も同様に scrub。 - #31(ID 採番衝突): 新規
MAS176_*/FF_UC8_S01_CALC_ROIパラメータキーは投入前に03_sys_paramsで grep。新規ファイル番号450は449_dividend_mix_optimizer.jsと451_multiyear_planner.jsの間。810_migration_mas176.jsは809_backup_tool.jsの次(CLAUDE.md ファイル番号体系遵守)。 HeadcountRepository.findAllの有効フラグスキップ:flag === false || String(flag).toUpperCase() === 'FALSE'のパターンを_calcExecHourlyRate_内でも踏襲。- 役員特定ロジック:
MAS176_EXEC_MEMBER_NAMEで氏名・ポジション列の 完全一致 で照合。Phase 0 で22_bud_headcountの実列名(単一列か分離列か)を確認し、分離列の場合は AND 条件に変更。複数役員(共同代表)がいる場合は v1 では 1 名のみ対応・v2 で複数役員の合算をサポート(人間検討事項 #5)。 - 時間単価のフォールバック: 役員レコードが見つからない or 月額給与=0 の場合、
hourlyRate=0→ ROI=0 → ダッシュボード上に「⚠️ 役員時間単価未設定」と表示。算出に失敗したツールもUtils.persistLog('WARN', 'AiToolRoiEngine.calculateMonthlyRoi', ...)でログ出力。 - 解約済ツールの除外: Phase 0 で
BUD_SUBS.利用ステータスのdataValidation.values実値を確認し、解約済/解約済み等の実値で完全一致除外。 - MAS-013 との棲み分け: 本案件は月額 SaaS の継続投資。一時的・大型投資(オフィス開設・サーバー購入)は MAS-013(NPV/IRR)で評価。サブスクの契約形態が「単発」(CTR_SPT)の場合は ROI 計算対象外として
継続/月額のみフィルタ(人間検討事項 #6)。 - 月額/年額正規化: BUD_SUBS に
課金サイクル列(Y 列)を新設し、年の場合は(税抜+税)/12で月額換算。これにより年額一括契約と月額契約を 1 シートで混在管理可能。 - 想定削減時間のエビデンス: 主観入力のため精度担保が課題。Step 1 では
23_bud_subscriptionの備考列(U 列)にエビデンス記述を運用規約化(人間検討事項 #1)。v2 で99_ai_tool_evidenceシートに「いつ・どんなタスクで・何時間削減したか」のジャーナル化を検討。 - 個人情報管理: 役員報酬・氏名を扱うため、KPI ダッシュボードの閲覧権限・出力範囲を内部統制規程で別途明文化。役員時間単価は K7 sub 行で表示されるため、社内開示制限が必要な場合は
FF_UC8_S01_CALC_ROIを OFF にするか、表示を集計値のみに変更するオプション運用を検討(人間検討事項追加)。 - 改定証跡: ROI 閾値(warn/target)や時間単価ベース変更時は
migrationMAS176SysParams実行時にUtils.auditLogで改定日・改定者・旧値/新値を99_audit_logに記録(監査耐性)。
エッジケース
実装時に必ず以下 14 件を 901_test_runner.js に追加する単体テストでカバーする(テスト名 T-MAS176-01 〜 T-MAS176-14)。
| # | 条件 | 検知方法 | 期待される挙動 | ログ出力 |
|---|---|---|---|---|
| 1 | 23_bud_subscription の 想定月間削減時間 列が空欄 | `Number(dto['想定月間削減時間']) | 0` | |
| 2 | 想定月間削減時間 がマイナス値(誤入力) | savedHours <= 0 | 対象外(>0 ガード) | Utils.persistLog('WARN', ...) |
| 3 | 役員レコードが 22_bud_headcount に未登録 | monthlySalarySum === 0 | hourlyRate = 0 → ROI = 0 → K7 に「⚠️ 役員時間単価未設定」表示 | Utils.persistLog('WARN', 'AiToolRoiEngine._calcExecHourlyRate_', '役員未登録: ' + execName, '') |
| 4 | 役員月額給与が 0 円(未入力) | annualSalary === 0 | 同上 | 同上 |
| 5 | MAS176_EXEC_ANNUAL_HOURS が 0 以下に設定された | annualHours <= 0 | フォールバック値 2000 を使用 | Utils.persistLog('WARN', '...', 'annualHours <= 0, fallback to 2000', '') |
| 6 | サブスク有効フラグ=FALSE 行 | `flag === false | String(flag).toUpperCase() === 'FALSE'` | |
| 7 | サブスク利用ステータス=「解約済」 | String(dto['利用ステータス']).trim() === '解約済' | スキップ(無効化と同等扱い) | — |
| 8 | 税抜金額_計画 + 消費税額_計画 = 0(無料プラン) | cost === 0 | items 配列に含めるが個別 ROI は Infinity 回避のため 0 で表示・WARN ログ | Utils.persistLog('INFO', '...', '無料ツール: ' + toolName, '') |
| 9 | roi 値が Infinity / NaN | !Number.isFinite(roi) | 上流(_calcExecHourlyRate_)と最終合算の 2 段階で 0 に scrub(failure_patterns #29 直接対策) | Utils.persistLog('WARN', '...', 'Non-finite ROI scrubbed', toolName) |
| 10 | FF_UC8_S01_CALC_ROI = false | Constants.getParam('FF_UC8_S01_CALC_ROI') が 'false' 系 | K7 セクション生成自体をスキップ(Early Return)・計算スキップ | — |
| 11 | サブスクシートが存在しない(DDL 未実行) | _getSheet() が null | 空 { headers: [], dtos: [] } 返却(readSheetAsDtos_ の null guard) | — |
| 12 | 想定月間削減時間 / 課金サイクル 列がヘッダーに未追加(DDL 未実行) | dto['想定月間削減時間'] === undefined | `Number(undefined) | |
| 13 | サブスク全件が ROI 計算対象外 | items.length === 0 | K7 main 行に「📋 対象ツール未登録」表示 | — |
| 14 | 課金サイクル = 年 の年額契約 | cycle === '年' | (税抜+税)/12 で月額換算してから totalCost に加算 | — |
実データ検証
実装着手前(Phase 0)に MCP / GAS エディタで以下を確認し、Read 結果のコードスニペットを本セクションに inline 貼付する(failure_patterns #18-#20 直接対策・コード未読類推禁止)。
1. 23_bud_subscription 現行ヘッダー実態確認
101_sys_config.js の DDL 定義(24 列)と本番シートのヘッダー行を完全一致確認:
有効フラグ | 管理ID | サービス・ツール名 | 利用者・部門 | 費用科目 | 取引先名 | 契約形態 |
開始・契約年月 | 次回更新・終了年月 | 税抜金額_計画 | 消費税額_計画 | 税区分 | 決済手段 |
決済ラグ(月) | 支払基準日 | 休日調整 | CF計上 | 自動更新アラート | 利用ステータス |
組織名 | 備考 | 起票ターゲット月 | 最終起票年月日 | 最終決済予定日
DDL 定義と実シートが乖離している場合、本案件着手前に setupAllSchemas を dev で実行して同期 してから列追加に進む。inputCols / autoCols の実値(配列リテラル)も Read で取得し本セクションに貼付。
2. 22_bud_headcount 役員レコード存在確認
氏名・ポジションが単一列か分離列かを実 DDL 定義で確認(仮に氏名/ポジション/雇用形態の分離だった場合は AND 条件に変更)- 該当役員レコードの
有効フラグ= TRUE か 月額給与・報酬列が空欄でないか・正の数値か- 共同代表など複数役員がいる場合、v1 では 1 名固定の制約を運用ガイドに明記
3. 93_kpi_dashboard 既存 K1-K6 行レイアウト確認
600_report/609_datamart_kpi.js の renderKpiRows_ の rows 配列定義を Read で確認し、K7 を追加する正しい位置を特定。renderKpiHelperRows_ の helper 行構造を確認し、行 35 を追加する場所を特定。sheet.hideRows(...) の引数も同期更新する。
4. Constants.getParam のシグネチャ確認
000_infra/002_constants.js で Constants.getParam(key, default) の戻り値型を確認。string で返ってくるため Number(...) / /^(true|1|yes)$/i.test(...) で型変換が必要。
5. HeadcountRepository.findAll() の戻り値構造確認
200_data/202_repository.js で { headers: string[], dtos: Object[] } 構造を確認。dto 内のキーはヘッダー名そのまま。
6. 400_domain/449_dividend_mix_optimizer.js の名前空間パターン確認
新規ファイル 450_ai_tool_roi_engine.js の構造参考として、IIFE + 名前空間パターン(var XxxEngine = (function() { ... return { ... }; })();)を Read で確認。名前空間内の private ヘルパーは末尾 _ 付きで定義(failure_patterns #28 反映)。
7. 02_rpa_subscription.js への影響確認
400_domain/402_rpa_subscription.js で subSheet = ss.getSheetByName('23_bud_subscription') と直接シート参照しているが、列追加(Y/Z 列)は RPA 起票ロジックに影響しないことを確認(Y/Z 列は計算用で起票には不使用)。
8. メニュー定義実存ファイル確認
grep -rn 'マイグレーション' 000_infra/ 100_config/ 300_ui/ でメニュー定義の実在ファイルとデータ構造(MENU_DEFINITION 配列 / onOpen() 内 ui.createMenu() 等)を特定し、本セクションに実パス・実行番号を貼付。
9. migrationF66DividendSeed 実在ファイル確認
grep -rn 'function migrationF66DividendSeed' . で実パスを特定し、本セクションに貼付。実在しない場合は別の seed 関数(例: migrationF57Seed 等)をパターン参考にする。
10. BUD_SUBS.利用ステータス の dataValidation.values 確認
DDL の validations.values 実値を Read で確認し、解約済 / 解約済み / 停止中 等の表記揺れを _calcExecHourlyRate_ の除外条件に正確に反映。
関連ドキュメント
| ドキュメント | 関連箇所 |
|---|---|
| MAS-003 KPI ダッシュボード | dev_mas-003_kpi_dashboard.md — K1-K6 既存実装。K7 追加の参考 |
| MAS-048 HC TCO シミュレーター | dev_mas-048_hiring_tco_bep_simulator.md — HeadcountRepository.findAll パターン |
| MAS-013 NPV/IRR 一時投資判定 | dev_mas-013_npv_irr_investment.md — 棲み分け(一時投資 vs 月額 SaaS) |
| MAS-066 配当ミックス | dev_mas-066_dividend_mix_optimizer.md — 名前空間パターン参考(449_dividend_mix_optimizer.js) |
| MAS-071 稼働率連動型役員報酬 | v2 で MAS176_HOURLY_RATE_BASIS = 'MAS071_BLENDED' 連携検討(V_d ベース時間単価) |
| MAS-230 Jr 採用要件定義 | 後続連携(Jr 学習教材としての ROI 体感) |
| ADR-0028 / ADR-0029 | Feature Flag 命名規約(FF_UC{N}_S{NN}_{verb} 形式)・特権操作の監査ログ要件 |
| failure_patterns | docs/_internal/failure_patterns.md — #18-20 / #21-24 / #25 / #26 / #29 / #31 |
| dev_spec_prompt_template.md v1.10+ | 仕様書作成標準テンプレート |
| CLAUDE.md | ファイル番号体系・名前空間規約・列参照ヘッダー名ベース |
| PRD プロダクトポリシー | Human-in-the-Loop(想定削減時間の主観入力 → 月次レビュー) |
人間が検討すべき事項
- 想定削減時間のエビデンス管理: 主観入力のため精度担保方法を確立する必要あり。v1 は
備考列に文章で記述・運用規約化。監査耐性強化のため v2 で99_ai_tool_evidenceシート新設してジャーナル化(入力者・タスク内容・削減時間・実施日・編集者・日付)を強く推奨。 - 時間単価の計算ベース: 役員年収÷年間稼働時間の計算根拠(年間稼働時間 = 8h × 250 営業日 = 2000h)の妥当性。短時間労働の場合や残業多発の場合の補正を検討。
MAS176_HOURLY_RATE_BASISの v2 拡張案:MARKET_RATE(外注ハードルレート)/JR_SUBSTITUTE_RATE(Jr 採用代替コスト・MAS-230 連動)/MAS071_BLENDED(MAS-071 稼働率連動 V_d ベース)。 - ROI 閾値(warn=2.0x / target=4.0x)の設定値: 業界標準・社内基準の定義。SaaS 投資は一般に ROI 3-5x が目安だが、AI ツールは初期スピードアップが大きいため 4-10x を期待値とする見方もある。実データ蓄積後にチューニング。閾値変更時は
99_audit_logに改定日・改定理由・旧値/新値を記録(監査証跡)。 - Claude Code Max の位置づけ: フルタイム Jr 代替として見るか、個別ツール扱いとするか。月額 $200 で Jr 入社(推定 50-80 万円/月)の 1/15-1/25 のコスト → ROI 計算では月 100h 以上の削減を想定しないと過小評価リスク(個別ツール扱いだと 20-30h/月で 4-6x ROI 程度)。
- 複数役員対応: v1 では 1 名固定(
MAS176_EXEC_MEMBER_NAME)。共同代表・取締役複数の場合は合算するか、最高位役員の単価を使うか。MAS176_EXEC_MEMBER_NAMES(複数形・カンマ区切り)への拡張を v2 で検討。複数役員体制移行時の手動運用手順を内部統制規程に明記。 - 契約形態フィルタ:
継続/月額のみ ROI 計算対象とし、スポット(CTR_SPT)は除外するか。スポット契約の AI ツール(年契約一括等)の ROI 計算方法を別途定義。 - マイナス ROI の扱い: 削減時間が想定を下回った場合 ROI < 1.0x になる。1.0x 未満のツールは UI 上で「解約候補」アラート表示するか、月次レビュー時の人間判断に委ねるか。
- 税抜 vs 税込の選択: 月額コストは税込で計算するが、
想定削減金額(人件費換算)は税込/税抜どちらか。役員報酬の月額給与・報酬は税込みのため税込ベースで統一が妥当。 - 削減時間の上限ガード: 1 ツールあたり想定月間削減時間 > 200h は誤入力疑い(フルタイム超)。Step 1 のバリデーションで warn 表示するか。
- 過去月 ROI のスナップショット保存: v1 は SPARKLINE を出さず単一セル表示。v2 で
99_ai_tool_roi_snapシート新設して月次自動保存→ SPARKLINE 連動。 - K7 行追加による 93_kpi_dashboard レイアウト影響: K6 ランウェイの下に K7 が増えると印刷 1 ページに収まらなくなるリスク。helper 行の
sheet.hideRows(...)の数値も同期更新。 FF_UC8_S01_CALC_ROI = false時の K7 行削除 vs 非表示 vs Early Return: 本仕様では Early Return(生成自体スキップ)を採用。SPA 版 KPI ダッシュボード(MAS-232)との互換性も確認。- AI ツール以外のサブスク(Slack / Notion 等)の扱い:
想定月間削減時間を入力できるが、本来 ROI 評価対象は AI ツール限定か。費用科目で「AI ツール」専用科目を新設するか、現行の「ソフトウェア利用料」のまま備考で識別するか。 - 個別ツール ROI の表示粒度: subsub 行で各ツールの ROI を出すと行数が爆増(5-10 ツールなら +10 行)。クリッカブルな展開/折りたたみ UI は GAS では困難 → CSV エクスポートまたは別シート(
93b_ai_tool_roi_detail)への分離を v2 で検討。 - Jr 採用後の評価軸変更: MAS-230 で Jr 採用後は「Jr 1 名分のコスト」が新しい比較基準になるため、
MAS176_HOURLY_RATE_BASISをJR_SUBSTITUTE_RATEに切り替える運用検討。 - 個人情報・権限管理: 役員報酬・氏名を K7 sub 行に表示するため、KPI ダッシュボードの閲覧権限・出力範囲を別途内部統制規程で明文化。社内開示制限が必要な場合は
FF_UC8_S01_CALC_ROIを OFF にするか、集計値のみ表示するオプションを検討。 - KPI セルの編集競合: K7 行に人手で値を直接入力されると GAS 再実行時に上書きされ混乱の元。
range.protect().setWarningOnly(true)で警告表示するか、setProtectionで完全保護するかを運用判断。 - 月次締後の再集計手順: サブスク変更(解約・想定削減時間修正)後の K7 再集計タイミング。月次締後の遡及修正可否と監査証跡保存方針を運用規定に明記。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-176「開発者向け AI ツール投資 ROI 管理(D.8 派生)」を
Phase 0 → Step 1 → Step 6 の順で段階 PR 化して実装してください。
## Phase 0: 実データ Read 裏取り(必ず最初に実施・failure_patterns #18-#20 直接対策)
以下を Read し、行番号や配列内容を **論理的にロケート** してください
(ハードコードされた行番号は古い可能性があるため、シンボル名で探索):
1. `100_config/101_sys_config.js` の `BUD_SUBS` ヘッダー定義と
`schemas['BUD_SUBS'].inputCols` / `autoCols` の実値
2. `200_data/202_repository.js` の `HeadcountRepository` パターンと
`readSheetAsDtos_` 共通ヘルパー
3. `22_bud_headcount` の DDL ヘッダー(氏名関連列が単一か分離か)
4. `grep -rn 'マイグレーション' 000_infra/ 100_config/ 300_ui/` で
メニュー定義の実在ファイルとデータ構造特定
5. `grep -rn 'function migrationF66DividendSeed' .` で実パス特定
6. `BUD_SUBS.利用ステータス` の `dataValidation.values` 実値
7. `400_domain/449_dividend_mix_optimizer.js` の名前空間 IIFE パターン
8. `600_report/609_datamart_kpi.js` の `renderKpiRows_` の rows 配列と
`renderKpiHelperRows_` helper 行構造
9. `000_infra/002_constants.js` の `Constants.getParam` シグネチャ
10. `400_domain/402_rpa_subscription.js` で X/Y/Z 列が起票に不使用と確認
11. `docs/_internal/failure_patterns.md` の #18-20 / #21-24 / #25 / #26 / #29 / #31
Phase 0 完了後、仕様書の「実データ検証」セクションに Read 結果のコード
スニペットを inline 貼付してから Step 1 に進む。
## 修正対象ファイル
- `100_config/101_sys_config.js`(変更・BUD_SUBS ヘッダー +2 列)
- `000_infra/003_contracts.js`(変更・SubscriptionDTO JSDoc 追加)
- `200_data/202_repository.js`(変更・SubscriptionRepository 新設)
- `400_domain/450_ai_tool_roi_engine.js`(**新規・約 160 行**)
- `600_report/609_datamart_kpi.js`(変更・K7 ブロック追加)
- `800_ops/810_migration_mas176.js`(**新規・約 60 行**)
- Phase 0 で特定したメニュー実存ファイル(変更・登録 +1 行)
- `docs/_config.json`(変更・nav に新規仕様書登録 +1 行・**failure_patterns #11 必須**)
- `900_test/901_test_runner.js`(変更・T-MAS176-01〜14 追加)
## Step 1: BUD_SUBS DDL 拡張(Sonnet 推奨)
`101_sys_config.js` の `BUD_SUBS` ヘッダー配列末尾に
"課金サイクル" / "想定月間削減時間" の 2 列を順に追加。
`schemas['BUD_SUBS'].inputCols` 配列の末尾に `25` / `26` を追加(Y/Z 列)。
autoCols は不変。**両配列の更新は同一 Edit ブロック**(failure_patterns #25)。
動作確認: dev で `setupAllSchemas` を実行 → `23_bud_subscription` の
Y/Z 列に "課金サイクル" / "想定月間削減時間" が表示され薄青背景
(INPUT_BG)になること。Y 列の dataValidation で「月」「年」固定。
## Step 2: SubscriptionDTO + SubscriptionRepository 新設(Haiku で OK)
`003_contracts.js` に `SubscriptionDTO` JSDoc 型定義を追加。
`202_repository.js` の HeadcountRepository の直後に
`SubscriptionRepository` を追加。`_getSheet` + `findAll` + `save`(未実装エラー)
+ `append`(未実装エラー)の 4 メソッド構成で他リポと対称化。
BUD_SUBS シートキーを使用。
## Step 3: AiToolRoiEngine 新設(Opus 推奨・名前空間設計)
`400_domain/450_ai_tool_roi_engine.js` を新設。IIFE + 名前空間パターン
(449_dividend_mix_optimizer と同形式)。`calculateMonthlyRoi()` を public、
`_calcExecHourlyRate_()` / `_normalizeMonthlyCost_()` を private(末尾 `_`
必須・failure_patterns #28)。
実装ポイント:
- 役員特定は Phase 0 で確認した実列名で完全一致(単一/分離列に応じ条件変更)
- 有効フラグ FALSE スキップ
- `Number.isFinite()` で **上流(hourlyRate 段階)と最終合算の 2 段階 scrub**(failure_patterns #29)
- `FF_UC8_S01_CALC_ROI = false` 時は `disabled: true` を含む空 result 返却
- 課金サイクル='年' は (税抜+税)/12 で月額換算
## Step 4: 93_kpi_dashboard に K7 追加(Opus 推奨・既存パターン横展開)
`609_datamart_kpi.js` の `renderKpiRows_` の rows 配列末尾(K6 ランウェイ後)に
K7 main + sub 4 + subsub N を追加。
**値はすべて GAS 側で `AiToolRoiEngine.calculateMonthlyRoi()` 実行後の
スカラーを `setValue()` で埋める**(数式不使用・failure_patterns #21-24 直接対策)。
- `FF_UC8_S01_CALC_ROI` 評価し OFF なら K7 セクション生成自体スキップ(Early Return)
- `range.setNumberFormat('0.0"x"')` で 4.0x 形式表示
- メイン処理ブロックを `Utils.measureRuntime_` でラップ(性能監視)
- v1 では SPARKLINE 出力せず単一セル表示(TODO コメント残す)
- `range.protect().setWarningOnly(true)` で人手編集警告
- `applyKpiConditionalFormat_` に ROI 閾値ルール(warn=2.0/target=4.0)追加
- `sheet.hideRows(...)` の引数を helper 行 +1 に同期更新
## Step 5: マイグレーション関数 + メニュー登録(Sonnet 推奨)
`800_ops/810_migration_mas176.js` を新設。`migrationMAS176SysParams()` 関数で
6 キーを冪等投入(F66 等の既存 seed パターン参照)。
特権操作の監査要件:
- 実行前: `SpreadsheetApp.getUi().alert()` で確認ダイアログ
- 各キー投入時: 既存値 → 新値を `Utils.auditLog('RUN', ...)` で記録
- 完了時: `99_audit_log` に改定日・改定者・改定理由を記録
Phase 0 で特定したメニュー実存ファイルに
`{ label: 'MAS-176: AI ツール ROI sys_params 投入', funcName: 'migrationMAS176SysParams', ... }` を追加。
## Step 6: 901_test_runner.js テスト追加(Sonnet 推奨)
T-MAS176-01〜14 の 14 件をエッジケース表に対応する形で追加。
特に T-MAS176-09(Infinity scrub 上流/下流 2 段階)、
T-MAS176-12(DDL 未実行検知)、T-MAS176-14(年額契約月額換算)は必須。
## 制約
- `appsscript.json` を一切編集しない(failure_patterns #26 厳守)
- `02_rpa_subscription.js` の RPA 起票ロジックは変更禁止
- 既存 K1-K6 の行番号を変更しない(既存条件付き書式が壊れる)
- `想定月間削減時間` / `課金サイクル` ヘッダー名は **完全一致**(同義語禁止)
- `docs/_config.json` への nav 登録を忘れない(failure_patterns #11)
- 行番号や配列インデックスはハードコードせず、シンボル名で論理的にロケート
## デプロイ手順
- Phase 0 → Step 1-6 を独立 PR 化推奨(rollback 容易性)
- 各 PR で dev push → setupAllSchemas → migrationMAS176SysParams 実行 →
AiToolRoiEngine.calculateMonthlyRoi() を GAS エディタで実行確認 →
buildKpiDashboard で K7 描画確認 → prod push
- コミットメッセージ: `feat(MAS-176 Step N): <内容>`
## failure_patterns チェックリスト
- [ ] Phase 0: 実データ Read 完了・スニペット仕様書貼付済み
- [ ] #18-20: SubscriptionRepository / AiToolRoiEngine / MAS176_* / FF_UC8_S01_CALC_ROI キーは Read で裏取り済み
- [ ] #21-24: K7 値は GAS 側スカラー埋め込み(数式不使用)
- [ ] #25: inputCols / autoCols 両方を同一 Edit で更新、SubscriptionRepository は findAll/save/append 対称
- [ ] #26: appsscript.json 不変
- [ ] #29: roi の Number.isFinite() ガード上流/下流 2 段階実装済み
- [ ] #31: 新規ファイル番号 450 / 810 と MAS176_* / FF_UC8_S01_CALC_ROI キーの衝突確認済み
推奨実行モデル
| Phase | 推奨モデル | 根拠 |
|---|---|---|
| Phase 0 実データ Read 裏取り | Claude Sonnet 4.6 | 機械的な Read + grep 作業 |
| Step 1 BUD_SUBS DDL 拡張(ヘッダー +2 / inputCols 配列更新) | Claude Sonnet 4.6 | パターン適用・既存配列への追加のみ |
| Step 2 SubscriptionDTO + SubscriptionRepository 新設 | Claude Haiku 4.5 | HeadcountRepository を機械的に複製 |
| Step 3 AiToolRoiEngine 新設(名前空間 + 計算ロジック + 月額正規化) | Claude Opus 4.7 | 新規ドメインエンジン設計・複数 Repository 横断・Infinity scrub 2 段階 |
| Step 4 93_kpi_dashboard K7 追加 | Claude Opus 4.7 | 既存 K1-K6 レイアウト整合性 + helper 行更新 + 条件付き書式追加 + Feature Flag Early Return |
| Step 5 マイグレーション関数 + メニュー登録 + auditLog | Claude Sonnet 4.6 | パターン横展開 |
| Step 6 901_test_runner.js テスト追加 | Claude Sonnet 4.6 | エッジケース表既定義・パターン化 |
仕様書レビュー (scripts/4_review_specs_by_gemini.js) | Gemini 3 Pro Preview + Deep Think | ROI 計算ロジック・閾値設定の第三者検証 |
変更履歴
| 日時 | バージョン | 変更内容 |
|---|---|---|
| 2026-04-30 | v0.1 (仕様書完了・Draft) | 初版作成。tasks/prompts/task_F-43.md(手動骨格)を input として Claude Opus 4.7 (1M context) で本体起草。14 セクション全網羅。約 280 行(新規 200 行 + 既存変更 80 行)+ 工数 1.5-2 週間。 |
| 2026-05-13 | v0.2 (3 モデルレビュー Critical/Major 反映) | Gemini/Claude Opus/GPT-4.1 の 3 モデルレビューを受けて修正: (a) Feature Flag 命名規約準拠: MAS176_ENABLED → FF_UC8_S01_CALC_ROI(ADR-0028/0029)/ (b) SubscriptionDTO 追加: 003_contracts.js に JSDoc 型定義を追加(データ境界明示)/ (c) Phase 0 実データ Read 裏取り強制化: BUD_SUBS 列番号・22_bud_headcount 実列名・メニュー実存ファイル・migrationF66 実パス・利用ステータス dataValidation を着手前 Read 必須化 / (d) 月額/年額正規化対応: 課金サイクル 列を Y 列に追加(25 列目)、想定月間削減時間 は Z 列(26 列目)に変更 / (e) SubscriptionRepository API 対称化: findAll / save / append 3 メソッド構成(v1 は save/append 未実装エラー throw)/ (f) 特権操作監査要件追加: マイグレーション実行時の確認ダイアログ + Utils.auditLog 記録 / (g) NumberFormat 明記: '0.0"x"' で 4.0x 表示 / (h) SPARKLINE v1 取り下げ: 過去スナップショット未保存のため水平直線 UX バグ回避、単一セル表示に変更 / (i) Infinity scrub 2 段階化: 上流(hourlyRate 段階)+ 最終合算の 2 段階 / (j) 個人情報・改定証跡・セル保護: 人間検討事項 #16-#18 追加 / (k) 実装規模約 340 行(新規 220 行 + 既存変更 120 行)に増加。 |
仕様書作成プロンプト
展開して表示(投入プロンプト全文・再現性 / 監査性のため記録)
あなたは GAS 会計システム (bizlp-gas-accounting) のシニア開発者兼仕様書ライターです。
## タスク
案件 ID **MAS-176**「開発者向け AI ツール投資 ROI 管理(D.8 派生)」の開発仕様書を作成し、`/Users/ts_kuma/projects/my-gas-project-doc/docs/dev/dev_mas-176_ai_tool_roi.md` に保存してください。
## 案件概要 (todo_master_tables.md L437)
- **Phase**: P1.5 / **★**: ★★★ / **Layer**: 🌐 Cross (Domain + Data + Report)
- **D.8 §2.e-6 抽出案件**
- **目的**: 月 2.5 万円の AI ツール投資が月 10 万円相当の生産性を生むことを定量証明 + 継続的 AI ツール投資の正当化と予算確保
- **実装スコープ**:
- `23_bud_subscription` シートに `est_hours_saved_per_month`(想定月間削減時間)列を DDL で追加 (`101_sys_config.js` 更新)
- `22_bud_headcount` の役員年収÷稼働時間で算出した時間単価を乗じて月次 ROI を計算
- `93_kpi_dashboard` に出力
- 計算ロジックは `600_report/` または `400_domain/` の既存ファイルへの関数追加で対応 (新規 GAS ファイル不要の可能性あり)
- **対象ツール例**: Cursor Pro $20 / Claude Max 5x $100 / GitHub Copilot Pro $10 / v0 $20 / ChatGPT Plus $20 (合計月 $170 ≒ 25,500 円)
- **MAS-013 (大型一時投資 NPV/IRR) との違い**: 月額 SaaS マイクロ継続投資をサブスクマスタ連動で月次自動モニタリング
## 検討事項
1. 想定削減時間のエビデンス管理 (主観入力の精度担保方法)
2. 時間単価の計算ベース (役員報酬の月間稼働時間換算方法)
3. ROI 閾値と警告アラートの設定値
4. Claude Code Max の位置づけ (フルタイム Jr 代替として見るか個別ツール扱いか)
## 必読ファイル (調査して固有名詞を確定)
実装の前に以下のファイルを `Read` で参照し、関数名・列名・定数名を確実に存在するものに揃えてください:
1. `/Users/ts_kuma/projects/my-gas-project-doc/docs/_internal/dev_spec_prompt_template.md` — 仕様書 14 セクションテンプレート (必須準拠)
2. `/Users/ts_kuma/projects/my-gas-project-doc/docs/_internal/failure_patterns.md` — #18-#31 の失敗パターン (#18-#20 命名造語 / #25 並列実装対称性 / #26 oauthScopes / #29 V8→Java Infinity null / #31 ID 採番衝突 を必ずインライン警告)
3. `/Users/ts_kuma/projects/my-gas-project-doc/100_config/101_sys_config.js` — DDL 定義 (`23_bud_subscription` の現行スキーマ確認)
4. `/Users/ts_kuma/projects/my-gas-project-doc/200_data/202_repository.js` — `SubscriptionRepository` の有無確認 + `findAll()` パターン
5. `/Users/ts_kuma/projects/my-gas-project-doc/600_report/` 配下のマートビルダー (KPI ダッシュボード書込パターン)
6. `/Users/ts_kuma/projects/my-gas-project-doc/CLAUDE.md` — コーディング規約・列参照ヘッダー名ベース・名前空間
7. 参考: `/Users/ts_kuma/projects/my-gas-project-doc/docs/dev/dev_mas-231_gas_perf_optimization.md` — 直近作成した同フォーマット仕様書
## 仕様書フォーマット要件 (14 セクション)
`dev_spec_prompt_template.md` 準拠。以下を必ず含める:
- フロントマター (id: MAS-176, aliases: ["F-176"], type: Story, status: Draft)
- 概要テーブル (案件 ID / カテゴリ / Phase / 優先度 / 所要時間 / 前提案件 / 後続案件)
- 目的 / 修正方針 / 実装スコープ / Step 分割 (3-5 Step 程度)
- 注意事項 (failure_patterns 参照含む)
- エッジケース表 (10 件以上、テーブル化)
- 人間が検討すべき事項 (10 件以上)
- 推奨実行モデル (Step ごとに Haiku/Sonnet/Opus)
- 関連ドキュメント
- 開発プロンプト (Claude Code 向け実装指示)
- 変更履歴
- 末尾に <details><summary>展開して表示</summary> ブロックでこのプロンプト全文を記録
## 必ず守る規約
- 列参照はヘッダー名ベース (`indexOf` / `buildHeaderIndex_`)、列番号ハードコード禁止
- 有効フラグ=FALSE 行スキップ
- 名前空間: `Contracts` / `SubscriptionRepository` / 新設名前空間は `400_domain/` 配下
- ファイル名は番号体系 (000_infra/100_config/200_data/300_ui/400_domain/500_import/600_report/800_ops/900_test) に従う
- failure_patterns #18-#20 (命名造語禁止) — 推測で関数名を作らず Read で実在確認
## アウトプット
ファイル: `docs/dev/dev_mas-176_ai_tool_roi.md`
Step 分割で `Write` → `Edit` で完成させてください (一気に書ききろうとせず、骨格 → 各セクションの順):
1. Step 2-1: 骨格 Write (フロントマター + 14 H2 見出し)
2. Step 2-2: 概要〜注意事項 Edit
3. Step 2-3a: エッジケース〜人間検討事項 Edit
4. Step 2-3b: 開発プロンプト〜変更履歴 Edit
5. Step 2-4: <details> プロンプト全文記録 Edit
最終確認として行数を `wc -l` で測定し報告してください (期待: 350-500 行)。