概要

項目内容
案件IDMAS-039
カテゴリFP&A / レポーティング
PhaseP2
優先度★★
ステータス仕様書完了(未実装)
対象ファイル(新規)600_report/612_datamart_abc_analysis.js
対象ファイル(既存変更)000_infra/002_constants.jsMENU_DEFINITION「📋 サイドバー: 📊 マート更新」カテゴリへ 1 項目追記のみ)
新規シート69_abc_analysisDDL 管理外・関数内で 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.jsInvoiceRepository.findAll() L163-165取引先別 ABC のデータソース。戻り値型 { headers: string[], dtos: InvoiceDTO[] } をそのまま受けて dtos を走査
000_infra/003_contracts.jsInvoiceDTO typedef L38-67必須フィールド: 有効フラグ(boolean)/ 請求ステータス"未処理" | "承認済" | "却下")/ 収支区分"収入" | "支出")/ 取引先名 / PJ名 / 税抜金額_計画 を確認済。いずれも実在
000_infra/002_constants.jsConstants.getParam(key, defaultVal) L147-167閾値 A/B を 03_sys_params から読み取る。第 2 引数が number 型なら Number(val) で数値化、文字列なら String(val) で文字列化される仕様を確認済
400_domain/420_project_profitability.js78_pj_pl 出力ロジック L536-698PJ 別 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.js600_report/ レイヤーに配置する。既存の 601_datamart_ingest.js609_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.jsschemas への追加は行わない(setupAllSchemas 実行時に毎回クリアされると閾値や書式が消失するため)。

取引先別 ABC のデータソースと集計

  1. データ取得: InvoiceRepository.findAll() を呼び出し、{ headers, dtos } のうち dtos: InvoiceDTO[] を対象にする。
  2. フィルタ: 以下の 2 条件を AND で満たす INV のみを ABC 分析対象にする。
    • dto.有効フラグ !== falsetrue および未定義は通す。論理削除行は除外)
    • dto.請求ステータス === '承認済'(Phase 1 で InvoiceDTO typedef の有効値 "未処理" | "承認済" | "却下" を確認済。推測ではなく型注釈から引用)
  3. 取引先別集計: dto.取引先名 をキーとして以下の利益額を計算する。
    利益 = Σ(収支区分 === '収入' の税抜金額_計画)
         − Σ(収支区分 === '支出' の税抜金額_計画)
    
    • 税抜金額_計画Number(dto['税抜金額_計画']) || 0 で非数値は 0 として扱う
    • dto.収支区分'収入' / '支出' 以外(空欄・未知値)の INV は加減算しない(カウントには含めるが利益計算から除外)
  4. 除外: 利益額 ≤ 0 の取引先は ABC 分析対象から除外する(ABC 分析は利益貢献度の順位付けが目的で、赤字・収支トントンの取引先は「別の問題」として切り分ける)。

PJ 別 ABC のデータソース

  1. 読み取り元: ss.getSheetByName('78_pj_pl') の内容を直接読む。DDL 管理外のため sheet.getDataRange().getValues() で全件取得する。
  2. 行特定: A 列(表示区分)を上から走査し、String(cell).replace(/\n/g, '').trim() === '✨ 限界利益' に一致する行を検出する。見つからない場合はシートにエラーメッセージを書き込み中断(78_pj_pl 未生成 or buildProjectProfitability 構造変更の検知)。
  3. PJ 抽出: 2 行目のヘッダーから C 列以降の PJ 名を取得し、限界利益行の同じ列位置の金額とペアリングする。
  4. 除外条件:
    • 最終列(全社合計)は集計対象外
    • B 列(配賦元合計)は集計対象外(個別 PJ ではなく集約値のため)
    • 空欄 PJ 名、'' および null を除外
    • 利益額 ≤ 0 の PJ を除外

ランク付けロジック

  1. ソート: 利益額の降順。利益額が同額の場合は取引先名 / 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);
    });
    
  2. 累積構成比:

    totalProfit = Σ(items.profit)  // フィルタ・除外後の全件合計
    items[i].cumulativeRatio = Σ(items[0..i].profit) / totalProfit
    
  3. 閾値取得:

    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)が使われる。

  4. ランク付け:

    • cumulativeRatio < thA(< 0.7)→ A ランク
    • cumulativeRatio < thB(< 0.9)→ B ランク
    • それ以外(≥ 0.9)→ C ランク
    • ただし、累積比 0.6999... → 0.7 をまたぐ境界上の 1 件は A ランクに含める(厳密な < 比較で運用)

書き込み方式(冪等性)

  1. sheet.getRange(2, 1, Math.max(1, sheet.getMaxRows() - 1), numCols).clearContent()既存データをクリアしてから全件再書き込み(追記による二重計上・差分バグの防止)。
  2. 列数 numCols は取引先別 ABC / PJ 別 ABC の最大列数(7 列想定: ランク / 対象名 / 利益額 / 構成比 / 累積構成比 / 順位 / 備考)。
  3. A1 / A2 セルにメタ情報を書き込む:
    • A1: 'データソース: 32_wrk_invoice(承認済INV) / 78_pj_pl(限界利益行)'
    • A2: '最終更新: ' + Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm')
  4. 取引先別ブロックと 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) { /* 既に解放済なら握りつぶす */ }
  }
}

パレート図(グラフ)

  1. sheet.newChart()Charts.ChartType.COMBO(複合グラフ)で生成。
  2. 系列構成:
    • 棒グラフ(主軸・左): 利益額列
    • 折れ線グラフ(副軸・右): 累積構成比列(0 〜 1.0)
  3. GAS の EmbeddedComboChartBuilder は UI 上の編集機能(色ごとの濃淡指定、第 2 軸の詳細フォーマット等)に比べて表現が制約される。視覚スタイルの微調整は手動対応とし、本スクリプトでは「系列種別の指定(棒 vs 折れ線)」と「基本的な軸ラベル」までを自動化する方針。
  4. 取引先別チャート / PJ 別チャートをそれぞれ 1 枚ずつ、sheet.insertChart(chart) でシート右側に挿入する。既存チャートは初回クリア時点で sheet.getCharts().forEach(function(c) { sheet.removeChart(c); }) にて全削除してから再挿入する(冪等性確保)。

UI メニュー

Phase 1 で 000_infra/002_constants.jsMENU_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() は呼び出さない。

注意事項

  1. Constants.getParam の所在: 000_infra/002_constants.js L147-167 に定義されている(004_utils.js ではない)。Phase 1 で Read 済。
  2. 78_pj_pl は DDL 管理外: 列名・行ラベルは実コード(400_domain/420_project_profitability.js L540-552)を Read して確認した '✨ 限界利益' 固定文字列のみを使用する。推測・造語禁止(failure_patterns #20)。行ラベル比較時は .replace(/\n/g, '').trim() で改行と前後空白を除去する。
  3. 絵文字・全角スペースの取り扱い: '✨ 限界利益' の先頭絵文字は UTF-16 サロゲートペアのため String.lengthindexOf で扱う際に注意。比較は 完全一致===)で行い、includes による部分一致は使わない(failure_patterns #22-#24 の教訓)。
  4. InvoiceDTO.税抜金額_計画 は計画値: 実績ベース分析が必要な場合は JournalRepository.findAll()税抜金額_実績 を使う設計への変更が必要(「人間が検討すべき事項」に記載)。現行 v1 は計画値ベースで固定する。
  5. パレート図の GAS API 制約: Charts.ChartType.COMBO による複合グラフは GAS の EmbeddedComboChartBuilder を使うが、setSeriesType(index, 'bars' / 'line') の指定は効く一方、第 2 軸の目盛範囲の自動調整・色指定・凡例位置の細部は UI 側の微調整が必要。本案件では「動く最低限」を自動化し、美装は手動対応。
  6. 03_sys_params 未登録時の挙動: Constants.getParam('ABC_THRESHOLD_A', 0.7) はキャッシュ機構(_paramsCache)で初回のみ全件読み込み。未登録 or 空文字の場合、第 2 引数のデフォルト(0.7 / 0.9)が返されるため、初期動作は保証される。
  7. チャート再挿入の冪等性: 2 回目以降の実行で旧チャートが残存しないよう、書き込み前に sheet.getCharts() で全チャートを取得して sheet.removeChart() ですべて削除する(無しの状態を保証)。
  8. ロック取得失敗時: waitLock(30000) が 30 秒経っても取得できない場合は例外が飛ぶ。UI メニュー実行中は Google Apps Script の UI 側でエラートーストが表示されるため、本関数では追加のエラーハンドリングは行わない(実行履歴と監査ログに自動記録される)。

エッジケース

failure_patterns #2(ゼロ除算フォールバック)・#3(DDL vs 実データの乖離)の教訓を反映し、異常系の挙動を事前定義する。

条件動作・表示値理由
利益額 ≤ 0 の取引先 / PJABC 計算対象から除外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_B03_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 手動)で確認すべき項目:

  1. 32_wrk_invoice の「請求ステータス」列の実格納値: DDL 定義(InvoiceDTO typedef の "未処理" | "承認済" | "却下")と実データが一致しているかを確認(failure_patterns #3 の教訓)。過去データに "承認" / "approved" 等の揺れがないかチェック。
  2. 32_wrk_invoice の「有効フラグ」列の格納値: 実データが TRUE / FALSE(boolean)として格納されているか、文字列 "TRUE" / "FALSE" として格納されているかを確認。後者の場合は比較ロジックを dto.有効フラグ !== false && String(dto.有効フラグ) !== 'FALSE' にする必要がある。
  3. 78_pj_pl の「✨ 限界利益」行の存在と列構造: buildProjectProfitability() を実行後、実シート上で A 列に '✨ 限界利益'(絵文字+半角スペース+「限界利益」)が存在するかを目視確認。Unicode レベルで絵文字のコードポイントが一致することを確認(failure_patterns #22 の教訓)。
  4. 03_sys_params への追加可能性: 既存の行構造(A 列: パラメータ名、B 列: 値)で ABC_THRESHOLD_A / ABC_THRESHOLD_B を追加可能か確認。
  5. 取引先名の表記揺れ: 同一取引先が微妙に異なる表記(末尾スペース・全角半角等)で入力されていないかを確認。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.md78_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 原文

  1. ABC の分類対象: PJ / 取引先 / 科目のどれを主軸にするか(本案件では 2 軸併記で対応)
  2. ランク変動のトレンド表示: 前月比で A → B に落ちた案件の警告(本案件では v1 スコープ外、将来課題)
  3. F-06(配賦)の精度が ABC 分析の精度を決める: 配賦ロジックに不備があると PJ 別 ABC が歪むため、F-06 仕様書のレビューと並行する

Phase 1 調査で追加された論点

  1. 計画値 vs 実績値: 現行 v1 は InvoiceDTO.税抜金額_計画 を使用(計画ベースの ABC)。実績ベース(JournalRepository.findAll()税抜金額_実績)に切り替える設計変更を将来検討。期中の「計画上は A ランクだが実績は B ランク」のギャップ分析は別案件として起票。
  2. 78_pj_pl の利益列の選択: 本案件では '✨ 限界利益'(売上総利益 − 直接販管費 − 労務費)を使用。代替として '✨ PJ営業利益'(限界利益 − 共通費配賦)や '✨ 売上総利益' も選択可能。ABC 分析の目的(配賦後の真の利益 vs 配賦前の貢献利益)によって使い分ける。運用で利用者の要望を聞いてから切り替える。
  3. 閾値のチューニング: 既定の 70% / 90% はパレート原則の一般値。ビジネスの性質(顧客集中度が高い業種は 60% / 85% の方が実用的な場合もある)によって 03_sys_params で調整する運用ガイダンスを別ドキュメント化。
  4. シート番号の衝突: 69_abc_analysis は F-28 仕様書の 69_kpi_saas と番号重複。実装着手時に先行案件と調整し、本案件側を 68_abc_analysis に変更する可能性がある。MENU_DEFINITION の追記とシート生成コードで 1 文字修正するだけで対応可能。
  5. 取引先別 ABC の「収入 / 支出」混在: 同一取引先に対して「売上」(収入)と「仕入原価」(支出)の両方の INV が存在する場合、本案件の式(収入 − 支出)は正味利益を示す。ただし売上先 A 社と仕入先 B 社で構成される「両側取引先」では、利益額が歪む可能性があるため、運用で「取引先区分」(売上先のみ / 仕入先のみ)を事前フィルタする選択肢も残す。
  6. パレート曲線の見栄え: GAS の Charts.ChartType.COMBO は第 2 軸の目盛自動調整が UI 編集と比べて弱い。経営会議資料化時の美装を誰がどのタイミングでやるか(本関数実行後に手動で整える or 将来 Python + matplotlib での別出力)を決める。
  7. 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.6GAS の EmbeddedComboChartBuilder API に依存。API の細部を確認しながらの実装
002_constants.js への MENU_DEFINITION 1 行追記Claude Haiku挿入位置が明確で判断要素なし

全体を通してデフォルトは Claude Sonnet 4.6MENU_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)。 実装時にファイル番号の再割当が必要。