MAS-033: Rule of 40 スコアボード
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-033 |
| 案件名 | Rule of 40 スコアボード |
| カテゴリ | FP&A・レポーティング(BizDev / プロダクト事業メトリクス) |
| Phase | P3 |
| 優先度 | ★ |
| ステータス | 未着手(仕様書のみ作成) |
| 所要時間見積 | 3〜4h(新規実装+MCP実データ検証含む) |
| 対象ファイル | 600_report/612_dashboard_rule_of_40.js(新規作成)100_config/101_sys_config.js(Read のみ。実際のメニュー追加は 000_infra/002_constants.js の MENU_DEFINITION)000_infra/002_constants.js(MENU_DEFINITION へ 1 項目追加) |
| 前提案件 | F-28(ARR/MRR トラッキング・未着手)/F-32(プロダクト別P/L・未着手) ※両案件ともステータス「未着手」のため、F-33 単独で先行実装する場合は「人間が検討すべき事項」で依存判断を確定させる |
| 依存しない | 科目マスタ 11_mst_account(大分類 列)・仕訳帳 42_trn_journal のみを参照し、両案件(F-28/F-32)の実装有無に依存しない最小独立設計を採用する |
目的
プロダクト事業の健全性を SaaS 業界標準指標「Rule of 40」(成長率 + 利益率 ≥ 40%)で可視化し、成長速度と収益性のバランスに関する経営意思決定を支援する。
Rule of 40 の背景: Brad Feld (Foundry Group) / Bessemer Venture Partners 等が提唱した SaaS 事業の健全性指標。「成長率を優先するか、利益率を優先するか」の二項対立を、両者の合計で総合評価する考え方で、40% 以上であれば「成長と利益のバランスが健全」とされる。
本機能の出力:
- プロダクト事業(フィルタ条件は
03_sys_paramsパラメータで指定)の月次スコアをダッシュボードシート95_dashboard_rule_of_40に表示 - 直近 13 ヶ月(または利用可能な全期間)の TTM 成長率・TTM 営業利益率・Rule of 40 スコアを時系列で並べ、推移を把握できるようにする
- Human-in-the-Loop: ヘッダー行に計算前提(フィルタ条件・利益率定義・成長率定義)を明記し、利用者が算出根拠を目視で検証できるようにする
現在のコード
新規実装。対応するシート・関数なし。
類似実装の参照元: 600_report/609_datamart_kpi.js(F-03 KPI ダッシュボード)
93_kpi_dashboardタブをss.insertSheet()で動的生成しsheet.clear()でクリアしてから再描画するパターンを踏襲する- 境界月の検出(仕訳から過去方向に非空セル末尾を走査)・行番号を事前計算してから
setValuesで一括書き込みする方式を踏襲する
関連する既存モジュール:
200_data/202_repository.jsのJournalRepository.findAll()— 仕訳帳 DTO 取得(戻り値:{ headers: string[], dtos: JournalEntryDTO[] })200_data/202_repository.jsのAccountRepository.findAsMap()— 科目名 →{stmt, cat}マップ(キャッシュ付き。キャッシュはAccountRepository.resetCache()でリセット可能)000_infra/002_constants.jsのConstants.getParam(key, defaultVal)—03_sys_paramsシート(列 A=キー / 列 B=値)からパラメータを読み取り、引数defaultValの型に応じてNumberorStringを返す。未設定時はdefaultValを返す000_infra/004_utils.jsのUtils.parseDateToYm(date)— Date | 文字列をYYYY-MM形式に正規化するヘルパー(既存の 600_report/ で多用)000_infra/002_constants.jsのConstants.MENU_DEFINITION— メニューは宣言的定義で管理(ハードコードされたui.createMenu()呼び出しはない)
修正方針
アーキテクチャ概要
┌─────────────────────┐ ┌─────────────────────┐
│ 42_trn_journal │ │ 11_mst_account │
│ (JournalRepository) │ │ (AccountRepository) │
└──────────┬──────────┘ └──────────┬──────────┘
│ findAll() │ findAsMap()
│ {headers, dtos} │ {科目名: {stmt, cat}}
▼ ▼
┌──────────────────────────────────────────────┐
│ 600_report/612_dashboard_rule_of_40.js │
│ 1. プロダクト事業フィルタ(PJ名/組織名) │
│ 2. 月次 TTM 集計(売上・費用の12ヶ月合計)│
│ 3. 前年同月 TTM 成長率・TTM 営業利益率 │
│ 4. Rule of 40 = 成長率 + 営業利益率 │
└──────────┬───────────────────────────────────┘
│ setValues()
▼
┌──────────────────────────────────────────────┐
│ 95_dashboard_rule_of_40 │
│ (動的生成シート・DDL setupAllSchemas 管理外)│
└──────────────────────────────────────────────┘
出力先シート
- シート名:
95_dashboard_rule_of_40 - DDL 管理: 管理対象外(
setupAllSchemasに追加しない) - CLAUDE.md 追記:
## DDL (setupAllSchemas) で管理されないタブセクションに95_dashboard_rule_of_40を追記する(既存の93_kpi_dashboardに続ける) - 生成方法:
ss.getSheetByName('95_dashboard_rule_of_40')が null の場合ss.insertSheet()、存在する場合はsheet.clear()で全消去してから再描画(609_datamart_kpi.jsのbuildKpiDashboardと同一パターン)
データ取得
var journal = JournalRepository.findAll(); // {headers, dtos: JournalEntryDTO[]}
var acctMap = AccountRepository.findAsMap(); // {科目名: {stmt: 'P/L'|'B/S', cat: '売上高'|...}}
JournalEntryDTOのプロパティは000_infra/003_contracts.jsの@typedefに準拠- 使用するキー:
発生日(P/L計上日)/科目名/税抜金額_実績/PJ名/組織名/仕訳ステータス/収支区分 findAsMap()の戻り値構造は{stmt, cat}であり、findAsMap_()等の派生関数名は存在しない(Phase 1 で202_repository.jsL323-341 を Read して確認済み)
集計単位(TTM: Trailing Twelve Months)
各基準月 YM について、発生日(P/L計上日) が YM - 11ヶ月 から YM の範囲に入る仕訳を集計する。
- 基準月の列挙: 仕訳の最古月〜最新月を
Utils.parseDateToYm()で収集し、ユニーク化して昇順ソート - TTM 集計の最小データ要件:
YM時点で基準月を含む 12 ヶ月のデータが必要。不足月は"-"表示 - YoY 成長率の最小データ要件:
YMとYM - 12ヶ月(前年同月)の両時点で TTM が計算可能であること(= 24 ヶ月以上のデータが必要)
計算定義(暫定。「人間が検討すべき事項」確定後に最終化する)
以下 3 点は Phase 1 のコード調査時点での暫定定義。実装前に MCP 実データ検証(後述)と「人間が検討すべき事項」で確定させる。
| 指標 | 暫定定義 |
|---|---|
| TTM 売上高 | 大分類 が売上に該当する科目(暫定: 大分類 === '売上高')の 税抜金額_実績 の 12 ヶ月合計 |
| TTM 全費用 | 諸表区分 === 'P/L' かつ 大分類 が売上以外の科目の 税抜金額_実績 の 12 ヶ月合計(絶対値で加算) |
| TTM 営業利益 | TTM売上 − TTM全費用 |
| 成長率 | `(TTM売上_当月 − TTM売上_前年同月) / |
| 営業利益率(TTM) | TTM営業利益 / TTM売上 × 100 (%) |
| Rule of 40 スコア | 成長率 + 営業利益率 |
| 判定 | スコア ≥ 40 → ✅ OK / スコア < 40 → ⚠️ 要改善 |
注記: 「売上」に該当する 大分類 の正確な文字列は 11_mst_account の実データに依存するため、実装時に MCP で確認する(後述「実データ検証」)。
プロダクト事業フィルタ
- フィルタ対象:
JournalEntryDTO.PJ名またはJournalEntryDTO.組織名(どちらを使うかは「人間が検討すべき事項」で確定) - パラメータ取得:
Constants.getParam('RULE_OF_40_FILTER_KEY', 'PJ名')およびConstants.getParam('RULE_OF_40_FILTER_VALUE', '')を03_sys_paramsから読み取るRULE_OF_40_FILTER_KEY:'PJ名'|'組織名'(デフォルト:'PJ名')RULE_OF_40_FILTER_VALUE: 完全一致する値(例:'プロダクト事業')。空文字の場合はフィルタなし(全仕訳が対象)+Utils.logInfoで警告出力
- マッチング方式: 完全一致(
trim().equals())。複数値対応が必要になったらカンマ区切り対応を検討(将来拡張)
HitL 対応(Human-in-the-Loop)
ダッシュボードのヘッダー領域(A1:H4 程度)に計算前提を明記:
| 行 | セル例 |
|---|---|
| 1 | Rule of 40 スコアボード (大タイトル) |
| 2 | フィルタ条件: {RULE_OF_40_FILTER_KEY} = "{RULE_OF_40_FILTER_VALUE}" |
| 3 | 利益率定義: 営業利益率 = (TTM売上 − TTM全費用) / TTM売上 × 100 |
| 4 | `成長率定義: TTM売上 YoY = (当月TTM − 前年同月TTM) / |
| 5 | (空行) |
| 6 | 表ヘッダー: 基準年月 / TTM売上 / TTM営業利益 / 成長率(%) / 営業利益率(%) / Rule of 40 スコア / 判定 / 備考 |
| 7〜 | 直近 13 ヶ月 or 全期間のデータ行 |
メニュー登録
- 宣言的定義:
000_infra/002_constants.jsのConstants.MENU_DEFINITIONに追加する(onOpen()の実装はMENU_DEFINITIONを動的展開するため、ui.createMenu()をハードコードしない) - 追加先カテゴリ:
'📋 サイドバー: 📊 マート更新'(F-03 KPI ダッシュボード等が所属する既存カテゴリ) - 追加項目:
{ label: '📏 Rule of 40 スコア更新', funcName: 'buildRuleOf40Dashboard', description: '95_dashboard_rule_of_40 (Rule of 40 スコアボード) を再計算' } - 造語禁止:
'📋 サイドバー: 📊 マート更新'は Phase 1 で002_constants.jsL230 を Read して実在を確認した文字列。新規カテゴリは作成しない
影響範囲
変更ファイル
| ファイル | 変更量 | 内容 |
|---|---|---|
600_report/612_dashboard_rule_of_40.js | 新規 約 250 行 | buildRuleOf40Dashboard() のエントリ関数+TTM集計ヘルパー+出力ヘルパー |
000_infra/002_constants.js | 追記 1 行(MENU_DEFINITION 内) | 既存カテゴリ '📋 サイドバー: 📊 マート更新' に 1 項目追加 |
CLAUDE.md | 追記 1 行 | ## DDL (setupAllSchemas) で管理されないタブ に 95_dashboard_rule_of_40 を追記 |
変更しないファイル
42_trn_journal(読み取り専用)11_mst_account(読み取り専用)100_config/101_sys_config.jsのsetupAllSchemas/ DDL 定義(95_dashboard_rule_of_40は DDL 管理外)100_config/101_sys_config.jsのonOpen()本体(MENU_DEFINITIONを動的展開しているため、メニュー追加は002_constants.js側のみ)000_infra/003_contracts.js(既存のJournalEntryDTO@typedefをそのまま使用)200_data/202_repository.js(既存のJournalRepository/AccountRepositoryをそのまま使用)
既存動作への影響
- 読み取り専用・独立ダッシュボード出力のため、トランザクションデータや既存マート(
61_pl_monthly等)への副作用はない AccountRepository.findAsMap()のキャッシュ(_cache)を共有利用するが、Read 専用なので他機能のキャッシュ状態を破壊しない
注意事項
- 読み取り専用設計: この機能は
42_trn_journalと11_mst_accountを読み取り専用で集計し、独立したダッシュボードシート95_dashboard_rule_of_40に書き出す。トランザクションデータへの二重更新リスクはないため、排他ロック機構(LockService)は不要 - 列参照はヘッダー名ベース:
42_trn_journal/11_mst_accountのアクセスはJournalRepository.findAll()/AccountRepository.findAsMap()経由で行うため、DTO プロパティ名(= ヘッダー名)でアクセスする。列番号ハードコード禁止(CLAUDE.md コーディング規約準拠) - 有効フラグ=FALSE はスキップ:
AccountRepository.findAsMap()は内部で有効フラグがFALSEのレコードを既にスキップしている(202_repository.jsL330 で確認済み)。JournalRepositoryは有効フラグ列を持たないため追加スキップ不要 - 存在しない関数名を使用しない:
AccountRepository.findAsMap_()/findAccountMap_()等の派生関数名は存在しない。必ずfindAsMap()(アンダースコア末尾なし、_はcacheとresetCacheのみ)を使用する - 仕訳ステータスの完全一致判定:
仕訳ステータスコード === "JSTAT_REVERSAL"等のコード値や仕訳ステータス === "仕訳振替"の文字列で判定する場合は===の完全一致。実際の格納値は実データ検証で確認(CLAUDE.md「仕訳振替の判定は=== "仕訳振替"の完全一致」規約準拠) - メニューは MENU_DEFINITION 経由:
100_config/101_sys_config.jsのonOpen()には直接編集を加えず、000_infra/002_constants.jsのConstants.MENU_DEFINITIONに宣言的に追加する(ハードコード禁止) - 絶対値計算の取り扱い: 費用は
税抜金額_実績が正値で入っている前提(科目の符号反転は DTO レベルでは行わず、集計時にMath.abs()で絶対値化する)。実際の符号は MCP 実データで確認する - TTM 集計の性能: 仕訳件数 × 月数のループで
O(N × M)になるため、前処理で仕訳をYYYY-MM→sumMapに集約してから月次ループを回す実装にすること(609_datamart_kpi.js の境界月検出パターンを参考に)
エッジケース
| # | 条件 | 表示値 / 挙動 | 理由 |
|---|---|---|---|
| 1 | 前年同月 TTM 売上 = 0(成長率の分母ゼロ) | 成長率 = "-"、スコア = "-"、判定 = "-" | ゼロ除算回避。前年データ不足または創業初年度。Math.abs(前年TTM売上) === 0 で判定 |
| 2 | 当月 TTM 売上 = 0(利益率の分母ゼロ) | 利益率 = "-"、スコア = "-"、判定 = "-" | ゼロ除算回避。売上未計上月(例: 開発フェーズのみ) |
| 3 | 集計対象が 24 ヶ月未満(YoY 計算不可) | 成長率 = "-"、スコア = "-"、判定 = "-" | TTM 成長率には前年同月の TTM が必要。データ開始から 24 ヶ月未満は算出不能 |
| 4 | 集計対象が 12 ヶ月未満(TTM 計算不可) | 当該行はそもそも出力しない | TTM は 12 ヶ月合計のため、12 ヶ月未満では暫定値として意味を持たない |
| 5 | 営業利益 < 0(赤字) | マイナス値をそのまま表示(例: -15.2%) | 正常な挙動。Rule of 40 スコアにマイナス値が反映されるのが仕様(Bessemer / Brad Feld の定義通り) |
| 6 | Rule of 40 スコア ≥ 40 | ✅ OK | 成長と収益性のバランスが良好 |
| 7 | Rule of 40 スコア < 40 | ⚠️ 要改善 | 改善が必要な状態(成長率を上げるか利益率を改善するか) |
| 8 | プロダクト事業フィルタに一致するレコード = 0 件 | 全行 "-" + Utils.logInfo で警告 | フィルタ設定ミス or プロダクト事業未開始の可能性。ヘッダー行のフィルタ条件表示で利用者が気づけるようにする |
| 9 | RULE_OF_40_FILTER_VALUE が空文字 | フィルタなし(全仕訳が対象)+ Utils.logInfo('fn', 'フィルタ値未設定のため全仕訳が対象') | デフォルトは無フィルタ。運用開始前に 03_sys_params への登録を促すログ出力 |
| 10 | 大分類 不明な科目(acctMap[科目名] が undefined) | 該当仕訳をスキップ + Utils.logInfo で件数集計 | 科目マスタ未登録科目を誤って売上/費用にカウントしないための防御。CLAUDE.md「科目マスタ未登録科目はエラー」規約との整合 |
| 11 | 仕訳ステータス が '仕訳振替' の仕訳 | 集計から除外 | 仕訳振替は P/L 残高の付け替えで、同一金額の借方/貸方が立つため二重計上を防ぐ必要がある。「人間が検討すべき事項」の仕訳ステータスフィルタ方針に従う |
| 12 | 発生日(P/L計上日) が空 or 不正な Date | 該当仕訳をスキップ + Utils.logInfo で件数集計 | Utils.parseDateToYm() が null/空を返す場合は集計対象外 |
| 13 | 税抜金額_実績 が空 or 数値以外 | `Number(...) | |
| 14 | 仕訳帳が空(件数 0) | シートヘッダーのみ出力 + Utils.logInfo('fn', '仕訳データが存在しません') | 新規 SS の初回実行時に想定 |
| 15 | 成長率・利益率の数値オーバーフロー(例: 過去期売上が極小で当期が巨額) | 表示はそのまま(上限なし)+ 数値フォーマットで 0.0% を適用 | Rule of 40 は絶対値の意味を持つため上限クリップしない |
実データ検証
実装前に MCP ツールで以下を確認すること(Phase 1 のコード読み取りでは確定できない、実データ依存の情報):
| # | 確認対象 | 確認方法 | 用途 |
|---|---|---|---|
| 1 | 11_mst_account の 大分類 列の実値一覧 | mcp__google-spreadsheet__query_range で 11_mst_account!D:D(または buildHeaderIndex_ 後に 大分類 列)を取得してユニーク化 | 売上に該当する 大分類 文字列の正確な判定('売上高' / '売上' / '売上高(事業)' 等の揺れを確認) |
| 2 | 42_trn_journal の 仕訳ステータス 列の実値一覧 | mcp__google-spreadsheet__query_range でユニーク値を取得 | '自動計上' / '手動' / '仕訳振替' の完全一致フィルタ対象を確定 |
| 3 | 42_trn_journal の PJ名 列の実値一覧 | MCP で頻度集計 | プロダクト事業識別に使用する値の候補特定(例: 'プロダクト事業' / 'SaaS事業' / '指定なし_共通費など') |
| 4 | 42_trn_journal の 組織名 列の実値一覧 | MCP で頻度集計 | PJ名 以外に組織名でフィルタする選択肢を検討する際の参考 |
| 5 | 税抜金額_実績 の符号運用 | 42_trn_journal で 収支区分 === '支出' のレコード 10 件程度をサンプル取得 | 費用側の金額が正値か負値かを確認。正値なら絶対値化不要、負値なら Math.abs() 必須 |
| 6 | 03_sys_params に RULE_OF_40_FILTER_KEY / RULE_OF_40_FILTER_VALUE を登録するか | 運用担当と合意 | 未登録時のデフォルト値(本仕様書では 'PJ名' / '')で期待通りの挙動となるかを利用者と確認 |
注意: 本仕様書では暫定定義として 大分類 === '売上高' を使うが、実データの実値により Constants.RULE_OF_40_SALES_CATEGORIES = ['売上高', '売上高(本業)'] 等の配列定数に外出しする設計に変更する可能性がある。MCP 検証時に決定すること。
関連ドキュメント
| リンク | 関連箇所 |
|---|---|
| TODO_future.md | §3.2.1 プロダクト事業メトリクス(F-28 / F-29 / F-30 / F-31 / F-32 / F-33)。F-33 の案件定義行(優先度★・P3・BizDev) |
| dev_F-28_arr_mrr_tracking.md | F-28 ARR/MRR トラッキング。成長率指標の代替ソース候補(ARR YoY)。F-33 実装前の依存判断で参照 |
| dev_F-03_kpi_dashboard.md | F-03 KPI ダッシュボード。93_kpi_dashboard 動的生成パターンの参考実装元 |
| CLAUDE.md | ## DDL (setupAllSchemas) で管理されないタブ セクション(95_dashboard_rule_of_40 追記対象)。コーディング規約(ヘッダー名ベース列参照・有効フラグ=FALSE スキップ)。「変更時の動作確認テスト」(600_report/6*_datamart_*.js → マート更新テスト) |
| prd.md | Human-in-the-Loop ポリシー。ダッシュボード計算前提の明示表示方針 |
人間が検討すべき事項
TODO_future.md の F-33 行から転記: 「利益率の定義(営業利益率 vs EBITDA margin)。成長率の算出期間(MoM vs YoY)」
上記 2 点に加え、以下 5 項目を実装前に確定する必要がある。
1. プロダクト事業の識別方法(実装前の必須判断)
- 論点:
42_trn_journalのPJ名でフィルタするか、組織名でフィルタするか、両方の AND/OR 条件が必要か - 選択肢:
- A.
PJ名単独フィルタ(本仕様書のデフォルト): シンプルだが、PJ 名が「プロダクト」プレフィックスで統一されていないと漏れる - B.
組織名単独フィルタ: プロダクト事業部が組織として独立している前提。受託 PJ が同じ組織名で計上されていると混在する - C.
PJ名 AND 組織名の複合フィルタ: 最も正確だが運用負荷が高い - D.
14_mst_projectマスタに「事業区分」列を追加し、マスタ経由で識別: 本格運用向き(F-32 プロダクト別 P/L と共通基盤化可能)
- A.
- 判断タイミング: F-33 実装前。F-32(プロダクト別 P/L)が先行実装される場合は D 案で統合する
- 本仕様書のデフォルト: A 案。
Constants.getParam('RULE_OF_40_FILTER_KEY', 'PJ名')で切替可能にする
2. 利益率の定義(TODO_future.md 元記載項目)
- 論点: 営業利益率(デフォルト)と EBITDA マージンのどちらを採用するか
- 選択肢:
- A. 営業利益率 =
(TTM売上 − TTM全費用) / TTM売上 × 100(本仕様書のデフォルト): 一般企業の標準指標 - B. EBITDA マージン =
(営業利益 + 減価償却費 + 無形資産償却費) / TTM売上 × 100: SaaS 業界標準。資本集約度が異なる企業間の比較に有利
- A. 営業利益率 =
- EBITDA 採用時の追加作業:
11_mst_accountで減価償却費・のれん償却費等に該当する科目名を特定(例:'減価償却費'/'無形固定資産償却費')- 特定科目の金額を営業利益に足し戻す処理を追加
- 本仕様書のデフォルト: A 案。将来的に B 案に切り替える場合は
Constants.getParam('RULE_OF_40_PROFIT_METRIC', 'OPERATING')で'OPERATING'/'EBITDA'を切替可能にする設計を取る
3. 成長率の算出期間(TODO_future.md 元記載項目)
- 論点: MoM(Month-over-Month、前月比)と YoY(Year-over-Year、前年同月比)のどちらか
- 選択肢:
- A. TTM YoY 成長率(本仕様書のデフォルト):
(当月TTM − 前年同月TTM) / |前年同月TTM| × 100。季節変動を吸収する SaaS 業界標準 - B. MoM 成長率:
(当月TTM − 前月TTM) / |前月TTM| × 100。変化の検知が早いが季節変動を拾う - C. MoM 成長率を年率換算:
((当月TTM / 前月TTM)^12 − 1) × 100。単月の変動が年率に増幅されるため推奨しない
- A. TTM YoY 成長率(本仕様書のデフォルト):
- 本仕様書のデフォルト: A 案
4. 成長率のデータソース(F-28 との依存判断)
- 論点: 成長率指標のベースを
42_trn_journalベースの TTM 売上高 YoY とするか、F-28(ARR/MRR)実装後の ARR YoY とするか - 選択肢:
- A.
42_trn_journalTTM 売上 YoY(本仕様書のデフォルト・F-28 非依存): F-33 を F-28 より先に実装可能。ただしストック収益(MRR)とフロー収益(スポット)が混在した指標になる - B. F-28 の ARR YoY 成長率(F-28 依存): SaaS 的には正統。ただし F-28 のステータスは現時点「未着手」(Phase 1 で TODO_future.md L266 を確認済み)
- A.
- 判断タイミング: F-28 のスコープ確定後。F-28 が実装されるまでは A 案で先行運用し、F-28 完了後に B 案へ切り替える(
Constants.getParam('RULE_OF_40_GROWTH_SOURCE', 'JOURNAL_TTM_SALES')切替) - 本仕様書のデフォルト: A 案(F-28 非依存で先行実装可能とする)
5. 仕訳ステータスのフィルタ方針
- 論点: どの
仕訳ステータスを集計対象に含めるか - 選択肢:
- A.
'自動計上'のみ: RPA 経由の信頼できる仕訳に限定。手動入力ミスを排除 - B.
'自動計上'と'手動'の両方を対象、'仕訳振替'のみ除外(本仕様書のデフォルト): 人手確認済みの手動仕訳も含めて実態を把握。仕訳振替は二重計上防止のため除外 - C. 全ステータスを対象: 最も包括的だが、検討用の仕訳振替まで入ってしまう
- A.
- 判断タイミング: 実データ検証(上記「実データ検証」#2)完了後
- 本仕様書のデフォルト: B 案
6. 判定閾値の調整可能性
- 論点: スコア 40 固定で良いか、段階判定(40 未満 = 要改善 / 40〜60 = 良好 / 60 以上 = 優秀)が必要か
- 選択肢:
- A. 40 固定の 2 段階(本仕様書のデフォルト):
✅ OK/⚠️ 要改善 - B.
Constants.getParam('RULE_OF_40_THRESHOLD', 40)で閾値を可変化 - C. 3 段階 or 5 段階(例: 60 以上
🚀 優秀/ 40〜60✅ OK/ 20〜40⚠️ 要改善/ 0〜20🔴 警戒/ 0 未満🚨 緊急)
- A. 40 固定の 2 段階(本仕様書のデフォルト):
- 本仕様書のデフォルト: A 案(運用開始後に B/C へ拡張)
7. 再実行頻度と月次スナップショット保存の要否
- 論点: 毎回
95_dashboard_rule_of_40を全置換する方式で運用するか、月次でスナップショットを別シートに保存するか - 本仕様書のデフォルト: 全置換方式(
93_kpi_dashboardと同じ運用)。スナップショットが必要になったら別途96_snapshot_rule_of_40_YYYYMM的な保存機能を追加(F-02 前年度 P/L スナップショットに類似)
実装プロンプト(Claude Code 用)
実装時は以下のプロンプトを Claude Code に与えて自律実行させる。行頭 4 スペースインデントで記述(コードブロック不使用):
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-33「Rule of 40 スコアボード」を実装してください。
## 実行前タスク(全て Read で裏取りすること。名前から推測しない)
1. `000_infra/003_contracts.js` を Read し、JournalEntryDTO の全プロパティ名を確認する
(特に `発生日(P/L計上日)` / `科目名` / `税抜金額_実績` / `PJ名` / `組織名` /
`仕訳ステータス` / `収支区分` の実際のキー文字列)
2. `200_data/202_repository.js` を Read し、以下を確認する:
- JournalRepository.findAll() の戻り値型が { headers: string[], dtos: JournalEntryDTO[] }
- AccountRepository.findAsMap() の戻り値型が { [科目名]: {stmt, cat} }
- findAsMap_() / findAccountMap_() 等の派生関数名は存在しない
3. `000_infra/002_constants.js` を Read し、以下を確認する:
- Constants.getParam(key, defaultVal) が 03_sys_params からパラメータを読む
- MENU_DEFINITION の '📋 サイドバー: 📊 マート更新' カテゴリの配置順
4. `600_report/609_datamart_kpi.js` を Read し、以下のパターンを踏襲する:
- ss.insertSheet() + sheet.clear() による動的生成
- 境界月検出・行番号事前計算・一括 setValues()
5. `000_infra/004_utils.js` を Read し、Utils.parseDateToYm() / Utils.logInfo() /
Utils.logError() のシグネチャを確認する
6. MCP で以下を確認する(実データ依存項目):
- 11_mst_account の `大分類` 列の実値一覧(売上に該当する文字列特定)
- 42_trn_journal の `仕訳ステータス` 列の実値一覧
- 42_trn_journal の `PJ名` / `組織名` 列の実値一覧(プロダクト事業の識別値)
- 42_trn_journal の `税抜金額_実績` の符号運用(費用が正値か負値か)
## 修正対象ファイル
- `600_report/612_dashboard_rule_of_40.js`(新規作成・約250行)
- エントリ関数: `buildRuleOf40Dashboard()`
- 内部ヘルパー: `collectTtmJournal_(journal, acctMap, filterKey, filterVal)` /
`computeRuleOf40_(ttmByMonth)` / `renderRuleOf40Sheet_(sheet, rows, ctx)`
- `000_infra/002_constants.js`(MENU_DEFINITION への1項目追加のみ)
- '📋 サイドバー: 📊 マート更新' カテゴリ配列の末尾付近に
{ label: '📏 Rule of 40 スコア更新', funcName: 'buildRuleOf40Dashboard',
description: '95_dashboard_rule_of_40 (Rule of 40 スコアボード) を再計算' } を追加
- `CLAUDE.md`(1行追記のみ)
- `## DDL (setupAllSchemas) で管理されないタブ` の列挙に
`95_dashboard_rule_of_40` を追記
## 実装内容
1. 出力先シート `95_dashboard_rule_of_40` の取得 or 新規作成+ clear()
2. JournalRepository.findAll() で仕訳 DTO 取得(戻り値: {headers, dtos})
3. AccountRepository.findAsMap() で科目マスタ取得
(戻り値: { [科目名]: {stmt, cat} })
4. プロダクト事業フィルタを適用:
- Constants.getParam('RULE_OF_40_FILTER_KEY', 'PJ名') で 'PJ名' or '組織名' を取得
- Constants.getParam('RULE_OF_40_FILTER_VALUE', '') でフィルタ値を取得
- 値が空なら無フィルタ + Utils.logInfo で警告
- 値がある場合は完全一致(trim 比較)
5. 仕訳ステータスフィルタ: '仕訳振替' を除外(本仕様書デフォルト方針 B 案)
6. 事前集約: filteredJournal を月(YYYY-MM)×{売上, 費用} の2次元 Map に集計
(O(N × M) 避けるため。609_datamart_kpi.js の境界月検出パターンを参考)
7. 月次 TTM ループ:
- 基準月 YM について、YM-11 から YM までの12ヶ月分の売上・費用を集計
- YM-12 から YM-23 までの12ヶ月分(前年同月 TTM)も集計
8. 成長率・営業利益率・Rule of 40 スコアの計算:
- エッジケース(当期売上=0 / 前年売上=0 / 24ヶ月未満)はすべて '-' で返す
- 判定: score >= 40 → '✅ OK' / score < 40 → '⚠️ 要改善'
9. ヘッダー領域(A1:H4)に計算前提を明記:
- A1: 'Rule of 40 スコアボード'
- A2: 'フィルタ条件: {key} = "{value}"'
- A3: '利益率定義: 営業利益率 = (TTM売上 − TTM全費用) / TTM売上 × 100'
- A4: '成長率定義: TTM売上 YoY = (当月TTM − 前年同月TTM) / |前年同月TTM| × 100'
10. 表ヘッダー(6行目):
'基準年月' / 'TTM売上' / 'TTM営業利益' / '成長率(%)' /
'営業利益率(%)' / 'Rule of 40 スコア' / '判定' / '備考'
11. データ行(7行目以降)を直近13ヶ月 or 全期間で出力
12. 数値フォーマット適用:
- 金額列: Constants.NUMBER_FORMATS.CURRENCY
- パーセント列: Constants.NUMBER_FORMATS.PERCENT or '0.0%'
13. 002_constants.js の MENU_DEFINITION に 1 項目追加
14. CLAUDE.md の DDL 管理外タブ一覧に追記
## 制約(CLAUDE.md コーディング規約準拠)
- 列参照は必ずヘッダー名ベース(DTO プロパティアクセス)。列番号ハードコード禁止
- 42_trn_journal および 11_mst_account への書き込み禁止(読み取り専用)
- 95_dashboard_rule_of_40 は DDL 管理外。setupAllSchemas に追加しない
- AccountRepository.findAsMap_() 等の存在しない関数名を使用しない
- メニュー追加は MENU_DEFINITION 経由。onOpen() 本体は変更しない
- 仕訳ステータス判定は === の完全一致
- 有効フラグ=FALSE のマスタレコードはスキップ
(findAsMap() が内部で処理済み。JournalRepository には該当列なし)
## エッジケース実装(本仕様書の「エッジケース」セクション参照)
| 条件 | 処理 |
|------|------|
| 前年 TTM 売上 = 0 | 成長率 = "-" |
| 当年 TTM 売上 = 0 | 利益率 = "-" |
| TTM 計算に 24ヶ月未満のデータ | 成長率 = "-" |
| 営業利益 < 0 | マイナス値をそのまま表示(正常) |
| フィルタ一致レコード = 0件 | "-" + Utils.logInfo で警告出力 |
| 科目マスタ未登録の科目 | 該当仕訳をスキップ + Utils.logInfo で件数集計 |
| 発生日(P/L計上日) が空 | 該当仕訳をスキップ |
| 税抜金額_実績 が数値以外 | Number(...) || 0 で 0 として扱う |
## 動作確認
1. npm run push:dev でデプロイ
2. dev スプレッドシートの `🚀 BizLP` > `操作パネルを開く` からサイドバーを開き、
「📏 Rule of 40 スコア更新」を実行
3. 95_dashboard_rule_of_40 が生成され、ヘッダー行(計算前提)と
データ行(直近13ヶ月)が正しく出力されることを確認
4. 以下のエッジケースを目視確認:
- 売上ゼロ月で利益率 = "-"
- 24ヶ月未満のデータで成長率 = "-"
- 03_sys_params に RULE_OF_40_FILTER_VALUE を設定してフィルタ動作を確認
- 空文字設定時に Utils.logInfo で警告出力されることを確認
5. 数値フォーマット(通貨・パーセント)が適用されていることを確認
6. 動作確認後に git commit → git push → PR → main マージの順で進める
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| Phase 1(調査・設計) | あり | ファイル構造把握・固有名詞裏取り・MCP 実データ検証 |
| Phase 2(実装) | なし | Phase 1 確定内容の書き下しに徹する |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 仕様書作成(本プロンプト) | Claude Sonnet 4.6 | 複数ファイル横断調査・既存パターン適用判断が必要(Opus まで要さない) |
| Phase 1(実行前タスク・MCP 実データ検証) | Claude Sonnet 4.6 | 複数ファイル横断調査・実データ分類判断 |
| Phase 2(612_dashboard_rule_of_40.js 新規実装) | Claude Opus 4.6 | 新規ファイル設計・TTM 集計ロジック・エッジケース網羅・会計ロジック(売上/費用/仕訳ステータスの意味理解)が必要 |
| Phase 3(002_constants.js / CLAUDE.md 追記) | Claude Haiku 4.5 | 確定した1行追記のみ。判断要素なし |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成。TTMベースのRule of 40スコア(成長率+利益率)計算方針、エッジケース15項目(ゼロ除算・データ不足・科目マスタ未登録等)、Human-in-the-Loop設計(ヘッダー行に計算前提明記)、プロダクト事業識別方法/利益率定義/成長率算出期間/成長率データソース/仕訳ステータスフィルタ/判定閾値/スナップショット保存の人間検討事項7項目を記載。新規ファイル番号は 600_report/609_datamart_kpi.js が既存のため 612_dashboard_rule_of_40.js を採用。メニューは MENU_DEFINITION 経由で 📋 サイドバー: 📊 マート更新 カテゴリに追加。F-28(ARR/MRR)未着手のため F-33 単独実装可能とする A 案(JournalRepository TTM 売上 YoY)をデフォルトに設定し、F-28 完了後に B 案(ARR YoY)へ切替可能な Constants.getParam 切替設計を採用 |
仕様書作成プロンプト
展開して表示
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
- 拡張思考の使い分け: Phase 1(設計フェーズ)では拡張思考をフル活用し、ファイル名形式・エッジケース一覧・Step 分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全に確定させる。Phase 2(清書フェーズ)の各 Step 内では拡張思考を最小限に抑え、Phase 1 で確定した内容の書き下しに徹する。出力途中で再考しない。
- テキスト報告の禁止: 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。説明は 1 文以内。直ちに tool を呼ぶ。
- 4-5 分割の Write/Edit 実行: 仕様書作成は以下の Step に分けて実行する:
- 2-1 骨格 Write(〜20行)
- 2-2 概要〜注意事項 Edit/Bash(〜300行)
- 2-3a エッジケース〜人間が検討すべき事項 Edit/Bash(〜200行)
- 2-3b 実装プロンプト〜変更履歴 Edit/Bash(〜250行)
- 2-4
<details>にプロンプト全文記録 Edit/Bash(最重量・必ず独立 Step) 1 回の Write/Edit は約 300 行以内を目安にする。
- 各 Step で何を書くかを具体指示: 設計判断を Phase 2 実行時に持ち込まないよう、Phase 1 で全項目を確定させる。各 Step の内容は以下に明示してある。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
CLIエージェント「Claude Code」として、案件 F-33「Rule of 40 スコアボード」の開発仕様書を作成してください。
仕様書を新規作成したら、docs/_config.json の nav 配列の適切なセクションにも必ず追記すること。
Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
1-A: 案件定義の読み込み
docs/_internal/TODO_future.mdで F-33 の行を検索し、案件名・カテゴリ・Phase・優先度・概要・人間が検討すべき事項を取得する。- 同ファイルで F-28(ARR/MRR 指標)・F-32(プロダクト別P/L)のステータス(完了/未着手)を確認し、F-33 の前提依存関係を把握する。
1-B: プロジェクト規約の読み込み
CLAUDE.mdを Read し、コーディング規約(列参照のindexOf/buildHeaderIndex_必須、有効フラグ=FALSE スキップ)・ファイル番号体系・DDL 管理対象外タブ一覧(93_kpi_dashboard等が動的生成タブである旨)を把握する。
1-C: 既存仕様書テンプレートの読み込み
docs/dev/配下から FP&A・レポート系の既存仕様書を 1 件 Read してフォーマットを把握する。候補(存在するものを選ぶ):dev_F-24_bep_analysis.md、dev_F-20_yoy_comparison.md、dev_F-01_variance_analysis.md。
1-D: 関連コードの調査(Grep で場所を探し、Read で構造を確認する。名前から推測して書かない)
以下を Read で確認し、仕様書に書く固有名詞(列名・関数名・定数名・シート名)を全て裏取りすること:
000_infra/003_contracts.js—JournalEntryDTOの全プロパティ名(発生日(P/L計上日)・科目名・税抜金額_実績・PJ名・組織名・仕訳ステータス・収支区分の実際のキー文字列)を確認する。200_data/202_repository.js—JournalRepository.findAll()の戻り値型({headers, dtos})と、AccountRepository.findAsMap()が返すオブジェクトの構造({stmt, cat}のフィールド名)を確認する。findAsMap_()等の存在しない派生関数名を使わないこと。000_infra/002_constants.js—Constants.getParam(key, defaultVal)の引数・戻り値型を確認する(03_sys_paramsからパラメータを読み取るヘルパー)。Constants.CONFIG_SHEETの実際の値('01_sys_config')を確認する。600_report/ディレクトリ — 既存のデータマート・ダッシュボード生成スクリプト(601_datamart_ingest.js〜608_datamart_render.js等)を 1〜2 件 Read し、シート出力・関数命名・メニュー登録のパターンを把握する。新規ファイルを追加する場合は609_以降の番号を使う想定だが、既存ファイルへの追記が適切か判断する。100_config/101_sys_config.js—onOpen()のui.createMenuを Read し、F-33 の実行関数を追加すべきメニュー項目の実在する文字列を確認する(メニュー名は造語しない)。DDLsetupAllSchemasでの動的タブ扱いのコード上の根拠も確認する。
Phase 2: 仕様書の分割作成
出力先: docs/dev/dev_F-33_rule_of_40_scoreboard.md
【重要】絶対に 1 回のツール呼び出しで全内容を出力しない。以下の Step 順に分割実行すること。
Step 2-1: 骨格の作成(File Write、〜20行)
docs/dev/dev_F-33_rule_of_40_scoreboard.md に以下の見出しのみの骨格を Write で作成する(本文は空で可):
# F-33: Rule of 40 スコアボード
## 概要
## 目的
## 現在のコード
## 修正方針
## 影響範囲
## 注意事項
## エッジケース
## 実データ検証
## 関連ドキュメント
## 人間が検討すべき事項
## 実装プロンプト(Claude Code 用)
## 推奨実行モデル
## 変更履歴
## 仕様書作成プロンプト
Step 2-2: 概要〜注意事項の追記(File Edit または Bash、〜300行)
Phase 1 で裏取り済みの固有名詞のみ使用して以下を書き込む:
概要テーブル: 案件ID=F-33、カテゴリ=FP&A・レポーティング、Phase/優先度/所要時間=TODO_future.md から転記、対象ファイル=Phase 1 で特定したもの、前提案件=F-28・F-32
目的: プロダクト事業の健全性を「Rule of 40」スコアで可視化し、成長率と収益性のバランスに関する経営意思決定を支援する。
現在のコード: 新規実装(対応するシート・関数なし)。類似実装として 600_report/ の既存ダッシュボード生成パターン(Phase 1 で確認したもの)に従うことを明記する。
修正方針: 以下のアーキテクチャを記述する:
- 出力先シート:
95_dashboard_rule_of_40(CLAUDE.md の「DDL で管理されないタブ」として追記。setupAllSchemasの管理対象外) - データ取得:
JournalRepository.findAll()で42_trn_journalの全仕訳 DTO を取得。AccountRepository.findAsMap()で科目マスタ(戻り値:{ [科目名]: {stmt, cat} })を取得し大分類ベースで売上・費用を分類する - 集計単位: TTM(Trailing Twelve Months / 直近12ヶ月移動合計)。各基準月において
発生日(P/L計上日)が過去12ヶ月以内の仕訳をUtils.parseDateToYm()で判定・集計する - 計算定義(下記3点は「人間が検討すべき事項」確定後に最終化する暫定定義):
- TTM 売上高:
大分類が売上に該当する科目の税抜金額_実績の12ヶ月合計(実際の大分類文字列は実データ検証で確認) - 成長率:
(TTM売上_当月 − TTM売上_前年同月) / |TTM売上_前年同月| × 100(%) - 営業利益率(TTM):
TTM営業利益 / TTM売上 × 100(%)(営業利益 = TTM売上 − TTM全費用) - Rule of 40スコア:
成長率 + 営業利益率。40以上で「✅ OK」、未満で「⚠️ 要改善」と判定
- TTM 売上高:
- プロダクト事業フィルタ:
PJ名または組織名(実際の列名は Phase 1 で確認したJournalEntryDTOのキー文字列を使用)によるフィルタ。フィルタ値はConstants.getParam(key, defaultVal)経由で03_sys_paramsから読み取りパラメータ化する(フィルタ値は「人間が検討すべき事項」確定後に設定) - HitL 対応(Human-in-the-Loop): ダッシュボードのヘッダー行に「計算前提(フィルタ条件・利益率定義・成長率定義)」を記載し、利用者が計算根拠を目視確認できるようにする
- メニュー登録:
100_config/101_sys_config.jsのonOpen()に実行関数を追加(Phase 1 で確認した実在するメニュー文字列の下に追加。造語禁止)
影響範囲: 変更ファイル・変更量・既存動作への影響を Phase 1 の調査結果に基づき記述する。
注意事項:
- この機能は実績データを読み取り専用で集計し独立したダッシュボードシートに書き出すため、トランザクションデータの二重更新リスクはなく排他ロック機構は不要
- 列参照は必ずヘッダー名ベース(
indexOfまたはbuildHeaderIndex_)。列番号ハードコード禁止 - 有効フラグ=FALSE の行は全処理でスキップ
AccountRepository.findAsMap()の戻り値構造は{stmt, cat}であることを Phase 1 で Read 確認済み。findAsMap_()等の存在しない関数名を使用しない
Step 2-3a: エッジケース〜人間が検討すべき事項の追記(File Edit または Bash、〜200行)
エッジケーステーブル:
| 条件 | 表示値 | 理由 |
|---|---|---|
| 前年 TTM 売上 = 0(成長率の分母ゼロ) | "-" | ゼロ除算。前年データ不足または創業初年度 |
| 当年 TTM 売上 = 0(利益率の分母ゼロ) | "-" | ゼロ除算。売上計上なし |
| 集計対象が 24ヶ月未満(YoY 計算不可) | "-" | TTM 成長率には前年同月の TTM が必要 |
| 営業利益 < 0(赤字) | マイナス値をそのまま表示 | 正常な挙動。スコアにマイナス値が反映される |
| Rule of 40スコア ≥ 40 | ✅ OK | 成長と収益性のバランスが良好 |
| Rule of 40スコア < 40 | ⚠️ 要改善 | 改善が必要な状態 |
| プロダクト事業フィルタ一致レコード = 0件 | "-" + Utils.logInfo で警告 | フィルタ設定ミスの可能性 |
実データ検証(実装前に MCP で確認すること):
11_mst_accountの大分類列の実際の値一覧(売上・費用に相当する大分類の正確な文字列。DDL 定義と実データの乖離チェック)42_trn_journalの仕訳ステータス列の実際の格納値("自動計上""手動"等。フィルタ対象を完全一致で絞り込む)42_trn_journalのPJ名・組織名の実際の格納値(プロダクト事業識別フィルタに使用)03_sys_paramsにプロダクト事業識別用のパラメータキーを追加するかどうかの確認
関連ドキュメント: TODO_future.md の F-28・F-32、docs/prd.md(Human-in-the-Loop ポリシー)
人間が検討すべき事項: TODO_future.md の F-33 行から転記した上で、以下 3 点を追記し「実装前に意思決定が必要」と明示する:
- プロダクト事業の識別方法:
42_trn_journalのPJ名または組織名(JournalEntryDTOの実際のキー名)のどの値でフィルタするかを確定する。14_mst_project等のマスタとの連携も考慮する - 利益率の定義: デフォルトは営業利益率。SaaS 事業では EBITDA マージンも選択肢。EBITDA を採用する場合、「減価償却費」に該当する勘定科目を
11_mst_accountから特定する必要がある - 成長率のデータソース: デフォルトは
42_trn_journalベースの TTM 売上高 YoY 成長率。F-28(ARR/MRR)のステータス(Phase 1 で確認)によっては ARR YoY 成長率の方が適切。F-28 完了前に F-33 を実装するか、依存関係として待つかを判断する
Step 2-3b: 実装プロンプト〜変更履歴の追記(File Edit または Bash、〜250行)
実装プロンプトは行頭 4 スペースインデントで記述(コードブロック不使用):
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-33「Rule of 40 スコアボード」を実装してください。
## 実行前タスク
- `000_infra/003_contracts.js` を Read し、JournalEntryDTO の全プロパティ名を確認する
- `200_data/202_repository.js` を Read し、JournalRepository.findAll() の戻り値型と
AccountRepository.findAsMap() の {stmt, cat} 構造を確認する
- `100_config/101_sys_config.js` を Read し、onOpen() のメニュー追加先の実在する文字列を確認する
- `600_report/` の既存ファイルを Read し、新規ファイル番号(609以降)または追記先ファイルを決定する
- MCP で 11_mst_account の大分類実値、42_trn_journal の仕訳ステータス実値、
PJ名・組織名の実際の格納値を確認する
## 修正対象ファイル
- `600_report/609_rule_of_40.js`(新規作成)または Phase 1 で特定した既存ファイルへの追記
- `100_config/101_sys_config.js`(onOpen() へのメニュー追加のみ)
## 実装内容
1. `95_dashboard_rule_of_40` シートの作成/クリア処理
2. JournalRepository.findAll() で仕訳 DTO 取得(戻り値: {headers, dtos})
3. AccountRepository.findAsMap() で科目マスタ取得(戻り値: { [科目名]: {stmt, cat} })
4. プロダクト事業フィルタを適用(PJ名 または 組織名。Constants.getParam() で取得)
5. 月次 TTM 集計ループ(Utils.parseDateToYm() で発生日(P/L計上日)を YYYY-MM に正規化して判定)
6. 成長率・営業利益率・Rule of 40スコアの計算(エッジケース処理を必ず含める)
7. ダッシュボードヘッダー行に計算前提(フィルタ条件・利益率定義・成長率定義)を記載
8. 101_sys_config.js の onOpen() に実行メニューを追加(Read で確認した実在する文字列の下に追加)
## 制約
- 列参照は必ずヘッダー名ベース(indexOf または buildHeaderIndex_)。列番号ハードコード禁止
- 有効フラグ=FALSE の行はスキップ
- 42_trn_journal および 11_mst_account への書き込み禁止(読み取り専用)
- 95_dashboard_rule_of_40 は DDL 管理外(setupAllSchemas に追加しない)
- AccountRepository.findAsMap_() 等の存在しない関数名を使用しない
- メニュー名は onOpen() を Read して実在する文字列のみ使用する(造語禁止)
## エッジケース
| 条件 | 処理 |
|------|------|
| 前年 TTM 売上 = 0 | 成長率 = "-" |
| 当年 TTM 売上 = 0 | 利益率 = "-" |
| TTM 計算に 24ヶ月未満のデータ | 成長率 = "-" |
| 営業利益 < 0 | マイナス値をそのまま表示(正常) |
| フィルタ一致レコード = 0件 | "-" + Utils.logInfo で警告出力 |
## 動作確認
1. npm run push:dev でデプロイ
2. メニューから Rule of 40 実行関数を起動
3. 95_dashboard_rule_of_40 が生成されヘッダー・データ行が正しく出力されることを確認
4. 売上ゼロ月・24ヶ月未満データ月で "-" が正しく表示されることを確認
5. 動作確認後に git commit → git push → PR → main マージの順で進める
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| Phase 1(調査・設計) | あり | ファイル構造把握・固有名詞裏取り |
| Phase 2(実装) | なし | Phase 1 確定内容の書き下しに徹する |
推奨実行モデルテーブル:
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 仕様書作成(本プロンプト) | Claude Sonnet 4.6 | 複数ファイル横断調査・設計判断が必要 |
| F-33 実装 | Claude Opus 4.6 | 新規ファイル設計・複数ファイル横断・会計ロジック理解が必要 |
変更履歴テーブル:
| 日付 | 変更内容 |
|---|---|
| 2026-04-20 | 初版作成 |
Step 2-4: 仕様書作成プロンプトの記録(File Edit または Bash、最重量・必ず独立 Step)
## 仕様書作成プロンプト セクションに以下の形式で追記する:
<details><summary>展開して表示</summary>
(この <instruction> タグ内の全文をここに転記する)
</details>
Phase 3: 保存・登録・コミット
3-A: ファイル保存確認
docs/dev/dev_F-33_rule_of_40_scoreboard.md が全 Step 分のコンテンツを含んで保存されていることを確認する。
3-B: _config.json への登録(必須・省略禁止)
docs/_config.json の nav 配列の §E.5(FP&A・レポーティング)セクションに以下を追加する:
{ "file": "dev/dev_F-33_rule_of_40_scoreboard.md", "title": "E.5.X F-33 Rule of 40 スコアボード" }
追加後に JSON 構文チェックを実行する(例: node -e "JSON.parse(require('fs').readFileSync('docs/_config.json','utf8'))" && echo OK)。
3-C: changelog.md への追記
docs/_internal/changelog.md のヘッダー直後に以下を追記する:
| 2026-04-20 | [dev_F-33_rule_of_40_scoreboard.md](dev_mas-033_rule_of_40_scoreboard.md) | 初版作成。TTMベースのRule of 40スコアボード仕様書 |
3-D: コミット&プッシュ
git add docs/dev/dev_F-33_rule_of_40_scoreboard.md docs/_config.json docs/_internal/changelog.md
git commit -m "docs: F-33 Rule of 40 スコアボードの開発仕様書を作成
TTMベースの成長率・営業利益率・Rule of 40スコア計算方針、
エッジケース(ゼロ除算・データ不足)、Human-in-the-Loop設計、
プロダクト事業識別等の人間検討事項3点を記載。
https://claude.ai/code/session_XXXXX"
git push -u origin {現在のブランチ}
📌 取り込み時の注記 (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)。 実装時にファイル番号の再割当が必要。