最終更新: 2026/06/22 18:56
MAS-024: 損益分岐点(BEP)の月次自動算出
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-024 |
| カテゴリ | FP&A |
| Phase | P2 |
| 優先度 | ★★★ |
| 所要時間 | 1時間 |
| 対象ファイル | 600_report/601_datamart_ingest.js(acctMap拡張)600_report/603_datamart_pl.js(BEP計算+出力行追加) |
| 後続案件 | MAS-003(KPIダッシュボード)の基盤 |
目的
科目マスタの固変区分(固定費/変動費)を活用し、損益分岐点(BEP)売上高を月次・通期で自動算出してP/Lタブに表示する。
BEP計算式
BEP売上高 = 固定費 / (1 - 変動費率)
変動費率 = 変動費 / 売上高
現在のコード
科目マスタの固変区分(101_sys_config.js L451, L794)
DDL定義済み。MST_ACCT ヘッダーの第8列。
// ヘッダー定義
headers: [..., "固変区分", "デフォルト税率", "科目名", "説明"]
// プルダウン値(15_mst_dict)
['固変区分', [['FV_NA','対象外'], ['FV_VAR','変動費'], ['FV_FIX','固定費']]]
acctMap の現在の構造(601_datamart_ingest.js L21付近)
acctMap[inputName] = {
stmt: String(acctData[i][2]).trim(), // 諸表区分 (P/L or B/S)
cat: String(acctData[i][3]).trim(), // 大分類
disp: String(acctData[i][4]).trim() // 表示区分
// ★ 固変区分が未含有
};
martPl の構造
martPl[sectionId][科目名] = [Total, M1, M2, ..., M12] // 13要素配列
sectionTotalsPl[sectionId] = [Total, M1, M2, ..., M12]
修正方針
Step 1: acctMap に固変区分を追加
601_datamart_ingest.js の acctMap 構築箇所で、第8列(固変区分)を追加。
acctMap[inputName] = {
stmt: String(acctData[i][2]).trim(),
cat: String(acctData[i][3]).trim(),
disp: String(acctData[i][4]).trim(),
fvCat: String(acctData[i][7]).trim() // S-24追加: FV_NA / FV_VAR / FV_FIX
};
Step 2: BEP計算と出力行追加
603_datamart_pl.js の dmBuildPlOutput_() の末尾(return直前)に、BEPサマリー行を追加。
// F-24: 損益分岐点(BEP)サマリー
var acctMap = ctx.acctMap;
var fixedCost = Array(13).fill(0); // 固定費合計
var variableCost = Array(13).fill(0); // 変動費合計
var salesTotal = sectionTotalsPl['sales'] || Array(13).fill(0);
// 費用セクション(cogs, sga, buffer)から固変分類集計
['cogs', 'sga', 'buffer'].forEach(function(secId) {
var secAccts = martPl[secId] || {};
Object.keys(secAccts).forEach(function(accName) {
var fv = acctMap[accName] ? acctMap[accName].fvCat : '';
var arr = secAccts[accName];
for (var i = 0; i < 13; i++) {
var amt = Math.abs(Number(arr[i]) || 0);
if (fv === 'FV_FIX') fixedCost[i] += amt;
else if (fv === 'FV_VAR') variableCost[i] += amt;
// FV_NA / 未設定 は固定費として扱う(保守的推計)
else fixedCost[i] += amt;
}
});
});
// BEP算出
var bepSales = Array(13).fill(0);
var bepRatio = Array(13).fill(0); // BEP / 実績売上(安全余裕率の逆数)
for (var i = 0; i < 13; i++) {
var sales = Math.abs(Number(salesTotal[i]) || 0);
var varRate = sales > 0 ? variableCost[i] / sales : 0;
var contribution = 1 - varRate;
bepSales[i] = contribution > 0 ? Math.round(fixedCost[i] / contribution) : 0;
bepRatio[i] = sales > 0 ? bepSales[i] / sales : 0;
}
// 出力行に追加
outM.push(Array(hPl.length).fill('')); // 空行
fmtM.push('empty');
outM.push(['📊 損益分岐点分析 (BEP)', ...Array(hPl.length - 1).fill('')]);
fmtM.push('mega_header');
outM.push([' 固定費合計', ...filterWithRecalcTotal(fixedCost)]);
fmtM.push('account');
outM.push([' 変動費合計', ...filterWithRecalcTotal(variableCost)]);
fmtM.push('account');
outM.push([' BEP売上高', ...filterWithRecalcTotal(bepSales)]);
fmtM.push('profit');
outM.push([' BEP到達率 (BEP/売上)', ...filterValues(bepRatio)]);
fmtM.push('auto_tax');
YTD(outY)にも同様に追加(filterValues + dmToYtdArray_ の組み合わせ)。
未設定科目の扱い
| 固変区分 | 扱い | 理由 |
|---|---|---|
| FV_FIX | 固定費 | そのまま |
| FV_VAR | 変動費 | そのまま |
| FV_NA / 空 | 固定費 | 保守的推計(BEPが高く出る方が安全) |
影響範囲
- 601_datamart_ingest.js: acctMap に
fvCatプロパティ追加(1行)。既存の参照箇所(stmt, cat, disp)に影響なし - 603_datamart_pl.js:
dmBuildPlOutput_()の return 直前にBEPセクション追加(約40行) - 既存タブへの影響: 61/62/63/64タブの末尾にBEPセクションが追加される。既存の行は一切変更なし
注意事項
- 固変区分の精度: BEP計算の精度は科目マスタの固変区分設定に依存する。「人間が検討すべき事項」参照
- isActualOnly の適用: BEP行にも既存の
filterValues/filterWithRecalcTotalを適用し、実績タブ(61/62)では境界月以降を空白にする - BEP到達率の数値フォーマット: パーセント表示。100%超=売上がBEP未達(赤字ライン)
- 売上=0の月: 変動費率が計算不可のためBEP=0とする
- 営業外・特別損益: BEP計算には含めない(営業利益ベース)
関連ドキュメント
| 仕様書 | 関連箇所 |
|---|---|
| CLAUDE.md | 変更時の動作確認テスト: 600_report/6*_datamart_*.js → マート更新テスト |
| TODO_future.md | MAS-003 KPIダッシュボード(MAS-024が基盤) |
人間が検討すべき事項
- 固変区分の正確性の確認(科目マスタのレビュー)。特に「通信費」「水道光熱費」等の準変動費をどちらに分類するか
- 未設定科目を固定費として扱うか変動費として扱うか(本仕様では固定費=保守的推計を採用)
実装プロンプト(Claude Code 用)
Step 1: acctMap 拡張
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-24 の Step 1(acctMap への固変区分追加)を実装してください。
## 実行前タスク
以下のファイルを読み込んでください:
1. `600_report/601_datamart_ingest.js` — acctMap 構築箇所。科目マスタ(11_mst_account)のどの列を読んでいるか確認
2. `100_config/101_sys_config.js` — L451の MST_ACCT headers。固変区分の列位置(index 7)を確認
3. `docs/dev/dev_mas-024_bep_analysis.md`
## 実装内容
`601_datamart_ingest.js` の acctMap 構築ループで、`acctData[i][7]`(固変区分)を `fvCat` プロパティとして追加。
## 制約
- 既存の stmt, cat, disp プロパティは変更しない
- acctData の列インデックスがヘッダーベースでない場合は、ヘッダー検索方式に変更してもよい
Step 2: BEP計算と出力行追加
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-24 の Step 2(BEP計算と出力行追加)を実装してください。
## 実行前タスク
以下のファイルを読み込んでください:
1. `600_report/603_datamart_pl.js` — `dmBuildPlOutput_()` の全体構造。`martPl`, `sectionTotalsPl`, `filterValues`, `filterWithRecalcTotal` の使い方。return文の位置
2. Step 1 で追加した `acctMap.fvCat` の構造
3. `docs/dev/dev_mas-024_bep_analysis.md` — BEP計算式と出力レイアウト
## 実装内容
`dmBuildPlOutput_()` の return 文の直前に、BEPサマリーセクションを追加:
1. 費用セクション(cogs, sga, buffer)の科目別データを `acctMap[科目名].fvCat` で固変分類
2. 固定費合計・変動費合計を月次で集計
3. BEP売上高 = 固定費 / (1 - 変動費/売上) を月次で算出
4. BEP到達率 = BEP売上高 / 実績売上高 を月次で算出
5. outM / outY の両方に行を追加。isActualOnly のフィルタも適用
**未設定科目(fvCat が FV_NA/空/undefined)は固定費として集計する。**
## 動作確認
`npm run push:dev` 後:
1. `buildBudgetTrendDataMart()` を実行
2. 61タブの末尾に「📊 損益分岐点分析」セクションが表示されること
3. 固定費合計 + 変動費合計 ≒ 売上原価 + 販管費 であること
4. BEP到達率が100%以下なら黒字ライン(売上 > BEP)
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| Step 1: acctMap拡張 | なし | 1プロパティ追加のみ |
| Step 2: BEP計算 | あり | 固変分類→変動費率→BEP算出のロジック構築。売上=0のエッジケース |
| Step 2: 出力行追加 | なし | 既存パターン(outM.push + fmtM.push)の踏襲 |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 仕様書作成(本ドキュメント) | Claude Opus 4.6 | 会計上のBEP算出ロジック、固変区分の未設定科目の扱いに高い推論力が必要 |
| Step 1 実装 | Claude Haiku 4.5 | 1プロパティ追加のみ |
| Step 2 実装 | Claude Sonnet 4.6 | BEP計算ロジック構築、filterValues/filterWithRecalcTotal の適切な適用に中程度の判断力が必要 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-14 | 初版作成 |