概要

項目内容
案件IDMAS-025
カテゴリFP&A・レポーティング
PhaseP1
優先度★★★
所要時間3-4時間
実装ステータス📝 仕様書段階・実装未着手 (2026-04-28 監査時点)
対象ファイル200_data/202_repository.js(BudgetRepository 追加)
100_config/101_sys_config.jsDDL登録: PL_BUD_VAR)
templates/operations_sidebar.html(操作パネルにボタン追加)
600_report/610_datamart_budget_variance.js(新規作成)

目的

41_trn_budget の予算データと 42_trn_journal の実績データを科目・月次で突合し、差異額・差異率を新規タブ 66_pl_budget_variance に出力するレポートビルダーを新規実装する。

MAS-001(65_pl_variance)は「直近の確定実績 vs 当初計画(dmIngestPlanData_ で生成される計画値)」を比較する着地見込み・承認漏れ検出ベースの差異分析であり、予算策定→PDCAサイクルの主要ツールである「正規予算(41_trn_budget の入力データ)vs 実績」の比較は未実装であった。本案件はこのギャップを埋めるもので、独立した出力タブ 66_pl_budget_variance に予算/実績/差異額/差異率の 4 行 × 全科目を出力する。

BudgetRepository が未実装であるため、200_data/202_repository.js への追加も本案件のスコープに含める。

現在のコード

ファイル/シート現状
200_data/202_repository.jsBudgetRepository が未定義(末尾は L351、AccountRepository.resetCache() 直後)。OrderRepository / InvoiceRepository / BankTxRepository / JournalRepository / AccountRepository のみ実装済み
100_config/101_sys_config.js41_trn_budget のシステムキーは TRN_BUDG(L604, L657)として登録済。ただし 66_pl_budget_variance の DDL 登録(L613 PL_VAR 隣接位置)は未登録
templates/operations_sidebar.html「📊 マート更新」セクション(L62-68)に予実差異分析ボタン未配置。本プロジェクトはサイドバー HTML を UI 起点とし、SpreadsheetApp.getUi().createMenu() ベースの GAS 標準メニューは使用していない
66_pl_budget_variance シート未作成
600_report/ 配下既存最大番号は 609_datamart_kpi.js(次の新規ファイルは 610 を採番)

修正方針

3 Step 構成で実装する。

Step 1 — BudgetRepository 追加(200_data/202_repository.js 末尾、L351 の直後)

OrderRepository(L107-146)と同一パターンで実装する。

  • _getSheet(): Utils.getSheetByKey('TRN_BUDG', '41_trn_budget')(システムキー登録済を活用)
  • findAll(): readSheetAsDtos_(BudgetRepository._getSheet()) を呼び、{ headers: string[], dtos: BudgetDTO[] } を返す
  • save() / append() は本案件では実装不要(読み取り専用)

有効フラグ のフィルタは Repository 層では行わず、呼び出し側でスキップする方針(既存 AccountRepository.findAsMap() L323-341 と同パターン)。

Step 2 — レポートビルダー新規作成(600_report/610_datamart_budget_variance.js

  • 関数名: buildBudgetVarianceReport()(公開関数)
  • 既存 600_report/ 配下のレポートビルダーは LockService を使用していない(609_datamart_kpi.js 等で確認済)。本案件でも LockService は導入せず、既存パターンに合わせて try { ... } catch (e) { Utils.logError(FUNC, e); throw e; } のみで実装する
  • 出力シートのクリア手段は sheet.clear()(書式・条件付き書式を含めて全消去、609_datamart_kpi.js L28 と同パターン)
  • 処理フロー:
    1. 予算データ取得BudgetRepository.findAll().dtos → 有効フラグフィルタ(flag === false || String(flag).toUpperCase() === 'FALSE' で除外)→ 予算バージョン === '最新予算(V1)' でフィルタ
    2. 予算データ 0 件ガードgetWebSpreadsheet_().toast('予算データが未登録です(バージョン=最新予算(V1)、有効フラグ=TRUE 該当なし)', 'F-25', 5) を表示して return
    3. 対象期間の確定 — フィルタ後予算 DTO から Utils.parseDateToYm(dto['対象年月']) の最小・最大値を求め、{ minYm, maxYm, count } の集計サマリを作成
    4. 実行前確認ダイアログSpreadsheetApp.getUi().alert('F-25 予算vs実績差異分析', '予算 ' + count + ' 件(' + minYm + ' 〜 ' + maxYm + ')を集計します。よろしいですか?', SpreadsheetApp.getUi().ButtonSet.OK_CANCEL) を表示し、OK 以外が返ったら return(Webアプリ経由で getUi() が取れない場合は try/catch でスキップして続行)
    5. 実績データ取得JournalRepository.findAll().dtos をそのまま使用(JournalEntryDTO には 有効フラグ が定義されていないことを Phase 1 1-3 で確認済。TRN_JOUR の DDL ヘッダー定義 L656 にも 有効フラグ は含まれない。フィルタ不要)
    6. 科目マスタ取得AccountRepository.findAsMap()(既存キャッシュをそのまま使用、resetCache() は呼ばない)
    7. 集計Utils.parseDateToYm() でキー生成し、科目名×YYYY-MM の 2 段階 Map 構造で budgetactual を加算するピボット集計。予算側は 予算金額、実績側は 税抜金額_実績 を使用。Utils.parseDateToYm が空文字列を返した行(パース不能)は集計対象外
    8. 出力シート取得・クリアgetWebSpreadsheet_().getSheetByName('66_pl_budget_variance')null なら ss.insertSheet('66_pl_budget_variance')sheet.clear()(書式含めて全消去)
    9. ヘッダー行書き込み — A=大分類、B=科目名、C=指標、D〜O=4月〜3月(会計年度 12 ヶ月、列ヘッダー値は YYYY-MM 文字列)、P=年度合計
    10. データ行書き込み — 科目ごとに「予算」「実績」「差異額」「差異率」の 4 行を書き込み
  • 差異計算:
    • 差異額 = 実績金額 − 予算金額
    • 差異率: 予算金額 ≠ 0 → 差異額 / 予算金額 / 予算金額 = 0 かつ 実績金額 = 0 → 0(数値)/ それ以外 → '-'(文字列)
  • 対象年度の決定: buildKpiDashboard609_datamart_kpi.js)が「P/L 売上高行の末尾非空セル」から境界月を逆算するのに対し、本案件は 予算データそのものから 対象年月 の最小〜最大を取り、該当年度を全カバーする 12 ヶ月 を対象とする。明示的な会計年度設定は行わず、予算の登録範囲=集計範囲とする(年度跨ぎがある場合は人間検討事項参照)

Step 3 — DDL 登録 + サイドバー UI 追加

3-1. DDL 登録(100_config/101_sys_config.js

setupAllSchemas 内、PL_VAR(L613)の直後に以下を追加:

if (!existKeys.includes('PL_BUD_VAR')) confSheet.appendRow(['PL_BUD_VAR', '', '66_pl_budget_variance', 'P/L予実差異分析(予算ベース)']);

schemas 辞書には登録しない(書式は buildBudgetVarianceReport 側で sheet.clear() 後に都度設定するため、DDL 管理外として扱う)。

3-2. サイドバー UI 追加(templates/operations_sidebar.html

「📊 マート更新」セクション(L62-68)の buildKpiDashboard ボタン直下に以下を追加:

<button class="btn" onclick="run('buildBudgetVarianceReport', this)">📊 予実差異分析(予算ベース)を更新</button>

SpreadsheetApp.getUi().createMenu() ベースの GAS 標準メニューは本プロジェクトでは使用していないため、addItem 追加ではなくサイドバー HTML への button 要素追加とする。

影響範囲

ファイル/シート変更種別影響
200_data/202_repository.js追加(末尾、新規 BudgetRepository 名前空間)既存 Repository には変更なし。新規モジュールの追加のみ
100_config/101_sys_config.js1 行追加(PL_BUD_VARappendRow のみ。schemas 辞書は変更しない)setupAllSchemas 実行時に 01_sys_config に 1 行追加。既存タブの DDL 適用には影響なし
templates/operations_sidebar.html1 行追加(button 要素)既存ボタンへの影響なし
600_report/610_datamart_budget_variance.js新規作成他レポートビルダーへの影響なし
66_pl_budget_variance シート初回実行時に自動作成setupAllSchemas で物理シートは作成されない。buildBudgetVarianceReport 内の ss.insertSheet で初回作成

注意事項

  1. 有効フラグの判定パターン統一flag === false || String(flag).toUpperCase() === 'FALSE' で除外する(AccountRepository.findAsMap() L329-330、Utils.normalizePartnerName() L357 と同じパターン)。BudgetDTO は 有効フラグ を先頭フィールド(003_contracts.js L134)として持つので、DTO 経由で安全に取得可能。
  2. JournalEntryDTO に 有効フラグ は存在しない003_contracts.jsJournalEntryDTO 定義(L97-129)および TRN_JOUR の DDL ヘッダー(101_sys_config.js L656)の両方に 有効フラグ が含まれないことを Phase 1 で確認済。実績データのフィルタは行わない。将来 有効フラグ 列が追加された場合は本仕様の更新が必要。
  3. 予算バージョンの複数共存リスクConstants.SHEET_DEFAULTS41_trn_budget エントリ(002_constants.js L74)でデフォルト値 '最新予算(V1)' が定義されているが、ユーザーが手動で別バージョン(例: '当初予算''修正予算')を入力する可能性がある。本仕様ではフィルタ対象を '最新予算(V1)' の完全一致のみとし、複数バージョン対応は将来案件(MAS-002 予算スナップショット/バージョン管理)に委ねる。
  4. 収支区分の符号扱いの統一BudgetDTO.収支区分'収入' / '支出')と JournalEntryDTO.収支区分(同上)はいずれも文字列ラベルであり、金額は両者とも 正値で格納される(既存の P/L マートも金額の絶対値で比較)。差異額計算(実績 − 予算)は単純な数値減算でよい。「収益科目で実績 > 予算 = 良好」「費用科目で実績 < 予算 = 良好」の業務的な解釈は、エッジケースセクションの差異評価方向性の表で示す。
  5. Utils.parseDateToYm() の入力形式BudgetDTO.対象年月'YYYY-MM' 文字列、JournalEntryDTO.発生日(P/L計上日)Date 型または文字列。Utils.parseDateToYm()004_utils.js L92-99)は両方に対応済(Date / 'YYYY-MM' / 'YYYY/MM' / 'YYYY年MM月')。空値が来た場合は '' を返す → 空キーは集計対象外として扱う必要あり。
  6. 科目マスタ未登録時の扱い — 予算 / 実績の 科目名11_mst_account に未登録の場合、本仕様では「未分類」グループに集約せず、集計対象から除外し warning ログを出力Utils.logInfo('buildBudgetVarianceReport', '科目マスタ未登録: ' + acc))。既存 dmProcessAllEvents_602_datamart_main.js L49)は throw する設計だが、レポート系では実行継続を優先する。
  7. DDL 管理外シートの追記66_pl_budget_variance は DDL の schemas 辞書には登録しないため、CLAUDE.md の「DDL (setupAllSchemas) で管理されないタブ」一覧への追記が必要。本案件のスコープに含める。
  8. SpreadsheetApp.getUi() がない実行コンテキスト — トリガーや Web アプリ経由で実行された場合、getUi() は例外を投げる。確認ダイアログ表示は try { ui = SpreadsheetApp.getUi(); ... } catch(e) { /* スキップして続行 */ } で囲む(setupAllSchemas L580 と同パターン)。

エッジケース

条件表示値理由
予算金額=0、実績金額>0"-"(文字列)差異率が無限大(ゼロ除算)のため計算不能
予算金額=0、実績金額=00(数値、フォーマット適用後 0.0%計画通り=差異なし、計算可能
予算金額=0、実績金額<0"-"(文字列)予算外の費用発生 / マイナス計上、ゼロ除算で計算不可
予算金額>0、実績金額=0-1(数値、フォーマット適用後 △ 100.0%予算に対して実績ゼロ、通常計算で差異率 -100%
予算金額<0、実績金額>0通常計算(差異額 = 実績 − 予算 / 差異率 = 差異額 / 予算金額)戻入予算など特殊パターン。符号反転に注意
予算データが 0 件(バージョンフィルタ後)処理中断・トースト表示未登録データでの実行防止(誤操作ガード)
予算 + 実績ともに 0 件の科目該当科目を出力スキップ全科目羅列を避け、出力レポートをコンパクトに保つ
科目が予算にのみ存在実績=0 として差異計算(差異率=-100.0%和集合アプローチ:予算未消化の可視化
科目が実績にのみ存在予算=0 として差異計算(差異率="-"和集合アプローチ:予算外支出の可視化
科目マスタに未登録の科目名集計対象から除外 + ログ警告レポート系は実行継続を優先(注意事項 6 参照)
対象年月 が空または不正な値該当 DTO を集計対象外Utils.parseDateToYm()"" を返した場合のガード
集計対象月が会計年度範囲を超える範囲外月は出力しない(最小〜最大の枠内のみ表示)動的算出方式で予算データに合わせる(人間検討事項参照)

差異評価の方向性(業務解釈ガイド)

科目区分良好な状態警戒すべき状態レポート上の推奨表示
収益(売上高、その他収益等)差異額 > 0(実績 > 予算)差異額 < 0(実績 < 予算 = 未達)差異額 ≥ 0 を 緑系、< 0 を 赤系で色分け(将来案件)
費用・原価(売上原価、販管費等)差異額 ≤ 0(実績 ≤ 予算 = 節約)差異額 > 0(実績 > 予算 = 超過)差異額 ≤ 0 を 緑系、> 0 を 赤系で色分け(将来案件)
B/S本案件のスコープ外

本仕様では数値・差異額・差異率の出力までを範囲とし、色分け(条件付き書式)は将来案件として「人間が検討すべき事項」に記載する。

実データ検証

実装前および実装後に以下を MCP(または手動シート確認)で検証する:

  • 41_trn_budget の状態確認

    • 有効フラグ=TRUE のレコード件数(バージョン別カウント)。フィルタ後 0 件にならないことを事前確認
    • 予算バージョン の実際の格納値一覧。'最新予算(V1)' との完全一致確認(前後スペースなし、全角半角混在なし)
    • 対象年月 の最小・最大値、および年度跨ぎがある場合の月分布
    • 予算金額 の絶対値合計(予算規模の把握)
  • 11_mst_account41_trn_budget の科目名突合

    • 41_trn_budget.科目名11_mst_account.科目名 と完全一致するか(全角半角・前後スペースの違いも確認)
    • 不一致がある場合、注意事項 6 に従い該当行は集計対象外として除外(ログ警告で可視化)
  • 42_trn_journal の確認

    • JournalEntryDTO 定義に 有効フラグ 列が含まれていないことを再確認(003_contracts.js L97-129、および TRN_JOUR ヘッダー定義 101_sys_config.js L656)
    • 集計対象期間内の 税抜金額_実績 の科目別合計(実績規模の把握)
    • 仕訳ステータス=仕訳振替 のレコードを除外する必要があるかの判断(CLAUDE.md「仕訳振替の判定は === "仕訳振替" の完全一致」ルール参照)。本仕様では当面除外せず、将来要件に応じて対応
  • 出力後の検算

    • 全科目の差異額の合算が、 全科目の実績合計 − 全科目の予算合計 と一致すること(集計ロジックのサンクチェック)
    • 単一科目の月別差異額の合計が、その科目の年度合計差異額(P 列)と一致すること

関連ドキュメント

ドキュメント関連箇所
docs/dev/dev_mas-001_variance_analysis.md既存の予実差異分析(着地見込みベース)。レポートビルダーの基本構成・実装パターン参考
docs/dev/dev_mas-003_kpi_dashboard.mdKPIダッシュボードのレポートビルダー実装パターン(609_datamart_kpi.jssheet.clear() + try/catch パターン参考)
000_infra/003_contracts.jsBudgetDTO / JournalEntryDTO の型定義
200_data/202_repository.jsBudgetRepository 追加先・既存 Repository パターン(OrderRepository がテンプレート)
100_config/101_sys_config.jssetupAllSchemas01_sys_config システムキー登録セクション(PL_VAR 隣接)
templates/operations_sidebar.html「📊 マート更新」セクションへのボタン追加
CLAUDE.mdDDL で管理されないタブ一覧への追記、コーディング規約(有効フラグ判定パターン)

人間が検討すべき事項

docs/_internal/TODO_future.md の MAS-025 行に記載された「人間が検討すべき事項」:

  • 41_trn_budget のデータ整備状況 — 本案件の前提として、ユーザー(経営層・経理担当)が 41_trn_budget シートに予算を入力済みであることが必要。実装後に「データ未登録のため使えない」とならないよう、運用開始前に予算入力フェーズの完了を確認すること。

追加で検討すべき事項(実装過程で判明したもの):

  1. 集計対象年度の指定方法 — 本仕様は「予算データの 対象年月 最小〜最大」で動的に決定する設計。代替案として a) 03_sys_paramsBUDGET_VARIANCE_FISCAL_YEAR を追加して固定 / b) 実行時ダイアログで「対象年度」をプルダウン選択 / c) 暦年度・会計年度の選択を可能にする、なども検討余地あり。
  2. 予算バージョン フィルタ仕様の拡張 — 複数バージョン共存(当初予算 / 修正予算 / 最終予算 など)が必要になった場合、 a) 全バージョン横並び比較レポート / b) 実行時にバージョン選択ダイアログ / c) 03_sys_params で参照バージョンを明示指定、のいずれを採用するか。MAS-002(予算スナップショット/バージョン管理)と統合検討すべき。
  3. 66_pl_budget_variance シートの DDL 管理化 — 現在は DDL 管理外(schemas 辞書未登録)。書式パターンが安定したら DDL schemas 辞書に登録して setupAllSchemas で初期化される運用に移行することを推奨。本仕様完了後、運用 1〜2 ヶ月の安定確認をもって判断。
  4. 差異セルの条件付き書式(色分け) — 「差異評価の方向性」テーブルに記載した収益/費用ごとの良/悪の色分けを将来案件として実装する余地あり(MAS-001 と同様に 03_sys_paramsCFG_VARIANCE_* 閾値を流用可能)。
  5. 科目マスタ未登録の警告通知 — 本仕様ではログ出力のみだが、件数が多い場合は実行後トースト通知での集約サマリ表示を追加検討。
  6. PJ別 / 組織別の予実差異BudgetDTO には PJ名 / 組織名 フィールドがあるが、本仕様では集計次元に含めない(科目×月のみ)。需要があれば 66b_pl_budget_variance_by_pj 等の派生レポートを後続案件として検討。

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

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-025「予算vs実績差異分析(41_trn_budget ベース)」を実装してください。

## 実行前タスク
- `200_data/202_repository.js` を Read し、`OrderRepository` の `_getSheet` / `findAll` の実装(L107-146)を確認する(BudgetRepository 作成のテンプレートとして使用)。`readSheetAsDtos_`(L19)の引数・戻り値も確認
- `100_config/101_sys_config.js` を Read し、`41_trn_budget` のシステムキー `TRN_BUDG`(L604)と、`PL_VAR` 登録行(L613)の正確な位置を確認する
- `templates/operations_sidebar.html` を Read し、「📊 マート更新」セクション(L62-68)の `buildKpiDashboard` ボタンの位置を確認する
- `000_infra/003_contracts.js` を Read し、`BudgetDTO`(L131-149)の全フィールドと、`JournalEntryDTO`(L97-129)に `有効フラグ` 列が含まれないことを確認する
- `600_report/609_datamart_kpi.js` を Read し、`sheet.clear()` パターン(L28)と `try/catch` での `Utils.logError` 呼び出しパターン(L49-52)を確認する
- `000_infra/004_utils.js` を Read し、`Utils.parseDateToYm()`(L92-99)の入力形式と戻り値仕様、`getWebSpreadsheet_()`(L9-15)の挙動を確認する

## 修正対象ファイル
1. `200_data/202_repository.js` — `BudgetRepository` を末尾(L351 の直後)に追加
2. `600_report/610_datamart_budget_variance.js` — 新規作成(番号は既存最大 609 +1 で `610` 確定)
3. `100_config/101_sys_config.js` — `setupAllSchemas` の `PL_VAR` 直後(L613 の次)に `PL_BUD_VAR` の `appendRow` 1 行を追加
4. `templates/operations_sidebar.html` — 「📊 マート更新」セクションの `buildKpiDashboard` ボタン直下に新規ボタン 1 行を追加
5. `CLAUDE.md` — 「DDL (setupAllSchemas) で管理されないタブ」一覧に `66_pl_budget_variance` を追記

## 実装内容

### 1. BudgetRepository 追加(`202_repository.js` 末尾)
`OrderRepository`(L107-146)と同一構造で実装すること:
- `_getSheet()`: `Utils.getSheetByKey('TRN_BUDG', '41_trn_budget')`(システムキー登録済を活用)
- `findAll()` のみ実装(戻り値: `{ headers: string[], dtos: BudgetDTO[] }`)
- `save()` / `append()` は実装しない(読み取り専用 Repository)
- 有効フラグフィルタは Repository 層では行わない(呼び出し側で実施)

### 2. レポートビルダー新規作成(`600_report/610_datamart_budget_variance.js`)
関数名: `buildBudgetVarianceReport()`

処理フロー(既存 `609_datamart_kpi.js` のパターンに倣うこと):
1. `var FUNC = 'buildBudgetVarianceReport'; try { ... } catch (e) { Utils.logError(FUNC, e); throw e; }` でラップ。`LockService` は使用しない(既存レポートビルダーとの整合)
2. 予算データ取得: `BudgetRepository.findAll().dtos` → 有効フラグフィルタ(`flag === false || String(flag).toUpperCase() === 'FALSE'` で除外)→ `予算バージョン === '最新予算(V1)'` でフィルタ
3. 件数 0 ガード: `getWebSpreadsheet_().toast('予算データが未登録です(バージョン=最新予算(V1)、有効フラグ=TRUE 該当なし)', 'F-25', 5)` → return
4. 対象期間決定: フィルタ後 DTO の `Utils.parseDateToYm(dto['対象年月'])` から最小・最大を計算(空文字列は除外)
5. 実行前確認ダイアログ: `try { var ui = SpreadsheetApp.getUi(); var resp = ui.alert('F-25 予算vs実績差異分析', msg, ui.ButtonSet.OK_CANCEL); if (resp !== ui.Button.OK) return; } catch(e) {}`(Webアプリ経由は try/catch でスキップ)
6. 実績データ取得: `JournalRepository.findAll().dtos` をそのまま使用(`有効フラグ` 列なしのためフィルタ不要)
7. 科目マスタ取得: `AccountRepository.findAsMap()`(既存キャッシュ流用、`resetCache()` 呼ばない)
8. 集計: 科目名 × `Utils.parseDateToYm(...)` の 2 段階 Map で `budget` / `actual` を加算。予算側は `予算金額`、実績側は `税抜金額_実績`。`acctMap[acc]` が undefined の科目はスキップ + `Utils.logInfo(FUNC, '科目マスタ未登録: ' + acc)` でログ警告
9. 出力シート取得: `ss.getSheetByName('66_pl_budget_variance')` → null なら `ss.insertSheet('66_pl_budget_variance')` → `sheet.clear()`
10. ヘッダー行書き込み: A=大分類、B=科目名、C=指標、D〜O=4月〜3月(YYYY-MM 文字列、対象期間に基づき動的算出)、P=年度合計
11. データ行書き込み: 科目ごとに「予算」「実績」「差異額」「差異率」の 4 行(科目名は B 列に予算行のみ表示、他は空欄でグルーピング表現)
12. 数値フォーマット: 予算/実績/差異額は `Constants.NUMBER_FORMATS.CURRENCY`、差異率は `Constants.NUMBER_FORMATS.PERCENT`、文字列 `"-"` のセルはフォーマット適用後も `"-"` 表示
13. 列幅調整: A=120, B=240, C=80, D-O=90, P=100
14. 完了通知: `Utils.toastResult(FUNC, sheetName + ' 再描画完了 (科目=' + accCount + ', 期間=' + minYm + '〜' + maxYm + ')')`

差異計算式:
- 差異額 = 実績金額 − 予算金額
- 差異率: 予算金額 ≠ 0 → 差異額 / 予算金額(数値)/ 予算=0 かつ 実績=0 → 0 / それ以外 → 文字列 `'-'`

### 3. DDL 登録(`101_sys_config.js`)
`setupAllSchemas` 内、`PL_VAR` の `appendRow`(L613)の直後の行に追加:
```js
if (!existKeys.includes('PL_BUD_VAR')) confSheet.appendRow(['PL_BUD_VAR', '', '66_pl_budget_variance', 'P/L予実差異分析(予算ベース)']);
```
`schemas` 辞書には登録しない(DDL 管理外シートとして扱う)。

### 4. サイドバー UI 追加(`templates/operations_sidebar.html`)
「📊 マート更新」セクション(L62-68)の `buildKpiDashboard` ボタン直下に追加:
```html
<button class="btn" onclick="run('buildBudgetVarianceReport', this)">📊 予実差異分析(予算ベース)を更新</button>
```

### 5. CLAUDE.md 追記
「DDL (setupAllSchemas) で管理されないタブ」一覧に `66_pl_budget_variance` を追記する(既存リスト末尾、`78_pj_pl` の後ろ等の適切な位置)。

## 制約
- `202_repository.js` の既存 Repository(OrderRepository / InvoiceRepository / BankTxRepository / JournalRepository / AccountRepository)は変更しない
- 有効フラグ判定は必ず `flag === false || String(flag).toUpperCase() === 'FALSE'` パターンを使用すること(`AccountRepository.findAsMap()` L329-330 と同パターン)
- 列参照はヘッダー名ベース(列番号ハードコード禁止)
- `AccountRepository.resetCache()` は呼ばない(既存動作への影響回避)
- `LockService` は使用しない(既存 600_report/ 配下レポートビルダーとの整合)
- `SpreadsheetApp.getUi()` の例外(Webアプリ経由)は `try/catch` で握りつぶしてダイアログをスキップする
- 出力シートが既存の場合は `sheet.clear()` で書式・条件付き書式まで全消去してから再描画(冪等性)

## エッジケース
| 条件 | 表示値 | 理由 |
|------|--------|------|
| 予算金額=0、実績金額>0 | `"-"` | 差異率が無限大のため表示不可 |
| 予算金額=0、実績金額=0 | `0`(フォーマット適用後 `0.0%`) | 計画通り |
| 予算金額=0、実績金額<0 | `"-"` | 予算外費用、計算不可 |
| 予算データが 0 件(バージョンフィルタ後) | 処理中断・トースト | 未登録ガード |
| 科目が一方のみに存在 | 不在側=0 として計算 | 和集合アプローチ |
| 科目マスタ未登録 | 集計対象外 + ログ警告 | レポート系は実行継続優先 |
| `対象年月` パース失敗 | 該当 DTO を集計対象外 | `Utils.parseDateToYm()` `""` ガード |

## 動作確認
1. `npm run push:dev` で開発用 GAS にデプロイ
2. GAS エディタで `setupAllSchemas` を実行し、`01_sys_config` に `PL_BUD_VAR` 行が追加されたことを確認
3. 操作パネルを開き、「📊 マート更新」セクションに「📊 予実差異分析(予算ベース)を更新」ボタンが表示されることを確認
4. ボタンクリック → 確認ダイアログが「件数」「期間」付きで表示されること
5. OK 押下 → `66_pl_budget_variance` シートが生成され、科目ごとに予算/実績/差異額/差異率 の 4 行が出力されること
6. `予算バージョン` を一時的に変更して 0 件状態を作り、トースト通知で中断されることを確認
7. 2 回連続実行してシートが重複せず上書きされること(冪等性確認)
8. Webアプリ経由(`getUi()` が取れない経路)で実行しても例外で止まらず、ダイアログをスキップして処理が完走することを確認(任意)

### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| BudgetRepository 追加 | なし | OrderRepository パターン踏襲のみ |
| レポートビルダー実装 | あり | ピボット集計ロジック・差異率エッジケース処理の設計 |
| DDL 登録 + サイドバー追加 | なし | 挿入位置特定後は定型作業 |
| CLAUDE.md 追記 | なし | リスト末尾追加のみ |

推奨実行モデル

工程推奨モデル理由
仕様書作成(本ドキュメント)Claude Opus 4.7レポート設計・既存パターン突合・人間検討事項抽出に高い推論力が必要
Step 1 BudgetRepository 追加Claude Haiku 4.5既存 OrderRepository パターンの横展開、判断不要
Step 2 レポートビルダー新規作成Claude Sonnet 4.6ピボット集計・ダイアログ制御・差異率エッジケース処理の設計判断が必要
Step 3-1 DDL 登録Claude Haiku 4.51 行追加、挿入位置特定後は定型
Step 3-2 サイドバー UI 追加Claude Haiku 4.5button 1 行追加、定型作業
Step 4 CLAUDE.md 追記Claude Haiku 4.5リスト末尾追加のみ

変更履歴

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

仕様書作成プロンプト

展開して表示

【タイムアウト回避・実行原則(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> プロンプト全文記録) に分割。1 回の Write/Edit は約 300 行以内。
  4. 各 Step で何を書くかを具体指示: Phase 1 で確定済みの内容の書き下しに徹し、Phase 2 実行時に設計判断を持ち込まない。

====================================================================== あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。 案件 MAS-025「予算vs実績差異分析(41_trn_budget ベース)」の開発仕様書を作成してください。 開発仕様書を新規作成した場合は、docs/_config.jsonnav 配列の適切なセクションにも必ず追記してください。


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

「どう書くか」の判断は必ず Read で裏取りすること(Grep は発見まで)。 以下をすべて調査完了させてから Phase 2 に進む。

1-1: 案件定義の取得

docs/_internal/TODO_future.md で MAS-025 の行を検索し、案件名・概要・期待される効果・人間が検討すべき事項を取得する。

1-2: 類似仕様書の読み込み

docs/dev/dev_mas-001_variance_analysis.md を読み込み、レポートビルダー仕様書のセクション構成・実装プロンプト形式・推奨実行モデルテーブルの書き方を把握する。

1-3: DTO の全フィールド確認

000_infra/003_contracts.js を Read し、以下の型定義を確認する:

  • BudgetDTO41_trn_budget): 有効フラグ / 予算バージョン / 対象年月 / 収支区分 / 科目名 / 予算金額 の型と値パターン。有効フラグ が先頭フィールドであることを確認。
  • JournalEntryDTO42_trn_journal): 有効フラグ が定義に含まれるかどうかを確認する(含まれない場合、実績データのフィルタ方針を変更する必要があるため必ず確認)。発生日(P/L計上日) / 収支区分 / 科目名 / 税抜金額_実績 の型も確認。

1-4: 定数の確認

000_infra/002_constants.js を Read し、SHEET_DEFAULTSpattern: '41_trn_budget' エントリで 予算バージョン のデフォルト値('最新予算(V1)')を確認する。

1-5: Repository 層の確認

200_data/202_repository.js を Read し、以下を確認する:

  • OrderRepository の全構造(_getSheet / findAll の実装)を BudgetRepository 新規作成のテンプレートとして把握する。
  • Utils.getSheetByKey に渡す第 1 引数(システムキー)の形式(例: 'WRK_ORDR')を確認し、41_trn_budget に対応するキーの命名規則を推定する。
  • JournalRepository.findAll()AccountRepository.findAsMap() の戻り値型を確認する。
  • BudgetRepository が存在しないことを確認する。
  • readSheetAsDtos_ / appendDtosToSheet_ 等の内部ヘルパー関数の所在を確認する。

1-6: システム設定・メニュー構造の確認

100_config/101_sys_config.js を Read し、以下を確認する:

  • 01_sys_config シートに 41_trn_budget のシステムキーが登録されているか(または Utils.getSheetByKey のフォールバック名のみで対応するかを判断する)。
  • 既存の「📊 レポート」メニュー項目の一覧と、新メニュー項目を追加する正確な行番号を特定する。
  • 66_pl_budget_variance シートが DDL(setupAllSchemas)で管理されているかどうかを確認する。

1-7: 既存レポートビルダーのパターン確認

600_report/ 配下のファイル一覧を確認し、既存の最大ファイル番号を特定する(新規ファイルの番号決定のため)。その上で、最も実装が近いと思われるレポートビルダーを 1 件 Read し、以下のパターンを把握する:

  • LockService.getScriptLock() + try...finally による多重実行防止の実装箇所(行番号まで)。
  • 出力シートのクリア手段(clearContents() / clear() / clearContent() のどれを使っているか)。
  • データ取得 → 集計 → シート書き込みの全体フロー。

1-8: ユーティリティ確認

000_infra/004_utils.js を Read し、Utils.parseDateToYm() の引数型(Date / 文字列 "YYYY-MM" 等)と戻り値形式("YYYY-MM")を確認する。BudgetDTO.対象年月 が文字列 "YYYY-MM" 形式で格納されている場合の挙動も確認する。


Phase 2: 仕様書の分割作成

出力先: docs/dev/dev_mas-025_budget_variance.md 【重要】1 回のツール呼び出しで全内容を出力しない。以下の Step に必ず分割して実行すること。

Step 2-1: 骨格の作成 (Write、目安 ~20行)

以下の見出しのみを持つ骨格ファイルを Write で作成する(本文は空で可):

# F-25: 予算vs実績差異分析(41_trn_budget ベース)
## 概要
## 目的
## 現在のコード
## 修正方針
## 影響範囲
## 注意事項
## エッジケース
## 実データ検証
## 関連ドキュメント
## 人間が検討すべき事項
## 実装プロンプト(Claude Code 用)
## 推奨実行モデル
## 変更履歴
## 仕様書作成プロンプト

Step 2-2: 前半セクションの追記 (Edit または Bash heredoc、目安 ~300行)

以下を「概要」〜「注意事項」に書き込む:

  • 概要テーブル: 案件ID=MAS-025、カテゴリ=FP&A・レポーティング、Phase/優先度/所要時間は 1-1 で取得した値を転記、対象ファイル=200_data/202_repository.js(BudgetRepository 追加)・100_config/101_sys_config.js(メニュー追加)・600_report/60X_datamart_budget_variance.js(新規作成・番号は 1-7 で確認した最大番号+1)

  • 目的: 41_trn_budget の予算データと 42_trn_journal の実績データを科目・月次で突合し、差異額・差異率を 66_pl_budget_variance シートに出力するレポートビルダーを新規実装する。BudgetRepository が未実装であるため、202_repository.js への追加も本案件のスコープに含める。

  • 現在のコード:

    • 200_data/202_repository.js: BudgetRepository が未実装(1-5 で確認した末尾行番号を明記)
    • 100_config/101_sys_config.js: 「📊 レポート」メニューに予実差異分析のメニュー項目が存在しない(1-6 で確認した行番号を明記)
    • 66_pl_budget_variance シートが未作成
  • 修正方針(3 Step 構成):

    Step 1 — BudgetRepository 追加200_data/202_repository.js 末尾に追加)

    • OrderRepository と同一パターンで実装する
    • _getSheet(): Utils.getSheetByKey('{1-6で確認したシステムキー}', '41_trn_budget')(システムキー未登録の場合は第 1 引数に null 相当の代替を使い、フォールバック名のみで対応)
    • findAll() のみ実装(書き込みは不要)。戻り値: { headers: string[], dtos: BudgetDTO[] }
    • 有効フラグ のフィルタは呼び出し側で行う(Repository 層では素通し)

    Step 2 — レポートビルダー新規作成600_report/60X_datamart_budget_variance.js

    • 関数名: buildBudgetVarianceReport()
    • LockService.getScriptLock() + try...finally でラップ(1-7 で確認したパターンに倣う)
    • 処理フロー:
      1. 予算データ取得: BudgetRepository.findAll().dtos有効フラグ フィルタ(flag === false || String(flag).toUpperCase() === 'FALSE' で除外)→ 予算バージョン === '最新予算(V1)' でフィルタ
      2. 予算データ 0 件ガード: SpreadsheetApp.getActiveSpreadsheet().toast('予算データが未登録です', 'F-25', 5) を表示して return
      3. 実行前確認ダイアログ: 集計対象件数・対象年月 の最小〜最大値を SpreadsheetApp.getUi().alert() で表示し、ユーザーが OK を選択した場合のみ処理継続(ButtonSet.OK_CANCEL を使用)
      4. 実績データ取得: JournalRepository.findAll().dtos → 1-3 で確認した 有効フラグ 列の有無に応じてフィルタ
      5. 科目マスタ取得: AccountRepository.findAsMap()
      6. 集計: Utils.parseDateToYm(dto['対象年月']) / Utils.parseDateToYm(dto['発生日(P/L計上日)']) でキー生成し、{ 科目名: { YYYY-MM: { budget: 0, actual: 0 } } } 形式でピボット集計
      7. 出力シート取得・クリア: ss.getSheetByName('66_pl_budget_variance')null なら ss.insertSheet('66_pl_budget_variance')sheet.clearContents()(フォーマットは保持)
      8. ヘッダー行書き込み: A=大分類、B=科目名、C=指標、D〜O=4月〜3月(会計年度 12 ヶ月)、P=年度合計
      9. 科目ごとに「予算」「実績」「差異額」「差異率」の 4 行を書き込み
    • 差異計算:
      • 差異額 = 実績金額 − 予算金額
      • 差異率 = 差異額 / 予算金額(ゼロ除算はエッジケーステーブル参照)
    • 対象年度の決定: 1-6 で確認した既存パターンに合わせること。既存パターンが不明な場合は「要確認事項」として仕様書に明記し、暫定として実行時ダイアログ入力とする

    Step 3 — メニュー追加100_config/101_sys_config.js

    • 1-6 で確認した「📊 レポート」メニューの適切な位置(行番号を明記)に .addItem('予実差異分析(予算ベース)を更新', 'buildBudgetVarianceReport') を追加
  • 影響範囲:

    • 200_data/202_repository.js: BudgetRepository 追加(既存 Repository への変更なし)
    • 100_config/101_sys_config.js: メニュー項目 1 件追加
    • 600_report/60X_datamart_budget_variance.js: 新規作成
    • 66_pl_budget_variance シート: 初回実行時に自動作成
  • 注意事項:

    1. 有効フラグ の判定は flag === false || String(flag).toUpperCase() === 'FALSE' パターンを徹底すること(202_repository.jsAccountRepository.findAsMap() に同パターンあり)
    2. 予算バージョン が複数存在する場合の重複計上に注意。Constants.SHEET_DEFAULTS'最新予算(V1)' でフィルタするが、将来的に複数バージョン対応が必要になる可能性を「人間が検討すべき事項」に記載
    3. BudgetDTO.収支区分('収入' | '支出')と JournalEntryDTO.収支区分 を突合する際、符号の扱いを統一すること(両者とも正値で格納されているか確認)
    4. Utils.parseDateToYm() に文字列 "YYYY-MM" を渡した場合の正常動作は 1-8 で確認済みであること
    5. 66_pl_budget_variance シートが DDL 管理外の場合、CLAUDE.md の「DDLで管理されないタブ」一覧に追記する必要がある

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

  • ## エッジケース セクション:
条件表示値理由
予算金額=0、実績金額>0"-"差異率が無限大のため表示不可
予算金額=0、実績金額=0"0.0%"計画通り(計算可能)
予算金額=0、実績金額<0"-"予算外の費用発生(計算不可)
予算金額>0、実績金額=0-100.0%予算に対して実績ゼロ(通常計算)
予算データが 0 件(バージョンフィルタ後)処理中断・トースト表示未登録データでの実行防止
科目が予算にのみ存在実績=0 として差異計算和集合アプローチ
科目が実績にのみ存在予算=0 として差異計算(差異率は "-"和集合アプローチ、予算外支出

差異評価の方向性: 収益科目(売上高等)は 差異額 > 0 が良好だが、費用・原価科目は 差異額 < 0(実績 < 予算)が良好となる。レポート上での色分けや記号(△)による区別を推奨事項として記載すること。

  • ## 実データ検証 セクション:

    • 41_trn_budget シートで MCP またはシート直接確認: 有効フラグ=TRUE のレコード件数、予算バージョン の実際の格納値('最新予算(V1)' との一致確認)、対象年月 の最小・最大値
    • 11_mst_account科目名41_trn_budget科目名 が完全一致するか(全角半角・スペースの違いも確認)
    • 42_trn_journal有効フラグ 列が実際に存在するかどうか(1-3 の JournalEntryDTO 定義に含まれていない場合は特に要確認)
  • ## 関連ドキュメント セクション:

ドキュメント関連箇所
docs/dev/dev_mas-001_variance_analysis.mdレポートビルダーの基本構成・実装パターン参考
000_infra/003_contracts.jsBudgetDTO / JournalEntryDTO の型定義
200_data/202_repository.jsBudgetRepository 追加先・既存 Repository パターン
  • ## 人間が検討すべき事項 セクション:
    • 1-1 で取得した TODO_future.md の「人間が検討すべき事項」を転記
    • 追加事項: 集計対象年度の指定方法(03_sys_params パラメータ参照 / ダイアログ入力 / 固定値のいずれか)
    • 追加事項: 予算バージョン フィルタ仕様(複数バージョン共存時、どのバージョンを使うかのルール)
    • 追加事項: 66_pl_budget_variance シートを setupAllSchemas(DDL)管理に含めるかどうか。含めない場合は CLAUDE.md の「DDLで管理されないタブ」一覧に追記が必要

Step 2-3b: 実装プロンプト〜変更履歴の追記 (Edit または Bash、目安 ~250行)

実装プロンプトは コードブロック(バッククォート3つ以上)で囲まず、行頭4スペースインデント で以下の内容を出力すること:

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-025「予算vs実績差異分析(41_trn_budget ベース)」を実装してください。

## 実行前タスク
- `200_data/202_repository.js` を Read し、`OrderRepository` の `_getSheet` / `findAll` の実装を確認する(BudgetRepository 作成のテンプレートとして使用)
- `100_config/101_sys_config.js` を Read し、`41_trn_budget` のシステムキーと「📊 レポート」メニューへの追加位置(行番号)を確認する
- `000_infra/003_contracts.js` を Read し、`BudgetDTO` の全フィールドと `JournalEntryDTO` の `有効フラグ` 列の有無を確認する
- `600_report/` 配下の既存レポートビルダーを 1 件 Read し、`LockService.getScriptLock()` の使用パターンと出力シートのクリア手段を確認する

## 修正対象ファイル
1. `200_data/202_repository.js` — `BudgetRepository` を末尾に追加
2. `600_report/60X_datamart_budget_variance.js` — 新規作成(番号は `600_report/` 配下の既存最大番号+1 で確定)
3. `100_config/101_sys_config.js` — 「📊 レポート」メニューへのメニュー項目追加

## 実装内容

### 1. BudgetRepository 追加(`202_repository.js` 末尾)
`OrderRepository` と同一構造で実装すること:
- `_getSheet()`: `Utils.getSheetByKey('{101_sys_config.jsで確認したシステムキー}', '41_trn_budget')`(キー未登録の場合はフォールバック名のみで対応)
- `findAll()` のみ実装。戻り値: `{ headers: string[], dtos: BudgetDTO[] }`
- `有効フラグ` フィルタは Repository 層では行わない(呼び出し側で実施)

### 2. レポートビルダー新規作成(`600_report/60X_datamart_budget_variance.js`)
関数名: `buildBudgetVarianceReport()`

処理フロー(既存レポートビルダーのパターンに倣うこと):
1. `LockService.getScriptLock().tryLock(3000)` でロック取得。失敗時は toast 表示して return。`try...finally` で `lock.releaseLock()` を確実に実行
2. 予算データ取得: `BudgetRepository.findAll().dtos` → 有効フラグフィルタ(`flag === false || String(flag).toUpperCase() === 'FALSE'` で除外)→ `予算バージョン === '最新予算(V1)'` でフィルタ
3. 件数 0 ガード: `SpreadsheetApp.getActiveSpreadsheet().toast('予算データが未登録です', 'F-25', 5)` → return
4. 実行前確認ダイアログ: 件数と `対象年月` の範囲を整形して `SpreadsheetApp.getUi().alert('確認', msg, SpreadsheetApp.getUi().ButtonSet.OK_CANCEL)` で表示。CANCEL なら return
5. 実績データ取得: `JournalRepository.findAll().dtos` → 有効フラグ列が存在する場合のみフィルタ
6. 科目マスタ取得: `AccountRepository.findAsMap()`
7. 集計: `Utils.parseDateToYm()` でキー生成し、`{ 科目名: { 'YYYY-MM': { budget: 0, actual: 0 } } }` 形式でピボット集計
8. 出力シート取得: `ss.getSheetByName('66_pl_budget_variance')` → null なら `ss.insertSheet('66_pl_budget_variance')` → `sheet.clearContents()`
9. ヘッダー行書き込み: A=大分類、B=科目名、C=指標、D〜O=4月〜3月(会計年度 12 ヶ月)、P=年度合計
10. 科目ごとに「予算」「実績」「差異額」「差異率」の 4 行を書き込み

差異計算式:
- 差異額 = 実績金額 − 予算金額
- 差異率: 予算金額 ≠ 0 → 差異額 / 予算金額 / 予算=0 かつ 実績=0 → 0 / それ以外 → null(セルに "-" を表示)

### 3. メニュー追加(`101_sys_config.js`)
「📊 レポート」メニューの確認済み行番号に `.addItem('予実差異分析(予算ベース)を更新', 'buildBudgetVarianceReport')` を追加

## 制約
- `202_repository.js` の既存 Repository(OrderRepository / InvoiceRepository 等)は変更しない
- 有効フラグ判定は必ず `flag === false || String(flag).toUpperCase() === 'FALSE'` パターンを使用すること
- 列参照はヘッダー名ベース(列番号ハードコード禁止)
- `AccountRepository` のキャッシュをリセットしない(既存動作に影響しないよう、`AccountRepository.resetCache()` を呼ばない)

## エッジケース
| 条件 | 表示値 | 理由 |
|------|--------|------|
| 予算金額=0、実績金額>0 | "-" | 差異率が無限大のため表示不可 |
| 予算金額=0、実績金額=0 | "0.0%" | 計画通り |
| 予算金額=0、実績金額<0 | "-" | 予算外費用、計算不可 |
| 予算データが 0 件 | 処理中断・トースト | 未登録ガード |
| 科目が一方のみに存在 | 不在側=0 として計算 | 和集合アプローチ |

## 動作確認
1. `npm run push:dev` で開発用 GAS にデプロイ
2. 「📊 レポート」>「予実差異分析(予算ベース)を更新」を実行
3. 確認ダイアログが件数・期間付きで表示されること
4. `66_pl_budget_variance` シートが生成され、科目ごとに予算/実績/差異額/差異率 の 4 行が出力されること
5. `予算バージョン` フィルタで 0 件になる場合にトースト通知で中断されること
6. 2 回実行してシートが重複せず上書きされること(べき等性確認)

### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| BudgetRepository 追加 | なし | OrderRepository パターン踏襲のみ |
| レポートビルダー実装 | あり | ピボット集計ロジックの設計 |
| メニュー追加 | なし | 挿入位置特定後は定型作業 |
  • 推奨実行モデル:
工程推奨モデル理由
BudgetRepository 追加Claude Haiku既存 OrderRepository パターンの横展開、判断不要
レポートビルダー新規作成Claude Sonnetピボット集計・ダイアログ制御の設計判断が必要
メニュー追加Claude Haiku挿入位置特定後は定型作業
  • 変更履歴:
日付変更内容
2026-04-19初版作成

Step 2-4: 仕様書作成プロンプトの記録 (Edit または Bash)

## 仕様書作成プロンプト セクションに以下の形式で、この <instruction> タグ内の全文をそのまま貼り付けて記録する:

<details><summary>展開して表示</summary>

(この instruction 全文)

</details>

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

  1. docs/_config.json 追記: nav 配列の「§E.5 FP&A・レポーティング」セクションに追加: { "file": "dev/dev_mas-025_budget_variance.md", "title": "E.5.X F-25 予算vs実績差異分析" }E.5.X の番号は既存エントリの最大番号+1 に修正すること。

  2. docs/_internal/changelog.md 先頭行に追記: | 2026-04-19 | [dev_mas-025_budget_variance.md](dev_mas-025_budget_variance.md) | 初版作成。F-25 予算vs実績差異分析の開発仕様書 |

  3. Git コミット&プッシュ:

    git add docs/dev/dev_mas-025_budget_variance.md docs/_internal/changelog.md docs/_config.json
    git commit -m "docs: F-25 予算vs実績差異分析の開発仕様書を作成
    
    BudgetRepository新規追加・66_pl_budget_varianceレポートビルダー・メニュー追加の仕様書。
    BudgetDTO/JournalEntryDTOのフィールド確認・エッジケーステーブル・実データ検証を含む。
    
    https://claude.ai/code/session_XXXXX"
    git push -u origin docs/dev-F-25