MAS-002: 期末スナップショット保存 + バージョン管理 (Period-End Snapshot + Version Management)
案件位置づけ
- 案件 ID: MAS-002 (TODO_future.md §1 P2・🧠 Domain・バージョン管理 35% 進行中の残課題)
- 連携先:
- MAS-057 cockpit (PR #400/#454 で localStorage 系完了 → 本案件で確定値保存)
- MAS-067 マルチイヤー (Y0 着地見込み PR #459)
- MAS-177 多年度データ基盤 (並行起票・本案件は MAS-177 利用例)
Section 1. 概要
| 項目 | 内容 |
|---|---|
| 案件 ID | MAS-002 |
| カテゴリ | 🧠 Domain (バージョン管理・確定スナップショット) |
| Phase | P2 |
| 優先度 | ★★ |
| 所要時間 | 1-2 週間 |
| 対象ファイル | 200_data/204_snapshot_repository.js 新設 / 400_domain/470_snapshot_engine.js 新設 / 100_config/101_sys_config.js schemas に 99_snapshot_periods + 99_snapshot_data 追加 / webapp_client/src/cockpit/SnapshotPanel.tsx 新設 |
| 前提案件 | MAS-057 (PR #400/#454 シナリオ保存) / MAS-077 (settlement_date 同期) / MAS-085 (整合性チェック) |
| 後続案件 | MAS-021 (PDF/Excel エクスポート) / MAS-067 (期首 BS 安定化) / MAS-177 (多年度データ基盤) / MAS-251-253 (第三者監査) |
Section 2. 目的
bizlp の財務諸表 (91_fs_bs / 92_fs_pl / 81_cf_actual) は 動的に再計算される 構造になっている。datamart 再構築のたびに INV データから再生成され、過去の確定値が「上書き」されて消えてしまう。
本案件は、特定時点の財務諸表値を 不変スナップショット として永続化する仕組みを導入する。
達成目標
- 期末確定 B/S・P/L・CF の 不変保存 (動的再計算の影響を受けない)
- 過去 N 期分の財務諸表を 確定値として再現可能 に
- スナップショット間 差分比較 で年次変動の可視化
- 改竄不可性 (SHA-256 ハッシュ) で第三者監査適合性確保
- MAS-067 多年シミュレーションの 期首 B/S 安定化 基盤
- MAS-021 PDF/Excel エクスポートの データソース 提供
非目標 (本案件のスコープ外)
- PDF/Excel 形式の出力 (MAS-021 で実装)
- 多年度データ基盤への自動配信 (MAS-177 で連携)
- 第三者監査ワークフロー全体 (MAS-251-253 で実装)
- 旧バージョンへのロールバック (参照モードのみ提供)
Section 3. 現在のコード
3.1 既存パターン
| ファイル | 役割 | 本案件での参照点 |
|---|---|---|
100_config/101_sys_config.js | DDL setupAllSchemas() でスキーマシート一括生成 | 99_snapshot_periods + 99_snapshot_data 追加 |
200_data/202_repository.js | Repository パターン (cache + findAll + save + append) | SnapshotRepository は同パターンで新設 |
200_data/201_data_validator.js | 入力 DTO バリデーション | スナップショット保存前の整合性チェック |
600_report/601_datamart_ingest.js | 財務諸表生成 (dmIngestPlanData_ 等) | 期末確定処理で datamart 再構築を先行実行 |
600_report/608_datamart_render.js | 91_fs_bs / 92_fs_pl レンダリング | 主要 5 タブから値取得する起点 |
webapp_client/src/cockpit/CockpitApp.tsx | Step 7 SoloFinancialStatementsPanel | SnapshotPanel を Step 7 隣接配置 |
000_infra/002_constants.js | Constants.ID_PREFIXES | SNP プレフィックス追加 |
000_infra/004_utils.js | Utils.lockUntil / Utils.auditLog / Utils.computeHash | 排他制御・監査ログ・改竄検知 |
3.2 主要 5 タブ (スナップショット対象)
| タブキー | 内容 | 取得粒度 |
|---|---|---|
61_pl_monthly | 月次 P/L (科目 × 月) | 全行 |
71_bs | B/S 残高 (科目 × YearMonth) | 期末月の列 |
81_cf_actual | CF (実績) | 期末月までの累計 |
91_fs_bs | 財務諸表 B/S (確定形式) | 全行 |
92_fs_pl | 財務諸表 P/L (確定形式) | 全行 |
3.3 既存の「バージョン管理」機能 (35% 進行中)
- PR #400/#454: cockpit
38_f57_assumptions/39_f57_scenarios(シナリオ前提保存) - 上記は 入力前提 のバージョン管理であり、出力確定値 は未対応
- 本案件は「出力確定値」の永続化に特化 (役割の明確な分担)
Section 4. 修正方針
4.1 シート設計
4.1.1 99_snapshot_periods (DDL 管理・メタ情報)
| 列 | 列名 | データ型 | 内容 |
|---|---|---|---|
| A | (チェックボックス) | bool | 削除/参照用 |
| B | スナップショット ID | string | SNP_YYYYMM_NNNN |
| C | スナップショット種別 | string | 期末B/S / 期末P/L / 期末CF / カスタム |
| D | 対象年月 | string | YYYY-MM |
| E | 保存日時 | Date | スナップショット作成タイムスタンプ |
| F | 保存者 | string | '手動' or 'AUTO_PERIOD_END' or ユーザー email |
| G | 備考 | string | フリーテキスト (期末確定理由・暫定/確定区別) |
| H | payloadJSON | string | サマリ JSON 文字列 (タブメタ + 主要指標) |
| I | 承認FLG | bool | true=確定, false=暫定 |
| J | ハッシュ | string | SHA-256 hex (改竄不可性・MAS-251 連携) |
| K | 有効フラグ | bool | 削除時 false 化 (物理削除しない) |
4.1.2 99_snapshot_data (DDL 管理・大規模行データ用)
| 列 | 列名 | データ型 | 内容 |
|---|---|---|---|
| A | (チェックボックス) | bool | 削除/参照用 |
| B | スナップショット ID | string | 外部キー (99_snapshot_periods.B) |
| C | タブキー | string | 61_pl_monthly / 71_bs / 81_cf_actual / 91_fs_bs / 92_fs_pl |
| D | 行番号 | int | 元タブ内の行番号 (再現用) |
| E | 行ヘッダー | string | 元タブのキー列値 (科目名・月度等) |
| F | 行データJSON | string | 元タブ 1 行を JSON.stringify |
| G | 有効フラグ | bool | 削除時 false 化 |
設計意図: 単一 payloadJSON では GAS の 50MB 上限に近づくため、行単位正規化で 任意行参照 と 大容量対応 を両立 (Q2 案 Z ハイブリッド採用)。
4.2 SnapshotRepository (200_data/204_snapshot_repository.js)
const SnapshotRepository = (() => {
/**
* @param {string} targetYm - YYYY-MM
* @param {{type: string, approver: string, note: string, isApproved: boolean}} options
* @returns {string} snapId
*/
function savePeriodEnd(targetYm, options) { /* ... */ }
/**
* @param {string} targetYm
* @param {string} [type] - 期末B/S 等。省略時は最新承認済
* @returns {Contracts.Snapshot|null}
*/
function findPeriodEnd(targetYm, type) { /* ... */ }
/**
* @returns {Array<Contracts.Snapshot>}
*/
function listSnapshots() { /* ... */ }
/**
* @param {string} snapId
*/
function deleteSnapshot(snapId) { /* 有効フラグ false 化 */ }
/**
* @param {string} snapIdA
* @param {string} snapIdB
* @returns {Array<Contracts.SnapshotDiff>}
*/
function compareSnapshots(snapIdA, snapIdB) { /* ... */ }
return { savePeriodEnd, findPeriodEnd, listSnapshots, deleteSnapshot, compareSnapshots };
})();
4.3 SnapshotEngine (400_domain/470_snapshot_engine.js)
const SnapshotEngine = (() => {
/**
* 期末確定処理: datamart 再構築 → 主要 5 タブから値取得 → 保存
* @param {string} targetYm
* @param {{forceRebuild: boolean, isApproved: boolean, note: string}} options
* @returns {{snapId: string, hash: string, rowCount: number}}
*/
function executePeriodEnd(targetYm, options) {
return Utils.lockUntil(60000, () => {
// 1. datamart 再構築 (forceRebuild === true 時)
// 2. 主要 5 タブから値取得 (61/71/81/91/92)
// 3. payloadJSON 生成 + SHA-256 ハッシュ計算
// 4. SnapshotRepository.savePeriodEnd 呼出
// 5. Utils.auditLog('SNAPSHOT_SAVED', { snapId, targetYm, hash })
});
}
/**
* 参照モードでの復元 (91_fs_bs 等は上書きしない)
* @param {string} snapId
* @returns {Contracts.SnapshotPayload}
*/
function restoreFromSnapshot(snapId) { /* ... */ }
/**
* @returns {Array<{tabKey, rowKey, valueA, valueB, delta}>}
*/
function diffSnapshots(snapIdA, snapIdB) { /* ... */ }
return { executePeriodEnd, restoreFromSnapshot, diffSnapshots };
})();
4.4 UI (cockpit パネル)
webapp_client/src/cockpit/SnapshotPanel.tsx:
export const SnapshotPanel: React.FC = () => {
// セクション 1: 期末確定実行
// - 対象年月入力 (デフォルトは現在年月)
// - 種別選択 (期末B/S/P/L/CF/カスタム)
// - 承認FLG チェックボックス
// - 「期末確定実行」ボタン (Apps Script google.script.run 呼出)
// セクション 2: 過去スナップショット一覧
// - 年月降順テーブル (ID/種別/対象年月/保存日時/承認/ハッシュ末尾4桁)
// - 削除ボタン (有効フラグ false 化のみ)
// セクション 3: スナップショット間差分比較
// - A/B 2 つのドロップダウン (snapId 選択)
// - 「差分計算」ボタン → 結果テーブル表示
// - 主要科目 (経常利益・現預金・売上) を上位表示
// セクション 4: 復元 (参照モード)
// - snapId 選択 → 別ダイアログでスナップショット内容を読み取り表示
// - 「91_fs_bs に書き戻す」ボタンは提供しない (誤上書き防止)
};
4.5 メニュー (100_config/101_sys_config.js)
// MENU_DEFINITION への追加項目 (抜粋)
{
name: '📸 スナップショット',
items: [
{ name: '期末確定実行', functionName: 'menuExecutePeriodEnd' },
{ name: '一覧表示', functionName: 'menuListSnapshots' },
{ name: '差分比較', functionName: 'menuDiffSnapshots' },
],
}
4.6 Constants 拡張 (000_infra/002_constants.js)
Constants.ID_PREFIXES.SNP = 'SNP'; // SNP_YYYYMM_NNNN
Constants.SNAPSHOT_TYPES = {
PERIOD_END_BS: '期末B/S',
PERIOD_END_PL: '期末P/L',
PERIOD_END_CF: '期末CF',
CUSTOM: 'カスタム',
};
Constants.SNAPSHOT_TARGET_TABS = [
'61_pl_monthly',
'71_bs',
'81_cf_actual',
'91_fs_bs',
'92_fs_pl',
];
4.7 Contracts 拡張 (000_infra/003_contracts.js)
/**
* @typedef {Object} Contracts.Snapshot
* @property {string} snapId
* @property {string} type
* @property {string} targetYm
* @property {Date} savedAt
* @property {string} savedBy
* @property {string} note
* @property {Object} payload
* @property {boolean} isApproved
* @property {string} hash
* @property {boolean} isActive
*/
/**
* @typedef {Object} Contracts.SnapshotDiff
* @property {string} tabKey
* @property {string} rowKey
* @property {number|string} valueA
* @property {number|string} valueB
* @property {number} delta
*/
Section 5. 影響範囲
5.1 新規ファイル
| ファイル | 概算行数 | 役割 |
|---|---|---|
200_data/204_snapshot_repository.js | ~150 行 | Repository 層 |
400_domain/470_snapshot_engine.js | ~200 行 | ドメインサービス |
webapp_client/src/cockpit/SnapshotPanel.tsx | ~250 行 | UI パネル |
8XX_migration_snapshot_seed.js | ~80 行 | 初期データ投入 (任意) |
8XX 番号採番: 822 まで使用済み + MAS-336/337/338 予約 = 824 以降 で採番。実装着手時に最終確定。
5.2 既存ファイル変更
| ファイル | 変更内容 |
|---|---|
100_config/101_sys_config.js | schemas に 99_snapshot_periods + 99_snapshot_data 追加 / MENU_DEFINITION 拡張 |
000_infra/002_constants.js | ID_PREFIXES.SNP + SNAPSHOT_TYPES + SNAPSHOT_TARGET_TABS 追加 |
000_infra/003_contracts.js | Snapshot / SnapshotDiff typedef 追加 |
000_infra/004_utils.js | (既存の場合は不要) computeHash / auditLog 利用 |
webapp_client/src/cockpit/CockpitApp.tsx | Step 7 隣接で SnapshotPanel をインポート・配置 |
5.3 既存ロジックへの影響
- datamart 再構築: 不変 (本案件は読取専用追加機能)
- 91_fs_bs / 92_fs_pl: 不変 (上書きしない・別シート保存)
- MAS-057 cockpit: 不変 (シナリオ保存と確定値保存は役割分離)
- Action A/B 仕訳エンジン: 不変
5.4 後続案件への波及
- MAS-067: 期首 BS として
SnapshotEngine.findPeriodEnd(prevYm, '期末B/S')を参照する v2 設計余地 - MAS-021: snapId からの PDF/Excel 出力 API
- MAS-177: 多年スナップショット履歴の集約・JOIN
- MAS-251-253: ハッシュ列を監査ログとして外部へエクスポート
Section 6. 注意事項
不変性の保証: payloadJSON で全データ保存し、元データ (61/71/81/91/92) を変更してもスナップショット内容は影響しない。スナップショット書込後に元シートが変わっても、スナップショット側は独立保持される。
スナップショットの種類: 期末確定/暫定/監査用の用途別タグ (
SNAPSHOT_TYPES) を必ず付与する。検索時の絞込で用途識別ができないと、暫定値を誤って税理士提出してしまうリスクあり。承認フラグ: 検討中の暫定スナップショットを複数試作 → 承認済 1 件で他は参照保持。承認済み 2 件以上の同種別 + 同年月は警告ダイアログ表示する。
大規模データ正規化: 99_snapshot_data に行単位で保持。1 ID あたり数千行を想定するが、
SpreadsheetApp.appendRowのループは遅いためsetValuesで一括書込必須。payloadJSON サイズ上限: GAS の
JSON.stringify結果は 50MB 上限。99_snapshot_periods.payloadJSON はサマリのみ・全行は 99_snapshot_data 側に逃がすことで実質無制限化 (Q2 ハイブリッド採用理由)。ID 採番:
SNP_YYYYMM_NNNN・Constants.ID_PREFIXES.SNPを使用。同一年月・同一日内の連番はUtils.lockUntilで発番ロック必須 (エッジケース 7 参照)。GAS 権限スコープ: シート読み書きのみで OAuth 追加スコープ不要。既存
appsscript.jsonのoauthScopes拡張なし。冪等性: 同 targetYm + 種別の重複時のルールは Q3 で議論。並列保存 (案 Y) を推奨するため、ID で完全分離する。
failure_patterns #34 整合性: PHASE 1 仕訳振替 INV (MAS-336) との整合性確保。スナップショット作成時点で
dmIsAccruedExpense_が正しく動作している前提。期末確定実行前に MAS-085 整合性チェックを呼ぶフックを Step 5 で検討。MAS-057 既存 localStorage 系との関係: 38_f57_assumptions / 39_f57_scenarios は 入力前提 の保存、MAS-002 は 出力確定値 の保存。役割を仕様書 + UI 上で明示し混同を防ぐ。
多年運用: MAS-177 多年度データ基盤と相互利用。MAS-177 は本案件のスナップショットを履歴ソースとして集約する設計余地あり (将来 v2)。
MAS-021 連携: snapId からの PDF 生成 API 設計余地。本案件では payload 構造を MAS-021 が読みやすい形 (タブキー + 行データ JSON) に揃えておく。
Section 7. エッジケース
月初実行で当該月データ空: 期末確定処理を月初 (例 4/1) に実行すると当該月 (4 月) の INV データが空 → datamart 再構築結果も空 → フォールバック: 直近確定月までで保存し、保存ダイアログに「対象年月のデータが空です。直近確定月で保存しますか?」と確認表示。
過去年月のスナップショット取得でデータ削除済: 99_snapshot_data の有効フラグ FALSE 化済の場合 →
findPeriodEndは null 返却 + UI で「該当スナップショットは削除されています」表示。同一年月複数スナップショット (検討中複数試作): ID 別に並列保存 (Q3 案 Y)。承認済 2 件以上は UI で警告 + 一覧で「承認」列ハイライト。
payloadJSON 50MB 上限超過: 行単位正規化 (99_snapshot_data) で回避。99_snapshot_periods.payloadJSON はサマリのみで上限到達リスクなし。
保存中に他ユーザーが datamart 再構築:
Utils.lockUntil(60000, ...)で排他制御。タイムアウト時は「他処理実行中です。1 分後に再試行ください」ダイアログ。比較で大量行差分: 主要 5 タブ全行 × 2 スナップショットで数千行の差分が出る場合 → ページネーション (50 行/ページ) + 上位 N 件 (絶対値降順) のサマリビュー両方提供。
同時実行で ID 重複:
SNP_YYYYMM_NNNNの発番はUtils.lockUntil内で99_snapshot_periodsの最新 ID + 1 を計算。並列実行時は後行が待機。データ修正で過去スナップショット元 INV 削除済: スナップショットは payloadJSON で独立保持されるため、元 INV 削除でも参照可能。元 INV 復元時の整合性検証は MAS-085 側で扱う (本案件はスナップショット側を信頼)。
月次決算と期末決算の使い分け: スナップショット種別 (
SNAPSHOT_TYPES) で区別。月次はカスタム+ 備考に「月次」記載、期末は期末B/S等。UI のフィルタで切替表示。MAS-067 多年シミュレーションで期首 BS = MAS-002:
findPeriodEnd(prevYm, '期末B/S')で取得。取得失敗時のフォールバックは MAS-067 側で「期首 BS 未確定です。手動入力に切替」ダイアログ。スナップショット復元 = 91_fs_bs 上書きせず別ダイアログ表示 (Q4 案 X): 復元ボタンクリック → モーダルで snapshot 内容を読み取りビュー表示。「91_fs_bs に書き戻す」機能は本案件では提供せず、誤上書き防止。
MAS-251 第三者監査でハッシュ値 (SHA-256) (Q5 案 X): payloadJSON に対する SHA-256 を J 列に保存。監査時は payloadJSON を再ハッシュして J 列と照合 → 一致で改竄なし証明。
Section 8. 実データ検証 (5 シナリオ)
Scenario 1: bizlp 設立年度 Y0 (2026FY) の期末確定
- 前提: 2026-04 〜 2027-03 の Y0 期末
- 手順: 2027-03-31 時点で
executePeriodEnd('2027-03', { isApproved: true, note: 'Y0 期末確定' }) - 期待:
SNP_202703_0001で 5 タブの確定値が保存・ハッシュ列に SHA-256 記録 - 検証: 翌期で datamart 再構築実行後、
findPeriodEnd('2027-03', '期末B/S')で当時の確定値再現
Scenario 2: 2 期目 Y1 で過去 1 年スナップショット取得
- 前提: Y0 (2027-03) 期末スナップショット保存済 + Y1 期中 (2027-09) で過去 1 年参照要求
- 手順:
SnapshotRepository.findPeriodEnd('2027-03')呼出 - 期待: Y0 確定値が即時取得 (datamart の現状に左右されない)
- 検証: 銀行融資審査向け「過去 3 期 B/S」要請を Scenario 1 + 同様処理 × 3 回で再現
Scenario 3: 同一年月複数スナップショット
- 前提: 2027-03 期末を決算前 2 回 (暫定) + 決算後 1 回 (確定) の 3 段階で保存
- 手順:
executePeriodEnd('2027-03', { isApproved: false, note: '暫定 v1' })→SNP_202703_0001executePeriodEnd('2027-03', { isApproved: false, note: '暫定 v2 (修正)' })→SNP_202703_0002executePeriodEnd('2027-03', { isApproved: true, note: '確定' })→SNP_202703_0003
- 期待: 3 件並列保存・承認FLG で確定 1 件のみ識別可能
- 検証:
listSnapshots()で 3 件すべて返却・UI で承認済 1 件強調表示
Scenario 4: スナップショット間差分比較
- 前提: A =
SNP_202703_0003(Y0 期末) + B =SNP_202803_0001(Y1 期末) - 手順:
diffSnapshots('SNP_202703_0003', 'SNP_202803_0001')呼出 - 期待: 経常利益・現預金・売上の年次変動が
delta列で算出 - 検証: UI で上位 N 件 (絶対値降順) ハイライト・年次成長率の概観把握
Scenario 5: スナップショット復元 (参照モード起動)
- 前提:
SNP_202703_0003の内容確認要求 - 手順:
restoreFromSnapshot('SNP_202703_0003')呼出 → モーダル表示 - 期待: payloadJSON + 99_snapshot_data 行データを統合表示・91_fs_bs は上書きされない
- 検証: 復元後に
91_fs_bsのハッシュが復元前と同一 (誤上書き防止確認)
Section 9. 関連ドキュメント
| 案件 ID / ドキュメント | 関係 |
|---|---|
| MAS-057 | cockpit・SnapshotPanel 配置先・シナリオ保存基盤 (PR #400/#454) |
| MAS-067 | 期首 BS として参照 (将来 v2) |
| MAS-177 | 並行起票・本案件は MAS-177 の利用例 |
| MAS-021 | PDF/Excel エクスポート・将来連携 |
| MAS-251-253 | 第三者監査・データ主権・将来連携 |
| MAS-085 | 整合性チェック・スナップショット保存時の整合性検証 |
| MAS-077 | settlement_date 同期・前提案件 |
| MAS-336 | PHASE 1 仕訳振替 INV・failure_patterns #34 整合性 |
| failure_patterns #34 | 計画 B/S 構造的乖離・PHASE 1 仕訳振替 INV 整合性 |
docs/_internal/dev_spec_prompt_template.md | 仕様書テンプレート |
docs/prd.md | プロダクトポリシー Human-in-the-Loop |
Section 10. 人間が検討すべき事項 (Q1-Q5)
Q1. UI 配置
| 案 | 内容 | メリット | デメリット |
|---|---|---|---|
| X (推奨) | cockpit パネル (SnapshotPanel.tsx) | 経営判断後の確定アクションとして自然・Step 7 隣接で視認性高 | cockpit 全体の表示量増 |
| Y | 独立サイドバー (MAS-232 配下) | サイドバーに集約・運用ツール扱い | cockpit との文脈分離 |
| Z | メニューのみ | 実装最小 | 一覧・差分 UI を提供できない |
推奨理由: 期末確定は「経営判断 → 承認 → 確定」の HIL フロー (PRD ポリシー) に合致。cockpit Step 7 の財務諸表閲覧直後に確定アクションがあると操作動線が自然。
Q2. payloadJSON の格納方針
| 案 | 内容 | メリット | デメリット |
|---|---|---|---|
| X | 99_snapshot_periods.payloadJSON 1 列に全 JSON | スキーマ単純 | 50MB 上限到達リスク |
| Y | 99_snapshot_data に行単位正規化 | 任意行参照容易・上限なし | サマリ取得が遅い |
| Z (推奨) | ハイブリッド (メタ + サマリは X・主要 5 タブ全行は Y) | サマリ高速 + 大容量対応 | スキーマ 2 シート |
推奨理由: 99_snapshot_periods 一覧表示時はサマリ JSON のみで高速描画、詳細閲覧 / 差分比較時のみ 99_snapshot_data を JOIN。GAS の制約 (50MB 上限・appendRow パフォーマンス) を両立。
Q3. 期末確定の冪等性
| 案 | 内容 | メリット | デメリット |
|---|---|---|---|
| X | 既存スナップショットがあればスキップ | 重複防止 | 検討中複数試作不可 |
| Y (推奨) | 並列保存 (履歴保持) | 検討中/確定/監査用の複数バージョン保持可 | 一覧で複数件表示 |
推奨理由: Solo-CEO の期末決算では暫定値を複数回試算する運用が想定される。承認FLG で確定 1 件を識別する設計のほうが実運用に合致。
Q4. スナップショット復元時の動作
| 案 | 内容 | メリット | デメリット |
|---|---|---|---|
| X (推奨) | 91_fs_bs を上書きせず参照モード | 元データ破壊リスクなし | 「過去値で再計算」は手動コピペ |
| Y | 一時上書き + バックアップ + リバート | ワンクリック切替 | バックアップ漏れで上書き事故 |
推奨理由: bizlp の単一 SoT (Single Source of Truth) 原則に合致。スナップショットは「読み取り専用の過去断面」であり、現行データへの書き戻しは別 UX (将来要求があれば MAS-021 PDF 生成等) で扱う。
Q5. 改竄不可性 (第三者監査向け)
| 案 | 内容 | メリット | デメリット |
|---|---|---|---|
| X (推奨) | payloadJSON SHA-256 ハッシュ記録 | MAS-251 連携を見据えて当初実装・実装コスト低 | ハッシュ列管理必要 |
| Y | 要求しない (社内利用前提) | 実装最小 | 後付けで全スナップショット再ハッシュ困難 |
推奨理由: MAS-251-253 第三者監査・データ主権で改竄検知は必須要件。SHA-256 計算は GAS の Utilities.computeDigest で 1 行・実装コスト最小。後付けで全件再ハッシュは不可能なため当初から仕込む。
Section 11. 実装プロンプト (Step 1-5)
Step 1: DDL シート追加 + Repository (Sonnet)
目的: スキーマ + Repository 層の基盤実装
作業:
200_data/204_snapshot_repository.js新設 (~150 行)savePeriodEnd/findPeriodEnd/listSnapshots/deleteSnapshot/compareSnapshots- cache + findAll + save + append の Repository パターン適用
100_config/101_sys_config.jsの schemas に99_snapshot_periods+99_snapshot_data追加000_infra/002_constants.jsにID_PREFIXES.SNP+SNAPSHOT_TYPES+SNAPSHOT_TARGET_TABS追加000_infra/003_contracts.jsにSnapshot/SnapshotDifftypedef 追加
完了基準:
setupAllSchemas()実行で 99_snapshot_periods + 99_snapshot_data が生成される- 単体テスト F002-01 〜 F002-05 (Repository CRUD + 一覧 + 差分基本動作) が通る
テストケース:
- F002-01: savePeriodEnd → findPeriodEnd で同一データ取得
- F002-02: listSnapshots で全件取得 (有効フラグ true のみ)
- F002-03: deleteSnapshot で有効フラグ false 化
- F002-04: 同一年月 2 件保存 → ID 重複なし
- F002-05: compareSnapshots で 2 件の delta 配列取得
Step 2: SnapshotEngine (Opus)
目的: 期末確定 + 復元 + 差分比較のドメインロジック
作業:
400_domain/470_snapshot_engine.js新設 (~200 行)executePeriodEnd(targetYm, options): datamart 再構築 → 5 タブ取得 → SnapshotRepository.savePeriodEndrestoreFromSnapshot(snapId): 参照モード読取 (91_fs_bs 上書きしない)diffSnapshots(snapIdA, snapIdB): 行レベル差分計算
Utils.lockUntilで排他制御Utils.computeHash(SHA-256) で payloadJSON ハッシュ計算- 既存 datamart 再構築 (
dmIngestPlanData_等) との整合性確保
完了基準:
- 単体テスト F002-06 〜 F002-10 が通る
テストケース:
- F002-06: executePeriodEnd 実行 → datamart 再構築 + 5 タブ取得 + 保存
- F002-07: 同一実行で ID 重複なし (lockUntil 動作)
- F002-08: SHA-256 ハッシュが payloadJSON と一致
- F002-09: restoreFromSnapshot で 91_fs_bs が変更されないこと確認
- F002-10: diffSnapshots で経常利益等の delta が正しく計算
Step 3: cockpit パネル + メニュー (Sonnet)
目的: UI 提供
作業:
webapp_client/src/cockpit/SnapshotPanel.tsx新設 (~250 行)- 期末確定実行セクション (年月入力 + 種別 + 承認FLG + 実行ボタン)
- 過去スナップショット一覧 (年月降順テーブル)
- スナップショット間差分比較 (A/B ドロップダウン + 結果テーブル)
- 復元 (参照モード) (snapId 選択 → モーダル表示)
100_config/101_sys_config.jsMENU_DEFINITION に「📸 スナップショット」メニュー追加webapp_client/src/cockpit/CockpitApp.tsxの Step 7 隣接で SnapshotPanel 配置
完了基準:
- cockpit Step 7 隣接に SnapshotPanel 表示
- 期末確定ボタンで
google.script.run.executePeriodEnd呼出成功 - 単体テスト F002-11 〜 F002-12 が通る
テストケース:
- F002-11: 期末確定ボタン → google.script.run 呼出 → snapId 返却
- F002-12: 一覧表示で listSnapshots 結果が年月降順表示
Step 4: バージョン間比較ビュー (Sonnet)
目的: 差分比較 UI の機能拡充
作業:
- SnapshotPanel に差分比較 UI 拡張
- A/B ドロップダウン (snapId 選択)
- 「差分計算」ボタン → 結果テーブル (上位 N 件 + ページネーション)
- 主要科目 (経常利益・現預金・売上) を上位ハイライト
SnapshotEngine.diffSnapshots戻り値構造設計{ tabKey, rowKey, valueA, valueB, delta, deltaPct }[]- 主要科目フラグ
isHighlightを付与
完了基準:
- 2 件の snapshot 選択 → 差分テーブル表示・主要科目ハイライト
- ページネーション動作 (50 行/ページ)
Step 5: 改竄不可性 + 監査ログ (Haiku)
目的: 第三者監査適合性の担保
作業:
Utils.computeHash(SHA-256) をUtilities.computeDigest(SHA_256, ...)で実装 (既存なら流用)- payloadJSON 保存時に SHA-256 計算 + ハッシュ列保存
Utils.auditLog('SNAPSHOT_SAVED', { snapId, targetYm, hash, savedBy })呼出- スナップショット読取時にハッシュ照合 (改竄検知) を
restoreFromSnapshot内で実施 - 不一致時は警告ダイアログ「このスナップショットは改竄されている可能性があります」
完了基準:
- スナップショット保存時にハッシュ列が記録される
- 監査ログテーブル (既存の
Utils.auditLog出力先) に SNAPSHOT_SAVED イベントが記録 - ハッシュ不一致シミュレーション (J 列を手動編集) で警告ダイアログ表示
Section 12. 推奨実行モデル (Step ごと)
| Step | モデル | 理由 |
|---|---|---|
| Step 1 | Sonnet | DDL + Repository パターン応用 (中程度の判断) |
| Step 2 | Opus | 期末確定 + 復元 + 差分比較ロジック設計 (複数ファイル横断・会計ロジック理解必要) |
| Step 3 | Sonnet | cockpit パネルパターン応用 (中程度の判断) |
| Step 4 | Sonnet | 既存 SnapshotPanel への拡張 (中程度の判断) |
| Step 5 | Haiku | ハッシュ計算 + 監査ログ呼出 (機械的書込) |
CLAUDE.md「実装時のモデル選択ルール」に準拠。
Section 13. 変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-05-01 | v0.1 起票 (main 側起票依頼。バージョン管理進捗 35% の残課題「期末スナップショット保存」を仕様化。Q1-Q5 推奨判断 + Step 1-5 段階リリース計画。MAS-057 cockpit シナリオ保存 (PR #400/#454) と役割を区別 (シナリオ vs 確定値)・MAS-067 多年シミュレーション期首 BS 安定化として後続活用想定・MAS-177 多年度データ基盤と並行起票で相互利用。バージョン管理 35% → 残機能の主要部 (期末スナップショット保存) を仕様化) |
Section 14. 仕様書作成プロンプト
展開して表示
# MAS-002: 期末スナップショット保存 + バージョン管理 (Period-End Snapshot + Version Management)
## frontmatter (先頭・必須)
---
id: MAS-002
aliases: ["F-02", "PERIOD-END-SNAPSHOT", "VERSION-MGMT"]
type: Story
status: Open
---
## 案件位置づけ
- 案件 ID: MAS-002 (TODO_future.md §1 P2・🧠 Domain・バージョン管理 35% 進行中の残課題)
- 連携先: MAS-057 cockpit (PR #400/#454 で localStorage 系完了→本案件で確定値保存)・MAS-067 マルチイヤー (Y0 着地見込み PR #459)・MAS-177 多年度データ基盤 (並行起票・本案件は MAS-177 利用例)
## 背景・問題意識
bizlp の財務諸表 (`91_fs_bs` / `92_fs_pl` / `81_cf_actual`) は**動的に再計算される**: datamart 再構築のたびに INV データから再生成・過去の確定値が「上書き」されて消える。期末スナップショット (BSE: B/S Ending) を**確定値として保存**する仕組みがない。
### 顕在化している不足
- 税理士向け過去 3 期分財務諸表エクスポートで各期の確定値が再現できない
- 銀行融資審査での「過去 3 期分財務諸表」要請に確定スナップショット必須
- MAS-067 マルチイヤー計画で各年度の期首 B/S が動くと多年シミュレーション再現性失われる
- MAS-057 cockpit シナリオ保存 (38_f57_assumptions / 39_f57_scenarios) は前提条件・シナリオ保存だが**確定値**保存していない
- MAS-251-253 第三者監査・データ主権で過去確定値の証跡必要
### User Story
- Solo-CEO: 「2026-03 期末確定 B/S を期末確定後に修正しても当時の値が残る」
- 税理士: 「過去 3 期の財務諸表を bizlp から確定値で取得」
- 銀行融資審査: 「過去 3 期確定 B/S を即提出」
- MAS-067 多年シミュレーション: 「各年度の期首 B/S が安定して再現可能」
## 仕様書本体 (14 セクション必須)
### Section 1. 概要
(対象ファイル / 前提案件 / 後続案件 表)
### Section 2. 目的
動的再計算される財務諸表に対し、特定時点の確定値を**不変スナップショット**として永続化する仕組み。
### Section 3. 現在のコード
- 100_config/101_sys_config.js の schemas DDL パターン
- 200_data/202_repository.js の Repository パターン
- 600_report/601_datamart_ingest.js の財務諸表生成プロセス
- webapp_client/src/cockpit/CockpitApp.tsx の Step 7 SoloFinancialStatementsPanel
### Section 4. 修正方針
4.1 シート設計 (99_snapshot_periods + 99_snapshot_data)
4.2 SnapshotRepository (204_snapshot_repository.js)
4.3 SnapshotEngine (470_snapshot_engine.js)
4.4 UI (SnapshotPanel.tsx)
4.5 メニュー
### Section 5. 影響範囲
新規ファイル / DDL 拡張 / 既存ロジック / MAS-057 cockpit / MAS-067 / MAS-021 / MAS-177
### Section 6. 注意事項 (10-12 件)
### Section 7. エッジケース (10-12 件)
### Section 8. 実データ検証 (5 シナリオ)
### Section 9. 関連ドキュメント
### Section 10. 人間が検討すべき事項 (Q1-Q5)
Q1. UI 配置 / Q2. payloadJSON 格納 / Q3. 冪等性 / Q4. 復元動作 / Q5. 改竄不可性
### Section 11. 実装プロンプト (Step 1-5)
### Section 12. 推奨実行モデル (Step ごと)
### Section 13. 変更履歴
### Section 14. 仕様書作成プロンプト