概要

項目内容
案件 IDMAS-042
カテゴリFP&A・レポーティング
対象ファイル(変更あり)100_config/101_sys_config.jsschemasMST_HURDLE 追加)
200_data/202_repository.js(末尾に HurdleRateRepository 追記)
400_domain/431_investment_analyzer.js(新規・全判定ロジック集約)
000_infra/002_constants.jsMENU_DEFINITION📊 マート更新 カテゴリに 1 項目追加)
出力先シート68_report_investment_gonogov1.2 で実装時に変更 — 旧: MAS-013 既存出力 67_report_investment_analysis に判定ラベル列を追記する案。新: 判定結果専用シート 68_report_investment_gonogo を新設し MAS-013 出力との視認性を分離)
22_bud_headcount(採用投資スクリーニング結果は別処理。HC シート自体への判定列追加は未実施・screenHiringInvestments で別アラート出力)
前提案件MAS-013(投資回収シミュレーション・仕様書)/ MAS-017(WACC 計算・未実装)

MAS-013 が算出する NPV / IRR / Payback / ROI に対し、投資カテゴリー別の閾値と照合して Go / グレー / No-Go の 3 段階ラベルを自動付与する判断補助エンジン。MAS-013 は計算エンジン、MAS-042 は判断エンジンという役割分担で、両者が揃うことで「いくらなら Go / いくらなら No-Go」を即断できる経営判断基盤が完成する。

ハードルレートはマスタ管理(29_mst_investment_hurdle 新設)することで、税制改正(経営強化税制 B 類型 5%→7%、2025 年改正)や業界相場変動をコード改修なしにマスタ更新だけで吸収できる設計。採用投資(離職リスク係数反映)と業務委託→正社員化アラート(月 50 万円 × 継続 12 ヶ月超の検知)の 2 つの特殊スクリーニングも同一エンジンに集約する。

目的

  • MAS-013 計算結果の解釈を自動化: NPV / IRR / Payback / ROI を閾値と照合してラベル化することで、投資稟議の属人化・判断基準のバラツキを解消する
  • 閾値変動のマスタ管理: ハードコード禁止原則(CLAUDE.md コーディング規約)に従い、税制改正・金利変動・業界相場の変化をマスタ更新だけで追随可能にする
  • 採用投資の定量評価: 採用費 + オンボーディング工数 + 離職リスク係数から実効 Payback を算出し、職種別閾値で Go/No-Go を判定する(採用稟議を勘・感覚から脱却させる)
  • 業務委託→正社員化の損益分岐アラート: 月 50 万円 × 継続 12 ヶ月超の業務委託を検知し、正社員化時のコスト差(20-30% 減)を自動試算することで、コスト最適化の機会を能動通知する

現在のコード

600_report/610_service_investment_analysis.js(MAS-013 計算エンジン)

MAS-013 で実装済みの計算エンジン。25_bud_investment_caseDDL 管理)を入力、67_report_investment_analysis(DDL 管理外・動的上書き)を出力とし、以下の 4 指標を計算する(MAS-013 仕様書 §出力シート 67_report_investment_analysis より):

  • NPV(正味現在価値)
  • IRR(内部収益率, %)
  • 回収期間(Payback Period, 年)
  • ROI(投資収益率, %)

MAS-042 はこの出力シートに 判定ラベル列(Go / グレー / No-Go) を追記する形で連携する。計算ロジックには手を入れない(MAS-013 の非侵襲拡張)。

200_data/202_repository.js(Repository パターン)

読み取り専用マスタの標準形として AccountRepository(L304-350)と PartnerRepository(L356-406)が実装済み。本案件の HurdleRateRepositoryAccountRepository のキャッシュパターン(_cache: null / findAsMap() 内で有効フラグスキップ + キャッシュ返却 / resetCache())に完全準拠する。

  • readSheetAsDtos_(sheet)(L19): ファイルスコープのプライベート関数。戻り値 { headers: string[], dtos: Object[] }rows ではなく dtos)。HurdleRateRepository を同ファイル末尾に追記すれば直接呼び出し可能。
  • Utils.getSheetByKey(key, fallbackName)000_infra/004_utils.js L302): システムキーで GID 解決、フォールバック名で GID 未登録時に対応。

100_config/101_sys_config.js(DDL スキーマ)

setupAllSchemas() 関数内(L802 起点)の const schemas = {...}(L879 起点)でシート毎の headers / color / validations を宣言的に定義。MAS-042 ではここに 'MST_HURDLE': { headers: [...], color: "#666666" } を追加する。DDL 適用後、initConfigs()(L1596)で 01_sys_config にシートキーを登録する手順が標準フロー。

000_infra/002_constants.js(MENU_DEFINITION・COLORS・getParam)

  • MENU_DEFINITION(L206 起点): 📋 サイドバー: 📊 マート更新 カテゴリ(L231-241)の items 配列末尾に { label, funcName, description } を追加する形式。
  • Constants.COLORS(L36-55): HEADER_CONFIG#d9ead3 緑・Go 用)/ PROFIT_BG#fff2cc 黄・グレー用)/ WARN_RED_BG#f4cccc 赤・No-Go 用)が実在。条件付き背景色の引数として使用する。
  • Constants.getParam(key, defaultVal)(L147-167): 03_sys_params からパラメータを読み込むヘルパー。本案件では採用スクリーニング・業務委託アラートの閾値を 03_sys_params で管理するために使用する。

400_domain/402_rpa_subscription.js(業務委託検知の入力元)

23_bud_subscription シートに 契約形態 / 税抜金額_計画 / 開始・契約年月 列が存在(BUD_SUBS schema headers L943 で実在確認済)。業務委託→正社員化アラートはこのシートの 契約形態 = '業務委託' 行を対象として、金額閾値 × 継続月数で検知する。

修正方針

全判定ロジックを新規 400_domain/431_investment_analyzer.js に集約する 4 機能統合設計。既存 MAS-013 のロジックは無変更、出力シートへの追記のみで連携する。

Step 1: DDL 追加 + HurdleRateRepository 新設

101_sys_config.jsschemas 配列に追加:

'MST_HURDLE': {
  headers: ["有効フラグ", "カテゴリー名", "WACC閾値(%)", "Payback閾値(年)", "ROI閾値(%)", "年商比上限(%)", "備考"],
  color: "#666666"
},

カラム構成は AccountRepository / PartnerRepository と同じく 有効フラグ を先頭に配置。カテゴリー名は 採用 / 設備 / SaaS / ERP / IT小物 / 一般CAPEX の 6 区分を初期投入予定。

01_sys_config へのシステムキー登録: サイドバー 🔧 開発・設定 → シートGID取得・リンクinitConfigs / L1596)を実行して MST_HURDLE キーを登録する(DDL 適用後 1 回のみ)。

202_repository.js 末尾に HurdleRateRepository を追記:

// =====================================================================
// HurdleRateRepository — 29_mst_investment_hurdle (読み取り専用マスタ)
// =====================================================================

var HurdleRateRepository = {
  _getSheet: function() {
    return Utils.getSheetByKey('MST_HURDLE', '29_mst_investment_hurdle');
  },
  findAll: function() {
    return readSheetAsDtos_(HurdleRateRepository._getSheet());
  },
  findAsMap: function() {
    if (HurdleRateRepository._cache) return HurdleRateRepository._cache;
    var result = HurdleRateRepository.findAll();
    var map = {};
    for (var i = 0; i < result.dtos.length; i++) {
      var dto = result.dtos[i];
      var flag = dto['有効フラグ'];
      if (flag === false || String(flag).toUpperCase() === 'FALSE') continue;
      var name = String(dto['カテゴリー名'] || '').trim();
      if (!name || map[name]) continue;  // 重複は先行行採用
      map[name] = {
        wacc:              Number(dto['WACC閾値(%)']) || null,
        payback:           Number(dto['Payback閾値(年)']) || null,
        roi:               Number(dto['ROI閾値(%)']) || null,
        revenueRatioCap:   Number(dto['年商比上限(%)']) || null,
      };
    }
    HurdleRateRepository._cache = map;
    return map;
  },
  _cache: null,
  resetCache: function() { HurdleRateRepository._cache = null; },
};

AccountRepository.findAsMap()(L323-341)のパターンと完全一致。有効フラグ FALSE / false の行をスキップし、同一カテゴリー名の重複行は先着行を採用(後行は findAsMap 内で検知 → Utils.persistLog('WARN', ...) 記録)。

Step 2: 400_domain/431_investment_analyzer.js 新設

グローバルエントリポイントrunInvestmentGoNoGoAnalysis()MENU_DEFINITIONfuncName と完全一致)。内部で プライベートサブ関数(末尾 _ で示される GAS 慣例の private 関数)applyGoNoGoToReport_() / screenHiringInvestments_() / detectOutsourcingAlerts_() を順次呼び出す設計。runInvestmentGoNoGoAnalysis() が冒頭で HurdleRateRepository.findAsMap()1 回だけ 呼び出し、閾値マップを引数として各サブ関数に渡す(シート読み込みの多重実行防止)。これら 3 サブ関数は外部からは呼び出されず、runInvestmentGoNoGoAnalysis() のみが公開エントリ。

  • applyGoNoGoToReport_(hurdleMap): 67_report_investment_analysis の行を走査し、投資カテゴリー列(MAS-013 出力の既存列)で hurdleMap[category] を引き当てる。NPV ≥ 0 かつ IRR ≥ WACC 閾値 かつ Payback ≤ 閾値 かつ ROI ≥ 閾値 → Go、±10% の境界値は グレー、いずれか未達は No-Go。判定列(ヘッダー Go/No-Go 判定)に書き込み、条件付き背景色を設定。
  • screenHiringInvestments_(hurdleMap): 22_bud_headcount の採用予定行(入社年月 が未来日付)を対象に、採用費 + オンボーディング工数(年収 × HC_ONBOARDING_COST_RATE、既定 0.25)× 離職リスク係数(HC_TURNOVER_RATE、既定 0.527=厚労省小規模法人 3 年以内離職率)から実効 Payback を算出。職種別閾値(コンサル/営業 6-12 ヶ月、エンジニア 12-18 ヶ月、新卒 24-36 ヶ月)はマスタの カテゴリー名='採用-職種' 行で個別管理(将来拡張余地)、MVP では 採用 カテゴリー共通閾値を参照。
  • detectOutsourcingAlerts_(hurdleMap): 23_bud_subscription契約形態 = '業務委託' 行を走査し、税抜金額_計画 ≥ OUTSOURCING_ALERT_AMOUNT(既定 500,000)かつ 開始・契約年月 から OUTSOURCING_ALERT_MONTHS(既定 12)ヶ月以上経過した行を抽出。正社員化時の年間コスト差(20-30% 減)を試算し、Utils.persistLog('WARN', 'detectOutsourcingAlerts_', msg, JSON.stringify(detail))99_error_log に記録 + Utils.toastResult('detectOutsourcingAlerts_', summary, 8) で件数サマリーを Toast 通知する 2 段構成。

Step 3: MENU_DEFINITION への 1 行追記

000_infra/002_constants.jsMENU_DEFINITION(L206)内、📋 サイドバー: 📊 マート更新 カテゴリ(L231 起点)の items 配列末尾(buildFiveYearForecast / openWhatIfSidebar の直後、L240 相当)に以下を追加:

{ label: '📈 投資案件 Go/No-Go 判定', funcName: 'runInvestmentGoNoGoAnalysis', description: '29_mst_investment_hurdle の閾値で F-13・採用・業務委託の Go/No-Go を自動判定' },

funcName の値 'runInvestmentGoNoGoAnalysis' は Step 2 で定義するグローバル関数名と 完全一致 させること(failure_patterns #20 命名造語禁止)。

Step 4: テスト追加(900_test/901_test_runner.js

  • calcEffectivePayback_(annualCost, annualSavings, turnoverRate) の単体テスト: 手計算との誤差 0.01% 未満を合格基準
  • calcRegularizationSavings_(monthlyAmount, monthsContinued) の単体テスト: 同上
  • HurdleRateRepository.findAsMap() のモックテスト: 有効フラグ=FALSE 行除外 / 重複カテゴリー時の先行行採用 / 空マスタ時の空オブジェクト返却

影響範囲

変更ファイル一覧

ファイル変更種別内容
100_config/101_sys_config.js追加のみschemas 配列に MST_HURDLE エントリ追加(約 4 行)
200_data/202_repository.js追加のみ末尾に HurdleRateRepository 定義追記(約 35 行)
400_domain/431_investment_analyzer.js新規エントリポイント + 3 サブ関数 + 計算ヘルパー(約 250 行)
000_infra/002_constants.js追加のみMENU_DEFINITION に 1 エントリ追加(1 行)
900_test/901_test_runner.js追加のみテストケース 3 件追加
docs/_config.json追加のみnav に仕様書エントリ追加

既存動作への影響

  • MAS-013 計算エンジン (610_service_investment_analysis.js): 変更なし。MAS-042 は 67_report_investment_analysis に列追記するのみで、MAS-013 の書き込み処理と競合しない(MAS-013 は clearContents() → 再書き込み方式、MAS-042 は MAS-013 実行後に 判定列のみ 上書き)。
  • 22_bud_headcount(HC 予算): 判定ラベル列を追加。既存の HC RPA 起票(401_rpa_hc.js)は indexOf ベースの列参照のため影響なし。
  • 01_sys_config: MST_HURDLE キーが 1 行追加。既存キー参照への影響なし。

運用・デプロイ手順

  1. dev 環境で npm run push:dev → サイドバー 🔧 開発・設定 → DDL 全更新 (Full)setupAllSchemas)を実行して 29_mst_investment_hurdle を作成
  2. サイドバー 🔧 開発・設定 → シートGID取得・リンクinitConfigs)を実行して MST_HURDLE01_sys_config に登録
  3. 29_mst_investment_hurdle に 6 カテゴリーの初期データを手動投入(採用 / 設備 / SaaS / ERP / IT小物 / 一般CAPEX)
  4. サイドバー 📊 マート更新 → 📈 投資案件 Go/No-Go 判定 を実行して動作確認
  5. prod 反映は npm run push:prod 後に同じ手順(DDL 更新 → initConfigs → 初期データ投入 → 実行)

注意事項

  • ⚠️ failure_patterns #18-#20(コード未読による固有名詞誤記・命名造語禁止): DDL スキーマ名・システムキー MST_HURDLE・メニューラベル・関数名等の固有名詞を仕様書に記述する前に、必ず 101_sys_config.js / 202_repository.js / 002_constants.js を Read して実在する文字列のみ引用すること。「〜という定数があるはず」「〜というメニューがあったはず」と推測した瞬間に手を止めて Read する(MAS-003 で 1 回のコード未読から同日 3 件の固有名詞誤記が発生した直接対策)。
  • ⚠️ failure_patterns #25(並列実装対称性漏れ): カテゴリー別(採用/設備/SaaS/ERP/IT小物/一般CAPEX)の判定ロジックを実装する際、全カテゴリーで「①閾値取得 → ②計算 → ③ラベル付与 → ④条件付き書式設定」の処理ステップが対称になっているか、実装後にカテゴリーを並べて確認すること。片方だけエラーハンドリングが省略されていないか特に注意する。
  • ⚠️ failure_patterns #26(oauthScopes 部分宣言): 本案件は外部 API 連携なし。appsscript.jsonoauthScopes フィールドは 変更不要(該当なし)。理由: SpreadsheetApp / Utils 内で完結し、UrlFetchApp / AdminDirectory 等の外部スコープは使用しない。
  • ⚠️ failure_patterns #27(Admin SDK API 変動): 外部 Google API 連携なし(該当なし)。
  • ⚠️ Utils.auditLog(operation, ...) の誤用禁止: 第 1 引数 operation'CREATE' | 'UPDATE' | 'DELETE' | 'CONFIRM' | 'CANCEL' | 'RUN' | 'MIGRATE' のいずれかであり、ログレベルのパラメータは存在しない。業務委託アラートのような WARN レベル通知は Utils.persistLog('WARN', funcName, message, JSON.stringify(detail)) + Utils.toastResult(funcName, summary, 8) の 2 段構成を使うこと。
  • ⚠️ 列番号ハードコード禁止: 22_bud_headcount / 67_report_investment_analysis への判定ラベル書き込みは、ヘッダー配列を indexOf で検索して列インデックスを動的取得してから getRange(row, colIdx + 1).setValue(val) で実行する(CLAUDE.md コーディング規約)。
  • ⚠️ SpreadsheetApp 直接呼び出し禁止: 29_mst_investment_hurdle へのアクセスは必ず HurdleRateRepository 経由。SpreadsheetApp.getActiveSpreadsheet().getSheetByName('29_mst_investment_hurdle') のような直接呼び出しは禁止(シート名変更時の影響波及を避けるため)。
  • ⚠️ Human-in-the-Loop: 自動付与される Go/グレー/No-Go ラベルは 判断補助 であり、最終的な投資決定は人間が行う。仕様書末尾・実装の UI メッセージ・GAS 関数コメントに明記すること。

Human-in-the-Loop ポリシー

本案件は CLAUDE.md プロダクトポリシーの「AI/自動処理の結果は必ず人間がレビュー・承認してから確定する」に直接該当する。以下を遵守:

  • 判定ラベル(Go / グレー / No-Go)はシートに書き込まれるが、投資実行の是非は人間が決定 する位置付けを 67_report_investment_analysis シート上部のメタ行に明記する
  • 条件付き背景色(Go=緑 / グレー=黄 / No-Go=赤)で視覚的に差分化し、人間の注意喚起を促す
  • 業務委託アラートは Toast 通知のみで、業務委託契約の自動解除・変更は行わない(人間が個別に判断)

エッジケース

#条件検知方法期待される挙動ログ出力
129_mst_investment_hurdle シート自体が未作成(DDL 未実行)HurdleRateRepository._getSheet()null「マスタ 29_mst_investment_hurdle が未作成です。setupAllSchemas を実行してください」ダイアログ表示して処理中断Utils.logError('runInvestmentGoNoGoAnalysis', new Error('MST_HURDLE 未作成'))
2マスタが空(ヘッダー行のみ・データ 0 件)HurdleRateRepository.findAsMap() が空オブジェクト {}「ハードルレートマスタにデータがありません」Toast 表示して処理中断Utils.persistLog('WARN', 'findAsMap', 'empty master', '')
3判定対象の投資カテゴリーがマスタ未登録findAsMap()[category]undefinedラベルを 「判定不能(マスタ未登録)」 として処理続行(他行の判定は継続)Utils.persistLog('WARN', 'applyGoNoGoToReport_', '未登録カテゴリー: ' + category, rowId)
4マスタの Payback / ROI 閾値が空欄または非数値文字列isNaN(Number(val)) または空文字判定当該閾値をスキップし他の有効な閾値のみで判定。空欄閾値は 「—」 表示Utils.persistLog('WARN', 'findAsMap', '閾値欠損: ' + field, category)
5マスタ閾値にマイナス値が設定されているthreshold < 0絶対値で評価(保守的フォールバック)し WARN 記録Utils.persistLog('WARN', 'findAsMap', 'マイナス閾値: ' + threshold, category)
6同一カテゴリーがマスタに重複登録findAsMap 構築時に map[name] 既存検知最初に検出した有効行を採用し後行を無視Utils.persistLog('WARN', 'findAsMap', '重複カテゴリー: ' + name, '先着採用')
767_report_investment_analysis の計算結果が空欄またはシートエラー(#N/A / #REF! 等)String(val).startsWith('#') または空文字判定当該行の判定をスキップ。ラベルを 「計算エラー(要確認)」 とするUtils.persistLog('WARN', 'applyGoNoGoToReport_', '計算エラー: ' + cellValue, rowId)
822_bud_headcount の「月額給与・報酬」が 0 またはマイナスUtils.parseAmt(val) <= 0実効 Payback を Infinity(回収不能)として扱い No-Go 判定Utils.persistLog('WARN', 'screenHiringInvestments_', '給与 0 またはマイナス', empId)
923_bud_subscription の「税抜金額_計画」が 0 またはマイナスUtils.parseAmt(val) <= 0業務委託アラート対象外として該行スキップ(正常系扱い)ログ出力なし
10業務委託「開始・契約年月」が不正フォーマットUtils.parseDateToYm(val) === ''継続月数計算をスキップし該行を対象外として処理続行Utils.persistLog('WARN', 'detectOutsourcingAlerts_', '契約年月が不正: ' + val, subId)
11年商比上限判定で参照年商データが 0(ゼロ除算)annualRevenue === 0年商比チェックをスキップし他の閾値のみで判定Utils.persistLog('WARN', 'applyGoNoGoToReport_', '年商 0 のため年商比判定スキップ', rowId)
12Payback 閾値は登録済みだが ROI 閾値のみ空欄(片方欠損)各閾値の有無を独立チェック有効な Payback 閾値のみで判定。ROI 判定は 「—(閾値未設定)」 表示Utils.persistLog('WARN', 'applyGoNoGoToReport_', 'ROI 閾値未設定: ' + category, rowId)
13グレー判定境界値(実績値が閾値の ±10% 以内)Math.abs(actual - threshold) / threshold <= 0.1ラベルを 「グレー(境界値)」 と付記。人間によるレビューを促す(背景色 Constants.COLORS.PROFIT_BG 黄)ログ出力なし(正常系扱い)
14LockService.getScriptLock() の取得失敗(既に別実行中)lock.tryLock(5000)false「別の処理が実行中です。しばらく待ってから再実行してください」ToastUtils.persistLog('WARN', 'runInvestmentGoNoGoAnalysis', 'lock acquisition failed', '')
15Utils.getSheetByKey('MST_HURDLE', ...) の fallback 名 29_mst_investment_hurdle 自体が削除されているgetSheetByKeynullエッジケース #1 と同一処理(ダイアログ → 中断)エッジケース #1 と同一

冪等性・再実行の設計

判定処理はシートへの 上書きのみ で副作用なし(新規レコード生成・ID 発番なし)。メニューから再実行した場合、古い判定ラベル・計算値・条件付き書式が新しい結果で完全に上書きされる設計とする(「既存結果が残り混在する」バグを防ぐ)。具体的には:

  • applyGoNoGoToReport_ は対象行の判定列を clearContent() してから setValue() で書き込む
  • 条件付き背景色は setBackground() を先に null(既定色)にリセットしてから新しい色を設定
  • 業務委託アラートの Toast 通知は毎回新規表示(過去の通知を復元しない)

Human-in-the-Loop(再掲・詳細)

CLAUDE.md プロダクトポリシー準拠の具体実装:

  • 67_report_investment_analysis22_bud_headcount の判定ラベル列に、GAS で条件付き背景色を設定:
    • Go: Constants.COLORS.HEADER_CONFIG#d9ead3 緑)
    • グレー: Constants.COLORS.PROFIT_BG#fff2cc 黄)
    • No-Go: Constants.COLORS.WARN_RED_BG#f4cccc 赤)
  • すべて 002_constants.js 実在定数。独自色定義禁止
  • 業務委託アラートは「能動通知」にとどめ、契約の自動解除・金額変更は一切行わない

テスト要件(901_test_runner.js への追加必須)

テスト関数合格基準備考
test_calcEffectivePayback_手計算との誤差 0.01% 未満(5 パターン)採用費 100 万 / 月給 50 万 / 離職率 0.5 → Payback 3.33 ヶ月の検証等
test_calcRegularizationSavings_手計算との誤差 0.01% 未満(3 パターン)月 50 万 × 12 ヶ月 → 正社員化で 120 万円/年削減の検証等
test_HurdleRateRepository_findAsMap_emptyMaster空マスタで空オブジェクト返却DDL 直後の状態を再現
test_HurdleRateRepository_findAsMap_disabledFlag有効フラグ=FALSE 行が除外される2 行中 1 行無効化 → 1 行のみマップに含まれる
test_HurdleRateRepository_findAsMap_duplicateCategory重複カテゴリーで先行行が採用される同一カテゴリー 2 行 → 1 行目の値のみマップ登録

実データ検証

実装前に MCP で以下を確認:

  1. 29_mst_investment_hurdle シートの有無: DDL 実行前は存在しないことを確認(mcp__google-sheets__list_sheets で名前検索)
  2. 22_bud_headcount の実際のヘッダー行: 「月額給与・報酬」「採用エージェント費」「PC等初期費用」「雇用形態」の正確な列名を mcp__google-sheets__get_sheet_data で確認(BUD_HC schema L930 と照合)
  3. 67_report_investment_analysis の実際のヘッダー行: MAS-013 実装済みの場合は実シートの判定対象列名(投資カテゴリー・Payback・ROI 等)を確認。未実装時は dev_mas-013_investment_simulation.md の §出力シート仕様から推定
  4. 23_bud_subscription の列名: 契約形態 / 税抜金額_計画 / 開始・契約年月BUD_SUBS schema L943 の宣言と一致することを確認(402_rpa_subscription.js の読み取りロジックとも突合)
  5. 01_sys_config の既存キー一覧: MST_HURDLE が未登録であることを確認(衝突回避)
  6. 03_sys_params の既存パラメータ: HC_ONBOARDING_COST_RATE / HC_TURNOVER_RATE / OUTSOURCING_ALERT_AMOUNT / OUTSOURCING_ALERT_MONTHS / WACC_RATE が未登録であることを確認
  7. Constants.TAX_RATES の累進ブラケット定義: MAS-013 で参照している 800 万円境界 / 実効税率の値を仕様書「現在のコード」と照合(MAS-042 側の計算には使わないが、MAS-013 との整合性確認)

関連ドキュメント

仕様書・ドキュメント関連箇所
dev_mas-013_investment_simulation.md計算エンジン本体。本案件は MAS-013 出力 67_report_investment_analysis に判定ラベル列を追加する形で連携
dev_mas-010_financial_modeling.mdMAS-042 判定の「年商比上限」算出の参照元として、MAS-010 出力 94_fs_longterm_forecast からの売上予測を使用する余地あり(Phase 2 検討)
dev_mas-018_financial_statement_linkage.mdCF 連動の設計思想参考
dev_mas-017_funding_simulation.mdWACC 計算エンジン。MAS-017 完成後は本案件のマスタ WACC閾値(%) 列を MAS-017 算出値に切り替える
dev_mas-043_hybrid_track_profitability.md採用投資 Payback の単価前提(RICE/VALUE 単価)に相互影響
CLAUDE.mdコーディング規約(ヘッダー名ベース列参照・有効フラグスキップ・Human-in-the-Loop ポリシー)
failure_patterns.md#18-#20 命名造語禁止 / #25 並列実装対称性 / #26 oauthScopes / #27 Admin SDK

人間が検討すべき事項

  1. 初期閾値の採用方針: 2026 年水準(設備 Payback 2 年 / SaaS 1.5 年 / ERP 3 年 / ROI 20% / 中小経営強化税制 B 類型 7%)で行くか、業界中央値(例: 中小企業白書のベンチマーク)を採用するか。どの数字を「基準」として主張するかは経営方針による
  2. 離職リスク係数の業種別テーブル化: 現状は全職種共通 HC_TURNOVER_RATE (0.527) を使う予定だが、厚労省の業種別離職率データ(IT 業 25%、サービス業 35% 等)をマスタ化する価値があるか検討。判断基準: 採用の職種分布が多様化した時点(概ね 5 職種超)で導入
  3. 採用投資の単価前提(MAS-043 連動): RICE/VALUE のどちらの単価で採用の Payback を計算するか。注意: 誤って VALUE 単価(高単価)で Payback を計算すると、あらゆる採用が「回収早い」と誤判定されるリスクあり。MAS-043 の案件種別との整合運用が必要
  4. MAS-017 完成までの暫定 WACC 値の持ち方: 案件ごと手入力(シートの「WACC閾値(%)」列に直接数値記入)とマスタで全社単一値を保持(Constants.getParam('WACC_RATE', 0.07))のどちらをデフォルトにするか。初期案: マスタで単一値(7%)を保持し、特殊案件のみシート側で上書き
  5. 業務委託アラートの通知手段: 画面 Toast だけで十分か、メール通知・Slack 通知を追加するか。依存関係: MAS-186(Slack 経費精算)実装時に通知基盤を共通化する余地あり
  6. Go / グレー / No-Go の境界共通化: 現状案は「ROI 20%↑ で Go、10〜20% でグレー、10%↓ で No-Go」を全カテゴリー共通としているが、採用案件のみグレー範囲を広く(±20%)取る等の個別調整が必要か
  7. MAS-010 (5 カ年予測) との連動: 年商比上限判定の「参照年商」を、過去 12 ヶ月実績にするか MAS-010 の将来予測値にするか。影響: 成長期の過小評価(過去年商ベース)vs 楽観バイアス(将来予測ベース)のトレードオフ
  8. 29_mst_investment_hurdle のバージョン管理: 閾値改定の履歴を残すか。: 04_mst_hurdle_history 独立シートで日付付き履歴を保持、または MAS-002(予算バージョン管理)パターンをマスタにも適用
  9. 職種別閾値の管理粒度: 現状案は カテゴリー名='採用' で共通閾値だが、「採用-エンジニア」「採用-営業」「採用-新卒」等の細分化マスタ行に拡張するか。判断基準: 採用実績が年 5 名超になった時点
  10. 仕様書内「連動性」記載の精度: MAS-013 / MAS-017 / MAS-043 との依存関係を仕様書に明記するが、MAS-017 / MAS-043 未実装時の MVP での動作保証範囲を明文化する必要があるか

実装プロンプト(Claude Code 用)

あなたは GAS 会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-042「投資ハードルレートマスタ & Go/No-Go 判定(MAS-013 補完)」を以下の 4 Step に分けて実装してください。

## 実行前タスク(各 Step で Read してから着手すること)
1. `200_data/202_repository.js` の `AccountRepository`(L304-350)/ `PartnerRepository`(L356-406)を Read し、`HurdleRateRepository` の実装パターンを確認する
2. `100_config/101_sys_config.js` の `schemas` 配列(L879 起点)を Read し、DDL 追加フォーマット(既存エントリの `headers` / `color` / `validations` 構造)を確認する
3. `000_infra/002_constants.js` の `MENU_DEFINITION`(L206)内の `📋 サイドバー: 📊 マート更新` カテゴリ(L231-241)を Read し、`items` 末尾への追記フォーマットと追加位置を確認する
4. MCP で `29_mst_investment_hurdle` シートの有無を確認する(DDL 実行前は存在しないはず)
5. `docs/dev/dev_mas-013_investment_simulation.md` を Read し、`67_report_investment_analysis` の実際のヘッダー列名(NPV / IRR / Payback / ROI の正式表記)を確認する

## Step 1: DDL・Repository・初期データ投入(推奨モデル: Opus)
修正ファイル: `100_config/101_sys_config.js`(`schemas` 配列へ追記)/ `200_data/202_repository.js`(末尾に `HurdleRateRepository` 追記)

- `101_sys_config.js` の `schemas` 配列に `29_mst_investment_hurdle` の DDL 定義を追加:
  ```javascript
  'MST_HURDLE': {
    headers: ["有効フラグ", "カテゴリー名", "WACC閾値(%)", "Payback閾値(年)", "ROI閾値(%)", "年商比上限(%)", "備考"],
    color: "#666666"
  },
  ```
- `202_repository.js` 末尾に `HurdleRateRepository` を追加(`AccountRepository` パターン完全準拠):
  - `_getSheet()`: `Utils.getSheetByKey('MST_HURDLE', '29_mst_investment_hurdle')` を呼ぶ
  - `findAll()`: `readSheetAsDtos_(HurdleRateRepository._getSheet())` を呼ぶ
  - `findAsMap()`: キャッシュ付き。カテゴリー名キー → `{ wacc, payback, roi, revenueRatioCap }` マップ。`有効フラグ=FALSE` 行スキップ必須。重複カテゴリーは先着行採用(後行は `Utils.persistLog('WARN', 'findAsMap', ...)` で記録)
  - `_cache: null` / `resetCache()` を実装
- `npm run push:dev` → サイドバー `🔧 開発・設定 → DDL 全更新 (Full)`(`setupAllSchemas`)実行
- サイドバー `🔧 開発・設定 → シート GID 取得・リンク`(`initConfigs`)実行(`MST_HURDLE` を `01_sys_config` に登録)
- `29_mst_investment_hurdle` にカテゴリー別初期データを MCP(`mcp__google-sheets__add_rows`)で投入(採用 / 設備 / SaaS / ERP / IT小物 / 一般CAPEX の 6 行)。初期値は 2026 年水準(設備 Payback 2 年、SaaS 1.5 年、ERP 3 年、ROI 20%、中小経営強化税制 B 類型 7% 等)

## Step 2: `431_investment_analyzer.js` の新設(推奨モデル: Sonnet)
修正ファイル: `400_domain/431_investment_analyzer.js`(新規作成のみ)

- グローバル関数 `runInvestmentGoNoGoAnalysis()` を定義(`MENU_DEFINITION` の `funcName` と完全一致させること)
- `LockService.getScriptLock().tryLock(5000)` で排他制御、`try/finally` で確実に解放
- 内部サブ関数: `applyGoNoGoToReport_(hurdleMap)` / `screenHiringInvestments_(hurdleMap)` / `detectOutsourcingAlerts_(hurdleMap)`
- 各サブ関数の先頭で **呼び出し元から渡された閾値マップ** を使う(多重読み込み防止。`runInvestmentGoNoGoAnalysis()` が `HurdleRateRepository.findAsMap()` を 1 回だけ呼び出して引数で渡す)
- 変動パラメータは `Constants.getParam('HC_ONBOARDING_COST_RATE', 0.25)` / `Constants.getParam('HC_TURNOVER_RATE', 0.527)` / `Constants.getParam('OUTSOURCING_ALERT_AMOUNT', 500000)` / `Constants.getParam('OUTSOURCING_ALERT_MONTHS', 12)` / `Constants.getParam('WACC_RATE', 0.07)` で取得(`002_constants.js` 実在関数)
- 業務委託アラート通知: `Utils.persistLog('WARN', 'detectOutsourcingAlerts_', msg, JSON.stringify(detail))` + `Utils.toastResult('detectOutsourcingAlerts_', summary, 8)` の 2 段構成
- 判定結果書き込み: ヘッダー `indexOf` → 動的列インデックス取得 → `getRange(row, colIdx + 1).setValue(val)`(列番号ハードコード禁止)
- 条件付き書式: Go=`Constants.COLORS.HEADER_CONFIG`(緑)/ グレー=`Constants.COLORS.PROFIT_BG`(黄)/ No-Go=`Constants.COLORS.WARN_RED_BG`(赤)。`setBackground(null)` リセット → 新色設定の順
- エッジケース 15 件すべてを try/catch で防御的に実装。エラー発生時も処理を中断せず次の行へ続行する(冪等性を担保)

## Step 3: MENU_DEFINITION 追記(推奨モデル: Haiku)
修正ファイル: `000_infra/002_constants.js`(`MENU_DEFINITION` の 1 エントリ追記のみ)

- `📋 サイドバー: 📊 マート更新` カテゴリ(L231-241)の `items` 末尾(`buildFiveYearForecast` / `openWhatIfSidebar` の直後)に追記:
  ```javascript
  { label: '📈 投資案件 Go/No-Go 判定', funcName: 'runInvestmentGoNoGoAnalysis', description: '29_mst_investment_hurdle の閾値で F-13・採用・業務委託の Go/No-Go を自動判定' },
  ```
- `funcName` の文字列が Step 2 で定義したグローバル関数名と完全一致していることを確認してからコミット(failure_patterns #20 命名造語禁止)

## Step 4: テスト追加・動作確認(推奨モデル: Sonnet)
修正ファイル: `900_test/901_test_runner.js`(テストケース追記)

- `calcEffectivePayback_` の単体テスト(手計算との誤差 0.01% 未満を合格基準、5 パターン)
- `calcRegularizationSavings_` の単体テスト(同上、3 パターン)
- `HurdleRateRepository.findAsMap()` のモックテスト(空マスタ / 有効フラグ FALSE / 重複カテゴリーの 3 パターン)
- `npm run push:dev` → GAS エディタで `runInvestmentGoNoGoAnalysis()` を手動実行し動作確認
- `99_error_log` に不要な ERROR / WARN が記録されていないことを確認
- サイドバー `📊 マート更新 → 📈 投資案件 Go/No-Go 判定` から実行し、UI 表示・Toast 通知・条件付き背景色の全てが正常に動作することを確認

## 制約
- `appsscript.json` の `oauthScopes` を変更しない(failure_patterns #26)
- 列番号ハードコード禁止(CLAUDE.md コーディング規約)
- 架空の関数名・シート名・定数名を使わない(failure_patterns #18-#20)。記述前に必ず Read で実在を確認する
- `Utils.auditLog()` の `operation` 引数に `'WARN'` 等のログレベル文字列を渡さない(正しい `operation` 値: `CREATE` / `UPDATE` / `DELETE` / `CONFIRM` / `CANCEL` / `RUN` / `MIGRATE`)
- `29_mst_investment_hurdle` へのアクセスは必ず `HurdleRateRepository` 経由(`SpreadsheetApp` 直接呼び出し禁止)
- MAS-013 の既存計算エンジン(`610_service_investment_analysis.js`)は変更しない。`67_report_investment_analysis` への判定ラベル列追記のみで連携

## 実データ検証(実装前に MCP で確認)
- `29_mst_investment_hurdle` シートの有無(DDL 実行前は存在しない)
- `22_bud_headcount` の実際のヘッダー行(「月額給与・報酬」「採用エージェント費」「PC等初期費用」「雇用形態」の正確な列名)
- `67_report_investment_analysis` の実際のヘッダー行(MAS-013 未実装時は「要確認」、実装済みの場合は MAS-013 出力列と判定列の位置関係を確定)
- `23_bud_subscription` の「税抜金額_計画」「開始・契約年月」「契約形態」の実際の列名(`402_rpa_subscription.js` で確認済みの列名と一致させる)
- `01_sys_config` の既存キー一覧(`MST_HURDLE` 未登録を確認)
- `03_sys_params` の既存パラメータ(`HC_ONBOARDING_COST_RATE` / `HC_TURNOVER_RATE` / `OUTSOURCING_ALERT_AMOUNT` / `OUTSOURCING_ALERT_MONTHS` / `WACC_RATE` 未登録を確認)

### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---|:---:|---|
| Phase 1 調査 | あり | `67_report_investment_analysis` 実ヘッダー・既存判定列有無の裏取り |
| Phase 2 実装 | なし | 仕様書で決定済みの内容を書き下すのみ |

推奨実行モデル

工程推奨モデル理由
Step 1: DDL 設計 + HurdleRateRepository 実装Claude Opus 4.7 (1M context)schemas フォーマット解読 + Repository 設計判断 + 初期データ設計が複合的に必要
Step 2: 431_investment_analyzer.js 新設Claude Sonnet 4.6複数ファイル横断だが仕様書でロジック完全定義済み
Step 3: MENU_DEFINITION 追記Claude Haiku 4.5既存パターンの 1 エントリ追記のみ、判断要素なし
Step 4: テスト追加Claude Sonnet 4.6テストケース設計の判断が必要

変更履歴

日付変更内容
2026-04-23初版作成。Gemini Pro メタプロンプト + Claude Sonnet 添削(scripts/1_generate_prompts_gemini.js パイプライン)で生成された tasks/prompts/task_F-42.md をベースに執筆。MAS-013(投資回収シミュレーション)補完として、投資カテゴリー別閾値の Go/No-Go 判定・採用投資スクリーニング・業務委託→正社員化アラートの 4 機能を 400_domain/431_investment_analyzer.js に集約。新規マスタ 29_mst_investment_hurdleHurdleRateRepository + DDL)で閾値を一元管理し、税制改正・業界相場変動をコード改修なしに吸収可能な設計。エッジケース 15 件・人間検討事項 10 件・推奨実行モデル 4 工程を定義
2026-04-23MAS-011 との番号衝突回避のため 430_investment_analyzer.js431_investment_analyzer.js にリネーム。MAS-011 MVP(PR #315)で 400_domain/430_what_if_simulator.js が実装されており同番号衝突のため。仕様書内 8 箇所 + tasks/prompts/task_F-42.md 6 箇所を一括置換
2026-04-28v1.2 (実装後の整合化・PR #336 反映)

仕様書作成プロンプト

展開して表示(Gemini Pro + Claude Sonnet レビュー済み・`tasks/prompts/task_F-42.md`)
<instruction>
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**: Phase 1(設計)では拡張思考をフル活用し、ファイル名形式・エッジケース一覧・Step分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全確定させる。Phase 2(清書)の各Step内では拡張思考を最小限に抑え、Phase 1で確定済みの内容の書き下しに徹する。出力途中で再考しない。
2. **テキスト報告の禁止**: 「〜を作成します」等のtextのみでtool_useなしにturnを終了しない。説明は1文以内。直ちにtoolを呼ぶ。
3. **4-5分割のWrite/Edit実行**: Step 2-1(骨格 ~20行) / 2-2(概要〜注意事項 ~300行) / 2-3a(エッジケース〜人間検討事項 ~200行) / 2-3b(実装プロンプト〜変更履歴 ~250行) / 2-4(<details>プロンプト全文記録) の5Stepに分けて実行する。1回のWrite/Editは300行以内を目安。
4. **各Stepで何を書くかを具体指示**: Phase 2実行時に設計判断を持ち込まないよう、各Stepの内容をPhase 1で完全確定させる。

======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 F-42「投資ハードルレートマスタ & Go/No-Go 判定(F-13 補完)」の開発仕様書を作成してください。
開発仕様書を新規作成した場合は、`docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。

(Phase 1 調査項目 1-6、Phase 2 Step 2-1〜2-4、Phase 3 登録・記録の全内容が記載される。
全文は `tasks/prompts/task_F-42.md` を参照)
</instruction>

※ Gemini Pro (gemini-2.5-pro) が docs/_internal/failure_patterns.md / docs/_internal/dev_spec_prompt_template.md / コアコード 4 ファイル(002_constants.js / 003_contracts.js / 004_utils.js / 202_repository.js)を読み込んで設計した後、Claude Sonnet 4.6 が実行者目線で添削した成果物。プロンプトパイプライン: scripts/1_generate_prompts_gemini.jstasks/prompts/task_F-42.gemini.md(Gemini 原版)→ tasks/prompts/task_F-42.md(Sonnet 添削後・最終版)