MAS-028: ARR/MRR トラッキング
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-028 |
| カテゴリ | BizDev / FP&A・プロダクト事業メトリクス |
| Phase | P2 |
| 優先度 | ★★ |
| 所要時間 | 4-6時間 |
| 対象ファイル | 000_infra/003_contracts.js(PipelineDTO @typedef 追記)200_data/202_repository.js(PipelineRepository 追記)600_report/610_datamart_saas_kpi.js(新規作成)100_config/101_sys_config.js(KPI_SAAS スキーマ定義追加)000_infra/002_constants.js(MENU_DEFINITION にメニュー項目追加) |
| 前提案件 | なし(既存 21_bud_pipeline シートのデータを読み取るのみ) |
目的
21_bud_pipeline(予算_売上パイプライン(狩猟/農耕))に登録された継続契約データから、月次の ARR / MRR / New MRR / Expansion MRR / Churn MRR / Net New MRR を自動集計し、新規シート 69_kpi_saas に出力する機能を追加する。
SaaS 型プロダクト事業の健全性指標(T2D3 モデル、Compound Growth の基礎指標)をスプレッドシート上でリアルタイムにトラッキング可能にすることで、「攻めの FP&A」戦略テーマの第一歩とする。
現状は 21_bud_pipeline に契約単位の情報(計上開始年月 / 継続月数 / 継続月額(MRR))は存在するが、月次時系列の集計ビューがない。本仕様で導入する buildSaaSMetrics() は読み取り専用の純粋集計機能であり、既存の RPA 自動起票(generatePipelineInvoices)には一切影響を与えない。
現在のコード
新規機能のため既存ロジックなし
MAS-028 は完全新規のレポーティング機能であり、既存コードの変更は スキーマ追加・メニュー追加 のみ。集計ロジックは新規ファイル 600_report/610_datamart_saas_kpi.js に全て格納する。
参考にする既存 Repository パターン(200_data/202_repository.js)
新規 PipelineRepository の実装は、既存の AccountRepository(L304-350)や PartnerRepository(L356-406)のような 読み取り専用マスタ Repository のパターンを踏襲する。
// 既存 AccountRepository の雛形(202_repository.js:304-350 抜粋)
var AccountRepository = {
_getSheet: function() {
return Utils.getSheetByKey('MST_ACCT', '11_mst_account');
},
findAll: function() {
return readSheetAsDtos_(AccountRepository._getSheet());
},
// save() / append() は未定義(読み取り専用)
};
共通ヘルパー readSheetAsDtos_(sheet)(202_repository.js:19-29)は { headers: string[], dtos: Object[] } 形式で返し、ヘッダー行を自動スキップする。PipelineRepository.findAll() はこれを呼ぶのみで十分。
既存 21_bud_pipeline スキーマ(100_config/101_sys_config.js:883)
'BUD_PIPE': { headers: [
"有効フラグ","管理ID","PJ・案件名","契約形態","売上科目","確度(ヨミ)",
"計上開始年月","スポット売上・初期費用","継続月額(MRR)","継続月数",
"取引先名","決済手段","CF計上","入金ラグ(月)","入金日","休日調整",
"組織名","起票ターゲット月","最終起票年月日","備考"
], color: "#e69138", ... }
ID プレフィックスは PIP_(000_infra/002_constants.js:104)。契約形態 のデフォルト値は スポット(狩猟)(000_infra/002_constants.js:77)のため、継続契約を示す値は実シートで MCP 確認する(推定: 継続(農耕))。
既存 Utils.parseDateToYm / Utils.addMonths / Utils.parseAmt(000_infra/004_utils.js)
Utils.parseDateToYm(val)— Date 型 / "YYYY-MM" / "YYYY/MM" / "YYYY年MM月" を受けて"YYYY-MM"文字列を返す。パース不可は""(004_utils.js:92-99)Utils.addMonths(ymStr, months)—"YYYY-MM"に月を加算して"YYYY-MM"を返す(004_utils.js:127-132)Utils.parseAmt(val)— 全角数字・カンマ区切りを数値化(004_utils.js:191-198)
修正方針
本機能は 4 Step 構成で段階的に実装する。
Step 1: PipelineDTO @typedef の追記
- 対象ファイル:
000_infra/003_contracts.js - 追記位置:
BudgetDTO(L132-149)の直後(§2 仕訳帳 / 予算 DTO セクションの末尾)。PartnerDTO(L152-168)の前。 - プロパティ(
21_bud_pipelineシートの実列に準拠):有効フラグ(boolean)管理ID(string、"PIP_NNNN")PJ・案件名(string)契約形態(string、"スポット(狩猟)"/"継続(農耕)"等)売上科目(string、通常は"売上高")確度(ヨミ)(string、"Aヨミ (確度80%)"等)計上開始年月(Date|string、"YYYY-MM")スポット売上・初期費用(number)継続月額(MRR)(number)継続月数(number)取引先名(string)組織名(string)備考(string)
- 記述スタイル: 既存
OrderDTO/BudgetDTOと 完全一致させる(@typedef {Object}→@property {型} プロパティ名 - コメント形式)。
Step 2: PipelineRepository の追記
- 対象ファイル:
200_data/202_repository.js - 追記位置: ファイル末尾(
PartnerRepository, L356-406 の直後、L406以降に追記) - 実装内容:
_getSheet():Utils.getSheetByKey('BUD_PIPE', '21_bud_pipeline')を使用(BUD_PIPEは101_sys_config.js:777で登録済み)findAll():readSheetAsDtos_(PipelineRepository._getSheet())を返すのみsave()/append()は 実装しない(MAS-028 は読み取り専用のため)
- 記述スタイル:
AccountRepository(L304-350)と完全一致させる。
Step 3: buildSaaSMetrics() の新規実装
- 対象ファイル:
600_report/610_datamart_saas_kpi.js(新規作成) - 処理フロー:
PipelineRepository.findAll()で全レコード取得- フィルタリング(順序厳守)
有効フラグ === falseor"FALSE"→ スキップ契約形態 !== CONTINUOUS_CONTRACT_TYPE(MCP 確認値を定数化) → スキップ計上開始年月が空 → スキップUtils.parseAmt(継続月数) <= 0→ スキップUtils.parseAmt(継続月額(MRR)) === 0→ スキップ(エッジケースセクション参照、要 Human-in-the-Loop)
- 各契約のライフサイクル月ループ
startYm = Utils.parseDateToYm(dto['計上開始年月'])durMonths = Utils.parseAmt(dto['継続月数'])endYm = Utils.addMonths(startYm, durMonths - 1)- 開始月 → 終了月を 1 ヶ月ずつ
Utils.addMonthsでインクリメント - 中間データ構造
customerMrrByYm: Map<ym, Map<取引先名, MRR合計>>を構築(同一取引先の複数契約は MRR を合算)
- 時系列スキャン(ym 昇順ソート)
- 前月マップと当月マップを比較
- 前月に存在しない取引先 → New MRR
- 前月に存在かつ MRR 増加 → Expansion MRR(差分のみ)
- 前月に存在かつ MRR 減少 → Churn MRR(差分のみ、負値)
- 当月に存在しない前月取引先 → Churn MRR(前月 MRR 全額)
- 初月(前月マップ空):
New MRR = 当月総 MRR/Expansion MRR = 0/Churn MRR = 0 MRR = 当月全取引先 MRR 合計ARR = MRR × 12Net New MRR = New + Expansion - Churn
- 前月マップと当月マップを比較
69_kpi_saasシートへの書き込み(毎回既存データをクリアして全件一括setValuesで冪等性保証)
- 出力列(
69_kpi_saasシート):対象年月 | MRR | ARR | New MRR | Expansion MRR | Churn MRR | Net New MRR | 顧客数
Step 4: スキーマ定義・メニュー追加
- 対象ファイル (a):
100_config/101_sys_config.jssetupAllSchemas内のschemasオブジェクト(L826 以降)にKPI_SAASエントリを追加'KPI_SAAS': { headers: ["対象年月","MRR","ARR","New MRR","Expansion MRR","Churn MRR","Net New MRR","顧客数"], color: "#674ea7" },confSheet登録(existKeys.includes('KPI_SAAS')ガード付き、L820 前後の既存パターンに合わせる):if (!existKeys.includes('KPI_SAAS')) confSheet.appendRow(['KPI_SAAS', '', '69_kpi_saas', 'SaaS KPI(ARR/MRR トラッキング)']);
- 対象ファイル (b):
000_infra/002_constants.jsMENU_DEFINITIONの📋 サイドバー: 📊 マート更新セクション(L229-239)に以下を追加:{ label: '📈 SaaS KPI (ARR/MRR) 更新', funcName: 'buildSaaSMetrics', description: '21_bud_pipeline から MRR/ARR/New/Expansion/Churn を集計し 69_kpi_saas に出力' },- 既存項目
📊 KPIダッシュボード再描画(L236)の直後に挿入するのが自然。
影響範囲
| 区分 | ファイル | 変更種別 | リスク |
|---|---|---|---|
| 新規作成 | 600_report/610_datamart_saas_kpi.js | 新規 | なし |
| 追記のみ | 000_infra/003_contracts.js | 追記(PipelineDTO) | なし(既存 DTO に影響なし) |
| 追記のみ | 200_data/202_repository.js | 追記(PipelineRepository) | なし(既存 Repository に影響なし) |
| 追記のみ | 100_config/101_sys_config.js | 追記(KPI_SAAS スキーマ + confSheet 登録) | 低(DDL setupAllSchemas 再実行で反映) |
| 追記のみ | 000_infra/002_constants.js | 追記(メニュー項目 1 件) | なし |
| 読み取り | 21_bud_pipeline | 読み取り専用アクセス | なし |
| 新規出力 | 69_kpi_saas | 新規シート生成 | なし(既存シートと衝突なし) |
既存機能への破壊的変更は一切なし。400_domain/406_rpa_pipeline.js(RPA 自動起票)、600_report/602_datamart_main.js(財務 3 表生成)、609_datamart_kpi.js(93_kpi_dashboard)はいずれも変更対象外。
注意事項
契約形態フィルタ値のハードコード禁止: Step 3 実装時に MCP で21_bud_pipelineの契約形態列実セル値(データ入力規則のドロップダウン候補)を確認し、継続契約を示す値(推定:継続(農耕))を定数CONTINUOUS_CONTRACT_TYPEに切り出す。000_infra/002_constants.js:77のSHEET_DEFAULTSにあるデフォルト値"スポット(狩猟)"から対称的に類推するだけで書かず、実データで裏取りする。Utils.addMonthsの引数型:"YYYY-MM"文字列のみを受け付け、Date型や数値を渡すと空文字""が返る。継続月数はシートによっては文字列で格納されるため、必ずUtils.parseAmt(dto['継続月数'])で数値化してからaddMonthsに渡すこと。69_kpi_saas書き込みは冪等性を保証: 既存データ行をclearContent()してから全件setValuesで一括書き込み(202_repository.js:38-59のwriteDtosToSheet_パターンに準拠)。同一関数を複数回実行しても結果が変わらないこと。- DDL 再実行への耐性:
setupAllSchemas実行で69_kpi_saasシートが再生成されると集計結果は消えるが、buildSaaSMetrics()を再実行すれば21_bud_pipelineの元データから完全復元できる。DDL スキーマは最小限の列定義(ヘッダー行のみ)に留め、数式・条件付き書式はbuildSaaSMetrics()内で動的に付与する。 有効フラグ列の有無:21_bud_pipelineの DDL ヘッダー(L883)には"有効フラグ"が含まれるため存在前提で実装する。ただし MCP 確認時に実シートで列が欠落していたら Step 3 のフィルタ 2-a をスキップする(if (col['有効フラグ'] !== undefined)で防御的にガード)。- 取引先名の表記揺れ: 同一顧客を
"株式会社 XX"と"XX"で別レコードとして扱うと Churn/Expansion が誤検知される。本仕様は取引先名列をそのまま顧客識別キーとし、正規化は ユーザー側のデータ整備責務 とする(Utils.normalizePartnerNameの適用は将来課題)。 - 新規 datamart ファイル番号:
600_report/の既存ファイルは 601-609。610_datamart_saas_kpi.jsが次の採番となる(609_datamart_kpi.jsの次)。
エッジケース
| 条件 | 処理 | 理由 |
|---|---|---|
計上開始年月 が空欄・不正形式 | レコードをスキップ | Utils.parseDateToYm が "" を返すため月ループ不可 |
継続月数 が 0・空欄・負値 | レコードをスキップ | ライフサイクルが 0 ヶ月以下は月次展開不能 |
継続月額(MRR) が 0・空欄 | レコードをスキップ(要設計判断) | 0 MRR は NRR/Churn 計算を歪める。Human-in-the-Loop で除外 or New 計上のどちらにするか PO が確定する |
取引先名 が空欄 | New/Expansion/Churn 算出から除外(ただし ARR/MRR 合計には含める) | 顧客同一性の判定ができないため前月比較不可 |
| 集計期間の初月(前月データなし) | Expansion = 0 / Churn = 0 / 当月全 MRR を New MRR として計上 | 前月マップが空のため差分比較不可。初月扱いとする規約 |
| 同一取引先の複数契約 | 取引先名 でグルーピングし MRR を合算してから前月と比較 | New/Expansion/Churn の算出単位は 契約ではなく顧客 |
継続月数 が整数でなく文字列で格納されている | Utils.parseAmt で数値化 | Sheets は文字列/数値を混在させる場合があるため防御的にパース |
有効フラグ 列が 21_bud_pipeline に存在しない | スキップ処理を省略し全件を対象 | DDL 差分でスキーマがロールバックされた場合を想定した防御的実装 |
計上開始年月 が未来月 | そのまま月ループの開始点とする | 予算・パイプラインの性質上、将来月の予測契約も集計対象に含める(経営計画ビュー) |
継続月数 が異常に大きい値(例: 120 ヶ月 = 10 年) | 上限なしで全月展開 | 既存 BUD_PIPE バリデーション(継続月数: range 0-120)と整合。10 年超は入力時点で弾かれる |
契約形態 が空欄 | スキップ(!== CONTINUOUS_CONTRACT_TYPE 判定で自動除外) | スポット契約と判定不可なレコードは ARR/MRR 集計対象外 |
実データ検証
Step 3 実装着手前に MCP で以下の項目を必ず確認する。コード値(DDL スキーマや SHEET_DEFAULTS)と実データに乖離があった場合、実データを優先する(失敗パターン #18-#20 参照)。
1. 21_bud_pipeline のヘッダー行(MCP read_sheet でヘッダー 1 行のみ読む)
期待値(100_config/101_sys_config.js:883):
有効フラグ | 管理ID | PJ・案件名 | 契約形態 | 売上科目 | 確度(ヨミ) |
計上開始年月 | スポット売上・初期費用 | 継続月額(MRR) | 継続月数 |
取引先名 | 決済手段 | CF計上 | 入金ラグ(月) | 入金日 | 休日調整 |
組織名 | 起票ターゲット月 | 最終起票年月日 | 備考
確認観点: 列名の一致(全角半角・括弧種別)、列の存在(特に 有効フラグ / 契約形態 / 計上開始年月 / 継続月数 / 継続月額(MRR) / 取引先名)。
2. 契約形態 列のドロップダウン実値(MCP get_data_validations or 実データの値集合)
000_infra/002_constants.js:77 の SHEET_DEFAULTS には '契約形態': 'スポット(狩猟)' とあるため、対称的に継続契約は '継続(農耕)' と推定される(タブ論理名 予算_売上パイプライン(狩猟/農耕) の記述とも整合)。ただし 推定のまま書かず、MCP で以下を確認する。
- データ入力規則(ドロップダウン候補リスト)で「継続」を含む値
- 実データ行に登録されている
契約形態セルの値集合(UNIQUE 値のスキャン)
確定した値を定数 CONTINUOUS_CONTRACT_TYPE としてコード冒頭に切り出し、if (契約形態 !== CONTINUOUS_CONTRACT_TYPE) continue; でフィルタする。
3. 有効フラグ 列の有無
DDL には存在するが、DDL 再適用前の古い環境では未反映の可能性がある。MCP で実シートの 1 行目を確認し、有効フラグ 列が無ければ Step 3 のスキップ処理を省略する。
4. 継続月数 = 0 または空欄のレコード件数
Utils.parseAmt(継続月数) <= 0 でスキップ対象となるレコードの規模感を把握する。多数存在する場合は、PO に対して「スキップして集計から除外する」方針の追認を得る。
5. 継続月額(MRR) = 0 のレコード件数
同上。0 MRR レコードの扱い(除外 vs New 計上)はエッジケーステーブルで要設計判断項目として PO にエスカレーションする。
関連ドキュメント
- E.5.5 MAS-003 KPI ダッシュボード — 93_kpi_dashboard の実装パターン(Label→Row マップ構築方式)。本仕様の
69_kpi_saas書き込みロジックは別途 write-all 方式を採用するが、シート再生成の冪等性方針は共通。 - E.5.6 MAS-008 Cash Runway —
03_sys_paramsパラメータ化の参考(本仕様では閾値パラメータは導入しないが、将来 Churn Rate 等の KPI 閾値を導入する場合に参考) - 4.3.4 パイプライン売上 RPA —
21_bud_pipelineの RPA 自動起票仕様(本仕様と読み取り対象シートが同一) - 4.6.2 パイプラインデータ取込 —
21_bud_pipelineのデータソース仕様 - CLAUDE.md § データアクセス — 「列参照はヘッダー名ベース」「有効フラグ=FALSE 行スキップ」の規約
- C.1 TODO_future.md — MAS-028 の案件定義(§3.2.1 プロダクト事業メトリクス)
人間が検討すべき事項
- MRR 計上基準の確認: 本仕様は 契約基準(
計上開始年月月から MRR 計上開始)を採用。入金基準ではない。SaaS 業界の SSOT(Stripe / ChartMogul 等)は契約基準が標準だが、事業実態と会計処理の整合性を PO が確認すること。契約開始後の代金回収が数ヶ月遅れる取引形態の場合、MRR と CF マートのタイミングが乖離する。 - 年額契約の月額換算の前提:
21_bud_pipelineの継続月額(MRR)列には、年額契約も含め 月額換算済みの値が入力されていることを前提とする。年額一括請求を受ける顧客について、継続月額(MRR) = 年額 / 12でユーザー側が事前計算することを運用ルール化する必要がある。このデータ整備はユーザー側責務である旨を運用ドキュメントに明記すること。 - 顧客識別キーの表記揺れ: 本仕様は
取引先名列をそのまま顧客識別キーとする。12_mst_partnerの略称/取引先名_正式との突き合わせは行わない。表記揺れ(例:"株式会社 XX"vs"XX")が Churn/Expansion の誤検知を招く場合、将来的にUtils.normalizePartnerNameを適用する拡張を検討する。初期導入時は入力時のマスタ参照強制(MAS-139)の完了を前提とする。 - 0 MRR レコードの扱い(要設計判断): エッジケーステーブルの該当行を参照。
継続月額(MRR) = 0のレコードについて以下 2 案を PO が確定:- 案 A(除外): 集計対象外。Free プラン顧客は Customer Count にも含めない
- 案 B(New 計上): MRR = 0 のまま顧客数だけカウント。無料トライアル → 有料転換を後日 Expansion として扱う
- 算出結果の定期検証: Expansion MRR / Churn MRR の数値が事業の肌感覚と乖離する場合、
21_bud_pipelineの元データと突き合わせて検証する運用を整備する。特に月末の契約更新・ダウングレード操作が計上開始年月/継続月数にどう反映されているかを実査する。 - 月次スナップショット保存の要否: 本仕様は
21_bud_pipelineを常時 re-scan する揮発型集計。過去月のMRR/ARRは常に現在のパイプラインデータから逆算されるため、過去の契約データを後から編集すると遡及的に変わる。会計監査観点で 月次スナップショット保存(MAS-002 の拡張)が必要か PO が判断する。 - 前提案件 MAS-081 との関係: 請負契約の売上計上ロジック(MAS-081)が導入されると、契約基準 MRR と請負基準(検収基準)売上が乖離する可能性がある。MAS-081 実装後に本仕様の計上基準を再確認する。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-028「ARR/MRR トラッキング」を実装してください。
大規模案件のため Step 単位で実装する。実行する Step を冒頭に明記すること。
## 実行前タスク(拡張思考あり)
- `000_infra/003_contracts.js` を Read し、既存 @typedef の末尾行番号を特定する(PipelineDTO 追記位置)
- `200_data/202_repository.js` を Read し、PartnerRepository(AccountRepository の後の最後の Repository)の末尾行番号を特定する(PipelineRepository 追記位置)
- `100_config/101_sys_config.js` を Read し、setupAllSchemas 内の schemas オブジェクトの記述位置、および confSheet.appendRow の登録パターン(既存 KPI_DASH 周辺)を確認する
- `000_infra/002_constants.js` を Read し、MENU_DEFINITION の「📋 サイドバー: 📊 マート更新」セクションの行番号を特定する(メニュー項目追加位置)
- **MCP で `21_bud_pipeline` の `契約形態` 列の実際のセル値を確認**し、継続契約を表す文字列をフィルタ値として特定する。SHEET_DEFAULTS の `'スポット(狩猟)'` から類推するだけでは不十分。実データで裏取りすること
- MCP で `21_bud_pipeline` のヘッダー行を読み、`有効フラグ` / `契約形態` / `計上開始年月` / `継続月数` / `継続月額(MRR)` / `取引先名` の各列名が DDL スキーマと一致することを確認する
## 修正対象ファイル(既存 4 件への追記・新規 1 件)
1. `000_infra/003_contracts.js` — PipelineDTO @typedef を追記
2. `200_data/202_repository.js` — PipelineRepository(findAll のみ)を追記
3. `600_report/610_datamart_saas_kpi.js` — 新規作成。buildSaaSMetrics() を実装
4. `100_config/101_sys_config.js` — KPI_SAAS スキーマ定義と confSheet 登録を追加
5. `000_infra/002_constants.js` — MENU_DEFINITION にメニュー項目を追加
## 実装内容(Step 別)
### Step 1: PipelineDTO 追記(003_contracts.js)
- BudgetDTO(§2 末尾)の直後、PartnerDTO(§3 前)の前に追記する
- プロパティ: 有効フラグ / 管理ID(PIP) / PJ・案件名 / 契約形態 / 売上科目 / 確度(ヨミ) / 計上開始年月 / スポット売上・初期費用 / 継続月額(MRR) / 継続月数 / 取引先名 / 組織名 / 備考
- 記述スタイルは既存 @typedef(OrderDTO / BudgetDTO)と完全一致させる
### Step 2: PipelineRepository 追記(202_repository.js)
- ファイル末尾(最後の Repository の直後)に追記する
- _getSheet(): Utils.getSheetByKey('BUD_PIPE', '21_bud_pipeline') を使用
- findAll(): readSheetAsDtos_(PipelineRepository._getSheet()) を返すのみ
- save() / append() は実装しない(読み取り専用)
### Step 3: buildSaaSMetrics() 実装(610_datamart_saas_kpi.js 新規作成)
1. ファイル冒頭に定数を切り出す:
```js
var CONTINUOUS_CONTRACT_TYPE = '継続(農耕)'; // MCP 確認値で上書き
```
2. PipelineRepository.findAll() で { headers, dtos } を取得
3. フィルタリング(順序厳守)
- 有効フラグ === false or "FALSE" → スキップ(列存在時のみ)
- 契約形態 !== CONTINUOUS_CONTRACT_TYPE → スキップ
- 計上開始年月 が空 → スキップ
- Utils.parseAmt(継続月数) <= 0 → スキップ
- Utils.parseAmt(継続月額(MRR)) === 0 → スキップ(0MRR)
4. 月ループで中間マップ構築
- startYm = Utils.parseDateToYm(dto['計上開始年月'])
- durMonths = Utils.parseAmt(dto['継続月数'])
- endYm = Utils.addMonths(startYm, durMonths - 1)
- customerMrrByYm[ym][取引先名] += mrr(加算、複数契約合算)
5. ymKeys を昇順ソートして時系列スキャン
- 前月マップ prev と当月マップ curr を比較:
- 前月になく当月にある取引先 → New += mrr
- 両月にあり curr > prev → Expansion += (curr - prev)
- 両月にあり curr < prev → Churn += (prev - curr) // 正値として積算
- 前月にあり当月にない取引先 → Churn += prev
- 初月(prev が空): New = 全MRR / Expansion = 0 / Churn = 0
- MRR = curr の全顧客合計
- ARR = MRR × 12
- Net New MRR = New + Expansion - Churn
6. 69_kpi_saas シートに書き込む
- 既存データ行を clearContent で全クリア
- ヘッダー行(対象年月 / MRR / ARR / New MRR / Expansion MRR / Churn MRR / Net New MRR / 顧客数)
- データ行を setValues で一括書き込み(全件・冪等性)
- sheet.setFrozenRows(1) + フィルタ設定
### Step 4: スキーマ定義・メニュー追加(101_sys_config.js + 002_constants.js)
(a) 101_sys_config.js:
- setupAllSchemas 内の `schemas` オブジェクト(L826 以降)に以下を追加:
```js
'KPI_SAAS': { headers: ["対象年月","MRR","ARR","New MRR","Expansion MRR","Churn MRR","Net New MRR","顧客数"], color: "#674ea7" },
```
- confSheet.appendRow ガード(L789 前後のパターン)に以下を追加:
```js
if (!existKeys.includes('KPI_SAAS')) confSheet.appendRow(['KPI_SAAS', '', '69_kpi_saas', 'SaaS KPI(ARR/MRR トラッキング)']);
```
(b) 002_constants.js:
- MENU_DEFINITION の「📋 サイドバー: 📊 マート更新」items 配列(L229-239 付近)、既存「📊 KPIダッシュボード再描画」項目(L236)の直後に以下を追加:
```js
{ label: '📈 SaaS KPI (ARR/MRR) 更新', funcName: 'buildSaaSMetrics', description: '21_bud_pipeline から MRR/ARR/New/Expansion/Churn を集計し 69_kpi_saas に出力' },
```
## 制約
- 21_bud_pipeline への書き込み禁止(読み取り専用)
- 既存 Repository(OrderRepository / InvoiceRepository 等)のインターフェースを変更しない
- 既存メニュー項目を削除・変更しない
- 69_kpi_saas への書き込みは毎回クリア → 全件書き込みで冪等性を保証する
- 契約形態フィルタ値は MCP 確認値を定数に切り出す(ハードコード禁止・SHEET_DEFAULTS から類推もしない)
- setupAllSchemas / onOpen への追記で既存エントリを削除・変更しない
- 列参照はヘッダー名ベース(indexOf)。列番号ハードコード禁止(CLAUDE.md § データアクセス)
## エッジケース
(仕様書の「エッジケース」セクションのテーブルを転記)
## 実データ検証
(仕様書の「実データ検証」セクションの手順を転記)
## 動作確認
1. `npm run push:dev` で開発環境にデプロイ
2. GAS エディタで `setupAllSchemas()` を実行 → `69_kpi_saas` タブがヘッダー付きで新規作成されることを確認
3. GAS エディタで `buildSaaSMetrics()` を直接実行 → `69_kpi_saas` シートが月次ヘッダーと各 KPI で埋まることを確認
4. 同一関数を再実行 → 結果が完全一致すること(**冪等性テスト**)
5. `21_bud_pipeline` に `継続月数 = 0` のテストレコードを追加 → `buildSaaSMetrics` 再実行 → そのレコードが 69_kpi_saas に反映されない(スキップされる)ことを確認
6. `21_bud_pipeline` に新規継続契約を追加 → 該当月の `New MRR` に金額が加算されることを確認
7. 既存契約の `継続月額(MRR)` を増額 → 増額月の `Expansion MRR` に差分が加算されることを確認
8. 既存契約の `継続月数` を減らして契約終了月を前倒し → 終了直後の月の `Churn MRR` に前月 MRR 全額が計上されることを確認
9. スプレッドシートのサイドバー「📊 マート更新」メニューから「📈 SaaS KPI (ARR/MRR) 更新」を選択 → 同じ結果が得られることを確認
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(Read / MCP) | あり | 追記位置・フィルタ値の確定 |
| Step 1-2(DTO/Repository 追記) | なし | 仕様で完全定義済み |
| Step 3(buildSaaSMetrics 実装) | あり | SaaS KPI 計算ロジック(月ループ / 前月比較)の設計判断 |
| Step 4(スキーマ + メニュー追記) | なし | 挿入位置は実行前タスクで確定済み |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
Step 1: PipelineDTO 追記(003_contracts.js) | Claude Haiku | 既存 @typedef の横展開のみ、判断要素なし |
Step 2: PipelineRepository 追記(202_repository.js) | Claude Haiku | 既存 AccountRepository パターンの横展開のみ、判断要素なし |
Step 3: buildSaaSMetrics 実装(610_datamart_saas_kpi.js) | Claude Sonnet | SaaS KPI 計算ロジック(New/Expansion/Churn 分類、初月扱い、顧客単位グルーピング)の設計判断が必要 |
Step 4: スキーマ + メニュー追記(101_sys_config.js + 002_constants.js) | Claude Sonnet | 2 ファイル横断・挿入位置の特定と既存コードとの整合確認が必要 |
| 実行前タスク(Read / MCP 実データ確認) | Claude Sonnet | コード値と実データの乖離判断、契約形態フィルタ値の確定 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-20 | 初版作成(MAS-028 ARR/MRR トラッキング仕様書) |
仕様書作成プロンプト(再現性・監査性のため必ず記録)
展開して表示
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**: Phase 1(設計)では拡張思考をフル活用し、ファイル名形式・エッジケース一覧・Step分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全に確定させる。Phase 2(清書)の各Step内では拡張思考を最小限に抑え、Phase 1で確定済みの内容の書き下しに徹する。出力途中で再考しない。
2. **テキスト報告の禁止**: 「〜を作成します」等のtextのみでtool_useなしにturnを終了しない。説明は1文以内。直ちにtoolを呼ぶ。
3. **4-5分割のWrite/Edit実行**: Step 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で全固有名詞(列名・関数名・メニュー名・行番号)を確定させてから Phase 2 に進む。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 F-28「ARR/MRR トラッキング」の開発仕様書を作成してください。
作成後は `docs/_config.json` の `nav` 配列の適切なセクションに必ず追記してください。
---
## Phase 1: 案件情報の収集(テキスト報告禁止。即座にツール実行)
### 1-A: 案件定義の読み込み
`docs/_internal/TODO_future.md` を検索し、F-28 の案件名・カテゴリ・Phase・優先度・概要・人間が検討すべき事項を取得する。
### 1-B: 関連コードの調査(Grep で発見 → 必ずReadで裏取り)
仕様書に書く固有名詞(変数名・関数名・シート名・メニュー名・行番号)は**全てReadで確認した実在する文字列のみ**引用する。Grepの部分ヒットや記憶からの類推で書かない(失敗パターン #18-#20)。
1. **`000_infra/003_contracts.js`** を Read する。
- 既存 `@typedef` の記述スタイル(`OrderDTO`/`InvoiceDTO` 等)と末尾行番号を確認する。`PipelineDTO` 追記時のフォーマット・追記位置の根拠にする。
- `Contracts.toDtoList` の戻り値型(`{ headers, rows }` vs `{ headers, dtos }`)を確認する。
2. **`200_data/202_repository.js`** を Read する。
- `readSheetAsDtos_` / `appendDtosToSheet_` / `writeDtosToSheet_` の引数・戻り値の型を確認する。
- 既存 Repository(`OrderRepository` 等)の `_getSheet()` / `findAll()` / `save()` / `append()` の実装パターンを確認し、`PipelineRepository` の雛形として使う。末尾 Repository(`AccountRepository`)の末尾行番号を特定する(追記位置)。
3. **`000_infra/004_utils.js`** を Read する。
- `Utils.parseDateToYm` の対応フォーマット(`Date`型 / `"YYYY-MM"` / `"YYYY/MM"` / `"YYYY年MM月"`)を確認する。
- `Utils.addMonths(ymStr, months)` のシグネチャと戻り値型を確認する。契約終了月は `addMonths(計上開始年月, 継続月数 - 1)` で計算する想定。
- `Utils.parseAmt(val)` の仕様を確認する(`継続月数` が文字列で入っている場合の数値変換に使用)。
4. **`100_config/101_sys_config.js`** を Read する。
- `setupAllSchemas` 内で `600_report/` 系シート(例: `69_kpi_saas` と近い番号帯)のスキーマ定義箇所と記述フォーマットを確認し、新規スキーマの追加位置・書き方を特定する。
- `onOpen()` の `createMenu` の実在するメニュー名・階層を確認する。`buildSaaSMetrics()` を追加する親メニュー名を**そのままの文字列で**引用できるようにする。
5. **`600_report/` の既存ファイル**(`601_datamart_ingest.js` または `608_datamart_render.js` など)を1件 Read する。
- `69_kpi_saas` への書き込みで使う「既存データクリア→全件一括書き込み」パターンの実装例を確認する。
### 1-C: MCP での実データ確認(DDLコード値 vs 実データの乖離チェック)
以下を MCP で確認し、コード値の乖離がないか検証する。**確認前にフィルタ値や列名をコードに書かない。**
- **`21_bud_pipeline` シートのヘッダー行**: `契約形態` / `計上開始年月` / `継続月数` / `継続月額(MRR)` / `取引先名` の列名が実シートと一致するか確認。`有効フラグ` 列が存在するかも確認する(存在しない場合はスキップ処理不要)。
- **`契約形態` の実際のドロップダウン値**: `000_infra/002_constants.js` の `SHEET_DEFAULTS` では `21_bud_pipeline` のデフォルト値が `スポット(狩猟)` と定義されている。「継続」契約を表すセルの実際の格納値(`継続(農耕)` 等)を MCP で確認する。この値がフィルタキーになるため**推測で書かない**。
- **`継続月数` が 0 またはブランクのレコード数**: 除外対象の規模感を把握する。
### 1-D: テンプレートの読み込み
- `docs/_internal/dev_spec_prompt_template.md` の「Phase 2: 仕様書の作成」セクション構成を確認し、出力仕様書が全必須セクションを含むことを担保する。
- `docs/dev/` 内の既存 FP&A 系仕様書を1件 Read してフォーマットを把握する(例: `dev_mas-001_variance_analysis.md`)。
---
## Phase 2: 仕様書の分割作成
出力先: `docs/dev/dev_mas-028_arr_mrr_tracking.md`
**絶対に1回のツール呼び出しで全内容を出力しない。以下の5 Stepに分割して実行すること。**
### Step 2-1: 骨格の作成(Write・~20行)
以下の見出しのみ含む骨格ファイルを Write で作成する(本文は空で可):
# F-28: ARR/MRR トラッキング
## 概要
## 目的
## 現在のコード
## 修正方針
## 影響範囲
## 注意事項
## エッジケース
## 実データ検証
## 関連ドキュメント
## 人間が検討すべき事項
## 実装プロンプト(Claude Code 用)
## 推奨実行モデル
## 変更履歴
## 仕様書作成プロンプト(再現性・監査性のため必ず記録)
### Step 2-2: 前半セクションの追記(Edit または Bash heredoc・~300行)
Phase 1 で確定した内容を書き下す。出力中に再考しない。以下のセクションを順に記述する。
- **概要テーブル**: 案件ID=F-28, カテゴリ, Phase, 優先度, 所要時間(推定), 対象ファイル(`000_infra/003_contracts.js` / `200_data/202_repository.js` / `600_report/610_datamart_saas_kpi.js` / `100_config/101_sys_config.js`・新規1件含む), 前提案件(Phase 1で特定したもの、なければ「なし」)。
- **目的**: `21_bud_pipeline` の継続契約データから月次 ARR/MRR・新規(New)/拡大(Expansion)/解約(Churn) KPI を自動集計し `69_kpi_saas` シートに出力する機能を追加する。SaaSビジネスの健全性指標をスプレッドシート上でトラッキング可能にする。
- **現在のコード**: 新規追加のため既存ロジックなし。Phase 1-B で確認した `202_repository.js` の既存 Repository パターン(該当行番号)を参考実装として提示する。
- **修正方針**: 以下の4 Step 構成で記述する(各 Step の実装内容・対象ファイル・追記位置の行番号を明示すること):
- **Step 1**: `000_infra/003_contracts.js` に `PipelineDTO` `@typedef` を追記。既存 `@typedef`(`BudgetDTO` 等)の末尾直後に、Phase 1-B で確認した記述スタイルで追記。最低限のプロパティ(`発注ID` の代わりに `PIP_` プレフィックスの ID 列・`契約形態`・`計上開始年月`・`継続月数`・`継続月額(MRR)`・`取引先名`・`有効フラグ`)を定義する。
- **Step 2**: `200_data/202_repository.js` に `PipelineRepository` を追記(末尾 `AccountRepository` の直後)。`_getSheet()` は `Utils.getSheetByKey(null, '21_bud_pipeline')` を使用。`findAll()` は `readSheetAsDtos_` を呼ぶのみ。書き込みは行わないため `save()` / `append()` は実装不要。
- **Step 3**: `600_report/610_datamart_saas_kpi.js` を新規作成し `buildSaaSMetrics()` を実装。
- `PipelineRepository.findAll()` で全データ取得 → `有効フラグ=FALSE` をスキップ(列が存在する場合)→ `契約形態`(Phase 1-C で MCP 確認した実際の値)でフィルタ → `継続月数 <= 0` / `計上開始年月` が空のレコードをスキップ
- 各契約の月ループ: `Utils.parseDateToYm` で `計上開始年月` を正規化 → `Utils.addMonths(startYm, Utils.parseAmt(継続月数) - 1)` で終了月を算出 → 開始月〜終了月を1ヶ月ずつ `addMonths` でインクリメントし「顧客×月別 MRR」中間テーブル(`Map<ym, Map<顧客名, MRR>>` 形式)を構築
- 時系列スキャン: 月を昇順でソート → 前月マップと当月マップを比較し New / Expansion / Churn を算出(同一取引先の複数契約は MRR を合算してから比較)
- 集計結果を `69_kpi_saas` シートに書き込む(Phase 1-B で確認した既存 datamart の書き込みパターンを踏襲。毎回既存データクリア→全件一括 `setValues`。冪等性保証)
- **Step 4**: `100_config/101_sys_config.js` の `setupAllSchemas` に `69_kpi_saas` スキーマ定義を追加(Phase 1-B で確認した実在する記述位置・フォーマット通りに書く)。`onOpen()` の実在するメニュー項目(Phase 1-B で確認した文字列)に `buildSaaSMetrics()` 呼び出しを追加する。
- **影響範囲**: 変更ファイル3件・新規ファイル1件。`21_bud_pipeline` は読み取り専用アクセスのみ。既存 Repository / DTO に破壊的変更なし。
- **注意事項**(番号付きリスト):
1. `契約形態` フィルタ値は Phase 1-C で MCP 確認した実際のセル値を定数(`var CONTINUOUS_CONTRACT_TYPE = '...'`)に切り出して使う。`SHEET_DEFAULTS` のデフォルト値 `スポット(狩猟)` から類推しない。
2. `Utils.addMonths` は `"YYYY-MM"` 形式の文字列を受け取り `"YYYY-MM"` 文字列を返す。`継続月数` は `Utils.parseAmt` で数値化してから渡すこと。
3. `69_kpi_saas` への書き込みは、Phase 1-B で確認した既存 datamart の書き込みパターンに合わせる。
4. `setupAllSchemas` 実行でシートが再生成されると計算結果は消えるが、`buildSaaSMetrics()` 再実行で復元可能。DDLスキーマは最小限の列定義に留める。
5. `有効フラグ` 列が `21_bud_pipeline` に存在しない場合(Phase 1-C で確認)はスキップ処理を省略する。
### Step 2-3a: エッジケース〜人間検討事項の追記(Edit または Bash・~200行)
- **エッジケーステーブル**(テーブル形式: 条件 | 処理 | 理由):
| 条件 | 処理 | 理由 |
|------|------|------|
| `計上開始年月` が空欄・不正形式 | レコードをスキップ | `parseDateToYm` が `""` を返し月ループ不可 |
| `継続月数` が 0・空欄・負値 | レコードをスキップ | ライフサイクルが0ヶ月以下は計算不能 |
| `継続月額(MRR)` が 0・空欄 | レコードをスキップ | 0MRRは NRR/Churn 計算を歪める。要設計判断: 0MRR を New として計上するか除外するかをHuman-in-the-Loopで確定する |
| `取引先名` が空欄 | New/Expansion/Churn 算出から除外(ただし ARR/MRR 合計には含める) | 顧客同一性の判定不能 |
| 集計期間の初月(前月データなし) | Expansion=0, Churn=0, 当月全MRRをNew MRRとして計上 | 前月比較不可 |
| 同一取引先の複数契約 | 取引先名でグルーピングしてMRRを合算してから前月と比較 | New/Expansion/Churnの単位は取引先(顧客)ベース |
| `継続月数` が整数でなく文字列で格納されている場合 | `Utils.parseAmt` で数値化 | Sheets は文字列/数値を混在させる場合がある |
- **実データ検証セクション**: Phase 1-C で確認すべき項目(`契約形態` の実際のドロップダウン値・ヘッダー列名の実態・`有効フラグ` 列の有無・`継続月数=0` のレコード数)を記載する。
- **関連ドキュメント**: Phase 1 で参照した既存仕様書・`CLAUDE.md` の関連セクションをリンク。
- **人間が検討すべき事項**(`TODO_future.md` 転記 + Phase 1 調査で判明した追加事項):
1. **MRR 計上基準の確認**: 本仕様は「契約基準(`計上開始年月` 月から計上)」を採用。入金基準ではない。事業実態との整合性を PO が確認すること。
2. **年額契約の月額換算**: `21_bud_pipeline` の `継続月額(MRR)` には年額契約も含め月額換算済みの値が入力されていることを前提とする。このデータ整備はユーザー側の責務であることを明記する。
3. **顧客識別キーの表記揺れ**: 本仕様は `取引先名` を顧客識別キーとする。表記揺れがある場合は `12_mst_partner` の略称との突き合わせが必要(将来課題)。
4. **0MRRレコードの扱い**: エッジケーステーブルの当該行を参照。除外 or New 計上のどちらにするかを PO が判断すること。
5. **算出結果の定期検証**: Expansion/Churn の値が事業の肌感覚と乖離する場合、`21_bud_pipeline` の元データと突き合わせて検証すること。
### Step 2-3b: 実装プロンプト〜変更履歴の追記(Edit または Bash・~250行)
実装プロンプトは**バッククォートで囲まず、行頭4スペースインデントで記述**すること。以下の内容を含む実装プロンプトを作成する:
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-28「ARR/MRR トラッキング」を実装してください。
大規模案件のため Step 単位で実装する。実行する Step を冒頭に明記すること。
## 実行前タスク
- `000_infra/003_contracts.js` を Read し、既存 @typedef の末尾行番号を特定する(PipelineDTO 追記位置)
- `200_data/202_repository.js` を Read し、AccountRepository の末尾行番号を特定する(PipelineRepository 追記位置)
- `100_config/101_sys_config.js` を Read し、setupAllSchemas 内の 600_report 系スキーマ定義箇所と onOpen() のメニュー構造を確認する
- MCP で `21_bud_pipeline` の `契約形態` 列の実際のセル値を確認し、継続契約を表す文字列をフィルタ値として特定する
## 修正対象ファイル(既存3件への追記・新規1件)
1. `000_infra/003_contracts.js` — PipelineDTO @typedef を追記
2. `200_data/202_repository.js` — PipelineRepository(findAll のみ)を追記
3. `600_report/610_datamart_saas_kpi.js` — 新規作成。buildSaaSMetrics() を実装
4. `100_config/101_sys_config.js` — 69_kpi_saas スキーマ定義とメニュー追加
## 実装内容(Step別)
### Step 1: PipelineDTO 追記(003_contracts.js)
- 既存 @typedef(BudgetDTO 等)の末尾直後に追記する
- プロパティ: 有効フラグ / パイプラインID(PIP) / 取引先名 / 契約形態 / 計上開始年月 / 継続月数 / 継続月額(MRR) / 売上科目 / 確度(ヨミ) / 取引先名 / PJ名 / 組織名
- 記述スタイルは既存 @typedef(OrderDTO 等)と完全に一致させる
### Step 2: PipelineRepository 追記(202_repository.js)
- AccountRepository の末尾直後に追記する
- _getSheet(): Utils.getSheetByKey(null, '21_bud_pipeline') を使用
- findAll(): readSheetAsDtos_(_getSheet()) を返すのみ
- save() / append() は実装しない(読み取り専用)
### Step 3: buildSaaSMetrics() 実装(610_datamart_saas_kpi.js 新規作成)
1. PipelineRepository.findAll() で全データ取得
2. 有効フラグ=FALSE のレコードをスキップ(有効フラグ列が存在する場合)
3. 契約形態が継続(MCP確認値を定数 CONTINUOUS_CONTRACT_TYPE に切り出す)以外をスキップ
4. 計上開始年月が空・継続月数<=0・継続月額(MRR)が空のレコードをスキップ
5. 各契約のライフサイクルを月ループして「ym → 顧客名 → MRR合計」の中間Map構築
- startYm = Utils.parseDateToYm(dto['計上開始年月'])
- endYm = Utils.addMonths(startYm, Utils.parseAmt(dto['継続月数']) - 1)
- 同一取引先の複数契約は MRR を合算する
6. ymKeys を昇順ソートして時系列スキャン
- 前月マップと当月マップを比較し New / Expansion / Churn / MRR / ARR を算出
- 初月は前月マップ空のため Expansion=0, Churn=0, 全MRR=New MRR
7. 69_kpi_saas シートに書き込む(既存データクリア → ヘッダー行 + データ行を setValues で一括書き込み)
### Step 4: スキーマ定義・メニュー追加(101_sys_config.js)
- setupAllSchemas の実在する記述位置に 69_kpi_saas のスキーマを追加(Read で確認した位置・フォーマット通り)
- onOpen() の実在するメニュー名(Read で確認した文字列)に buildSaaSMetrics の呼び出しを追加
## 制約
- 21_bud_pipeline への書き込み禁止(読み取り専用)
- 既存 Repository(OrderRepository / InvoiceRepository 等)のインターフェースを変更しない
- 69_kpi_saas への書き込みは毎回クリア→全件書き込みで冪等性を保証する
- 契約形態フィルタ値は MCP 確認値を定数に切り出す(ハードコード禁止)
- setupAllSchemas / onOpen への追記で既存エントリを削除・変更しない
## エッジケース
(仕様書の「エッジケース」セクションのテーブルを転記)
## 実データ検証
(仕様書の「実データ検証」セクションを転記)
## 動作確認
1. npm run push:dev で開発環境にデプロイ
2. GASエディタで buildSaaSMetrics() を直接実行
3. 69_kpi_saas シートが生成され、月次ヘッダーと ARR/MRR/New/Expansion/Churn 列に値が入ることを確認
4. 同一関数を再実行し、結果が変わらないこと(冪等性)を確認
5. 21_bud_pipeline に継続月数=0 のテストレコードを追加し、69_kpi_saas に反映されないことを確認
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(Read / MCP) | あり | 追記位置・フィルタ値の確定 |
| Step 1-2(DTO/Repository 追記) | なし | 仕様で完全定義済み |
| Step 3(buildSaaSMetrics 実装) | あり | 計算ロジックの設計判断 |
| Step 4(スキーマ+メニュー追記) | なし | 挿入位置は実行前タスクで確定済み |
続いて **推奨実行モデル**(テーブル: 工程 | 推奨モデル | 理由)を記述する:
- Step 1-2(PipelineDTO / PipelineRepository 追記): **Claude Haiku** — 既存パターンの横展開のみ、判断要素なし
- Step 3(buildSaaSMetrics 実装): **Claude Sonnet** — SaaS KPI 計算ロジックの設計判断が必要
- Step 4(スキーマ+メニュー追記): **Claude Sonnet** — 挿入位置の特定と既存コードとの整合確認が必要
続いて **変更履歴**(テーブル: 日付 | 変更内容)を記述する:
- 作成日(2026-04-20)と「初版作成」を記載。
### Step 2-4: 仕様書作成プロンプトの記録(Edit または Bash・独立Step)
末尾の `## 仕様書作成プロンプト(再現性・監査性のため必ず記録)` セクションに `<details><summary>展開して表示</summary>` ブロックを設け、この `<instruction>` タグの全文をそのまま記録する。
---
## Phase 3: 登録と記録
- **`docs/_config.json`**: 追記前に `git pull origin main` で最新版を取得する。`nav` 配列の「§E.5 FP&A・レポーティング」セクションに以下を追加する:
`{ "file": "dev/dev_mas-028_arr_mrr_tracking.md", "title": "E.5.X F-28 ARR/MRR トラッキング" }`
- **`docs/_internal/changelog.md`**: 先頭行(ヘッダー直後)に以下を追記する:
`| 2026-04-20 | [dev_mas-028_arr_mrr_tracking.md](dev_mas-028_arr_mrr_tracking.md) | 初版作成。F-28 ARR/MRR トラッキング仕様書 |`
- コミット&プッシュ:
```
git add docs/dev/dev_mas-028_arr_mrr_tracking.md docs/_internal/changelog.md docs/_config.json
git commit -m "docs: MAS-028 ARR/MRR トラッキング開発仕様書を作成"
git push -u origin {現在のブランチ}
```