MAS-039: ABC分析(パレート分析)ダッシュボード
概要
| 項目 | 内容 |
|---|---|
| 案件ID | MAS-039 |
| カテゴリ | FP&A / レポーティング |
| Phase | P2 |
| 優先度 | ★★ |
| ステータス | 仕様書完了(未実装) |
| 対象ファイル(新規) | 600_report/612_datamart_abc_analysis.js |
| 対象ファイル(既存変更) | 000_infra/002_constants.js(MENU_DEFINITION「📋 サイドバー: 📊 マート更新」カテゴリへ 1 項目追記のみ) |
| 新規シート | 69_abc_analysis(DDL 管理外・関数内で insertSheet にて冪等生成) |
| 前提案件 | なし(既存 InvoiceRepository.findAll() と 78_pj_pl シートの実データを読み取る純粋なレポーティング機能) |
| 参照案件 | F-06 (docs/dev/dev_F-06_project_overhead_allocation.md) — 78_pj_pl の限界利益を生成する配賦エンジン |
ファイル番号の確定経緯: Phase 1 で 600_report/ 配下の実ファイルを Read し、最大番号は 609_datamart_kpi.js と確認。F-28 仕様書が 610_datamart_saas_kpi.js、F-17 仕様書が 611_service_funding_simulation.js を予約済のため、本案件は 612_datamart_abc_analysis.js を採番する(docs/_internal/changelog.md 2026-04-20 / 2026-04-21 エントリの予約状況から確定)。
シート番号の注意: 69_abc_analysis は F-28 仕様書の 69_kpi_saas と番号が重複している。どちらも仕様書段階で未実装のため、実装着手時点で先行案件と調整し、必要に応じて本案件側を 68_abc_analysis 等に変更する(「人間が検討すべき事項」に明記)。
目的
取引先別・PJ 別の利益貢献度をパレート原則(20 / 80 の法則)で可視化し、「どの事業・顧客に経営資源を集中すべきか」をデータで即座に判断するための分析基盤を提供する。売上ベースのざっくり判断ではなく、F-06 の共通費配賦を織り込んだ「真の利益」でランク付けすることで、以下の経営判断を支援する:
- A ランク(利益の 70% を生む最重要層): リソース集中投下、契約更新時の条件改善、VIP サポート体制
- B ランク(利益の 70〜90% を生む中核層): 標準工数での維持、アップセル余地の評価
- C ランク(利益の 90〜100% を埋める末端層): 自動化・撤退・条件見直しの検討
売上 A ランクだが利益 C ランク(いわゆる「赤字優良顧客」)の炙り出しも副次効果として期待する。
現在のコード
現状は ABC 分析機能が存在しない。以下の既存資産を読み取り専用で再利用する(改変なし):
| 既存モジュール | 参照箇所 | 本案件での使途 |
|---|---|---|
200_data/202_repository.js | InvoiceRepository.findAll() L163-165 | 取引先別 ABC のデータソース。戻り値型 { headers: string[], dtos: InvoiceDTO[] } をそのまま受けて dtos を走査 |
000_infra/003_contracts.js | InvoiceDTO typedef L38-67 | 必須フィールド: 有効フラグ(boolean)/ 請求ステータス("未処理" | "承認済" | "却下")/ 収支区分("収入" | "支出")/ 取引先名 / PJ名 / 税抜金額_計画 を確認済。いずれも実在 |
000_infra/002_constants.js | Constants.getParam(key, defaultVal) L147-167 | 閾値 A/B を 03_sys_params から読み取る。第 2 引数が number 型なら Number(val) で数値化、文字列なら String(val) で文字列化される仕様を確認済 |
400_domain/420_project_profitability.js | 78_pj_pl 出力ロジック L536-698 | PJ 別 ABC のデータソース。行ラベル '✨ 限界利益'(L546、plSections の { id:'marginal', name:'✨ 限界利益', type:'profit', calc:[...] })を目印に行を特定する |
78_pj_pl シートの構造(Read で確認):
78_pj_plは DDL 管理外で、buildProjectProfitability()実行時にss.insertSheet('78_pj_pl')→sheet78.clear()で毎回再生成される動的シート- 列レイアウト(420_project_profitability.js L621-634):
- 列 A: 表示区分(科目名やセクション名)
- 列 B: 配賦元合計(配賦元 PJ の合算値)
- 列 C 以降: PJ ごとの金額列(
uniquePjs.length列) - 最終列: 全社合計
- 2 行目にヘッダー(
['表示区分', '配賦元合計', PJ1名, PJ2名, ..., ''])、3 行目以降がデータ - 利益行は A 列が
'✨ 限界利益'(行頭に全角スペースを含まない) - 配賦元 PJ(社内業務・共通費など)は C 列以降には登場しない(B 列の「配賦元合計」に集約されている)
Constants.getParam の引数仕様(Read で確認):
Constants.getParam('ABC_THRESHOLD_A', 0.7)
// → 03_sys_params 未登録 or 空なら 0.7 を返す
// → 登録済なら Number(値) を返す(第 2 引数の型が number のため数値化される)
修正方針
アーキテクチャ
新規ファイル 600_report/612_datamart_abc_analysis.js を 600_report/ レイヤーに配置する。既存の 601_datamart_ingest.js 〜 609_datamart_kpi.js と同一レイヤーのため、GAS ロード順序への影響はない。
新規シート 69_abc_analysis は DDL 管理外とし、関数内で ss.getSheetByName('69_abc_analysis') || ss.insertSheet('69_abc_analysis') で冪等に生成する(93_kpi_dashboard / 78_pj_pl と同じパターン)。100_config/101_sys_config.js の schemas への追加は行わない(setupAllSchemas 実行時に毎回クリアされると閾値や書式が消失するため)。
取引先別 ABC のデータソースと集計
- データ取得:
InvoiceRepository.findAll()を呼び出し、{ headers, dtos }のうちdtos: InvoiceDTO[]を対象にする。 - フィルタ: 以下の 2 条件を AND で満たす INV のみを ABC 分析対象にする。
dto.有効フラグ !== false(trueおよび未定義は通す。論理削除行は除外)dto.請求ステータス === '承認済'(Phase 1 でInvoiceDTOtypedef の有効値"未処理" | "承認済" | "却下"を確認済。推測ではなく型注釈から引用)
- 取引先別集計:
dto.取引先名をキーとして以下の利益額を計算する。利益 = Σ(収支区分 === '収入' の税抜金額_計画) − Σ(収支区分 === '支出' の税抜金額_計画)税抜金額_計画はNumber(dto['税抜金額_計画']) || 0で非数値は 0 として扱うdto.収支区分が'収入'/'支出'以外(空欄・未知値)の INV は加減算しない(カウントには含めるが利益計算から除外)
- 除外: 利益額
≤ 0の取引先は ABC 分析対象から除外する(ABC 分析は利益貢献度の順位付けが目的で、赤字・収支トントンの取引先は「別の問題」として切り分ける)。
PJ 別 ABC のデータソース
- 読み取り元:
ss.getSheetByName('78_pj_pl')の内容を直接読む。DDL 管理外のためsheet.getDataRange().getValues()で全件取得する。 - 行特定: A 列(表示区分)を上から走査し、
String(cell).replace(/\n/g, '').trim() === '✨ 限界利益'に一致する行を検出する。見つからない場合はシートにエラーメッセージを書き込み中断(78_pj_pl未生成 orbuildProjectProfitability構造変更の検知)。 - PJ 抽出: 2 行目のヘッダーから C 列以降の PJ 名を取得し、限界利益行の同じ列位置の金額とペアリングする。
- 除外条件:
- 最終列(全社合計)は集計対象外
- B 列(配賦元合計)は集計対象外(個別 PJ ではなく集約値のため)
- 空欄 PJ 名、
''およびnullを除外 - 利益額
≤ 0の PJ を除外
ランク付けロジック
ソート: 利益額の降順。利益額が同額の場合は取引先名 / PJ 名の昇順で二次ソート(JavaScript の
sortは stable だが、明示的に二次キーを指定して実行のたびに順序が変わる不安定な結果を防ぐ)。items.sort(function(a, b) { if (b.profit !== a.profit) return b.profit - a.profit; return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0); });累積構成比:
totalProfit = Σ(items.profit) // フィルタ・除外後の全件合計 items[i].cumulativeRatio = Σ(items[0..i].profit) / totalProfit閾値取得:
var thA = Constants.getParam('ABC_THRESHOLD_A', 0.7); // 既定 0.7 var thB = Constants.getParam('ABC_THRESHOLD_B', 0.9); // 既定 0.9ハードコード禁止。
03_sys_paramsシートに未登録の場合はデフォルト値(0.7 / 0.9)が使われる。ランク付け:
cumulativeRatio < thA(< 0.7)→AランクcumulativeRatio < thB(< 0.9)→Bランク- それ以外(≥ 0.9)→
Cランク - ただし、累積比 0.6999... → 0.7 をまたぐ境界上の 1 件は A ランクに含める(厳密な
<比較で運用)
書き込み方式(冪等性)
sheet.getRange(2, 1, Math.max(1, sheet.getMaxRows() - 1), numCols).clearContent()で 既存データをクリアしてから全件再書き込み(追記による二重計上・差分バグの防止)。- 列数
numColsは取引先別 ABC / PJ 別 ABC の最大列数(7 列想定: ランク / 対象名 / 利益額 / 構成比 / 累積構成比 / 順位 / 備考)。 - A1 / A2 セルにメタ情報を書き込む:
A1:'データソース: 32_wrk_invoice(承認済INV) / 78_pj_pl(限界利益行)'A2:'最終更新: ' + Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm')
- 取引先別ブロックと PJ 別ブロックを 1 枚のシートに上下 2 段で出力(セクション見出し行で分離)。
排他ロック
マート更新関数の冒頭で LockService.getScriptLock().waitLock(30000) を try / finally 構造で取得する。600_report/ 既存ファイル群は LockService を使用していない(Phase 1 で grep -r "LockService" 600_report/ にて 0 件を確認)が、本案件は書き込み専用のマート更新で、同時実行されると書き込み途中でのクリア競合が起こりうるため、F-23 仕様書(dev_F-23_monthly_comments.md)と同じパターンで明示的にロックを取得する:
function updateAbcAnalysisDashboard() {
var lock = LockService.getScriptLock();
try {
lock.waitLock(30000); // 最大 30 秒待機
// (ABC 計算と書き込み)
} finally {
try { lock.releaseLock(); } catch (e) { /* 既に解放済なら握りつぶす */ }
}
}
パレート図(グラフ)
sheet.newChart()→Charts.ChartType.COMBO(複合グラフ)で生成。- 系列構成:
- 棒グラフ(主軸・左): 利益額列
- 折れ線グラフ(副軸・右): 累積構成比列(0 〜 1.0)
- GAS の
EmbeddedComboChartBuilderは UI 上の編集機能(色ごとの濃淡指定、第 2 軸の詳細フォーマット等)に比べて表現が制約される。視覚スタイルの微調整は手動対応とし、本スクリプトでは「系列種別の指定(棒 vs 折れ線)」と「基本的な軸ラベル」までを自動化する方針。 - 取引先別チャート / PJ 別チャートをそれぞれ 1 枚ずつ、
sheet.insertChart(chart)でシート右側に挿入する。既存チャートは初回クリア時点でsheet.getCharts().forEach(function(c) { sheet.removeChart(c); })にて全削除してから再挿入する(冪等性確保)。
UI メニュー
Phase 1 で 000_infra/002_constants.js の MENU_DEFINITION(L206-324)を Read し、'📋 サイドバー: 📊 マート更新' カテゴリが実在することを確認済。既存の項目(財務3表の更新 / 📅 基準年月指定で更新 / プロジェクト別 採算 / 📊 KPIダッシュボード再描画 / 📸 前年度P/Lスナップショット)の末尾に 1 行追加する。
{ label: '📊 ABC分析(パレート)更新', funcName: 'updateAbcAnalysisDashboard', description: '取引先別・PJ別の利益貢献度をパレート分析し 69_abc_analysis に出力' },
造語禁止: F-03 仕様書作成時の失敗パターン #20(実在しないメニュー名を造語した)に倣い、カテゴリ名 '📋 サイドバー: 📊 マート更新' は Read 済の実在文字列のみを引用している。
影響範囲
| ファイル | 変更種別 | 影響 |
|---|---|---|
600_report/612_datamart_abc_analysis.js | 新規作成 | 新規ファイルのため既存ロジックへの影響なし |
000_infra/002_constants.js | 追記のみ | MENU_DEFINITION の '📋 サイドバー: 📊 マート更新' カテゴリ items 配列末尾に 1 オブジェクト追加。他の構造変更なし |
03_sys_params シート(任意) | データ追記 | ABC_THRESHOLD_A / ABC_THRESHOLD_B を登録すると閾値を変更可能。未登録でも Constants.getParam のデフォルト値で動作 |
69_abc_analysis シート | 新規生成(動的) | 関数初回実行時に insertSheet で自動作成。DDL 管理外 |
既存シートへの書き込みは行わない(読み取り専用: 32_wrk_invoice / 78_pj_pl)。InvoiceRepository.save() / InvoiceRepository.append() は呼び出さない。
注意事項
Constants.getParamの所在:000_infra/002_constants.jsL147-167 に定義されている(004_utils.jsではない)。Phase 1 で Read 済。78_pj_plは DDL 管理外: 列名・行ラベルは実コード(400_domain/420_project_profitability.jsL540-552)を Read して確認した'✨ 限界利益'固定文字列のみを使用する。推測・造語禁止(failure_patterns #20)。行ラベル比較時は.replace(/\n/g, '').trim()で改行と前後空白を除去する。- 絵文字・全角スペースの取り扱い:
'✨ 限界利益'の先頭絵文字は UTF-16 サロゲートペアのためString.lengthやindexOfで扱う際に注意。比較は 完全一致(===)で行い、includesによる部分一致は使わない(failure_patterns #22-#24 の教訓)。 InvoiceDTO.税抜金額_計画は計画値: 実績ベース分析が必要な場合はJournalRepository.findAll()の税抜金額_実績を使う設計への変更が必要(「人間が検討すべき事項」に記載)。現行 v1 は計画値ベースで固定する。- パレート図の GAS API 制約:
Charts.ChartType.COMBOによる複合グラフは GAS のEmbeddedComboChartBuilderを使うが、setSeriesType(index, 'bars' / 'line')の指定は効く一方、第 2 軸の目盛範囲の自動調整・色指定・凡例位置の細部は UI 側の微調整が必要。本案件では「動く最低限」を自動化し、美装は手動対応。 03_sys_params未登録時の挙動:Constants.getParam('ABC_THRESHOLD_A', 0.7)はキャッシュ機構(_paramsCache)で初回のみ全件読み込み。未登録 or 空文字の場合、第 2 引数のデフォルト(0.7 / 0.9)が返されるため、初期動作は保証される。- チャート再挿入の冪等性: 2 回目以降の実行で旧チャートが残存しないよう、書き込み前に
sheet.getCharts()で全チャートを取得してsheet.removeChart()ですべて削除する(無しの状態を保証)。 - ロック取得失敗時:
waitLock(30000)が 30 秒経っても取得できない場合は例外が飛ぶ。UI メニュー実行中は Google Apps Script の UI 側でエラートーストが表示されるため、本関数では追加のエラーハンドリングは行わない(実行履歴と監査ログに自動記録される)。
エッジケース
failure_patterns #2(ゼロ除算フォールバック)・#3(DDL vs 実データの乖離)の教訓を反映し、異常系の挙動を事前定義する。
| 条件 | 動作・表示値 | 理由 |
|---|---|---|
利益額 ≤ 0 の取引先 / PJ | ABC 計算対象から除外 | ABC 分析は利益貢献度の順位付けが目的。赤字・ゼロ利益先は「別の問題」として分離 |
| フィルタ後の対象件数 0 件(承認済 INV が 0 件など) | A3 セルに '⚠️ 分析対象データがありません(承認済レコード 0 件)' を書き、取引先別ブロック自体を空で出力して中断 | ゼロ除算回避 + フィルタ条件の誤設定との切り分け(失敗パターン #2) |
除外後の合計利益 ≤ 0(ゼロ除算の可能性) | A3 セルに '⚠️ 分析対象データがありません(合計利益が 0 以下)' を書いて当該ブロックをスキップ | items[i].profit / totalProfit のゼロ除算を明示的に回避(失敗パターン #2) |
32_wrk_invoice シート自体が空(ヘッダーのみ) | InvoiceRepository.findAll().dtos が [] → 「データ 0 件」ブロックに合流 | findAll の戻り値 .dtos が空配列でも安全に通過 |
| 取引先別と PJ 別の両方が 0 件 | シート全体に '⚠️ 取引先別・PJ 別ともに分析対象データがありません' を表示、チャート生成もスキップ | チャート生成前に早期 return |
| 利益額が同額の取引先 / PJ 複数 | 取引先名 / PJ 名の昇順で二次ソート | 実行のたびに順序が変わる不安定な結果を防止 |
取引先名 null / '' / 半角空白のみ の INV | 取引先名を '(未分類)' にまとめて集計 | INV 入力途中の未記入を明示。除外すると合計利益からも消えて閾値計算がずれる |
78_pj_pl シート未作成(buildProjectProfitability 未実行) | PJ 別ブロックに '⚠️ 78_pj_pl が未生成です。「プロジェクト別 採算」メニューを先に実行してください' を表示、取引先別だけ出力 | 依存シートの欠落を検知(failure_patterns #3) |
78_pj_pl で '✨ 限界利益' 行が見つからない | PJ 別ブロックに '⚠️ 78_pj_pl の「✨ 限界利益」行が検出できませんでした。buildProjectProfitability の構造変更を確認してください' を表示 | 420_project_profitability.js 側の構造変更を早期検知 |
ABC_THRESHOLD_A / ABC_THRESHOLD_B が 03_sys_params 未登録 | デフォルト値 0.7 / 0.9 を使用して処理継続 | Constants.getParam の第 2 引数による安全なフォールバック |
ABC_THRESHOLD_A >= ABC_THRESHOLD_B(設定ミス) | 警告ログを出力し、その実行は thA = 0.7, thB = 0.9 のデフォルトにフォールバック | 論理矛盾した閾値で無意味なランク(全件 C 等)になるのを防ぐ |
69_abc_analysis シートが未作成 | ss.insertSheet('69_abc_analysis') で自動作成 | DDL 管理外のため関数内で冪等に作成 |
69_abc_analysis シートに手動で値が入力されていた | getRange(2, 1, lastRow-1, numCols).clearContent() で毎回クリア→全件再書き込み | 手動編集データが累積しない冪等保証 |
| ロック取得失敗(30 秒経過) | 例外が UI にスローされ、トーストでエラー表示 | 同時実行競合を早期に通知(本関数では追加の握りつぶしはしない) |
実データ検証
実装前に MCP(or 手動)で確認すべき項目:
32_wrk_invoiceの「請求ステータス」列の実格納値: DDL 定義(InvoiceDTOtypedef の"未処理" | "承認済" | "却下")と実データが一致しているかを確認(failure_patterns #3 の教訓)。過去データに"承認"/"approved"等の揺れがないかチェック。32_wrk_invoiceの「有効フラグ」列の格納値: 実データがTRUE/FALSE(boolean)として格納されているか、文字列"TRUE"/"FALSE"として格納されているかを確認。後者の場合は比較ロジックをdto.有効フラグ !== false && String(dto.有効フラグ) !== 'FALSE'にする必要がある。78_pj_plの「✨ 限界利益」行の存在と列構造:buildProjectProfitability()を実行後、実シート上で A 列に'✨ 限界利益'(絵文字+半角スペース+「限界利益」)が存在するかを目視確認。Unicode レベルで絵文字のコードポイントが一致することを確認(failure_patterns #22 の教訓)。03_sys_paramsへの追加可能性: 既存の行構造(A 列: パラメータ名、B 列: 値)でABC_THRESHOLD_A/ABC_THRESHOLD_Bを追加可能か確認。- 取引先名の表記揺れ: 同一取引先が微妙に異なる表記(末尾スペース・全角半角等)で入力されていないかを確認。F-28 仕様書と同じく、必要なら
String(x).trim()正規化を加える。
関連ドキュメント
docs/_internal/failure_patterns.md- #1 — filterWithRecalcTotal / 加算可否の区別(本案件では利益額は加算可能指標、累積構成比は加算不可指標として扱い、Total 独自計算)
- #2 — ゼロ除算フォールバック(合計利益 ≤ 0 時のエラーメッセージ表示)
- #3 — DDL コード値 vs 実データの乖離(「承認済」文字列を実データで要確認)
- #18-#20 — 仕様書記述の固有名詞誤記(本仕様書は全てのメニュー名・行ラベル・型名を Read で裏取り済)
- #22-#24 — ラベル正規化(
'✨ 限界利益'の絵文字・空白は.replace(/\n/g, '').trim()+ 完全一致比較で安全側に倒す)
docs/dev/dev_F-06_project_overhead_allocation.md—78_pj_plを生成する共通費配賦エンジン。本案件の PJ 別 ABC の精度を左右する前提案件docs/dev/dev_F-24_bep_analysis.md— ゼロ除算対策・エッジケーステーブルの書式例。本案件でも同パターンを踏襲docs/dev/dev_F-03_kpi_dashboard.md— GAS 側でラベル行番号を事前計算してから埋め込む方式(failure_patterns #21-#24 の対策)。本案件の78_pj_plスキャンロジックで流用
人間が検討すべき事項
TODO_future.md F-39 の「人間が検討すべき事項」欄を全文転記したうえで、Phase 1 で発見した追加論点を加える:
TODO_future.md 原文
- ABC の分類対象: PJ / 取引先 / 科目のどれを主軸にするか(本案件では 2 軸併記で対応)
- ランク変動のトレンド表示: 前月比で A → B に落ちた案件の警告(本案件では v1 スコープ外、将来課題)
- F-06(配賦)の精度が ABC 分析の精度を決める: 配賦ロジックに不備があると PJ 別 ABC が歪むため、F-06 仕様書のレビューと並行する
Phase 1 調査で追加された論点
- 計画値 vs 実績値: 現行 v1 は
InvoiceDTO.税抜金額_計画を使用(計画ベースの ABC)。実績ベース(JournalRepository.findAll()の税抜金額_実績)に切り替える設計変更を将来検討。期中の「計画上は A ランクだが実績は B ランク」のギャップ分析は別案件として起票。 78_pj_plの利益列の選択: 本案件では'✨ 限界利益'(売上総利益 − 直接販管費 − 労務費)を使用。代替として'✨ PJ営業利益'(限界利益 − 共通費配賦)や'✨ 売上総利益'も選択可能。ABC 分析の目的(配賦後の真の利益 vs 配賦前の貢献利益)によって使い分ける。運用で利用者の要望を聞いてから切り替える。- 閾値のチューニング: 既定の 70% / 90% はパレート原則の一般値。ビジネスの性質(顧客集中度が高い業種は 60% / 85% の方が実用的な場合もある)によって
03_sys_paramsで調整する運用ガイダンスを別ドキュメント化。 - シート番号の衝突:
69_abc_analysisは F-28 仕様書の69_kpi_saasと番号重複。実装着手時に先行案件と調整し、本案件側を68_abc_analysisに変更する可能性がある。MENU_DEFINITIONの追記とシート生成コードで 1 文字修正するだけで対応可能。 - 取引先別 ABC の「収入 / 支出」混在: 同一取引先に対して「売上」(収入)と「仕入原価」(支出)の両方の INV が存在する場合、本案件の式(収入 − 支出)は正味利益を示す。ただし売上先 A 社と仕入先 B 社で構成される「両側取引先」では、利益額が歪む可能性があるため、運用で「取引先区分」(売上先のみ / 仕入先のみ)を事前フィルタする選択肢も残す。
- パレート曲線の見栄え: GAS の
Charts.ChartType.COMBOは第 2 軸の目盛自動調整が UI 編集と比べて弱い。経営会議資料化時の美装を誰がどのタイミングでやるか(本関数実行後に手動で整える or 将来 Python + matplotlib での別出力)を決める。 - Aランク件数の警告: A ランクが 1 件しかない(極端な集中)、または全件が A ランク(取引先数が少なく分散しない)等の偏りを検知して運用者に通知するロジックの要否。
実装プロンプト(Claude Code 用)
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 F-39「ABC分析(パレート分析)ダッシュボード」を実装してください。
## 実行前タスク(実装開始前に必ず Read で確認)
- `000_infra/002_constants.js`
- `Constants.getParam(key, defaultVal)` L147-167 — 引数の型仕様と戻り値
- `MENU_DEFINITION` L206-324 — 特に `'📋 サイドバー: 📊 マート更新'` カテゴリの items 配列の形式
- `000_infra/003_contracts.js` L38-67
- `InvoiceDTO` の以下プロパティの正確な名称(一字一句):
`有効フラグ` / `請求ステータス` / `収支区分` / `取引先名` / `PJ名` / `税抜金額_計画`
- `請求ステータス` の承認済値は **`'承認済'`**(他の値 `'未処理'` / `'却下'` との三択)
- `200_data/202_repository.js` L149-165
- `InvoiceRepository.findAll()` の戻り値型 `{ headers: string[], dtos: InvoiceDTO[] }` 構造
- `400_domain/420_project_profitability.js` L536-552
- `78_pj_pl` の行ラベル `'✨ 限界利益'` の正確な文字列(絵文字 U+2728 + 半角スペース)
- L621-634 の列構造(A: 表示区分, B: 配賦元合計, C 以降: PJ 列, 最終列: 全社合計)
- `600_report/609_datamart_kpi.js`
- 類似レイヤーの書き込みパターン(`ss.insertSheet` / `sheet.clear` / チャート挿入の流れ)
- `docs/_internal/failure_patterns.md` #2 / #3 / #18-#24 — ゼロ除算・実データ乖離・仕様書記述・ラベル正規化の教訓
## 修正対象ファイル
1. **新規作成**: `600_report/612_datamart_abc_analysis.js`
2. **追記のみ**: `000_infra/002_constants.js`(`MENU_DEFINITION` の `'📋 サイドバー: 📊 マート更新'` カテゴリ `items` 配列末尾に 1 オブジェクト追加。既存コード変更禁止)
## 実装内容
### 1. `600_report/612_datamart_abc_analysis.js` の新規作成
公開関数 `updateAbcAnalysisDashboard()` を実装。以下の骨格で書く:
function updateAbcAnalysisDashboard() {
var FUNC = 'updateAbcAnalysisDashboard';
var lock = LockService.getScriptLock();
try {
lock.waitLock(30000);
var ss = getWebSpreadsheet_();
var sheet = ss.getSheetByName('69_abc_analysis') || ss.insertSheet('69_abc_analysis');
// --- クリア(既存データと既存チャートを全削除) ---
var lastRow = sheet.getMaxRows();
if (lastRow > 1) sheet.getRange(1, 1, lastRow, sheet.getMaxColumns()).clearContent();
sheet.getCharts().forEach(function(c) { sheet.removeChart(c); });
// --- メタ情報 ---
sheet.getRange('A1').setValue('データソース: 32_wrk_invoice(承認済INV) / 78_pj_pl(限界利益行)');
sheet.getRange('A2').setValue('最終更新: ' + Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm'));
var thA = Constants.getParam('ABC_THRESHOLD_A', 0.7);
var thB = Constants.getParam('ABC_THRESHOLD_B', 0.9);
if (!(thA > 0 && thB > thA && thB < 1)) {
Utils.logInfo(FUNC, '閾値の論理矛盾を検知。デフォルト 0.7 / 0.9 にフォールバック');
thA = 0.7; thB = 0.9;
}
// --- 取引先別 ABC ---
var partnerItems = abcBuildPartnerItems_(); // [{name, profit}]
var partnerStartRow = 4;
abcWriteBlock_(sheet, partnerStartRow, '取引先別 ABC(利益貢献度)', partnerItems, thA, thB);
// --- PJ 別 ABC ---
var pjItems = abcBuildPjItems_(ss); // [{name, profit}] or null if 78_pj_pl 未生成
var pjStartRow = partnerStartRow + 4 + partnerItems.length + 5;
abcWriteBlock_(sheet, pjStartRow, 'PJ別 ABC(限界利益ベース)', pjItems, thA, thB);
// --- チャート ---
abcInsertChart_(sheet, partnerStartRow, partnerItems.length, '取引先別パレート図');
abcInsertChart_(sheet, pjStartRow, (pjItems || []).length, 'PJ別パレート図');
Utils.logInfo(FUNC, '69_abc_analysis 更新完了');
} catch (e) {
Utils.logError(FUNC, e);
throw e;
} finally {
try { lock.releaseLock(); } catch (e) {}
}
}
プライベートヘルパー(同ファイル内にトップレベル関数として):
- `abcBuildPartnerItems_()`
- `InvoiceRepository.findAll().dtos` を取得
- フィルタ: `dto.有効フラグ !== false && dto.請求ステータス === '承認済'`
- 取引先別に `Σ収入_税抜金額_計画 − Σ支出_税抜金額_計画` を計算
- 取引先名が空の INV は `'(未分類)'` にまとめる
- 利益 ≤ 0 を除外
- 利益降順、同額時は取引先名昇順でソート
- `[{name, profit}]` 配列を返却
- `abcBuildPjItems_(ss)`
- `ss.getSheetByName('78_pj_pl')` を取得。未生成なら `null` を返却
- `sheet.getDataRange().getValues()` で全件取得
- A 列を走査し `String(row[0]).replace(/\n/g, '').trim() === '✨ 限界利益'` 行を検出
- 検出失敗なら `null` を返却
- 2 行目のヘッダーから C 列以降の PJ 名を取得(列 index 2..length-2、最終列=全社合計は除外)
- 限界利益行の対応列の金額を `Number(val) || 0` でペア化
- PJ 名が空の列はスキップ
- 利益 ≤ 0 を除外 → ソート → 配列返却
- `abcWriteBlock_(sheet, startRow, title, items, thA, thB)`
- `items` が null → `⚠️` メッセージを `sheet.getRange(startRow, 1).setValue(...)` で書き込み、return
- `items` が空 → `'⚠️ 分析対象データがありません(対象 0 件)'` を書き込み return
- `totalProfit = items.reduce((s, it) => s + it.profit, 0)`
- `totalProfit <= 0` → `'⚠️ 分析対象データがありません(合計利益が 0 以下)'` を書き込み return
- タイトル行(startRow)→ ヘッダー行(startRow+1: `['ランク','順位','対象','利益額','構成比','累積構成比']`)→ データ行(startRow+2 以降)を setValues で一括書き込み
- 各行の累積比から `cum < thA ? 'A' : cum < thB ? 'B' : 'C'` でランク判定
- 金額列の number format, 構成比列の `0.0%` format を setNumberFormat で指定
- `abcInsertChart_(sheet, startRow, itemCount, title)`
- `itemCount === 0` ならスキップ
- データ範囲: ヘッダー行(startRow+1)+ データ行の `対象` / `利益額` / `累積構成比` 列
- `sheet.newChart().setChartType(Charts.ChartType.COMBO)`
- `.asComboChart().setTitle(title).setSeriesType(0, 'bars').setSeriesType(1, 'line')` 相当の API を使用
- `sheet.insertChart(chart.build())`
### 2. `000_infra/002_constants.js` への追記
`MENU_DEFINITION` の `'📋 サイドバー: 📊 マート更新'` カテゴリ(Read で L234 付近に実在することを確認)の `items` 配列の末尾に以下 1 オブジェクトを追加する:
{ label: '📊 ABC分析(パレート)更新', funcName: 'updateAbcAnalysisDashboard', description: '取引先別・PJ別の利益貢献度をパレート分析し 69_abc_analysis に出力' },
既存メニュー項目の変更・削除・並び替えは禁止。新規項目の挿入位置は既存 `'📸 前年度P/Lスナップショット'` の直下。
## 制約
- 列名・メニュー名・ステータス値・行ラベルは全て Read で確認した実在する文字列のみ使用(failure_patterns #18-#20・#22-#24)
- `Constants.getParam` の呼び出しは `002_constants.js` で確認した引数形式(key, defaultVal)に従う。第 2 引数が数値の場合は数値が返る
- `101_sys_config.js` / `004_utils.js` / `202_repository.js` / `003_contracts.js` / `420_project_profitability.js` は変更禁止(読み取り専用依存)
- `69_abc_analysis` は DDL スキーマに追加しない(`setupAllSchemas` 実行時のクリアを避けるため関数内で動的生成)
## エッジケース
| 条件 | 動作 |
|------|------|
| 利益額 ≤ 0 の取引先 / PJ | 計算対象から除外 |
| 合計利益 ≤ 0(ゼロ除算) | 当該ブロックに `⚠️ 分析対象データがありません(合計利益が 0 以下)` 表示 |
| `78_pj_pl` 未生成 or `✨ 限界利益` 行が見つからない | PJ 別ブロックに `⚠️` メッセージ、取引先別は通常処理 |
| 承認済 INV 0 件 | 取引先別ブロックに `⚠️ 分析対象データがありません(承認済レコード 0 件)` |
| 同額利益が複数 | 取引先名 / PJ 名の昇順で二次ソート |
| 閾値が `03_sys_params` 未登録 | デフォルト 0.7 / 0.9 で処理継続 |
| 閾値の論理矛盾(thA >= thB) | 警告ログを出してデフォルト 0.7 / 0.9 にフォールバック |
| 取引先名が空 / null | `(未分類)` にまとめる |
| 2 回目以降の実行で旧チャートが残存 | `sheet.getCharts().forEach(removeChart)` で毎回クリア |
## 動作確認
1. `npm run push:dev` で開発用 GAS にデプロイ
2. 開発用スプレッドシートを開き、`📋 サイドバー: 📊 マート更新` メニューに **`📊 ABC分析(パレート)更新`** が表示されることを確認
3. 先に `プロジェクト別 採算` を実行し `78_pj_pl` を生成してから本メニューを実行
4. `69_abc_analysis` シートが自動作成され、取引先別・PJ 別の 2 ブロックが上下に書き込まれ、パレート図が 2 枚挿入されることを確認
5. `03_sys_params` に `ABC_THRESHOLD_A = 0.5` を追加して再実行し、A ランクの件数が減少することを確認(閾値反映の検証)
6. `32_wrk_invoice` で全 INV のステータスを「承認済」以外に変更して実行し、`⚠️ 分析対象データがありません` メッセージが表示されることを確認
7. `78_pj_pl` を削除して実行し、取引先別のみ出力され PJ 別ブロックに警告が表示されることを確認
8. 動作確認が取れてから `git push` → PR 作成
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(Read 調査) | あり | 列名・メニュー名・行ラベル・型を一字一句確定 |
| 実装(コーディング) | なし | 確定済み情報の書き下しに徹する |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| 実行前タスク(Read 調査) | Claude Sonnet 4.6 | 複数ファイル横断の調査と固有名詞の裏取り。判断を要するが複雑な設計判断ではない |
612_datamart_abc_analysis.js 新規作成(骨格 + ヘルパー関数 4 本) | Claude Sonnet 4.6 | 仕様書でロジックが完全定義済み。Read 結果を踏まえた書き下しが中心 |
78_pj_pl 読み取りロジック(abcBuildPjItems_) | Claude Sonnet 4.6 | 動的シートの列構造判定が必要。ヘッダー走査と B 列除外の判定で中程度の判断 |
チャート生成(abcInsertChart_) | Claude Sonnet 4.6 | GAS の EmbeddedComboChartBuilder API に依存。API の細部を確認しながらの実装 |
002_constants.js への MENU_DEFINITION 1 行追記 | Claude Haiku | 挿入位置が明確で判断要素なし |
全体を通してデフォルトは Claude Sonnet 4.6。MENU_DEFINITION 追記のみ Haiku を使ってもよい。
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-21 | 初版作成 |
仕様書作成プロンプト
展開して表示
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. **拡張思考の使い分け**: Phase 1(設計)では拡張思考をフル活用し、ファイル名・関数名・列名・行番号・エッジケース一覧・Step分割粒度を完全に確定させる。Phase 2(清書)の各Step内では拡張思考を最小限に抑え、Phase 1で確定済みの内容の書き下しに徹する。出力途中で再考しない。
2. **テキスト報告の禁止**: 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。説明は1文以内。直ちに tool を呼ぶ。
3. **4-5 分割の Write/Edit 実行**: 仕様書を以下の Step に分割して実行する(1回の Write/Edit は約300行以内):
- 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)
4. **各 Step で何を書くかを具体指示**: 設計判断は Phase 1 で完全に確定させ、Phase 2 は書き下しのみ。途中での再考はストリーム idle timeout の直接原因。
======================================================================
あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
CLIエージェント「Claude Code」として、案件 F-39「ABC分析(パレート分析)ダッシュボード」の開発仕様書を作成してください。
作成後は `docs/_config.json` の `nav` 配列の適切なセクションにも必ず追記してください。
---
## Phase 1: 実行前タスク(テキスト報告禁止。即座にツール実行)
以下を全て Read/Grep で調査し、Phase 2 で設計判断を再考しないよう必要な情報をここで確定させる。
**Grep は「どこにあるか」の発見まで。「どう書くか」の判断は必ず Read。** 名前や記憶から推測した瞬間に手を止めて Read する(失敗パターン #18-#20 の直接対策)。
### 1-A: 案件定義の読み込み
- `docs/_internal/TODO_future.md` で F-39 の要件・期待効果・人間が検討すべき事項を取得。
### 1-B: 関連ファイルの調査(Read で型・固有名詞・呼び出し経路を裏取りする)
1. **`000_infra/002_constants.js`**(`004_utils.js` ではない):
- `Constants.getParam(key, defaultVal)` の実装(`03_sys_params` シートから読み込む)を確認。引数の型と戻り値の型を把握する。
- `SHEET_DEFAULTS` の構造(`{ pattern, prefix, defaults }` オブジェクト配列)を確認し、新シート `69_abc_analysis` を追加すべきか判断する。
2. **`000_infra/003_contracts.js`**:
- `InvoiceDTO` の全プロパティ名を確認(特に「取引先名」「税抜金額_計画」「収支区分」「請求ステータス」「有効フラグ」「PJ名」が実在するかを一字一句正確に確認)。
3. **`200_data/202_repository.js`**:
- `InvoiceRepository.findAll()` の戻り値型(`{ headers: string[], dtos: InvoiceDTO[] }` 構造)と `_getSheet()` のキー文字列(`'WRK_INVC'`)を確認。
4. **`100_config/101_sys_config.js`**:
- `onOpen()` の `ui.createMenu` を Read し、FP&Aレポート系メニューの**正確な文字列**(絵文字含む)を確認。「📊 FP&Aレポート更新」が実在するか、またサブメニューの追加方法(`addItem` の形式)をそのまま引用する。実在しない場合は仕様書に代替案を記載する(造語禁止)。
- `setupAllSchemas` で `69_abc_analysis` を DDL 管理対象に追加すべきか判断する。
5. **`600_report/` ディレクトリ**:
- 既存ファイルの一覧を確認し、使用済みの最大番号を特定する(CLAUDE.md 記載では `601〜608` だが、実態を必ず確認する)。新規ファイル番号は最大番号+1 とする。
6. **`600_report/` 既存ファイルのうち最大番号のもの(例: `608_datamart_render.js`)**:
- `LockService.getScriptLock().waitLock(30000)` と `clearContent()` の実装パターンを確認し、新ファイルで踏襲する。
7. **`78_pj_pl` を生成しているファイル**(`600_report/` 内を Grep で特定してから Read):
- `78_pj_pl` は DDL 管理外の動的生成シートのため、「限界利益」列が実在するかをコード上で確認する。列名が異なる場合は実在する列名を使用する(推測禁止)。
8. **`docs/_internal/failure_patterns.md`**:
- #1(filterWithRecalcTotal / 加算可否の区別)、#2(ゼロ除算フォールバック)、#18-#20(固有名詞の誤記)を確認し、仕様書設計に反映する。
### 1-C: Phase 1 調査結果の確定事項(Phase 2 で再考しない)
Phase 1 終了時点で以下を確定させる:
- 新規 GAS ファイルの正確なファイル名(例: `609_datamart_abc_analysis.js`。番号は調査結果による)
- UIメニューに追加するサブメニューの正確な親メニュー名(`101_sys_config.js` の Read 結果から引用)
- `InvoiceDTO` の「請求ステータス」の承認済を示す正確な文字列(「承認済」を推測ではなく DDL/実データから確認)
- `78_pj_pl` の利益を表す列の正確な列名
---
## Phase 2: 仕様書の分割作成
出力先: `docs/dev/dev_F-39_abc_analysis_dashboard.md`
**【重要】絶対に1回のツール呼び出しで全内容を出力せず、以下の Step に分割して実行すること。**
### Step 2-1: 骨格の作成(File Write、〜20行)
`docs/_internal/dev_spec_prompt_template.md` のセクション構成に従い、見出しのみの骨格ファイルを作成。本文は空で可。必須セクションは以下:
# F-39: ABC分析(パレート分析)ダッシュボード
## 概要
## 目的
## 現在のコード
## 修正方針
## 影響範囲
## 注意事項
## エッジケース
## 実データ検証
## 関連ドキュメント
## 人間が検討すべき事項
## 実装プロンプト(Claude Code 用)
## 推奨実行モデル
## 変更履歴
## 仕様書作成プロンプト
### Step 2-2: 概要〜注意事項の追記(File Edit または Bash、〜300行)
以下の内容を記述する(Phase 1 の調査結果を使用。設計判断の再考禁止):
- **概要テーブル**: 案件ID=F-39、カテゴリ=FP&Aレポート、対象ファイル=新規 `6XX_datamart_abc_analysis.js`(番号は Phase 1 確定値)、対象シート=新規 `69_abc_analysis`、前提案件=なし
- **目的**: 取引先別・PJ別の利益貢献度をパレート原則で可視化し、重点管理対象の特定と経営判断の迅速化を実現する
- **現在のコード**: 該当なし(新規実装)。データソースとして使用する `InvoiceRepository.findAll()`(`200_data/202_repository.js` L数行目)と `78_pj_pl` シートの構造(Phase 1 で確認した列名)を記載
- **修正方針**(以下を全て含める):
- **アーキテクチャ**: 新規シート `69_abc_analysis` を新設。`6XX_datamart_abc_analysis.js` に計算ロジックを実装。既存の `600_report/` 群と同一レイヤーに配置。DDL管理対象外とし、シート未存在時は関数内で `ss.insertSheet('69_abc_analysis')` で生成する
- **取引先別 ABC のデータソースと集計**:
- `InvoiceRepository.findAll().dtos` から `有効フラグ !== false` かつ `請求ステータス === '(Phase 1 で確認した正確な文字列)'` のレコードのみフィルタ
- 取引先ごとに「収支区分 === '収入'」の `税抜金額_計画` 合計 − 「収支区分 === '支出'」の `税抜金額_計画` 合計 を「利益額」として計算
- 利益額 ≤ 0 の取引先は除外してからランク付けを行う
- **PJ別 ABC のデータソース**: `78_pj_pl` シートの「(Phase 1 で確認した正確な列名)」列を参照。同様に利益額 ≤ 0 の PJ は除外
- **ランク付けロジック**:
- 利益額の降順でソート。同額の場合は取引先名/PJ名の昇順で二次ソート
- 累積構成比を計算し、閾値A未満を A ランク、閾値B未満を B ランク、それ以外を C ランクとする
- 閾値は `Constants.getParam('ABC_THRESHOLD_A', 0.7)` / `Constants.getParam('ABC_THRESHOLD_B', 0.9)` で `02_constants.js` の `getParam` 経由で `03_sys_params` から取得(ハードコード禁止)
- **書き込み方式**: 毎回 `sheet.getRange(2, 1, sheet.getMaxRows() - 1, numCols).clearContent()` で既存データをクリアしてから全件再書き込み(追記による二重計上防止)
- **排他ロック**: マート更新関数の冒頭で `LockService.getScriptLock().waitLock(30000)` を `try/finally` 構造で実装(Phase 1 で確認した既存パターンと同形式)
- **データソース情報の記録**: ダッシュボードシート最上部のセル(A1 等)に「データソース: 32_wrk_invoice / 78_pj_pl」「最終更新: Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm')」を書き込む
- **パレート図**: `sheet.newChart()` と `Charts.ChartType.COMBO` を使用した複合グラフを生成(棒グラフ=利益額、折れ線グラフ=累積構成比)。GAS の EmbeddedComboChartBuilder は UI 上の編集機能と比べて制約があるため、視覚スタイルの詳細調整は手動対応とする旨を注意事項に明記
- **UIメニュー**: `101_sys_config.js` の Read 結果で確認した正確な親メニュー名に `addItem('ABC分析 更新', 'updateAbcAnalysisDashboard')` を追加
- **影響範囲**: 新規ファイル追加のため既存ロジックへの影響なし。`101_sys_config.js` のメニュー定義のみ変更(追記のみ)
- **注意事項** 5 項目を明記(本仕様書本文に記載)
### Step 2-3a: エッジケース〜人間検討事項の追記(File Edit または Bash、〜200行)
- **エッジケーステーブル**(`failure_patterns.md` #2 の教訓を反映。テーブル形式で全件記述)
- **実データ検証**(実装前に MCP または手動で確認すべき項目)
- **関連ドキュメント**
- **人間が検討すべき事項**:
- `TODO_future.md` F-39 記載の「人間が検討すべき事項」を全文転記
- ランク変動のトレンド表示は本案件のスコープ外(将来課題)
- データソースとして `InvoiceDTO` の `税抜金額_計画`(計画値)を使用しているが、実績ベースの分析が必要な場合は `JournalRepository.findAll()` の `税抜金額_実績` を使う設計への変更を検討すること
- `78_pj_pl` の利益列が「限界利益」か「営業利益」か「粗利」かによってランク付けの意味が変わる。ABC分析の目的に合った列を選定すること
- ABC 閾値(70%/90%)はビジネスの性質(顧客集中度等)によって変わる。`03_sys_params` での調整を推奨
### Step 2-3b: 実装プロンプト〜変更履歴の追記(File Edit または Bash、〜250行)
**【注意】実装プロンプトはバッククォートで囲まず、行頭4スペースインデントで以下を全て出力すること。**
(実装プロンプト本文。本仕様書「## 実装プロンプト(Claude Code 用)」セクションに記載)
- **推奨実行モデルテーブル**
- **変更履歴テーブル**: `| 2026-04-21 | 初版作成 |`
### Step 2-4: 仕様書作成プロンプトの記録(File Edit または Bash)
末尾の「## 仕様書作成プロンプト」セクションに `<details><summary>展開して表示</summary>` で囲んで全文記録。
---
## Phase 3: 後処理(3-A → 3-B → 3-C の順に実行)
### 3-A: `_config.json` への追記
追記前に必ず `docs/_config.json` を Read し、§E.5 セクションの現在の最終エントリ番号を確認する。
確認後、§E.5 の末尾に追加:
{ "file": "dev/dev_F-39_abc_analysis_dashboard.md", "title": "E.5.X F-39 ABC分析(パレート分析)ダッシュボード" }
(`E.5.X` の番号は Read で確認した最終エントリ番号+1)
### 3-B: changelog への追記
`docs/_internal/changelog.md` の先頭行(ヘッダー直後)に追記:
| 2026-04-21 | [dev_F-39_abc_analysis_dashboard.md](dev_mas-039_abc_analysis_dashboard.md) | 初版作成。取引先別・PJ別 ABC 分析ダッシュボードの設計仕様書 |
### 3-C: コミット&プッシュ(3-A・3-B 完了後)
git add docs/dev/dev_F-39_abc_analysis_dashboard.md docs/_config.json docs/_internal/changelog.md
git commit -m "docs: F-39 ABC分析(パレート分析)ダッシュボードの開発仕様書を作成"
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)。 実装時にファイル番号の再割当が必要。