概要

項目内容
案件IDMAS-036
カテゴリFP&A・レポーティング
PhaseP3
優先度
前提案件MAS-032(プロダクト別P/L)/ MAS-034(R&D費用集計)
対象ファイル(新規)600_report/610_datamart_rd_roi.js
対象ファイル(既存変更)100_config/101_sys_config.jsDDLスキーマ追加)
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-034R&D費用集計(R&Dプロジェクト軸の費用集計基盤)未実装(仕様書未作成)

重要: 本案件は MAS-032 / MAS-034 の前提となるデータ構造(PJ名プレフィックス規約 R&D_ / プロダクト_)に依存するため、両案件未実装の段階では実データから ROI 計算ができない。実施順序は「人間が検討すべき事項」を参照。

現在のコード(関連既存実装)

ファイル関連箇所流用ポイント
200_data/202_repository.jsJournalRepository.findAll()(L270){ headers, dtos: JournalEntryDTO[] } を返す。dtos 配列を 収支区分 / PJ名 / 科目名 でフィルタする
200_data/202_repository.jsreadSheetAsDtos_(sheet)(L19)プライベートヘルパー(GAS グローバルスコープから呼出可能)。新規 19_mst_rd_product_link 読込に流用
000_infra/003_contracts.jsJournalEntryDTO typedef(L97-129)収支区分 / PJ名 / 科目名 / 税抜金額_実績 / 税込金額_実績 / 発生日(P/L計上日) / 仕訳ステータス を読み出す
000_infra/004_utils.jsUtils.parseDateToYm(val)(L92)`Date
000_infra/004_utils.jsUtils.parseAmt(val)(L191)任意値 → number(パース不可なら 0
100_config/101_sys_config.jssetupAllSchemasschemas 定義(L826〜)'SYS_KEY': { headers: [...], color: "#HEX", validations: {...} } 形式。19_mst_rd_product_link の追加先
100_config/101_sys_config.jsconfSheet.appendRow([key, '', sheetName, label])(L773〜)システムキー登録ブロック。MST_RD_LINK を新規追加
000_infra/002_constants.jsMENU_DEFINITION(L206〜)の 📋 サイドバー: 📊 マート更新 カテゴリ(L230〜)新規メニュー項目 🔬 R&D ROIダッシュボード を追加
600_report/609_datamart_kpi.jsbuildKpiDashboard()(L21)+ シート clear → ctx 構築 → render の構造94_rd_roi_dashboard の動的生成・上書き+公開関数命名規則の雛形
600_report/601_datamart_ingest.jsdmIngestData_()(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_dashboardsetupAllSchemas 非管理93_kpi_dashboard と同様、動的生成・毎回上書き)。CLAUDE.md「DDL非管理タブ」一覧に追記する。シート未存在時は ss.insertSheet('94_rd_roi_dashboard') で生成。
  • 新規マスタシート 19_mst_rd_product_linksetupAllSchemas 管理101_sys_config.jsschemas 定義 L826〜 に追加)。システムキー MST_RD_LINK01_sys_config に登録。
  • メニュー登録: 000_infra/002_constants.jsMENU_DEFINITION📋 サイドバー: 📊 マート更新 カテゴリ(L230〜)に { label: '🔬 R&D ROIダッシュボード', funcName: 'buildRdRoiDashboard', description: 'R&Dプロジェクト別 投資対効果 (94 タブ) を再計算' } を追加。メニュー文字列はカテゴリ名と既存項目(📊 KPIダッシュボード再描画 等)の表記に揃える。造語禁止(失敗パターン #20)。

データソースと取得方法

集計対象データソースフィルタ条件金額カラム
投資額(R&D費用)JournalRepository.findAll().dtosdto['収支区分'] === '支出' かつ 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 に存在しないフィールド」参照)。
  1. シート取得: var linkSheet = ss.getSheetByName('19_mst_rd_product_link');
  2. DTO 化: var linkResult = readSheetAsDtos_(linkSheet);{ headers, dtos } を返す。GAS グローバルスコープから呼出可能)
  3. フィルタ: dto['有効フラグ'] === false || String(dto['有効フラグ']).toUpperCase() === 'FALSE' の行はスキップ(既存 AccountRepository.findAsMap L329-330 の判定パターンに準拠)
  4. マップ構築: { 'R&Dプロジェクト名': { product: '関連プロダクト名', rate: Number, startYm: 'YYYY-MM', endYm: 'YYYY-MM' | '' } }
  5. デフォルト値:
    • 収益貢献率(%) が空欄/null/NaN → 1.0(100%)
    • 紐付け終了年月 が空欄 → 期限なし扱い(全期間対象)
  6. マスタ未登録の R&Dプロジェクトの扱いはダッシュボード行の出力可否を決める(「人間が検討すべき事項」で確定)

新規マスタスキーマ(19_mst_rd_product_link)

100_config/101_sys_config.jsschemas 定義に追加(既存パターンに倣う):

カラム名説明
有効フラグbooleanFALSE の行はスキップ(科目マスタ等と同パターン)
R&Dプロジェクト名string42_trn_journalPJ名R&D_ プレフィックス込み)と完全一致
関連プロダクト名string42_trn_journalPJ名プロダクト_ プレフィックス込み)と完全一致
収益貢献率(%)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プロジェクト。列構成:

カラム名内容
AR&Dプロジェクト名42_trn_journalPJ名 値(R&D_ プレフィックス含む)
B関連プロダクト名マスタの 関連プロダクト名
C累計投資額投資データの全期間合計(円)
D累計リターン(貢献率調整後)収益額 × 収益貢献率(紐付け期間内のみ)
E純利益D - C
FROI(%)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.jsconfSheet.appendRow(['MST_RD_LINK', '', '19_mst_rd_product_link', 'マスタ_R&Dプロジェクト⇔プロダクト紐付け']) の追加(L773〜のシステムキー登録ブロック)/ schemas への 'MST_RD_LINK': {...} 追加(L826〜)
既存変更000_infra/002_constants.jsMENU_DEFINITION📋 サイドバー: 📊 マート更新 カテゴリ(L230〜)に 🔬 R&D ROIダッシュボード 項目を追加
既存変更CLAUDE.md「DDL (setupAllSchemas) で管理されないタブ」一覧に 94_rd_roi_dashboard93_kpi_dashboard の直後に追記
動作影響既存マート(P/L・B/S・CF・KPI 等)影響なし(読み取りのみで参照する 42_trn_journal を変更しない)

注意事項

  1. 94_rd_roi_dashboard は setupAllSchemas 管理外。動的生成シートのため、CLAUDE.md「DDL非管理タブ」リストに 94_rd_roi_dashboard を追記すること(93_kpi_dashboard の直後)。実装漏れがあると次回の DDL 全更新時に挙動が読めなくなる。
  2. PJ名のプレフィックス規約'R&D_' / 'プロダクト_')は実装着手前に「実データ検証」セクションの手順で実値を確認する。記憶・推測で記述しない(失敗パターン #18-#20)。
  3. 列番号ハードコード禁止。ヘッダー名ベースで列参照する(CLAUDE.md コーディング規約)。headers.indexOf('PJ名') 等の動的取得を必ず使用。
  4. 冪等性: buildRdRoiDashboard() 冒頭で 94_rd_roi_dashboard の既存行を sheet.clearContent() してから書き込む。シート未存在時は ss.insertSheet('94_rd_roi_dashboard') で生成。2 回連続実行で重複行が発生しないこと。
  5. 仕訳ステータス === '仕訳振替' の取扱いを実装者が独断で決めない。「人間が検討すべき事項」で経営判断を待つ。デフォルト挙動は「振替も含めて集計」とし、除外する判断が下りたら dto['仕訳ステータス'] !== '仕訳振替' のフィルタを追加する設計とする。
  6. JournalEntryDTO に 有効フラグ フィールドが存在しないため、42_trn_journal 側ではフラグフィルタ不要。一方、19_mst_rd_product_link 側では 有効フラグ === false || 'FALSE' のスキップを実施する(既存 AccountRepository.findAsMap L329-330 の判定パターンに準拠)。
  7. 収益貢献率(%) の値域は 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 のコメントに記録すること。

#確認対象確認方法期待値失敗パターン
142_trn_journalPJ名 列で R&D_ プレフィックスのプロジェクトが実在するかサンプル行の PJ名 値を 10 件程度目視1 件以上の R&D_ 始まり PJ が存在存在しない場合は本案件実装不可 → 人間が検討すべき事項に「PJ名 命名規約 R&D_ を新規制定する必要あり」と記載
242_trn_journalPJ名 列で プロダクト_ プレフィックスのプロジェクトが実在するか同上1 件以上の プロダクト_ 始まり PJ が存在同上(収益側)
3科目名 === '売上高' の仕訳で PJ名 = 'プロダクト_xxx' 形式が実在するかフィルタ目視該当仕訳が存在存在しない場合は MAS-032(プロダクト別P/L)未整備
442_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年スパンで評価する 等)。本仕様書は「累積純利益が初めてプラスに転じた月数」をステータス基準とするのみで、目安基準は持たない。

本仕様書で確定が必要な追加事項

  1. 19_mst_rd_product_link.収益貢献率(%) の運用主体・レビュー頻度: マスタ値の経営判断は事業責任者・経営企画部の責務。レビュー頻度は推奨「四半期ごと」。
  2. 仕訳ステータス === '仕訳振替' 行を ROI 計算に含めるか: CLAUDE.md「仕訳振替の判定は === '仕訳振替' の完全一致」を踏まえた判断が必要。デフォルトは「含める」(除外しない)。除外決定時は dto['仕訳ステータス'] !== '仕訳振替' のフィルタを追加する。
  3. MAS-032(プロダクト別P/L)/ MAS-034(R&D費用集計)が未実装の段階で本案件を着手するか否か:
    • 両案件未実装の場合、PJ名 の命名規約自体が未確立で、本ダッシュボードは空のまま。
    • 推奨実施順序: MAS-032 / MAS-034 → MAS-036。または「MAS-036 を先行実装してマスタ・ダッシュボード枠組みのみ用意し、データ流入を MAS-032 / MAS-034 完了後に開始する」段階運用も可。
  4. PJ名 命名規約の周知: R&D_ / プロダクト_ プレフィックスを全メンバーに周知し、既存仕訳データの遡及整備(リネーム)が必要かを判断する。整備が必要な場合は別案件として 8XX_migration_*.js を新設(CLAUDE.md「マイグレーションスクリプト運用ガイドライン」に従う)。
  5. マスタ未登録プロジェクトの表示方法:
    • 案A: 当該行を出力しない(マスタ整備を強く促進)
    • 案B: 関連プロダクト名"マスタ未設定" と表示し、収益 = 0 で出力(投資が見えやすい)
    • デフォルト推奨は案A(マスタ整備を運用ルールとして定着させるため)
  6. 同一R&Dプロジェクトが複数プロダクトに紐付くケースの行展開:
    • 案A: マスタ行ごとに 1 行(プロダクト別の細分粒度・本仕様書のデフォルト)
    • 案B: R&Dプロジェクト単位で集約 1 行・関連プロダクト名はカンマ連結
  7. 紐付け開始年月 / 紐付け終了年月 の運用: プロダクトの撤退・スピンアウト時に 紐付け終了年月 を入れる運用ルールを定義する。撤退判定の責任部署を明確化する。
  8. 税抜 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.5MENU_DEFINITION 配列への 1 項目追加のみ。判断要素なし
CLAUDE.md DDL非管理タブ追記Claude Haiku 4.51 行追加のみ

変更履歴

日付変更内容
2026-04-21初版作成

仕様書作成プロンプト

展開して表示

【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】

  1. 拡張思考の使い分け: Phase 1(設計)ではフル活用し、ファイル名・関数名・行番号・エッジケース一覧・Step分割粒度を完全に確定させる。Phase 2(清書)の各Step内では最小限に抑え、Phase 1確定内容の書き下しに徹する。出力途中で再考しない。
  2. テキスト報告の禁止: 「〜を作成します」等のtext-only turnを作らない。説明は1文以内。直ちに tool を呼ぶ。
  3. 4-5 分割の Write/Edit 実行: 2-1(骨格20行)/2-2(概要〜注意事項300行)/2-3a(エッジケース〜人間検討事項200行)/2-3b(実装プロンプト〜変更履歴250行)/2-4(
    プロンプト全文記録) に分割。1回の Write/Edit は約300行以内。
  4. 各Stepで何を書くかを具体指示: 設計判断はPhase 1で完結させ、Phase 2では持ち込まない。

====================================================================== あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。 案件 MAS-036「R&D投資対効果(ROI)ダッシュボード」の開発仕様書を作成してください。 作成後、docs/_config.jsonnav 配列(§E.5 FP&A・レポーティング)に必ず追記してください。


Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)

以下を全て Read/Grep してから Phase 2 に進むこと。「名前から類推」禁止。全固有名詞は Read で裏取り済みのものだけ記述する(失敗パターン #18-#20)。

  1. 案件定義: docs/_internal/TODO_future.md で MAS-036 の概要・期待効果・人間が検討すべき事項を取得。

  2. JournalEntryDTO の全フィールド確認: 000_infra/003_contracts.js を Read し、JournalEntryDTO の全 @property を確認。特に以下のフィールドが実在するかをコードで確認: PJ名科目名収支区分諸表区分税抜金額_実績税込金額_実績有効フラグOrderDTO には存在するが JournalEntryDTO には未定義の可能性あり — 必ず目視確認し、仕様書に記載する。

  3. Repositoryパターン確認: 200_data/202_repository.js を Read し、JournalRepository.findAll() の戻り値型 { headers, dtos } と、プライベートヘルパー readSheetAsDtos_(sheet) の実装(引数・戻り値型)を確認。19_mst_rd_product_link の読み込みにこのヘルパーを流用するため。

  4. Utils 関数のシグネチャ確認: 000_infra/004_utils.js を Read し、Utils.parseDateToYm(val)Utils.parseAmt(val) のシグネチャ・戻り値型を確認。

  5. setupAllSchemas パターンとメニュー構造の確認: 100_config/101_sys_config.js を Read し、以下を確認:

    • setupAllSchemas 内のスキーマ定義形式(カラム定義・型・プルダウン設定の書き方の具体的な構造)
    • onOpen() のメニュー構造(実在するメニュー項目の文字列・階層)
    • CLAUDE.md に列挙された「DDL非管理タブ」(93_kpi_dashboard 等)と 94_rd_roi_dashboard の扱いの整合性を判断する
  6. 既存データマートのモジュール構造確認: 600_report/ 配下の既存ファイルを1件 Read し(601_datamart_ingest.js 等)、公開関数の命名規則・冪等性パターン(シートクリア処理)・モジュール末尾の構造を把握する。新規 610_datamart_rd_roi.js の雛形として使用する。

  7. 先行案件仕様書の確認: 以下のファイルが存在するか Glob で確認し、あれば関連箇所を Read する:

    • docs/dev/dev_mas-032_*.md(プロダクト別P/L)
    • docs/dev/dev_mas-034_*.md(R&D費用集計)
    • 存在しない場合は「前提案件が未実装であることを仕様書に明記し、実施順序を人間が検討すべき事項に記載する」
  8. 失敗パターン確認: docs/_internal/failure_patterns.md を Read し、以下を把握:

    • #2(ゼロ除算フォールバック)
    • #18〜#20(仕様書記述での固有名詞誤記:型誤認・シート名混同・存在しないメニュー名造語)
    • #21〜#24(Sheets数式設計・文字列書き込みの落とし穴)
  9. _config.json の §E.5 構造確認: docs/_config.json を Read し、§E.5 セクションの既存エントリ形式・連番を確認する。

  10. フォーマット参考仕様書の確認: 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_dashboardsetupAllSchemas 非管理93_kpi_dashboard と同様に動的生成・上書き。CLAUDE.md の「DDL非管理タブ」一覧に追記が必要)。シートが存在しない場合はモジュール内で ss.insertSheet('94_rd_roi_dashboard') して生成する。
    • 新規マスタシート 19_mst_rd_product_linksetupAllSchemas に追加101_sys_config.js の DDL スキーマ定義に追記。Phase 1 で確認したスキーマ定義形式に従う)。
    • メニュー登録: 101_sys_config.jsonOpen()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.js19_mst_rd_product_link DDLスキーマ追加・メニュー追加)+ CLAUDE.md の「DDL非管理タブ」一覧に 94_rd_roi_dashboard を追記。既存シート・既存モジュールへの破壊的変更なし。

「注意事項」:

  1. 94_rd_roi_dashboard は setupAllSchemas 管理外の動的生成シート。CLAUDE.md の「DDL非管理タブ」リストに 94_rd_roi_dashboard を追記すること(93_kpi_dashboard の直後)。
  2. PJ名 のプレフィックス規約('R&D_''プロダクト_')は Phase 1 の実データ検証で確認した実際の値に従うこと。記憶・推測で記述しない(失敗パターン #18-#20)。
  3. 列番号ハードコード禁止。ヘッダー名ベースで列参照する(CLAUDE.md コーディング規約)。
  4. 冪等性: buildRdRoiDashboard() 冒頭で 94_rd_roi_dashboard のデータ行を clearContent() してから書き込む。
  5. 仕訳ステータス === '仕訳振替' の行をROI計算に含めるか除外するかは「人間が検討すべき事項」に記載し、実装者が独断で決定しないこと。

Step 2-3a: エッジケース〜人間が検討すべき事項の追記(File Edit または Bash、~200行)

「エッジケース」テーブル(| 条件 | 表示値 | 理由 |):

条件表示値理由
累計投資額 = 0ROI(%) = "-"ゼロ除算回避(失敗パターン #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 は既存エントリ数から適切な番号を付与する)