概要

項目内容
案件IDMAS-004
カテゴリFP&A
PhaseP3(予測高度化・シミュレーション)
優先度
所要時間(推定)1〜1.5 日(Step 1=0.25d / Step 2=0.25d / Step 3=0.5d + 動作確認 0.25d)
対象ファイル新規 600_report/614_datamart_rolling_forecast.js
100_config/101_sys_config.jsDDL 登録・メニュー追加・03_sys_params パラメータ追加)
前提案件なし(F-01 予実差異分析が既に ctx.isActualOnly による境界月ロック方式を導入済で、本案件の実装に流用する)

目的

常に「直近実績月+向こう 11 ヶ月(計 12 ヶ月)」を表示する単一タブ 67_pl_rolling_forecast を生成し、実績確定月はロックしつつ翌月以降の予測値を直近実績トレンドから再計算する。固定の会計年度(8 月起点の 12 ヶ月)に縛られない「動く 12 ヶ月」ビューを提供することで、予算期末付近の予測精度低下と、予算見直しまでの予測空白期間をなくすことが目的である。

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

項目参照
P/L セクション定義・集計600_report/603_datamart_pl.js
 ・dmBuildPlSections_(taxRates) (L59-79): 売上高/売上原価/販管費/営業外/特別/法人税等の group・profit・auto_tax セクション定義
 ・dmGetPlSectionId_(disp, accName) (L87-100): 科目 → セクションID 判定
オーケストレーション600_report/602_datamart_main.js
 ・dmProcessAllEvents_(ctx) (L28-149): 42_trn_journal + 41_trn_budget を martPl[sectionId][科目名] = [Total, M1, ..., M12] 形式の 13 要素配列に集計
 ・targetMonths 生成 (L192-193): 会計年度 12 ヶ月分の "YYYY-MM" 配列
 ・boundaryMonthStr (L216-217): 実績/予測の境界月
既存タブ構造61_pl_monthly (PL_M_ACT): ヘッダー [ "P/L科目 (表示区分 > 勘定科目)", "通期(Total)", "YYYY-MM" × 12 ]dmBuildPlOutput_() (L196-463) が 13 列で出力
実績専用モードの境界月フィルタdmBuildPlOutput_()filterValues / filterWithRecalcTotal (L207-230)。ctx.isActualOnly=true の時に targetMonths[i] >= boundaryMonthStr 以降を空白にする既存パターン
Repository / DTO200_data/202_repository.js: JournalRepository.findAll(){headers, dtos: JournalEntryDTO[]}BudgetRepository は未定義のため、41_trn_budget は本案件でも直接シートアクセスする
集計ロジック流用元602_datamart_main.js L94 以降の期ずれ計算・603_datamart_pl.js L115-189 の dmCalcPl_() による group/profit/auto_tax 合算。本案件は P/L 科目のみ対象で B/S・期ずれ計算は不要

修正方針

本案件の出力は 61_pl_monthly とは別軸(会計年度ではなく「直近実績月を起点とする 12 ヶ月」)であるため、既存の 602_datamart_main.js のパイプラインは流用せず、新規ビルダー buildRollingForecast() を独立関数として切り出す。セクション定義・科目 → セクションID 判定のみ既存関数 dmBuildPlSections_() / dmGetPlSectionId_() を呼び出して再利用する。

新規シート 67_pl_rolling_forecast

  • フォーマット: 61_pl_monthly を踏襲。1 列目 = 科目ラベル、2 列目 = 通期(Total)、3〜14 列目 = 月別列(12 ヶ月)。
  • 行ラベル列: 既存 P/L と同じ構成(■ 売上高  売上高 【売上高 計】✨ 売上総利益 → ... → ✨ 当期純利益)。
  • 列ヘッダー(月列): 03_sys_paramsLAST_ACTUAL_MONTH を起点とし、Utils.addMonths(ym, -1) を 11 回繰り返した配列を反転して得る「過去 0 ヶ月 〜 未来 11 ヶ月」の 12 ヶ月。具体的には列 C = 基準月(実績)、列 D 〜 列 N = 基準月+1 〜 基準月+11(予測)。
  • DDL 管理: 対象とする。101_sys_config.jssetupAllSchemas の CONFIG_SHEET 登録(L791〜 の PL_M_ACT 等と同じ箇所)にシステムキー PL_ROLL_FC を追加し、schemas ディクショナリには列ヘッダーが動的(基準月に依存)なため追加しない(ヘッダーは buildRollingForecast() 実行時に毎回書き直す。75_ss_equity_changes / 77_pj_raw 等と同じ「動的生成タブ」方針)。CLAUDE.md の「DDL で管理されないタブ」一覧にも本タブを追加すること。
  • セルの区別: 基準月列(実績)と翌月以降の 11 列(予測)で背景色を変える(§エッジケース参照)。

基準月 (LAST_ACTUAL_MONTH) の取得

var baseYm = String(Constants.getParam('LAST_ACTUAL_MONTH', '')).trim();
  • 空文字の場合は Utils.toastResult(FUNC, '⚠️ LAST_ACTUAL_MONTH が 03_sys_params に未設定です', 10) で通知し処理中断。
  • 現在日 new Date()Utils.parseDateToYm() で当月 YM に変換し、baseYm > 当月YM の場合も処理中断(§エッジケース)。

トリガー(メニュー)

現状 Constants.MENU_DEFINITION000_infra/002_constants.js L206-228)に FP&A 系メニューは存在しない🚀 BizLP💾 バックアップ のみ)。操作は原則 openOperationsSidebar() のサイドバーへ集約されている。したがって本案件では、以下のいずれかを新規追加する(Step 1 で決定):

  • 案 A(推奨): templates/operations_sidebar.html の FP&A セクション相当箇所に「🔁 ローリングフォーキャスト更新」ボタンを追加し、google.script.run.buildRollingForecast() を呼ぶ。既存の FP&A レポート系(buildBudgetTrendDataMart 等)と同じパターン。
  • 案 B: Constants.MENU_DEFINITION に新規カテゴリ 📊 FP&A レポート を追加し、items: [{ label: 'ローリングフォーキャスト更新', funcName: 'buildRollingForecast' }] を登録。

いずれの案でも関数名は buildRollingForecast で統一する(動作確認ダイアログから直接実行しやすいため)。実装時はまず operations_sidebar.html の既存構造を Read して案 A を優先採用し、既存 FP&A 機能が MENU_DEFINITION 側に登録されている場合のみ案 B へ切り替える。

大規模実装のため Step 分割

実装プロンプトでも同 Step 番号を使用する。

Step 1: シート初期化・DDL・メニュー登録

サブタスク内容
1-a101_sys_config.js の CONFIG_SHEET 登録箇所に if (!existKeys.includes('PL_ROLL_FC')) confSheet.appendRow(['PL_ROLL_FC', '', '67_pl_rolling_forecast', 'P/L ローリングフォーキャスト']); を追加
1-bbuildRollingForecast() の冒頭で 67_pl_rolling_forecast シートを Utils.getSheetByKey('PL_ROLL_FC', '67_pl_rolling_forecast') で取得し、未存在時は ss.insertSheet() で作成
1-c既存データをクリア(sheet.clear())後、行ラベル列(A 列)を既存 61_pl_monthly と同じ構造で書き込む。具体的には dmBuildPlSections_(taxRates) を呼んで PL_SECTIONS を取得し、group セクションは「group_header 行 + 科目明細行(初期は 0 行、Step 2 で動的追加)+ 小計行」、profit / auto_tax は 1 行を書き込む
1-d列ヘッダー(行 2)に [科目名, 通期(Total), M1, M2, ..., M12] を書き込む。月文字列セルは必ず setNumberFormat('@') してから setValue("YYYY-MM") で書く(失敗パターン #23)
1-eメニュー or サイドバーに「🔁 ローリングフォーキャスト更新」ボタン追加(前節「トリガー」参照)
1-f03_sys_params に必要なパラメータキー(LAST_ACTUAL_MONTH / FC_TREND_MONTHS / FC_HORIZON_MONTHS)が未登録なら 03_sys_params に append する処理を 101_sys_config.jssetupAllSchemas 内に追加(F-01 の CFG_VARIANCE_ALERT_PCT と同様パターン)

Step 2: 基準月以前の実績埋め込み

サブタスク内容
2-aJournalRepository.findAll() で 42_trn_journal 全件取得
2-b有効フラグ=FALSE の行はスキップ(CLAUDE.md コーディング規約)
2-cAccountRepository.findAsMap() で科目 → {stmt, cat} マップを取得し、stmt === "P/L" または P/L 系(売上/販管/原価等)の仕訳のみを対象とする
2-d各仕訳について Utils.parseDateToYm(発生日(P/L計上日)) で YM を取得し、基準月以前ym <= baseYm)かつ基準月を含む直近 12 ヶ月範囲内ym >= Utils.addMonths(baseYm, -11))のみを集計対象とする
2-edmGetPlSectionId_(表示区分, 科目名) でセクションID に分類し、mart[sectionId][科目名][monthIdx] += 税抜金額_実績 を積み上げる(monthIdx は基準月起点の相対インデックス 0〜11)
2-f列位置の特定は列ヘッダー名ベースheaders.indexOf(ymStr) する(列番号ハードコード禁止・失敗パターン #21)
2-gdmCalcPl_(ctx) を呼び出して group 合計・profit・auto_tax を再計算し、出力用の 2 次元配列を構築

Step 3: 基準月翌月以降の予測計算・書き込み

直近 N ヶ月(デフォルト FC_TREND_MONTHS=303_sys_params で変更可)の実績を使い、以下のロジックで基準月+1 ヶ月目〜基準月+11 ヶ月目の予測値を計算する。

カテゴリ判定基準予測ロジックゼロ除算時の表示
売上科目dmGetPlSectionId_()sales / non_op_inc / ext_inc を返す科目直近 N ヶ月の「実績売上 ÷ 予算売上」比率の単純平均 × 将来月の予算売上直近 N ヶ月の予算売上がすべて 0 → "—"
変動費科目科目マスタの 固変区分 === 'FV_VAR' または '変動費'直近 N ヶ月の「実績変動費 ÷ 実績売上」比率の単純平均 × 予測売上高(上記)直近 N ヶ月の実績売上がすべて 0 → "—"
固定費科目科目マスタの 固変区分 === 'FV_FIX' または '固定費' / 'FV_NA' / 空41_trn_budget の当該月の 予算金額 をそのまま採用(トレンド補正なし)予算 0 円の月 → 0 をそのまま表示("—" にしない)

41_trn_budget の読み取り:

var budSheet = Utils.getSheetByKey('TRN_BUDG', '41_trn_budget');
var budData = budSheet.getDataRange().getValues();
var budHeaders = budData[0].map(function(h) { return String(h).trim(); });
var budDtos = [];
for (var i = 1; i < budData.length; i++) {
  budDtos.push(Contracts.toDto(budHeaders, budData[i]));
}
// 有効フラグ=FALSE をスキップしつつ { 対象年月, 科目名, 予算金額 } を集計

BudgetRepository202_repository.js に未定義のため、本案件では readSheetAsDtos_ 相当の処理を 614_datamart_rolling_forecast.js 内部にインライン実装する。将来 BudgetRepository が追加された場合は乗り換える前提で、読み取りロジックは 1 箇所(プライベートヘルパー fetchBudgetDtos_())に集約しておく。

書き込み:

  • 予測値は背景色 #fce5cd(薄オレンジ、既存 COLORS には該当なしのためリテラル指定)、実績列は背景色 #fff2ccConstants.COLORS.PROFIT_BG)で区別する。
  • 手動上書きセル(§エッジケース参照)の判定にも本背景色ルールを使う。

二重実行防止

実行時間が長い(42_trn_journal の全件走査 + 12 ヶ月 × 科目数の書き込み)ため、以下で多重実行を防ぐ:

var lock = LockService.getScriptLock();
if (!lock.tryLock(60000)) {
  Utils.toastResult(FUNC, '⚠️ 他の処理が実行中です。しばらくしてから再実行してください', 10);
  return;
}
try {
  // 本体
} finally {
  lock.releaseLock();
}

影響範囲

種別対象内容
新規作成600_report/614_datamart_rolling_forecast.jsbuildRollingForecast() 本体 + プライベートヘルパー
変更100_config/101_sys_config.jsCONFIG_SHEET に PL_ROLL_FC 追加 / 03_sys_params に LAST_ACTUAL_MONTH / FC_TREND_MONTHS / FC_HORIZON_MONTHS の初期値投入 / メニュー or サイドバー登録
変更templates/operations_sidebar.htmlFP&A セクションに「🔁 ローリングフォーキャスト更新」ボタン追加(Step 1-e 案 A 採用時)
変更CLAUDE.md「DDL で管理されないタブ」一覧に 67_pl_rolling_forecast を追記(タブ自体は PL_ROLL_FC で CONFIG_SHEET 管理するが、列ヘッダーが動的)
新規シート67_pl_rolling_forecast本案件で初回生成
既存シートへの影響なし42_trn_journal / 41_trn_budget / 61_pl_monthly いずれも読み取りのみ。書き込み・DDL 変更なし

注意事項

  1. getLastColumn() / getLastRow() による動的範囲取得は使わない。月列は 12 ヶ月の固定範囲(列 C〜N)として扱い、行数は行ラベル列(A 列)の非空行数を GAS 側でカウントして決定する。(失敗パターン #21)
  2. 年月文字列セルへの書き込みは setValue("'YYYY-MM")(先頭アポストロフィで強制文字列化)または setNumberFormat('@') を先に設定してから setValue("YYYY-MM") とする。setValue("2026-04") を直接書くと Sheets が 2026 - 4 = 2022 の減算式と解釈する。(失敗パターン #23)
  3. ラベル検索は数式側(MATCH + ARRAYFORMULA)で行わない。GAS 側で行番号を事前特定し、リテラル埋め込みで数式を生成する。本案件は全セルを値書き込み(数式なし)とするため基本的に該当しないが、将来「実績/予測をトグルする数式」を追加する際はこの原則を守る。(失敗パターン #24)
  4. BudgetRepository200_data/202_repository.js に定義されていない。41_trn_budgetreadSheetAsDtos_ 相当の処理(Contracts.toDto(headers, row))を本ファイル内に実装する。将来 Repository が追加された際に乗り換えできるよう、読み取り箇所は fetchBudgetDtos_() に閉じ込める。
  5. 固変区分の判定値は実データで再確認する。DDL 定義上は FV_NA(対象外)/FV_VAR(変動費)/FV_FIX(固定費)の 3 値コード(101_sys_config.js L1309)だが、既存コード(603_datamart_pl.js L285)は fv === 'FV_VAR' || fv === '変動費' と両方に対応している。本案件でも両方受け入れる実装とする(失敗パターン #3)。
  6. 42_trn_journal の金額列は 税抜金額_実績JournalEntryDTO)を使用する。税込金額_実績 を P/L 集計に使うと消費税込み数字になり、61_pl_monthly と乖離する。
  7. Utils.addMonths(ymStr, months)"YYYY-MM" 形式を前提とする。Date オブジェクトを渡すと空文字が返るため、必ず Utils.parseDateToYm() で変換してから渡す。
  8. Constants.getParam() は内部キャッシュ (_paramsCache) を持つ。03_sys_params に新規キーを追加した直後に値を読む場合、Constants._paramsCache = null でキャッシュを破棄する処理を setupAllSchemas の末尾に入れる。

エッジケース

条件表示値 / 挙動理由
直近 N 月の予算売上がすべてゼロ(予実差異率の分母=0)該当セルに "—" を表示(Number ではなく文字列)ゼロ除算回避(失敗パターン #2)。Infinity / NaN をシートに書くと後続の集計で暴走する
直近 N 月の実績売上がすべてゼロ(変動費率の分母=0)該当セルに "—" を表示ゼロ除算回避。ベンチマーク変動費率が算出不能なため予算値で代替しない(恣意的な値になる)
実績データが N 月未満しかない(トレンド計算不可)当該科目の全予測月は 41_trn_budget の予算値をそのまま採用データ不足時のフォールバック。売上・変動費ともに「トレンド補正なし=予算そのまま」に退避させる
LAST_ACTUAL_MONTH が未来日付(当月 YM よりも後)処理中断 + Utils.toastResult(FUNC, '⚠️ LAST_ACTUAL_MONTH が未来日付です。03_sys_params を確認してください', 10)実績なし月を基準にしたトレンド計算は無意味。誤設定の早期検知
LAST_ACTUAL_MONTH が未設定(空文字 / undefined)処理中断 + Utils.toastResult(FUNC, '⚠️ LAST_ACTUAL_MONTH が 03_sys_params に未設定です', 10)基準月なしでは実績/予測の境界が不明で、全セルが予測値扱いになり意味をなさない
LAST_ACTUAL_MONTH の形式が "YYYY-MM" でない処理中断 + Utils.toastResult(FUNC, '⚠️ LAST_ACTUAL_MONTH の形式が不正です ("YYYY-MM" を期待)', 10)Utils.addMonths() が空文字を返してしまい列ヘッダーが全空になるのを防ぐ
手動上書きセル(背景色が予測用の #fce5cd 以外に変更されている)が存在する状態で再計算実行直前に SpreadsheetApp.getUi().alert('ローリングフォーキャスト再計算', '手動で上書きされたセルが N 個検出されました。\\n[はい] 既存の上書きを保持し、他セルのみ更新\\n[いいえ] 全セルを自動計算値で洗い替え\\n[キャンセル] 中断', ui.ButtonSet.YES_NO_CANCEL) で選択Human-in-the-Loop(下記参照)。ユーザーが手で調整した値を勝手に上書きしない
42_trn_journal発生日(P/L計上日) が空 or Date 変換不可の行がある当該行をスキップし、console.warn にログ出力(停止はしない)既存の dmProcessAllEvents_ でも空日付はスキップしている挙動に合わせる
41_trn_budget に基準月翌月以降 11 ヶ月分の予算が入っていない科目がある当該月は 0 を表示("—" ではなく 0。予算未設定=見込みなし、の会計意味を尊重)売上/変動費は "—" だが、固定費は「予算なし=ゼロ」が事業上正しい
基準月が会計年度(8 月〜7 月)の境界をまたぐ列 C〜N は単純に baseYm → baseYm+11 の連続 12 ヶ月を表示。会計年度リセット処理は行わないローリング予測の本質は「年度に依らない 12 ヶ月」である。F-25 予算vs実績差異とは意図的に別軸とする
固変区分 が空 or FV_NA(対象外)の科目固定費扱い(予算値をそのまま採用、トレンド補正なし)安全側の挙動。変動費扱いすると売上連動で振れ幅が大きすぎる
実績トレンドが全科目で不在(42_trn_journal が空)処理中断 + Utils.toastResult(FUNC, '⚠️ 42_trn_journal に実績がありません', 10)意味ある出力を作れない

Human-in-the-Loop 設計要件

要件実装
パラメータ調整トレンド計算期間は 03_sys_paramsFC_TREND_MONTHS(デフォルト 3)、予測期間は FC_HORIZON_MONTHS(デフォルト 11、基準月を含めず翌月以降の月数)で変更可能にする。Constants.getParam('FC_TREND_MONTHS', 3) / Constants.getParam('FC_HORIZON_MONTHS', 11) で取得
手動上書き機能67_pl_rolling_forecast 上での直接手入力を前提とする。ユーザーが手入力すると Sheets のデフォルト背景色(白)になり、自動計算値の背景色(実績=#fff2cc / 予測=#fce5cd)と自然に区別される
手動値の可視化予測セル背景色 = #fce5cd(薄オレンジ、ハードコードリテラル。COLORS パレットに該当色なし)
実績セル背景色 = Constants.COLORS.PROFIT_BG#fff2cc000_infra/002_constants.js L51 で確認済)
手動上書きセル = 背景色なし(白)=Sheets デフォルト
再計算時の選択buildRollingForecast() 冒頭で 67_pl_rolling_forecast をスキャンし、予測列範囲(列 D〜N)で背景色が #fce5cd 以外のセルを「手動上書きセル」として検出。1 個以上あれば ui.alert()YES_NO_CANCEL ダイアログを出し、YES=保持 / NO=洗い替え / CANCEL=中断の 3 分岐で処理
監査ログ再計算時に Utils.auditLog('RUN', '67_pl_rolling_forecast', '', '', FUNC, '', '', '基準月=' + baseYm + ' / 手動保持=' + keepManual + ' / 更新セル数=' + updatedCount) を呼んで N-03 の操作追跡に残す

実データ検証

Phase 1-G の MCP による実データ確認は本仕様書作成時点(2026-04-21)では未実施(MCP 接続が本環境で利用できないため)。実装時に以下を必ず確認すること:

確認項目確認方法未確認時の挙動
03_sys_paramsLAST_ACTUAL_MONTH キーが登録されているかdev 環境の 03_sys_params タブを開いて A 列を確認未登録の場合、Step 1-f で setupAllSchemas が初期値 "" で追加する。運用者が手動で "2026-03" 等を設定する必要あり(仕様書・動作確認手順にも記載)
41_trn_budget の列ヘッダーが BudgetDTO 定義と一致しているか(有効フラグ / 予算ID / 対象年月 / 決済予定年月 / 予算バージョン / 収支区分 / 組織名 / PJ名 / 科目名 / 予算金額 / 摘要 / 収支区分コード / 組織コード / PJコード / 主科目コードdev 環境の 41_trn_budget タブのヘッダー行を確認乖離している場合は 003_contracts.js L133-149 または 101_sys_config.jsTRN_BUDG schemas のどちらが正か判定し、不整合を解消してから本案件に着手する(失敗パターン #3)
11_mst_account固変区分 列の実データ値(FV_NA/FV_VAR/FV_FIX コードか、対象外/変動費/固定費 表記か)dev 環境の 11_mst_account の固変区分列を抽出実装時は両方受け入れる(`fv === 'FV_VAR'
42_trn_journal発生日(P/L計上日) / 税抜金額_実績 列の実データ先頭 100 行をサンプル取得Date 型と文字列が混在している可能性があるため Utils.parseDateToYm() で必ず正規化する

関連ドキュメント

仕様書関連箇所
CLAUDE.mdコーディング規約(ヘッダー名ベースのデータアクセス / 有効フラグ=FALSE スキップ)、変更時の動作確認テスト(600_report/6*_datamart_*.jsマート更新テスト)
dev_F-01_variance_analysis.md既存 P/L 差異分析との役割分担。本案件(F-04)は「動く 12 ヶ月」、F-01 は「会計年度固定 12 ヶ月」
dev_F-25_budget_variance.md41_trn_budget を読み取るパターンの参照元
docs/prd.mdHuman-in-the-Loop ポリシー(AI/自動処理の結果は必ず人間がレビュー・承認してから確定)
docs/arch/arch_data_model.md11_mst_account の固変区分の役割
docs/_internal/failure_patterns.md#2 ゼロ除算、#3 DDL 定義と実データ乖離、#21 getLastColumn 誤用、#23 年月文字列セルの減算式誤解釈、#24 ラベル解決を数式側で行う誤り
docs/test/02_integration_test.mdマート更新テスト手順

人間が検討すべき事項

TODO_future.md 由来(案件登録時点の検討事項)

  • 予測期間: 12 ヶ月 or 18 ヶ月 のどちらを採用するか。本仕様書は 12 ヶ月(基準月 + 向こう 11 ヶ月)をデフォルトとし、03_sys_params.FC_HORIZON_MONTHS で可変にした。18 ヶ月に拡張する場合は列幅・列ヘッダー処理のリファクタが必要。
  • 更新頻度: 月次 or 週次。現状はユーザーが「🔁 ローリングフォーキャスト更新」ボタンを押したタイミング(手動実行)。自動化する場合は時間ドリブントリガーを installAutoOpenSidebarTrigger と同様のパターンで追加する。

Phase 1 調査で判明した追加事項

  • FP&A 系メニューの新設: Constants.MENU_DEFINITION に FP&A 系カテゴリが存在しない。本案件で 📊 FP&A レポート カテゴリを新設するのか、それとも templates/operations_sidebar.html のサイドバーのみに追加するのか、運用方針として決める必要がある。F-01 / F-03 / F-25 など他の FP&A 案件は現状どちらに入っているのかも含めて整合させる(実装時に operations_sidebar.html を Read して決定)。
  • 固変区分が未整備の科目の扱い: 11_mst_account で固変区分が FV_NA または空の科目が売上系に含まれている場合、現仕様では「固定費扱い=予算そのまま」となり売上予測が走らない。売上系は dmGetPlSectionId_()sales を返す科目すべてをトレンド計算対象とする(固変区分に依存しない)方針で良いか要確認。
  • 直近 N ヶ月の「N」の選定根拠: デフォルト 3 ヶ月は経験則。季節性の強い業種では 12 ヶ月移動平均が適切、安定業種では 6 ヶ月が良い等の業種別推奨値があれば 03_sys_params のコメント or 仕様書に記載すると運用者が調整しやすい。
  • 再計算時の手動上書き保持/洗い替えのデフォルト: 現仕様は 3 択ダイアログだが、心理的負担が大きい(毎回判断を要求される)。「デフォルトは保持、洗い替えたい時だけ明示オプション」のようなトグルスイッチ(03_sys_params.FC_KEEP_MANUAL_OVERRIDES)化も検討余地あり。
  • 再計算パフォーマンス: 42_trn_journal が数万行規模になった場合、findAll() の全件読み込み + 基準月起点 12 ヶ月フィルタはメモリと時間を消費する。対策として「基準月の 2 年前以降のみ getRange() で部分読み込み」に切り替える最適化は将来課題。

Human-in-the-Loop 関連

  • 再計算実行時の上書き保持ロジックを「セル背景色で判定」する方式は簡便だが、ユーザーが意図せず背景色を変更した場合に誤判定する。より厳密にするには「開発者メモ」列(M 列)のチェックボックスで手動フラグを明示する代替案もある。どちらを採用するかは実運用のフィードバックを得て決定する。

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

以下は各 Step を個別に実装する際に Claude Code に渡すプロンプトである。行頭 4 スペースインデントで記述しているため、markdown ビルド時はそのままコードブロックとしてレンダリングされる。

Step 1: シート初期化・DDL・メニュー登録

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-04「ローリングフォーキャスト」の Step 1(シート初期化・DDL・メニュー登録)を実装してください。

## 実行前タスク
- `200_data/202_repository.js` を Read し、`JournalRepository.findAll()` の戻り値型と `readSheetAsDtos_` のパターンを確認する
- `000_infra/003_contracts.js` を Read し、`BudgetDTO` / `JournalEntryDTO` の列定義プロパティ名を確認する
- `000_infra/002_constants.js` を Read し、`Constants.getParam(key, defaultVal)` の引数型と `COLORS` の実在キー、`MENU_DEFINITION` 構造を確認する
- `100_config/101_sys_config.js` を Read し、CONFIG_SHEET への登録箇所(L790 付近の `PL_M_ACT` 〜 `PL_VAR`)と `setupAllSchemas` の 03_sys_params 初期値投入パターン、および `onOpen()` のメニュー生成方法を確認する
- `600_report/603_datamart_pl.js` を Read し、`dmBuildPlSections_(taxRates)` が返す `PL_SECTIONS` 配列の構造を確認する
- `templates/operations_sidebar.html` を Read し、FP&A 系ボタンの既存配置と `google.script.run.<funcName>()` 呼び出しパターンを確認する
- `03_sys_params` の実データを確認し、`LAST_ACTUAL_MONTH` キーの有無と値の形式を確認する

## 修正対象ファイル
- `600_report/614_datamart_rolling_forecast.js`(新規作成。Step 1〜3 は全てこのファイルに実装)
- `100_config/101_sys_config.js`(CONFIG_SHEET に PL_ROLL_FC 追加 / 03_sys_params 初期値投入 / メニュー or サイドバー登録)
- `templates/operations_sidebar.html`(サイドバー案採用時のみ)

## 実装内容
1. `614_datamart_rolling_forecast.js` を新規作成し、公開関数 `buildRollingForecast()` の骨格(LockService ロック取得 + baseYm 取得 + 事前バリデーション + try/finally)を実装する
2. シート `67_pl_rolling_forecast` を取得(未存在時は `ss.insertSheet()` で作成)し、既存内容を `clear()` してから行ラベル列・列ヘッダー行を書き込む
3. 列ヘッダー行(行 2): `[科目名, 通期(Total), M1, M2, ..., M12]`。M1〜M12 は `baseYm` 起点で `Utils.addMonths(baseYm, i)` を i=0..11 で生成。セルは `setNumberFormat('@')` してから `setValue("YYYY-MM")` で書く(失敗パターン #23)
4. 行ラベル列(A 列): `dmBuildPlSections_(Constants.TAX_RATES)` を呼んで `PL_SECTIONS` を取得し、既存 `61_pl_monthly` と同じ構成(group は group_header + 小計、profit / auto_tax は 1 行)を書き込む。group 内の科目明細行は Step 2 で動的追加するため Step 1 では骨格のみ
5. `101_sys_config.js` の CONFIG_SHEET 登録箇所(L791〜)に `if (!existKeys.includes('PL_ROLL_FC')) confSheet.appendRow(['PL_ROLL_FC', '', '67_pl_rolling_forecast', 'P/L ローリングフォーキャスト']);` を追加
6. `setupAllSchemas` の末尾(または 03_sys_params 初期値投入セクション)に `LAST_ACTUAL_MONTH`(空文字 or 当月 YM の 1 ヶ月前)、`FC_TREND_MONTHS`(`3`)、`FC_HORIZON_MONTHS`(`11`)を `getSheetByName('03_sys_params').appendRow([key, val, 'ローリングフォーキャスト用']);` で追加(既存キーなら skip)
7. 実行ボタン追加(以下のいずれか・事前タスクで確認した現状に合わせる):
   - 案 A: `templates/operations_sidebar.html` の FP&A セクションに「🔁 ローリングフォーキャスト更新」ボタンを追加し `google.script.run.buildRollingForecast()` を呼ぶ
   - 案 B: `000_infra/002_constants.js` の `Constants.MENU_DEFINITION` に `{ category: '📊 FP&A レポート', items: [{ label: '🔁 ローリングフォーキャスト更新', funcName: 'buildRollingForecast' }] }` を追加

## 制約
- `BudgetRepository` は存在しない。`41_trn_budget` は `Contracts.toDto(headers, row)` で直接読む(Step 2 以降で必要)
- `getLastColumn()` による動的列範囲取得を使わない(失敗パターン #21)
- 年月文字列セルは `setNumberFormat('@')` + `setValue("YYYY-MM")`(失敗パターン #23)
- 列番号ハードコード禁止。ヘッダー名ベースで `indexOf` する
- 有効フラグ=FALSE の行は全処理でスキップ

## エッジケース
(仕様書「エッジケース」テーブルの全行を Step 2 / Step 3 で実装で網羅すること。Step 1 ではシート初期化時の `baseYm` バリデーション 3 件のみ)

## 動作確認
1. `npm run push:dev` でデプロイ
2. GAS エディタで `setupAllSchemas()` を実行 → 03_sys_params に 3 キーが追加され、CONFIG_SHEET に PL_ROLL_FC が登録されていること
3. 03_sys_params の `LAST_ACTUAL_MONTH` に `"2026-03"` 等の文字列を手入力
4. 案 A/B どちらかの方法で `buildRollingForecast()` を実行 → 67_pl_rolling_forecast タブが作成され、列ヘッダーが `2026-03, 2026-04, ..., 2027-02` の 12 ヶ月、行ラベルに `■ 売上高` 〜 `✨ 当期純利益` が並んでいること(数値はまだ全セル空/0)
5. `03_sys_params` の `LAST_ACTUAL_MONTH` を未来日付に変更して再実行し、処理中断と `Utils.toastResult()` 通知が出ることを確認(エッジケース)

Step 2: 基準月以前の実績埋め込み

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-04「ローリングフォーキャスト」の Step 2(基準月以前の実績埋め込み)を実装してください。

## 実行前タスク
- `200_data/202_repository.js` を Read し、`JournalRepository.findAll()` / `AccountRepository.findAsMap()` の戻り値型を確認する
- `000_infra/003_contracts.js` を Read し、`JournalEntryDTO` の列名(特に `発生日(P/L計上日)` / `税抜金額_実績` / `科目名` / `有効フラグ`)を確認する
- `600_report/603_datamart_pl.js` を Read し、`dmGetPlSectionId_(disp, accName)` / `dmCalcPl_(ctx)` の呼び出し方と `martPl[sectionId][科目名] = Array(13).fill(0)` 構造を確認する
- `600_report/602_datamart_main.js` の `dmProcessAllEvents_` を Read し、`targetMonths.indexOf(ym)` 方式の集計パターンを確認する
- `000_infra/004_utils.js` を Read し、`Utils.parseDateToYm(val)` の仕様(Date / 文字列 / YYYY/MM / YYYY-MM 対応)を確認する

## 修正対象ファイル
- `600_report/614_datamart_rolling_forecast.js`(Step 1 で作成したファイルに Step 2 の処理を追加)

## 実装内容
1. `buildRollingForecast()` 本体の「シート初期化」のあとに、以下の順で実装:
   a. `var baseYm = String(Constants.getParam('LAST_ACTUAL_MONTH', '')).trim()` で基準月取得
   b. 直近 12 ヶ月の YM 配列を `Utils.addMonths(baseYm, -11+i)` で i=0..11 生成し `targetMonthsActual` とする
   c. `JournalRepository.findAll()` で 42_trn_journal を読み、`有効フラグ === false` の行をスキップ
   d. 各行について `Utils.parseDateToYm(dto['発生日(P/L計上日)'])` で YM 取得し、`targetMonthsActual.indexOf(ym)` で月インデックス特定(-1 ならスキップ)
   e. `AccountRepository.findAsMap()` で `{stmt, cat}` 取得。`stmt` が B/S 系の科目はスキップ(P/L のみ)
   f. `dmGetPlSectionId_(表示区分, 科目名)` でセクションID 決定し、`mart[sectionId][科目名][monthIdx] += 税抜金額_実績` を積み上げ
   g. `mart[sectionId][科目名][0]` に月合計を集計
2. `dmCalcPl_(ctx)` に `{PL_SECTIONS, martPl: mart, TAX_RATES: Constants.TAX_RATES}` を渡し group/profit/auto_tax 再計算
3. Step 1 の行ラベルと照合し、実績月列(基準月起点の過去側 12 ヶ月の中で 42_trn_journal に実績がある月のみ)の値を `sheet.getRange(row, col).setValue(val).setBackground('#fff2cc')` で書き込む。**本タブの列 C〜N は基準月 → 基準月+11 ヶ月の未来向き 12 ヶ月である**ため、Step 2 で実績が入るのは列 C(= baseYm)の 1 列のみ。それ以前の 11 ヶ月分の実績は集計メモリ上には残すが、シート書き込み対象外(= Step 3 でのトレンド計算にのみ使用)
4. 通期(Total)列(列 B)は実績月のみの合計で一旦書き込む(Step 3 で予測を加えた合計に上書き)

## 制約
- 列番号ハードコード禁止。`headers.indexOf(ymStr)` で列特定
- `税抜金額_実績` を使う(税込不可)
- 有効フラグ=FALSE は全処理でスキップ

## エッジケース
- `42_trn_journal` が空 or P/L 実績ゼロ件 → 中断(`Utils.toastResult()`)
- `発生日(P/L計上日)` が空 or 変換不可 → 該当行スキップ、`console.warn` 出力
- 有効フラグ=FALSE 行 → スキップ

## 動作確認
1. `npm run push:dev` でデプロイ
2. `buildRollingForecast()` 実行 → 列 C(基準月)に実績値が背景色 `#fff2cc` で書き込まれ、61_pl_monthly の同月列と一致することを確認
3. 列 D〜N は未埋め(Step 3 で埋まる)
4. 有効フラグ=FALSE の仕訳を追加して再実行 → 列 C の値が変わらないこと

Step 3: 基準月翌月以降の予測計算 + Human-in-the-Loop

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-04「ローリングフォーキャスト」の Step 3(予測計算・書き込み + 手動上書き保持)を実装してください。

## 実行前タスク
- `000_infra/003_contracts.js` の `BudgetDTO` 列定義(有効フラグ / 対象年月 / 科目名 / 予算金額 等)を Read
- `100_config/101_sys_config.js` L827 の `'MST_ACCT'` schemas で `固変区分` が 8 列目であること、および L1309 付近の `FV_NA`/`FV_VAR`/`FV_FIX` コード値を確認
- `600_report/603_datamart_pl.js` L282-291 の `fv === 'FV_VAR' || fv === '変動費'` 判定パターンを確認
- `03_sys_params` に `FC_TREND_MONTHS` / `FC_HORIZON_MONTHS` / `LAST_ACTUAL_MONTH` が登録されていることを確認
- `41_trn_budget` の実データサンプルを 10 行程度確認し、対象年月・科目名・予算金額の形式を把握

## 修正対象ファイル
- `600_report/614_datamart_rolling_forecast.js`(Step 2 までで作成したファイルに Step 3 の処理を追加)

## 実装内容
1. **手動上書き保持ダイアログ**: シート初期化前(`clear()` の前)に予測列(D〜N)の背景色が `#fce5cd` 以外のセルを検出。1 個以上あれば `ui.alert('ローリングフォーキャスト再計算', メッセージ, ui.ButtonSet.YES_NO_CANCEL)` で 3 択:
   - YES → `manualOverrides[row][col] = {value, background}` に退避
   - NO → 何もせず続行(全洗い替え)
   - CANCEL → return(中断)
2. **固変区分マップの取得**: `AccountRepository.findAll()` を直接呼び、`fvMap[科目名] = dto['固変区分']` を構築(`findAsMap()` は固変区分を返さないため代用不可)
3. **41_trn_budget の読み取り**(プライベートヘルパー `fetchBudgetDtos_()`):
   ```
   var budSheet = Utils.getSheetByKey('TRN_BUDG', '41_trn_budget');
   var data = budSheet.getDataRange().getValues();
   var headers = data[0].map(h => String(h).trim());
   var dtos = [];
   for (var i = 1; i < data.length; i++) dtos.push(Contracts.toDto(headers, data[i]));
   return dtos.filter(d => d['有効フラグ'] !== false && String(d['有効フラグ']).toUpperCase() !== 'FALSE');
   ```
4. **予測計算ループ**: 列 D 〜 列 D+FC_HORIZON_MONTHS-1 の各月 YM × 各科目について:
   - 売上系(`dmGetPlSectionId_` が `sales`/`non_op_inc`/`ext_inc`): 直近 N ヶ月の `実績売上 ÷ 予算売上` 比率平均 × 将来月の予算売上。予算ゼロ → `"—"`、実績不足 → 予算そのまま
   - 変動費系(固変区分が `FV_VAR` or `変動費`): 直近 N ヶ月の `実績変動費 ÷ 実績売上` 比率 × 予測売上。実績売上ゼロ → `"—"`
   - 固定費系(それ以外): 41_trn_budget の当該月の予算金額そのまま。未登録 → 0
5. 予測値を `sheet.getRange(row, col).setValue(val).setBackground('#fce5cd')` で書き込む。YES 選択時は `manualOverrides[row][col]` が存在するセルは手動値と背景色を復元
6. 通期(Total)列(列 B)を実績 + 予測の 12 ヶ月合計で再計算
7. `Utils.auditLog('RUN', '67_pl_rolling_forecast', '', '', 'buildRollingForecast', '', '', '基準月=' + baseYm + ' / 手動保持=' + (keepManual?'Y':'N'))` と `Utils.toastResult('buildRollingForecast', '✅ 完了', 5)` を呼ぶ

## エッジケース(必ず実装に含めること)
- `baseYm === ''` / 未来日付 / 形式不正 → 中断 + `Utils.toastResult()`
- 直近 N 月の予算売上すべて 0 → 売上予測セルを `"—"`
- 直近 N 月の実績売上すべて 0 → 変動費予測セルを `"—"`
- 実績データが N 月未満 → 当該科目は予算値そのまま
- 42_trn_journal 空 → 中断
- 41_trn_budget に当該科目の予測月予算未登録 → 固定費=0、売上/変動費=`"—"`
- 手動上書きセル検出時 → YES/NO/CANCEL ダイアログ

## 制約
- `BudgetRepository` 未定義のため `fetchBudgetDtos_()` で直接読む
- `getLastColumn()` 不使用
- 列番号ハードコード禁止
- `"—"` は文字列
- 有効フラグ=FALSE は全処理でスキップ

## 動作確認
1. `npm run push:dev` でデプロイ
2. `buildRollingForecast()` 実行 → 列 D〜N が背景色 `#fce5cd` で埋まる
3. `03_sys_params.LAST_ACTUAL_MONTH` を未来日付 `"2030-01"` に変更して再実行 → 処理中断 + `Utils.toastResult()`
4. 直近 3 ヶ月の予算値を 0 にして再実行 → 売上系予測セル = `"—"`
5. 予測値セルを手動上書き → 再実行 → YES/NO/CANCEL ダイアログ表示、YES で上書き保持、NO で洗い替え
6. 98_audit_log に `RUN / buildRollingForecast` 行が追加されること

推奨実行モデル

工程推奨モデル理由
仕様書作成(本ドキュメント)Claude OpusFP&A 機能の設計判断・複数ファイル横断・Human-in-the-Loop 設計
Step 1 シート初期化・DDLClaude Sonnet既存パターン(61_pl_monthly・F-01 の DDL 登録)の踏襲。中程度の判断(メニュー案 A/B の選択)
Step 2 実績埋め込みClaude SonnetJournalRepository.findAll() + dmGetPlSectionId_() + 既存集計パターンの応用
Step 3 予測計算・手動上書き保持Claude Opus固変区分判定・ゼロ除算・41_trn_budget 直接読み込み・手動上書き保持ロジック等、複数ファイル横断の設計判断と会計ロジック理解が必要

変更履歴

日付変更内容
2026-04-21初版作成。ローリングフォーキャスト仕様書。新規シート 67_pl_rolling_forecast への実績/予測ハイブリッド出力仕様、BudgetRepository 未定義のため fetchBudgetDtos_() による 41_trn_budget 直接読み取りパターンを明記、Human-in-the-Loop(手動上書き保持/洗い替え選択)を設計に含む。ファイル番号は既存 610-613 の roadmap 仮予約を避け 614 を採用

仕様書作成プロンプト

展開して表示

以下は本仕様書を作成した際の Claude Code への指示プロンプト全文である。仕様書の再作成・改訂時の参考として記録する。

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

======================================================================
あなたはGAS会計システムのシニア開発者兼仕様書ライターです。
CLIエージェント「Claude Code」として、案件 F-04「ローリングフォーキャスト」の開発仕様書を作成してください。
作成後は `docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。

---

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

**Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read。** 固有名詞(関数名・シート名・メニュー名・定数名)は Read で裏取り済みのものだけ仕様書に記載する。推測した瞬間に手を止めて Read する(失敗パターン #18-#20)。

### 1-A: 案件定義の読み込み
- `docs/_internal/TODO_future.md` を Read し、案件 F-04 の「案件名」「概要」「期待される効果」「人間が検討すべき事項」を把握する。

### 1-B: データアクセス層の確認
- `200_data/202_repository.js` を Read し、定義済みの Repository クラス(`JournalRepository` / `InvoiceRepository` / `BankTxRepository` / `AccountRepository` 等)と `findAll()` の戻り値型を確認する。
  - ⚠️ **`BudgetRepository` は同ファイルに定義されていない**。`41_trn_budget` の読み取りには Repository が存在しないため、`Contracts.toDtoList(sheet.getDataRange().getValues())` を使った直接シートアクセスで代替する(`readSheetAsDtos_` 内部ヘルパーと同パターン)。この制約を仕様書に明記すること。
- `000_infra/003_contracts.js` を Read し、`BudgetDTO`(`41_trn_budget` の列定義)と `JournalEntryDTO`(`42_trn_journal` の列定義)のプロパティ名を確認する。

### 1-C: ユーティリティ・定数の確認
- `000_infra/004_utils.js` を Read し、`addMonths(ymStr, months)` / `parseDateToYm(val)` / `toastResult(funcName, message, duration)` の引数・戻り値型を確認する。
- `000_infra/002_constants.js` を Read し、`Constants.getParam(key, defaultVal)` の実装(`03_sys_params` シートを参照する仕様)と `COLORS` パレットの実在キーを確認する。

### 1-D: メニュー名の確認(ハルシネーション防止・必須)
- `100_config/101_sys_config.js` を Read し、`onOpen()` 内の `createMenu` / `addItem` でFP&A系メニューの**実在する文字列**を特定する。仕様書に記載するメニュー名は、ここで確認した文字列のみ使用する。実在しない場合は「【要確認: 新規メニュー追加が必要】」と記載する(失敗パターン #20)。

### 1-E: 既存P/Lマートビルダーの確認
- `600_report/` 配下のファイルを Grep し、P/L月次集計を担当するファイル(`601_datamart_ingest.js` および `602_〜.js` 〜 `608_〜.js` の中で関連するもの)を特定してから Read する。科目別・月別集計の実装パターン(ループ構造・ヘッダー参照方式)を把握し、流用可能なロジックを確認する。
- `61_pl_monthly` シートのスキーマ(行ラベル=科目・列構成=年月)を MCP または Grep で確認し、`67_pl_rolling_forecast` のフォーマット設計の基準とする。
- 新規GASファイルの番号を確定する(`600_report/` 配下で使用済みの番号を Grep し、重複しない番号を選ぶ)。

### 1-F: 類似仕様書の読み込み
- `docs/dev/dev_F-01_variance_analysis.md` を Read し、FP&A機能の仕様書セクション構成・エッジケース記述パターンを把握する。

### 1-G: 実データ検証(MCP)
- `03_sys_params` に `LAST_ACTUAL_MONTH` キーが実際に登録されているか確認する。未登録の場合は実装前に追加が必要である旨を仕様書に記載する。
- `41_trn_budget` の列ヘッダーが `003_contracts.js` の `BudgetDTO` 定義と一致しているか確認する(失敗パターン #3)。

---

## Phase 2: 仕様書の分割作成

出力先: `docs/dev/dev_F-04_rolling_forecast.md`
**【重要】1 回のツール呼び出しで全内容を出力しない。以下の Step に厳密に分割して実行すること。**

### Step 2-1: 骨格の作成 (Write, ~20行)
全セクションの見出しのみを含む骨格ファイルを新規作成する(概要 / 目的 / 現在のコード(関連既存実装)/ 修正方針 / 影響範囲 / 注意事項 / エッジケース / 実データ検証 / 関連ドキュメント / 人間が検討すべき事項 / 実装プロンプト(Claude Code 用)/ 推奨実行モデル / 変更履歴 / 仕様書作成プロンプト)。

### Step 2-2: 前半セクションの追記 (Edit, ~300行)
概要テーブル・目的・現在のコード・修正方針(新規シート 67_pl_rolling_forecast / 基準月取得 / トリガー / Step 1〜3 / 二重実行防止)・影響範囲・注意事項(#21 #23 #24 等)を記述。

### Step 2-3a: エッジケース〜人間検討事項の追記 (Edit, ~200行)
ゼロ除算(予算/実績売上ゼロ)/ LAST_ACTUAL_MONTH 未来日付・未設定 / 手動上書き保持 / Human-in-the-Loop 設計要件 / 実データ検証結果 / 関連ドキュメント / 人間が検討すべき事項を記述。

### Step 2-3b: 実装プロンプト〜変更履歴の追記 (Edit, ~250行)
実装プロンプト(Step 1/2/3 の行頭 4 スペースインデント形式)/ 推奨実行モデルテーブル / 変更履歴テーブルを記述。

### Step 2-4: 仕様書作成プロンプトの記録 (Edit)
仕様書末尾の「仕様書作成プロンプト」セクションに `<details><summary>展開して表示</summary>` ブロックを追記し、この `<instruction>` 全文を記録する。

---

## Phase 3: 後処理(テキスト報告禁止。即座にツール実行)

### 3-A: `docs/_config.json` への追記
`docs/_config.json` を Read して §E.5 の実在セクション名と末尾の連番を確認してから、以下を追加する:

    { "file": "dev/dev_F-04_rolling_forecast.md", "title": "E.5.X F-04 ローリングフォーキャスト" }

### 3-B: `docs/_internal/changelog.md` への追記
先頭行(ヘッダー直後)に追記する:

    | 2026-04-21 | [dev_F-04_rolling_forecast.md](dev_mas-004_rolling_forecast.md) | 初版作成。ローリングフォーキャスト仕様書 |

### 3-C: Git コミット&プッシュ

    git add docs/dev/dev_F-04_rolling_forecast.md docs/_config.json docs/_internal/changelog.md
    git commit -m "docs: F-04 ローリングフォーキャストの開発仕様書を作成

    新規シート 67_pl_rolling_forecast への実績/予測ハイブリッド出力仕様。
    BudgetRepository 未定義のため Contracts.toDtoList による直接読み取りパターンを明記。
    Human-in-the-Loop(手動上書き保持/洗い替え選択)設計を仕様に含む。"
    git push -u origin {現在のブランチ}
</instruction>

📌 取り込み時の注記 (2026-06-02 sub 復元)

本仕様書は旧 F-番号体系で作成され PR 未マージのまま孤立していたドラフトを、origin/docs/dev-* ブランチから内容無改変で復元し、案件ID のみ MAS 体系へ正規化したもの。status: Open(未実装)

⚠️ ファイル番号ドリフト: 本文「対象ファイル」が指す 600_report/610〜612_*.js は現行 main で 別機能に使用済み(610=投資分析/MAS-013・611=財務モデリング/MAS-010・612=採用sim/MAS-012)。 実装時にファイル番号の再割当が必要。