MAS-036: R&D投資対効果(ROI)ダッシュボード
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-036 |
| カテゴリ | FP&A・レポーティング |
| Phase | P3 |
| 優先度 | ★ |
| 前提案件 | MAS-032(プロダクト別P/L)/ MAS-034(R&D費用集計) |
| 対象ファイル(新規) | 600_report/610_datamart_rd_roi.js |
| 対象ファイル(既存変更) | 100_config/101_sys_config.js(DDLスキーマ追加)000_infra/002_constants.js(メニュー追加)CLAUDE.md(DDL非管理タブ一覧に 94_rd_roi_dashboard を追記) |
| 新規シート | 19_mst_rd_product_link(マスタ・DDL管理)94_rd_roi_dashboard(ダッシュボード・DDL非管理) |
目的
R&Dプロジェクトごとの累積投資額と、そこから生まれたプロダクト収益・ライセンス収入・特許収入を紐付け、投資回収状況(ROI・回収期間・回収ステータス)を1枚のダッシュボードで可視化する。「このR&Dテーマにいくら投資して、いくら回収できたか」を定量的に把握し、R&D予算配分の意思決定材料を提供する。
前提案件
| 案件 | 役割 | 状態 |
|---|---|---|
| MAS-032 | プロダクト別P/L(プロダクト軸の収益集計基盤) | 未実装(仕様書未作成) |
| MAS-034 | R&D費用集計(R&Dプロジェクト軸の費用集計基盤) | 未実装(仕様書未作成) |
重要: 本案件は MAS-032 / MAS-034 の前提となるデータ構造(PJ名プレフィックス規約 R&D_ / プロダクト_)に依存するため、両案件未実装の段階では実データから ROI 計算ができない。実施順序は「人間が検討すべき事項」を参照。
現在のコード(関連既存実装)
| ファイル | 関連箇所 | 流用ポイント |
|---|---|---|
200_data/202_repository.js | JournalRepository.findAll()(L270) | { headers, dtos: JournalEntryDTO[] } を返す。dtos 配列を 収支区分 / PJ名 / 科目名 でフィルタする |
200_data/202_repository.js | readSheetAsDtos_(sheet)(L19) | プライベートヘルパー(GAS グローバルスコープから呼出可能)。新規 19_mst_rd_product_link 読込に流用 |
000_infra/003_contracts.js | JournalEntryDTO typedef(L97-129) | 収支区分 / PJ名 / 科目名 / 税抜金額_実績 / 税込金額_実績 / 発生日(P/L計上日) / 仕訳ステータス を読み出す |
000_infra/004_utils.js | Utils.parseDateToYm(val)(L92) | `Date |
000_infra/004_utils.js | Utils.parseAmt(val)(L191) | 任意値 → number(パース不可なら 0) |
100_config/101_sys_config.js | setupAllSchemas の schemas 定義(L826〜) | 'SYS_KEY': { headers: [...], color: "#HEX", validations: {...} } 形式。19_mst_rd_product_link の追加先 |
100_config/101_sys_config.js | confSheet.appendRow([key, '', sheetName, label])(L773〜) | システムキー登録ブロック。MST_RD_LINK を新規追加 |
000_infra/002_constants.js | MENU_DEFINITION(L206〜)の 📋 サイドバー: 📊 マート更新 カテゴリ(L230〜) | 新規メニュー項目 🔬 R&D ROIダッシュボード を追加 |
600_report/609_datamart_kpi.js | buildKpiDashboard()(L21)+ シート clear → ctx 構築 → render の構造 | 94_rd_roi_dashboard の動的生成・上書き+公開関数命名規則の雛形 |
600_report/601_datamart_ingest.js | dmIngestData_()(L19)/ dmIngestPlanData_()(L277)の _ 終端プライベート関数の命名規則 | 新規プライベートヘルパーの命名規則 |
JournalEntryDTO に存在しないフィールド(要注意)
000_infra/003_contracts.js L97-129 の typedef および 100_config/101_sys_config.js L860 の TRN_JOUR スキーマで確認した結果、42_trn_journal シートには以下のフィールドが存在しない:
| フィールド | 状態 | 影響 |
|---|---|---|
有効フラグ | 未定義(OrderDTO/InvoiceDTO/BankTxDTO/BudgetDTO には存在するが JournalEntryDTO にはなし) | ROI 計算では 有効フラグ フィルタを実施しない(仕訳全件が対象) |
諸表区分 | 未定義(仕訳台帳は科目マスタ経由で諸表区分を解決する設計) | ROI 計算では 諸表区分 直接参照禁止。必要なら AccountRepository.findAsMap() を経由する |
修正方針
アーキテクチャ
- 新規モジュール
600_report/610_datamart_rd_roi.jsを作成。公開関数buildRdRoiDashboard()のみを export(既存buildKpiDashboard()(609_datamart_kpi.js)の命名規則に倣う)。 - 出力先シート
94_rd_roi_dashboardは setupAllSchemas 非管理(93_kpi_dashboardと同様、動的生成・毎回上書き)。CLAUDE.md「DDL非管理タブ」一覧に追記する。シート未存在時はss.insertSheet('94_rd_roi_dashboard')で生成。 - 新規マスタシート
19_mst_rd_product_linkは setupAllSchemas 管理(101_sys_config.jsのschemas定義 L826〜 に追加)。システムキーMST_RD_LINKを01_sys_configに登録。 - メニュー登録:
000_infra/002_constants.jsのMENU_DEFINITIONの📋 サイドバー: 📊 マート更新カテゴリ(L230〜)に{ label: '🔬 R&D ROIダッシュボード', funcName: 'buildRdRoiDashboard', description: 'R&Dプロジェクト別 投資対効果 (94 タブ) を再計算' }を追加。メニュー文字列はカテゴリ名と既存項目(📊 KPIダッシュボード再描画等)の表記に揃える。造語禁止(失敗パターン #20)。
データソースと取得方法
| 集計対象 | データソース | フィルタ条件 | 金額カラム |
|---|---|---|---|
| 投資額(R&D費用) | JournalRepository.findAll().dtos | dto['収支区分'] === '支出' かつ String(dto['PJ名']).startsWith('R&D_') | Utils.parseAmt(dto['税抜金額_実績']) |
| 収益額(プロダクト収益) | 同上 | dto['収支区分'] === '収入' かつ String(dto['PJ名']).startsWith('プロダクト_') かつ dto['科目名'] === '売上高' | Utils.parseAmt(dto['税抜金額_実績']) |
- 日付正規化: 全ての集計キーは
Utils.parseDateToYm(dto['発生日(P/L計上日)'])で"YYYY-MM"に統一。String(Date)の直接比較は禁止(失敗パターン #17)。 仕訳ステータス === '仕訳振替'の扱い: 振替仕訳を ROI 計算に含めるか除外するかは経営判断。デフォルトは「除外しない(含める)」。決定は「人間が検討すべき事項」参照(CLAUDE.md「仕訳振替の判定は=== '仕訳振替'の完全一致」を踏襲)。- 有効フラグの扱い:
42_trn_journalには有効フラグカラムが存在しないため、フィルタは適用しない(前述の「JournalEntryDTO に存在しないフィールド」参照)。
紐付けロジック(19_mst_rd_product_link 使用)
- シート取得:
var linkSheet = ss.getSheetByName('19_mst_rd_product_link'); - DTO 化:
var linkResult = readSheetAsDtos_(linkSheet);({ headers, dtos }を返す。GAS グローバルスコープから呼出可能) - フィルタ:
dto['有効フラグ'] === false || String(dto['有効フラグ']).toUpperCase() === 'FALSE'の行はスキップ(既存AccountRepository.findAsMapL329-330 の判定パターンに準拠) - マップ構築:
{ 'R&Dプロジェクト名': { product: '関連プロダクト名', rate: Number, startYm: 'YYYY-MM', endYm: 'YYYY-MM' | '' } } - デフォルト値:
収益貢献率(%)が空欄/null/NaN →1.0(100%)紐付け終了年月が空欄 → 期限なし扱い(全期間対象)
- マスタ未登録の R&Dプロジェクトの扱いはダッシュボード行の出力可否を決める(「人間が検討すべき事項」で確定)
新規マスタスキーマ(19_mst_rd_product_link)
100_config/101_sys_config.js の schemas 定義に追加(既存パターンに倣う):
| カラム名 | 型 | 説明 |
|---|---|---|
有効フラグ | boolean | FALSE の行はスキップ(科目マスタ等と同パターン) |
R&Dプロジェクト名 | string | 42_trn_journal の PJ名(R&D_ プレフィックス込み)と完全一致 |
関連プロダクト名 | string | 42_trn_journal の PJ名(プロダクト_ プレフィックス込み)と完全一致 |
収益貢献率(%) | number (0〜1) | 0.0〜1.0 の小数。1.0 = 100%。空欄時はデフォルト 1.0 |
紐付け開始年月 | string ("YYYY-MM") | この年月以降の収益のみ集計対象 |
紐付け終了年月 | string ("YYYY-MM") | 空欄時は無期限。指定時はこの年月までの収益を集計対象 |
schemas への追加例:
'MST_RD_LINK': { headers: ["有効フラグ","R&Dプロジェクト名","関連プロダクト名","収益貢献率(%)","紐付け開始年月","紐付け終了年月"], color: "#666666",
validations: {
"収益貢献率(%)": { type: 'range', min: 0, max: 1, helpText: '0〜1の小数で入力してください(例: 0.8 = 80%)' }
}
}
color: "#666666" はマスタの慣例色(MST_ACCT / MST_PART 等と統一)。
ダッシュボード出力レイアウト(94_rd_roi_dashboard)
1行 = 1 R&Dプロジェクト。列構成:
| 列 | カラム名 | 内容 |
|---|---|---|
| A | R&Dプロジェクト名 | 42_trn_journal の PJ名 値(R&D_ プレフィックス含む) |
| B | 関連プロダクト名 | マスタの 関連プロダクト名 |
| C | 累計投資額 | 投資データの全期間合計(円) |
| D | 累計リターン(貢献率調整後) | 収益額 × 収益貢献率(紐付け期間内のみ) |
| E | 純利益 | D - C |
| F | ROI(%) | E / C × 100。C = 0 の場合は "-"(ゼロ除算回避) |
| G | 回収期間(月) | 月次純利益の累積が初めて > 0 になった月数。回収未到達なら "未回収" |
| H | ステータス | E < 0 → 投資中、E >= 0 かつ G が 未回収 → 回収中、E >= 0 かつ G が数値 → 回収済 |
| I | 月次純利益推移(Sparkline) | =SPARKLINE(...) 数式または月次純利益配列を文字列化したもの |
ヘッダーは行1、データは行2以降。冪等性のため毎回 clearContent() してから書き込む。
影響範囲
| 種別 | パス | 内容 |
|---|---|---|
| 新規 | 600_report/610_datamart_rd_roi.js | 公開関数 buildRdRoiDashboard() + プライベートヘルパー |
| 既存変更 | 100_config/101_sys_config.js | confSheet.appendRow(['MST_RD_LINK', '', '19_mst_rd_product_link', 'マスタ_R&Dプロジェクト⇔プロダクト紐付け']) の追加(L773〜のシステムキー登録ブロック)/ schemas への 'MST_RD_LINK': {...} 追加(L826〜) |
| 既存変更 | 000_infra/002_constants.js | MENU_DEFINITION の 📋 サイドバー: 📊 マート更新 カテゴリ(L230〜)に 🔬 R&D ROIダッシュボード 項目を追加 |
| 既存変更 | CLAUDE.md | 「DDL (setupAllSchemas) で管理されないタブ」一覧に 94_rd_roi_dashboard を 93_kpi_dashboard の直後に追記 |
| 動作影響 | 既存マート(P/L・B/S・CF・KPI 等) | 影響なし(読み取りのみで参照する 42_trn_journal を変更しない) |
注意事項
94_rd_roi_dashboardは setupAllSchemas 管理外。動的生成シートのため、CLAUDE.md「DDL非管理タブ」リストに94_rd_roi_dashboardを追記すること(93_kpi_dashboardの直後)。実装漏れがあると次回の DDL 全更新時に挙動が読めなくなる。- PJ名のプレフィックス規約(
'R&D_'/'プロダクト_')は実装着手前に「実データ検証」セクションの手順で実値を確認する。記憶・推測で記述しない(失敗パターン #18-#20)。 - 列番号ハードコード禁止。ヘッダー名ベースで列参照する(CLAUDE.md コーディング規約)。
headers.indexOf('PJ名')等の動的取得を必ず使用。 - 冪等性:
buildRdRoiDashboard()冒頭で94_rd_roi_dashboardの既存行をsheet.clearContent()してから書き込む。シート未存在時はss.insertSheet('94_rd_roi_dashboard')で生成。2 回連続実行で重複行が発生しないこと。 仕訳ステータス === '仕訳振替'の取扱いを実装者が独断で決めない。「人間が検討すべき事項」で経営判断を待つ。デフォルト挙動は「振替も含めて集計」とし、除外する判断が下りたらdto['仕訳ステータス'] !== '仕訳振替'のフィルタを追加する設計とする。- JournalEntryDTO に
有効フラグフィールドが存在しないため、42_trn_journal側ではフラグフィルタ不要。一方、19_mst_rd_product_link側では有効フラグ === false || 'FALSE'のスキップを実施する(既存AccountRepository.findAsMapL329-330 の判定パターンに準拠)。 収益貢献率(%)の値域は 0.0〜1.0 の小数。100のような百分率値で入力されるとリターンが 100 倍に膨張する。DDL 側でvalidations: { "収益貢献率(%)": { type: 'range', min: 0, max: 1 } }を指定して入力時点で防ぐ。
エッジケース
| 条件 | 表示値 | 理由 |
|---|---|---|
| 累計投資額 = 0(投資データ0件) | ROI(%) = "-" | ゼロ除算回避(失敗パターン #2) |
| 純利益 < 0(投資超過状態) | ROI(%) を負値でそのまま表示(例: -45.0%) | 投資超過状態を正確に表現するため、強制的に 0 や - に丸めない |
| 累計リターンが累計投資額を一度も超えない | 回収期間 = "未回収" / ステータス = 回収中(純利益 >= 0 の場合)/ 投資中(純利益 < 0 の場合) | 月次累積で交差点が存在しない |
| 紐付けマスタにR&Dプロジェクトが未登録 | 当該R&D行を出力しない(または 関連プロダクト名 を "マスタ未設定" と表示) | 計算根拠が確定しないため。どちらにするかは「人間が検討すべき事項」で経営判断 |
| マスタ定義あり・投資データ0件 | 累計投資額 = 0、ROI = "-" で行は出力する | データなし ≠ マスタなし。プロジェクトの存在を可視化する |
| マスタ定義あり・収益データ0件 | 累計リターン = 0、純利益 = -投資額、ROI = -100%、ステータス = 投資中 | 投資中フェーズの可視化 |
紐付け終了年月 を過ぎた期間の収益 | 貢献率 = 0 で集計対象から除外 | 紐付け期間外の収益は当該R&Dプロジェクトに帰属させない |
紐付け開始年月 より前の期間の収益 | 同上、貢献率 = 0 で除外 | 紐付け期間外 |
収益貢献率(%) が空欄/null | デフォルト 1.0(100%)で計算 | 「全額帰属」を暗黙のデフォルトとする |
| 同一R&Dプロジェクトに複数のマスタ行(複数プロダクトに紐付け) | 各プロダクトごとに行を分けて出力(または合算) | どちらにするかは「人間が検討すべき事項」で経営判断。設計確定までは「マスタ行ごとに1行(プロダクト別の細分粒度)」をデフォルト |
PJ名 が R&D_ で始まらない仕訳 | 投資集計から除外 | 規約外データはノイズとして扱う |
PJ名 が プロダクト_ で始まらない収益 | 収益集計から除外 | 同上 |
| 月次純利益累積が複数回 0 を跨ぐ(投資 → 回収 → 再投資 → 再回収) | 「初めて > 0 になった月数」を回収期間とする | シンプルな閾値検出。複合判定は別案件で検討 |
実データ検証
実装着手前に MCP(Google Sheets MCP サーバー)または getValues() で以下を確認する。確認結果を実装プロンプト Step 1 のコメントに記録すること。
| # | 確認対象 | 確認方法 | 期待値 | 失敗パターン |
|---|---|---|---|---|
| 1 | 42_trn_journal の PJ名 列で R&D_ プレフィックスのプロジェクトが実在するか | サンプル行の PJ名 値を 10 件程度目視 | 1 件以上の R&D_ 始まり PJ が存在 | 存在しない場合は本案件実装不可 → 人間が検討すべき事項に「PJ名 命名規約 R&D_ を新規制定する必要あり」と記載 |
| 2 | 42_trn_journal の PJ名 列で プロダクト_ プレフィックスのプロジェクトが実在するか | 同上 | 1 件以上の プロダクト_ 始まり PJ が存在 | 同上(収益側) |
| 3 | 科目名 === '売上高' の仕訳で PJ名 = 'プロダクト_xxx' 形式が実在するか | フィルタ目視 | 該当仕訳が存在 | 存在しない場合は MAS-032(プロダクト別P/L)未整備 |
| 4 | 42_trn_journal シートに物理的に 有効フラグ カラムが存在するか | ヘッダー行 (row 1) を確認 | 存在しない(typedef 通り) | 存在する場合は典定義との乖離(失敗パターン #3)。実装はヘッダーの実体に合わせる |
| 5 | 収支区分 の実データ値が '収入' / '支出' の文字列で格納されているか | サンプル目視 | '収入' または '支出' の完全一致 | コード値(数値・英字)で格納されている場合は変換が必要(失敗パターン #3) |
| 6 | 仕訳ステータス の実データ値で '仕訳振替' がどの程度の比率で出現するか | フィルタで件数把握 | 件数を仕様書に記録 | 多数出現する場合は ROI 計算結果に大きく影響するため、振替包含/除外の判断材料となる |
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| CLAUDE.md | コーディング規約(列参照ヘッダー名ベース・冪等性)/DDL非管理タブ一覧 |
| F.4 実装失敗パターン一覧 | #2 ゼロ除算フォールバック/#3 DDLコード値 vs 実データ/#17 日付ソート/#18-#20 仕様書記述の固有名詞誤記 |
| dev_mas-032(未作成) | プロダクト別P/L — 本案件の収益サイドの上流 |
| dev_mas-034(未作成) | R&D費用集計 — 本案件の投資サイドの上流 |
| E.5.5 MAS-003 KPIダッシュボード | 93_kpi_dashboard 動的生成パターン(94 タブの設計雛形) |
| E.5.1 MAS-001 予実差異分析 | 仕様書フォーマット・実装プロンプト形式の参照元 |
人間が検討すべき事項
TODO_future.md MAS-036 記載事項(転記)
- R&D成果とプロダクト収益の紐付けルール: 直接帰属(特定R&Dから生まれた特定プロダクトの全収益)vs 間接貢献(複数R&Dが組み合わさったプロダクトに対する貢献率の按分)。本仕様書では
19_mst_rd_product_link.収益貢献率(%)で間接貢献を扱う設計だが、按分ルールの運用ポリシーは経営判断。 - 回収期間の目安: 「投資中 → 回収中 → 回収済」のステータス判定で「回収済」と見なすまでの期間(例: 5年以内に回収すべき / 10年スパンで評価する 等)。本仕様書は「累積純利益が初めてプラスに転じた月数」をステータス基準とするのみで、目安基準は持たない。
本仕様書で確定が必要な追加事項
19_mst_rd_product_link.収益貢献率(%)の運用主体・レビュー頻度: マスタ値の経営判断は事業責任者・経営企画部の責務。レビュー頻度は推奨「四半期ごと」。仕訳ステータス === '仕訳振替'行を ROI 計算に含めるか: CLAUDE.md「仕訳振替の判定は=== '仕訳振替'の完全一致」を踏まえた判断が必要。デフォルトは「含める」(除外しない)。除外決定時はdto['仕訳ステータス'] !== '仕訳振替'のフィルタを追加する。- MAS-032(プロダクト別P/L)/ MAS-034(R&D費用集計)が未実装の段階で本案件を着手するか否か:
- 両案件未実装の場合、
PJ名の命名規約自体が未確立で、本ダッシュボードは空のまま。 - 推奨実施順序: MAS-032 / MAS-034 → MAS-036。または「MAS-036 を先行実装してマスタ・ダッシュボード枠組みのみ用意し、データ流入を MAS-032 / MAS-034 完了後に開始する」段階運用も可。
- 両案件未実装の場合、
PJ名命名規約の周知:R&D_/プロダクト_プレフィックスを全メンバーに周知し、既存仕訳データの遡及整備(リネーム)が必要かを判断する。整備が必要な場合は別案件として8XX_migration_*.jsを新設(CLAUDE.md「マイグレーションスクリプト運用ガイドライン」に従う)。- マスタ未登録プロジェクトの表示方法:
- 案A: 当該行を出力しない(マスタ整備を強く促進)
- 案B:
関連プロダクト名を"マスタ未設定"と表示し、収益 = 0 で出力(投資が見えやすい) - デフォルト推奨は案A(マスタ整備を運用ルールとして定着させるため)
- 同一R&Dプロジェクトが複数プロダクトに紐付くケースの行展開:
- 案A: マスタ行ごとに 1 行(プロダクト別の細分粒度・本仕様書のデフォルト)
- 案B: R&Dプロジェクト単位で集約 1 行・関連プロダクト名はカンマ連結
紐付け開始年月/紐付け終了年月の運用: プロダクトの撤退・スピンアウト時に紐付け終了年月を入れる運用ルールを定義する。撤退判定の責任部署を明確化する。- 税抜 vs 税込: 本仕様書は
税抜金額_実績を集計対象とする(経営管理上の本来金額)。経営方針として税込で見たい場合は税込金額_実績に変更する経営判断が必要。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-036「R&D投資対効果(ROI)ダッシュボード」を実装してください。
## 実行前タスク(必ず Read してから実装着手)
- `000_infra/003_contracts.js` を Read し、`JournalEntryDTO` の全 `@property` を確認する。特に `有効フラグ` / `諸表区分` は未定義であることを確認(フィルタを使わない根拠)
- `200_data/202_repository.js` を Read し、`JournalRepository.findAll()` の戻り値型 `{ headers, dtos }` と `readSheetAsDtos_(sheet)` の引数・戻り値を確認
- `000_infra/004_utils.js` を Read し、`Utils.parseDateToYm(val)` → `"YYYY-MM" | ""` と `Utils.parseAmt(val)` → `number` のシグネチャを確認
- `100_config/101_sys_config.js` を Read し、`setupAllSchemas` 内の `schemas` 定義形式(L826〜)、システムキー登録 `confSheet.appendRow([...])` ブロック(L773〜)を確認
- `000_infra/002_constants.js` の `MENU_DEFINITION`(L206〜)を Read し、`📋 サイドバー: 📊 マート更新` カテゴリ(L230〜)の既存項目 `buildKpiDashboard` の表記を確認(実在文字列のみ引用)
- `600_report/609_datamart_kpi.js` を Read し、`buildKpiDashboard()` の「シート取得→clear→描画」パターンを把握
- `600_report/601_datamart_ingest.js` を Read し、プライベートヘルパーの命名規則(`dm*_`)を把握
- MCP または `getValues` で `42_trn_journal` の `PJ名` 列サンプルを10件程度確認し、`R&D_` / `プロダクト_` プレフィックスが実在するかを検証(実データ検証 #1-#6)
- `CLAUDE.md` の「DDL (setupAllSchemas) で管理されないタブ」一覧を確認
## 修正対象ファイル
- `600_report/610_datamart_rd_roi.js`(**新規作成**)
- `100_config/101_sys_config.js`(`MST_RD_LINK` の DDL スキーマ追加 + システムキー登録行 1 行)
- `000_infra/002_constants.js`(`MENU_DEFINITION` に 1 項目追加のみ)
- `CLAUDE.md`(「DDL非管理タブ」一覧に `94_rd_roi_dashboard` を追記)
## 実装内容
### 1. `600_report/610_datamart_rd_roi.js` 新規作成
公開関数: `buildRdRoiDashboard()`
処理ステップ:
1. **シート初期化・冪等性確保**
- `ss.getSheetByName('94_rd_roi_dashboard')` でシート取得。未存在なら `ss.insertSheet('94_rd_roi_dashboard')` で生成
- `sheet.clearContent()` でデータクリア(ヘッダーも含めて全消去してから再書き込み)
2. **マスタ読込(19_mst_rd_product_link)**
- `ss.getSheetByName('19_mst_rd_product_link')` → `readSheetAsDtos_(sheet)` で DTO 配列取得
- 有効フラグチェック: `dto['有効フラグ'] === false || String(dto['有効フラグ']).toUpperCase() === 'FALSE'` はスキップ
- `linkMap[R&Dプロジェクト名] = [{ product, rate, startYm, endYm }, ...]` でマップ構築(同一R&Dに複数プロダクトがあり得る)
- `rate` が空欄/NaN → 1.0 をデフォルト
3. **仕訳読込(42_trn_journal)**
- `JournalRepository.findAll()` → `{ dtos }` を取得
- 投資フィルタ: `dto['収支区分'] === '支出'` かつ `String(dto['PJ名']).startsWith('R&D_')`
- 収益フィルタ: `dto['収支区分'] === '収入'` かつ `String(dto['PJ名']).startsWith('プロダクト_')` かつ `dto['科目名'] === '売上高'`
- 各仕訳で `ym = Utils.parseDateToYm(dto['発生日(P/L計上日)'])` を取得。`ym === ''` のレコードはスキップ
- 金額: `amt = Utils.parseAmt(dto['税抜金額_実績'])`
4. **月次集計**
- `investByRd[R&Dプロジェクト名][ym] += amt`(投資)
- `revenueByProduct[プロダクト名][ym] += amt`(収益)
5. **R&Dプロジェクト別 ROI 計算**
- マスタの全 R&Dプロジェクト行をループ
- 投資: `Object.values(investByRd[rdName]).reduce((a,b) => a+b, 0)`
- 収益: 紐付け期間内の月次収益 × `rate` を加算(`startYm <= ym <= endYm`、`endYm === ''` なら上限なし)
- 純利益 = 収益 - 投資
- ROI(%) = 投資 === 0 ? "-" : (純利益 / 投資) * 100
- 回収期間(月): 月次純利益の累積が初めて > 0 になった月数。該当なしなら "未回収"
- ステータス: 純利益 < 0 → "投資中"、純利益 >= 0 && 回収期間 === "未回収" → "回収中"、純利益 >= 0 && 回収期間 が数値 → "回収済"
6. **書き込み**
- ヘッダー行: `[R&Dプロジェクト名, 関連プロダクト名, 累計投資額, 累計リターン(貢献率調整後), 純利益, ROI(%), 回収期間(月), ステータス, 月次純利益推移]`
- データ行を `sheet.getRange(2, 1, rows.length, 9).setValues(rows)` で一括書き込み
- フィルター設定: `sheet.getRange(1, 1, 1, 9).createFilter()`(既存行にフィルタがあれば先に `sheet.getFilter().remove()`)
### 2. `100_config/101_sys_config.js` 変更
**2-A**: L773〜のシステムキー登録ブロックに 1 行追加(既存 `if (!existKeys.includes(...))` パターンに揃える):
if (!existKeys.includes('MST_RD_LINK')) confSheet.appendRow(['MST_RD_LINK', '', '19_mst_rd_product_link', 'マスタ_R&Dプロジェクト⇔プロダクト紐付け']);
**2-B**: L826〜の `schemas` 定義に 1 エントリ追加(他のマスタ(MST_ACCT 等)の近くに、色は慣例の `#666666`):
'MST_RD_LINK': { headers: ["有効フラグ","R&Dプロジェクト名","関連プロダクト名","収益貢献率(%)","紐付け開始年月","紐付け終了年月"], color: "#666666",
validations: {
"収益貢献率(%)": { type: 'range', min: 0, max: 1, helpText: '0〜1の小数で入力してください(例: 0.8 = 80%)' }
}
},
### 3. `000_infra/002_constants.js` 変更
`MENU_DEFINITION` の `📋 サイドバー: 📊 マート更新` カテゴリ(L230〜)の `items` 配列末尾(`📸 前年度P/Lスナップショット` の後)に 1 行追加:
{ label: '🔬 R&D ROIダッシュボード', funcName: 'buildRdRoiDashboard', description: 'R&Dプロジェクト別 投資対効果 (94 タブ) を再計算' },
### 4. `CLAUDE.md` 変更
「DDL (setupAllSchemas) で管理されないタブ」セクションの `93_kpi_dashboard,` の直後に `94_rd_roi_dashboard,` を挿入。
## 制約
- 列番号ハードコード禁止。ヘッダー名ベースで列参照(`headers.indexOf('PJ名')` 等)。CLAUDE.md 規約
- `94_rd_roi_dashboard` は DDL 管理外。`101_sys_config.js` の `schemas` には追加しない
- メニュー文字列・シート名・関数名は実在する文字列のみ引用。造語禁止(失敗パターン #20)
- `PJ名` プレフィックス(`R&D_` / `プロダクト_`)は実データ検証(実行前タスク)で確認した値に従う。記憶で書かない(失敗パターン #18)
- `JournalEntryDTO` には `有効フラグ` / `諸表区分` が存在しない。これらのフィールドを参照しない
- `仕訳ステータス === '仕訳振替'` の扱いはデフォルト「含める」(仕様書「人間が検討すべき事項 2」で別途判断)
- ゼロ除算時は `"-"`(ハイフン文字列)を出力。`0` や空文字にしない(失敗パターン #2)
## エッジケース(実装必須)
| 条件 | 実装 |
|------|------|
| 累計投資額 = 0 | ROI(%) セルに `"-"` を出力 |
| 純利益 < 0 | ROI(%) を負値でそのまま出力 |
| 回収未到達(累積純利益が一度も > 0 にならない) | 回収期間に `"未回収"` を出力 |
| マスタ未登録の R&Dプロジェクト | 出力行なし(「人間が検討すべき事項 5」決定後に再検討) |
| `収益貢献率(%)` が空欄 | デフォルト 1.0 を適用 |
| `紐付け終了年月` が空欄 | 終了期限なしとして全期間を対象 |
| `紐付け開始年月` より前の収益 | 貢献率 = 0 で除外 |
| 同一R&Dに複数プロダクトのマスタ行 | マスタ行ごとに 1 行出力(デフォルト・プロダクト別の細分粒度) |
## 動作確認
1. `npm run push:dev` で dev 環境にデプロイ
2. GAS エディタで `setupAllSchemas()` を実行 → `19_mst_rd_product_link` シートが生成され、ヘッダー・バリデーションが設定されることを確認
3. `19_mst_rd_product_link` にテストデータを入力(R&Dプロジェクト名・関連プロダクト名・収益貢献率 1.0・紐付け開始年月)
4. `42_trn_journal` に `PJ名 = 'R&D_xxx'` の支出と `PJ名 = 'プロダクト_xxx'` かつ `科目名 = '売上高'` の収入のテストデータがあることを確認(なければ手動で数件追加)
5. サイドバー or メニュー「📊 マート更新 → 🔬 R&D ROIダッシュボード」を実行
6. `94_rd_roi_dashboard` に ROI 計算結果が出力されることを確認
7. 累計投資額 = 0 の行で ROI(%) が `"-"` になることを確認(ゼロ除算テスト)
8. 2 回連続実行し、重複行が発生しないこと・既存行が置き換わることを確認(冪等性テスト)
9. `19_mst_rd_product_link` の `収益貢献率(%)` に 1.5 を入力しようとしてバリデーションでブロックされることを確認(範囲チェック)
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 仕様書作成(本ドキュメント) | Claude Opus 4.7 | 前提案件未実装の中で紐付けロジック・エッジケース網羅・マスタスキーマ設計を決め切る必要があり、複数ファイル横断の設計判断が必要 |
610_datamart_rd_roi.js 新規作成 | Claude Sonnet 4.6 | 既存 609_datamart_kpi.js の「シート取得→clear→描画」パターン適用+ROI/回収期間算出ロジックの実装。設計判断は本仕様書で確定済み |
101_sys_config.js スキーマ・システムキー追記 | Claude Sonnet 4.6 | 挿入位置の特定(L773付近・L826付近)と既存 if (!existKeys.includes(...)) / schemas[KEY] = {...} パターンの踏襲 |
002_constants.js メニュー追記 | Claude Haiku 4.5 | MENU_DEFINITION 配列への 1 項目追加のみ。判断要素なし |
CLAUDE.md DDL非管理タブ追記 | Claude Haiku 4.5 | 1 行追加のみ |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成 |
仕様書作成プロンプト
展開して表示
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
- 拡張思考の使い分け: Phase 1(設計)ではフル活用し、ファイル名・関数名・行番号・エッジケース一覧・Step分割粒度を完全に確定させる。Phase 2(清書)の各Step内では最小限に抑え、Phase 1確定内容の書き下しに徹する。出力途中で再考しない。
- テキスト報告の禁止: 「〜を作成します」等のtext-only turnを作らない。説明は1文以内。直ちに tool を呼ぶ。
- 4-5 分割の Write/Edit 実行: 2-1(骨格
20行)/2-2(概要〜注意事項300行)/2-3a(エッジケース〜人間検討事項200行)/2-3b(実装プロンプト〜変更履歴250行)/2-4(プロンプト全文記録) に分割。1回の Write/Edit は約300行以内。 - 各Stepで何を書くかを具体指示: 設計判断はPhase 1で完結させ、Phase 2では持ち込まない。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 MAS-036「R&D投資対効果(ROI)ダッシュボード」の開発仕様書を作成してください。
作成後、docs/_config.json の nav 配列(§E.5 FP&A・レポーティング)に必ず追記してください。
Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
以下を全て Read/Grep してから Phase 2 に進むこと。「名前から類推」禁止。全固有名詞は Read で裏取り済みのものだけ記述する(失敗パターン #18-#20)。
案件定義:
docs/_internal/TODO_future.mdで MAS-036 の概要・期待効果・人間が検討すべき事項を取得。JournalEntryDTO の全フィールド確認:
000_infra/003_contracts.jsを Read し、JournalEntryDTOの全@propertyを確認。特に以下のフィールドが実在するかをコードで確認:PJ名、科目名、収支区分、諸表区分、税抜金額_実績、税込金額_実績。有効フラグはOrderDTOには存在するがJournalEntryDTOには未定義の可能性あり — 必ず目視確認し、仕様書に記載する。Repositoryパターン確認:
200_data/202_repository.jsを Read し、JournalRepository.findAll()の戻り値型{ headers, dtos }と、プライベートヘルパーreadSheetAsDtos_(sheet)の実装(引数・戻り値型)を確認。19_mst_rd_product_linkの読み込みにこのヘルパーを流用するため。Utils 関数のシグネチャ確認:
000_infra/004_utils.jsを Read し、Utils.parseDateToYm(val)とUtils.parseAmt(val)のシグネチャ・戻り値型を確認。setupAllSchemas パターンとメニュー構造の確認:
100_config/101_sys_config.jsを Read し、以下を確認:setupAllSchemas内のスキーマ定義形式(カラム定義・型・プルダウン設定の書き方の具体的な構造)onOpen()のメニュー構造(実在するメニュー項目の文字列・階層)- CLAUDE.md に列挙された「DDL非管理タブ」(
93_kpi_dashboard等)と94_rd_roi_dashboardの扱いの整合性を判断する
既存データマートのモジュール構造確認:
600_report/配下の既存ファイルを1件 Read し(601_datamart_ingest.js等)、公開関数の命名規則・冪等性パターン(シートクリア処理)・モジュール末尾の構造を把握する。新規610_datamart_rd_roi.jsの雛形として使用する。先行案件仕様書の確認: 以下のファイルが存在するか Glob で確認し、あれば関連箇所を Read する:
docs/dev/dev_mas-032_*.md(プロダクト別P/L)docs/dev/dev_mas-034_*.md(R&D費用集計)- 存在しない場合は「前提案件が未実装であることを仕様書に明記し、実施順序を人間が検討すべき事項に記載する」
失敗パターン確認:
docs/_internal/failure_patterns.mdを Read し、以下を把握:- #2(ゼロ除算フォールバック)
- #18〜#20(仕様書記述での固有名詞誤記:型誤認・シート名混同・存在しないメニュー名造語)
- #21〜#24(Sheets数式設計・文字列書き込みの落とし穴)
_config.json の §E.5 構造確認:
docs/_config.jsonを Read し、§E.5 セクションの既存エントリ形式・連番を確認する。フォーマット参考仕様書の確認:
docs/dev/dev_mas-001_variance_analysis.mdを Read し、新機能(FP&A・レポート)案件の仕様書フォーマット・実装プロンプト形式・推奨実行モデルテーブルの書き方を把握する(Step 2-3b で使用)。
Phase 2: 仕様書の分割作成
出力先: docs/dev/dev_mas-036_rd_roi_dashboard.md
絶対に1回のツール呼び出しで全文を出力しない。以下のStepに必ず分割すること。
Step 2-1: 骨格の作成(File Write、~20行)
以下の見出しのみを含む骨格ファイルを Write する(本文は空欄で可):
# MAS-036: R&D投資対効果(ROI)ダッシュボード
## 概要
## 目的
## 前提案件
## 現在のコード(関連既存実装)
## 修正方針
## 影響範囲
## 注意事項
## エッジケース
## 実データ検証
## 関連ドキュメント
## 人間が検討すべき事項
## 実装プロンプト(Claude Code 用)
## 推奨実行モデル
## 変更履歴
## 仕様書作成プロンプト
Step 2-2: 概要〜注意事項の追記(File Edit または Bash heredoc、~300行)
Phase 1 で確認した内容をもとに記述する。ここで設計を再考しない。
「概要」テーブル:
- 案件ID: MAS-036 / カテゴリ: FP&A・レポーティング / 前提案件: MAS-032(プロダクト別P/L)・MAS-034(R&D費用集計) / 対象ファイル(新規):
600_report/610_datamart_rd_roi.js/ 対象ファイル(既存変更):100_config/101_sys_config.js
「修正方針」に記述する設計内容:
アーキテクチャ:
- 新規モジュール
600_report/610_datamart_rd_roi.jsを作成。公開関数名は Phase 1 で確認した既存データマートの命名規則に倣う(例:buildRdRoiDashboard())。 - 出力先シート
94_rd_roi_dashboardは setupAllSchemas 非管理(93_kpi_dashboardと同様に動的生成・上書き。CLAUDE.md の「DDL非管理タブ」一覧に追記が必要)。シートが存在しない場合はモジュール内でss.insertSheet('94_rd_roi_dashboard')して生成する。 - 新規マスタシート
19_mst_rd_product_linkは setupAllSchemas に追加(101_sys_config.jsの DDL スキーマ定義に追記。Phase 1 で確認したスキーマ定義形式に従う)。 - メニュー登録:
101_sys_config.jsのonOpen()にbuildRdRoiDashboardの呼び出しを追加。メニュー文字列は Phase 1 で確認した実在する文字列のみ使用すること(造語禁止)。
- 新規モジュール
データソースと取得方法:
- 投資額:
JournalRepository.findAll().dtosから収支区分 === '支出'かつPJ名.startsWith('R&D_')のレコードをフィルタし、Utils.parseAmt(dto['税抜金額_実績'])で金額パース後に累計集計。(有効フラグの扱いは Phase 1 確認結果に従う) - 収益額: 同 dtos から
収支区分 === '収入'かつPJ名.startsWith('プロダクト_')かつ科目名 === '売上高'のレコードをフィルタし、同様に集計。 - 日付正規化: 全日付フィールドは
Utils.parseDateToYm(val)でYYYY-MM形式に正規化してから月次集計。String(Date)による直接比較禁止(失敗パターン #17)。 - 紐付けロジック:
19_mst_rd_product_linkシートをreadSheetAsDtos_(sheet)で読み込み(GAS グローバルスコープからアクセス可能)、有効フラグ === falseはスキップ。収益貢献率(%)が空欄の場合はデフォルト 1.0(100%)を使用。紐付け終了年月が空欄の場合は期限なし扱い。マスタ未設定プロジェクトはダッシュボードに出力しない(または「マスタ未設定」と明示)。
- 投資額:
新規マスタスキーマ (
19_mst_rd_product_link):- カラム:
有効フラグ(boolean) /R&Dプロジェクト名(string) /関連プロダクト名(string) /収益貢献率(%)(number, 0〜1) /紐付け開始年月(string, YYYY-MM) /紐付け終了年月(string, YYYY-MM, 空欄許容)
- カラム:
ダッシュボード出力レイアウト(1行 = 1 R&Dプロジェクト):
- 列構成:
R&Dプロジェクト名/関連プロダクト名/累計投資額/累計リターン(貢献率調整後)/純利益(リターン - 投資額)/ROI(%)(純利益 ÷ 累計投資額)/回収期間(月)/ステータス(投資中 / 回収中 / 回収済)/月次純利益推移(Sparkline)
- 列構成:
「影響範囲」: 新規ファイル1件(600_report/610_datamart_rd_roi.js)+ 既存ファイル変更1件(100_config/101_sys_config.js に 19_mst_rd_product_link DDLスキーマ追加・メニュー追加)+ CLAUDE.md の「DDL非管理タブ」一覧に 94_rd_roi_dashboard を追記。既存シート・既存モジュールへの破壊的変更なし。
「注意事項」:
94_rd_roi_dashboardは setupAllSchemas 管理外の動的生成シート。CLAUDE.md の「DDL非管理タブ」リストに94_rd_roi_dashboardを追記すること(93_kpi_dashboardの直後)。PJ名のプレフィックス規約('R&D_'・'プロダクト_')は Phase 1 の実データ検証で確認した実際の値に従うこと。記憶・推測で記述しない(失敗パターン #18-#20)。- 列番号ハードコード禁止。ヘッダー名ベースで列参照する(CLAUDE.md コーディング規約)。
- 冪等性:
buildRdRoiDashboard()冒頭で94_rd_roi_dashboardのデータ行をclearContent()してから書き込む。 - 仕訳ステータス
=== '仕訳振替'の行をROI計算に含めるか除外するかは「人間が検討すべき事項」に記載し、実装者が独断で決定しないこと。
Step 2-3a: エッジケース〜人間が検討すべき事項の追記(File Edit または Bash、~200行)
「エッジケース」テーブル(| 条件 | 表示値 | 理由 |):
| 条件 | 表示値 | 理由 |
|---|---|---|
| 累計投資額 = 0 | ROI(%) = "-" | ゼロ除算回避(失敗パターン #2) |
| 純利益 < 0(投資超過) | ROI(%) を負値でそのまま表示 | 投資超過状態を正確に表現 |
| 累計リターンが累計投資額を一度も超えない | 回収期間 = "未回収" | 月次データ上で交差点が存在しない |
| 紐付けマスタにR&Dプロジェクト未登録 | 当該行を出力しない(または "マスタ未設定" と表示) | 計算根拠なし。「人間が検討すべき事項」と整合させること |
| マスタ定義あり・投資または収益データ0件 | 金額を 0 として表示 | データなし ≠ マスタなし。存在を示すために行は出力する |
| 紐付け終了年月を過ぎた期間の収益 | 貢献率 = 0 で除外 | 対象外期間の収益は含めない |
「実データ検証」(実装着手前に MCP または直接シート参照で確認):
42_trn_journalシートのPJ名列でR&D_/プロダクト_プレフィックスが実際に使われているか(サンプル値を目視確認)科目名 === '売上高'の仕訳レコードがPJ名 = 'プロダクト_xxx'形式で実在するか有効フラグ列が42_trn_journalシートに物理的に存在するか(JournalEntryDTOの typedef には未定義のため要確認)収支区分の実際の値が'収入'/'支出'であることを確認(DDLコード値 vs 実データの乖離チェック。失敗パターン #3)
「人間が検討すべき事項」:
- TODO_future.md MAS-036 記載事項を転記
19_mst_rd_product_linkの収益貢献率(%)の設定は経営判断。事業責任者・経営企画部による定期レビュー(推奨: 四半期ごと)が必要- 仕訳ステータス
'仕訳振替'行をROI計算に含めるか除外するかを決定すること(CLAUDE.md: 「仕訳振替の判定は=== '仕訳振替'の完全一致」) - MAS-032(プロダクト別P/L)・MAS-034(R&D費用集計)が未実装の場合、本案件は実装不可。実施順序を確定すること
PJ名の命名規約(R&D_/プロダクト_プレフィックス)を全メンバーに周知し、既存データとの整合性を確認すること- マスタ未登録プロジェクトの表示方法(行を非表示 vs
"マスタ未設定"と表示)を決定すること
Step 2-3b: 実装プロンプト〜変更履歴の追記(File Edit または Bash、~250行)
dev_mas-001_variance_analysis.md(Phase 1 で確認済み)のフォーマットに倣い、以下を記述する。実装プロンプトは行頭4スペースインデント、バッククォートブロック使用禁止。
(実装プロンプト本文は本仕様書「実装プロンプト(Claude Code 用)」セクションに記述)
「推奨実行モデル」テーブル(dev_mas-001_variance_analysis.md の形式に倣う):
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 610_datamart_rd_roi.js 新規作成 | Claude Sonnet | 既存データマートパターン適用+ROI計算ロジックの設計判断が必要 |
| 101_sys_config.js スキーマ・メニュー追記 | Claude Sonnet | 挿入位置の特定と既存パターンの適用が必要 |
「変更履歴」: | 2026-04-20 | 初版作成 |
Step 2-4: 仕様書作成プロンプトの記録(File Edit または Bash)
仕様書末尾の「仕様書作成プロンプト」セクションに <details><summary>展開して表示</summary> ブロックで本 <instruction> 全文を記録する。
Phase 3: _config.json への追記
docs/_config.json を Read し、§E.5(FP&A・レポーティング)セクションの既存エントリ連番を確認してから以下を追記する:
{ "file": "dev/dev_mas-036_rd_roi_dashboard.md", "title": "E.5.X MAS-036 R&D ROI ダッシュボード" }
(X は既存エントリ数から適切な番号を付与する)